KDEGames

kgthemeprovider.cpp
1 /***************************************************************************
2  * Copyright 2012 Stefan Majewsky <[email protected]> *
3  * *
4  * This program is free software; you can redistribute it and/or modify *
5  * it under the terms of the GNU Library General Public License *
6  * version 2 as published by the Free Software Foundation *
7  * *
8  * This program is distributed in the hope that it will be useful, *
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
11  * GNU Library General Public License for more details. *
12  * *
13  * You should have received a copy of the GNU Library General Public *
14  * License along with this program; if not, write to the *
15  * Free Software Foundation, Inc., *
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
17  ***************************************************************************/
18 
19 #include "kgthemeprovider.h"
20 #include "kgimageprovider_p.h"
21 
22 #include <QFileInfo>
23 #include <QStandardPaths>
24 #include <QGuiApplication>
25 
26 #include <KConfig>
27 #include <KConfigGroup>
28 #include <KSharedConfig>
29 
30 class KgThemeProvider::Private
31 {
32  public:
33  KgThemeProvider *q;
34  QString m_name;
35  QList<const KgTheme*> m_themes;
36  const QByteArray m_configKey;
37  const KgTheme* m_currentTheme;
38  const KgTheme* m_defaultTheme;
39  //this stores the arguments which were passed to discoverThemes()
40  QByteArray m_dtResource;
41  QString m_dtDirectory;
42  QString m_dtDefaultThemeName;
43  const QMetaObject* m_dtThemeClass;
44  //this remembers which themes were already discovered
45  QStringList m_discoveredThemes;
46  //this disables the addTheme() lock during rediscoverThemes()
47  bool m_inRediscover;
48 
49  Private(KgThemeProvider *parent, const QByteArray& key) : q(parent), m_configKey(key), m_currentTheme(nullptr), m_defaultTheme(nullptr), m_inRediscover(false) {}
50 
51  void updateThemeName()
52  {
53  Q_EMIT q->currentThemeNameChanged(q->currentThemeName());
54  }
55 };
56 
58  : QObject(parent)
59  , d(new Private(this, configKey))
60 {
61  qRegisterMetaType<const KgTheme*>();
62  qRegisterMetaType<KgThemeProvider*>();
63  connect(this, SIGNAL(currentThemeChanged(const KgTheme*)), this, SLOT(updateThemeName()));
64 }
65 
67 {
68  if (!d->m_themes.isEmpty())
69  {
70  //save current theme in config file (no sync() call here; this will most
71  //likely be called at application shutdown when others are also writing to
72  //KGlobal::config(); also KConfig's dtor will sync automatically)
73  //but do not save if there is no choice; this is esp. helpful for the
74  //KGameRenderer constructor overload that uses a single KgTheme instance
75  if (d->m_themes.size() > 1 && !d->m_configKey.isEmpty())
76  {
77  KConfigGroup cg(KSharedConfig::openConfig(), "KgTheme");
78  cg.writeEntry(d->m_configKey.data(), currentTheme()->identifier());
79  }
80  //cleanup
81  while (!d->m_themes.isEmpty())
82  {
83  delete const_cast<KgTheme*>(d->m_themes.takeFirst());
84  }
85  delete d;
86  }
87 }
88 
90 {
91  return d->m_name;
92 }
93 
95 {
96  return d->m_themes;
97 }
98 
100 {
101  //The intended use is to create the KgThemeProvider object, add themes,
102  //*then* start to work with the currentLevel(). The first call to
103  //currentTheme() will load the previous selection from the config, and the
104  //level list will be considered immutable from this point.
105  Q_ASSERT_X(d->m_currentTheme == nullptr || d->m_inRediscover,
106  "KgThemeProvider::addTheme",
107  "Only allowed before currentTheme() is called."
108  );
109  //add theme
110  d->m_themes.append(theme);
111  theme->setParent(this);
112 }
113 
115 {
116  return d->m_defaultTheme;
117 }
118 
120 {
121  if (d->m_currentTheme)
122  {
123  qCDebug(GAMES_LIB) << "You're calling setDefaultTheme after the current "
124  "theme has already been determined. That's not gonna work.";
125  return;
126  }
127  Q_ASSERT(d->m_themes.contains(theme));
128  d->m_defaultTheme = theme;
129 }
130 
132 {
133  if (d->m_currentTheme)
134  {
135  return d->m_currentTheme;
136  }
137  Q_ASSERT(!d->m_themes.isEmpty());
138  //check configuration file for saved theme
139  if (!d->m_configKey.isEmpty())
140  {
141  KConfigGroup cg(KSharedConfig::openConfig(), "KgTheme");
142  const QByteArray id = cg.readEntry(d->m_configKey.data(), QByteArray());
143  //look for a theme with this id
144  for (const KgTheme* theme : qAsConst(d->m_themes)) {
145  if (theme->identifier() == id)
146  {
147  return d->m_currentTheme = theme;
148  }
149  }
150  }
151  //fall back to default theme (or first theme if no default specified)
152  return d->m_currentTheme = (d->m_defaultTheme ? d->m_defaultTheme : d->m_themes.first());
153 }
154 
156 {
157  Q_ASSERT(d->m_themes.contains(theme));
158  if (d->m_currentTheme != theme)
159  {
160  d->m_currentTheme = theme;
162  }
163 }
164 
166 {
167  return currentTheme()->name();
168 }
169 
170 void KgThemeProvider::discoverThemes(const QByteArray& resource, const QString& directory, const QString& defaultThemeName, const QMetaObject* themeClass)
171 {
172  d->m_dtResource = resource;
173  d->m_dtDirectory = directory;
174  d->m_dtDefaultThemeName = defaultThemeName;
175  d->m_dtThemeClass = themeClass;
177 }
178 
179 // Function to replace KStandardDirs::relativeLocation()
180 static QString relativeToApplications(const QString& file)
181 {
182  const QString canonical = QFileInfo(file).canonicalFilePath();
184  for (const QString& base : dirs) {
185  if (canonical.startsWith(base))
186  return canonical.mid(base.length()+1);
187  }
188  return file;
189 }
190 
192 {
193  if (d->m_dtResource.isEmpty())
194  {
195  return; //discoverThemes() was never called
196  }
197 
198  KgTheme* defaultTheme = nullptr;
199 
200  d->m_inRediscover = true;
201  const QString defaultFileName = d->m_dtDefaultThemeName + QLatin1String(".desktop");
202 
203  QStringList themePaths;
205  for (const QString &dir : dirs) {
206  const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.desktop"));
207  for (const QString &file : fileNames) {
208  if (!themePaths.contains(file)) {
209  themePaths.append(dir + QLatin1Char('/') + file);
210  }
211  }
212  }
213 
214  //create themes from result, order default theme at the front (that's not
215  //needed by KgThemeProvider, but nice for the theme selector)
217  for (const QString& themePath : qAsConst(themePaths)) {
218  const QFileInfo fi(themePath);
219  if (d->m_discoveredThemes.contains(fi.fileName()))
220  {
221  continue;
222  }
223  d->m_discoveredThemes << fi.fileName();
224  //the identifier is constructed such that it is compatible with
225  //KGameTheme (e.g. "themes/default.desktop")
226 
227  const QByteArray id = relativeToApplications(themePath).toUtf8();
228  //create theme
229  KgTheme* theme;
230  if (d->m_dtThemeClass)
231  {
232  theme = qobject_cast<KgTheme*>(d->m_dtThemeClass->newInstance(
233  Q_ARG(QByteArray, id), Q_ARG(QObject*, this)
234  ));
235  Q_ASSERT_X(theme,
236  "KgThemeProvider::discoverThemes",
237  "Could not create theme instance. Is your constructor Q_INVOKABLE?"
238  );
239  }
240  else
241  {
242  theme = new KgTheme(id, this);
243  }
244  //silently discard invalid theme files
245  if (!theme->readFromDesktopFile(themePath))
246  {
247  delete theme;
248  continue;
249  }
250  //order default theme at the front (that's not necessarily needed by
251  //KgThemeProvider, but nice for the theme selector)
252  if (fi.fileName() == defaultFileName)
253  {
254  themes.prepend(theme);
255  defaultTheme = theme;
256  }
257  else
258  {
259  themes.append(theme);
260  }
261  }
262  //add themes in the determined order
263  for (KgTheme* theme : qAsConst(themes))
264  {
265  addTheme(theme);
266  }
267 
268  if(defaultTheme != nullptr)
269  {
270  setDefaultTheme(defaultTheme);
271  }
272  else if(d->m_defaultTheme == nullptr && themes.count() != 0)
273  {
274  setDefaultTheme(themes.value(0));
275  }
276 
277  d->m_inRediscover = false;
278 }
279 
281 {
282  const qreal dpr = qApp->testAttribute(Qt::AA_UseHighDpiPixmaps) ? qApp->devicePixelRatio() : 1;
283  QPixmap pixmap = QPixmap(theme->previewPath()).scaled(size * dpr, Qt::KeepAspectRatio);
284  pixmap.setDevicePixelRatio(dpr);
285  return pixmap;
286 }
287 
289 {
290  if (d->m_name != name) { // prevent multiple declarations
291  d->m_name = name;
292  engine->addImageProvider(name, new KgImageProvider(this));
293  engine->rootContext()->setContextProperty(name, this);
294  }
295 }
296 
297 #include "moc_kgthemeprovider.cpp"
void setCurrentTheme(const KgTheme *theme)
Select a new theme.
void setDefaultTheme(const KgTheme *theme)
QStringList locateAll(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
virtual bool readFromDesktopFile(const QString &path)
Initializes a KgTheme instance by reading a description file.
Definition: kgtheme.cpp:86
void rediscoverThemes()
After this provider has been set up with discoverThemes(), this method may be used to read additional...
A theme provider manages KgTheme instances, and maintains a selection of the currentTheme().
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
void addTheme(KgTheme *theme)
Adds a theme to this instance.
void writeEntry(const QString &key, const QVariant &value, WriteConfigFlags pFlags=Normal)
QStringList standardLocations(QStandardPaths::StandardLocation type)
T value(int i) const const
QString name() const
virtual QPixmap generatePreview(const KgTheme *theme, const QSize &size)
Generate a preview pixmap for the given theme.
void discoverThemes(const QByteArray &resource, const QString &directory, const QString &defaultThemeName=QStringLiteral("default"), const QMetaObject *themeClass=nullptr)
This method reads theme description files from a standard location.
QString canonicalFilePath() const const
int count(const T &value) const const
QList< const KgTheme * > themes() const
void append(const T &value)
QString fileName() const const
AA_UseHighDpiPixmaps
virtual ~KgThemeProvider()
Destructor.
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
void setContextProperty(const QString &name, QObject *value)
QQmlContext * rootContext() const const
void addImageProvider(const QString &providerId, QQmlImageProviderBase *provider)
const KgTheme * currentTheme() const
void setParent(QObject *parent)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
const KgTheme * defaultTheme() const
QString mid(int position, int n) const const
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
KgThemeProvider(const QByteArray &configKey=QByteArray("Theme"), QObject *parent=nullptr)
Constructor.
QString currentThemeName() const
KeepAspectRatio
qreal devicePixelRatio() const const
void prepend(const T &value)
A theme describes the visual appearance of a game.
Definition: kgtheme.h:72
void currentThemeChanged(const KgTheme *theme)
Emitted when the current theme changes.
void setDeclarativeEngine(const QString &name, QQmlEngine *engine)
Registers this KgThemeProvider with engine&#39;s root context with ID name and constructs a KgImageProvid...
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
QObject * parent() const const
void setDevicePixelRatio(qreal scaleFactor)
T readEntry(const QString &key, const T &aDefault) const
void currentThemeNameChanged(const QString &themeName)
Emitted when the name of the current theme changes.
Q_EMITQ_EMIT
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Nov 30 2020 22:37:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.