diff --git a/wallpapers/image/image.cpp b/wallpapers/image/image.cpp index d4812434..9c189918 100644 --- a/wallpapers/image/image.cpp +++ b/wallpapers/image/image.cpp @@ -1,866 +1,840 @@ /*************************************************************************** * 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 * * * * 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 #include // FLT_MAX #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "backgroundlistmodel.h" #include Image::Image(QObject *parent) : QObject(parent), m_ready(false), m_delay(10), m_dirWatch(new KDirWatch(this)), m_mode(SingleImage), m_currentSlide(-1), m_model(0), - m_dialog(0), - m_width(0), - m_height(0) + m_dialog(0) { 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(); - connect(this, &Image::sizeChanged, this, &Image::setTargetSize); - useSingleImageDefaults(); setSingleImage(); } 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(); } } 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); } QTimer::singleShot(200, this, &Image::startSlideshow); updateDirWatch(m_slidePaths); updateDirWatch(m_slidePaths); } else { // we need to reset the prefered image setSingleImage(); } } 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; //qDebug() << "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); //qDebug() << "candidate" << candidate << "distance" << dist << "aspect ratio" << candidateAspectRatio; if (bestImage.isEmpty() || dist < best) { bestImage = entry; best = dist; //qDebug() << "best" << bestImage; } } //qDebug() << "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", "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(); } -} -int Image::height() const -{ - return m_height; -} - -void Image::setHeight(int h) -{ - if (m_height != h) { - m_height = h; - emit sizeChanged(QSize(m_width, m_height)); - } -} - -int Image::width() const -{ - return m_width; -} - -void Image::setWidth(int w) -{ - if (m_width != w) { - m_width = w; - emit sizeChanged(QSize(m_width, m_height)); + 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; } 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(); } emit slidePathsChanged(); } void Image::showAddSlidePathsDialog() { QFileDialog *dialog = new QFileDialog(0, i18n("Directory with the wallpaper to show slides from"), QLatin1String("")); 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); } 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); } 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/") + QString(m_wallpaper + QString::fromLatin1("/metadata.json"))); if (path.isEmpty()) path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("wallpapers/") + QString(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); } // 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; nextSlide(); } //addUsersWallpaper(path); } void Image::startSlideshow() { 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; } } void Image::backgroundsFound(const QStringList &paths, const QString &token) { if (token != m_findToken) { return; } m_findToken.clear(); if(m_scanDirty) { m_scanDirty = false; startSlideshow(); return; } m_slideshowBackgrounds = paths; m_unseenSlideshowBackgrounds.clear(); // start slideshow if (m_slideshowBackgrounds.isEmpty()) { // 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; nextSlide(); m_timer.start(m_delay * 1000); } } void Image::getNewWallpaper() { 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); } 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(0, 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::ExistingFile); connect(m_dialog, &QDialog::accepted, this, &Image::wallpaperBrowseCompleted); } m_dialog->show(); m_dialog->raise(); m_dialog->activateWindow(); } void Image::fileDialogFinished() { m_dialog = 0; } void Image::wallpaperBrowseCompleted() { Q_ASSERT(m_model); if (m_dialog && m_dialog->selectedFiles().count() > 0) { addUsersWallpaper(m_dialog->selectedFiles().first()); emit customWallpaperPicked(); } } void Image::addUsersWallpaper(const QString &file) { QString f = file; f.replace(QLatin1String("file:/"), QLatin1String("")); 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_slideshowBackgrounds.isEmpty()) { return; } QString previousPath; if (m_currentSlide > -1 && m_currentSlide < m_unseenSlideshowBackgrounds.size()) { previousPath = m_unseenSlideshowBackgrounds.takeAt(m_currentSlide); } 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; } } } 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; } else { m_wallpaperPath = current; } Q_EMIT wallpaperPathChanged(); } void Image::openSlide() { if (!m_wallpaperPackage.isValid()) { return; } // open in image viewer QUrl filepath(m_wallpaperPackage.filePath("preferred")); qDebug() << "opening file " << filepath.path(); new KRun(filepath, NULL); } void Image::pathCreated(const QString &path) { if(!m_slideshowBackgrounds.contains(path)) { QFileInfo fileInfo(path); if(fileInfo.isFile() && BackgroundFinder::isAcceptableSuffix(fileInfo.suffix())) { m_slideshowBackgrounds.append(path); m_unseenSlideshowBackgrounds.append(path); if(m_slideshowBackgrounds.count() == 1) { nextSlide(); } } } } void Image::pathDeleted(const QString &path) { if(m_slideshowBackgrounds.removeAll(path)) { m_unseenSlideshowBackgrounds.removeAll(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); } } diff --git a/wallpapers/image/image.h b/wallpapers/image/image.h index 0f50959d..aa738f93 100644 --- a/wallpapers/image/image.h +++ b/wallpapers/image/image.h @@ -1,196 +1,187 @@ /*************************************************************************** * Copyright 2007 Paolo Capriotti * * Copyright 2008 by Petri Damsten * * Copyright 2014 Sebastian Kügler * * Copyright 2015 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 02110-1301 USA . * ***************************************************************************/ #ifndef IMAGE_HEADER #define IMAGE_HEADER #include #include #include #include #include #include #include #include #include class QPropertyAnimation; class QFileDialog; class KDirWatch; class KJob; namespace KNS3 { class DownloadDialog; } class BackgroundListModel; class Image : public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(RenderingMode renderingMode READ renderingMode WRITE setRenderingMode NOTIFY renderingModeChanged) Q_PROPERTY(QUrl wallpaperPath READ wallpaperPath NOTIFY wallpaperPathChanged) Q_PROPERTY(QAbstractItemModel *wallpaperModel READ wallpaperModel 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(int width MEMBER m_width READ width WRITE setWidth NOTIFY sizeChanged) - Q_PROPERTY(int height MEMBER m_height READ height WRITE setHeight NOTIFY sizeChanged) + Q_PROPERTY(QSize targetSize READ targetSize WRITE setTargetSize NOTIFY targetSizeChanged) Q_PROPERTY(QString photosPath READ photosPath CONSTANT) public: enum RenderingMode { SingleImage, SlideShow }; Q_ENUM(RenderingMode) Image(QObject* parent = 0); ~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 getNewWallpaper(); Q_INVOKABLE void showFileDialog(); Q_INVOKABLE void addUsersWallpaper(const QString &file); Q_INVOKABLE void commitDeletion(); RenderingMode renderingMode() const; void setRenderingMode(RenderingMode mode); QSize targetSize() const; void setTargetSize(const QSize &size); - int width() const; - int height() const; - void setWidth(int w); - void setHeight(int h); - KPackage::Package *package(); QAbstractItemModel* wallpaperModel(); 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; public Q_SLOTS: void nextSlide(); void removeWallpaper(QString name); Q_SIGNALS: void settingsChanged(bool); void wallpaperPathChanged(); void renderingModeChanged(); + void targetSizeChanged(); void slideTimerChanged(); void usersWallpapersChanged(); void slidePathsChanged(); void resizeMethodChanged(); - void sizeChanged(QSize s); void customWallpaperPicked(); 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); 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; KPackage::Package m_wallpaperPackage; QStringList m_slideshowBackgrounds; QStringList m_unseenSlideshowBackgrounds; QStringList m_slidePaths; QTimer m_timer; int m_currentSlide; BackgroundListModel *m_model; QFileDialog *m_dialog; - QSize m_size; - int m_width; - int m_height; QString m_img; QDateTime m_previousModified; QPointer m_newStuffDialog; QString m_findToken; }; #endif diff --git a/wallpapers/image/imagepackage/contents/config/main.xml b/wallpapers/image/imagepackage/contents/config/main.xml index f920a0d7..c097b30a 100644 --- a/wallpapers/image/imagepackage/contents/config/main.xml +++ b/wallpapers/image/imagepackage/contents/config/main.xml @@ -1,39 +1,31 @@ #000000 0 - - - 0 - - - - 0 - 10 diff --git a/wallpapers/image/imagepackage/contents/ui/config.qml b/wallpapers/image/imagepackage/contents/ui/config.qml index bc2f6bbb..29fc1dca 100644 --- a/wallpapers/image/imagepackage/contents/ui/config.qml +++ b/wallpapers/image/imagepackage/contents/ui/config.qml @@ -1,360 +1,359 @@ /* * Copyright 2013 Marco Martin * 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 1.0 as QtControls import QtQuick.Dialogs 1.1 as QtDialogs import QtQuick.Layouts 1.0 //We need units from it import org.kde.plasma.core 2.0 as Plasmacore import org.kde.plasma.wallpapers.image 2.0 as Wallpaper import org.kde.kquickcontrolsaddons 2.0 ColumnLayout { id: root property alias cfg_Color: colorDialog.color property string cfg_Image property int cfg_FillMode property var cfg_SlidePaths: "" property int cfg_SlideInterval: 0 signal restoreIndex(int count) function saveConfig() { root.restoreIndex(imageWallpaper.wallpaperModel.count) imageWallpaper.commitDeletion(); } SystemPalette { id: syspal } Wallpaper.Image { id: imageWallpaper - width: wallpaper.configuration.width - height: wallpaper.configuration.height + targetSize: Qt.size(plasmoid.width, plasmoid.height) onSlidePathsChanged: cfg_SlidePaths = slidePaths } onCfg_SlidePathsChanged: { imageWallpaper.slidePaths = cfg_SlidePaths } 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 } TextMetrics { id: textMetrics text: "00" } Row { //x: formAlignment - positionLabel.paintedWidth spacing: units.largeSpacing / 2 QtControls.Label { id: positionLabel width: formAlignment - units.largeSpacing anchors { verticalCenter: resizeComboBox.verticalCenter } text: i18nd("plasma_applet_org.kde.image", "Positioning:") horizontalAlignment: Text.AlignRight } QtControls.ComboBox { id: resizeComboBox property int textLength: 24 width: theme.mSize(theme.defaultFont).width * textLength model: [ { 'label': i18nd("plasma_applet_org.kde.image", "Scaled and Cropped"), 'fillMode': Image.PreserveAspectCrop }, { 'label': i18nd("plasma_applet_org.kde.image","Scaled"), 'fillMode': Image.Stretch }, { 'label': i18nd("plasma_applet_org.kde.image","Scaled, Keep Proportions"), 'fillMode': Image.PreserveAspectFit }, { 'label': i18nd("plasma_applet_org.kde.image", "Centered"), 'fillMode': Image.Pad }, { 'label': i18nd("plasma_applet_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); } } } } } QtDialogs.ColorDialog { id: colorDialog modality: Qt.WindowModal showAlphaChannel: false title: i18nd("plasma_applet_org.kde.image", "Select Background Color") } Row { id: colorRow spacing: units.largeSpacing / 2 QtControls.Label { width: formAlignment - units.largeSpacing anchors.verticalCenter: colorButton.verticalCenter horizontalAlignment: Text.AlignRight text: i18nd("plasma_applet_org.kde.image", "Background Color:") } QtControls.Button { id: colorButton width: units.gridUnit * 3 text: " " // needed to it gets a proper height... onClicked: colorDialog.open() Rectangle { id: colorRect anchors.centerIn: parent width: parent.width - 2 * units.smallSpacing height: theme.mSize(theme.defaultFont).height color: colorDialog.color } } } Component { id: foldersComponent ColumnLayout { anchors.fill: parent 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 RowLayout { Layout.fillWidth: true spacing: units.largeSpacing / 2 QtControls.Label { Layout.minimumWidth: formAlignment - units.largeSpacing anchors.verticalCenter: parent.verticalCenter horizontalAlignment: Text.AlignRight text: i18nd("plasma_applet_org.kde.image","Change every:") } QtControls.SpinBox { id: hoursInterval anchors.verticalCenter: parent.verticalCenter Layout.minimumWidth: textMetrics.width + units.gridUnit width: units.gridUnit * 3 decimals: 0 value: root.hoursIntervalValue minimumValue: 0 maximumValue: 24 onValueChanged: cfg_SlideInterval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value } QtControls.Label { anchors.verticalCenter: parent.verticalCenter text: i18nd("plasma_applet_org.kde.image","Hours") } Item { Layout.preferredWidth: units.gridUnit } QtControls.SpinBox { id: minutesInterval anchors.verticalCenter: parent.verticalCenter Layout.minimumWidth: textMetrics.width + units.gridUnit width: units.gridUnit * 3 decimals: 0 value: root.minutesIntervalValue minimumValue: 0 maximumValue: 60 onValueChanged: cfg_SlideInterval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value } QtControls.Label { anchors.verticalCenter: parent.verticalCenter text: i18nd("plasma_applet_org.kde.image","Minutes") } Item { Layout.preferredWidth: units.gridUnit } QtControls.SpinBox { id: secondsInterval anchors.verticalCenter: parent.verticalCenter Layout.minimumWidth: textMetrics.width + units.gridUnit width: units.gridUnit * 3 decimals: 0 value: root.secondsIntervalValue minimumValue: root.hoursIntervalValue === 0 && root.minutesIntervalValue === 0 ? 1 : 0 maximumValue: 60 onValueChanged: cfg_SlideInterval = hoursInterval.value * 3600 + minutesInterval.value * 60 + secondsInterval.value } QtControls.Label { anchors.verticalCenter: parent.verticalCenter text: i18nd("plasma_applet_org.kde.image","Seconds") } } QtControls.ScrollView { Layout.fillHeight: true; Layout.fillWidth: true frameVisible: true ListView { id: slidePathsView anchors.margins: 4 model: imageWallpaper.slidePaths delegate: QtControls.Label { text: modelData width: slidePathsView.width height: Math.max(paintedHeight, removeButton.height); QtControls.ToolButton { id: removeButton anchors { verticalCenter: parent.verticalCenter right: parent.right } iconName: "list-remove" onClicked: imageWallpaper.removeSlidePath(modelData); } } } } } } Component { id: thumbnailsComponent QtControls.ScrollView { anchors.fill: parent frameVisible: true highlightOnFocus: true; Component.onCompleted: { //replace the current binding on the scrollbar that makes it visible when content doesn't fit //otherwise we adjust gridSize when we hide the vertical scrollbar and //due to layouting that can make everything adjust which changes the contentWidth/height which //changes our scrollbars and we continue being stuck in a loop //looks better to not have everything resize anyway. //BUG: 336301 __verticalScrollBar.visible = true } GridView { id: wallpapersGrid model: imageWallpaper.wallpaperModel currentIndex: -1 focus: true cellWidth: Math.floor(wallpapersGrid.width / Math.max(Math.floor(wallpapersGrid.width / (units.gridUnit*12)), 3)) - cellHeight: cellWidth / (imageWallpaper.width / imageWallpaper.height) + cellHeight: Math.round(cellWidth / (imageWallpaper.targetSize.width / imageWallpaper.targetSize.height)) anchors.margins: 4 boundsBehavior: Flickable.StopAtBounds delegate: WallpaperDelegate { color: cfg_Color } onCountChanged: { wallpapersGrid.currentIndex = imageWallpaper.wallpaperModel.indexOf(cfg_Image); wallpapersGrid.positionViewAtIndex(wallpapersGrid.currentIndex, GridView.Visible) } Connections { target: root onRestoreIndex: { wallpapersGrid.currentIndex = wallpapersGrid.currentIndex - count } } Keys.onPressed: { if (count < 1) { return; } if (event.key == Qt.Key_Home) { currentIndex = 0; } else if (event.key == Qt.Key_End) { currentIndex = count - 1; } } Keys.onLeftPressed: moveCurrentIndexLeft() Keys.onRightPressed: moveCurrentIndexRight() Keys.onUpPressed: moveCurrentIndexUp() Keys.onDownPressed: moveCurrentIndexDown() Connections { target: imageWallpaper onCustomWallpaperPicked: { wallpapersGrid.currentIndex = 0 } } } } } Loader { Layout.fillWidth: true Layout.fillHeight: true sourceComponent: (configDialog.currentWallpaper == "org.kde.image") ? thumbnailsComponent : foldersComponent } RowLayout { id: buttonsRow anchors { right: parent.right } QtControls.Button { visible: (configDialog.currentWallpaper == "org.kde.slideshow") iconName: "list-add" text: i18nd("plasma_applet_org.kde.image","Add Folder") onClicked: imageWallpaper.showAddSlidePathsDialog() } QtControls.Button { visible: (configDialog.currentWallpaper == "org.kde.image") iconName: "document-open-folder" text: i18nd("plasma_applet_org.kde.image","Open...") onClicked: imageWallpaper.showFileDialog(); } QtControls.Button { iconName: "get-hot-new-stuff" text: i18nd("plasma_applet_org.kde.image","Get New Wallpapers...") onClicked: imageWallpaper.getNewWallpaper(); } } } diff --git a/wallpapers/image/imagepackage/contents/ui/main.qml b/wallpapers/image/imagepackage/contents/ui/main.qml index a19d754f..d5384e42 100644 --- a/wallpapers/image/imagepackage/contents/ui/main.qml +++ b/wallpapers/image/imagepackage/contents/ui/main.qml @@ -1,239 +1,226 @@ /* * 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 org.kde.plasma.wallpapers.image 2.0 as Wallpaper import org.kde.plasma.core 2.0 as PlasmaCore Item { id: root readonly property string configuredImage: wallpaper.configuration.Image readonly property string modelImage: imageWallpaper.wallpaperPath property Item currentImage: imageB property Item otherImage: imageA readonly property int fillMode: wallpaper.configuration.FillMode property bool ready: false //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(currentImage.source) } //private function fadeWallpaper() { if (!ready && width > 0 && height > 0) { // shell startup, setup immediately currentImage.sourceSize = Qt.size(root.width, root.height) currentImage.source = modelImage ready = true return } fadeAnim.running = false swapImages() currentImage.source = modelImage currentImage.sourceSize = Qt.size(root.width, root.height) // Prevent source size change when image has just been setup anyway sourceSizeTimer.stop() currentImage.opacity = 0 otherImage.z = 0 currentImage.z = 1 // only cross-fade if the new image could be smaller than the old one fadeOtherAnimator.enabled = Qt.binding(function() { return currentImage.paintedWidth < otherImage.paintedWidth || currentImage.paintedHeight < otherImage.paintedHeight }) // Alleviate stuttering by waiting with the fade animation until the image is loaded (or failed to) fadeAnim.running = Qt.binding(function() { return currentImage.status !== Image.Loading && otherImage.status !== Image.Loading }) } function fadeFillMode() { fadeAnim.running = false swapImages() currentImage.sourceSize = otherImage.sourceSize sourceSizeTimer.stop() currentImage.source = modelImage currentImage.opacity = 0 otherImage.z = 0 currentImage.fillMode = fillMode currentImage.z = 1 // only cross-fade if the new image could be smaller than the old one fadeOtherAnimator.enabled = Qt.binding(function() { return currentImage.paintedWidth < otherImage.paintedWidth || currentImage.paintedHeight < otherImage.paintedHeight }) fadeAnim.running = Qt.binding(function() { return currentImage.status !== Image.Loading && otherImage.status !== Image.Loading }) } function fadeSourceSize() { if (currentImage.sourceSize === Qt.size(root.width, root.height)) { return } fadeAnim.running = false swapImages() currentImage.sourceSize = Qt.size(root.width, root.height) currentImage.opacity = 0 currentImage.source = otherImage.source otherImage.z = 0 currentImage.z = 1 fadeOtherAnimator.enabled = false // the image size didn't change, avoid cross-dissolve fadeAnim.running = Qt.binding(function() { return currentImage.status !== Image.Loading && otherImage.status !== Image.Loading }) } function startFadeSourceTimer() { if (width > 0 && height > 0 && (imageA.status !== Image.Null || imageB.status !== Image.Null)) { sourceSizeTimer.restart() } } function swapImages() { if (currentImage == imageA) { currentImage = imageB otherImage = imageA } else { currentImage = imageA otherImage = imageB } } - Binding { - target: wallpaper.configuration - property: "width" - value: root.width - } - Binding { - target: wallpaper.configuration - property: "height" - value: root.height - } - onWidthChanged: startFadeSourceTimer() onHeightChanged: startFadeSourceTimer() Timer { id: sourceSizeTimer interval: 1000 // always delay reloading the image even when animations are turned off onTriggered: fadeSourceSize() } Component.onCompleted: { if (wallpaper.pluginName == "org.kde.slideshow") { wallpaper.setAction("open", i18nd("plasma_applet_org.kde.image", "Open Wallpaper Image"), "document-open"); wallpaper.setAction("next", i18nd("plasma_applet_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: "1920x1080" - width: root.width - height: root.height + targetSize: Qt.size(root.width, root.height) slidePaths: wallpaper.configuration.SlidePaths slideTimer: wallpaper.configuration.SlideInterval } onFillModeChanged: { fadeFillMode(); } onConfiguredImageChanged: { imageWallpaper.addUrl(configuredImage) } onModelImageChanged: { fadeWallpaper(); } SequentialAnimation { id: fadeAnim running: false ParallelAnimation { OpacityAnimator { target: currentImage from: 0 to: 1 duration: units.longDuration } OpacityAnimator { id: fadeOtherAnimator property bool enabled: true target: otherImage from: 1 // cannot disable an animation individually, so we just fade from 1 to 1 to: enabled ? 0 : 1 duration: units.longDuration } } ScriptAction { script: { otherImage.fillMode = fillMode; otherImage.source = ""; } } } Rectangle { id: backgroundColor anchors.fill: parent visible: ready && (currentImage.status === Image.Ready || otherImage.status === Image.Ready) color: wallpaper.configuration.Color Behavior on color { ColorAnimation { duration: units.longDuration } } } Image { id: imageA anchors.fill: parent asynchronous: true cache: false fillMode: wallpaper.configuration.FillMode autoTransform: true //new API in Qt 5.5, do not backport into Plasma 5.4. } Image { id: imageB anchors.fill: parent asynchronous: true cache: false fillMode: wallpaper.configuration.FillMode autoTransform: true //new API in Qt 5.5, do not backport into Plasma 5.4. } } diff --git a/wallpapers/image/slideshowpackage/contents/config/main.xml b/wallpapers/image/slideshowpackage/contents/config/main.xml index f920a0d7..c097b30a 100644 --- a/wallpapers/image/slideshowpackage/contents/config/main.xml +++ b/wallpapers/image/slideshowpackage/contents/config/main.xml @@ -1,39 +1,31 @@ #000000 0 - - - 0 - - - - 0 - 10