1/***************************************************************************
2 * Copyright 2012 Stefan Majewsky <majewsky@gmx.net> *
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 <QtCore/QFileInfo>
23#include <KDE/KConfig>
24#include <KDE/KConfigGroup>
25#include <KDE/KDebug>
26#include <KDE/KGlobal>
27#include <KDE/KStandardDirs>
28
29class KgThemeProvider::Private
30{
31 public:
32 KgThemeProvider *q;
33 QString m_name;
34 QList<const KgTheme*> m_themes;
35 const QByteArray m_configKey;
36 const KgTheme* m_currentTheme;
37 const KgTheme* m_defaultTheme;
38 //this stores the arguments which were passed to discoverThemes()
39 QByteArray m_dtResource;
40 QString m_dtDirectory;
41 QString m_dtDefaultThemeName;
42 const QMetaObject* m_dtThemeClass;
43 //this remembers which themes were already discovered
44 QStringList m_discoveredThemes;
45 //this disables the addTheme() lock during rediscoverThemes()
46 bool m_inRediscover;
47
48 Private(KgThemeProvider *parent, const QByteArray& key) : q(parent), m_configKey(key), m_currentTheme(0), m_defaultTheme(0), m_inRediscover(false) {}
49
50 void updateThemeName()
51 {
52 emit q->currentThemeNameChanged(q->currentThemeName());
53 }
54};
55
56KgThemeProvider::KgThemeProvider(const QByteArray& configKey, QObject* parent)
57 : QObject(parent)
58 , d(new Private(this, configKey))
59{
60 qRegisterMetaType<const KgTheme*>();
61 qRegisterMetaType<KgThemeProvider*>();
62 connect(this, SIGNAL(currentThemeChanged(const KgTheme*)), this, SLOT(updateThemeName()));
63}
64
65KgThemeProvider::~KgThemeProvider()
66{
67 if (d->m_themes.isEmpty())
68 {
69 return;
70 }
71 //save current theme in config file (no sync() call here; this will most
72 //likely be called at application shutdown when others are also writing to
73 //KGlobal::config(); also KConfig's dtor will sync automatically)
74 //but do not save if there is no choice; this is esp. helpful for the
75 //KGameRenderer constructor overload that uses a single KgTheme instance
76 if (d->m_themes.size() > 1 && !d->m_configKey.isEmpty())
77 {
78 KConfigGroup cg(KGlobal::config(), "KgTheme");
79 cg.writeEntry(d->m_configKey.data(), currentTheme()->identifier());
80 }
81 //cleanup
82 while (!d->m_themes.isEmpty())
83 {
84 delete const_cast<KgTheme*>(d->m_themes.takeFirst());
85 }
86}
87
88QString KgThemeProvider::name() const
89{
90 return d->m_name;
91}
92
93QList<const KgTheme*> KgThemeProvider::themes() const
94{
95 return d->m_themes;
96}
97
98void KgThemeProvider::addTheme(KgTheme* theme)
99{
100 //The intended use is to create the KgThemeProvider object, add themes,
101 //*then* start to work with the currentLevel(). The first call to
102 //currentTheme() will load the previous selection from the config, and the
103 //level list will be considered immutable from this point.
104 Q_ASSERT_X(d->m_currentTheme == 0 || d->m_inRediscover,
105 "KgThemeProvider::addTheme",
106 "Only allowed before currentTheme() is called."
107 );
108 //add theme
109 d->m_themes.append(theme);
110 theme->setParent(this);
111}
112
113const KgTheme* KgThemeProvider::defaultTheme() const
114{
115 return d->m_defaultTheme;
116}
117
118void KgThemeProvider::setDefaultTheme(const KgTheme* theme)
119{
120 if (d->m_currentTheme)
121 {
122 kDebug(11000) << "You're calling setDefaultTheme after the current "
123 "theme has already been determined. That's not gonna work.";
124 return;
125 }
126 Q_ASSERT(d->m_themes.contains(theme));
127 d->m_defaultTheme = theme;
128}
129
130const KgTheme* KgThemeProvider::currentTheme() const
131{
132 if (d->m_currentTheme)
133 {
134 return d->m_currentTheme;
135 }
136 Q_ASSERT(!d->m_themes.isEmpty());
137 //check configuration file for saved theme
138 if (!d->m_configKey.isEmpty())
139 {
140 KConfigGroup cg(KGlobal::config(), "KgTheme");
141 const QByteArray id = cg.readEntry(d->m_configKey.data(), QByteArray());
142 //look for a theme with this id
143 foreach (const KgTheme* theme, d->m_themes)
144 {
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
155void KgThemeProvider::setCurrentTheme(const KgTheme* theme)
156{
157 Q_ASSERT(d->m_themes.contains(theme));
158 if (d->m_currentTheme != theme)
159 {
160 d->m_currentTheme = theme;
161 emit currentThemeChanged(theme);
162 }
163}
164
165QString KgThemeProvider::currentThemeName() const
166{
167 return currentTheme()->name();
168}
169
170void 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;
176 rediscoverThemes();
177}
178
179void KgThemeProvider::rediscoverThemes()
180{
181 if (d->m_dtResource.isEmpty())
182 {
183 return; //discoverThemes() was never called
184 }
185
186 KgTheme* defaultTheme = NULL;
187
188 d->m_inRediscover = true;
189 const QString defaultFileName = d->m_dtDefaultThemeName + QLatin1String(".desktop");
190 const QStringList themePaths = KGlobal::dirs()->findAllResources(
191 d->m_dtResource, d->m_dtDirectory + QLatin1String("/*.desktop"),
192 KStandardDirs::NoDuplicates
193 );
194 //create themes from result, order default theme at the front (that's not
195 //needed by KgThemeProvider, but nice for the theme selector)
196 QList<KgTheme*> themes;
197 foreach (const QString& themePath, themePaths)
198 {
199 const QFileInfo fi(themePath);
200 if (d->m_discoveredThemes.contains(fi.fileName()))
201 {
202 continue;
203 }
204 d->m_discoveredThemes << fi.fileName();
205 //the identifier is constructed such that it is compatible with
206 //KGameTheme (e.g. "themes/default.desktop")
207 const QByteArray id = KGlobal::dirs()->relativeLocation(
208 d->m_dtResource, themePath).toUtf8();
209 //create theme
210 KgTheme* theme;
211 if (d->m_dtThemeClass)
212 {
213 theme = qobject_cast<KgTheme*>(d->m_dtThemeClass->newInstance(
214 Q_ARG(QByteArray, id), Q_ARG(QObject*, this)
215 ));
216 Q_ASSERT_X(theme,
217 "KgThemeProvider::discoverThemes",
218 "Could not create theme instance. Is your constructor Q_INVOKABLE?"
219 );
220 }
221 else
222 {
223 theme = new KgTheme(id, this);
224 }
225 //silently discard invalid theme files
226 if (!theme->readFromDesktopFile(themePath))
227 {
228 delete theme;
229 continue;
230 }
231 //order default theme at the front (that's not necessarily needed by
232 //KgThemeProvider, but nice for the theme selector)
233 if (fi.fileName() == defaultFileName)
234 {
235 themes.prepend(theme);
236 defaultTheme = theme;
237 }
238 else
239 {
240 themes.append(theme);
241 }
242 }
243 //add themes in the determined order
244 foreach (KgTheme* theme, themes)
245 {
246 addTheme(theme);
247 }
248
249 if(defaultTheme != NULL)
250 {
251 setDefaultTheme(defaultTheme);
252 }
253 else if(d->m_defaultTheme == NULL && themes.count() != 0)
254 {
255 setDefaultTheme(themes.value(0));
256 }
257
258 d->m_inRediscover = false;
259}
260
261QPixmap KgThemeProvider::generatePreview(const KgTheme* theme, const QSize& size)
262{
263 return QPixmap(theme->previewPath()).scaled(size, Qt::KeepAspectRatio);
264}
265
266void KgThemeProvider::setDeclarativeEngine(const QString& name, QDeclarativeEngine* engine)
267{
268 if (d->m_name != name) { // prevent multiple declarations
269 d->m_name = name;
270 engine->addImageProvider(name, new KgImageProvider(this));
271 engine->rootContext()->setContextProperty(name, this);
272 }
273}
274
275#include "kgthemeprovider.moc"
276