1 | /* |
2 | This file is part of the KDE project |
3 | SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #include "kcolorschememanager.h" |
9 | #include "kcolorschememanager_p.h" |
10 | |
11 | #include "kcolorscheme.h" |
12 | #include "kcolorschememodel.h" |
13 | |
14 | #include <KColorSchemeWatcher> |
15 | #include <KConfigGroup> |
16 | #include <KConfigGui> |
17 | #include <KLocalizedString> |
18 | #include <KSharedConfig> |
19 | |
20 | #include <QDir> |
21 | #include <QFileInfo> |
22 | #include <QGuiApplication> |
23 | #include <QIcon> |
24 | #include <QPainter> |
25 | #include <QPointer> |
26 | #include <QStandardPaths> |
27 | |
28 | #include <private/qguiapplication_p.h> |
29 | #include <qpa/qplatformtheme.h> |
30 | |
31 | // ensure we are linking KConfigGui, so QColor I/O from KConfig works |
32 | KCONFIGGUI_EXPORT int initKConfigGroupGui(); |
33 | static int s_init = initKConfigGroupGui(); |
34 | |
35 | constexpr int defaultSchemeRow = 0; |
36 | |
37 | static bool isKdePlatformTheme() |
38 | { |
39 | if (!QGuiApplicationPrivate::platformTheme()) { |
40 | return false; |
41 | } |
42 | |
43 | if (QGuiApplicationPrivate::platformTheme()->name() == QLatin1String("kde" )) { |
44 | return true; |
45 | } |
46 | |
47 | if (qgetenv(varName: "XDG_CURRENT_DESKTOP" ) == "KDE" && QGuiApplicationPrivate::platformTheme()->name() == QLatin1String("xdgdesktopportal" )) { |
48 | return true; |
49 | } |
50 | |
51 | return false; |
52 | } |
53 | |
54 | void KColorSchemeManagerPrivate::activateSchemeInternal(const QString &colorSchemePath) |
55 | { |
56 | // hint for plasma-integration to synchronize the color scheme with the window manager/compositor |
57 | // The property needs to be set before the palette change because is is checked upon the |
58 | // ApplicationPaletteChange event. |
59 | qApp->setProperty(name: "KDE_COLOR_SCHEME_PATH" , value: colorSchemePath); |
60 | if (colorSchemePath.isEmpty()) { |
61 | qApp->setPalette(QPalette()); |
62 | } else { |
63 | qApp->setPalette(KColorScheme::createApplicationPalette(config: KSharedConfig::openConfig(fileName: colorSchemePath))); |
64 | } |
65 | } |
66 | |
67 | QString KColorSchemeManagerPrivate::automaticColorSchemeId() const |
68 | { |
69 | if (!m_colorSchemeWatcher) { |
70 | return QString(); |
71 | } |
72 | |
73 | switch (m_colorSchemeWatcher->systemPreference()) { |
74 | case KColorSchemeWatcher::PreferHighContrast: |
75 | return QString(); |
76 | case KColorSchemeWatcher::PreferDark: |
77 | return getDarkColorScheme(); |
78 | case KColorSchemeWatcher::PreferLight: |
79 | case KColorSchemeWatcher::NoPreference: |
80 | return getLightColorScheme(); |
81 | }; |
82 | return QString(); |
83 | } |
84 | |
85 | // The meaning of the Default entry depends on the platform |
86 | // On KDE we apply a default KColorScheme |
87 | // On other platforms we automatically apply Breeze/Breeze Dark depending on the system preference |
88 | QString KColorSchemeManagerPrivate::automaticColorSchemePath() const |
89 | { |
90 | const QString colorSchemeId = automaticColorSchemeId(); |
91 | if (colorSchemeId.isEmpty()) { |
92 | return QString(); |
93 | } else { |
94 | return indexForSchemeId(id: colorSchemeId).data(arole: KColorSchemeModel::PathRole).toString(); |
95 | } |
96 | } |
97 | |
98 | QIcon KColorSchemeManagerPrivate::createPreview(const QString &path) |
99 | { |
100 | KSharedConfigPtr schemeConfig = KSharedConfig::openConfig(fileName: path, mode: KConfig::SimpleConfig); |
101 | QIcon result; |
102 | |
103 | KColorScheme activeWindow(QPalette::Active, KColorScheme::Window, schemeConfig); |
104 | KColorScheme activeButton(QPalette::Active, KColorScheme::Button, schemeConfig); |
105 | KColorScheme activeView(QPalette::Active, KColorScheme::View, schemeConfig); |
106 | KColorScheme activeSelection(QPalette::Active, KColorScheme::Selection, schemeConfig); |
107 | |
108 | auto pixmap = [&](int size) { |
109 | QPixmap pix(size, size); |
110 | pix.fill(fillColor: Qt::black); |
111 | QPainter p; |
112 | p.begin(&pix); |
113 | const int itemSize = size / 2 - 1; |
114 | p.fillRect(x: 1, y: 1, w: itemSize, h: itemSize, b: activeWindow.background()); |
115 | p.fillRect(x: 1 + itemSize, y: 1, w: itemSize, h: itemSize, b: activeButton.background()); |
116 | p.fillRect(x: 1, y: 1 + itemSize, w: itemSize, h: itemSize, b: activeView.background()); |
117 | p.fillRect(x: 1 + itemSize, y: 1 + itemSize, w: itemSize, h: itemSize, b: activeSelection.background()); |
118 | p.end(); |
119 | result.addPixmap(pixmap: pix); |
120 | }; |
121 | // 16x16 |
122 | pixmap(16); |
123 | // 24x24 |
124 | pixmap(24); |
125 | |
126 | return result; |
127 | } |
128 | |
129 | KColorSchemeManagerPrivate::KColorSchemeManagerPrivate() |
130 | : model(new KColorSchemeModel()) |
131 | { |
132 | } |
133 | |
134 | KColorSchemeManager::KColorSchemeManager(GuardApplicationConstructor, QGuiApplication *app) |
135 | : QObject(app) |
136 | , d(new KColorSchemeManagerPrivate) |
137 | { |
138 | init(); |
139 | } |
140 | |
141 | #if KCOLORSCHEME_BUILD_DEPRECATED_SINCE(6, 6) |
142 | KColorSchemeManager::KColorSchemeManager(QObject *parent) |
143 | : QObject(parent) |
144 | , d(new KColorSchemeManagerPrivate()) |
145 | { |
146 | init(); |
147 | } |
148 | #endif |
149 | |
150 | KColorSchemeManager::~KColorSchemeManager() |
151 | { |
152 | } |
153 | |
154 | void KColorSchemeManager::init() |
155 | { |
156 | QString platformThemeSchemePath = qApp->property(name: "KDE_COLOR_SCHEME_PATH" ).toString(); |
157 | if (!isKdePlatformTheme() && platformThemeSchemePath.isEmpty()) { |
158 | d->m_colorSchemeWatcher.emplace(); |
159 | QObject::connect(sender: &*d->m_colorSchemeWatcher, signal: &KColorSchemeWatcher::systemPreferenceChanged, context: this, slot: [this]() { |
160 | if (!d->m_activatedScheme.isEmpty()) { |
161 | // Don't override what has been manually set |
162 | return; |
163 | } |
164 | |
165 | d->activateSchemeInternal(colorSchemePath: d->automaticColorSchemePath()); |
166 | }); |
167 | } |
168 | |
169 | KSharedConfigPtr config = KSharedConfig::openConfig(); |
170 | KConfigGroup cg(config, QStringLiteral("UiSettings" )); |
171 | const QString scheme = cg.readEntry(key: "ColorScheme" , aDefault: QString()); |
172 | |
173 | QString schemePath; |
174 | |
175 | if (scheme.isEmpty() || scheme == QLatin1String("Default" )) { |
176 | // Color scheme might be already set from a platform theme |
177 | // This is used for example by QGnomePlatform that can set color scheme |
178 | // matching GNOME settings. This avoids issues where QGnomePlatform sets |
179 | // QPalette for dark theme, but end up mixing it also with Breeze light |
180 | // that is going to be used as a fallback for apps using KColorScheme. |
181 | // BUG: 447029 |
182 | if (platformThemeSchemePath.isEmpty()) { |
183 | schemePath = d->automaticColorSchemePath(); |
184 | } |
185 | } else { |
186 | const auto index = indexForScheme(name: scheme); |
187 | schemePath = index.data(arole: KColorSchemeModel::PathRole).toString(); |
188 | d->m_activatedScheme = index.data(arole: KColorSchemeModel::IdRole).toString(); |
189 | } |
190 | |
191 | if (!schemePath.isEmpty()) { |
192 | d->activateSchemeInternal(colorSchemePath: schemePath); |
193 | } |
194 | } |
195 | |
196 | QAbstractItemModel *KColorSchemeManager::model() const |
197 | { |
198 | return d->model.get(); |
199 | } |
200 | |
201 | QModelIndex KColorSchemeManagerPrivate::indexForSchemeId(const QString &id) const |
202 | { |
203 | // Empty string is mapped to "reset to the system scheme" |
204 | if (id.isEmpty()) { |
205 | return model->index(row: defaultSchemeRow); |
206 | } |
207 | for (int i = 1; i < model->rowCount(); ++i) { |
208 | QModelIndex index = model->index(row: i); |
209 | if (index.data(arole: KColorSchemeModel::IdRole).toString() == id) { |
210 | return index; |
211 | } |
212 | } |
213 | return QModelIndex(); |
214 | } |
215 | |
216 | void KColorSchemeManager::setAutosaveChanges(bool autosaveChanges) |
217 | { |
218 | d->m_autosaveChanges = autosaveChanges; |
219 | } |
220 | |
221 | QModelIndex KColorSchemeManager::indexForSchemeId(const QString &id) const |
222 | { |
223 | return d->indexForSchemeId(id); |
224 | } |
225 | |
226 | QModelIndex KColorSchemeManager::indexForScheme(const QString &name) const |
227 | { |
228 | // Empty string is mapped to "reset to the system scheme" |
229 | if (name.isEmpty()) { |
230 | return d->model->index(row: defaultSchemeRow); |
231 | } |
232 | for (int i = 1; i < d->model->rowCount(); ++i) { |
233 | QModelIndex index = d->model->index(row: i); |
234 | if (index.data(arole: KColorSchemeModel::NameRole).toString() == name) { |
235 | return index; |
236 | } |
237 | } |
238 | return QModelIndex(); |
239 | } |
240 | |
241 | void KColorSchemeManager::activateScheme(const QModelIndex &index) |
242 | { |
243 | const bool isDefaultEntry = index.data(arole: KColorSchemeModel::PathRole).toString().isEmpty(); |
244 | |
245 | if (index.isValid() && index.model() == d->model.get() && !isDefaultEntry) { |
246 | d->activateSchemeInternal(colorSchemePath: index.data(arole: KColorSchemeModel::PathRole).toString()); |
247 | d->m_activatedScheme = index.data(arole: KColorSchemeModel::IdRole).toString(); |
248 | if (d->m_autosaveChanges) { |
249 | saveSchemeToConfigFile(schemeName: index.data(arole: KColorSchemeModel::NameRole).toString()); |
250 | } |
251 | } else { |
252 | d->activateSchemeInternal(colorSchemePath: d->automaticColorSchemePath()); |
253 | d->m_activatedScheme = QString(); |
254 | if (d->m_autosaveChanges) { |
255 | saveSchemeToConfigFile(schemeName: QString()); |
256 | } |
257 | } |
258 | } |
259 | |
260 | void KColorSchemeManager::saveSchemeToConfigFile(const QString &schemeName) const |
261 | { |
262 | KSharedConfigPtr config = KSharedConfig::openConfig(); |
263 | KConfigGroup cg(config, QStringLiteral("UiSettings" )); |
264 | |
265 | if (schemeName.isEmpty() && !cg.hasDefault(key: "ColorScheme" )) { |
266 | cg.revertToDefault(key: "ColorScheme" ); |
267 | } else { |
268 | cg.writeEntry(key: "ColorScheme" , value: KLocalizedString::removeAcceleratorMarker(label: schemeName)); |
269 | } |
270 | |
271 | cg.sync(); |
272 | } |
273 | |
274 | QString KColorSchemeManager::activeSchemeId() const |
275 | { |
276 | return d->m_activatedScheme; |
277 | } |
278 | |
279 | QString KColorSchemeManager::activeSchemeName() const |
280 | { |
281 | return d->indexForSchemeId(id: d->m_activatedScheme).data(arole: KColorSchemeModel::NameRole).toString(); |
282 | } |
283 | |
284 | KColorSchemeManager *KColorSchemeManager::instance() |
285 | { |
286 | Q_ASSERT(qApp); |
287 | static QPointer<KColorSchemeManager> manager; |
288 | if (!manager) { |
289 | manager = new KColorSchemeManager(GuardApplicationConstructor{}, qApp); |
290 | } |
291 | return manager; |
292 | } |
293 | |
294 | #include "moc_kcolorschememanager.cpp" |
295 | |