File indexing completed on 2024-04-28 04:05:04
0001 /* 0002 SPDX-FileCopyrightText: 2012 Stefan Majewsky <majewsky@gmx.net> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only 0005 */ 0006 0007 #include "kgamethemeprovider.h" 0008 #include "kgameimageprovider_p.h" 0009 0010 // own 0011 #include <kdegames_logging.h> 0012 // KF 0013 #include <KConfig> 0014 #include <KConfigGroup> 0015 #include <KSharedConfig> 0016 // Qt 0017 #include <QDir> 0018 #include <QFileInfo> 0019 #include <QGuiApplication> 0020 #include <QQmlContext> 0021 #include <QStandardPaths> 0022 // Std 0023 #include <utility> 0024 0025 class KGameThemeProviderPrivate 0026 { 0027 public: 0028 KGameThemeProvider *const q; 0029 0030 QString m_name; 0031 QList<const KGameTheme *> m_themes; 0032 const QByteArray m_configKey; 0033 mutable const KGameTheme *m_currentTheme = nullptr; 0034 const KGameTheme *m_defaultTheme = nullptr; 0035 // this stores the arguments which were passed to discoverThemes() 0036 QString m_dtDirectory; 0037 QString m_dtDefaultThemeName; 0038 const QMetaObject *m_dtThemeClass = nullptr; 0039 // this remembers which themes were already discovered 0040 QStringList m_discoveredThemes; 0041 // this disables the addTheme() lock during rediscoverThemes() 0042 bool m_inRediscover = false; 0043 0044 public: 0045 KGameThemeProviderPrivate(KGameThemeProvider *parent, const QByteArray &key) 0046 : q(parent) 0047 , m_configKey(key) 0048 { 0049 } 0050 0051 void updateThemeName() 0052 { 0053 Q_EMIT q->currentThemeNameChanged(q->currentThemeName()); 0054 } 0055 }; 0056 0057 KGameThemeProvider::KGameThemeProvider(const QByteArray &configKey, QObject *parent) 0058 : QObject(parent) 0059 , d_ptr(new KGameThemeProviderPrivate(this, configKey)) 0060 { 0061 qRegisterMetaType<const KGameTheme *>(); 0062 qRegisterMetaType<KGameThemeProvider *>(); 0063 connect(this, &KGameThemeProvider::currentThemeChanged, this, [this]() { 0064 Q_D(KGameThemeProvider); 0065 d->updateThemeName(); 0066 }); 0067 } 0068 0069 KGameThemeProvider::~KGameThemeProvider() 0070 { 0071 Q_D(KGameThemeProvider); 0072 0073 if (!d->m_themes.isEmpty()) { 0074 // save current theme in config file (no sync() call here; this will most 0075 // likely be called at application shutdown when others are also writing to 0076 // KGlobal::config(); also KConfig's dtor will sync automatically) 0077 // but do not save if there is no choice; this is esp. helpful for the 0078 // KGameRenderer constructor overload that uses a single KGameTheme instance 0079 if (d->m_themes.size() > 1 && !d->m_configKey.isEmpty()) { 0080 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KgTheme")); 0081 cg.writeEntry(d->m_configKey.data(), currentTheme()->identifier()); 0082 } 0083 // cleanup 0084 while (!d->m_themes.isEmpty()) { 0085 delete const_cast<KGameTheme *>(d->m_themes.takeFirst()); 0086 } 0087 } 0088 } 0089 0090 QString KGameThemeProvider::name() const 0091 { 0092 Q_D(const KGameThemeProvider); 0093 0094 return d->m_name; 0095 } 0096 0097 QList<const KGameTheme *> KGameThemeProvider::themes() const 0098 { 0099 Q_D(const KGameThemeProvider); 0100 0101 return d->m_themes; 0102 } 0103 0104 void KGameThemeProvider::addTheme(KGameTheme *theme) 0105 { 0106 Q_D(KGameThemeProvider); 0107 0108 // The intended use is to create the KGameThemeProvider object, add themes, 0109 //*then* start to work with the currentLevel(). The first call to 0110 // currentTheme() will load the previous selection from the config, and the 0111 // level list will be considered immutable from this point. 0112 Q_ASSERT_X(d->m_currentTheme == nullptr || d->m_inRediscover, "KGameThemeProvider::addTheme", "Only allowed before currentTheme() is called."); 0113 // add theme 0114 d->m_themes.append(theme); 0115 theme->setParent(this); 0116 } 0117 0118 const KGameTheme *KGameThemeProvider::defaultTheme() const 0119 { 0120 Q_D(const KGameThemeProvider); 0121 0122 return d->m_defaultTheme; 0123 } 0124 0125 void KGameThemeProvider::setDefaultTheme(const KGameTheme *theme) 0126 { 0127 Q_D(KGameThemeProvider); 0128 0129 if (d->m_currentTheme) { 0130 qCDebug(KDEGAMES_LOG) << "You're calling setDefaultTheme after the current " 0131 "theme has already been determined. That's not gonna work."; 0132 return; 0133 } 0134 Q_ASSERT(d->m_themes.contains(theme)); 0135 d->m_defaultTheme = theme; 0136 } 0137 0138 const KGameTheme *KGameThemeProvider::currentTheme() const 0139 { 0140 Q_D(const KGameThemeProvider); 0141 0142 if (d->m_currentTheme) { 0143 return d->m_currentTheme; 0144 } 0145 Q_ASSERT(!d->m_themes.isEmpty()); 0146 // check configuration file for saved theme 0147 if (!d->m_configKey.isEmpty()) { 0148 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KgTheme")); 0149 const QByteArray id = cg.readEntry(d->m_configKey.data(), QByteArray()); 0150 // look for a theme with this id 0151 for (const KGameTheme *theme : std::as_const(d->m_themes)) { 0152 if (theme->identifier() == id) { 0153 return d->m_currentTheme = theme; 0154 } 0155 } 0156 } 0157 // fall back to default theme (or first theme if no default specified) 0158 return d->m_currentTheme = (d->m_defaultTheme ? d->m_defaultTheme : d->m_themes.first()); 0159 } 0160 0161 void KGameThemeProvider::setCurrentTheme(const KGameTheme *theme) 0162 { 0163 Q_D(KGameThemeProvider); 0164 0165 Q_ASSERT(d->m_themes.contains(theme)); 0166 if (d->m_currentTheme != theme) { 0167 d->m_currentTheme = theme; 0168 Q_EMIT currentThemeChanged(theme); 0169 } 0170 } 0171 0172 QString KGameThemeProvider::currentThemeName() const 0173 { 0174 Q_D(const KGameThemeProvider); 0175 0176 return currentTheme()->name(); 0177 } 0178 0179 void KGameThemeProvider::discoverThemes(const QString &directory, const QString &defaultThemeName, const QMetaObject *themeClass) 0180 { 0181 Q_D(KGameThemeProvider); 0182 0183 d->m_dtDirectory = directory; 0184 d->m_dtDefaultThemeName = defaultThemeName; 0185 d->m_dtThemeClass = themeClass; 0186 rediscoverThemes(); 0187 } 0188 0189 static QStringList findSubdirectories(const QStringList &dirs) 0190 { 0191 QStringList result; 0192 0193 for (const QString &dir : dirs) { 0194 const QStringList subdirNames = QDir(dir).entryList(QDir::Dirs | QDir::NoDotAndDotDot); 0195 result.reserve(result.size() + subdirNames.size()); 0196 for (const QString &subdirName : subdirNames) { 0197 const QString subdir = dir + QLatin1Char('/') + subdirName; 0198 result << subdir; 0199 } 0200 } 0201 if (!result.isEmpty()) { 0202 result += findSubdirectories(result); 0203 } 0204 0205 return result; 0206 } 0207 0208 void KGameThemeProvider::rediscoverThemes() 0209 { 0210 Q_D(KGameThemeProvider); 0211 0212 if (d->m_dtDirectory.isEmpty()) { 0213 return; // discoverThemes() was never called 0214 } 0215 0216 KGameTheme *defaultTheme = nullptr; 0217 0218 d->m_inRediscover = true; 0219 const QString defaultFileName = d->m_dtDefaultThemeName + QLatin1String(".desktop"); 0220 0221 QStringList themePaths; 0222 const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, d->m_dtDirectory, QStandardPaths::LocateDirectory); 0223 const QStringList allDirs = dirs + findSubdirectories(dirs); 0224 for (const QString &dir : allDirs) { 0225 const QStringList fileNames = QDir(dir).entryList({QStringLiteral("*.desktop")}); 0226 for (const QString &file : fileNames) { 0227 if (!themePaths.contains(file)) { 0228 themePaths.append(dir + QLatin1Char('/') + file); 0229 } 0230 } 0231 } 0232 0233 // create themes from result, order default theme at the front (that's not 0234 // needed by KGameThemeProvider, but nice for the theme selector) 0235 QList<KGameTheme *> themes; 0236 themes.reserve(themePaths.size()); 0237 if (d->m_discoveredThemes.isEmpty()) { 0238 d->m_discoveredThemes.reserve(themePaths.size()); 0239 } 0240 for (const QString &themePath : std::as_const(themePaths)) { 0241 const QFileInfo fi(themePath); 0242 if (d->m_discoveredThemes.contains(fi.fileName())) { 0243 continue; 0244 } 0245 d->m_discoveredThemes << fi.fileName(); 0246 // the identifier is constructed such that it is compatible with 0247 // KGameTheme (e.g. "themes/default.desktop") 0248 const QByteArray id = QString(d->m_dtDirectory + QLatin1Char('/') + fi.fileName()).toUtf8(); 0249 0250 // create theme 0251 KGameTheme *theme; 0252 if (d->m_dtThemeClass) { 0253 theme = qobject_cast<KGameTheme *>(d->m_dtThemeClass->newInstance(Q_ARG(QByteArray, id), Q_ARG(QObject *, this))); 0254 Q_ASSERT_X(theme, "KGameThemeProvider::discoverThemes", "Could not create theme instance. Is your constructor Q_INVOKABLE?"); 0255 } else { 0256 theme = new KGameTheme(id, this); 0257 } 0258 // silently discard invalid theme files 0259 if (!theme->readFromDesktopFile(themePath)) { 0260 delete theme; 0261 continue; 0262 } 0263 // order default theme at the front (that's not necessarily needed by 0264 // KGameThemeProvider, but nice for the theme selector) 0265 if (fi.fileName() == defaultFileName) { 0266 themes.prepend(theme); 0267 defaultTheme = theme; 0268 } else { 0269 themes.append(theme); 0270 } 0271 } 0272 // add themes in the determined order 0273 for (KGameTheme *theme : std::as_const(themes)) { 0274 addTheme(theme); 0275 } 0276 0277 if (defaultTheme != nullptr) { 0278 setDefaultTheme(defaultTheme); 0279 } else if (d->m_defaultTheme == nullptr && themes.count() != 0) { 0280 setDefaultTheme(themes.value(0)); 0281 } 0282 0283 d->m_inRediscover = false; 0284 } 0285 0286 QPixmap KGameThemeProvider::generatePreview(const KGameTheme *theme, QSize size) 0287 { 0288 const qreal dpr = qApp->devicePixelRatio(); 0289 QPixmap pixmap = QPixmap(theme->previewPath()).scaled(size * dpr, Qt::KeepAspectRatio, Qt::SmoothTransformation); 0290 pixmap.setDevicePixelRatio(dpr); 0291 return pixmap; 0292 } 0293 0294 void KGameThemeProvider::setDeclarativeEngine(const QString &name, QQmlEngine *engine) 0295 { 0296 Q_D(KGameThemeProvider); 0297 0298 if (d->m_name != name) { // prevent multiple declarations 0299 d->m_name = name; 0300 engine->addImageProvider(name, new KGameImageProvider(this)); 0301 engine->rootContext()->setContextProperty(name, this); 0302 } 0303 } 0304 0305 #include "moc_kgamethemeprovider.cpp"