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