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 | |
29 | class 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 | |
56 | KgThemeProvider::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 | |
65 | KgThemeProvider::~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 | |
88 | QString KgThemeProvider::name() const |
89 | { |
90 | return d->m_name; |
91 | } |
92 | |
93 | QList<const KgTheme*> KgThemeProvider::themes() const |
94 | { |
95 | return d->m_themes; |
96 | } |
97 | |
98 | void 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 | |
113 | const KgTheme* KgThemeProvider::defaultTheme() const |
114 | { |
115 | return d->m_defaultTheme; |
116 | } |
117 | |
118 | void 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 | |
130 | const 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 | |
155 | void 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 | |
165 | QString KgThemeProvider::currentThemeName() const |
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; |
176 | rediscoverThemes(); |
177 | } |
178 | |
179 | void 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 | |
261 | QPixmap KgThemeProvider::generatePreview(const KgTheme* theme, const QSize& size) |
262 | { |
263 | return QPixmap(theme->previewPath()).scaled(size, Qt::KeepAspectRatio); |
264 | } |
265 | |
266 | void 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 | |