diff --git a/wallpapers/image/image.cpp b/wallpapers/image/image.cpp index 8bd83ca97..88be291f1 100644 --- a/wallpapers/image/image.cpp +++ b/wallpapers/image/image.cpp @@ -1,914 +1,914 @@ /*************************************************************************** * 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(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.truncate(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::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_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_wallpaper = path; m_slideshowModel->addBackground(path); m_currentSlide = m_slideFilterModel->indexOf(path) - 1; nextSlide(); } //addUsersWallpaper(path); } void Image::startSlideshow() { if (!m_ready || m_slideFilterModel->property("usedInConfig").toBool()) { return; } // 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() { disconnect(m_slideshowModel, &SlideModel::done, this, 0); if(m_scanDirty) { m_scanDirty = false; startSlideshow(); return; } // start slideshow 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 { 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); } 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_slideFilterModel->rowCount() == 0) { return; } 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; } //We are starting again - avoid having the same random order when we restart the slideshow - if (m_slideshowMode == Random && previousSlide == m_slideFilterModel->rowCount() - 1) { + if (m_slideshowMode == Random && m_currentSlide == 0) { 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_timer.stop(); m_timer.start(m_delay * 1000); if (next.isEmpty()) { m_wallpaperPath = previousPath.toLocalFile(); } else { 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_slideshowModel->indexOf(path) == -1) { QFileInfo fileInfo(path); if(fileInfo.isFile() && BackgroundFinder::isAcceptableSuffix(fileInfo.suffix())) { m_slideshowModel->addBackground(path); if(m_slideFilterModel->rowCount() == 1) { nextSlide(); } } } } void Image::pathDeleted(const QString &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/slidefiltermodel.cpp b/wallpapers/image/slidefiltermodel.cpp index 71b87fc5c..0eb4967d1 100644 --- a/wallpapers/image/slidefiltermodel.cpp +++ b/wallpapers/image/slidefiltermodel.cpp @@ -1,90 +1,141 @@ /* * 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 +#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(); } +void SlideFilterModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + if (this->sourceModel()) { + disconnect(this->sourceModel(), nullptr, this, nullptr); + } + QSortFilterProxyModel::setSourceModel(sourceModel); + if (m_SortingMode == Image::Random && !m_usedInConfig) { + buildRandomOrder(); + } + if(sourceModel) { + connect(sourceModel, &QAbstractItemModel::rowsInserted, this, [this] { + if (m_SortingMode != Image::Random || m_usedInConfig) { + return; + } + const int old_count = m_randomOrder.size(); + m_randomOrder.resize(this->sourceModel()->rowCount()); + std::iota(m_randomOrder.begin() + old_count, m_randomOrder.end(), old_count); + }); + connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, [this] { + if (m_SortingMode != Image::Random || m_usedInConfig) { + return; + } + m_randomOrder.erase(std::remove_if(m_randomOrder.begin(), m_randomOrder.end(), [this] (const int v) { + return v >= this->sourceModel()->rowCount(); + }), m_randomOrder.end()); + }); + } +} + bool SlideFilterModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const { switch (m_SortingMode) { case Image::Random: - return (*QRandomGenerator::system())() % 2 == 0; + if (m_usedInConfig) { + return source_left.row() < source_right.row(); + } + return m_randomOrder.indexOf(source_left.row()) < m_randomOrder.indexOf(source_right.row()); 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(); + if (m_SortingMode == Image::Random && !m_usedInConfig) { + buildRandomOrder(); } + QSortFilterProxyModel::invalidate(); +} + +void SlideFilterModel::invalidate() +{ + if (m_SortingMode == Image::Random && !m_usedInConfig) { + std::random_shuffle(m_randomOrder.begin(), m_randomOrder.end()); + } + 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()); } + +void SlideFilterModel::buildRandomOrder() +{ + if (sourceModel()) { + m_randomOrder.resize(sourceModel()->rowCount()); + std::iota(m_randomOrder.begin(), m_randomOrder.end(), 0); + std::random_shuffle(m_randomOrder.begin(), m_randomOrder.end()); + } +} diff --git a/wallpapers/image/slidefiltermodel.h b/wallpapers/image/slidefiltermodel.h index 2ccd7bd29..5aaff55d6 100644 --- a/wallpapers/image/slidefiltermodel.h +++ b/wallpapers/image/slidefiltermodel.h @@ -1,50 +1,55 @@ /* * 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 - +#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 setSourceModel(QAbstractItemModel *sourceModel) override; void setSortingMode(Image::SlideshowMode mode); + void invalidate(); void invalidateFilter(); Q_INVOKABLE int indexOf(const QString& path); Q_INVOKABLE void openContainingFolder(int rowIndex); Q_SIGNALS: void usedInConfigChanged(); private: + void buildRandomOrder(); + + QVector m_randomOrder; Image::SlideshowMode m_SortingMode; bool m_usedInConfig; }; #endif