diff options
Diffstat (limited to 'src/plugins/platformthemes')
17 files changed, 911 insertions, 339 deletions
diff --git a/src/plugins/platformthemes/CMakeLists.txt b/src/plugins/platformthemes/CMakeLists.txt index be4adc196b..e5abcd1a11 100644 --- a/src/plugins/platformthemes/CMakeLists.txt +++ b/src/plugins/platformthemes/CMakeLists.txt @@ -1,7 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -# Generated from platformthemes.pro. if(QT_FEATURE_dbus AND QT_FEATURE_mimetype AND QT_FEATURE_regularexpression AND UNIX AND NOT APPLE) add_subdirectory(xdgdesktopportal) diff --git a/src/plugins/platformthemes/gtk3/CMakeLists.txt b/src/plugins/platformthemes/gtk3/CMakeLists.txt index 2ce2971e91..6d3c7bf3a2 100644 --- a/src/plugins/platformthemes/gtk3/CMakeLists.txt +++ b/src/plugins/platformthemes/gtk3/CMakeLists.txt @@ -1,12 +1,10 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -# Generated from gtk3.pro. - -qt_find_package(GTK3) # special case +qt_find_package(GTK3) if(QT_FEATURE_xlib) - qt_find_package(X11) # special case + qt_find_package(X11) endif() ##################################################################### @@ -25,9 +23,11 @@ qt_internal_add_plugin(QGtk3ThemePlugin qgtk3interface.cpp qgtk3interface_p.h qgtk3storage.cpp qgtk3storage_p.h qgtk3json.cpp qgtk3json_p.h + NO_PCH_SOURCES + qgtk3dialoghelpers.cpp # undef QT_NO_FOREACH DEFINES GDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_6 - LIBRARIES # special case + LIBRARIES PkgConfig::GTK3 Qt::Core Qt::CorePrivate @@ -35,10 +35,14 @@ qt_internal_add_plugin(QGtk3ThemePlugin Qt::GuiPrivate ) -qt_internal_extend_target(QGtk3ThemePlugin CONDITION QT_FEATURE_xlib +qt_internal_extend_target(QGtk3ThemePlugin CONDITION QT_FEATURE_dbus + SOURCES + qgtk3portalinterface.cpp LIBRARIES - X11::X11 # special case + Qt::DBus ) -#### Keys ignored in scope 1:.:.:gtk3.pro:<TRUE>: -# PLUGIN_EXTENDS = "-" +qt_internal_extend_target(QGtk3ThemePlugin CONDITION QT_FEATURE_xlib + LIBRARIES + X11::X11 +) diff --git a/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp b/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp index b8ba58d30e..08419ec7dc 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp +++ b/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp @@ -1,6 +1,8 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses + #include "qgtk3dialoghelpers.h" #include "qgtk3theme.h" diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface.cpp b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp index d3195863af..a35e211fbf 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3interface.cpp +++ b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp @@ -16,7 +16,6 @@ #include "qgtk3interface_p.h" #include "qgtk3storage_p.h" #include <QtCore/QMetaEnum> -#include <QtCore/QSettings> #include <QtCore/QFileInfo> #include <QtGui/QFontDatabase> @@ -64,6 +63,14 @@ QGtk3Interface::~QGtk3Interface() gtk_widget_destroy(v.second); } +/*! + \internal + \brief Converts a string into the GtkStateFlags enum. + + Converts a string formatted GTK color \param state into an enum value. + Returns an integer corresponding to GtkStateFlags. + Returns -1 if \param state does not correspond to a valid enum key. + */ int QGtk3Interface::toGtkState(const QString &state) { #define CASE(x) \ @@ -91,6 +98,10 @@ int QGtk3Interface::toGtkState(const QString &state) #undef CASE } +/*! + \internal + \brief Returns \param state converted into a string. + */ const QLatin1String QGtk3Interface::fromGtkState(GtkStateFlags state) { #define CASE(x) case GTK_STATE_FLAG_ ##x: return QLatin1String(#x) @@ -102,9 +113,12 @@ const QLatin1String QGtk3Interface::fromGtkState(GtkStateFlags state) #undef CONVERT } +/*! + \internal + \brief Populates the internal map used to find a GTK color's source and fallback generic color. + */ void QGtk3Interface::initColorMap() { - // Populate map with default values #define SAVE(src, state, prop, def)\ {ColorKey({QGtkColorSource::src, GTK_STATE_FLAG_ ##state}), ColorValue({#prop ##_L1, QGtkColorDefault::def})} @@ -131,8 +145,17 @@ void QGtk3Interface::initColorMap() qCDebug(lcQGtk3Interface) << "Color map populated from defaults."; } -// Return an image rather than an icon or a pixmap: -// Image can be cached and re-scaled to different sizes if requested multiple times +/*! + \internal + \brief Returns a QImage corresponding to \param standardPixmap. + + A QImage (not a QPixmap) is returned so it can be cached and re-scaled in case the pixmap is + requested multiple times with different resolutions. + + \note Rather than defaulting to a QImage(), all QPlatformTheme::StandardPixmap enum values have + been mentioned explicitly. + That way they can be covered more easily in case additional icons are provided by GTK. + */ QImage QGtk3Interface::standardPixmap(QPlatformTheme::StandardPixmap standardPixmap) const { switch (standardPixmap) { @@ -235,6 +258,10 @@ QImage QGtk3Interface::standardPixmap(QPlatformTheme::StandardPixmap standardPix Q_UNREACHABLE(); } +/*! + \internal + \brief Returns a QImage for a given GTK \param iconName. + */ QImage QGtk3Interface::qt_gtk_get_icon(const char* iconName) const { GtkIconSet* iconSet = gtk_icon_factory_lookup_default (iconName); @@ -242,28 +269,46 @@ QImage QGtk3Interface::qt_gtk_get_icon(const char* iconName) const return qt_convert_gdk_pixbuf(icon); } +/*! + \internal + \brief Returns a QImage converted from the GDK pixel buffer \param buf. + + The ability to convert GdkPixbuf to QImage relies on the following assumptions: + \list + \li QImage uses uchar as a data container (unasserted) + \li the types guint8 and uchar are identical (statically asserted) + \li GDK pixel buffer uses 8 bits per sample (assumed at runtime) + \li GDK pixel buffer has 4 channels (assumed at runtime) + \endlist + */ QImage QGtk3Interface::qt_convert_gdk_pixbuf(GdkPixbuf *buf) const { if (!buf) return QImage(); - // Ability to convert GdkPixbuf to QImage relies on the assumptions, that - // - QImage uses uchar as a data container - // - the types guint8 and uchar are identical const guint8 *gdata = gdk_pixbuf_read_pixels(buf); static_assert(std::is_same<decltype(gdata), const uchar *>::value, "guint8 has diverted from uchar. Code needs fixing."); - Q_ASSUME(gdk_pixbuf_get_bits_per_sample(buf) == 8); - Q_ASSUME(gdk_pixbuf_get_n_channels(buf) == 4); + Q_ASSERT(gdk_pixbuf_get_bits_per_sample(buf) == 8); + Q_ASSERT(gdk_pixbuf_get_n_channels(buf) == 4); const uchar *data = static_cast<const uchar *>(gdata); const int width = gdk_pixbuf_get_width(buf); const int height = gdk_pixbuf_get_height(buf); const int bpl = gdk_pixbuf_get_rowstride(buf); - QImage converted(data, width, height, bpl, QImage::Format_ARGB32); - return converted.copy(); // detatch to survive lifetime of buf + QImage converted(data, width, height, bpl, QImage::Format_RGBA8888); + + // convert to more optimal format and detach to survive lifetime of buf + return converted.convertToFormat(QImage::Format_ARGB32_Premultiplied); } +/*! + \internal + \brief Instantiate a new GTK widget. + + Returns a pointer to a new GTK widget of \param type, allocated on the heap. + Returns nullptr of gtk_Default has is passed. + */ GtkWidget *QGtk3Interface::qt_new_gtkWidget(QGtkWidget type) const { #define CASE(Type)\ @@ -298,6 +343,14 @@ GtkWidget *QGtk3Interface::qt_new_gtkWidget(QGtkWidget type) const Q_UNREACHABLE(); } +/*! + \internal + \brief Read a GTK widget's color from a generic color getter. + + This method returns a generic color of \param con, a given GTK style context. + The requested color is defined by \param def and the GTK color-state \param state. + The return type is GDK color in RGBA format. + */ GdkRGBA QGtk3Interface::genericColor(GtkStyleContext *con, GtkStateFlags state, QGtkColorDefault def) const { GdkRGBA color; @@ -316,9 +369,16 @@ GdkRGBA QGtk3Interface::genericColor(GtkStyleContext *con, GtkStateFlags state, #undef CASE } -// Deliver a QColor from a GTK widget, a source type and a GTK widget state -// Fall back to the generic color getter of source/state if the property name does not exist -// Fall back to a hard coded generic color getter of source/state are not mapped +/*! + \internal + \brief Read a GTK widget's color from a property. + + Returns a color of GTK-widget \param widget, defined by \param source and \param state. + The return type is GDK color in RGBA format. + + \note If no corresponding property can be found for \param source, the method falls back to a + suitable generic color. + */ QColor QGtk3Interface::color(GtkWidget *widget, QGtkColorSource source, GtkStateFlags state) const { GdkRGBA col; @@ -355,7 +415,15 @@ QColor QGtk3Interface::color(GtkWidget *widget, QGtkColorSource source, GtkState #undef CASE } -// Deliver a widget pointer +/*! + \internal + \brief Get pointer to a GTK widget by \param type. + + Returns the pointer to a GTK widget, specified by \param type. + GTK widgets are cached, so that only one instance of each type is created. + \note + The method returns nullptr for the enum value gtk_Default. + */ GtkWidget *QGtk3Interface::widget(QGtkWidget type) const { if (type == QGtkWidget::gtk_Default) @@ -371,7 +439,14 @@ GtkWidget *QGtk3Interface::widget(QGtkWidget type) const return w; } -// Return widget syle context or default style +/*! + \internal + \brief Access a GTK widget's style context. + + Returns the pointer to the style context of GTK widget \param w. + + \note If \param w is nullptr, the GTK default style context (entry style) is returned. + */ GtkStyleContext *QGtk3Interface::context(GtkWidget *w) const { if (w) @@ -380,26 +455,73 @@ GtkStyleContext *QGtk3Interface::context(GtkWidget *w) const return gtk_widget_get_style_context(widget(QGtkWidget::gtk_entry)); } -// FIXME -// Brush assets (e.g. 9-patches) can't be accessed by the GTK API. -// => brush height and width from GTK will be ignored for the time being, -// because it is unknown if they relate to a plain brush or an image brush. +/*! + \internal + \brief Create a QBrush from a GTK widget. + + Returns a QBrush corresponding to GTK widget type \param wtype, \param source and \param state. + + Brush height and width is ignored in GTK3, because brush assets (e.g. 9-patches) + can't be accessed by the GTK3 API. It's therefore unknown, if the brush relates only to colors, + or to a pixmap based style. + + */ QBrush QGtk3Interface::brush(QGtkWidget wtype, QGtkColorSource source, GtkStateFlags state) const { + // FIXME: When a color's pixmap can be accessed via the GTK API, + // read it and set it in the brush. return QBrush(color(widget(wtype), source, state)); } -const QString QGtk3Interface::themeName() const +/*! + \internal + \brief Returns the name of the current GTK theme. + */ +QString QGtk3Interface::themeName() const { - gchar *theme_name; - GtkSettings *settings = gtk_settings_get_default(); - if (!settings) - return QString(); + QString name; + + if (GtkSettings *settings = gtk_settings_get_default()) { + gchar *theme_name; + g_object_get(settings, "gtk-theme-name", &theme_name, nullptr); + name = QLatin1StringView(theme_name); + g_free(theme_name); + } + + return name; +} + +/*! + \internal + \brief Determine color scheme by colors. - g_object_get(settings, "gtk-theme-name", &theme_name, nullptr); - return QLatin1StringView(theme_name); + Returns the color scheme of the current GTK theme, heuristically determined by the + lightness difference between default background and foreground colors. + + \note Returns Unknown in the unlikely case that both colors have the same lightness. + */ +Qt::ColorScheme QGtk3Interface::colorSchemeByColors() const +{ + const QColor background = color(widget(QGtkWidget::gtk_Default), + QGtkColorSource::Background, + GTK_STATE_FLAG_ACTIVE); + const QColor foreground = color(widget(QGtkWidget::gtk_Default), + QGtkColorSource::Foreground, + GTK_STATE_FLAG_ACTIVE); + + if (foreground.lightness() > background.lightness()) + return Qt::ColorScheme::Dark; + if (foreground.lightness() < background.lightness()) + return Qt::ColorScheme::Light; + return Qt::ColorScheme::Unknown; } +/*! + \internal + \brief Map font type to GTK widget type. + + Returns the GTK widget type corresponding to the given QPlatformTheme::Font \param type. + */ inline constexpr QGtk3Interface::QGtkWidget QGtk3Interface::toWidgetType(QPlatformTheme::Font type) { switch (type) { @@ -420,13 +542,13 @@ inline constexpr QGtk3Interface::QGtkWidget QGtk3Interface::toWidgetType(QPlatfo case QPlatformTheme::ToolButtonFont: return QGtkWidget::gtk_button; case QPlatformTheme::ItemViewFont: return QGtkWidget::gtk_entry; case QPlatformTheme::ListViewFont: return QGtkWidget::gtk_tree_view; - case QPlatformTheme::HeaderViewFont: return QGtkWidget::gtk_fixed; + case QPlatformTheme::HeaderViewFont: return QGtkWidget::gtk_combo_box; case QPlatformTheme::ListBoxFont: return QGtkWidget::gtk_Default; case QPlatformTheme::ComboMenuItemFont: return QGtkWidget::gtk_combo_box; case QPlatformTheme::ComboLineEditFont: return QGtkWidget::gtk_combo_box_text; case QPlatformTheme::SmallFont: return QGtkWidget::gtk_Default; case QPlatformTheme::MiniFont: return QGtkWidget::gtk_Default; - case QPlatformTheme::FixedFont: return QGtkWidget::gtk_fixed; + case QPlatformTheme::FixedFont: return QGtkWidget::gtk_Default; case QPlatformTheme::GroupBoxTitleFont: return QGtkWidget::gtk_Default; case QPlatformTheme::TabButtonFont: return QGtkWidget::gtk_button; case QPlatformTheme::EditorFont: return QGtkWidget::gtk_entry; @@ -435,6 +557,10 @@ inline constexpr QGtk3Interface::QGtkWidget QGtk3Interface::toWidgetType(QPlatfo Q_UNREACHABLE(); } +/*! + \internal + \brief Convert pango \param style to QFont::Style. + */ inline constexpr QFont::Style QGtk3Interface::toFontStyle(PangoStyle style) { switch (style) { @@ -446,6 +572,13 @@ inline constexpr QFont::Style QGtk3Interface::toFontStyle(PangoStyle style) Q_UNREACHABLE(); } +/*! + \internal + \brief Convert pango font \param weight to an int, representing font weight in Qt. + + Compatibility of PangoWeight is statically asserted. + The minimum (1) and maximum (1000) weight in Qt is respeced. + */ inline constexpr int QGtk3Interface::toFontWeight(PangoWeight weight) { // GTK PangoWeight can be directly converted to QFont::Weight @@ -459,12 +592,42 @@ inline constexpr int QGtk3Interface::toFontWeight(PangoWeight weight) return qBound(1, static_cast<int>(weight), 1000); } +/*! + \internal + \brief Return a GTK styled font. + + Returns the QFont corresponding to \param type by reading the corresponding + GTK widget type's font. + + \note GTK allows to specify a non fixed font as the system's fixed font. + If a fixed font is requested, the method fixes the pitch and falls back to monospace, + unless a suitable fixed pitch font is found. + */ QFont QGtk3Interface::font(QPlatformTheme::Font type) const { GtkStyleContext *con = context(widget(toWidgetType(type))); if (!con) return QFont(); + // explicitly add provider for fixed font + GtkCssProvider *cssProvider = nullptr; + if (type == QPlatformTheme::FixedFont) { + cssProvider = gtk_css_provider_new(); + gtk_style_context_add_class (con, GTK_STYLE_CLASS_MONOSPACE); + const char *fontSpec = "* {font-family: monospace;}"; + gtk_css_provider_load_from_data(cssProvider, fontSpec, -1, NULL); + gtk_style_context_add_provider(con, GTK_STYLE_PROVIDER(cssProvider), + GTK_STYLE_PROVIDER_PRIORITY_USER); + } + + // remove monospace provider from style context and unref it + QScopeGuard guard([&](){ + if (cssProvider) { + gtk_style_context_remove_provider(con, GTK_STYLE_PROVIDER(cssProvider)); + g_object_unref(cssProvider); + } + }); + const PangoFontDescription *gtkFont = gtk_style_context_get_font(con, GTK_STATE_FLAG_NORMAL); if (!gtkFont) return QFont(); @@ -482,9 +645,6 @@ QFont QGtk3Interface::font(QPlatformTheme::Font type) const font.setPointSizeF(static_cast<float>(pango_font_description_get_size(gtkFont)/PANGO_SCALE)); font.setStyle(toFontStyle(pango_font_description_get_style(gtkFont))); - // fix pixel pitch if fixed font is requested - // NOTE: GTK allows to specify a non fixed font as the system's fixed font. - // => the returned font may still not be a fixed font. if (type == QPlatformTheme::FixedFont) { font.setFixedPitch(true); if (!QFontInfo(font).fixedPitch()) { @@ -494,16 +654,21 @@ QFont QGtk3Interface::font(QPlatformTheme::Font type) const font.setFamily("monospace"_L1); } } + return font; } +/*! + \internal + \brief Returns a GTK styled file icon for \param fileInfo. + */ QIcon QGtk3Interface::fileIcon(const QFileInfo &fileInfo) const { GFile *file = g_file_new_for_path(fileInfo.absoluteFilePath().toLatin1().constData()); if (!file) return QIcon(); - GFileInfo *info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + GFileInfo *info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_ICON, G_FILE_QUERY_INFO_NONE, nullptr, nullptr); if (!info) { g_object_unref(file); @@ -518,12 +683,11 @@ QIcon QGtk3Interface::fileIcon(const QFileInfo &fileInfo) const } GtkIconTheme *theme = gtk_icon_theme_get_default(); - GtkIconInfo *iconInfo = gtk_icon_theme_lookup_by_gicon(theme, icon, GTK_ICON_SIZE_BUTTON, + GtkIconInfo *iconInfo = gtk_icon_theme_lookup_by_gicon(theme, icon, 16, GTK_ICON_LOOKUP_FORCE_SIZE); if (!iconInfo) { g_object_unref(file); g_object_unref(info); - g_object_unref(icon); return QIcon(); } @@ -531,7 +695,6 @@ QIcon QGtk3Interface::fileIcon(const QFileInfo &fileInfo) const QImage image = qt_convert_gdk_pixbuf(buf); g_object_unref(file); g_object_unref(info); - g_object_unref(icon); g_object_unref(buf); return QIcon(QPixmap::fromImage(image)); } diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface_p.h b/src/plugins/platformthemes/gtk3/qgtk3interface_p.h index 1a44eed6cb..c43932a4fa 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3interface_p.h +++ b/src/plugins/platformthemes/gtk3/qgtk3interface_p.h @@ -16,10 +16,10 @@ // #include <QtCore/QString> -#include <QtCore/QLibrary> #include <QtCore/QCache> #include <private/qflatmap_p.h> #include <QtCore/QObject> +#include <QtGui/QIcon> #include <QtGui/QPalette> #include <QtWidgets/QWidget> #include <QtCore/QLoggingCategory> @@ -38,6 +38,18 @@ Q_DECLARE_LOGGING_CATEGORY(lcQGtk3Interface); using namespace Qt::StringLiterals; class QGtk3Storage; + +/*! + \internal + \brief The QGtk3Interface class centralizes communication with the GTK3 library. + + By encapsulating all GTK version specific syntax and conversions, it makes Qt's GTK theme + independent from GTK versions. + + \note + Including GTK3 headers requires #undef signals, which disables Qt signal/slot handling. + */ + class QGtk3Interface { Q_GADGET @@ -45,7 +57,13 @@ public: QGtk3Interface(QGtk3Storage *); ~QGtk3Interface(); - // Enum representing GTK widget types + /*! + * \internal + \enum QGtk3Interface::QGtkWidget + \brief Represents GTK widget types used to obtain color information. + + \note The enum value gtk_Default refers to the GTK default style, rather than to a specific widget. + */ enum class QGtkWidget { gtk_menu_bar, gtk_menu, @@ -70,7 +88,15 @@ public: }; Q_ENUM(QGtkWidget) - // Enum representing color sources of a GTK theme + /*! + \internal + \enum QGtk3Interface::QGtkColorSource + \brief The QGtkColorSource enum represents the source of a color within a GTK widgets style context. + + If the current GTK theme provides such a color for a given widget, the color can be read + from the style context by passing the enum's key as a property name to the GTK method + gtk_style_context_lookup_color. The method will return false, if no color has been found. + */ enum class QGtkColorSource { Foreground, Background, @@ -80,7 +106,18 @@ public: }; Q_ENUM(QGtkColorSource) - // Enum for default color getter + /*! + \internal + \enum QGtk3Interface::QGtkColorDefault + \brief The QGtkColorDefault enum represents generic GTK colors. + + The GTK3 methods gtk_style_context_get_color, gtk_style_context_get_background_color, and + gtk_style_context_get_foreground_color always return the respective colors with a widget's + style context. Unless set as a property by the current GTK theme, GTK's default colors will + be returned. + These generic default colors, represented by the GtkColorDefault enum, are used as a + back, if a specific color property is requested but not defined in the current GTK theme. + */ enum class QGtkColorDefault { Foreground, Background, @@ -97,7 +134,10 @@ public: QIcon fileIcon(const QFileInfo &fileInfo) const; // Return current GTK theme name - const QString themeName() const; + QString themeName() const; + + // Derive color scheme from default colors + Qt::ColorScheme colorSchemeByColors() const; // Convert GTK state to/from string static int toGtkState(const QString &state); diff --git a/src/plugins/platformthemes/gtk3/qgtk3json.cpp b/src/plugins/platformthemes/gtk3/qgtk3json.cpp index 039ae5313c..eb81e563be 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3json.cpp +++ b/src/plugins/platformthemes/gtk3/qgtk3json.cpp @@ -53,9 +53,9 @@ QLatin1String QGtk3Json::fromWidgetType(QGtk3Interface::QGtkWidget widgetType) return QLatin1String(QMetaEnum::fromType<QGtk3Interface::QGtkWidget>().valueToKey(static_cast<int>(widgetType))); } -QLatin1String QGtk3Json::fromAppearance(Qt::Appearance app) +QLatin1String QGtk3Json::fromColorScheme(Qt::ColorScheme app) { - return QLatin1String(QMetaEnum::fromType<Qt::Appearance>().valueToKey(static_cast<int>(app))); + return QLatin1String(QMetaEnum::fromType<Qt::ColorScheme>().valueToKey(static_cast<int>(app))); } #define CONVERT(type, key, def)\ @@ -63,9 +63,9 @@ QLatin1String QGtk3Json::fromAppearance(Qt::Appearance app) const int intVal = QMetaEnum::fromType<type>().keyToValue(key.toLatin1().constData(), &ok);\ return ok ? static_cast<type>(intVal) : type::def -Qt::Appearance QGtk3Json::toAppearance(const QString &appearance) +Qt::ColorScheme QGtk3Json::toColorScheme(const QString &colorScheme) { - CONVERT(Qt::Appearance, appearance, Unknown); + CONVERT(Qt::ColorScheme, colorScheme, Unknown); } QPlatformTheme::Palette QGtk3Json::toPalette(const QString &palette) @@ -175,7 +175,7 @@ const QJsonDocument QGtk3Json::save(const QGtk3Storage::PaletteMap &map) const QGtk3Storage::TargetBrush tb = brushIterator.key(); QGtk3Storage::Source s = brushIterator.value(); brushObject.insert(ceColorGroup, fromColorGroup(tb.colorGroup)); - brushObject.insert(ceAppearance, fromAppearance(tb.appearance)); + brushObject.insert(ceColorScheme, fromColorScheme(tb.colorScheme)); brushObject.insert(ceSourceType, fromSourceType(s.sourceType)); QJsonObject sourceObject; @@ -201,7 +201,7 @@ const QJsonDocument QGtk3Json::save(const QGtk3Storage::PaletteMap &map) case QGtk3Storage::SourceType::Modified:{ sourceObject.insert(ceColorGroup, fromColorGroup(s.rec.colorGroup)); sourceObject.insert(ceColorRole, fromColorRole(s.rec.colorRole)); - sourceObject.insert(ceAppearance, fromAppearance(s.rec.appearance)); + sourceObject.insert(ceColorScheme, fromColorScheme(s.rec.colorScheme)); sourceObject.insert(ceRed, s.rec.deltaRed); sourceObject.insert(ceGreen, s.rec.deltaGreen); sourceObject.insert(ceBlue, s.rec.deltaBlue); @@ -258,7 +258,7 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) { #define GETSTR(obj, key)\ if (!obj.contains(key)) {\ - qCDebug(lcQGtk3Interface) << key << "missing for palette" << paletteName\ + qCInfo(lcQGtk3Interface) << key << "missing for palette" << paletteName\ << ", Brush" << colorRoleName;\ return false;\ }\ @@ -266,7 +266,7 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) #define GETINT(obj, key, var) GETSTR(obj, key);\ if (!obj[key].isDouble()) {\ - qCDebug(lcQGtk3Interface) << key << "type mismatch" << value\ + qCInfo(lcQGtk3Interface) << key << "type mismatch" << value\ << "is not an integer!"\ << "(Palette" << paletteName << "), Brush" << colorRoleName;\ return false;\ @@ -276,7 +276,7 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) map.clear(); const QJsonObject top(doc.object()); if (doc.isEmpty() || top.isEmpty() || !top.contains(cePalettes)) { - qCDebug(lcQGtk3Interface) << "Document does not contain Palettes."; + qCInfo(lcQGtk3Interface) << "Document does not contain Palettes."; return false; } @@ -286,13 +286,13 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) const int intVal = QMetaEnum::fromType<QPlatformTheme::Palette>().keyToValue(paletteName .toLatin1().constData(), &ok); if (!ok) { - qCDebug(lcQGtk3Interface) << "Invalid Palette name:" << paletteName; + qCInfo(lcQGtk3Interface) << "Invalid Palette name:" << paletteName; return false; } const QJsonObject &paletteObject = top[cePalettes][paletteName].toObject(); const QStringList &brushList = paletteObject.keys(); if (brushList.isEmpty()) { - qCDebug(lcQGtk3Interface) << "Palette" << paletteName << "does not contain brushes"; + qCInfo(lcQGtk3Interface) << "Palette" << paletteName << "does not contain brushes"; return false; } @@ -303,7 +303,7 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) const int intVal = QMetaEnum::fromType<QPalette::ColorRole>().keyToValue(colorRoleName .toLatin1().constData(), &ok); if (!ok) { - qCDebug(lcQGtk3Interface) << "Palette" << paletteName + qCInfo(lcQGtk3Interface) << "Palette" << paletteName << "contains invalid color role" << colorRoleName; return false; } @@ -312,7 +312,7 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) for (int brushIndex = 0; brushIndex < brushArray.size(); ++brushIndex) { const QJsonObject brushObject = brushArray.at(brushIndex).toObject(); if (brushObject.isEmpty()) { - qCDebug(lcQGtk3Interface) << "Brush specification missing at for palette" + qCInfo(lcQGtk3Interface) << "Brush specification missing at for palette" << paletteName << ", Brush" << colorRoleName; return false; } @@ -322,13 +322,13 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) const QGtk3Storage::SourceType sourceType = toSourceType(value); GETSTR(brushObject, ceColorGroup); const QPalette::ColorGroup colorGroup = toColorGroup(value); - GETSTR(brushObject, ceAppearance); - const Qt::Appearance appearance = toAppearance(value); - QGtk3Storage::TargetBrush tb(colorGroup, colorRole, appearance); + GETSTR(brushObject, ceColorScheme); + const Qt::ColorScheme colorScheme = toColorScheme(value); + QGtk3Storage::TargetBrush tb(colorGroup, colorRole, colorScheme); QGtk3Storage::Source s; if (!brushObject.contains(ceData) || !brushObject[ceData].isObject()) { - qCDebug(lcQGtk3Interface) << "Source specification missing for palette" << paletteName + qCInfo(lcQGtk3Interface) << "Source specification missing for palette" << paletteName << "Brush" << colorRoleName; return false; } @@ -350,7 +350,7 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) case QGtk3Storage::SourceType::Fixed: { if (!sourceObject.contains(ceBrush)) { - qCDebug(lcQGtk3Interface) << "Fixed brush specification missing for palette" << paletteName + qCInfo(lcQGtk3Interface) << "Fixed brush specification missing for palette" << paletteName << "Brush" << colorRoleName; return false; } @@ -360,7 +360,7 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) GETSTR(fixedSource, ceColor); const QColor color(value); if (!color.isValid()) { - qCDebug(lcQGtk3Interface) << "Color" << value << "can't be parsed for:" << paletteName + qCInfo(lcQGtk3Interface) << "Color" << value << "can't be parsed for:" << paletteName << "Brush" << colorRoleName; return false; } @@ -376,19 +376,19 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) const QPalette::ColorGroup colorGroup = toColorGroup(value); GETSTR(sourceObject, ceColorRole); const QPalette::ColorRole colorRole = toColorRole(value); - GETSTR(sourceObject, ceAppearance); - const Qt::Appearance appearance = toAppearance(value); + GETSTR(sourceObject, ceColorScheme); + const Qt::ColorScheme colorScheme = toColorScheme(value); GETINT(sourceObject, ceLighter, lighter); GETINT(sourceObject, ceRed, red); GETINT(sourceObject, ceBlue, blue); GETINT(sourceObject, ceGreen, green); - s = QGtk3Storage::Source(colorGroup, colorRole, appearance, + s = QGtk3Storage::Source(colorGroup, colorRole, colorScheme, lighter, red, green, blue); } break; case QGtk3Storage::SourceType::Invalid: - qCDebug(lcQGtk3Interface) << "Invalid source type for palette" << paletteName + qInfo(lcQGtk3Interface) << "Invalid source type for palette" << paletteName << "Brush." << colorRoleName; return false; } diff --git a/src/plugins/platformthemes/gtk3/qgtk3json_p.h b/src/plugins/platformthemes/gtk3/qgtk3json_p.h index b4477eb667..daf280612c 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3json_p.h +++ b/src/plugins/platformthemes/gtk3/qgtk3json_p.h @@ -50,7 +50,7 @@ public: static QLatin1String fromGdkSource(QGtk3Interface::QGtkColorSource source); static QLatin1String fromSourceType(QGtk3Storage::SourceType sourceType); static QLatin1String fromWidgetType(QGtk3Interface::QGtkWidget widgetType); - static QLatin1String fromAppearance(Qt::Appearance app); + static QLatin1String fromColorScheme(Qt::ColorScheme colorScheme); // Convert strings to enums static QPlatformTheme::Palette toPalette(const QString &palette); @@ -61,7 +61,7 @@ public: static QGtk3Interface::QGtkColorSource toGdkSource(const QString &source); static QGtk3Storage::SourceType toSourceType(const QString &sourceType); static QGtk3Interface::QGtkWidget toWidgetType(const QString &widgetType); - static Qt::Appearance toAppearance(const QString &appearance); + static Qt::ColorScheme toColorScheme(const QString &colorScheme); // Json keys static constexpr QLatin1StringView cePalettes = "QtGtk3Palettes"_L1; @@ -82,7 +82,7 @@ public: static constexpr QLatin1StringView ceBrush = "FixedBrush"_L1; static constexpr QLatin1StringView ceData = "SourceData"_L1; static constexpr QLatin1StringView ceBrushes = "Brushes"_L1; - static constexpr QLatin1StringView ceAppearance = "Appearance"_L1; + static constexpr QLatin1StringView ceColorScheme = "ColorScheme"_L1; // Save to a file static bool save(const QGtk3Storage::PaletteMap &map, const QString &fileName, diff --git a/src/plugins/platformthemes/gtk3/qgtk3portalinterface.cpp b/src/plugins/platformthemes/gtk3/qgtk3portalinterface.cpp new file mode 100644 index 0000000000..1ffdda74fa --- /dev/null +++ b/src/plugins/platformthemes/gtk3/qgtk3portalinterface.cpp @@ -0,0 +1,123 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qgtk3portalinterface_p.h" +#include "qgtk3storage_p.h" + +#include <QtDBus/QDBusArgument> +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusMessage> +#include <QtDBus/QDBusPendingCall> +#include <QtDBus/QDBusPendingCallWatcher> +#include <QtDBus/QDBusPendingReply> +#include <QtDBus/QDBusVariant> +#include <QtDBus/QtDBus> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcQGtk3PortalInterface, "qt.qpa.gtk"); + +using namespace Qt::StringLiterals; + +static constexpr QLatin1StringView appearanceInterface("org.freedesktop.appearance"); +static constexpr QLatin1StringView colorSchemeKey("color-scheme"); + +const QDBusArgument &operator>>(const QDBusArgument &argument, QMap<QString, QVariantMap> &map) +{ + argument.beginMap(); + map.clear(); + + while (!argument.atEnd()) { + QString key; + QVariantMap value; + argument.beginMapEntry(); + argument >> key >> value; + argument.endMapEntry(); + map.insert(key, value); + } + + argument.endMap(); + return argument; +} + +QGtk3PortalInterface::QGtk3PortalInterface(QGtk3Storage *s) + : m_storage(s) { + qRegisterMetaType<QDBusVariant>(); + qDBusRegisterMetaType<QMap<QString, QVariantMap>>(); + + queryColorScheme(); + + if (!s) { + qCDebug(lcQGtk3PortalInterface) << "QGtk3PortalInterface instantiated without QGtk3Storage." + << "No reaction to runtime theme changes."; + } +} + +Qt::ColorScheme QGtk3PortalInterface::colorScheme() const +{ + return m_colorScheme; +} + +void QGtk3PortalInterface::queryColorScheme() { + QDBusConnection connection = QDBusConnection::sessionBus(); + QDBusMessage message = QDBusMessage::createMethodCall( + "org.freedesktop.portal.Desktop"_L1, + "/org/freedesktop/portal/desktop"_L1, + "org.freedesktop.portal.Settings"_L1, "ReadAll"_L1); + message << QStringList{ appearanceInterface }; + + QDBusPendingCall pendingCall = connection.asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this); + QObject::connect( + watcher, &QDBusPendingCallWatcher::finished, this, + [this](QDBusPendingCallWatcher *watcher) { + QDBusPendingReply<QMap<QString, QVariantMap>> reply = *watcher; + if (reply.isValid()) { + QMap<QString, QVariantMap> settings = reply.value(); + if (!settings.isEmpty()) { + settingChanged(appearanceInterface, colorSchemeKey, + QDBusVariant(settings.value(appearanceInterface).value(colorSchemeKey))); + } + } else { + qCDebug(lcQGtk3PortalInterface) << "Failed to query org.freedesktop.portal.Settings: " + << reply.error().message(); + } + watcher->deleteLater(); + }); + + QDBusConnection::sessionBus().connect( + "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1, + "org.freedesktop.portal.Settings"_L1, "SettingChanged"_L1, this, + SLOT(settingChanged(QString, QString, QDBusVariant))); +} + +void QGtk3PortalInterface::settingChanged(const QString &group, const QString &key, + const QDBusVariant &value) +{ + if (group == appearanceInterface && key == colorSchemeKey) { + const uint colorScheme = value.variant().toUInt(); + // From org.freedesktop.portal.Settings.xml + // "1" - Prefer dark appearance + Qt::ColorScheme newColorScheme = colorScheme == 1 ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light; + if (m_colorScheme != newColorScheme) { + m_colorScheme = newColorScheme; + if (m_storage) + m_storage->handleThemeChange(); + } + } +} + +QT_END_NAMESPACE + +#include "moc_qgtk3portalinterface_p.cpp" diff --git a/src/plugins/platformthemes/gtk3/qgtk3portalinterface_p.h b/src/plugins/platformthemes/gtk3/qgtk3portalinterface_p.h new file mode 100644 index 0000000000..25a5f58ab1 --- /dev/null +++ b/src/plugins/platformthemes/gtk3/qgtk3portalinterface_p.h @@ -0,0 +1,49 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QGTK3PORTALINTERFACE_H +#define QGTK3PORTALINTERFACE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/QObject> +#include <QtCore/QLoggingCategory> + +QT_BEGIN_NAMESPACE + +class QDBusVariant; +class QGtk3Storage; + +Q_DECLARE_LOGGING_CATEGORY(lcQGtk3PortalInterface); + +class QGtk3PortalInterface : public QObject +{ + Q_OBJECT +public: + QGtk3PortalInterface(QGtk3Storage *s); + ~QGtk3PortalInterface() = default; + + Qt::ColorScheme colorScheme() const; + +private Q_SLOTS: + void settingChanged(const QString &group, const QString &key, + const QDBusVariant &value); +private: + void queryColorScheme(); + + Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown; + QGtk3Storage *m_storage = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QGTK3PORTALINTERFACE_H diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp index bf694fe3e1..2877b28590 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +++ b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp @@ -21,10 +21,32 @@ QT_BEGIN_NAMESPACE QGtk3Storage::QGtk3Storage() { m_interface.reset(new QGtk3Interface(this)); +#if QT_CONFIG(dbus) + m_portalInterface.reset(new QGtk3PortalInterface(this)); +#endif populateMap(); } -// Set a brush from a source and resolve recursions +/*! + \internal + \enum QGtk3Storage::SourceType + \brief This enum represents the type of a color source. + + \value Gtk Color is read from a GTK widget + \value Fixed A fixed brush is specified + \value Modified The color is a modification of another color (fixed or read from GTK) + \omitvalue Invalid + */ + +/*! + \internal + \brief Find a brush from a source. + + Returns a QBrush from a given \param source and a \param map of available brushes + to search from. + + A null QBrush is returned, if no brush corresponding to the source has been found. + */ QBrush QGtk3Storage::brush(const Source &source, const BrushMap &map) const { switch (source.sourceType) { @@ -36,7 +58,7 @@ QBrush QGtk3Storage::brush(const Source &source, const BrushMap &map) const case SourceType::Modified: { // don't loop through modified sources, break if modified source not found Source recSource = brush(TargetBrush(source.rec.colorGroup, source.rec.colorRole, - source.rec.appearance), map); + source.rec.colorScheme), map); if (!recSource.isValid() || (recSource.sourceType == SourceType::Modified)) return QBrush(); @@ -64,7 +86,14 @@ QBrush QGtk3Storage::brush(const Source &source, const BrushMap &map) const Q_UNREACHABLE(); } -// Find source for a recursion and take dark/light/unknown into consideration +/*! + \internal + \brief Recurse to find a source brush for modification. + + Returns the source specified by the target brush \param b in the \param map of brushes. + Takes dark/light/unknown into consideration. + Returns an empty brush if no suitable one can be found. + */ QGtk3Storage::Source QGtk3Storage::brush(const TargetBrush &b, const BrushMap &map) const { #define FIND(brush) if (map.contains(brush))\ @@ -73,22 +102,31 @@ QGtk3Storage::Source QGtk3Storage::brush(const TargetBrush &b, const BrushMap &m // Return exact match FIND(b); - // unknown appearance can find anything - if (b.appearance == Qt::Appearance::Unknown) { - FIND(TargetBrush(b, Qt::Appearance::Dark)); - FIND(TargetBrush(b, Qt::Appearance::Light)); + // unknown color scheme can find anything + if (b.colorScheme == Qt::ColorScheme::Unknown) { + FIND(TargetBrush(b, Qt::ColorScheme::Dark)); + FIND(TargetBrush(b, Qt::ColorScheme::Light)); } // Color group All can always be found if (b.colorGroup != QPalette::All) - return brush(TargetBrush(QPalette::All, b.colorRole, b.appearance), map); + return brush(TargetBrush(QPalette::All, b.colorRole, b.colorScheme), map); // Brush not found return Source(); #undef FIND } -// Create a simple standard palette +/*! + \internal + \brief Returns a simple, hard coded base palette. + + Create a hard coded palette with default colors as a fallback for any color that can't be + obtained from GTK. + + \note This palette will be used as a default baseline for the system palette, which then + will be used as a default baseline for any other palette type. + */ QPalette QGtk3Storage::standardPalette() { QColor backgroundColor(0xd4, 0xd0, 0xc8); @@ -105,7 +143,13 @@ QPalette QGtk3Storage::standardPalette() return palette; } -// Deliver a palette styled according to the current GTK Theme +/*! + \internal + \brief Return a GTK styled QPalette. + + Returns the pointer to a (cached) QPalette for \param type, with its brushes + populated according to the current GTK theme. + */ const QPalette *QGtk3Storage::palette(QPlatformTheme::Palette type) const { if (type >= QPlatformTheme::NPalettes) @@ -140,13 +184,13 @@ const QPalette *QGtk3Storage::palette(QPlatformTheme::Palette type) const Source source = i.value(); // Brush is set if - // - theme and source appearance match + // - theme and source color scheme match // - or either of them is unknown - const auto appSource = i.key().appearance; - const auto appTheme = appearance(); + const auto appSource = i.key().colorScheme; + const auto appTheme = colorScheme(); const bool setBrush = (appSource == appTheme) || - (appSource == Qt::Appearance::Unknown) || - (appTheme == Qt::Appearance::Unknown); + (appSource == Qt::ColorScheme::Unknown) || + (appTheme == Qt::ColorScheme::Unknown); if (setBrush) { p.setBrush(i.key().colorGroup, i.key().colorRole, brush(source, brushes)); @@ -155,11 +199,17 @@ const QPalette *QGtk3Storage::palette(QPlatformTheme::Palette type) const m_paletteCache[type].emplace(p); if (type == QPlatformTheme::SystemPalette) - qCDebug(lcQGtk3Interface) << "System Palette defined" << themeName() << appearance() << p; + qCDebug(lcQGtk3Interface) << "System Palette defined" << themeName() << colorScheme() << p; return &m_paletteCache[type].value(); } +/*! + \internal + \brief Return a GTK styled font. + + Returns a QFont of \param type, styled according to the current GTK theme. +*/ const QFont *QGtk3Storage::font(QPlatformTheme::Font type) const { if (m_fontCache[type].has_value()) @@ -169,6 +219,13 @@ const QFont *QGtk3Storage::font(QPlatformTheme::Font type) const return &m_fontCache[type].value(); } +/*! + \internal + \brief Return a GTK styled standard pixmap if available. + + Returns a pixmap specified by \param standardPixmap and \param size. + Returns an empty pixmap if GTK doesn't support the requested one. + */ QPixmap QGtk3Storage::standardPixmap(QPlatformTheme::StandardPixmap standardPixmap, const QSizeF &size) const { @@ -186,14 +243,22 @@ QPixmap QGtk3Storage::standardPixmap(QPlatformTheme::StandardPixmap standardPixm return QPixmap::fromImage(image.scaled(size.toSize())); } +/*! + \internal + \brief Returns a GTK styled file icon corresponding to \param fileInfo. + */ QIcon QGtk3Storage::fileIcon(const QFileInfo &fileInfo) const { return m_interface ? m_interface->fileIcon(fileInfo) : QIcon(); } +/*! + \internal + \brief Clears all caches. + */ void QGtk3Storage::clear() { - m_appearance = Qt::Appearance::Unknown; + m_colorScheme = Qt::ColorScheme::Unknown; m_palettes.clear(); for (auto &cache : m_paletteCache) cache.reset(); @@ -202,33 +267,98 @@ void QGtk3Storage::clear() cache.reset(); } +/*! + \internal + \brief Handles a theme change at runtime. + + Clear all caches, re-populate with current GTK theme and notify the window system interface. + This method is a callback for the theme change signal sent from GTK. + */ void QGtk3Storage::handleThemeChange() { - clear(); populateMap(); QWindowSystemInterface::handleThemeChange(); } +/*! + \internal + \brief Populates a map with information about how to locate colors in GTK. + + This method creates a data structure to locate color information for each brush of a QPalette + within GTK. The structure can hold mapping information for each QPlatformTheme::Palette + enum value. If no specific mapping is stored for an enum value, the system palette is returned + instead of a specific one. If no mapping is stored for the system palette, it will fall back to + QGtk3Storage::standardPalette. + + The method will populate the data structure with a standard mapping, covering the following + palette types: + \list + \li QPlatformTheme::SystemPalette + \li QPlatformTheme::CheckBoxPalette + \li QPlatformTheme::RadioButtonPalette + \li QPlatformTheme::ComboBoxPalette + \li QPlatformTheme::GroupBoxPalette + \li QPlatformTheme::MenuPalette + \li QPlatformTheme::TextLineEditPalette + \endlist + + The method will check the environment variable {{QT_GUI_GTK_JSON_SAVE}}. If it points to a + valid path with write access, it will write the standard mapping into a Json file. + That Json file can be modified and/or extended. + The Json syntax is + - "QGtk3Palettes" (top level value) + - QPlatformTheme::Palette + - QPalette::ColorRole + - Qt::ColorScheme + - Qt::ColorGroup + - Source data + - Source Type + - [source data] + + If the environment variable {{QT_GUI_GTK_JSON_HARDCODED}} contains the keyword \c true, + all sources are converted to fixed sources. In that case, they contain the hard coded HexRGBA + values read from GTK. + + The method will also check the environment variable {{QT_GUI_GTK_JSON}}. If it points to a valid + Json file with read access, it will be parsed instead of creating a standard mapping. + Parsing errors will be printed out with qCInfo if the logging category {{qt.qpa.gtk}} is activated. + In case of a parsing error, the method will fall back to creating a standard mapping. + + \note + If a Json file contains only fixed brushes (e.g. exported with {{QT_GUI_GTK_JSON_HARDCODED=true}}), + no colors will be imported from GTK. + */ void QGtk3Storage::populateMap() { static QString m_themeName; // Distiguish initialization, theme change or call without theme change + Qt::ColorScheme newColorScheme = Qt::ColorScheme::Unknown; const QString newThemeName = themeName(); - if (m_themeName == newThemeName) + +#if QT_CONFIG(dbus) + // Prefer color scheme we get from xdg-desktop-portal as this is what GNOME + // relies on these days + newColorScheme = m_portalInterface->colorScheme(); +#endif + + if (newColorScheme == Qt::ColorScheme::Unknown) { + // Derive color scheme from theme name + newColorScheme = newThemeName.contains("dark"_L1, Qt::CaseInsensitive) + ? Qt::ColorScheme::Dark : m_interface->colorSchemeByColors(); + } + + if (m_themeName == newThemeName && m_colorScheme == newColorScheme) return; clear(); - // Derive appearance from theme name - m_appearance = newThemeName.contains("dark"_L1, Qt::CaseInsensitive) - ? Qt::Appearance::Dark : Qt::Appearance::Light; - if (m_themeName.isEmpty()) { - qCDebug(lcQGtk3Interface) << "GTK theme initialized:" << newThemeName << m_appearance; + qCDebug(lcQGtk3Interface) << "GTK theme initialized:" << newThemeName << newColorScheme; } else { - qCDebug(lcQGtk3Interface) << "GTK theme changed to:" << newThemeName << m_appearance; + qCDebug(lcQGtk3Interface) << "GTK theme changed to:" << newThemeName << newColorScheme; } + m_colorScheme = newColorScheme; m_themeName = newThemeName; // create standard mapping or load from Json file? @@ -248,6 +378,15 @@ void QGtk3Storage::populateMap() qWarning() << "File" << jsonOutput << "could not be saved.\n"; } +/*! + \internal + \brief Return a palette map for saving. + + This method returns the existing palette map, if the environment variable + {{QT_GUI_GTK_JSON_HARDCODED}} is not set or does not contain the keyword \c true. + If it contains the keyword \c true, it returns a palette map with all brush + sources converted to fixed sources. + */ const QGtk3Storage::PaletteMap QGtk3Storage::savePalettes() const { const QString hard = qEnvironmentVariable("QT_GUI_GTK_JSON_HARDCODED"); @@ -282,21 +421,50 @@ const QGtk3Storage::PaletteMap QGtk3Storage::savePalettes() const return map; } +/*! + \internal + \brief Saves current palette mapping to a \param filename with Json format \param f. + + Saves the current palette mapping into a QJson file, + taking {{QT_GUI_GTK_JSON_HARDCODED}} into consideration. + Returns \c true if saving was successful and \c false otherwise. + */ bool QGtk3Storage::save(const QString &filename, QJsonDocument::JsonFormat f) const { return QGtk3Json::save(savePalettes(), filename, f); } +/*! + \internal + \brief Returns a QJsonDocument with current palette mapping. + + Saves the current palette mapping into a QJsonDocument, + taking {{QT_GUI_GTK_JSON_HARDCODED}} into consideration. + Returns \c true if saving was successful and \c false otherwise. + */ QJsonDocument QGtk3Storage::save() const { return QGtk3Json::save(savePalettes()); } +/*! + \internal + \brief Loads palette mapping from Json file \param filename. + + Returns \c true if the file was successfully parsed and \c false otherwise. + */ bool QGtk3Storage::load(const QString &filename) { return QGtk3Json::load(m_palettes, filename); } +/*! + \internal + \brief Creates a standard palette mapping. + + The method creates a hard coded standard mapping, used if no external Json file + containing a valid mapping has been specified in the environment variable {{QT_GUI_GTK_JSON}}. + */ void QGtk3Storage::createMapping() { // Hard code standard mapping @@ -311,19 +479,19 @@ void QGtk3Storage::createMapping() // Define a modified source #define LIGHTER(group, role, lighter)\ source = Source(QPalette::group, QPalette::role,\ - Qt::Appearance::Unknown, lighter) + Qt::ColorScheme::Unknown, lighter) #define MODIFY(group, role, red, green, blue)\ source = Source(QPalette::group, QPalette::role,\ - Qt::Appearance::Unknown, red, green, blue) + Qt::ColorScheme::Unknown, red, green, blue) // Define fixed source #define FIX(color) source = FixedSource(color); // Add the source to a target brush - // Use default Qt::Appearance::Unknown, if no appearance was specified + // Use default Qt::ColorScheme::Unknown, if no color scheme was specified #define ADD_2(group, role) map.insert(TargetBrush(QPalette::group, QPalette::role), source); #define ADD_3(group, role, app) map.insert(TargetBrush(QPalette::group, QPalette::role,\ - Qt::Appearance::app), source); + Qt::ColorScheme::app), source); #define ADD_X(x, group, role, app, FUNC, ...) FUNC #define ADD(...) ADD_X(,##__VA_ARGS__, ADD_3(__VA_ARGS__), ADD_2(__VA_ARGS__)) // Save target brushes to a palette type @@ -332,129 +500,177 @@ void QGtk3Storage::createMapping() #define CLEAR map.clear() /* - * Macro ussage: - * - * 1. Define a source - * - * GTK(QGtkWidget, QGtkColorSource, GTK_STATE_FLAG) - * Fetch the color from a GtkWidget, related to a source and a state. - * - * LIGHTER(ColorGroup, ColorROle, lighter) - * Use a color of the same QPalette related to ColorGroup and ColorRole. - * Make the color lighter (if lighter >100) or darker (if lighter < 100) - * - * MODIFY(ColorGroup, ColorRole, red, green, blue) - * Use a color of the same QPalette related to ColorGroup and ColorRole. - * Modify it by adding red, green, blue. - * - * FIX(const QBrush &) - * Use a fixed brush without querying GTK - * - * 2. Define the target - * - * Use ADD(ColorGroup, ColorRole) to use the defined source for the - * color group / role in the current palette. - * - * Use ADD(ColorGroup, ColorRole, Appearance) to use the defined source - * only for a specific appearance - * - * 3. Save mapping - * Save the defined mappings for a specific palette. - * If a mapping entry does not cover all color groups and roles of a palette, - * the system palette will be used for the remaining values. - * If the system palette does not have all combination of color groups and roles, - * the remaining ones will be populated by a hard coded fusion-style like palette. - * - * 4. Clear mapping - * Use CLEAR to clear the mapping and begin a new one. + Macro usage: + + 1. Define a source + GTK(QGtkWidget, QGtkColorSource, GTK_STATE_FLAG) + Fetch the color from a GtkWidget, related to a source and a state. + + LIGHTER(ColorGroup, ColorROle, lighter) + Use a color of the same QPalette related to ColorGroup and ColorRole. + Make the color lighter (if lighter >100) or darker (if lighter < 100) + + MODIFY(ColorGroup, ColorRole, red, green, blue) + Use a color of the same QPalette related to ColorGroup and ColorRole. + Modify it by adding red, green, blue. + + FIX(const QBrush &) + Use a fixed brush without querying GTK + + 2. Define the target + Use ADD(ColorGroup, ColorRole) to use the defined source for the + color group / role in the current palette. + + Use ADD(ColorGroup, ColorRole, ColorScheme) to use the defined source + only for a specific color scheme + + 3. Save mapping + Save the defined mappings for a specific palette. + If a mapping entry does not cover all color groups and roles of a palette, + the system palette will be used for the remaining values. + If the system palette does not have all combination of color groups and roles, + the remaining ones will be populated by a hard coded fusion-style like palette. + + 4. Clear mapping + Use CLEAR to clear the mapping and begin a new one. */ // System palette - // background color and calculate derivates - GTK(Default, Background, INSENSITIVE); - ADD(Normal, Window); - ADD(Normal, Button); - ADD(Normal, Base); - ADD(Inactive, Base); - ADD(Normal, Window); // redundant - ADD(Inactive, Window); - LIGHTER(Normal, Window, 125); - ADD(Normal, Light); - LIGHTER(Normal, Window, 70); - ADD(Normal, Shadow); - LIGHTER(Normal, Window, 80); - ADD(Normal, Dark); - GTK(button, Foreground, ACTIVE); - ADD(Normal, WindowText); - ADD(Inactive, WindowText); - LIGHTER(Normal, WindowText, 50); - ADD(Disabled, Text); - ADD(Disabled, WindowText); - //ADD(Normal, ButtonText); - ADD(Inactive, ButtonText); - GTK(button, Text, NORMAL); - ADD(Disabled, ButtonText); - // special background colors - GTK(Default, Background, SELECTED); - ADD(Disabled, Highlight); - ADD(Normal, Highlight); - GTK(entry, Foreground, SELECTED); - ADD(Normal, HighlightedText); - GTK(entry, Background, ACTIVE); - ADD(Inactive, HighlightedText); - // text color and friends - GTK(entry, Text, NORMAL); - ADD(Normal, ButtonText); - ADD(Normal, WindowText); - ADD(Disabled, WindowText); - ADD(Disabled, HighlightedText); - GTK(Default, Text, NORMAL); - ADD(Normal, Text); - ADD(Inactive, Text); - ADD(Normal, HighlightedText); - LIGHTER(Normal, Base, 93); - ADD(All, AlternateBase); - GTK(Default, Foreground, NORMAL); - ADD(All, ToolTipText); - MODIFY(Normal, Text, 100, 100, 100); - ADD(All, PlaceholderText, Light); - MODIFY(Normal, Text, -100, -100, -100); - ADD(All, PlaceholderText, Dark); - SAVE(SystemPalette); - CLEAR; - - // Checkbox and Radio Button - GTK(button, Text, ACTIVE); - ADD(Normal, Base, Dark); - GTK(button, Text, NORMAL); - ADD(Normal, Base, Light); - SAVE(CheckBoxPalette); - SAVE(RadioButtonPalette); - CLEAR; - - // ComboBox, GroupBox, Frame - GTK(combo_box, Text, NORMAL); - ADD(Normal, ButtonText, Dark); - ADD(Normal, Text, Dark); - GTK(combo_box, Text, ACTIVE); - ADD(Normal, ButtonText, Light); - ADD(Normal, Text, Light); - SAVE(ComboBoxPalette); - SAVE(GroupBoxPalette); - CLEAR; - - // Menu bar - GTK(Default, Text, ACTIVE); - ADD(Normal, ButtonText); - SAVE(MenuPalette); - CLEAR; - - // LineEdit - GTK(Default, Background, NORMAL); - ADD(All, Base); - SAVE(TextLineEditPalette); - CLEAR; + { + // background color and calculate derivates + GTK(Default, Background, INSENSITIVE); + ADD(All, Window); + ADD(All, Button); + ADD(All, Base); + LIGHTER(Normal, Window, 125); + ADD(Normal, Light); + ADD(Inactive, Light); + LIGHTER(Normal, Window, 70); + ADD(Normal, Shadow); + LIGHTER(Normal, Window, 80); + ADD(Normal, Dark); + ADD(Inactive, Dark) + + GTK(button, Foreground, ACTIVE); + ADD(Inactive, WindowText); + LIGHTER(Normal, WindowText, 50); + ADD(Disabled, Text); + ADD(Disabled, WindowText); + ADD(Disabled, ButtonText); + + GTK(button, Text, NORMAL); + ADD(Inactive, ButtonText); + + // special background colors + GTK(Default, Background, SELECTED); + ADD(Disabled, Highlight); + ADD(Normal, Highlight); + ADD(Inactive, Highlight); + + GTK(entry, Foreground, SELECTED); + ADD(Normal, HighlightedText); + ADD(Inactive, HighlightedText); + + // text color and friends + GTK(entry, Text, NORMAL); + ADD(Normal, ButtonText); + ADD(Normal, WindowText); + ADD(Disabled, HighlightedText); + + GTK(Default, Text, NORMAL); + ADD(Normal, Text); + ADD(Inactive, Text); + ADD(Normal, HighlightedText); + LIGHTER(Normal, Base, 93); + ADD(All, AlternateBase); + + GTK(Default, Foreground, NORMAL); + MODIFY(Normal, Text, 100, 100, 100); + ADD(All, PlaceholderText, Light); + MODIFY(Normal, Text, -100, -100, -100); + ADD(All, PlaceholderText, Dark); + + // Light, midlight, dark, mid, shadow colors + LIGHTER(Normal, Button, 125); + ADD(All, Light) + LIGHTER(Normal, Button, 113); + ADD(All, Midlight) + LIGHTER(Normal, Button, 113); + ADD(All, Mid) + LIGHTER(Normal, Button, 87); + ADD(All, Dark) + LIGHTER(Normal, Button, 5); + ADD(All, Shadow) + + SAVE(SystemPalette); + CLEAR; + } + + // Label and TabBar Palette + { + GTK(entry, Text, NORMAL); + ADD(Normal, WindowText); + ADD(Inactive, WindowText); + + SAVE(LabelPalette); + SAVE(TabBarPalette); + CLEAR; + } + + // Checkbox and RadioButton Palette + { + GTK(button, Text, ACTIVE); + ADD(Normal, Base, Dark); + ADD(Inactive, WindowText, Dark); + + GTK(Default, Foreground, NORMAL); + ADD(All, Text); + + GTK(Default, Background, NORMAL); + ADD(All, Base); + + GTK(button, Text, NORMAL); + ADD(Normal, Base, Light); + ADD(Inactive, WindowText, Light); + + SAVE(CheckBoxPalette); + SAVE(RadioButtonPalette); + CLEAR; + } + + // ComboBox, GroupBox & Frame Palette + { + GTK(combo_box, Text, NORMAL); + ADD(Normal, ButtonText, Dark); + ADD(Normal, Text, Dark); + ADD(Inactive, WindowText, Dark); + + GTK(combo_box, Text, ACTIVE); + ADD(Normal, ButtonText, Light); + ADD(Normal, Text, Light); + ADD(Inactive, WindowText, Light); + + SAVE(ComboBoxPalette); + SAVE(GroupBoxPalette); + CLEAR; + } + + // MenuBar Palette + { + GTK(Default, Text, ACTIVE); + ADD(Normal, ButtonText); + SAVE(MenuPalette); + CLEAR; + } + + // LineEdit Palette + { + GTK(Default, Background, NORMAL); + ADD(All, Base); + SAVE(TextLineEditPalette); + CLEAR; + } #undef GTK #undef REC diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage_p.h b/src/plugins/platformthemes/gtk3/qgtk3storage_p.h index 57f6aeea96..45192263a9 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3storage_p.h +++ b/src/plugins/platformthemes/gtk3/qgtk3storage_p.h @@ -16,6 +16,9 @@ // #include "qgtk3interface_p.h" +#if QT_CONFIG(dbus) +#include "qgtk3portalinterface_p.h" +#endif #include <QtCore/QJsonDocument> #include <QtCore/QCache> @@ -33,6 +36,7 @@ class QGtk3Storage public: QGtk3Storage(); + // Enum documented in cpp file. Please keep it in line with updates made here. enum class SourceType { Gtk, Fixed, @@ -60,7 +64,7 @@ public: struct RecursiveSource { QPalette::ColorGroup colorGroup; QPalette::ColorRole colorRole; - Qt::Appearance appearance; + Qt::ColorScheme colorScheme; int lighter = 100; int deltaRed = 0; int deltaGreen = 0; @@ -70,7 +74,7 @@ public: QDebug operator<<(QDebug dbg) { return dbg << "QGtkStorage::RecursiceSource(colorGroup=" << colorGroup << ", colorRole=" - << colorRole << ", appearance=" << appearance << ", lighter=" << lighter + << colorRole << ", colorScheme=" << colorScheme << ", lighter=" << lighter << ", deltaRed="<< deltaRed << "deltaBlue =" << deltaBlue << "deltaGreen=" << deltaGreen << ", width=" << width << ", height=" << height << ")"; } @@ -105,23 +109,23 @@ public: // Recursive constructor for darker/lighter colors Source(QPalette::ColorGroup group, QPalette::ColorRole role, - Qt::Appearance app, int p_lighter = 100) + Qt::ColorScheme scheme, int p_lighter = 100) : sourceType(SourceType::Modified) { rec.colorGroup = group; rec.colorRole = role; - rec.appearance = app; + rec.colorScheme = scheme; rec.lighter = p_lighter; } // Recursive ocnstructor for color modification Source(QPalette::ColorGroup group, QPalette::ColorRole role, - Qt::Appearance app, int p_red, int p_green, int p_blue) + Qt::ColorScheme scheme, int p_red, int p_green, int p_blue) : sourceType(SourceType::Modified) { rec.colorGroup = group; rec.colorRole = role; - rec.appearance = app; + rec.colorScheme = scheme; rec.deltaRed = p_red; rec.deltaGreen = p_green; rec.deltaBlue = p_blue; @@ -129,12 +133,12 @@ public: // Recursive constructor for all: color modification and darker/lighter Source(QPalette::ColorGroup group, QPalette::ColorRole role, - Qt::Appearance app, int p_lighter, + Qt::ColorScheme scheme, int p_lighter, int p_red, int p_green, int p_blue) : sourceType(SourceType::Modified) { rec.colorGroup = group; rec.colorRole = role; - rec.appearance = app; + rec.colorScheme = scheme; rec.lighter = p_lighter; rec.deltaRed = p_red; rec.deltaGreen = p_green; @@ -158,25 +162,25 @@ public: } }; - // Struct with key attributes to identify a brush: color group, color role and appearance + // Struct with key attributes to identify a brush: color group, color role and color scheme struct TargetBrush { QPalette::ColorGroup colorGroup; QPalette::ColorRole colorRole; - Qt::Appearance appearance; + Qt::ColorScheme colorScheme; // Generic constructor TargetBrush(QPalette::ColorGroup group, QPalette::ColorRole role, - Qt::Appearance app = Qt::Appearance::Unknown) : - colorGroup(group), colorRole(role), appearance(app) {}; + Qt::ColorScheme scheme = Qt::ColorScheme::Unknown) : + colorGroup(group), colorRole(role), colorScheme(scheme) {}; - // Copy constructor with appearance modifier for dark/light aware search - TargetBrush(const TargetBrush &other, Qt::Appearance app) : - colorGroup(other.colorGroup), colorRole(other.colorRole), appearance(app) {}; + // Copy constructor with color scheme modifier for dark/light aware search + TargetBrush(const TargetBrush &other, Qt::ColorScheme scheme) : + colorGroup(other.colorGroup), colorRole(other.colorRole), colorScheme(scheme) {}; // struct becomes key of a map, so operator< is needed bool operator<(const TargetBrush& other) const { - return std::tie(colorGroup, colorRole, appearance) < - std::tie(other.colorGroup, other.colorRole, other.appearance); + return std::tie(colorGroup, colorRole, colorScheme) < + std::tie(other.colorGroup, other.colorRole, other.colorScheme); } }; @@ -189,7 +193,7 @@ public: // Public getters const QPalette *palette(QPlatformTheme::Palette = QPlatformTheme::SystemPalette) const; QPixmap standardPixmap(QPlatformTheme::StandardPixmap standardPixmap, const QSizeF &size) const; - Qt::Appearance appearance() const { return m_appearance; }; + Qt::ColorScheme colorScheme() const { return m_colorScheme; }; static QPalette standardPalette(); const QString themeName() const { return m_interface ? m_interface->themeName() : QString(); }; const QFont *font(QPlatformTheme::Font type) const; @@ -204,9 +208,11 @@ private: PaletteMap m_palettes; std::unique_ptr<QGtk3Interface> m_interface; +#if QT_CONFIG(dbus) + std::unique_ptr<QGtk3PortalInterface> m_portalInterface; +#endif - - Qt::Appearance m_appearance = Qt::Appearance::Unknown; + Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown; // Caches for Pixmaps, fonts and palettes mutable QCache<QPlatformTheme::StandardPixmap, QImage> m_pixmapCache; @@ -219,7 +225,7 @@ private: // Get GTK3 source for a target brush Source brush (const TargetBrush &brush, const BrushMap &map) const; - // clear cache, palettes and appearance + // clear cache, palettes and color scheme void clear(); // Data creation, import & export diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp index c846905786..9d23ba7e48 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp @@ -5,7 +5,6 @@ #include "qgtk3dialoghelpers.h" #include "qgtk3menu.h" #include <QVariant> -#include <QtCore/qregularexpression.h> #include <QGuiApplication> #include <qpa/qwindowsysteminterface.h> @@ -105,20 +104,6 @@ QGtk3Theme::QGtk3Theme() SETTING_CONNECT("gtk-cursor-theme-size"); #undef SETTING_CONNECT - /* Set XCURSOR_SIZE and XCURSOR_THEME for Wayland sessions */ - if (QGuiApplication::platformName().startsWith("wayland"_L1)) { - if (qEnvironmentVariableIsEmpty("XCURSOR_SIZE")) { - const int cursorSize = gtkSetting<gint>("gtk-cursor-theme-size"); - if (cursorSize > 0) - qputenv("XCURSOR_SIZE", QString::number(cursorSize).toUtf8()); - } - if (qEnvironmentVariableIsEmpty("XCURSOR_THEME")) { - const QString cursorTheme = gtkSetting("gtk-cursor-theme-name"); - if (!cursorTheme.isEmpty()) - qputenv("XCURSOR_THEME", cursorTheme.toUtf8()); - } - } - m_storage.reset(new QGtk3Storage); } @@ -175,48 +160,10 @@ QString QGtk3Theme::gtkFontName() const return QGnomeTheme::gtkFontName(); } -Qt::Appearance QGtk3Theme::appearance() const +Qt::ColorScheme QGtk3Theme::colorScheme() const { - if (m_storage) - return m_storage->appearance(); - /* - https://docs.gtk.org/gtk3/running.html - - It's possible to set a theme variant after the theme name when using GTK_THEME: - - GTK_THEME=Adwaita:dark - - Some themes also have "-dark" as part of their name. - - We test this environment variable first because the documentation says - it's mainly used for easy debugging, so it should be possible to use it - to override any other settings. - */ - QString themeName = qEnvironmentVariable("GTK_THEME"); - if (!themeName.isEmpty()) - return themeName.contains("dark"_L1, Qt::CaseInsensitive) - ? Qt::Appearance::Dark : Qt::Appearance::Light; - - /* - https://docs.gtk.org/gtk3/property.Settings.gtk-application-prefer-dark-theme.html - - This setting controls which theme is used when the theme specified by - gtk-theme-name provides both light and dark variants. We can save a - regex check by testing this property first. - */ - const auto preferDark = gtkSetting<gboolean>("gtk-application-prefer-dark-theme"); - if (preferDark) - return Qt::Appearance::Dark; - - /* - https://docs.gtk.org/gtk3/property.Settings.gtk-theme-name.html - */ - themeName = gtkSetting("gtk-theme-name"); - if (!themeName.isEmpty()) - return themeName.contains("dark"_L1, Qt::CaseInsensitive) - ? Qt::Appearance::Dark : Qt::Appearance::Light; - - return Qt::Appearance::Unknown; + Q_ASSERT(m_storage); + return m_storage->colorScheme(); } bool QGtk3Theme::usePlatformNativeDialog(DialogType type) const @@ -274,23 +221,28 @@ bool QGtk3Theme::useNativeFileDialog() const QPalette *QGtk3Theme::palette(Palette type) const { - return m_storage ? m_storage->palette(type) : QPlatformTheme::palette(type); + Q_ASSERT(m_storage); + return m_storage->palette(type); } QPixmap QGtk3Theme::standardPixmap(StandardPixmap sp, const QSizeF &size) const { - return m_storage ? m_storage->standardPixmap(sp, size) : QPlatformTheme::standardPixmap(sp, size); + Q_ASSERT(m_storage); + return m_storage->standardPixmap(sp, size); } const QFont *QGtk3Theme::font(Font type) const { - return m_storage ? m_storage->font(type) : QGnomeTheme::font(type); + Q_ASSERT(m_storage); + return m_storage->font(type); } QIcon QGtk3Theme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions) const { - return m_storage ? m_storage->fileIcon(fileInfo) : QGnomeTheme::fileIcon(fileInfo, iconOptions); + Q_UNUSED(iconOptions); + Q_ASSERT(m_storage); + return m_storage->fileIcon(fileInfo); } QT_END_NAMESPACE diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.h b/src/plugins/platformthemes/gtk3/qgtk3theme.h index 0071df9cea..2828cc56e6 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3theme.h +++ b/src/plugins/platformthemes/gtk3/qgtk3theme.h @@ -18,7 +18,7 @@ public: virtual QVariant themeHint(ThemeHint hint) const override; virtual QString gtkFontName() const override; - Qt::Appearance appearance() const override; + Qt::ColorScheme colorScheme() const override; bool usePlatformNativeDialog(DialogType type) const override; QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override; diff --git a/src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt b/src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt index f3c76ff7d1..6228e83ec7 100644 --- a/src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt +++ b/src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt @@ -1,8 +1,6 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause -# Generated from xdgdesktopportal.pro. - ##################################################################### ## QXdgDesktopPortalThemePlugin Plugin: ##################################################################### @@ -22,6 +20,3 @@ qt_internal_add_plugin(QXdgDesktopPortalThemePlugin Qt::Gui Qt::GuiPrivate ) - -#### Keys ignored in scope 1:.:.:xdgdesktopportal.pro:<TRUE>: -# PLUGIN_EXTENDS = "-" diff --git a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp index ec3872f174..1c162be8fc 100644 --- a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp +++ b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp @@ -169,19 +169,18 @@ void QXdgDesktopPortalFileDialog::openPortal(Qt::WindowFlags windowFlags, Qt::Wi options.insert("multiple"_L1, d->multipleFiles); options.insert("directory"_L1, d->directoryMode); - if (d->saveFile) { - if (!d->directory.isEmpty()) - options.insert("current_folder"_L1, QFile::encodeName(d->directory).append('\0')); - - if (!d->selectedFiles.isEmpty()) { - // current_file for the file to be pre-selected, current_name for the file name to be pre-filled - // current_file accepts absolute path and requires the file to exist - // while current_name accepts just file name - QFileInfo selectedFileInfo(d->selectedFiles.first()); - if (selectedFileInfo.exists()) - options.insert("current_file"_L1, QFile::encodeName(d->selectedFiles.first()).append('\0')); - options.insert("current_name"_L1, selectedFileInfo.fileName()); - } + if (!d->directory.isEmpty()) + options.insert("current_folder"_L1, QFile::encodeName(d->directory).append('\0')); + + if (d->saveFile && !d->selectedFiles.isEmpty()) { + // current_file for the file to be pre-selected, current_name for the file name to be + // pre-filled current_file accepts absolute path and requires the file to exist while + // current_name accepts just file name + QFileInfo selectedFileInfo(d->selectedFiles.constFirst()); + if (selectedFileInfo.exists()) + options.insert("current_file"_L1, + QFile::encodeName(d->selectedFiles.constFirst()).append('\0')); + options.insert("current_name"_L1, selectedFileInfo.fileName()); } // Insert filters @@ -214,6 +213,9 @@ void QXdgDesktopPortalFileDialog::openPortal(Qt::WindowFlags windowFlags, Qt::Wi filter.name = mimeType.comment(); filter.filterConditions = filterConditions; + if (filter.name.isEmpty()) + filter.name = mimeTypefilter; + filterList << filter; if (!d->selectedMimeTypeFilter.isEmpty() && d->selectedMimeTypeFilter == mimeTypefilter) diff --git a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp index c7b7ec550f..355d3e6cc9 100644 --- a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp +++ b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp @@ -20,8 +20,9 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; -class QXdgDesktopPortalThemePrivate : public QPlatformThemePrivate -{ +class QXdgDesktopPortalThemePrivate : public QObject + { + Q_OBJECT public: enum XdgColorschemePref { None, @@ -30,7 +31,7 @@ public: }; QXdgDesktopPortalThemePrivate() - : QPlatformThemePrivate() + : QObject() { } ~QXdgDesktopPortalThemePrivate() @@ -40,7 +41,7 @@ public: /*! \internal - Converts the given Freedesktop color scheme setting \a colorschemePref to a Qt::Appearance value. + Converts the given Freedesktop color scheme setting \a colorschemePref to a Qt::ColorScheme value. Specification: https://github.com/flatpak/xdg-desktop-portal/blob/d7a304a00697d7d608821253cd013f3b97ac0fb6/data/org.freedesktop.impl.portal.Settings.xml#L33-L45 Unfortunately the enum numerical values are not defined identically, so we have to convert them. @@ -53,18 +54,29 @@ public: 1: Prefer dark appearance | 2: Dark 2: Prefer light appearance | 1: Light */ - static Qt::Appearance appearanceFromXdgPref(const XdgColorschemePref colorschemePref) + static Qt::ColorScheme colorSchemeFromXdgPref(const XdgColorschemePref colorschemePref) { switch (colorschemePref) { - case PreferDark: return Qt::Appearance::Dark; - case PreferLight: return Qt::Appearance::Light; - default: return Qt::Appearance::Unknown; + case PreferDark: return Qt::ColorScheme::Dark; + case PreferLight: return Qt::ColorScheme::Light; + default: return Qt::ColorScheme::Unknown; } } +public Q_SLOTS: + void settingChanged(const QString &group, const QString &key, + const QDBusVariant &value) + { + if (group == "org.freedesktop.appearance"_L1 && key == "color-scheme"_L1) { + colorScheme = colorSchemeFromXdgPref(static_cast<XdgColorschemePref>(value.variant().toUInt())); + QWindowSystemInterface::handleThemeChange(); + } + } + +public: QPlatformTheme *baseTheme = nullptr; uint fileChooserPortalVersion = 0; - Qt::Appearance appearance = Qt::Appearance::Unknown; + Qt::ColorScheme colorScheme = Qt::ColorScheme::Unknown; }; QXdgDesktopPortalTheme::QXdgDesktopPortalTheme() @@ -104,7 +116,7 @@ QXdgDesktopPortalTheme::QXdgDesktopPortalTheme() message << "org.freedesktop.portal.FileChooser"_L1 << "version"_L1; QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); - QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [d] (QDBusPendingCallWatcher *watcher) { + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [d] (QDBusPendingCallWatcher *watcher) { QDBusPendingReply<QVariant> reply = *watcher; if (reply.isValid()) { d->fileChooserPortalVersion = reply.value().toUInt(); @@ -124,8 +136,13 @@ QXdgDesktopPortalTheme::QXdgDesktopPortalTheme() if (reply.isValid()) { const QDBusVariant dbusVariant = qvariant_cast<QDBusVariant>(reply.value()); const QXdgDesktopPortalThemePrivate::XdgColorschemePref xdgPref = static_cast<QXdgDesktopPortalThemePrivate::XdgColorschemePref>(dbusVariant.variant().toUInt()); - d->appearance = QXdgDesktopPortalThemePrivate::appearanceFromXdgPref(xdgPref); + d->colorScheme = QXdgDesktopPortalThemePrivate::colorSchemeFromXdgPref(xdgPref); } + + QDBusConnection::sessionBus().connect( + "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1, + "org.freedesktop.portal.Settings"_L1, "SettingChanged"_L1, d_ptr.get(), + SLOT(settingChanged(QString, QString, QDBusVariant))); } QPlatformMenuItem* QXdgDesktopPortalTheme::createPlatformMenuItem() const @@ -205,10 +222,12 @@ QVariant QXdgDesktopPortalTheme::themeHint(ThemeHint hint) const return d->baseTheme->themeHint(hint); } -Qt::Appearance QXdgDesktopPortalTheme::appearance() const +Qt::ColorScheme QXdgDesktopPortalTheme::colorScheme() const { Q_D(const QXdgDesktopPortalTheme); - return d->appearance; + if (d->colorScheme == Qt::ColorScheme::Unknown) + return d->baseTheme->colorScheme(); + return d->colorScheme; } QPixmap QXdgDesktopPortalTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const @@ -245,3 +264,5 @@ QString QXdgDesktopPortalTheme::standardButtonText(int button) const } QT_END_NAMESPACE + +#include "qxdgdesktopportaltheme.moc" diff --git a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h index 8e1cd37932..1ac04c45e6 100644 --- a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h +++ b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h @@ -34,7 +34,7 @@ public: QVariant themeHint(ThemeHint hint) const override; - Qt::Appearance appearance() const override; + Qt::ColorScheme colorScheme() const override; QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override; QIcon fileIcon(const QFileInfo &fileInfo, |