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