This simplifies ImageBackend, and makes the config dialog load slightly faster.wilder-5.26
parent
80539089c9
commit
8b64196342
9 changed files with 457 additions and 288 deletions
@ -0,0 +1,20 @@ |
||||
/*
|
||||
SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com> |
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later |
||||
*/ |
||||
|
||||
#pragma once |
||||
|
||||
class Provider |
||||
{ |
||||
Q_GADGET |
||||
|
||||
public: |
||||
enum class Type { |
||||
Unknown, |
||||
Image, |
||||
Package, |
||||
}; |
||||
Q_ENUM(Provider) |
||||
}; |
||||
@ -0,0 +1,288 @@ |
||||
/*
|
||||
SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com> |
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later |
||||
*/ |
||||
|
||||
#include "mediaproxy.h" |
||||
|
||||
#include <QFileInfo> |
||||
#include <QGuiApplication> |
||||
#include <QScreen> |
||||
#include <QUrlQuery> |
||||
|
||||
#include <KConfigGroup> |
||||
#include <KIO/OpenUrlJob> |
||||
#include <KNotificationJobUiDelegate> |
||||
#include <KPackage/PackageLoader> |
||||
|
||||
#include <Plasma/Theme> |
||||
|
||||
#include "../finder/packagefinder.h" |
||||
|
||||
#include "debug.h" |
||||
|
||||
MediaProxy::MediaProxy(QObject *parent) |
||||
: QObject(parent) |
||||
, m_targetSize(qGuiApp->primaryScreen()->size() * qGuiApp->primaryScreen()->devicePixelRatio()) |
||||
, m_isDarkColorScheme(isDarkColorScheme()) |
||||
{ |
||||
useSingleImageDefaults(); |
||||
} |
||||
|
||||
void MediaProxy::classBegin() |
||||
{ |
||||
} |
||||
|
||||
void MediaProxy::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; |
||||
|
||||
// Follow system color scheme
|
||||
connect(qGuiApp, &QGuiApplication::paletteChanged, this, &MediaProxy::slotSystemPaletteChanged); |
||||
|
||||
updateModelImage(); |
||||
} |
||||
|
||||
QString MediaProxy::source() const |
||||
{ |
||||
return m_source.toString(); |
||||
} |
||||
|
||||
void MediaProxy::setSource(const QString &url) |
||||
{ |
||||
if (m_source.toString() == url) { |
||||
return; |
||||
} |
||||
|
||||
m_source = QUrl(url); |
||||
Q_EMIT sourceChanged(); |
||||
|
||||
m_providerType = determineType(m_source); |
||||
updateModelImage(); |
||||
} |
||||
|
||||
QUrl MediaProxy::modelImage() const |
||||
{ |
||||
return m_modelImage; |
||||
} |
||||
|
||||
QSize MediaProxy::targetSize() const |
||||
{ |
||||
return m_targetSize; |
||||
} |
||||
|
||||
void MediaProxy::setTargetSize(const QSize &size) |
||||
{ |
||||
if (m_targetSize == size) { |
||||
return; |
||||
} |
||||
|
||||
m_targetSize = size; |
||||
Q_EMIT targetSizeChanged(size); |
||||
|
||||
if (m_providerType == Provider::Type::Package) { |
||||
updateModelImage(); |
||||
} |
||||
} |
||||
|
||||
Provider::Type MediaProxy::providerType() const |
||||
{ |
||||
return m_providerType; |
||||
} |
||||
|
||||
void MediaProxy::openModelImage() |
||||
{ |
||||
QUrl url; |
||||
|
||||
switch (m_providerType) { |
||||
case Provider::Type::Image: { |
||||
url = m_modelImage; |
||||
break; |
||||
} |
||||
|
||||
case Provider::Type::Package: { |
||||
url = findPreferredImageInPackage(); |
||||
break; |
||||
} |
||||
|
||||
default: |
||||
return; |
||||
} |
||||
|
||||
KIO::OpenUrlJob *job = new KIO::OpenUrlJob(url); |
||||
job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled)); |
||||
job->start(); |
||||
} |
||||
|
||||
void MediaProxy::useSingleImageDefaults() |
||||
{ |
||||
// Try from the look and feel package first, then from the plasma theme
|
||||
KPackage::Package lookAndFeelPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); |
||||
KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); |
||||
const QString packageName = cg.readEntry("LookAndFeelPackage", QString()); |
||||
// If empty, it will be the default (currently Breeze)
|
||||
if (!packageName.isEmpty()) { |
||||
lookAndFeelPackage.setPath(packageName); |
||||
} |
||||
|
||||
KConfigGroup lnfDefaultsConfig = KConfigGroup(KSharedConfig::openConfig(lookAndFeelPackage.filePath("defaults")), "Wallpaper"); |
||||
|
||||
const QString image = lnfDefaultsConfig.readEntry("Image", ""); |
||||
KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); |
||||
|
||||
if (!image.isEmpty()) { |
||||
package.setPath(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("wallpapers/") + image, QStandardPaths::LocateDirectory)); |
||||
|
||||
if (package.isValid()) { |
||||
m_source = QUrl::fromLocalFile(package.path()); |
||||
} |
||||
} |
||||
|
||||
// Try to get a default from the plasma theme
|
||||
if (m_source.isEmpty()) { |
||||
Plasma::Theme theme; |
||||
QString path = theme.wallpaperPath(); |
||||
int index = path.indexOf(QLatin1String("/contents/images/")); |
||||
if (index > -1) { // We have file from package -> get path to package
|
||||
m_source = QUrl::fromLocalFile(path.left(index)); |
||||
} else { |
||||
m_source = QUrl::fromLocalFile(path); |
||||
} |
||||
|
||||
package.setPath(m_source.toLocalFile()); |
||||
|
||||
if (!package.isValid()) { |
||||
return; |
||||
} |
||||
} |
||||
|
||||
PackageFinder::findPreferredImageInPackage(package, m_targetSize); |
||||
|
||||
// Make sure the image can be read, or there will be dead loops.
|
||||
if (m_source.isEmpty() || QImage(package.filePath("preferred")).isNull()) { |
||||
return; |
||||
} |
||||
|
||||
m_providerType = determineType(m_source); |
||||
updateModelImage(); |
||||
} |
||||
|
||||
QUrl MediaProxy::formatUrl(const QUrl &url) |
||||
{ |
||||
if (url.isLocalFile()) { |
||||
return url; |
||||
} |
||||
|
||||
// The url can be without file://, try again.
|
||||
return QUrl::fromLocalFile(url.toString()); |
||||
} |
||||
|
||||
void MediaProxy::slotSystemPaletteChanged(const QPalette &palette) |
||||
{ |
||||
if (m_providerType != Provider::Type::Package) { |
||||
// Currently only KPackage supports adaptive wallpapers
|
||||
return; |
||||
} |
||||
|
||||
const bool dark = isDarkColorScheme(palette); |
||||
|
||||
if (dark == m_isDarkColorScheme) { |
||||
return; |
||||
} |
||||
|
||||
m_isDarkColorScheme = dark; |
||||
Q_EMIT colorSchemeChanged(); |
||||
} |
||||
|
||||
bool MediaProxy::isDarkColorScheme(const QPalette &palette) const noexcept |
||||
{ |
||||
// 192 is from kcm_colors
|
||||
if (palette == QPalette()) { |
||||
return qGray(qGuiApp->palette().window().color().rgb()) < 192; |
||||
} |
||||
return qGray(palette.window().color().rgb()) < 192; |
||||
} |
||||
|
||||
Provider::Type MediaProxy::determineType(const QUrl &url) |
||||
{ |
||||
QFileInfo info(formatUrl(url).toLocalFile()); |
||||
|
||||
if (info.isFile()) { |
||||
return Provider::Type::Image; |
||||
} else if (info.isDir()) { |
||||
return Provider::Type::Package; |
||||
} |
||||
|
||||
return Provider::Type::Unknown; |
||||
} |
||||
|
||||
QUrl MediaProxy::findPreferredImageInPackage() |
||||
{ |
||||
KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Wallpaper/Images")); |
||||
package.setPath(m_source.toLocalFile()); |
||||
|
||||
QUrl url; |
||||
|
||||
if (!package.isValid()) { |
||||
return url; |
||||
} |
||||
|
||||
PackageFinder::findPreferredImageInPackage(package, m_targetSize); |
||||
url = QUrl::fromLocalFile(package.filePath("preferred")); |
||||
|
||||
if (isDarkColorScheme()) { |
||||
const QUrl darkUrl = package.fileUrl("preferredDark"); |
||||
|
||||
if (!darkUrl.isEmpty()) { |
||||
url = darkUrl; |
||||
} |
||||
} |
||||
|
||||
return url; |
||||
} |
||||
|
||||
void MediaProxy::updateModelImage() |
||||
{ |
||||
if (!m_ready) { |
||||
return; |
||||
} |
||||
|
||||
QUrl newRealSource; |
||||
|
||||
switch (m_providerType) { |
||||
case Provider::Type::Image: { |
||||
newRealSource = formatUrl(m_source); |
||||
break; |
||||
} |
||||
|
||||
case Provider::Type::Package: { |
||||
// Use a custom image provider
|
||||
QUrl composedUrl(QStringLiteral("image://package/get")); |
||||
|
||||
QUrlQuery urlQuery(composedUrl); |
||||
urlQuery.addQueryItem(QStringLiteral("dir"), formatUrl(m_source).toLocalFile()); |
||||
// To make modelImageChaged work
|
||||
urlQuery.addQueryItem(QStringLiteral("targetWidth"), QString::number(m_targetSize.width())); |
||||
urlQuery.addQueryItem(QStringLiteral("targetHeight"), QString::number(m_targetSize.height())); |
||||
|
||||
composedUrl.setQuery(urlQuery); |
||||
newRealSource = composedUrl; |
||||
break; |
||||
} |
||||
|
||||
case Provider::Type::Unknown: |
||||
default: |
||||
return; |
||||
} |
||||
|
||||
if (m_modelImage == newRealSource) { |
||||
return; |
||||
} |
||||
|
||||
m_modelImage = newRealSource; |
||||
Q_EMIT modelImageChanged(); |
||||
} |
||||
@ -0,0 +1,102 @@ |
||||
/*
|
||||
SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com> |
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later |
||||
*/ |
||||
|
||||
#ifndef MEDIAPROXY_H |
||||
#define MEDIAPROXY_H |
||||
|
||||
#include <QObject> |
||||
#include <QPalette> |
||||
#include <QQmlParserStatus> |
||||
#include <QSize> |
||||
#include <QUrl> |
||||
|
||||
#include "../provider/providertype.h" |
||||
|
||||
/**
|
||||
* A proxy class that converts a provider url to a real resource url. |
||||
*/ |
||||
class MediaProxy : public QObject, public QQmlParserStatus, public Provider |
||||
{ |
||||
Q_OBJECT |
||||
Q_INTERFACES(QQmlParserStatus) |
||||
|
||||
/**
|
||||
* Package path from the saved configuration, can be an image file, a url with |
||||
* "image://" scheme or a folder (KPackage). |
||||
*/ |
||||
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged) |
||||
|
||||
/**
|
||||
* The real path of the image |
||||
* e.g. /home/kde/Pictures/image.png |
||||
* image://package/get? (KPackage)
|
||||
*/ |
||||
Q_PROPERTY(QUrl modelImage READ modelImage NOTIFY modelImageChanged) |
||||
|
||||
Q_PROPERTY(QSize targetSize READ targetSize WRITE setTargetSize NOTIFY targetSizeChanged) |
||||
|
||||
public: |
||||
explicit MediaProxy(QObject *parent = nullptr); |
||||
|
||||
void classBegin() override; |
||||
void componentComplete() override; |
||||
|
||||
QString source() const; |
||||
void setSource(const QString &url); |
||||
|
||||
QUrl modelImage() const; |
||||
|
||||
QSize targetSize() const; |
||||
void setTargetSize(const QSize &size); |
||||
|
||||
Provider::Type providerType() const; |
||||
|
||||
Q_INVOKABLE void openModelImage(); |
||||
|
||||
Q_INVOKABLE void useSingleImageDefaults(); |
||||
|
||||
static QUrl formatUrl(const QUrl &url); |
||||
|
||||
Q_SIGNALS: |
||||
void sourceChanged(); |
||||
void modelImageChanged(); |
||||
void targetSizeChanged(const QSize &size); |
||||
|
||||
/**
|
||||
* Emitted when system color scheme changes. The frontend is required to |
||||
* reload the wallpaper even if the image path is not changed. |
||||
*/ |
||||
void colorSchemeChanged(); |
||||
|
||||
private Q_SLOTS: |
||||
/**
|
||||
* Switches to dark-colored wallpaper if available when system color |
||||
* scheme is dark. |
||||
* |
||||
* @since 5.26 |
||||
*/ |
||||
void slotSystemPaletteChanged(const QPalette &palette); |
||||
|
||||
private: |
||||
inline bool isDarkColorScheme(const QPalette &palette = {}) const noexcept; |
||||
|
||||
Provider::Type determineType(const QUrl &url); |
||||
|
||||
QUrl findPreferredImageInPackage(); |
||||
void updateModelImage(); |
||||
|
||||
bool m_ready = false; |
||||
|
||||
QUrl m_source; |
||||
QUrl m_modelImage; |
||||
Provider::Type m_providerType = Provider::Type::Unknown; |
||||
|
||||
QSize m_targetSize; |
||||
|
||||
bool m_isDarkColorScheme; |
||||
}; |
||||
|
||||
#endif // MEDIAPROXY_H
|
||||
Loading…
Reference in new issue