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#include "qpa/qplatformfontdatabase.h" // lcQpaFonts
44
45#include <QtGui/QPalette>
46#include <QtGui/QFont>
47#include <QtGui/QGuiApplication>
48#include <QtCore/QDir>
49#include <QtCore/QFileInfo>
50#include <QtCore/QFile>
51#include <QtCore/QDebug>
52#include <QtCore/QHash>
53#if QT_CONFIG(mimetype)
54#include <QtCore/QMimeDatabase>
55#endif
56#include <QtCore/QLoggingCategory>
57#if QT_CONFIG(settings)
58#include <QtCore/QSettings>
59#endif
60#include <QtCore/QVariant>
61#include <QtCore/QStandardPaths>
62#include <QtCore/QStringList>
63#include <private/qguiapplication_p.h>
64#include <qpa/qplatformintegration.h>
65#include <qpa/qplatformservices.h>
66#include <qpa/qplatformdialoghelper.h>
67#ifndef QT_NO_DBUS
68#include <private/qdbusplatformmenu_p.h>
69#include <private/qdbusmenubar_p.h>
70#endif
71#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
72#include <private/qdbustrayicon_p.h>
73#endif
74
75#include <algorithm>
76
77QT_BEGIN_NAMESPACE
78
79Q_DECLARE_LOGGING_CATEGORY(qLcTray)
80
81ResourceHelper::ResourceHelper()
82{
83 std::fill(palettes, palettes + QPlatformTheme::NPalettes, static_cast<QPalette *>(nullptr));
84 std::fill(fonts, fonts + QPlatformTheme::NFonts, static_cast<QFont *>(nullptr));
85}
86
87void 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 *>(nullptr));
92 std::fill(fonts, fonts + QPlatformTheme::NFonts, static_cast<QFont *>(nullptr));
93}
94
95const 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.
99static const char defaultSystemFontNameC[] = "Sans Serif";
100static const char defaultFixedFontNameC[] = "monospace";
101enum { defaultSystemFontSize = 9 };
102
103#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
104static 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
119static 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
128static bool isDBusGlobalMenuAvailable()
129{
130 static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable();
131 return dbusGlobalMenuAvailable;
132}
133#endif
134
135class QGenericUnixThemePrivate : public QPlatformThemePrivate
136{
137public:
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
151QGenericUnixTheme::QGenericUnixTheme()
152 : QPlatformTheme(new QGenericUnixThemePrivate())
153{
154}
155
156const 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 nullptr;
166 }
167}
168
169// Helper to return the icon theme paths from XDG.
170QStringList 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
185QStringList 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
196QPlatformMenuBar *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)
205QPlatformSystemTrayIcon *QGenericUnixTheme::createPlatformSystemTrayIcon() const
206{
207 if (isDBusTrayAvailable())
208 return new QDBusTrayIcon();
209 return nullptr;
210}
211#endif
212
213QVariant 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.
240static QList<QSize> availableXdgFileIconSizes()
241{
242 return QIcon::fromTheme(QStringLiteral("inode-directory")).availableSizes();
243}
244
245#if QT_CONFIG(mimetype)
246static 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)
264class QKdeThemePrivate : public QPlatformThemePrivate
265{
266public:
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
303void 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
408QVariant 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.
430static 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
441void 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
503const char *QKdeTheme::name = "kde";
504
505QKdeTheme::QKdeTheme(const QStringList& kdeDirs, int kdeVersion)
506 : QPlatformTheme(new QKdeThemePrivate(kdeDirs,kdeVersion))
507{
508 d_func()->refresh();
509}
510
511QFont *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 nullptr;
536}
537
538
539QStringList 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
551QVariant 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
597QIcon 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
607const QPalette *QKdeTheme::palette(Palette type) const
608{
609 Q_D(const QKdeTheme);
610 return d->resources.palettes[type];
611}
612
613const QFont *QKdeTheme::font(Font type) const
614{
615 Q_D(const QKdeTheme);
616 return d->resources.fonts[type];
617}
618
619QPlatformTheme *QKdeTheme::createKdeTheme()
620{
621 const QByteArray kdeVersionBA = qgetenv("KDE_SESSION_VERSION");
622 const int kdeVersion = kdeVersionBA.toInt();
623 if (kdeVersion < 4)
624 return nullptr;
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 nullptr;
669 }
670
671 return new QKdeTheme(kdeDirs, kdeVersion);
672}
673
674#ifndef QT_NO_DBUS
675QPlatformMenuBar *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)
684QPlatformSystemTrayIcon *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
702const char *QGnomeTheme::name = "gnome";
703
704class QGnomeThemePrivate : public QPlatformThemePrivate
705{
706public:
707 QGnomeThemePrivate() : systemFont(nullptr), fixedFont(nullptr) {}
708 ~QGnomeThemePrivate() { delete systemFont; delete fixedFont; }
709
710 void configureFonts(const QString &gtkFontName) const
711 {
712 Q_ASSERT(!systemFont);
713 const int split = gtkFontName.lastIndexOf(QChar::Space);
714 float size = QStringView{gtkFontName}.mid(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
727QGnomeTheme::QGnomeTheme()
728 : QPlatformTheme(new QGnomeThemePrivate())
729{
730}
731
732QVariant 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
764QIcon 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
774const 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 nullptr;
786 }
787}
788
789QString QGnomeTheme::gtkFontName() const
790{
791 return QStringLiteral("%1 %2").arg(QLatin1String(defaultSystemFontNameC)).arg(defaultSystemFontSize);
792}
793
794#ifndef QT_NO_DBUS
795QPlatformMenuBar *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)
804QPlatformSystemTrayIcon *QGnomeTheme::createPlatformSystemTrayIcon() const
805{
806 if (isDBusTrayAvailable())
807 return new QDBusTrayIcon();
808 return nullptr;
809}
810#endif
811
812QString 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
835QPlatformTheme *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
849QStringList 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
884QT_END_NAMESPACE
885