KDEGames

kgamethemeprovider.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Stefan Majewsky <majewsky@gmx.net>
3
4 SPDX-License-Identifier: LGPL-2.0-only
5*/
6
7#include "kgamethemeprovider.h"
8#include "kgameimageprovider_p.h"
9
10// own
11#include <kdegames_logging.h>
12// KF
13#include <KConfig>
14#include <KConfigGroup>
15#include <KSharedConfig>
16// Qt
17#include <QDir>
18#include <QFileInfo>
19#include <QGuiApplication>
20#include <QQmlContext>
21#include <QStandardPaths>
22// Std
23#include <utility>
24
25class KGameThemeProviderPrivate
26{
27public:
28 KGameThemeProvider *const q;
29
30 QString m_name;
32 const QByteArray m_configKey;
33 mutable const KGameTheme *m_currentTheme = nullptr;
34 const KGameTheme *m_defaultTheme = nullptr;
35 // this stores the arguments which were passed to discoverThemes()
36 QString m_dtDirectory;
37 QString m_dtDefaultThemeName;
38 const QMetaObject *m_dtThemeClass = nullptr;
39 // this disables the addTheme() lock during rediscoverThemes()
40 bool m_inRediscover = false;
41
42public:
43 KGameThemeProviderPrivate(KGameThemeProvider *parent, const QByteArray &key)
44 : q(parent)
45 , m_configKey(key)
46 {
47 }
48
49 void updateThemeName()
50 {
51 Q_EMIT q->currentThemeNameChanged(q->currentThemeName());
52 }
53};
54
56 : QObject(parent)
57 , d_ptr(new KGameThemeProviderPrivate(this, configKey))
58{
59 qRegisterMetaType<const KGameTheme *>();
60 qRegisterMetaType<KGameThemeProvider *>();
63 d->updateThemeName();
64 });
65}
66
68{
70
71 if (!d->m_themes.isEmpty()) {
72 // save current theme in config file (no sync() call here; this will most
73 // likely be called at application shutdown when others are also writing to
74 // KGlobal::config(); also KConfig's dtor will sync automatically)
75 // but do not save if there is no choice; this is esp. helpful for the
76 // KGameRenderer constructor overload that uses a single KGameTheme instance
77 if (d->m_themes.size() > 1 && !d->m_configKey.isEmpty()) {
78 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KgTheme"));
79 cg.writeEntry(d->m_configKey.data(), currentTheme()->identifier());
80 }
81 // cleanup
82 while (!d->m_themes.isEmpty()) {
83 delete const_cast<KGameTheme *>(d->m_themes.takeFirst());
84 }
85 }
86}
87
88QString KGameThemeProvider::name() const
89{
91
92 return d->m_name;
93}
94
96{
98
99 return d->m_themes;
100}
101
103{
105
106 // The intended use is to create the KGameThemeProvider object, add themes,
107 //*then* start to work with the currentLevel(). The first call to
108 // currentTheme() will load the previous selection from the config, and the
109 // level list will be considered immutable from this point.
110 Q_ASSERT_X(d->m_currentTheme == nullptr || d->m_inRediscover, "KGameThemeProvider::addTheme", "Only allowed before currentTheme() is called.");
111 // add theme
112 d->m_themes.append(theme);
113 theme->setParent(this);
114}
115
117{
118 Q_D(const KGameThemeProvider);
119
120 return d->m_defaultTheme;
121}
122
124{
126
127 if (d->m_currentTheme) {
128 qCDebug(KDEGAMES_LOG) << "You're calling setDefaultTheme after the current "
129 "theme has already been determined. That's not gonna work.";
130 return;
131 }
132 Q_ASSERT(d->m_themes.contains(theme));
133 d->m_defaultTheme = theme;
134}
135
136const KGameTheme *KGameThemeProvider::currentTheme() const
137{
138 Q_D(const KGameThemeProvider);
139
140 if (d->m_currentTheme) {
141 return d->m_currentTheme;
142 }
143 Q_ASSERT(!d->m_themes.isEmpty());
144 // check configuration file for saved theme
145 if (!d->m_configKey.isEmpty()) {
146 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KgTheme"));
147 const QByteArray id = cg.readEntry(d->m_configKey.data(), QByteArray());
148 // look for a theme with this id
149 for (const KGameTheme *theme : std::as_const(d->m_themes)) {
150 if (theme->identifier() == id) {
151 return d->m_currentTheme = theme;
152 }
153 }
154 }
155 // fall back to default theme (or first theme if no default specified)
156 return d->m_currentTheme = (d->m_defaultTheme ? d->m_defaultTheme : d->m_themes.first());
157}
158
160{
162
163 Q_ASSERT(d->m_themes.contains(theme));
164 if (d->m_currentTheme != theme) {
165 d->m_currentTheme = theme;
167 }
168}
169
170QString KGameThemeProvider::currentThemeName() const
171{
172 Q_D(const KGameThemeProvider);
173
174 return currentTheme()->name();
175}
176
177void KGameThemeProvider::discoverThemes(const QString &directory, const QString &defaultThemeName, const QMetaObject *themeClass)
178{
180
181 d->m_dtDirectory = directory;
182 d->m_dtDefaultThemeName = defaultThemeName;
183 d->m_dtThemeClass = themeClass;
185}
186
187static QStringList findSubdirectories(const QStringList &dirs)
188{
189 QStringList result;
190
191 for (const QString &dir : dirs) {
192 const QStringList subdirNames = QDir(dir).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
193 result.reserve(result.size() + subdirNames.size());
194 for (const QString &subdirName : subdirNames) {
195 const QString subdir = dir + QLatin1Char('/') + subdirName;
196 result << subdir;
197 }
198 }
199 if (!result.isEmpty()) {
200 result += findSubdirectories(result);
201 }
202
203 return result;
204}
205
207{
209
210 if (d->m_dtDirectory.isEmpty()) {
211 return; // discoverThemes() was never called
212 }
213
214 KGameTheme *defaultTheme = nullptr;
215
216 d->m_inRediscover = true;
217 const QString defaultFileName = d->m_dtDefaultThemeName + QLatin1String(".desktop");
218
219 QStringList themePaths;
221 const QStringList allDirs = dirs + findSubdirectories(dirs);
222 for (const QString &dir : allDirs) {
223 const QStringList fileNames = QDir(dir).entryList({QStringLiteral("*.desktop")});
224 for (const QString &file : fileNames) {
225 if (!themePaths.contains(file)) {
226 themePaths.append(dir + QLatin1Char('/') + file);
227 }
228 }
229 }
230
231 // create themes from result, order default theme at the front (that's not
232 // needed by KGameThemeProvider, but nice for the theme selector)
234 themes.reserve(themePaths.size());
235 QList<QByteArray> newThemeIds;
236 newThemeIds.reserve(themePaths.size());
237 QList<QByteArray> oldThemeIds;
238 oldThemeIds.reserve(d->m_themes.size());
239 for (const KGameTheme *theme : std::as_const(d->m_themes)) {
240 oldThemeIds.append(theme->identifier());
241 }
242
243 for (const QString &themePath : std::as_const(themePaths)) {
244 const QString themeDesktopFileName = QFileInfo(themePath).fileName();
245 // the identifier is constructed such that it is compatible with
246 // KGameTheme (e.g. "themes/default.desktop")
247 const QByteArray id = QString(d->m_dtDirectory + QLatin1Char('/') + themeDesktopFileName).toUtf8();
248
249 // avoid duplicates
250 if (newThemeIds.contains(id)) {
251 continue;
252 }
253
254 newThemeIds.append(id);
255 if (oldThemeIds.contains(id)) {
256 continue;
257 }
258
259 // create theme
260 KGameTheme *theme;
261 if (d->m_dtThemeClass) {
262 theme = qobject_cast<KGameTheme *>(d->m_dtThemeClass->newInstance(Q_ARG(QByteArray, id), Q_ARG(QObject *, this)));
263 Q_ASSERT_X(theme, "KGameThemeProvider::discoverThemes", "Could not create theme instance. Is your constructor Q_INVOKABLE?");
264 } else {
265 theme = new KGameTheme(id, this);
266 }
267 // silently discard invalid theme files
268 if (!theme->readFromDesktopFile(themePath)) {
269 delete theme;
270 continue;
271 }
272 // order default theme at the front (that's not necessarily needed by
273 // KGameThemeProvider, but nice for the theme selector)
274 if (themeDesktopFileName == defaultFileName) {
275 themes.prepend(theme);
276 defaultTheme = theme;
277 } else {
278 themes.append(theme);
279 }
280 }
281
282 // remove themes no longer installed
283 bool isCurrentThemeRemoved = false;
284 for (const QByteArray &id : std::as_const(oldThemeIds)) {
285 if (newThemeIds.contains(id)) {
286 continue;
287 }
288 auto it = std::find_if(d->m_themes.cbegin(), d->m_themes.cend(), [id](const KGameTheme *theme) {
289 return (theme->identifier() == id);
290 });
291 // should not happen, but safety check
292 if (it == d->m_themes.cend()) {
293 Q_ASSERT_X(it != d->m_themes.cend(), Q_FUNC_INFO, id.data());
294 continue;
295 }
296
297 const KGameTheme *removedTheme = *it;
298 if (d->m_defaultTheme == removedTheme) {
299 qCWarning(KDEGAMES_LOG) << "The default theme was uninstalled, should not happen.";
300 continue;
301 }
302
303 if (d->m_currentTheme == removedTheme) {
304 d->m_currentTheme = nullptr;
305 isCurrentThemeRemoved = true;
306 }
307 delete const_cast<KGameTheme *>(removedTheme);
308 d->m_themes.erase(it);
309 }
310
311 // add themes in the determined order
312 for (KGameTheme *theme : std::as_const(themes)) {
313 addTheme(theme);
314 }
315
316 if (defaultTheme != nullptr) {
318 } else if (d->m_defaultTheme == nullptr && themes.count() != 0) {
320 }
321
322 d->m_inRediscover = false;
323
324 if (isCurrentThemeRemoved) {
325 // auto-estimate new one and tell the world
326 Q_EMIT currentThemeChanged(currentTheme());
327 }
328}
329
331{
332 const qreal dpr = qApp->devicePixelRatio();
333 QPixmap pixmap = QPixmap(theme->previewPath()).scaled(size * dpr, Qt::KeepAspectRatio, Qt::SmoothTransformation);
334 pixmap.setDevicePixelRatio(dpr);
335 return pixmap;
336}
337
339{
341
342 if (d->m_name != name) { // prevent multiple declarations
343 d->m_name = name;
344 engine->addImageProvider(name, new KGameImageProvider(this));
345 engine->rootContext()->setContextProperty(name, this);
346 }
347}
348
349#include "moc_kgamethemeprovider.cpp"
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
A theme provider manages KGameTheme instances, and maintains a selection of the currentTheme().
QList< const KGameTheme * > themes() const
void currentThemeChanged(const KGameTheme *theme)
Emitted when the current theme changes.
~KGameThemeProvider() override
Destructor.
void rediscoverThemes()
After this provider has been set up with discoverThemes(), this method may be used to read additional...
void discoverThemes(const QString &directory, const QString &defaultThemeName=QStringLiteral("default"), const QMetaObject *themeClass=nullptr)
This method reads theme description files from a standard location.
virtual QPixmap generatePreview(const KGameTheme *theme, QSize size)
Generate a preview pixmap for the given theme.
void currentThemeNameChanged(const QString &themeName)
Emitted when the name of the current theme changes.
void setDefaultTheme(const KGameTheme *theme)
void setDeclarativeEngine(const QString &name, QQmlEngine *engine)
Registers this KGameThemeProvider with engine's root context with ID name and constructs a KGameImage...
void addTheme(KGameTheme *theme)
Adds a theme to this instance.
void setCurrentTheme(const KGameTheme *theme)
Select a new theme.
const KGameTheme * defaultTheme() const
KGameThemeProvider(const QByteArray &configKey=QByteArrayLiteral("Theme"), QObject *parent=nullptr)
Constructor.
A theme describes the visual appearance of a game.
Definition kgametheme.h:60
virtual bool readFromDesktopFile(const QString &path)
Initializes a KGameTheme instance by reading a description file.
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QStringList entryList(Filters filters, SortFlags sort) const const
QString fileName() const const
void append(QList< T > &&value)
bool contains(const AT &value) const const
qsizetype count() const const
bool isEmpty() const const
void prepend(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
T value(qsizetype i) const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T qobject_cast(QObject *object)
void setParent(QObject *parent)
qreal devicePixelRatio() const const
QPixmap scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
void setDevicePixelRatio(qreal scaleFactor)
void setContextProperty(const QString &name, QObject *value)
void addImageProvider(const QString &providerId, QQmlImageProviderBase *provider)
QQmlContext * rootContext() const const
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
QByteArray toUtf8() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
KeepAspectRatio
SmoothTransformation
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:58:11 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.