1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Thorben Kroeger <thorbenkroeger@gmail.com>.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "theme.h"
27 #include "theme_p.h"
28 #include "../algorithm.h"
29 #include "../hostosinfo.h"
30 #include "../qtcassert.h"
31 #ifdef Q_OS_MACOS
32 #import "theme_mac.h"
33 #endif
34
35 #include <QApplication>
36 #include <QFileInfo>
37 #include <QMetaEnum>
38 #include <QPalette>
39 #include <QSettings>
40
41 namespace Utils {
42
43 static Theme *m_creatorTheme = nullptr;
44
ThemePrivate()45 ThemePrivate::ThemePrivate()
46 {
47 const QMetaObject &m = Theme::staticMetaObject;
48 colors.resize (m.enumerator(m.indexOfEnumerator("Color")).keyCount());
49 imageFiles.resize (m.enumerator(m.indexOfEnumerator("ImageFile")).keyCount());
50 gradients.resize (m.enumerator(m.indexOfEnumerator("Gradient")).keyCount());
51 flags.resize (m.enumerator(m.indexOfEnumerator("Flag")).keyCount());
52 }
53
creatorTheme()54 Theme *creatorTheme()
55 {
56 return m_creatorTheme;
57 }
58
proxyTheme()59 Theme *proxyTheme()
60 {
61 return new Theme(m_creatorTheme);
62 }
63
setThemeApplicationPalette()64 void setThemeApplicationPalette()
65 {
66 if (m_creatorTheme && m_creatorTheme->flag(Theme::ApplyThemePaletteGlobally))
67 QApplication::setPalette(m_creatorTheme->palette());
68 }
69
setCreatorTheme(Theme * theme)70 void setCreatorTheme(Theme *theme)
71 {
72 if (m_creatorTheme == theme)
73 return;
74 delete m_creatorTheme;
75 m_creatorTheme = theme;
76
77 #ifdef Q_OS_MACOS
78 // Match the native UI theme and palette with the creator
79 // theme by forcing light aqua for light creator themes.
80 if (theme && !theme->flag(Theme::DarkUserInterface))
81 Internal::forceMacOSLightAquaApperance();
82 #endif
83
84 setThemeApplicationPalette();
85 }
86
Theme(const QString & id,QObject * parent)87 Theme::Theme(const QString &id, QObject *parent)
88 : QObject(parent)
89 , d(new ThemePrivate)
90 {
91 d->id = id;
92 }
93
Theme(Theme * originTheme,QObject * parent)94 Theme::Theme(Theme *originTheme, QObject *parent)
95 : QObject(parent)
96 , d(new ThemePrivate(*(originTheme->d)))
97 {
98 }
99
~Theme()100 Theme::~Theme()
101 {
102 delete d;
103 }
104
preferredStyles() const105 QStringList Theme::preferredStyles() const
106 {
107 return d->preferredStyles;
108 }
109
defaultTextEditorColorScheme() const110 QString Theme::defaultTextEditorColorScheme() const
111 {
112 return d->defaultTextEditorColorScheme;
113 }
114
id() const115 QString Theme::id() const
116 {
117 return d->id;
118 }
119
flag(Theme::Flag f) const120 bool Theme::flag(Theme::Flag f) const
121 {
122 return d->flags[f];
123 }
124
color(Theme::Color role) const125 QColor Theme::color(Theme::Color role) const
126 {
127 return d->colors[role].first;
128 }
129
imageFile(Theme::ImageFile imageFile,const QString & fallBack) const130 QString Theme::imageFile(Theme::ImageFile imageFile, const QString &fallBack) const
131 {
132 const QString &file = d->imageFiles.at(imageFile);
133 return file.isEmpty() ? fallBack : file;
134 }
135
gradient(Theme::Gradient role) const136 QGradientStops Theme::gradient(Theme::Gradient role) const
137 {
138 return d->gradients[role];
139 }
140
readNamedColor(const QString & color) const141 QPair<QColor, QString> Theme::readNamedColor(const QString &color) const
142 {
143 if (d->palette.contains(color))
144 return qMakePair(d->palette[color], color);
145 if (color == QLatin1String("style"))
146 return qMakePair(QColor(), QString());
147
148 const QColor col('#' + color);
149 if (!col.isValid()) {
150 qWarning("Color \"%s\" is neither a named color nor a valid color", qPrintable(color));
151 return qMakePair(Qt::black, QString());
152 }
153 return qMakePair(col, QString());
154 }
155
filePath() const156 QString Theme::filePath() const
157 {
158 return d->fileName;
159 }
160
displayName() const161 QString Theme::displayName() const
162 {
163 return d->displayName;
164 }
165
setDisplayName(const QString & name)166 void Theme::setDisplayName(const QString &name)
167 {
168 d->displayName = name;
169 }
170
readSettings(QSettings & settings)171 void Theme::readSettings(QSettings &settings)
172 {
173 d->fileName = settings.fileName();
174 const QMetaObject &m = *metaObject();
175
176 {
177 d->displayName = settings.value(QLatin1String("ThemeName"), QLatin1String("unnamed")).toString();
178 d->preferredStyles = settings.value(QLatin1String("PreferredStyles")).toStringList();
179 d->preferredStyles.removeAll(QString());
180 d->defaultTextEditorColorScheme =
181 settings.value(QLatin1String("DefaultTextEditorColorScheme")).toString();
182 }
183 {
184 settings.beginGroup(QLatin1String("Palette"));
185 const QStringList allKeys = settings.allKeys();
186 for (const QString &key : allKeys)
187 d->palette[key] = readNamedColor(settings.value(key).toString()).first;
188 settings.endGroup();
189 }
190 {
191 settings.beginGroup(QLatin1String("Colors"));
192 QMetaEnum e = m.enumerator(m.indexOfEnumerator("Color"));
193 for (int i = 0, total = e.keyCount(); i < total; ++i) {
194 const QString key = QLatin1String(e.key(i));
195 if (!settings.contains(key)) {
196 if (i < PaletteWindow || i > PaletteShadowDisabled)
197 qWarning("Theme \"%s\" misses color setting for key \"%s\".",
198 qPrintable(d->fileName), qPrintable(key));
199 continue;
200 }
201 d->colors[i] = readNamedColor(settings.value(key).toString());
202 }
203 settings.endGroup();
204 }
205 {
206 settings.beginGroup(QLatin1String("ImageFiles"));
207 QMetaEnum e = m.enumerator(m.indexOfEnumerator("ImageFile"));
208 for (int i = 0, total = e.keyCount(); i < total; ++i) {
209 const QString key = QLatin1String(e.key(i));
210 d->imageFiles[i] = settings.value(key).toString();
211 }
212 settings.endGroup();
213 }
214 {
215 settings.beginGroup(QLatin1String("Gradients"));
216 QMetaEnum e = m.enumerator(m.indexOfEnumerator("Gradient"));
217 for (int i = 0, total = e.keyCount(); i < total; ++i) {
218 const QString key = QLatin1String(e.key(i));
219 QGradientStops stops;
220 int size = settings.beginReadArray(key);
221 for (int j = 0; j < size; ++j) {
222 settings.setArrayIndex(j);
223 QTC_ASSERT(settings.contains(QLatin1String("pos")), return);
224 const double pos = settings.value(QLatin1String("pos")).toDouble();
225 QTC_ASSERT(settings.contains(QLatin1String("color")), return);
226 const QColor c('#' + settings.value(QLatin1String("color")).toString());
227 stops.append(qMakePair(pos, c));
228 }
229 settings.endArray();
230 d->gradients[i] = stops;
231 }
232 settings.endGroup();
233 }
234 {
235 settings.beginGroup(QLatin1String("Flags"));
236 QMetaEnum e = m.enumerator(m.indexOfEnumerator("Flag"));
237 for (int i = 0, total = e.keyCount(); i < total; ++i) {
238 const QString key = QLatin1String(e.key(i));
239 QTC_ASSERT(settings.contains(key), return);;
240 d->flags[i] = settings.value(key).toBool();
241 }
242 settings.endGroup();
243 }
244 }
245
systemUsesDarkMode()246 bool Theme::systemUsesDarkMode()
247 {
248 if (HostOsInfo::isWindowsHost()) {
249 constexpr char regkey[]
250 = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
251 bool ok;
252 const auto setting = QSettings(regkey, QSettings::NativeFormat).value("AppsUseLightTheme").toInt(&ok);
253 return ok && setting == 0;
254 }
255 return false;
256 }
257
258 // If you copy QPalette, default values stay at default, even if that default is different
259 // within the context of different widgets. Create deep copy.
copyPalette(const QPalette & p)260 static QPalette copyPalette(const QPalette &p)
261 {
262 QPalette res;
263 for (int group = 0; group < QPalette::NColorGroups; ++group) {
264 for (int role = 0; role < QPalette::NColorRoles; ++role) {
265 res.setBrush(QPalette::ColorGroup(group),
266 QPalette::ColorRole(role),
267 p.brush(QPalette::ColorGroup(group), QPalette::ColorRole(role)));
268 }
269 }
270 return res;
271 }
272
initialPalette()273 QPalette Theme::initialPalette()
274 {
275 static QPalette palette = copyPalette(QApplication::palette());
276 return palette;
277 }
278
palette() const279 QPalette Theme::palette() const
280 {
281 QPalette pal = initialPalette();
282 if (!flag(DerivePaletteFromTheme))
283 return pal;
284
285 const static struct {
286 Color themeColor;
287 QPalette::ColorRole paletteColorRole;
288 QPalette::ColorGroup paletteColorGroup;
289 bool setColorRoleAsBrush;
290 } mapping[] = {
291 {PaletteWindow, QPalette::Window, QPalette::All, false},
292 {PaletteWindowDisabled, QPalette::Window, QPalette::Disabled, false},
293 {PaletteWindowText, QPalette::WindowText, QPalette::All, true},
294 {PaletteWindowTextDisabled, QPalette::WindowText, QPalette::Disabled, true},
295 {PaletteBase, QPalette::Base, QPalette::All, false},
296 {PaletteBaseDisabled, QPalette::Base, QPalette::Disabled, false},
297 {PaletteAlternateBase, QPalette::AlternateBase, QPalette::All, false},
298 {PaletteAlternateBaseDisabled, QPalette::AlternateBase, QPalette::Disabled, false},
299 {PaletteToolTipBase, QPalette::ToolTipBase, QPalette::All, true},
300 {PaletteToolTipBaseDisabled, QPalette::ToolTipBase, QPalette::Disabled, true},
301 {PaletteToolTipText, QPalette::ToolTipText, QPalette::All, false},
302 {PaletteToolTipTextDisabled, QPalette::ToolTipText, QPalette::Disabled, false},
303 {PaletteText, QPalette::Text, QPalette::All, true},
304 {PaletteTextDisabled, QPalette::Text, QPalette::Disabled, true},
305 {PaletteButton, QPalette::Button, QPalette::All, false},
306 {PaletteButtonDisabled, QPalette::Button, QPalette::Disabled, false},
307 {PaletteButtonText, QPalette::ButtonText, QPalette::All, true},
308 {PaletteButtonTextDisabled, QPalette::ButtonText, QPalette::Disabled, true},
309 {PaletteBrightText, QPalette::BrightText, QPalette::All, false},
310 {PaletteBrightTextDisabled, QPalette::BrightText, QPalette::Disabled, false},
311 {PaletteHighlight, QPalette::Highlight, QPalette::All, true},
312 {PaletteHighlightDisabled, QPalette::Highlight, QPalette::Disabled, true},
313 {PaletteHighlightedText, QPalette::HighlightedText, QPalette::All, true},
314 {PaletteHighlightedTextDisabled, QPalette::HighlightedText, QPalette::Disabled, true},
315 {PaletteLink, QPalette::Link, QPalette::All, false},
316 {PaletteLinkDisabled, QPalette::Link, QPalette::Disabled, false},
317 {PaletteLinkVisited, QPalette::LinkVisited, QPalette::All, false},
318 {PaletteLinkVisitedDisabled, QPalette::LinkVisited, QPalette::Disabled, false},
319 {PaletteLight, QPalette::Light, QPalette::All, false},
320 {PaletteLightDisabled, QPalette::Light, QPalette::Disabled, false},
321 {PaletteMidlight, QPalette::Midlight, QPalette::All, false},
322 {PaletteMidlightDisabled, QPalette::Midlight, QPalette::Disabled, false},
323 {PaletteDark, QPalette::Dark, QPalette::All, false},
324 {PaletteDarkDisabled, QPalette::Dark, QPalette::Disabled, false},
325 {PaletteMid, QPalette::Mid, QPalette::All, false},
326 {PaletteMidDisabled, QPalette::Mid, QPalette::Disabled, false},
327 {PaletteShadow, QPalette::Shadow, QPalette::All, false},
328 {PaletteShadowDisabled, QPalette::Shadow, QPalette::Disabled, false}
329 };
330
331 for (auto entry: mapping) {
332 const QColor themeColor = color(entry.themeColor);
333 // Use original color if color is not defined in theme.
334 if (themeColor.isValid()) {
335 if (entry.setColorRoleAsBrush)
336 // TODO: Find out why sometimes setBrush is used
337 pal.setBrush(entry.paletteColorGroup, entry.paletteColorRole, themeColor);
338 else
339 pal.setColor(entry.paletteColorGroup, entry.paletteColorRole, themeColor);
340 }
341 }
342
343 return pal;
344 }
345
346 } // namespace Utils
347