1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the plugins of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qgenericunixthemes_p.h" |
41 | |
42 | #include "qpa/qplatformtheme_p.h" |
43 | |
44 | #include <QtGui/QPalette> |
45 | #include <QtGui/QFont> |
46 | #include <QtGui/QGuiApplication> |
47 | #include <QtCore/QDir> |
48 | #include <QtCore/QFileInfo> |
49 | #include <QtCore/QFile> |
50 | #include <QtCore/QDebug> |
51 | #include <QtCore/QHash> |
52 | #if QT_CONFIG(mimetype) |
53 | #include <QtCore/QMimeDatabase> |
54 | #endif |
55 | #include <QtCore/QLoggingCategory> |
56 | #if QT_CONFIG(settings) |
57 | #include <QtCore/QSettings> |
58 | #endif |
59 | #include <QtCore/QVariant> |
60 | #include <QtCore/QStandardPaths> |
61 | #include <QtCore/QStringList> |
62 | #include <private/qguiapplication_p.h> |
63 | #include <qpa/qplatformintegration.h> |
64 | #include <qpa/qplatformservices.h> |
65 | #include <qpa/qplatformdialoghelper.h> |
66 | #ifndef QT_NO_DBUS |
67 | #include "qdbusplatformmenu_p.h" |
68 | #include "qdbusmenubar_p.h" |
69 | #endif |
70 | #if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) |
71 | #include "qdbustrayicon_p.h" |
72 | #endif |
73 | |
74 | #include <algorithm> |
75 | |
76 | QT_BEGIN_NAMESPACE |
77 | |
78 | Q_DECLARE_LOGGING_CATEGORY(qLcTray) |
79 | Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts" ) |
80 | |
81 | ResourceHelper::ResourceHelper() |
82 | { |
83 | std::fill(first: palettes, last: palettes + QPlatformTheme::NPalettes, value: static_cast<QPalette *>(0)); |
84 | std::fill(first: fonts, last: fonts + QPlatformTheme::NFonts, value: static_cast<QFont *>(0)); |
85 | } |
86 | |
87 | void ResourceHelper::clear() |
88 | { |
89 | qDeleteAll(begin: palettes, end: palettes + QPlatformTheme::NPalettes); |
90 | qDeleteAll(begin: fonts, end: fonts + QPlatformTheme::NFonts); |
91 | std::fill(first: palettes, last: palettes + QPlatformTheme::NPalettes, value: static_cast<QPalette *>(0)); |
92 | std::fill(first: fonts, last: fonts + QPlatformTheme::NFonts, value: static_cast<QFont *>(0)); |
93 | } |
94 | |
95 | const char *QGenericUnixTheme::name = "generic" ; |
96 | |
97 | // Default system font, corresponding to the value returned by 4.8 for |
98 | // XRender/FontConfig which we can now assume as default. |
99 | static const char defaultSystemFontNameC[] = "Sans Serif" ; |
100 | static const char defaultFixedFontNameC[] = "monospace" ; |
101 | enum { defaultSystemFontSize = 9 }; |
102 | |
103 | #if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) |
104 | static bool isDBusTrayAvailable() { |
105 | static bool dbusTrayAvailable = false; |
106 | static bool dbusTrayAvailableKnown = false; |
107 | if (!dbusTrayAvailableKnown) { |
108 | QDBusMenuConnection conn; |
109 | if (conn.isStatusNotifierHostRegistered()) |
110 | dbusTrayAvailable = true; |
111 | dbusTrayAvailableKnown = true; |
112 | qCDebug(qLcTray) << "D-Bus tray available:" << dbusTrayAvailable; |
113 | } |
114 | return dbusTrayAvailable; |
115 | } |
116 | #endif |
117 | |
118 | #ifndef QT_NO_DBUS |
119 | static bool () |
120 | { |
121 | const QDBusConnection connection = QDBusConnection::sessionBus(); |
122 | static const QString registrarService = QStringLiteral("com.canonical.AppMenu.Registrar" ); |
123 | if (const auto iface = connection.interface()) |
124 | return iface->isServiceRegistered(serviceName: registrarService); |
125 | return false; |
126 | } |
127 | |
128 | static bool () |
129 | { |
130 | static bool = checkDBusGlobalMenuAvailable(); |
131 | return dbusGlobalMenuAvailable; |
132 | } |
133 | #endif |
134 | |
135 | class QGenericUnixThemePrivate : public QPlatformThemePrivate |
136 | { |
137 | public: |
138 | QGenericUnixThemePrivate() |
139 | : QPlatformThemePrivate() |
140 | , systemFont(QLatin1String(defaultSystemFontNameC), defaultSystemFontSize) |
141 | , fixedFont(QLatin1String(defaultFixedFontNameC), systemFont.pointSize()) |
142 | { |
143 | fixedFont.setStyleHint(QFont::TypeWriter); |
144 | qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont; |
145 | } |
146 | |
147 | const QFont systemFont; |
148 | QFont fixedFont; |
149 | }; |
150 | |
151 | QGenericUnixTheme::QGenericUnixTheme() |
152 | : QPlatformTheme(new QGenericUnixThemePrivate()) |
153 | { |
154 | } |
155 | |
156 | const QFont *QGenericUnixTheme::font(Font type) const |
157 | { |
158 | Q_D(const QGenericUnixTheme); |
159 | switch (type) { |
160 | case QPlatformTheme::SystemFont: |
161 | return &d->systemFont; |
162 | case QPlatformTheme::FixedFont: |
163 | return &d->fixedFont; |
164 | default: |
165 | return 0; |
166 | } |
167 | } |
168 | |
169 | // Helper to return the icon theme paths from XDG. |
170 | QStringList QGenericUnixTheme::xdgIconThemePaths() |
171 | { |
172 | QStringList paths; |
173 | // Add home directory first in search path |
174 | const QFileInfo homeIconDir(QDir::homePath() + QLatin1String("/.icons" )); |
175 | if (homeIconDir.isDir()) |
176 | paths.prepend(t: homeIconDir.absoluteFilePath()); |
177 | |
178 | paths.append(t: QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, |
179 | QStringLiteral("icons" ), |
180 | options: QStandardPaths::LocateDirectory)); |
181 | |
182 | return paths; |
183 | } |
184 | |
185 | QStringList QGenericUnixTheme::iconFallbackPaths() |
186 | { |
187 | QStringList paths; |
188 | const QFileInfo pixmapsIconsDir(QStringLiteral("/usr/share/pixmaps" )); |
189 | if (pixmapsIconsDir.isDir()) |
190 | paths.append(t: pixmapsIconsDir.absoluteFilePath()); |
191 | |
192 | return paths; |
193 | } |
194 | |
195 | #ifndef QT_NO_DBUS |
196 | QPlatformMenuBar *QGenericUnixTheme::() const |
197 | { |
198 | if (isDBusGlobalMenuAvailable()) |
199 | return new QDBusMenuBar(); |
200 | return nullptr; |
201 | } |
202 | #endif |
203 | |
204 | #if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) |
205 | QPlatformSystemTrayIcon *QGenericUnixTheme::createPlatformSystemTrayIcon() const |
206 | { |
207 | if (isDBusTrayAvailable()) |
208 | return new QDBusTrayIcon(); |
209 | return nullptr; |
210 | } |
211 | #endif |
212 | |
213 | QVariant QGenericUnixTheme::themeHint(ThemeHint hint) const |
214 | { |
215 | switch (hint) { |
216 | case QPlatformTheme::SystemIconFallbackThemeName: |
217 | return QVariant(QString(QStringLiteral("hicolor" ))); |
218 | case QPlatformTheme::IconThemeSearchPaths: |
219 | return xdgIconThemePaths(); |
220 | case QPlatformTheme::IconFallbackSearchPaths: |
221 | return iconFallbackPaths(); |
222 | case QPlatformTheme::DialogButtonBoxButtonsHaveIcons: |
223 | return QVariant(true); |
224 | case QPlatformTheme::StyleNames: { |
225 | QStringList styleNames; |
226 | styleNames << QStringLiteral("Fusion" ) << QStringLiteral("Windows" ); |
227 | return QVariant(styleNames); |
228 | } |
229 | case QPlatformTheme::KeyboardScheme: |
230 | return QVariant(int(X11KeyboardScheme)); |
231 | case QPlatformTheme::UiEffects: |
232 | return QVariant(int(HoverEffect)); |
233 | default: |
234 | break; |
235 | } |
236 | return QPlatformTheme::themeHint(hint); |
237 | } |
238 | |
239 | // Helper functions for implementing QPlatformTheme::fileIcon() for XDG icon themes. |
240 | static QList<QSize> availableXdgFileIconSizes() |
241 | { |
242 | return QIcon::fromTheme(QStringLiteral("inode-directory" )).availableSizes(); |
243 | } |
244 | |
245 | #if QT_CONFIG(mimetype) |
246 | static QIcon xdgFileIcon(const QFileInfo &fileInfo) |
247 | { |
248 | QMimeDatabase mimeDatabase; |
249 | QMimeType mimeType = mimeDatabase.mimeTypeForFile(fileInfo); |
250 | if (!mimeType.isValid()) |
251 | return QIcon(); |
252 | const QString &iconName = mimeType.iconName(); |
253 | if (!iconName.isEmpty()) { |
254 | const QIcon icon = QIcon::fromTheme(name: iconName); |
255 | if (!icon.isNull()) |
256 | return icon; |
257 | } |
258 | const QString &genericIconName = mimeType.genericIconName(); |
259 | return genericIconName.isEmpty() ? QIcon() : QIcon::fromTheme(name: genericIconName); |
260 | } |
261 | #endif |
262 | |
263 | #if QT_CONFIG(settings) |
264 | class QKdeThemePrivate : public QPlatformThemePrivate |
265 | { |
266 | public: |
267 | QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion) |
268 | : kdeDirs(kdeDirs) |
269 | , kdeVersion(kdeVersion) |
270 | { } |
271 | |
272 | static QString kdeGlobals(const QString &kdeDir, int kdeVersion) |
273 | { |
274 | if (kdeVersion > 4) |
275 | return kdeDir + QLatin1String("/kdeglobals" ); |
276 | return kdeDir + QLatin1String("/share/config/kdeglobals" ); |
277 | } |
278 | |
279 | void refresh(); |
280 | static QVariant readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings); |
281 | static void readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal); |
282 | static QFont *kdeFont(const QVariant &fontValue); |
283 | static QStringList kdeIconThemeSearchPaths(const QStringList &kdeDirs); |
284 | |
285 | const QStringList kdeDirs; |
286 | const int kdeVersion; |
287 | |
288 | ResourceHelper resources; |
289 | QString iconThemeName; |
290 | QString iconFallbackThemeName; |
291 | QStringList styleNames; |
292 | int toolButtonStyle = Qt::ToolButtonTextBesideIcon; |
293 | int toolBarIconSize = 0; |
294 | bool singleClick = true; |
295 | bool showIconsOnPushButtons = true; |
296 | int wheelScrollLines = 3; |
297 | int doubleClickInterval = 400; |
298 | int startDragDist = 10; |
299 | int startDragTime = 500; |
300 | int cursorBlinkRate = 1000; |
301 | }; |
302 | |
303 | void QKdeThemePrivate::refresh() |
304 | { |
305 | resources.clear(); |
306 | |
307 | toolButtonStyle = Qt::ToolButtonTextBesideIcon; |
308 | toolBarIconSize = 0; |
309 | styleNames.clear(); |
310 | if (kdeVersion >= 5) |
311 | styleNames << QStringLiteral("breeze" ); |
312 | styleNames << QStringLiteral("Oxygen" ) << QStringLiteral("fusion" ) << QStringLiteral("windows" ); |
313 | if (kdeVersion >= 5) |
314 | iconFallbackThemeName = iconThemeName = QStringLiteral("breeze" ); |
315 | else |
316 | iconFallbackThemeName = iconThemeName = QStringLiteral("oxygen" ); |
317 | |
318 | QHash<QString, QSettings*> kdeSettings; |
319 | |
320 | QPalette systemPalette = QPalette(); |
321 | readKdeSystemPalette(kdeDirs, kdeVersion, kdeSettings, pal: &systemPalette); |
322 | resources.palettes[QPlatformTheme::SystemPalette] = new QPalette(systemPalette); |
323 | //## TODO tooltip color |
324 | |
325 | const QVariant styleValue = readKdeSetting(QStringLiteral("widgetStyle" ), kdeDirs, kdeVersion, kdeSettings); |
326 | if (styleValue.isValid()) { |
327 | const QString style = styleValue.toString(); |
328 | if (style != styleNames.front()) |
329 | styleNames.push_front(t: style); |
330 | } |
331 | |
332 | const QVariant singleClickValue = readKdeSetting(QStringLiteral("KDE/SingleClick" ), kdeDirs, kdeVersion, kdeSettings); |
333 | if (singleClickValue.isValid()) |
334 | singleClick = singleClickValue.toBool(); |
335 | |
336 | const QVariant showIconsOnPushButtonsValue = readKdeSetting(QStringLiteral("KDE/ShowIconsOnPushButtons" ), kdeDirs, kdeVersion, kdeSettings); |
337 | if (showIconsOnPushButtonsValue.isValid()) |
338 | showIconsOnPushButtons = showIconsOnPushButtonsValue.toBool(); |
339 | |
340 | const QVariant themeValue = readKdeSetting(QStringLiteral("Icons/Theme" ), kdeDirs, kdeVersion, kdeSettings); |
341 | if (themeValue.isValid()) |
342 | iconThemeName = themeValue.toString(); |
343 | |
344 | const QVariant toolBarIconSizeValue = readKdeSetting(QStringLiteral("ToolbarIcons/Size" ), kdeDirs, kdeVersion, kdeSettings); |
345 | if (toolBarIconSizeValue.isValid()) |
346 | toolBarIconSize = toolBarIconSizeValue.toInt(); |
347 | |
348 | const QVariant toolbarStyleValue = readKdeSetting(QStringLiteral("Toolbar style/ToolButtonStyle" ), kdeDirs, kdeVersion, kdeSettings); |
349 | if (toolbarStyleValue.isValid()) { |
350 | const QString toolBarStyle = toolbarStyleValue.toString(); |
351 | if (toolBarStyle == QLatin1String("TextBesideIcon" )) |
352 | toolButtonStyle = Qt::ToolButtonTextBesideIcon; |
353 | else if (toolBarStyle == QLatin1String("TextOnly" )) |
354 | toolButtonStyle = Qt::ToolButtonTextOnly; |
355 | else if (toolBarStyle == QLatin1String("TextUnderIcon" )) |
356 | toolButtonStyle = Qt::ToolButtonTextUnderIcon; |
357 | } |
358 | |
359 | const QVariant wheelScrollLinesValue = readKdeSetting(QStringLiteral("KDE/WheelScrollLines" ), kdeDirs, kdeVersion, kdeSettings); |
360 | if (wheelScrollLinesValue.isValid()) |
361 | wheelScrollLines = wheelScrollLinesValue.toInt(); |
362 | |
363 | const QVariant doubleClickIntervalValue = readKdeSetting(QStringLiteral("KDE/DoubleClickInterval" ), kdeDirs, kdeVersion, kdeSettings); |
364 | if (doubleClickIntervalValue.isValid()) |
365 | doubleClickInterval = doubleClickIntervalValue.toInt(); |
366 | |
367 | const QVariant startDragDistValue = readKdeSetting(QStringLiteral("KDE/StartDragDist" ), kdeDirs, kdeVersion, kdeSettings); |
368 | if (startDragDistValue.isValid()) |
369 | startDragDist = startDragDistValue.toInt(); |
370 | |
371 | const QVariant startDragTimeValue = readKdeSetting(QStringLiteral("KDE/StartDragTime" ), kdeDirs, kdeVersion, kdeSettings); |
372 | if (startDragTimeValue.isValid()) |
373 | startDragTime = startDragTimeValue.toInt(); |
374 | |
375 | const QVariant cursorBlinkRateValue = readKdeSetting(QStringLiteral("KDE/CursorBlinkRate" ), kdeDirs, kdeVersion, kdeSettings); |
376 | if (cursorBlinkRateValue.isValid()) { |
377 | cursorBlinkRate = cursorBlinkRateValue.toInt(); |
378 | cursorBlinkRate = cursorBlinkRate > 0 ? qBound(min: 200, val: cursorBlinkRate, max: 2000) : 0; |
379 | } |
380 | |
381 | // Read system font, ignore 'smallestReadableFont' |
382 | if (QFont *systemFont = kdeFont(fontValue: readKdeSetting(QStringLiteral("font" ), kdeDirs, kdeVersion, kdeSettings))) |
383 | resources.fonts[QPlatformTheme::SystemFont] = systemFont; |
384 | else |
385 | resources.fonts[QPlatformTheme::SystemFont] = new QFont(QLatin1String(defaultSystemFontNameC), defaultSystemFontSize); |
386 | |
387 | if (QFont *fixedFont = kdeFont(fontValue: readKdeSetting(QStringLiteral("fixed" ), kdeDirs, kdeVersion, kdeSettings))) { |
388 | resources.fonts[QPlatformTheme::FixedFont] = fixedFont; |
389 | } else { |
390 | fixedFont = new QFont(QLatin1String(defaultFixedFontNameC), defaultSystemFontSize); |
391 | fixedFont->setStyleHint(QFont::TypeWriter); |
392 | resources.fonts[QPlatformTheme::FixedFont] = fixedFont; |
393 | } |
394 | |
395 | if (QFont * = kdeFont(fontValue: readKdeSetting(QStringLiteral("menuFont" ), kdeDirs, kdeVersion, kdeSettings))) { |
396 | resources.fonts[QPlatformTheme::MenuFont] = menuFont; |
397 | resources.fonts[QPlatformTheme::MenuBarFont] = new QFont(*menuFont); |
398 | } |
399 | |
400 | if (QFont *toolBarFont = kdeFont(fontValue: readKdeSetting(QStringLiteral("toolBarFont" ), kdeDirs, kdeVersion, kdeSettings))) |
401 | resources.fonts[QPlatformTheme::ToolButtonFont] = toolBarFont; |
402 | |
403 | qCDebug(lcQpaFonts) << "default fonts: system" << resources.fonts[QPlatformTheme::SystemFont] |
404 | << "fixed" << resources.fonts[QPlatformTheme::FixedFont]; |
405 | qDeleteAll(c: kdeSettings); |
406 | } |
407 | |
408 | QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings) |
409 | { |
410 | for (const QString &kdeDir : kdeDirs) { |
411 | QSettings *settings = kdeSettings.value(akey: kdeDir); |
412 | if (!settings) { |
413 | const QString kdeGlobalsPath = kdeGlobals(kdeDir, kdeVersion); |
414 | if (QFileInfo(kdeGlobalsPath).isReadable()) { |
415 | settings = new QSettings(kdeGlobalsPath, QSettings::IniFormat); |
416 | kdeSettings.insert(akey: kdeDir, avalue: settings); |
417 | } |
418 | } |
419 | if (settings) { |
420 | const QVariant value = settings->value(key); |
421 | if (value.isValid()) |
422 | return value; |
423 | } |
424 | } |
425 | return QVariant(); |
426 | } |
427 | |
428 | // Reads the color from the KDE configuration, and store it in the |
429 | // palette with the given color role if found. |
430 | static inline bool kdeColor(QPalette *pal, QPalette::ColorRole role, const QVariant &value) |
431 | { |
432 | if (!value.isValid()) |
433 | return false; |
434 | const QStringList values = value.toStringList(); |
435 | if (values.size() != 3) |
436 | return false; |
437 | pal->setBrush(acr: role, abrush: QColor(values.at(i: 0).toInt(), values.at(i: 1).toInt(), values.at(i: 2).toInt())); |
438 | return true; |
439 | } |
440 | |
441 | void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal) |
442 | { |
443 | if (!kdeColor(pal, role: QPalette::Button, value: readKdeSetting(QStringLiteral("Colors:Button/BackgroundNormal" ), kdeDirs, kdeVersion, kdeSettings))) { |
444 | // kcolorscheme.cpp: SetDefaultColors |
445 | const QColor defaultWindowBackground(214, 210, 208); |
446 | const QColor defaultButtonBackground(223, 220, 217); |
447 | *pal = QPalette(defaultButtonBackground, defaultWindowBackground); |
448 | return; |
449 | } |
450 | |
451 | kdeColor(pal, role: QPalette::Window, value: readKdeSetting(QStringLiteral("Colors:Window/BackgroundNormal" ), kdeDirs, kdeVersion, kdeSettings)); |
452 | kdeColor(pal, role: QPalette::Text, value: readKdeSetting(QStringLiteral("Colors:View/ForegroundNormal" ), kdeDirs, kdeVersion, kdeSettings)); |
453 | kdeColor(pal, role: QPalette::WindowText, value: readKdeSetting(QStringLiteral("Colors:Window/ForegroundNormal" ), kdeDirs, kdeVersion, kdeSettings)); |
454 | kdeColor(pal, role: QPalette::Base, value: readKdeSetting(QStringLiteral("Colors:View/BackgroundNormal" ), kdeDirs, kdeVersion, kdeSettings)); |
455 | kdeColor(pal, role: QPalette::Highlight, value: readKdeSetting(QStringLiteral("Colors:Selection/BackgroundNormal" ), kdeDirs, kdeVersion, kdeSettings)); |
456 | kdeColor(pal, role: QPalette::HighlightedText, value: readKdeSetting(QStringLiteral("Colors:Selection/ForegroundNormal" ), kdeDirs, kdeVersion, kdeSettings)); |
457 | kdeColor(pal, role: QPalette::AlternateBase, value: readKdeSetting(QStringLiteral("Colors:View/BackgroundAlternate" ), kdeDirs, kdeVersion, kdeSettings)); |
458 | kdeColor(pal, role: QPalette::ButtonText, value: readKdeSetting(QStringLiteral("Colors:Button/ForegroundNormal" ), kdeDirs, kdeVersion, kdeSettings)); |
459 | kdeColor(pal, role: QPalette::Link, value: readKdeSetting(QStringLiteral("Colors:View/ForegroundLink" ), kdeDirs, kdeVersion, kdeSettings)); |
460 | kdeColor(pal, role: QPalette::LinkVisited, value: readKdeSetting(QStringLiteral("Colors:View/ForegroundVisited" ), kdeDirs, kdeVersion, kdeSettings)); |
461 | kdeColor(pal, role: QPalette::ToolTipBase, value: readKdeSetting(QStringLiteral("Colors:Tooltip/BackgroundNormal" ), kdeDirs, kdeVersion, kdeSettings)); |
462 | kdeColor(pal, role: QPalette::ToolTipText, value: readKdeSetting(QStringLiteral("Colors:Tooltip/ForegroundNormal" ), kdeDirs, kdeVersion, kdeSettings)); |
463 | |
464 | // The above code sets _all_ color roles to "normal" colors. In KDE, the disabled |
465 | // color roles are calculated by applying various effects described in kdeglobals. |
466 | // We use a bit simpler approach here, similar logic than in qt_palette_from_color(). |
467 | const QColor button = pal->color(cr: QPalette::Button); |
468 | int h, s, v; |
469 | button.getHsv(h: &h, s: &s, v: &v); |
470 | |
471 | const QBrush whiteBrush = QBrush(Qt::white); |
472 | const QBrush buttonBrush = QBrush(button); |
473 | const QBrush buttonBrushDark = QBrush(button.darker(f: v > 128 ? 200 : 50)); |
474 | const QBrush buttonBrushDark150 = QBrush(button.darker(f: v > 128 ? 150 : 75)); |
475 | const QBrush buttonBrushLight150 = QBrush(button.lighter(f: v > 128 ? 150 : 75)); |
476 | const QBrush buttonBrushLight = QBrush(button.lighter(f: v > 128 ? 200 : 50)); |
477 | |
478 | pal->setBrush(cg: QPalette::Disabled, cr: QPalette::WindowText, brush: buttonBrushDark); |
479 | pal->setBrush(cg: QPalette::Disabled, cr: QPalette::ButtonText, brush: buttonBrushDark); |
480 | pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Button, brush: buttonBrush); |
481 | pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Text, brush: buttonBrushDark); |
482 | pal->setBrush(cg: QPalette::Disabled, cr: QPalette::BrightText, brush: whiteBrush); |
483 | pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Base, brush: buttonBrush); |
484 | pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Window, brush: buttonBrush); |
485 | pal->setBrush(cg: QPalette::Disabled, cr: QPalette::Highlight, brush: buttonBrushDark150); |
486 | pal->setBrush(cg: QPalette::Disabled, cr: QPalette::HighlightedText, brush: buttonBrushLight150); |
487 | |
488 | // set calculated colors for all groups |
489 | pal->setBrush(acr: QPalette::Light, abrush: buttonBrushLight); |
490 | pal->setBrush(acr: QPalette::Midlight, abrush: buttonBrushLight150); |
491 | pal->setBrush(acr: QPalette::Mid, abrush: buttonBrushDark150); |
492 | pal->setBrush(acr: QPalette::Dark, abrush: buttonBrushDark); |
493 | } |
494 | |
495 | /*! |
496 | \class QKdeTheme |
497 | \brief QKdeTheme is a theme implementation for the KDE desktop (version 4 or higher). |
498 | \since 5.0 |
499 | \internal |
500 | \ingroup qpa |
501 | */ |
502 | |
503 | const char *QKdeTheme::name = "kde" ; |
504 | |
505 | QKdeTheme::QKdeTheme(const QStringList& kdeDirs, int kdeVersion) |
506 | : QPlatformTheme(new QKdeThemePrivate(kdeDirs,kdeVersion)) |
507 | { |
508 | d_func()->refresh(); |
509 | } |
510 | |
511 | QFont *QKdeThemePrivate::kdeFont(const QVariant &fontValue) |
512 | { |
513 | if (fontValue.isValid()) { |
514 | // Read font value: Might be a QStringList as KDE stores fonts without quotes. |
515 | // Also retrieve the family for the constructor since we cannot use the |
516 | // default constructor of QFont, which accesses QGuiApplication::systemFont() |
517 | // causing recursion. |
518 | QString fontDescription; |
519 | QString fontFamily; |
520 | if (fontValue.userType() == QMetaType::QStringList) { |
521 | const QStringList list = fontValue.toStringList(); |
522 | if (!list.isEmpty()) { |
523 | fontFamily = list.first(); |
524 | fontDescription = list.join(sep: QLatin1Char(',')); |
525 | } |
526 | } else { |
527 | fontDescription = fontFamily = fontValue.toString(); |
528 | } |
529 | if (!fontDescription.isEmpty()) { |
530 | QFont font(fontFamily); |
531 | if (font.fromString(fontDescription)) |
532 | return new QFont(font); |
533 | } |
534 | } |
535 | return 0; |
536 | } |
537 | |
538 | |
539 | QStringList QKdeThemePrivate::kdeIconThemeSearchPaths(const QStringList &kdeDirs) |
540 | { |
541 | QStringList paths = QGenericUnixTheme::xdgIconThemePaths(); |
542 | const QString iconPath = QStringLiteral("/share/icons" ); |
543 | for (const QString &candidate : kdeDirs) { |
544 | const QFileInfo fi(candidate + iconPath); |
545 | if (fi.isDir()) |
546 | paths.append(t: fi.absoluteFilePath()); |
547 | } |
548 | return paths; |
549 | } |
550 | |
551 | QVariant QKdeTheme::themeHint(QPlatformTheme::ThemeHint hint) const |
552 | { |
553 | Q_D(const QKdeTheme); |
554 | switch (hint) { |
555 | case QPlatformTheme::UseFullScreenForPopupMenu: |
556 | return QVariant(true); |
557 | case QPlatformTheme::DialogButtonBoxButtonsHaveIcons: |
558 | return QVariant(d->showIconsOnPushButtons); |
559 | case QPlatformTheme::DialogButtonBoxLayout: |
560 | return QVariant(QPlatformDialogHelper::KdeLayout); |
561 | case QPlatformTheme::ToolButtonStyle: |
562 | return QVariant(d->toolButtonStyle); |
563 | case QPlatformTheme::ToolBarIconSize: |
564 | return QVariant(d->toolBarIconSize); |
565 | case QPlatformTheme::SystemIconThemeName: |
566 | return QVariant(d->iconThemeName); |
567 | case QPlatformTheme::SystemIconFallbackThemeName: |
568 | return QVariant(d->iconFallbackThemeName); |
569 | case QPlatformTheme::IconThemeSearchPaths: |
570 | return QVariant(d->kdeIconThemeSearchPaths(kdeDirs: d->kdeDirs)); |
571 | case QPlatformTheme::IconPixmapSizes: |
572 | return QVariant::fromValue(value: availableXdgFileIconSizes()); |
573 | case QPlatformTheme::StyleNames: |
574 | return QVariant(d->styleNames); |
575 | case QPlatformTheme::KeyboardScheme: |
576 | return QVariant(int(KdeKeyboardScheme)); |
577 | case QPlatformTheme::ItemViewActivateItemOnSingleClick: |
578 | return QVariant(d->singleClick); |
579 | case QPlatformTheme::WheelScrollLines: |
580 | return QVariant(d->wheelScrollLines); |
581 | case QPlatformTheme::MouseDoubleClickInterval: |
582 | return QVariant(d->doubleClickInterval); |
583 | case QPlatformTheme::StartDragTime: |
584 | return QVariant(d->startDragTime); |
585 | case QPlatformTheme::StartDragDistance: |
586 | return QVariant(d->startDragDist); |
587 | case QPlatformTheme::CursorFlashTime: |
588 | return QVariant(d->cursorBlinkRate); |
589 | case QPlatformTheme::UiEffects: |
590 | return QVariant(int(HoverEffect)); |
591 | default: |
592 | break; |
593 | } |
594 | return QPlatformTheme::themeHint(hint); |
595 | } |
596 | |
597 | QIcon QKdeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions) const |
598 | { |
599 | #if QT_CONFIG(mimetype) |
600 | return xdgFileIcon(fileInfo); |
601 | #else |
602 | Q_UNUSED(fileInfo); |
603 | return QIcon(); |
604 | #endif |
605 | } |
606 | |
607 | const QPalette *QKdeTheme::palette(Palette type) const |
608 | { |
609 | Q_D(const QKdeTheme); |
610 | return d->resources.palettes[type]; |
611 | } |
612 | |
613 | const QFont *QKdeTheme::font(Font type) const |
614 | { |
615 | Q_D(const QKdeTheme); |
616 | return d->resources.fonts[type]; |
617 | } |
618 | |
619 | QPlatformTheme *QKdeTheme::createKdeTheme() |
620 | { |
621 | const QByteArray kdeVersionBA = qgetenv(varName: "KDE_SESSION_VERSION" ); |
622 | const int kdeVersion = kdeVersionBA.toInt(); |
623 | if (kdeVersion < 4) |
624 | return 0; |
625 | |
626 | if (kdeVersion > 4) |
627 | // Plasma 5 follows XDG spec |
628 | // but uses the same config file format: |
629 | return new QKdeTheme(QStandardPaths::standardLocations(type: QStandardPaths::GenericConfigLocation), kdeVersion); |
630 | |
631 | // Determine KDE prefixes in the following priority order: |
632 | // - KDEHOME and KDEDIRS environment variables |
633 | // - ~/.kde(<version>) |
634 | // - read prefixes from /etc/kde<version>rc |
635 | // - fallback to /etc/kde<version> |
636 | |
637 | QStringList kdeDirs; |
638 | const QString kdeHomePathVar = QFile::decodeName(localFileName: qgetenv(varName: "KDEHOME" )); |
639 | if (!kdeHomePathVar.isEmpty()) |
640 | kdeDirs += kdeHomePathVar; |
641 | |
642 | const QString kdeDirsVar = QFile::decodeName(localFileName: qgetenv(varName: "KDEDIRS" )); |
643 | if (!kdeDirsVar.isEmpty()) |
644 | kdeDirs += kdeDirsVar.split(sep: QLatin1Char(':'), behavior: Qt::SkipEmptyParts); |
645 | |
646 | const QString kdeVersionHomePath = QDir::homePath() + QLatin1String("/.kde" ) + QLatin1String(kdeVersionBA); |
647 | if (QFileInfo(kdeVersionHomePath).isDir()) |
648 | kdeDirs += kdeVersionHomePath; |
649 | |
650 | const QString kdeHomePath = QDir::homePath() + QLatin1String("/.kde" ); |
651 | if (QFileInfo(kdeHomePath).isDir()) |
652 | kdeDirs += kdeHomePath; |
653 | |
654 | const QString kdeRcPath = QLatin1String("/etc/kde" ) + QLatin1String(kdeVersionBA) + QLatin1String("rc" ); |
655 | if (QFileInfo(kdeRcPath).isReadable()) { |
656 | QSettings kdeSettings(kdeRcPath, QSettings::IniFormat); |
657 | kdeSettings.beginGroup(QStringLiteral("Directories-default" )); |
658 | kdeDirs += kdeSettings.value(QStringLiteral("prefixes" )).toStringList(); |
659 | } |
660 | |
661 | const QString kdeVersionPrefix = QLatin1String("/etc/kde" ) + QLatin1String(kdeVersionBA); |
662 | if (QFileInfo(kdeVersionPrefix).isDir()) |
663 | kdeDirs += kdeVersionPrefix; |
664 | |
665 | kdeDirs.removeDuplicates(); |
666 | if (kdeDirs.isEmpty()) { |
667 | qWarning(msg: "Unable to determine KDE dirs" ); |
668 | return 0; |
669 | } |
670 | |
671 | return new QKdeTheme(kdeDirs, kdeVersion); |
672 | } |
673 | |
674 | #ifndef QT_NO_DBUS |
675 | QPlatformMenuBar *QKdeTheme::() const |
676 | { |
677 | if (isDBusGlobalMenuAvailable()) |
678 | return new QDBusMenuBar(); |
679 | return nullptr; |
680 | } |
681 | #endif |
682 | |
683 | #if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) |
684 | QPlatformSystemTrayIcon *QKdeTheme::createPlatformSystemTrayIcon() const |
685 | { |
686 | if (isDBusTrayAvailable()) |
687 | return new QDBusTrayIcon(); |
688 | return nullptr; |
689 | } |
690 | #endif |
691 | |
692 | #endif // settings |
693 | |
694 | /*! |
695 | \class QGnomeTheme |
696 | \brief QGnomeTheme is a theme implementation for the Gnome desktop. |
697 | \since 5.0 |
698 | \internal |
699 | \ingroup qpa |
700 | */ |
701 | |
702 | const char *QGnomeTheme::name = "gnome" ; |
703 | |
704 | class QGnomeThemePrivate : public QPlatformThemePrivate |
705 | { |
706 | public: |
707 | QGnomeThemePrivate() : systemFont(nullptr), fixedFont(nullptr) {} |
708 | ~QGnomeThemePrivate() { delete systemFont; delete fixedFont; } |
709 | |
710 | void configureFonts(const QString >kFontName) const |
711 | { |
712 | Q_ASSERT(!systemFont); |
713 | const int split = gtkFontName.lastIndexOf(c: QChar::Space); |
714 | float size = gtkFontName.midRef(position: split + 1).toFloat(); |
715 | QString fontName = gtkFontName.left(n: split); |
716 | |
717 | systemFont = new QFont(fontName, size); |
718 | fixedFont = new QFont(QLatin1String(defaultFixedFontNameC), systemFont->pointSize()); |
719 | fixedFont->setStyleHint(QFont::TypeWriter); |
720 | qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont; |
721 | } |
722 | |
723 | mutable QFont *systemFont; |
724 | mutable QFont *fixedFont; |
725 | }; |
726 | |
727 | QGnomeTheme::QGnomeTheme() |
728 | : QPlatformTheme(new QGnomeThemePrivate()) |
729 | { |
730 | } |
731 | |
732 | QVariant QGnomeTheme::themeHint(QPlatformTheme::ThemeHint hint) const |
733 | { |
734 | switch (hint) { |
735 | case QPlatformTheme::DialogButtonBoxButtonsHaveIcons: |
736 | return QVariant(true); |
737 | case QPlatformTheme::DialogButtonBoxLayout: |
738 | return QVariant(QPlatformDialogHelper::GnomeLayout); |
739 | case QPlatformTheme::SystemIconThemeName: |
740 | return QVariant(QStringLiteral("Adwaita" )); |
741 | case QPlatformTheme::SystemIconFallbackThemeName: |
742 | return QVariant(QStringLiteral("gnome" )); |
743 | case QPlatformTheme::IconThemeSearchPaths: |
744 | return QVariant(QGenericUnixTheme::xdgIconThemePaths()); |
745 | case QPlatformTheme::IconPixmapSizes: |
746 | return QVariant::fromValue(value: availableXdgFileIconSizes()); |
747 | case QPlatformTheme::StyleNames: { |
748 | QStringList styleNames; |
749 | styleNames << QStringLiteral("fusion" ) << QStringLiteral("windows" ); |
750 | return QVariant(styleNames); |
751 | } |
752 | case QPlatformTheme::KeyboardScheme: |
753 | return QVariant(int(GnomeKeyboardScheme)); |
754 | case QPlatformTheme::PasswordMaskCharacter: |
755 | return QVariant(QChar(0x2022)); |
756 | case QPlatformTheme::UiEffects: |
757 | return QVariant(int(HoverEffect)); |
758 | default: |
759 | break; |
760 | } |
761 | return QPlatformTheme::themeHint(hint); |
762 | } |
763 | |
764 | QIcon QGnomeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions) const |
765 | { |
766 | #if QT_CONFIG(mimetype) |
767 | return xdgFileIcon(fileInfo); |
768 | #else |
769 | Q_UNUSED(fileInfo); |
770 | return QIcon(); |
771 | #endif |
772 | } |
773 | |
774 | const QFont *QGnomeTheme::font(Font type) const |
775 | { |
776 | Q_D(const QGnomeTheme); |
777 | if (!d->systemFont) |
778 | d->configureFonts(gtkFontName: gtkFontName()); |
779 | switch (type) { |
780 | case QPlatformTheme::SystemFont: |
781 | return d->systemFont; |
782 | case QPlatformTheme::FixedFont: |
783 | return d->fixedFont; |
784 | default: |
785 | return 0; |
786 | } |
787 | } |
788 | |
789 | QString QGnomeTheme::gtkFontName() const |
790 | { |
791 | return QStringLiteral("%1 %2" ).arg(a: QLatin1String(defaultSystemFontNameC)).arg(a: defaultSystemFontSize); |
792 | } |
793 | |
794 | #ifndef QT_NO_DBUS |
795 | QPlatformMenuBar *QGnomeTheme::() const |
796 | { |
797 | if (isDBusGlobalMenuAvailable()) |
798 | return new QDBusMenuBar(); |
799 | return nullptr; |
800 | } |
801 | #endif |
802 | |
803 | #if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) |
804 | QPlatformSystemTrayIcon *QGnomeTheme::createPlatformSystemTrayIcon() const |
805 | { |
806 | if (isDBusTrayAvailable()) |
807 | return new QDBusTrayIcon(); |
808 | return nullptr; |
809 | } |
810 | #endif |
811 | |
812 | QString QGnomeTheme::standardButtonText(int button) const |
813 | { |
814 | switch (button) { |
815 | case QPlatformDialogHelper::Ok: |
816 | return QCoreApplication::translate(context: "QGnomeTheme" , key: "&OK" ); |
817 | case QPlatformDialogHelper::Save: |
818 | return QCoreApplication::translate(context: "QGnomeTheme" , key: "&Save" ); |
819 | case QPlatformDialogHelper::Cancel: |
820 | return QCoreApplication::translate(context: "QGnomeTheme" , key: "&Cancel" ); |
821 | case QPlatformDialogHelper::Close: |
822 | return QCoreApplication::translate(context: "QGnomeTheme" , key: "&Close" ); |
823 | case QPlatformDialogHelper::Discard: |
824 | return QCoreApplication::translate(context: "QGnomeTheme" , key: "Close without Saving" ); |
825 | default: |
826 | break; |
827 | } |
828 | return QPlatformTheme::standardButtonText(button); |
829 | } |
830 | |
831 | /*! |
832 | \brief Creates a UNIX theme according to the detected desktop environment. |
833 | */ |
834 | |
835 | QPlatformTheme *QGenericUnixTheme::createUnixTheme(const QString &name) |
836 | { |
837 | if (name == QLatin1String(QGenericUnixTheme::name)) |
838 | return new QGenericUnixTheme; |
839 | #if QT_CONFIG(settings) |
840 | if (name == QLatin1String(QKdeTheme::name)) |
841 | if (QPlatformTheme *kdeTheme = QKdeTheme::createKdeTheme()) |
842 | return kdeTheme; |
843 | #endif |
844 | if (name == QLatin1String(QGnomeTheme::name)) |
845 | return new QGnomeTheme; |
846 | return nullptr; |
847 | } |
848 | |
849 | QStringList QGenericUnixTheme::themeNames() |
850 | { |
851 | QStringList result; |
852 | if (QGuiApplication::desktopSettingsAware()) { |
853 | const QByteArray desktopEnvironment = QGuiApplicationPrivate::platformIntegration()->services()->desktopEnvironment(); |
854 | QList<QByteArray> gtkBasedEnvironments; |
855 | gtkBasedEnvironments << "GNOME" |
856 | << "X-CINNAMON" |
857 | << "UNITY" |
858 | << "MATE" |
859 | << "XFCE" |
860 | << "LXDE" ; |
861 | const QList<QByteArray> desktopNames = desktopEnvironment.split(sep: ':'); |
862 | for (const QByteArray &desktopName : desktopNames) { |
863 | if (desktopEnvironment == "KDE" ) { |
864 | #if QT_CONFIG(settings) |
865 | result.push_back(t: QLatin1String(QKdeTheme::name)); |
866 | #endif |
867 | } else if (gtkBasedEnvironments.contains(t: desktopName)) { |
868 | // prefer the GTK3 theme implementation with native dialogs etc. |
869 | result.push_back(QStringLiteral("gtk3" )); |
870 | // fallback to the generic Gnome theme if loading the GTK3 theme fails |
871 | result.push_back(t: QLatin1String(QGnomeTheme::name)); |
872 | } else { |
873 | // unknown, but lowercase the name (our standard practice) and |
874 | // remove any "x-" prefix |
875 | QString s = QString::fromLatin1(str: desktopName.toLower()); |
876 | result.push_back(t: s.startsWith(s: QLatin1String("x-" )) ? s.mid(position: 2) : s); |
877 | } |
878 | } |
879 | } // desktopSettingsAware |
880 | result.append(t: QLatin1String(QGenericUnixTheme::name)); |
881 | return result; |
882 | } |
883 | |
884 | QT_END_NAMESPACE |
885 | |