KColorScheme

kcolorschememanager.cpp
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
32KCONFIGGUI_EXPORT int initKConfigGroupGui();
33static int s_init = initKConfigGroupGui();
34
35constexpr int defaultSchemeRow = 0;
36
37static 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("XDG_CURRENT_DESKTOP") == "KDE" && QGuiApplicationPrivate::platformTheme()->name() == QLatin1String("xdgdesktopportal")) {
48 return true;
49 }
50
51 return false;
52}
53
54void 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("KDE_COLOR_SCHEME_PATH", colorSchemePath);
60 if (colorSchemePath.isEmpty()) {
61 qApp->setPalette(QPalette());
62 } else {
63 qApp->setPalette(KColorScheme::createApplicationPalette(KSharedConfig::openConfig(colorSchemePath)));
64 }
65}
66
67QString KColorSchemeManagerPrivate::automaticColorSchemeId() const
68{
69 if (!m_colorSchemeWatcher) {
70 return QString();
71 }
72
73 switch (m_colorSchemeWatcher->systemPreference()) {
75 return QString();
77 return getDarkColorScheme();
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
88QString KColorSchemeManagerPrivate::automaticColorSchemePath() const
89{
90 const QString colorSchemeId = automaticColorSchemeId();
91 if (colorSchemeId.isEmpty()) {
92 return QString();
93 } else {
94 return indexForSchemeId(colorSchemeId).data(KColorSchemeModel::PathRole).toString();
95 }
96}
97
98QIcon KColorSchemeManagerPrivate::createPreview(const QString &path)
99{
100 KSharedConfigPtr schemeConfig = KSharedConfig::openConfig(path, 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(Qt::black);
111 QPainter p;
112 p.begin(&pix);
113 const int itemSize = size / 2 - 1;
114 p.fillRect(1, 1, itemSize, itemSize, activeWindow.background());
115 p.fillRect(1 + itemSize, 1, itemSize, itemSize, activeButton.background());
116 p.fillRect(1, 1 + itemSize, itemSize, itemSize, activeView.background());
117 p.fillRect(1 + itemSize, 1 + itemSize, itemSize, itemSize, activeSelection.background());
118 p.end();
119 result.addPixmap(pix);
120 };
121 // 16x16
122 pixmap(16);
123 // 24x24
124 pixmap(24);
125
126 return result;
127}
128
129KColorSchemeManagerPrivate::KColorSchemeManagerPrivate()
130 : model(new KColorSchemeModel())
131{
132}
133
134KColorSchemeManager::KColorSchemeManager(GuardApplicationConstructor, QGuiApplication *app)
135 : QObject(app)
136 , d(new KColorSchemeManagerPrivate)
137{
138 init();
139}
140
141#if KCOLORSCHEME_BUILD_DEPRECATED_SINCE(6, 6)
142KColorSchemeManager::KColorSchemeManager(QObject *parent)
143 : QObject(parent)
144 , d(new KColorSchemeManagerPrivate())
145{
146 init();
147}
148#endif
149
150KColorSchemeManager::~KColorSchemeManager()
151{
152}
153
154void KColorSchemeManager::init()
155{
156 QString platformThemeSchemePath = qApp->property("KDE_COLOR_SCHEME_PATH").toString();
157 if (!isKdePlatformTheme() && platformThemeSchemePath.isEmpty()) {
158 d->m_colorSchemeWatcher.emplace();
159 QObject::connect(&*d->m_colorSchemeWatcher, &KColorSchemeWatcher::systemPreferenceChanged, this, [this]() {
160 if (!d->m_activatedScheme.isEmpty()) {
161 // Don't override what has been manually set
162 return;
163 }
164
165 d->activateSchemeInternal(d->automaticColorSchemePath());
166 });
167 }
168
169 KSharedConfigPtr config = KSharedConfig::openConfig();
170 KConfigGroup cg(config, QStringLiteral("UiSettings"));
171 const QString scheme = cg.readEntry("ColorScheme", 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(scheme);
187 schemePath = index.data(KColorSchemeModel::PathRole).toString();
188 d->m_activatedScheme = index.data(KColorSchemeModel::IdRole).toString();
189 }
190
191 if (!schemePath.isEmpty()) {
192 d->activateSchemeInternal(schemePath);
193 }
194}
195
197{
198 return d->model.get();
199}
200
201QModelIndex 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(defaultSchemeRow);
206 }
207 for (int i = 1; i < model->rowCount(); ++i) {
208 QModelIndex index = model->index(i);
209 if (index.data(KColorSchemeModel::IdRole).toString() == id) {
210 return index;
211 }
212 }
213 return QModelIndex();
214}
215
217{
218 d->m_autosaveChanges = autosaveChanges;
219}
220
222{
223 return d->indexForSchemeId(id);
224}
225
227{
228 // Empty string is mapped to "reset to the system scheme"
229 if (name.isEmpty()) {
230 return d->model->index(defaultSchemeRow);
231 }
232 for (int i = 1; i < d->model->rowCount(); ++i) {
233 QModelIndex index = d->model->index(i);
234 if (index.data(KColorSchemeModel::NameRole).toString() == name) {
235 return index;
236 }
237 }
238 return QModelIndex();
239}
240
242{
243 const bool isDefaultEntry = index.data(KColorSchemeModel::PathRole).toString().isEmpty();
244
245 if (index.isValid() && index.model() == d->model.get() && !isDefaultEntry) {
246 d->activateSchemeInternal(index.data(KColorSchemeModel::PathRole).toString());
247 d->m_activatedScheme = index.data(KColorSchemeModel::IdRole).toString();
248 if (d->m_autosaveChanges) {
249 saveSchemeToConfigFile(index.data(KColorSchemeModel::NameRole).toString());
250 }
251 } else {
252 d->activateSchemeInternal(d->automaticColorSchemePath());
253 d->m_activatedScheme = QString();
254 if (d->m_autosaveChanges) {
256 }
257 }
258}
259
261{
262 KSharedConfigPtr config = KSharedConfig::openConfig();
263 KConfigGroup cg(config, QStringLiteral("UiSettings"));
264
265 if (schemeName.isEmpty() && !cg.hasDefault("ColorScheme")) {
266 cg.revertToDefault("ColorScheme");
267 } else {
268 cg.writeEntry("ColorScheme", KLocalizedString::removeAcceleratorMarker(schemeName));
269 }
270
271 cg.sync();
272}
273
275{
276 return d->m_activatedScheme;
277}
278
280{
281 return d->indexForSchemeId(d->m_activatedScheme).data(KColorSchemeModel::NameRole).toString();
282}
283
284KColorSchemeManager *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"
QAbstractItemModel * model() const
A QAbstractItemModel of all available color schemes.
QString activeSchemeId() const
Returns the id of the currently active scheme or an empty string if the default scheme is active.
QModelIndex indexForScheme(const QString &name) const
Returns the model index for the scheme with the given name.
QModelIndex indexForSchemeId(const QString &id) const
Returns the model index for the scheme with the given id.
static KColorSchemeManager * instance()
Returns the manager for the current application instance.
void activateScheme(const QModelIndex &index)
Activates the KColorScheme identified by the provided index.
QString activeSchemeName() const
Returns the name of the currently active scheme or an empty string if the default scheme is active.
void saveSchemeToConfigFile(const QString &schemeName) const
Saves the color scheme to config file.
void setAutosaveChanges(bool autosaveChanges)
Sets color scheme autosaving.
A model listing the KColorSchemes available in the system.
void systemPreferenceChanged()
A set of methods used to work with colors.
static QPalette createApplicationPalette(const KSharedConfigPtr &config)
Used to obtain the QPalette that will be used to set the application palette from KDE Platform theme.
@ View
Views; for example, frames, input fields, etc.
@ Window
Non-editable window elements; for example, menus.
@ Selection
Selected items in views.
@ Button
Buttons and button-like controls.
void revertToDefault(const char *key, WriteConfigFlags pFlag=WriteConfigFlags())
bool hasDefault(const char *key) const
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
bool sync() override
static QString removeAcceleratorMarker(const QString &label)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QString name(StandardAction id)
QCA_EXPORT void init()
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
void addPixmap(const QPixmap &pixmap, Mode mode, State state)
QVariant data(int role) const const
bool isValid() const const
const QAbstractItemModel * model() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool begin(QPaintDevice *device)
bool end()
void fillRect(const QRect &rectangle, QGradient::Preset preset)
QChar * data()
bool isEmpty() const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:57:11 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.