diff --git a/wallpapers/image/CMakeLists.txt b/wallpapers/image/CMakeLists.txt index 1cdc0756e..10b4878f5 100644 --- a/wallpapers/image/CMakeLists.txt +++ b/wallpapers/image/CMakeLists.txt @@ -1,43 +1,44 @@ add_definitions(-DTRANSLATION_DOMAIN=\"plasma_wallpaper_org.kde.image\") set(image_SRCS image.cpp imageplugin.cpp backgroundlistmodel.cpp slidemodel.cpp + slidefiltermodel.cpp ) ecm_qt_declare_logging_category(image_SRCS HEADER debug.h IDENTIFIER IMAGEWALLPAPER CATEGORY_NAME kde.wallpapers.image DEFAULT_SEVERITY Info) add_library(plasma_wallpaper_imageplugin SHARED ${image_SRCS}) target_link_libraries(plasma_wallpaper_imageplugin Qt5::Core Qt5::Quick Qt5::Qml KF5::Plasma KF5::KIOCore KF5::KIOWidgets KF5::I18n KF5::KIOCore KF5::KIOWidgets # KFileDialog KF5::NewStuff ) if(BUILD_TESTING) add_subdirectory(autotests) endif() install(TARGETS plasma_wallpaper_imageplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/wallpapers/image) install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/wallpapers/image) install(FILES wallpaper.knsrc DESTINATION ${KDE_INSTALL_KNSRCDIR}) plasma_install_package(imagepackage org.kde.image wallpapers wallpaper) plasma_install_package(slideshowpackage org.kde.slideshow wallpapers wallpaper) install(DIRECTORY imagepackage/contents/ui DESTINATION ${PLASMA_DATA_INSTALL_DIR}/wallpapers/org.kde.slideshow/contents PATTERN .svn EXCLUDE PATTERN CMakeLists.txt EXCLUDE PATTERN Messages.sh EXCLUDE) diff --git a/wallpapers/image/backgroundlistmodel.h b/wallpapers/image/backgroundlistmodel.h index 516eb3cae..9ae5a41b5 100644 --- a/wallpapers/image/backgroundlistmodel.h +++ b/wallpapers/image/backgroundlistmodel.h @@ -1,152 +1,152 @@ /*************************************************************************** * Copyright 2007 Paolo Capriotti * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #ifndef BACKGROUNDLISTMODEL_H #define BACKGROUNDLISTMODEL_H #include "image.h" #include #include #include #include #include #include #include #include #include #include class Image; class ImageSizeFinder : public QObject, public QRunnable { Q_OBJECT public: explicit ImageSizeFinder(const QString &path, QObject *parent = nullptr); void run() override; Q_SIGNALS: void sizeFound(const QString &path, const QSize &size); private: QString m_path; }; class BackgroundListModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(int count READ count NOTIFY countChanged) public: enum { AuthorRole = Qt::UserRole, ScreenshotRole, ResolutionRole, PathRole, PackageNameRole, RemovableRole, PendingDeletionRole, ToggleRole }; static const int BLUR_INCREMENT = 9; static const int MARGIN = 6; BackgroundListModel(Image *listener, QObject *parent); ~BackgroundListModel() override; QHash roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override ; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; KPackage::Package package(int index) const; void reload(); void reload(const QStringList &selected); void addBackground(const QString &path); + void removeBackground(const QString &path); Q_INVOKABLE int indexOf(const QString &path) const; virtual bool contains(const QString &bg) const; int count() const {return m_packages.size();} Q_INVOKABLE void openContainingFolder(int rowIndex); Q_INVOKABLE void setPendingDeletion(int rowIndex, bool pendingDeletion); const QStringList wallpapersAwaitingDeletion(); Q_SIGNALS: void countChanged(); protected Q_SLOTS: - void removeBackground(const QString &path); void showPreview(const KFileItem &item, const QPixmap &preview); void previewFailed(const KFileItem &item); void sizeFound(const QString &path, const QSize &s); void backgroundsFound(const QStringList &paths, const QString &token); void processPaths(const QStringList &paths); protected: QPointer m_wallpaper; QString m_findToken; QList m_packages; private: QSize bestSize(const KPackage::Package &package) const; QSet m_removableWallpapers; QHash m_sizeCache; QHash m_previewJobs; KDirWatch m_dirwatch; QCache m_imageCache; int m_screenshotSize; QHash m_pendingDeletion; }; class BackgroundFinder : public QThread { Q_OBJECT public: BackgroundFinder(Image *wallpaper, const QStringList &p); ~BackgroundFinder() override; QString token() const; static QStringList suffixes(); static bool isAcceptableSuffix(const QString &suffix); Q_SIGNALS: void backgroundsFound(const QStringList &paths, const QString &token); protected: void run() override; private: QStringList m_paths; QString m_token; static QMutex s_suffixMutex; static QStringList s_suffixes; }; #endif // BACKGROUNDLISTMODEL_H diff --git a/wallpapers/image/image.cpp b/wallpapers/image/image.cpp index 5d9e4c64f..65c05bdbf 100644 --- a/wallpapers/image/image.cpp +++ b/wallpapers/image/image.cpp @@ -1,929 +1,923 @@ /*************************************************************************** * Copyright 2007 Paolo Capriotti * * Copyright 2007 Aaron Seigo * * Copyright 2008 Petri Damsten * * Copyright 2008 Alexis Ménard * * Copyright 2014 Sebastian Kügler * * Copyright 2015 Kai Uwe Broulik * + * Copyright 2019 David Redondo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "image.h" #include "debug.h" #include #include // FLT_MAX #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backgroundlistmodel.h" #include "slidemodel.h" +#include "slidefiltermodel.h" #include Image::Image(QObject *parent) : QObject(parent), m_ready(false), m_delay(10), m_dirWatch(new KDirWatch(this)), m_mode(SingleImage), + m_slideshowMode(Random), m_currentSlide(-1), m_model(nullptr), - m_slideshowModel(nullptr), + m_slideshowModel(new SlideModel(this, this)), + m_slideFilterModel(new SlideFilterModel(this)), m_dialog(nullptr) { m_wallpaperPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); connect(&m_timer, &QTimer::timeout, this, &Image::nextSlide); connect(m_dirWatch, &KDirWatch::created, this, &Image::pathCreated); connect(m_dirWatch, &KDirWatch::dirty, this, &Image::pathDirty); connect(m_dirWatch, &KDirWatch::deleted, this, &Image::pathDeleted); m_dirWatch->startScan(); + m_slideFilterModel->setSourceModel(m_slideshowModel); + connect(this, &Image::uncheckedSlidesChanged, m_slideFilterModel, &SlideFilterModel::invalidateFilter); + useSingleImageDefaults(); + } Image::~Image() { delete m_dialog; } void Image::classBegin() { } void Image::componentComplete() { // don't bother loading single image until all properties have settled // otherwise we would load a too small image (initial view size) just // to load the proper one afterwards etc etc m_ready = true; if (m_mode == SingleImage) { setSingleImage(); } else if (m_mode == SlideShow) { startSlideshow(); } } QString Image::photosPath() const { return QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } QUrl Image::wallpaperPath() const { return QUrl::fromLocalFile(m_wallpaperPath); } void Image::addUrl(const QString &url) { addUrl(QUrl(url), true); } void Image::addUrls(const QStringList &urls) { bool first = true; for (const QString &url: urls) { // set the first drop as the current paper, just add the rest to the roll addUrl(QUrl(url), first); first = false; } } Image::RenderingMode Image::renderingMode() const { return m_mode; } void Image::setRenderingMode(RenderingMode mode) { if (mode == m_mode) { return; } m_mode = mode; if (m_mode == SlideShow) { if (m_slidePaths.isEmpty()) { m_slidePaths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("share/wallpapers"), QStandardPaths::LocateDirectory); } startSlideshow(); updateDirWatch(m_slidePaths); updateDirWatch(m_slidePaths); } else { // we need to reset the preferred image setSingleImage(); } } +Image::SlideshowMode Image::slideshowMode() const +{ + return m_slideshowMode; +} + +void Image::setSlideshowMode(Image::SlideshowMode mode) +{ + if (mode == m_slideshowMode) { + return; + } + m_slideshowMode = mode; + m_slideFilterModel->setSortingMode(mode); + m_slideFilterModel->sort(0); + if (m_mode == SlideShow) { + startSlideshow(); + } + emit slideshowModeChanged(); +} + float distance(const QSize& size, const QSize& desired) { // compute difference of areas float desiredAspectRatio = ( desired.height() > 0 ) ? desired.width() / (float)desired.height() : 0; float candidateAspectRatio = ( size.height() > 0 ) ? size.width() / (float)size.height() : FLT_MAX; float delta = size.width() - desired.width(); delta = (delta >= 0.0 ? delta : -delta*2 ); // Penalize for scaling up return qAbs(candidateAspectRatio - desiredAspectRatio)*25000 + delta; } QSize resSize(const QString &str) { int index = str.indexOf('x'); if (index != -1) { return QSize(str.leftRef(index).toInt(), str.midRef(index + 1).toInt()); } return QSize(); } QString Image::findPreferedImage(const QStringList &images) { if (images.empty()) { return QString(); } //float targetAspectRatio = (m_targetSize.height() > 0 ) ? m_targetSize.width() / (float)m_targetSize.height() : 0; //qCDebug(IMAGEWALLPAPER) << "wanted" << m_targetSize << "options" << images << "aspect ratio" << targetAspectRatio; float best = FLT_MAX; QString bestImage; foreach (const QString &entry, images) { QSize candidate = resSize(QFileInfo(entry).baseName()); if (candidate == QSize()) { continue; } //float candidateAspectRatio = (candidate.height() > 0 ) ? candidate.width() / (float)candidate.height() : FLT_MAX; float dist = distance(candidate, m_targetSize); //qCDebug(IMAGEWALLPAPER) << "candidate" << candidate << "distance" << dist << "aspect ratio" << candidateAspectRatio; if (bestImage.isEmpty() || dist < best) { bestImage = entry; best = dist; //qCDebug(IMAGEWALLPAPER) << "best" << bestImage; } } //qCDebug(IMAGEWALLPAPER) << "best image" << bestImage; return bestImage; } void Image::findPreferedImageInPackage(KPackage::Package &package) { if (!package.isValid() || !package.filePath("preferred").isEmpty()) { return; } QString preferred = findPreferedImage( package.entryList("images") ); package.removeDefinition("preferred"); package.addFileDefinition("preferred", QStringLiteral("images/") + preferred, i18n("Recommended wallpaper file")); } QSize Image::targetSize() const { return m_targetSize; } void Image::setTargetSize(const QSize &size) { bool sizeChanged = m_targetSize != size; m_targetSize = size; if (m_mode == SingleImage) { if (sizeChanged) { // If screen size was changed, we may want to select a new preferred image // which has correct aspect ratio for the new screen size. m_wallpaperPackage.removeDefinition("preferred"); } setSingleImage(); } if (sizeChanged) { emit targetSizeChanged(); } } KPackage::Package *Image::package() { return &m_wallpaperPackage; } void Image::useSingleImageDefaults() { Plasma::Theme theme; m_wallpaper = theme.wallpaperPath(); int index = m_wallpaper.indexOf(QString::fromLatin1("/contents/images/")); if (index > -1) { // We have file from package -> get path to package m_wallpaper = m_wallpaper.left(index); } } QAbstractItemModel* Image::wallpaperModel() { if (!m_model) { KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); m_usersWallpapers = cfg.readEntry("usersWallpapers", m_usersWallpapers); m_model = new BackgroundListModel(this, this); m_model->reload(m_usersWallpapers); } return m_model; } -QAbstractItemModel* Image::slideshowModel() -{ - if (!m_slideshowModel) { - m_slideshowModel = new SlideModel(this, this); - m_slideshowModel->reload(m_slidePaths); - } - return m_slideshowModel; +QAbstractItemModel * Image::slideFilterModel() { + return m_slideFilterModel; } - int Image::slideTimer() const { return m_delay; } void Image::setSlideTimer(int time) { if (time == m_delay) { return; } m_delay = time; if (m_mode == SlideShow) { updateDirWatch(m_slidePaths); startSlideshow(); } emit slideTimerChanged(); } QStringList Image::usersWallpapers() const { return m_usersWallpapers; } void Image::setUsersWallpapers(const QStringList &usersWallpapers) { if (usersWallpapers == m_usersWallpapers) { return; } m_usersWallpapers = usersWallpapers; emit usersWallpapersChanged(); } QStringList Image::slidePaths() const { return m_slidePaths; } void Image::setSlidePaths(const QStringList &slidePaths) { if (slidePaths == m_slidePaths) { return; } m_slidePaths = slidePaths; m_slidePaths.removeAll(QString()); if (m_slidePaths.isEmpty()) { m_slidePaths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("share/wallpapers"), QStandardPaths::LocateDirectory); } if (m_mode == SlideShow) { updateDirWatch(m_slidePaths); startSlideshow(); } if (m_slideshowModel) { m_slideshowModel->reload(m_slidePaths); } emit slidePathsChanged(); } void Image::showAddSlidePathsDialog() { QFileDialog *dialog = new QFileDialog(nullptr, i18n("Directory with the wallpaper to show slides from"), QString()); dialog->setAttribute(Qt::WA_DeleteOnClose, true ); dialog->setOptions(QFileDialog::ShowDirsOnly); dialog->setAcceptMode(QFileDialog::AcceptOpen); connect(dialog, &QDialog::accepted, this, &Image::addDirFromSelectionDialog); dialog->show(); } void Image::addSlidePath(const QString &path) { if (!path.isEmpty() && !m_slidePaths.contains(path)) { m_slidePaths.append(path); if (m_mode == SlideShow) { updateDirWatch(m_slidePaths); } if (m_slideshowModel) { m_slideshowModel->addDirs({m_slidePaths}); } emit slidePathsChanged(); startSlideshow(); } } void Image::removeSlidePath(const QString &path) { if (m_slidePaths.contains(path)) { m_slidePaths.removeAll(path); if (m_mode == SlideShow) { updateDirWatch(m_slidePaths); } if (m_slideshowModel) { bool haveParent = false; QStringList children; for (const QString& slidePath : m_slidePaths) { if (path.startsWith(slidePath)) { haveParent = true; } if (slidePath.startsWith(path)) { children.append(slidePath); } } /*If we have the parent directory do nothing since the directories are recursively searched. * If we have child directories just reload since removing the parent and then readding the children would * induce a race.*/ if (!haveParent) { if (children.size() > 0) { m_slideshowModel->reload(m_slidePaths); } else { m_slideshowModel->removeDir(path); } } } emit slidePathsChanged(); startSlideshow(); } } void Image::pathDirty(const QString& path) { updateDirWatch(QStringList(path)); } void Image::updateDirWatch(const QStringList &newDirs) { Q_FOREACH(const QString &oldDir, m_dirs) { if(!newDirs.contains(oldDir)) { m_dirWatch->removeDir(oldDir); } } Q_FOREACH(const QString &newDir, newDirs) { if(!m_dirWatch->contains(newDir)) { m_dirWatch->addDir(newDir, KDirWatch::WatchSubDirs | KDirWatch::WatchFiles); } } m_dirs = newDirs; } void Image::addDirFromSelectionDialog() { QFileDialog *dialog = qobject_cast(sender()); if (dialog) { addSlidePath(dialog->directoryUrl().toLocalFile()); } } void Image::syncWallpaperPackage() { m_wallpaperPackage.setPath(m_wallpaper); findPreferedImageInPackage(m_wallpaperPackage); m_wallpaperPath = m_wallpaperPackage.filePath("preferred"); } void Image::setSingleImage() { if (!m_ready) { return; } // supposedly QSize::isEmpty() is true if "either width or height are >= 0" if (!m_targetSize.width() || !m_targetSize.height()) { return; } const QString oldPath = m_wallpaperPath; if (m_wallpaper.isEmpty()) { useSingleImageDefaults(); } QString img; if (QDir::isAbsolutePath(m_wallpaper)) { syncWallpaperPackage(); if (QFile::exists(m_wallpaperPath)) { img = m_wallpaperPath; } } else { //if it's not an absolute path, check if it's just a wallpaper name QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("wallpapers/") + m_wallpaper + QString::fromLatin1("/metadata.json")); if (path.isEmpty()) path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("wallpapers/") + m_wallpaper + QString::fromLatin1("/metadata.desktop")); if (!path.isEmpty()) { QDir dir(path); dir.cdUp(); syncWallpaperPackage(); img = m_wallpaperPath; } } if (img.isEmpty()) { // ok, so the package we have failed to work out; let's try the default useSingleImageDefaults(); syncWallpaperPackage(); } if (m_wallpaperPath != oldPath) { Q_EMIT wallpaperPathChanged(); } } void Image::addUrls(const QList &urls) { bool first = true; Q_FOREACH (const QUrl &url, urls) { // set the first drop as the current paper, just add the rest to the roll addUrl(url, first); first = false; } } void Image::addUrl(const QUrl &url, bool setAsCurrent) { QString path; if (url.isLocalFile()) { path = url.toLocalFile(); } else if (url.scheme().isEmpty()) { if (QDir::isAbsolutePath(url.path())) { path = url.path(); } else { path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("wallpapers/") + url.path(), QStandardPaths::LocateDirectory); } if (path.isEmpty()) { return; } } else { QString wallpaperPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("wallpapers/") + url.path(); if (!wallpaperPath.isEmpty()) { KIO::FileCopyJob *job = KIO::file_copy(url, QUrl(wallpaperPath), -1, KIO::HideProgressInfo); if (setAsCurrent) { connect(job, &KJob::result, this, &Image::setWallpaperRetrieved); } else { connect(job, &KJob::result, this, &Image::addWallpaperRetrieved); } } return; } if (setAsCurrent) { setWallpaper(path); } else { if (m_mode != SingleImage) { // it's a slide show, add it to the slide show - m_slideshowBackgrounds.append(path); - m_unseenSlideshowBackgrounds.append(path); + m_slideshowModel->addBackground(path); } // always add it to the user papers, though addUsersWallpaper(path); } } void Image::setWallpaperRetrieved(KJob *job) { KIO::FileCopyJob *copyJob = qobject_cast(job); if (copyJob && !copyJob->error()) { setWallpaper(copyJob->destUrl().toLocalFile()); } } void Image::addWallpaperRetrieved(KJob *job) { KIO::FileCopyJob *copyJob = qobject_cast(job); if (copyJob && !copyJob->error()) { addUrl(copyJob->destUrl(), false); } } void Image::setWallpaper(const QString &path) { if (m_mode == SingleImage) { m_wallpaper = path; setSingleImage(); } else { - m_slideshowBackgrounds.append(path); - m_unseenSlideshowBackgrounds.clear(); - m_currentSlide = m_slideshowBackgrounds.size() - 2; + m_wallpaper = path; + m_slideshowModel->addBackground(path); + m_currentSlide = m_slideFilterModel->indexOf(path) - 1; nextSlide(); } //addUsersWallpaper(path); } void Image::startSlideshow() { - if (!m_ready) { + if (!m_ready || m_slideFilterModel->property("usedInConfig").toBool()) { return; } - - if(m_findToken.isEmpty()) { - // populate background list - m_timer.stop(); - m_slideshowBackgrounds.clear(); - m_unseenSlideshowBackgrounds.clear(); - BackgroundFinder *finder = new BackgroundFinder(this, m_dirs); - m_findToken = finder->token(); - connect(finder, &BackgroundFinder::backgroundsFound, this, &Image::backgroundsFound); - finder->start(); - //TODO: what would be cool: paint on the wallpaper itself a busy widget and perhaps some text - //about loading wallpaper slideshow while the thread runs - } else { - m_scanDirty = true; - } + // populate background list + m_timer.stop(); + m_slideshowModel->reload(m_slidePaths); + connect(m_slideshowModel, &SlideModel::done, this, &Image::backgroundsFound); + //TODO: what would be cool: paint on the wallpaper itself a busy widget and perhaps some text + //about loading wallpaper slideshow while the thread runs } -void Image::backgroundsFound(const QStringList &paths, const QString &token) +void Image::backgroundsFound() { - if (token != m_findToken) { - return; - } - - m_findToken.clear(); + disconnect(m_slideshowModel, &SlideModel::done, this, 0); if(m_scanDirty) { m_scanDirty = false; startSlideshow(); return; } - m_slideshowBackgrounds = paths; - for(const QString &slide : qAsConst(m_uncheckedSlides)) { - m_slideshowBackgrounds.removeAll(QUrl(slide).path()); - } - m_unseenSlideshowBackgrounds.clear(); + // start slideshow - if (m_slideshowBackgrounds.isEmpty()) { + if (m_slideFilterModel->rowCount() == 0) { // no image has been found, which is quite weird... try again later (this is useful for events which // are not detected by KDirWatch, like a NFS directory being mounted) QTimer::singleShot(1000, this, &Image::startSlideshow); } else { - m_currentSlide = -1; + if (m_currentSlide == -1 && m_slideshowMode != Random) { + m_currentSlide = m_slideFilterModel->indexOf(m_wallpaper) - 1; + } else { + m_currentSlide = -1; + } + m_slideFilterModel->sort(0); nextSlide(); m_timer.start(m_delay * 1000); } } void Image::getNewWallpaper(QQuickItem *ctx) { if (!m_newStuffDialog) { m_newStuffDialog = new KNS3::DownloadDialog( QString::fromLatin1("wallpaper.knsrc") ); KNS3::DownloadDialog *strong = m_newStuffDialog.data(); strong->setTitle(i18n("Download Wallpapers")); connect(m_newStuffDialog.data(), &QDialog::accepted, this, &Image::newStuffFinished); } if (ctx && ctx->window()) { m_newStuffDialog->setWindowModality(Qt::WindowModal); m_newStuffDialog->winId(); // so it creates the windowHandle(); m_newStuffDialog->windowHandle()->setTransientParent(ctx->window()); } m_newStuffDialog.data()->show(); } void Image::newStuffFinished() { if (m_model && (!m_newStuffDialog || m_newStuffDialog.data()->changedEntries().size() > 0)) { m_model->reload(m_usersWallpapers); } } void Image::showFileDialog() { if (!m_dialog) { QUrl baseUrl; if(m_wallpaper.indexOf(QDir::homePath()) > -1){ baseUrl = QUrl(m_wallpaper); } /* m_dialog = new KFileDialog(baseUrl, QString::fromLatin1("*.png *.jpeg *.jpg *.xcf *.svg *.svgz *.bmp"), 0); m_dialog->setOperationMode(KFileDialog::Opening); m_dialog->setInlinePreviewShown(true); m_dialog->setModal(false); connect(m_dialog, SIGNAL(okClicked()), this, SLOT(wallpaperBrowseCompleted())); connect(m_dialog, SIGNAL(destroyed(QObject*)), this, SLOT(fileDialogFinished())); */ QString path; const QStringList &locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); if (!locations.isEmpty()) { path = locations.at(0); } else { // HomeLocation is guaranteed not to be empty. path = QStandardPaths::standardLocations(QStandardPaths::HomeLocation).at(0); } QMimeDatabase db; QStringList imageGlobPatterns; foreach(const QByteArray &mimeType, QImageReader::supportedMimeTypes()) { QMimeType mime(db.mimeTypeForName(mimeType)); imageGlobPatterns << mime.globPatterns(); } m_dialog = new QFileDialog(nullptr, i18n("Open Image"), path, i18n("Image Files") + " ("+imageGlobPatterns.join(' ') + ')'); //i18n people, this isn't a "word puzzle". there is a specific string format for QFileDialog::setNameFilters m_dialog->setFileMode(QFileDialog::ExistingFiles); connect(m_dialog, &QDialog::accepted, this, &Image::wallpaperBrowseCompleted); } m_dialog->show(); m_dialog->raise(); m_dialog->activateWindow(); } void Image::fileDialogFinished() { m_dialog = nullptr; } void Image::wallpaperBrowseCompleted() { Q_ASSERT(m_model); if (m_dialog && m_dialog->selectedFiles().count() > 0) { for (const QString image : m_dialog->selectedFiles()) { addUsersWallpaper(image); } emit customWallpaperPicked(m_dialog->selectedFiles().first()); } } void Image::addUsersWallpaper(const QString &file) { QString f = file; f.remove(QLatin1String("file:/")); const QFileInfo info(f); // FIXME //the full file path, so it isn't broken when dealing with symlinks const QString wallpaper = info.canonicalFilePath(); if (wallpaper.isEmpty()) { return; } if (m_model) { if (m_model->contains(wallpaper)) { return; } // add background to the model m_model->addBackground(wallpaper); } // save it KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); m_usersWallpapers = cfg.readEntry("usersWallpapers", m_usersWallpapers); if (!m_usersWallpapers.contains(wallpaper)) { m_usersWallpapers.prepend(wallpaper); cfg.writeEntry("usersWallpapers", m_usersWallpapers); cfg.sync(); emit usersWallpapersChanged(); } } void Image::nextSlide() { - if (!m_ready || m_slideshowBackgrounds.isEmpty()) { + if (!m_ready || m_slideFilterModel->rowCount() == 0) { return; } - - QString previousPath; - if (m_currentSlide > -1 && m_currentSlide < m_unseenSlideshowBackgrounds.size()) { - previousPath = m_unseenSlideshowBackgrounds.takeAt(m_currentSlide); + int previousSlide = m_currentSlide; + QUrl previousPath = m_slideFilterModel->index(m_currentSlide, 0).data(BackgroundListModel::PathRole).toUrl(); + if (m_currentSlide == m_slideFilterModel->rowCount() - 1 || m_currentSlide < 0) { + m_currentSlide = 0; + } else { + m_currentSlide += 1; } - - if (m_unseenSlideshowBackgrounds.isEmpty()) { - m_unseenSlideshowBackgrounds = m_slideshowBackgrounds; - - // We're filling the queue again, make sure we can't pick up again - // the last one picked from the previous set - if (!previousPath.isEmpty()) { - m_unseenSlideshowBackgrounds.removeAll(previousPath); - - // prevent empty list - if (m_unseenSlideshowBackgrounds.isEmpty()) { - m_unseenSlideshowBackgrounds = m_slideshowBackgrounds; - } - } + //We are starting again - avoid having the same random order when we restart the slideshow + if (m_slideshowMode == Random && previousSlide == m_slideFilterModel->rowCount() - 1) { + m_slideFilterModel->invalidate(); + } + QUrl next = m_slideFilterModel->index(m_currentSlide, 0).data(BackgroundListModel::PathRole).toUrl(); + // And avoid showing the same picture twice + if (previousSlide == m_slideFilterModel->rowCount() - 1 && previousPath == next && m_slideFilterModel->rowCount() > 1) { + m_currentSlide += 1; + next = m_slideFilterModel->index(m_currentSlide, 0).data(BackgroundListModel::PathRole).toUrl(); } - - m_currentSlide = KRandom::random() % m_unseenSlideshowBackgrounds.size(); - const QString currentPath = m_unseenSlideshowBackgrounds.at(m_currentSlide); - - m_wallpaperPackage.setPath(currentPath); - findPreferedImageInPackage(m_wallpaperPackage); - m_timer.stop(); m_timer.start(m_delay * 1000); - - QString current = m_wallpaperPackage.filePath("preferred"); - if (current.isEmpty()) { - m_wallpaperPath = currentPath; + if (next.isEmpty()) { + m_wallpaperPath = previousPath.toLocalFile(); } else { - m_wallpaperPath = current; + m_wallpaperPath = next.toLocalFile(); } - Q_EMIT wallpaperPathChanged(); } void Image::openSlide() { if (!m_wallpaperPackage.isValid()) { return; } // open in image viewer QUrl filepath(m_wallpaperPackage.filePath("preferred")); qCDebug(IMAGEWALLPAPER) << "opening file " << filepath.path(); new KRun(filepath, nullptr); } void Image::pathCreated(const QString &path) { - if(!m_slideshowBackgrounds.contains(path)) { + if(m_slideshowModel->indexOf(path) == -1) { QFileInfo fileInfo(path); if(fileInfo.isFile() && BackgroundFinder::isAcceptableSuffix(fileInfo.suffix())) { - m_slideshowBackgrounds.append(path); - m_unseenSlideshowBackgrounds.append(path); - if(m_slideshowBackgrounds.count() == 1) { + m_slideshowModel->addBackground(path); + if(m_slideFilterModel->rowCount() == 1) { nextSlide(); } } } } void Image::pathDeleted(const QString &path) { - if(m_slideshowBackgrounds.removeAll(path)) { - m_unseenSlideshowBackgrounds.removeAll(path); + if(m_slideshowModel->indexOf(path) != -1) { + m_slideshowModel->removeBackground(path); if(path == m_img) { nextSlide(); } } } //FIXME: we have to save the configuration also when the dialog cancel button is clicked. void Image::removeWallpaper(QString name) { QString localWallpapers = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/wallpapers/"; QUrl nameUrl(name); //Package plugin name if (!name.contains('/')) { KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); KJob *j = p.uninstall(name, localWallpapers); connect(j, &KJob::finished, [=] () { m_model->reload(m_usersWallpapers); }); //absolute path in the home } else if (nameUrl.path().startsWith(localWallpapers)) { QFile f(nameUrl.path()); if (f.exists()) { f.remove(); } m_model->reload(m_usersWallpapers); } else { // save it KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), QStringLiteral("Wallpapers")); m_usersWallpapers = cfg.readEntry("usersWallpapers", m_usersWallpapers); int wallpaperIndex = -1; //passed as a path or as a file:// url? if (nameUrl.isValid()) { wallpaperIndex = m_usersWallpapers.indexOf(nameUrl.path()); } else { wallpaperIndex = m_usersWallpapers.indexOf(name); } if (wallpaperIndex >= 0){ m_usersWallpapers.removeAt(wallpaperIndex); m_model->reload(m_usersWallpapers); cfg.writeEntry("usersWallpapers", m_usersWallpapers); cfg.sync(); emit usersWallpapersChanged(); Q_EMIT settingsChanged(true); } } } void Image::commitDeletion() { //This is invokable from qml, so at any moment //we can't be sure the model exists if (!m_model) { return; } for (const QString &wallpaperCandidate : m_model->wallpapersAwaitingDeletion()) { removeWallpaper(wallpaperCandidate); } } void Image::openFolder(const QString& path) { new KRun(QUrl::fromLocalFile(path), nullptr); } void Image::toggleSlide(const QString& path, bool checked) { if (checked && m_uncheckedSlides.contains(path)) { m_uncheckedSlides.removeAll(path); emit uncheckedSlidesChanged(); startSlideshow(); } else if (!checked && ! m_uncheckedSlides.contains(path)) { m_uncheckedSlides.append(path); emit uncheckedSlidesChanged(); startSlideshow(); } } QStringList Image::uncheckedSlides() const { return m_uncheckedSlides; } void Image::setUncheckedSlides(const QStringList &uncheckedSlides) { if (uncheckedSlides == m_uncheckedSlides) { return; } m_uncheckedSlides = uncheckedSlides; emit uncheckedSlidesChanged(); startSlideshow(); } diff --git a/wallpapers/image/image.h b/wallpapers/image/image.h index e3526a413..228e3d599 100644 --- a/wallpapers/image/image.h +++ b/wallpapers/image/image.h @@ -1,200 +1,217 @@ /*************************************************************************** * Copyright 2007 Paolo Capriotti * * Copyright 2008 by Petri Damsten * * Copyright 2014 Sebastian Kügler * * Copyright 2015 Kai Uwe Broulik * + * Copyright 2019 David Redondo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #ifndef IMAGE_HEADER #define IMAGE_HEADER + #include #include #include #include #include #include #include #include #include class QFileDialog; class QQuickItem; class KDirWatch; class KJob; namespace KNS3 { class DownloadDialog; } class BackgroundListModel; class SlideModel; +class SlideFilterModel; class Image : public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(RenderingMode renderingMode READ renderingMode WRITE setRenderingMode NOTIFY renderingModeChanged) + Q_PROPERTY(SlideshowMode slideshowMode READ slideshowMode WRITE setSlideshowMode NOTIFY slideshowModeChanged) Q_PROPERTY(QUrl wallpaperPath READ wallpaperPath NOTIFY wallpaperPathChanged) Q_PROPERTY(QAbstractItemModel *wallpaperModel READ wallpaperModel CONSTANT) - Q_PROPERTY(QAbstractItemModel *slideshowModel READ slideshowModel CONSTANT) + Q_PROPERTY(QAbstractItemModel *slideFilterModel READ slideFilterModel CONSTANT) Q_PROPERTY(int slideTimer READ slideTimer WRITE setSlideTimer NOTIFY slideTimerChanged) Q_PROPERTY(QStringList usersWallpapers READ usersWallpapers WRITE setUsersWallpapers NOTIFY usersWallpapersChanged) Q_PROPERTY(QStringList slidePaths READ slidePaths WRITE setSlidePaths NOTIFY slidePathsChanged) Q_PROPERTY(QSize targetSize READ targetSize WRITE setTargetSize NOTIFY targetSizeChanged) Q_PROPERTY(QString photosPath READ photosPath CONSTANT) Q_PROPERTY(QStringList uncheckedSlides READ uncheckedSlides WRITE setUncheckedSlides NOTIFY uncheckedSlidesChanged) public: enum RenderingMode { SingleImage, SlideShow }; Q_ENUM(RenderingMode) + enum SlideshowMode { + Random, + Alphabetical, + AlphabeticalReversed, + Modified, + ModifiedReversed + }; + Q_ENUM(SlideshowMode) + explicit Image(QObject* parent = nullptr); ~Image() override; QUrl wallpaperPath() const; //this is for QML use Q_INVOKABLE void addUrl(const QString &url); Q_INVOKABLE void addUrls(const QStringList &urls); Q_INVOKABLE void addSlidePath(const QString &path); Q_INVOKABLE void removeSlidePath(const QString &path); Q_INVOKABLE void openFolder(const QString& path); Q_INVOKABLE void getNewWallpaper(QQuickItem *ctx = nullptr); Q_INVOKABLE void showFileDialog(); Q_INVOKABLE void addUsersWallpaper(const QString &file); Q_INVOKABLE void commitDeletion(); Q_INVOKABLE void toggleSlide(const QString &path, bool checked); RenderingMode renderingMode() const; void setRenderingMode(RenderingMode mode); + SlideshowMode slideshowMode() const; + void setSlideshowMode(SlideshowMode mode); + QSize targetSize() const; void setTargetSize(const QSize &size); KPackage::Package *package(); QAbstractItemModel* wallpaperModel(); - QAbstractItemModel* slideshowModel(); + QAbstractItemModel* slideFilterModel(); int slideTimer() const; void setSlideTimer(int time); QStringList usersWallpapers() const; void setUsersWallpapers(const QStringList &usersWallpapers); QStringList slidePaths() const; void setSlidePaths(const QStringList &slidePaths); void findPreferedImageInPackage(KPackage::Package &package); QString findPreferedImage(const QStringList &images); void classBegin() override; void componentComplete() override; QString photosPath() const; QStringList uncheckedSlides() const; void setUncheckedSlides(const QStringList &uncheckedSlides); public Q_SLOTS: void nextSlide(); void removeWallpaper(QString name); Q_SIGNALS: void settingsChanged(bool); void wallpaperPathChanged(); void renderingModeChanged(); + void slideshowModeChanged(); void targetSizeChanged(); void slideTimerChanged(); void usersWallpapersChanged(); void slidePathsChanged(); void resizeMethodChanged(); void customWallpaperPicked(const QString &path); void uncheckedSlidesChanged(); protected Q_SLOTS: void showAddSlidePathsDialog(); void wallpaperBrowseCompleted(); /** * Open the current slide in the default image application */ void openSlide(); void startSlideshow(); void fileDialogFinished(); void addUrl(const QUrl &url, bool setAsCurrent); void addUrls(const QList &urls); void setWallpaper(const QString &path); void setWallpaperRetrieved(KJob *job); void addWallpaperRetrieved(KJob *job); void newStuffFinished(); void updateDirWatch(const QStringList &newDirs); void addDirFromSelectionDialog(); void pathCreated(const QString &path); void pathDeleted(const QString &path); void pathDirty(const QString &path); - void backgroundsFound(const QStringList &paths, const QString &token); + void backgroundsFound(); protected: void syncWallpaperPackage(); void setSingleImage(); void useSingleImageDefaults(); private: - bool m_ready; int m_delay; QStringList m_dirs; QString m_wallpaper; QString m_wallpaperPath; QStringList m_usersWallpapers; KDirWatch *m_dirWatch; bool m_scanDirty; QSize m_targetSize; RenderingMode m_mode; + SlideshowMode m_slideshowMode; + KPackage::Package m_wallpaperPackage; - QStringList m_slideshowBackgrounds; - QStringList m_unseenSlideshowBackgrounds; QStringList m_slidePaths; QStringList m_uncheckedSlides; QTimer m_timer; int m_currentSlide; BackgroundListModel *m_model; SlideModel* m_slideshowModel; + SlideFilterModel* m_slideFilterModel; QFileDialog *m_dialog; QString m_img; QDateTime m_previousModified; QPointer m_newStuffDialog; QString m_findToken; }; #endif diff --git a/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml b/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml index 8206f77c2..8d22956bd 100644 --- a/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml +++ b/wallpapers/image/imagepackage/contents/ui/WallpaperDelegate.qml @@ -1,132 +1,134 @@ /* * Copyright 2013 Marco Martin * Copyright 2014 Sebastian Kügler * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.0 import QtQuick.Controls.Private 1.0 import QtQuick.Controls 2.3 as QtControls2 import QtGraphicalEffects 1.0 import org.kde.kquickcontrolsaddons 2.0 import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kirigami 2.4 as Kirigami import org.kde.kcm 1.1 as KCM KCM.GridDelegate { id: wallpaperDelegate property alias color: backgroundRect.color property bool selected: (wallpapersGrid.currentIndex === index) opacity: model.pendingDeletion ? 0.5 : 1 text: model.display toolTip: model.author.length > 0 ? i18ndc("plasma_wallpaper_org.kde.image", " by ", "By %1", model.author) : "" hoverEnabled: true actions: [ Kirigami.Action { icon.name: "document-open-folder" tooltip: i18nd("plasma_wallpaper_org.kde.image", "Open Containing Folder") onTriggered: imageModel.openContainingFolder(index) }, Kirigami.Action { icon.name: "edit-undo" visible: model.pendingDeletion tooltip: i18nd("plasma_wallpaper_org.kde.image", "Restore wallpaper") onTriggered: imageModel.setPendingDeletion(index, !model.pendingDeletion) }, Kirigami.Action { icon.name: "edit-delete" tooltip: i18nd("plasma_wallpaper_org.kde.image", "Remove Wallpaper") visible: model.removable && !model.pendingDeletion && configDialog.currentWallpaper == "org.kde.image" onTriggered: { imageModel.setPendingDeletion(index, true); if (wallpapersGrid.currentIndex === index) { - wallpapersGrid.currentIndex = (index + 1) % wallpapersGrid.count; + wallpapersGrid.currentIndex = (index + 1) % wallpapersGrid.rowCount(); } } } ] thumbnail: Rectangle { id: backgroundRect color: cfg_Color anchors.fill: parent QIconItem { anchors.centerIn: parent width: units.iconSizes.large height: width icon: "view-preview" visible: !walliePreview.visible } QPixmapItem { id: blurBackgroundSource visible: cfg_Blur anchors.fill: parent smooth: true pixmap: model.screenshot fillMode: QPixmapItem.PreserveAspectCrop } FastBlur { visible: cfg_Blur anchors.fill: parent source: blurBackgroundSource radius: 4 } QPixmapItem { id: walliePreview anchors.fill: parent visible: model.screenshot !== null smooth: true pixmap: model.screenshot fillMode: { if (cfg_FillMode == Image.Stretch) { return QPixmapItem.Stretch; } else if (cfg_FillMode == Image.PreserveAspectFit) { return QPixmapItem.PreserveAspectFit; } else if (cfg_FillMode == Image.PreserveAspectCrop) { return QPixmapItem.PreserveAspectCrop; } else if (cfg_FillMode == Image.Tile) { return QPixmapItem.Tile; } else if (cfg_FillMode == Image.TileVertically) { return QPixmapItem.TileVertically; } else if (cfg_FillMode == Image.TileHorizontally) { return QPixmapItem.TileHorizontally; } return QPixmapItem.PreserveAspectFit; } } QtControls2.CheckBox { visible: configDialog.currentWallpaper == "org.kde.slideshow" anchors.right: parent.right anchors.top: parent.top checked: visible ? model.checked : false onToggled: imageWallpaper.toggleSlide(model.path, checked) } } onClicked: { - cfg_Image = model.path; - wallpapersGrid.forceActiveFocus(); + if (configDialog.currentWallpaper == "org.kde.image") { + cfg_Image = model.path; + } + view.currentIndex = index; } } diff --git a/wallpapers/image/imagepackage/contents/ui/config.qml b/wallpapers/image/imagepackage/contents/ui/config.qml index cdab89982..a7442eb23 100644 --- a/wallpapers/image/imagepackage/contents/ui/config.qml +++ b/wallpapers/image/imagepackage/contents/ui/config.qml @@ -1,324 +1,374 @@ /* * Copyright 2013 Marco Martin * Copyright 2014 Kai Uwe Broulik + * Copyright 2019 David Redondo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.5 import QtQuick.Controls 2.3 as QtControls2 import QtQuick.Layouts 1.0 import QtQuick.Window 2.0 // for Screen import org.kde.plasma.wallpapers.image 2.0 as Wallpaper import org.kde.kquickcontrols 2.0 as KQuickControls import org.kde.kquickcontrolsaddons 2.0 import org.kde.kconfig 1.0 // for KAuthorized import org.kde.draganddrop 2.0 as DragDrop import org.kde.kcm 1.1 as KCM import org.kde.kirigami 2.5 as Kirigami ColumnLayout { id: root property alias cfg_Color: colorButton.color property string cfg_Image property int cfg_FillMode + property int cfg_SlideshowMode property alias cfg_Blur: blurRadioButton.checked property var cfg_SlidePaths: "" property int cfg_SlideInterval: 0 property var cfg_UncheckedSlides: [] function saveConfig() { imageWallpaper.commitDeletion(); } SystemPalette { id: syspal } Wallpaper.Image { id: imageWallpaper targetSize: { if (typeof plasmoid !== "undefined") { return Qt.size(plasmoid.width, plasmoid.height) } // Lock screen configuration case return Qt.size(Screen.width, Screen.height) } onSlidePathsChanged: cfg_SlidePaths = slidePaths onUncheckedSlidesChanged: cfg_UncheckedSlides = uncheckedSlides + onSlideshowModeChanged: cfg_SlideshowMode = slideshowMode } onCfg_SlidePathsChanged: { imageWallpaper.slidePaths = cfg_SlidePaths } onCfg_UncheckedSlidesChanged: { imageWallpaper.uncheckedSlides = cfg_UncheckedSlides } + onCfg_SlideshowModeChanged: { + imageWallpaper.slideshowMode = cfg_SlideshowMode + } + property int hoursIntervalValue: Math.floor(cfg_SlideInterval / 3600) property int minutesIntervalValue: Math.floor(cfg_SlideInterval % 3600) / 60 property int secondsIntervalValue: cfg_SlideInterval % 3600 % 60 //Rectangle { color: "orange"; x: formAlignment; width: formAlignment; height: 20 } Kirigami.FormLayout { twinFormLayouts: parentLayout QtControls2.ComboBox { id: resizeComboBox Kirigami.FormData.label: i18nd("plasma_wallpaper_org.kde.image", "Positioning:") model: [ { 'label': i18nd("plasma_wallpaper_org.kde.image", "Scaled and Cropped"), 'fillMode': Image.PreserveAspectCrop }, { 'label': i18nd("plasma_wallpaper_org.kde.image","Scaled"), 'fillMode': Image.Stretch }, { 'label': i18nd("plasma_wallpaper_org.kde.image","Scaled, Keep Proportions"), 'fillMode': Image.PreserveAspectFit }, { 'label': i18nd("plasma_wallpaper_org.kde.image", "Centered"), 'fillMode': Image.Pad }, { 'label': i18nd("plasma_wallpaper_org.kde.image","Tiled"), 'fillMode': Image.Tile } ] textRole: "label" onCurrentIndexChanged: cfg_FillMode = model[currentIndex]["fillMode"] Component.onCompleted: setMethod(); function setMethod() { for (var i = 0; i < model.length; i++) { if (model[i]["fillMode"] === wallpaper.configuration.FillMode) { resizeComboBox.currentIndex = i; var tl = model[i]["label"].length; //resizeComboBox.textLength = Math.max(resizeComboBox.textLength, tl+5); } } } } + QtControls2.ComboBox { + id: slideshowComboBox + visible: configDialog.currentWallpaper == "org.kde.slideshow" + Kirigami.FormData.label: i18nd("plasma_wallpaper_org.kde.image", "Order:") + model: [ + { + 'label': i18nd("plasma_wallpaper_org.kde.image", "Random"), + 'slideshowMode': Wallpaper.Image.Random + }, + { + 'label': i18nd("plasma_wallpaper_org.kde.image", "A to Z"), + 'slideshowMode': Wallpaper.Image.Alphabetical + }, + { + 'label': i18nd("plasma_wallpaper_org.kde.image", "Z to A"), + 'slideshowMode': Wallpaper.Image.AlphabeticalReversed + }, + { + 'label': i18nd("plasma_wallpaper_org.kde.image", "Date modified (newest first)"), + 'slideshowMode': Wallpaper.Image.ModifiedReversed + }, + { + 'label': i18nd("plasma_wallpaper_org.kde.image", "Date modified (oldest first)"), + 'slideshowMode': Wallpaper.Image.Modified + } + ] + textRole: "label" + onCurrentIndexChanged: { + cfg_SlideshowMode = model[currentIndex]["slideshowMode"]; + } + Component.onCompleted: setMethod(); + function setMethod() { + for (var i = 0; i < model.length; i++) { + if (model[i]["slideshowMode"] === wallpaper.configuration.SlideshowMode) { + slideshowComboBox.currentIndex = i; + } + } + } + } + QtControls2.ButtonGroup { id: backgroundGroup } QtControls2.RadioButton { id: blurRadioButton visible: cfg_FillMode === Image.PreserveAspectFit || cfg_FillMode === Image.Pad Kirigami.FormData.label: i18nd("plasma_wallpaper_org.kde.image", "Background:") text: i18nd("plasma_wallpaper_org.kde.image", "Blur") QtControls2.ButtonGroup.group: backgroundGroup } RowLayout { id: colorRow visible: cfg_FillMode === Image.PreserveAspectFit || cfg_FillMode === Image.Pad QtControls2.RadioButton { id: colorRadioButton text: i18nd("plasma_wallpaper_org.kde.image", "Solid color") checked: !cfg_Blur QtControls2.ButtonGroup.group: backgroundGroup } KQuickControls.ColorButton { id: colorButton dialogTitle: i18nd("plasma_wallpaper_org.kde.image", "Select Background Color") } } } Component { id: foldersComponent ColumnLayout { Connections { target: root onHoursIntervalValueChanged: hoursInterval.value = root.hoursIntervalValue onMinutesIntervalValueChanged: minutesInterval.value = root.minutesIntervalValue onSecondsIntervalValueChanged: secondsInterval.value = root.secondsIntervalValue } //FIXME: there should be only one spinbox: QtControls spinboxes are still too limited for it tough Kirigami.FormLayout { twinFormLayouts: parentLayout RowLayout { Kirigami.FormData.label: i18nd("plasma_wallpaper_org.kde.image","Change every:") QtControls2.SpinBox { id: hoursInterval value: root.hoursIntervalValue from: 0 to: 24 editable: true onValueChanged: cfg_SlideInterval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value } QtControls2.Label { text: i18nd("plasma_wallpaper_org.kde.image","Hours") } QtControls2.SpinBox { id: minutesInterval value: root.minutesIntervalValue from: 0 to: 60 editable: true onValueChanged: cfg_SlideInterval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value } QtControls2.Label { text: i18nd("plasma_wallpaper_org.kde.image","Minutes") } QtControls2.SpinBox { id: secondsInterval value: root.secondsIntervalValue from: root.hoursIntervalValue === 0 && root.minutesIntervalValue === 0 ? 1 : 0 to: 60 editable: true onValueChanged: cfg_SlideInterval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value } QtControls2.Label { text: i18nd("plasma_wallpaper_org.kde.image","Seconds") } } } Kirigami.Heading { text: i18nd("plasma_wallpaper_org.kde.image","Folders") level: 2 } GridLayout { columns: 2 Layout.fillWidth: true Layout.fillHeight: true columnSpacing: Kirigami.Units.largeSpacing QtControls2.ScrollView { id: foldersScroll Layout.fillHeight: true Layout.preferredWidth: 0.25 * parent.width Component.onCompleted: foldersScroll.background.visible = true; ListView { id: slidePathsView anchors.margins: 4 model: imageWallpaper.slidePaths delegate: Kirigami.SwipeListItem { id: folderDelegate actions: [ Kirigami.Action { iconName: "list-remove" tooltip: i18nd("plasma_wallpaper_org.kde.image", "Remove Folder") onTriggered: imageWallpaper.removeSlidePath(modelData) }, Kirigami.Action { icon.name: "document-open-folder" tooltip: i18nd("plasma_wallpaper_org.kde.image", "Open Folder") onTriggered: imageWallpaper.openFolder(modelData) } ] QtControls2.Label { text: modelData.endsWith("/") ? modelData.split('/').reverse()[1] : modelData.split('/').pop() Layout.fillWidth: true QtControls2.ToolTip.text: modelData QtControls2.ToolTip.visible: folderDelegate.hovered QtControls2.ToolTip.delay: 1000 QtControls2.ToolTip.timeout: 5000 } width: slidePathsView.width height: paintedHeight; } } } Loader { sourceComponent: thumbnailsComponent Layout.fillWidth: true Layout.fillHeight: true anchors.fill: undefined } QtControls2.Button { Layout.alignment: Qt.AlignRight icon.name: "list-add" text: i18nd("plasma_wallpaper_org.kde.image","Add Folder...") onClicked: imageWallpaper.showAddSlidePathsDialog() } QtControls2.Button { Layout.alignment: Qt.AlignRight icon.name: "get-hot-new-stuff" text: i18nd("plasma_wallpaper_org.kde.image","Get New Wallpapers...") visible: KAuthorized.authorize("ghns") onClicked: imageWallpaper.getNewWallpaper(this); } } } } Component { id: thumbnailsComponent KCM.GridView { id: wallpapersGrid anchors.fill: parent - property var imageModel: (configDialog.currentWallpaper == "org.kde.image")? imageWallpaper.wallpaperModel : imageWallpaper.slideshowModel + property var imageModel: (configDialog.currentWallpaper == "org.kde.image")? imageWallpaper.wallpaperModel : imageWallpaper.slideFilterModel //that min is needed as the module will be populated in an async way //and only on demand so we can't ensure it already exists - view.currentIndex: Math.min(imageModel.indexOf(cfg_Image), imageModel.count-1) + view.currentIndex: Math.min(imageModel.indexOf(cfg_Image), imageModel.rowCount()-1) //kill the space for label under thumbnails view.model: imageModel + Component.onCompleted: { + imageModel.usedInConfig = true; + } view.delegate: WallpaperDelegate { color: cfg_Color } } } DragDrop.DropArea { Layout.fillWidth: true Layout.fillHeight: true onDragEnter: { if (!event.mimeData.hasUrls) { event.ignore(); } } onDrop: { event.mimeData.urls.forEach(function (url) { if (url.indexOf("file://") === 0) { var path = url.substr(7); // 7 is length of "file://" if (configDialog.currentWallpaper === "org.kde.image") { imageWallpaper.addUsersWallpaper(path); } else { imageWallpaper.addSlidePath(path); } } }); } Loader { anchors.fill: parent sourceComponent: (configDialog.currentWallpaper == "org.kde.image") ? thumbnailsComponent : ((configDialog.currentWallpaper == "org.kde.slideshow") ? foldersComponent : undefined) } } RowLayout { id: buttonsRow Layout.alignment: Qt.AlignRight | Qt.AlignVCenter visible: configDialog.currentWallpaper == "org.kde.image" QtControls2.Button { icon.name: "list-add" text: i18nd("plasma_wallpaper_org.kde.image","Add Image...") onClicked: imageWallpaper.showFileDialog(); } QtControls2.Button { icon.name: "get-hot-new-stuff" text: i18nd("plasma_wallpaper_org.kde.image","Get New Wallpapers...") visible: KAuthorized.authorize("ghns") onClicked: imageWallpaper.getNewWallpaper(this); } } } diff --git a/wallpapers/image/imagepackage/contents/ui/main.qml b/wallpapers/image/imagepackage/contents/ui/main.qml index eba89a7ab..a53a519a0 100644 --- a/wallpapers/image/imagepackage/contents/ui/main.qml +++ b/wallpapers/image/imagepackage/contents/ui/main.qml @@ -1,169 +1,175 @@ /* * Copyright 2013 Marco Martin * Copyright 2014 Sebastian Kügler * Copyright 2014 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.5 import QtQuick.Controls 2.1 as QQC2 import QtQuick.Window 2.2 import QtGraphicalEffects 1.0 import org.kde.plasma.wallpapers.image 2.0 as Wallpaper import org.kde.plasma.core 2.0 as PlasmaCore QQC2.StackView { id: root readonly property string modelImage: imageWallpaper.wallpaperPath readonly property string configuredImage: wallpaper.configuration.Image readonly property int fillMode: wallpaper.configuration.FillMode readonly property string configColor: wallpaper.configuration.Color readonly property bool blur: wallpaper.configuration.Blur readonly property size sourceSize: Qt.size(root.width * Screen.devicePixelRatio, root.height * Screen.devicePixelRatio) //public API, the C++ part will look for those function setUrl(url) { wallpaper.configuration.Image = url imageWallpaper.addUsersWallpaper(url); } function action_next() { imageWallpaper.nextSlide(); } function action_open() { Qt.openUrlExternally(modelImage) } //private onConfiguredImageChanged: { - imageWallpaper.addUrl(configuredImage) + if (modelImage != configuredImage && configuredImage != "") { + imageWallpaper.addUrl(configuredImage); + } } Component.onCompleted: { if (wallpaper.pluginName === "org.kde.slideshow") { wallpaper.setAction("open", i18nd("plasma_wallpaper_org.kde.image", "Open Wallpaper Image"), "document-open"); wallpaper.setAction("next", i18nd("plasma_wallpaper_org.kde.image", "Next Wallpaper Image"), "user-desktop"); } } Wallpaper.Image { id: imageWallpaper //the oneliner of difference between image and slideshow wallpapers renderingMode: (wallpaper.pluginName === "org.kde.image") ? Wallpaper.Image.SingleImage : Wallpaper.Image.SlideShow targetSize: root.sourceSize slidePaths: wallpaper.configuration.SlidePaths slideTimer: wallpaper.configuration.SlideInterval + slideshowMode: wallpaper.configuration.SlideshowMode uncheckedSlides: wallpaper.configuration.UncheckedSlides } onFillModeChanged: Qt.callLater(loadImage); - onModelImageChanged: Qt.callLater(loadImage); + onModelImageChanged:{ + Qt.callLater(loadImage); + wallpaper.configuration.Image = modelImage; + } onConfigColorChanged: Qt.callLater(loadImage); onBlurChanged: Qt.callLater(loadImage); onWidthChanged: Qt.callLater(loadImage); onHeightChanged: Qt.callLater(loadImage); function loadImage() { var isFirst = (root.currentItem == undefined); var pendingImage = baseImage.createObject(root, { "source": root.modelImage, "fillMode": root.fillMode, "sourceSize": root.sourceSize, "color": root.configColor, "blur": root.blur, "opacity": isFirst ? 1: 0}); function replaceWhenLoaded() { if (pendingImage.status !== Image.Loading) { root.replace(pendingImage, {}, isFirst ? QQC2.StackView.Immediate : QQC2.StackView.Transition);//dont' animate first show pendingImage.statusChanged.disconnect(replaceWhenLoaded); } } pendingImage.statusChanged.connect(replaceWhenLoaded); replaceWhenLoaded(); } Component { id: baseImage Image { id: mainImage property alias color: backgroundColor.color property bool blur: false asynchronous: true cache: false autoTransform: true z: -1 QQC2.StackView.onRemoved: destroy() Rectangle { id: backgroundColor anchors.fill: parent visible: mainImage.status === Image.Ready && !blurLoader.active z: -2 } Loader { id: blurLoader anchors.fill: parent z: -3 active: mainImage.blur && (mainImage.fillMode === Image.PreserveAspectFit || mainImage.fillMode === Image.Pad) sourceComponent: Item { Image { id: blurSource anchors.fill: parent asynchronous: true cache: false autoTransform: true fillMode: Image.PreserveAspectCrop source: mainImage.source sourceSize: mainImage.sourceSize visible: false // will be rendered by the blur } GaussianBlur { id: blurEffect anchors.fill: parent source: blurSource radius: 32 samples: 65 visible: blurSource.status === Image.Ready } } } } } replaceEnter: Transition { OpacityAnimator { from: 0 to: 1 duration: wallpaper.configuration.TransitionAnimationDuration } } // Keep the old image around till the new one is fully faded in // If we fade both at the same time you can see the background behind glimpse through replaceExit: Transition{ PauseAnimation { duration: wallpaper.configuration.TransitionAnimationDuration } } } diff --git a/wallpapers/image/slidefiltermodel.cpp b/wallpapers/image/slidefiltermodel.cpp new file mode 100644 index 000000000..71b87fc5c --- /dev/null +++ b/wallpapers/image/slidefiltermodel.cpp @@ -0,0 +1,90 @@ +/* + * Copyright 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. + */ + +#include "slidefiltermodel.h" + +#include "backgroundlistmodel.h" +#include "slidemodel.h" + +#include +#include + +SlideFilterModel::SlideFilterModel(QObject* parent) + : QSortFilterProxyModel{parent} + , m_SortingMode{Image::Random} + , m_usedInConfig{false} +{ + setSortCaseSensitivity(Qt::CaseSensitivity::CaseInsensitive); + connect(this, &SlideFilterModel::usedInConfigChanged, this, &SlideFilterModel::invalidateFilter); +} + +bool SlideFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + auto index = sourceModel()->index(source_row, 0, source_parent); + return m_usedInConfig || index.data(BackgroundListModel::ToggleRole).toBool(); +} + +bool SlideFilterModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const +{ + switch (m_SortingMode) { + case Image::Random: + return (*QRandomGenerator::system())() % 2 == 0; + case Image::Alphabetical: + return QSortFilterProxyModel::lessThan(source_left, source_right); + case Image::AlphabeticalReversed: + return !QSortFilterProxyModel::lessThan(source_left, source_right); + case Image::Modified: // oldest first + { + QFileInfo f1(source_left.data(BackgroundListModel::PathRole).toUrl().toLocalFile()); + QFileInfo f2(source_right.data(BackgroundListModel::PathRole).toUrl().toLocalFile()); + return f1.lastModified() < f2.lastModified(); + } + case Image::ModifiedReversed: // newest first + { + QFileInfo f1(source_left.data(BackgroundListModel::PathRole).toUrl().toLocalFile()); + QFileInfo f2(source_right.data(BackgroundListModel::PathRole).toUrl().toLocalFile()); + return !(f1.lastModified() < f2.lastModified()); + } + } + Q_UNREACHABLE(); +} + +void SlideFilterModel::setSortingMode(Image::SlideshowMode mode) +{ + m_SortingMode = mode; + if (!(m_usedInConfig && mode == Image::Random)) { + QSortFilterProxyModel::invalidate(); + } +} + +void SlideFilterModel::invalidateFilter() +{ + QSortFilterProxyModel::invalidateFilter(); +} + +int SlideFilterModel::indexOf(const QString& path) +{ + auto sourceIndex = sourceModel()->index(static_cast(sourceModel())->indexOf(path), 0); + return mapFromSource(sourceIndex).row(); +} + +void SlideFilterModel::openContainingFolder(int rowIndex) +{ + auto sourceIndex = mapToSource(index(rowIndex, 0)); + static_cast(sourceModel())->openContainingFolder(sourceIndex.row()); +} diff --git a/wallpapers/image/slidefiltermodel.h b/wallpapers/image/slidefiltermodel.h new file mode 100644 index 000000000..2ccd7bd29 --- /dev/null +++ b/wallpapers/image/slidefiltermodel.h @@ -0,0 +1,50 @@ +/* + * Copyright 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. + */ + +#ifndef SLIDEFILTERMODEL_H +#define SLIDEFILTERMODEL_H + +#include + +#include + + +class SlideFilterModel : public QSortFilterProxyModel { + + Q_OBJECT + + Q_PROPERTY(bool usedInConfig MEMBER m_usedInConfig NOTIFY usedInConfigChanged); + +public: + SlideFilterModel(QObject* parent); + bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + void setSortingMode(Image::SlideshowMode mode); + void invalidateFilter(); + + Q_INVOKABLE int indexOf(const QString& path); + Q_INVOKABLE void openContainingFolder(int rowIndex); + +Q_SIGNALS: + void usedInConfigChanged(); + +private: + Image::SlideshowMode m_SortingMode; + bool m_usedInConfig; +}; +#endif diff --git a/wallpapers/image/slidemodel.cpp b/wallpapers/image/slidemodel.cpp index 41c285355..47c0f8de8 100644 --- a/wallpapers/image/slidemodel.cpp +++ b/wallpapers/image/slidemodel.cpp @@ -1,59 +1,78 @@ +/* + * Copyright 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. + */ + #include "slidemodel.h" void SlideModel::reload(const QStringList &selected) { if (!m_packages.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_packages.count() - 1); m_packages.clear(); endRemoveRows(); emit countChanged(); } addDirs(selected); } void SlideModel::addDirs(const QStringList &selected) { BackgroundFinder *finder = new BackgroundFinder(m_wallpaper.data(), selected); connect(finder, &BackgroundFinder::backgroundsFound, this, &SlideModel::backgroundsFound); m_findToken = finder->token(); finder->start(); } void SlideModel::backgroundsFound(const QStringList& paths, const QString& token) { if (token != m_findToken) { return; } processPaths(paths); + emit done(); } void SlideModel::removeDir(const QString &path) { BackgroundFinder *finder = new BackgroundFinder(m_wallpaper.data(), QStringList{path}); connect(finder, &BackgroundFinder::backgroundsFound, this, &SlideModel::removeBackgrounds); finder->start(); } void SlideModel::removeBackgrounds(const QStringList &paths, const QString &token) { Q_FOREACH (const QString &file, paths) { removeBackground(file); } } QVariant SlideModel::data(const QModelIndex& index, int role) const { if (role == ToggleRole) { return !m_wallpaper.data()->uncheckedSlides().contains(data(index, PathRole).toString()); } return BackgroundListModel::data(index, role); } QHash SlideModel::roleNames() const { QHash roleNames = BackgroundListModel::roleNames(); roleNames.insert(ToggleRole, "checked"); return roleNames; } diff --git a/wallpapers/image/slidemodel.h b/wallpapers/image/slidemodel.h index 0d5330cd3..b34f0e062 100644 --- a/wallpapers/image/slidemodel.h +++ b/wallpapers/image/slidemodel.h @@ -1,21 +1,43 @@ +/* + * Copyright 2019 David Redondo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. + */ + #ifndef SLIDEMODEL_H #define SLIDEMODEL_H #include "backgroundlistmodel.h" class SlideModel : public BackgroundListModel { Q_OBJECT public: using BackgroundListModel::BackgroundListModel; void reload(const QStringList &selected); void addDirs(const QStringList &selected); void removeDir(const QString &selected); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; + +Q_SIGNALS: + void done(); + private Q_SLOTS: void removeBackgrounds(const QStringList &paths, const QString &token); void backgroundsFound(const QStringList &paths, const QString &token); }; #endif diff --git a/wallpapers/image/slideshowpackage/contents/config/main.xml b/wallpapers/image/slideshowpackage/contents/config/main.xml index 272e568d7..48c3e19e4 100644 --- a/wallpapers/image/slideshowpackage/contents/config/main.xml +++ b/wallpapers/image/slideshowpackage/contents/config/main.xml @@ -1,43 +1,46 @@ false #000000 2 900 1000 + + + 0 + -