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 
Q_DECLARE_LOGGING_CATEGORY(qLcTray)78 Q_DECLARE_LOGGING_CATEGORY(qLcTray)
79 Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts")
80 
81 ResourceHelper::ResourceHelper()
82 {
83     std::fill(palettes, palettes + QPlatformTheme::NPalettes, static_cast<QPalette *>(0));
84     std::fill(fonts, fonts + QPlatformTheme::NFonts, static_cast<QFont *>(0));
85 }
86 
clear()87 void ResourceHelper::clear()
88 {
89     qDeleteAll(palettes, palettes + QPlatformTheme::NPalettes);
90     qDeleteAll(fonts, fonts + QPlatformTheme::NFonts);
91     std::fill(palettes, palettes + QPlatformTheme::NPalettes, static_cast<QPalette *>(0));
92     std::fill(fonts, fonts + QPlatformTheme::NFonts, 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)
isDBusTrayAvailable()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
checkDBusGlobalMenuAvailable()119 static bool checkDBusGlobalMenuAvailable()
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(registrarService);
125     return false;
126 }
127 
isDBusGlobalMenuAvailable()128 static bool isDBusGlobalMenuAvailable()
129 {
130     static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable();
131     return dbusGlobalMenuAvailable;
132 }
133 #endif
134 
135 class QGenericUnixThemePrivate : public QPlatformThemePrivate
136 {
137 public:
QGenericUnixThemePrivate()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 
QGenericUnixTheme()151 QGenericUnixTheme::QGenericUnixTheme()
152     : QPlatformTheme(new QGenericUnixThemePrivate())
153 {
154 }
155 
font(Font type) const156 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.
xdgIconThemePaths()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(homeIconDir.absoluteFilePath());
177 
178     paths.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
179                                            QStringLiteral("icons"),
180                                            QStandardPaths::LocateDirectory));
181 
182     return paths;
183 }
184 
iconFallbackPaths()185 QStringList QGenericUnixTheme::iconFallbackPaths()
186 {
187     QStringList paths;
188     const QFileInfo pixmapsIconsDir(QStringLiteral("/usr/share/pixmaps"));
189     if (pixmapsIconsDir.isDir())
190         paths.append(pixmapsIconsDir.absoluteFilePath());
191 
192     return paths;
193 }
194 
195 #ifndef QT_NO_DBUS
createPlatformMenuBar() const196 QPlatformMenuBar *QGenericUnixTheme::createPlatformMenuBar() 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)
createPlatformSystemTrayIcon() const205 QPlatformSystemTrayIcon *QGenericUnixTheme::createPlatformSystemTrayIcon() const
206 {
207     if (isDBusTrayAvailable())
208         return new QDBusTrayIcon();
209     return nullptr;
210 }
211 #endif
212 
themeHint(ThemeHint hint) const213 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.
availableXdgFileIconSizes()240 static QList<QSize> availableXdgFileIconSizes()
241 {
242     return QIcon::fromTheme(QStringLiteral("inode-directory")).availableSizes();
243 }
244 
245 #if QT_CONFIG(mimetype)
xdgFileIcon(const QFileInfo & fileInfo)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(iconName);
255         if (!icon.isNull())
256             return icon;
257     }
258     const QString &genericIconName = mimeType.genericIconName();
259     return genericIconName.isEmpty() ? QIcon() : QIcon::fromTheme(genericIconName);
260 }
261 #endif
262 
263 #if QT_CONFIG(settings)
264 class QKdeThemePrivate : public QPlatformThemePrivate
265 {
266 public:
QKdeThemePrivate(const QStringList & kdeDirs,int kdeVersion)267     QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion)
268         : kdeDirs(kdeDirs)
269         , kdeVersion(kdeVersion)
270     { }
271 
kdeGlobals(const QString & kdeDir,int kdeVersion)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 
refresh()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, &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(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(200, cursorBlinkRate, 2000) : 0;
379     }
380 
381     // Read system font, ignore 'smallestReadableFont'
382     if (QFont *systemFont = kdeFont(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(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 *menuFont = kdeFont(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(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(kdeSettings);
406 }
407 
readKdeSetting(const QString & key,const QStringList & kdeDirs,int kdeVersion,QHash<QString,QSettings * > & kdeSettings)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(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(kdeDir, 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.
kdeColor(QPalette * pal,QPalette::ColorRole role,const QVariant & value)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(role, QColor(values.at(0).toInt(), values.at(1).toInt(), values.at(2).toInt()));
438     return true;
439 }
440 
readKdeSystemPalette(const QStringList & kdeDirs,int kdeVersion,QHash<QString,QSettings * > & kdeSettings,QPalette * pal)441 void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal)
442 {
443     if (!kdeColor(pal, QPalette::Button, 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, QPalette::Window, readKdeSetting(QStringLiteral("Colors:Window/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
452     kdeColor(pal, QPalette::Text, readKdeSetting(QStringLiteral("Colors:View/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
453     kdeColor(pal, QPalette::WindowText, readKdeSetting(QStringLiteral("Colors:Window/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
454     kdeColor(pal, QPalette::Base, readKdeSetting(QStringLiteral("Colors:View/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
455     kdeColor(pal, QPalette::Highlight, readKdeSetting(QStringLiteral("Colors:Selection/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
456     kdeColor(pal, QPalette::HighlightedText, readKdeSetting(QStringLiteral("Colors:Selection/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
457     kdeColor(pal, QPalette::AlternateBase, readKdeSetting(QStringLiteral("Colors:View/BackgroundAlternate"), kdeDirs, kdeVersion, kdeSettings));
458     kdeColor(pal, QPalette::ButtonText, readKdeSetting(QStringLiteral("Colors:Button/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings));
459     kdeColor(pal, QPalette::Link, readKdeSetting(QStringLiteral("Colors:View/ForegroundLink"), kdeDirs, kdeVersion, kdeSettings));
460     kdeColor(pal, QPalette::LinkVisited, readKdeSetting(QStringLiteral("Colors:View/ForegroundVisited"), kdeDirs, kdeVersion, kdeSettings));
461     kdeColor(pal, QPalette::ToolTipBase, readKdeSetting(QStringLiteral("Colors:Tooltip/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings));
462     kdeColor(pal, QPalette::ToolTipText, 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(QPalette::Button);
468     int h, s, v;
469     button.getHsv(&h, &s, &v);
470 
471     const QBrush whiteBrush = QBrush(Qt::white);
472     const QBrush buttonBrush = QBrush(button);
473     const QBrush buttonBrushDark = QBrush(button.darker(v > 128 ? 200 : 50));
474     const QBrush buttonBrushDark150 = QBrush(button.darker(v > 128 ? 150 : 75));
475     const QBrush buttonBrushLight150 = QBrush(button.lighter(v > 128 ? 150 : 75));
476     const QBrush buttonBrushLight = QBrush(button.lighter(v > 128 ? 200 : 50));
477 
478     pal->setBrush(QPalette::Disabled, QPalette::WindowText, buttonBrushDark);
479     pal->setBrush(QPalette::Disabled, QPalette::ButtonText, buttonBrushDark);
480     pal->setBrush(QPalette::Disabled, QPalette::Button, buttonBrush);
481     pal->setBrush(QPalette::Disabled, QPalette::Text, buttonBrushDark);
482     pal->setBrush(QPalette::Disabled, QPalette::BrightText, whiteBrush);
483     pal->setBrush(QPalette::Disabled, QPalette::Base, buttonBrush);
484     pal->setBrush(QPalette::Disabled, QPalette::Window, buttonBrush);
485     pal->setBrush(QPalette::Disabled, QPalette::Highlight, buttonBrushDark150);
486     pal->setBrush(QPalette::Disabled, QPalette::HighlightedText, buttonBrushLight150);
487 
488     // set calculated colors for all groups
489     pal->setBrush(QPalette::Light, buttonBrushLight);
490     pal->setBrush(QPalette::Midlight, buttonBrushLight150);
491     pal->setBrush(QPalette::Mid, buttonBrushDark150);
492     pal->setBrush(QPalette::Dark, 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 
QKdeTheme(const QStringList & kdeDirs,int kdeVersion)505 QKdeTheme::QKdeTheme(const QStringList& kdeDirs, int kdeVersion)
506     : QPlatformTheme(new QKdeThemePrivate(kdeDirs,kdeVersion))
507 {
508     d_func()->refresh();
509 }
510 
kdeFont(const QVariant & fontValue)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(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 
kdeIconThemeSearchPaths(const QStringList & kdeDirs)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(fi.absoluteFilePath());
547     }
548     return paths;
549 }
550 
themeHint(QPlatformTheme::ThemeHint hint) const551 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(d->kdeDirs));
571     case QPlatformTheme::IconPixmapSizes:
572         return QVariant::fromValue(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 
fileIcon(const QFileInfo & fileInfo,QPlatformTheme::IconOptions) const597 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 
palette(Palette type) const607 const QPalette *QKdeTheme::palette(Palette type) const
608 {
609     Q_D(const QKdeTheme);
610     return d->resources.palettes[type];
611 }
612 
font(Font type) const613 const QFont *QKdeTheme::font(Font type) const
614 {
615     Q_D(const QKdeTheme);
616     return d->resources.fonts[type];
617 }
618 
createKdeTheme()619 QPlatformTheme *QKdeTheme::createKdeTheme()
620 {
621     const QByteArray kdeVersionBA = qgetenv("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(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(qgetenv("KDEHOME"));
639     if (!kdeHomePathVar.isEmpty())
640         kdeDirs += kdeHomePathVar;
641 
642     const QString kdeDirsVar = QFile::decodeName(qgetenv("KDEDIRS"));
643     if (!kdeDirsVar.isEmpty())
644         kdeDirs += kdeDirsVar.split(QLatin1Char(':'), 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("Unable to determine KDE dirs");
668         return 0;
669     }
670 
671     return new QKdeTheme(kdeDirs, kdeVersion);
672 }
673 
674 #ifndef QT_NO_DBUS
createPlatformMenuBar() const675 QPlatformMenuBar *QKdeTheme::createPlatformMenuBar() 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)
createPlatformSystemTrayIcon() const684 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:
QGnomeThemePrivate()707     QGnomeThemePrivate() : systemFont(nullptr), fixedFont(nullptr) {}
~QGnomeThemePrivate()708     ~QGnomeThemePrivate() { delete systemFont; delete fixedFont; }
709 
configureFonts(const QString & gtkFontName) const710     void configureFonts(const QString &gtkFontName) const
711     {
712         Q_ASSERT(!systemFont);
713         const int split = gtkFontName.lastIndexOf(QChar::Space);
714         float size = gtkFontName.midRef(split + 1).toFloat();
715         QString fontName = gtkFontName.left(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 
QGnomeTheme()727 QGnomeTheme::QGnomeTheme()
728     : QPlatformTheme(new QGnomeThemePrivate())
729 {
730 }
731 
themeHint(QPlatformTheme::ThemeHint hint) const732 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(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 
fileIcon(const QFileInfo & fileInfo,QPlatformTheme::IconOptions) const764 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 
font(Font type) const774 const QFont *QGnomeTheme::font(Font type) const
775 {
776     Q_D(const QGnomeTheme);
777     if (!d->systemFont)
778         d->configureFonts(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 
gtkFontName() const789 QString QGnomeTheme::gtkFontName() const
790 {
791     return QStringLiteral("%1 %2").arg(QLatin1String(defaultSystemFontNameC)).arg(defaultSystemFontSize);
792 }
793 
794 #ifndef QT_NO_DBUS
createPlatformMenuBar() const795 QPlatformMenuBar *QGnomeTheme::createPlatformMenuBar() 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)
createPlatformSystemTrayIcon() const804 QPlatformSystemTrayIcon *QGnomeTheme::createPlatformSystemTrayIcon() const
805 {
806     if (isDBusTrayAvailable())
807         return new QDBusTrayIcon();
808     return nullptr;
809 }
810 #endif
811 
standardButtonText(int button) const812 QString QGnomeTheme::standardButtonText(int button) const
813 {
814     switch (button) {
815     case QPlatformDialogHelper::Ok:
816         return QCoreApplication::translate("QGnomeTheme", "&OK");
817     case QPlatformDialogHelper::Save:
818         return QCoreApplication::translate("QGnomeTheme", "&Save");
819     case QPlatformDialogHelper::Cancel:
820         return QCoreApplication::translate("QGnomeTheme", "&Cancel");
821     case QPlatformDialogHelper::Close:
822         return QCoreApplication::translate("QGnomeTheme", "&Close");
823     case QPlatformDialogHelper::Discard:
824         return QCoreApplication::translate("QGnomeTheme", "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 
createUnixTheme(const QString & name)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 
themeNames()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(':');
862         for (const QByteArray &desktopName : desktopNames) {
863             if (desktopEnvironment == "KDE") {
864 #if QT_CONFIG(settings)
865                 result.push_back(QLatin1String(QKdeTheme::name));
866 #endif
867             } else if (gtkBasedEnvironments.contains(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(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(desktopName.toLower());
876                 result.push_back(s.startsWith(QLatin1String("x-")) ? s.mid(2) : s);
877             }
878         }
879     } // desktopSettingsAware
880     result.append(QLatin1String(QGenericUnixTheme::name));
881     return result;
882 }
883 
884 QT_END_NAMESPACE
885