diff --git a/qtbase-use-wayland-on-gnome.patch b/0001-Use-Wayland-by-default-on-GNOME.patch similarity index 72% rename from qtbase-use-wayland-on-gnome.patch rename to 0001-Use-Wayland-by-default-on-GNOME.patch index 526963c..92f04b4 100644 --- a/qtbase-use-wayland-on-gnome.patch +++ b/0001-Use-Wayland-by-default-on-GNOME.patch @@ -1,8 +1,17 @@ +From dacd0c6b3466258d175e7119a8e4836171400820 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 12:03:46 +0200 +Subject: [PATCH 01/25] Use Wayland by default on GNOME + +--- + src/gui/kernel/qguiapplication.cpp | 9 +-------- + 1 file changed, 1 insertion(+), 8 deletions(-) + diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp -index b8bfad4f16..676fdfad5e 100644 +index b39b3b4e5e..a217719ea8 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp -@@ -1376,14 +1376,7 @@ void QGuiApplicationPrivate::createPlatformIntegration() +@@ -1412,14 +1412,7 @@ void QGuiApplicationPrivate::createPlatformIntegration() if (sessionType == QByteArrayLiteral("x11") && !platformName.contains(QByteArrayLiteral("xcb"))) { platformName = QByteArrayLiteral("xcb"); } else if (sessionType == QByteArrayLiteral("wayland") && !platformName.contains(QByteArrayLiteral("wayland"))) { @@ -18,3 +27,6 @@ index b8bfad4f16..676fdfad5e 100644 } } #ifdef QT_QPA_DEFAULT_PLATFORM_NAME +-- +2.41.0 + diff --git a/0002-Add-QPlatformTheme-Appearance-for-detecting-light-da.patch b/0002-Add-QPlatformTheme-Appearance-for-detecting-light-da.patch new file mode 100644 index 0000000..5ae90bc --- /dev/null +++ b/0002-Add-QPlatformTheme-Appearance-for-detecting-light-da.patch @@ -0,0 +1,119 @@ +From 488d73ca92deb669abc724c82dbd3206597107fd Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 12:04:21 +0200 +Subject: [PATCH 02/25] Add QPlatformTheme::Appearance for detecting light/dark + modes + +And implement it on Windows and macOS. +--- + src/gui/kernel/qplatformtheme.cpp | 5 +++++ + src/gui/kernel/qplatformtheme.h | 8 ++++++++ + src/plugins/platforms/cocoa/qcocoatheme.h | 1 + + src/plugins/platforms/cocoa/qcocoatheme.mm | 5 +++++ + src/plugins/platforms/windows/qwindowstheme.cpp | 5 +++++ + src/plugins/platforms/windows/qwindowstheme.h | 3 +++ + 6 files changed, 27 insertions(+) + +diff --git a/src/gui/kernel/qplatformtheme.cpp b/src/gui/kernel/qplatformtheme.cpp +index 2325873245..662fef1426 100644 +--- a/src/gui/kernel/qplatformtheme.cpp ++++ b/src/gui/kernel/qplatformtheme.cpp +@@ -399,6 +399,11 @@ QPlatformDialogHelper *QPlatformTheme::createPlatformDialogHelper(DialogType typ + return nullptr; + } + ++QPlatformTheme::Appearance QPlatformTheme::appearance() const ++{ ++ return Appearance::Unknown; ++} ++ + const QPalette *QPlatformTheme::palette(Palette type) const + { + Q_D(const QPlatformTheme); +diff --git a/src/gui/kernel/qplatformtheme.h b/src/gui/kernel/qplatformtheme.h +index 7e6c9d5740..c0dee4c581 100644 +--- a/src/gui/kernel/qplatformtheme.h ++++ b/src/gui/kernel/qplatformtheme.h +@@ -131,6 +131,12 @@ public: + MessageDialog + }; + ++ enum class Appearance { ++ Unknown = 0x0000, ++ Light = 0x0001, ++ Dark = 0x0002 ++ }; ++ + enum Palette { + SystemPalette, + ToolTipPalette, +@@ -320,6 +326,8 @@ public: + virtual QString standardButtonText(int button) const; + virtual QKeySequence standardButtonShortcut(int button) const; + ++ virtual Appearance appearance() const; ++ + static QVariant defaultThemeHint(ThemeHint hint); + static QString defaultStandardButtonText(int button); + static QString removeMnemonics(const QString &original); +diff --git a/src/plugins/platforms/cocoa/qcocoatheme.h b/src/plugins/platforms/cocoa/qcocoatheme.h +index 50e56ef1bf..f719fd943b 100644 +--- a/src/plugins/platforms/cocoa/qcocoatheme.h ++++ b/src/plugins/platforms/cocoa/qcocoatheme.h +@@ -73,6 +73,7 @@ public: + QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions options = {}) const override; + + QVariant themeHint(ThemeHint hint) const override; ++ Appearance appearance() const override; + QString standardButtonText(int button) const override; + QKeySequence standardButtonShortcut(int button) const override; + +diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm +index d73b028afb..a79fa4e4b9 100644 +--- a/src/plugins/platforms/cocoa/qcocoatheme.mm ++++ b/src/plugins/platforms/cocoa/qcocoatheme.mm +@@ -544,6 +544,11 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const + return QPlatformTheme::themeHint(hint); + } + ++QPlatformTheme::Appearance QCocoaTheme::appearance() const ++{ ++ return qt_mac_applicationIsInDarkMode() ? Appearance::Dark : Appearance::Light; ++} ++ + QString QCocoaTheme::standardButtonText(int button) const + { + return button == QPlatformDialogHelper::Discard ? +diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp +index 25e083fd5c..510b8746da 100644 +--- a/src/plugins/platforms/windows/qwindowstheme.cpp ++++ b/src/plugins/platforms/windows/qwindowstheme.cpp +@@ -520,6 +520,11 @@ QVariant QWindowsTheme::themeHint(ThemeHint hint) const + return QPlatformTheme::themeHint(hint); + } + ++QPlatformTheme::Appearance QWindowsTheme::appearance() const ++{ ++ return QWindowsContext::isDarkMode() ? Appearance::Dark : Appearance::Light; ++} ++ + void QWindowsTheme::clearPalettes() + { + qDeleteAll(m_palettes, m_palettes + NPalettes); +diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h +index af28f2878c..9d5fcc92fe 100644 +--- a/src/plugins/platforms/windows/qwindowstheme.h ++++ b/src/plugins/platforms/windows/qwindowstheme.h +@@ -64,6 +64,9 @@ public: + QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override; + #endif + QVariant themeHint(ThemeHint) const override; ++ ++ Appearance appearance() const override; ++ + const QPalette *palette(Palette type = SystemPalette) const override + { return m_palettes[type]; } + const QFont *font(Font type = SystemFont) const override +-- +2.41.0 + diff --git a/0003-Add-enum-class-Qt-Appearance.patch b/0003-Add-enum-class-Qt-Appearance.patch new file mode 100644 index 0000000..254b880 --- /dev/null +++ b/0003-Add-enum-class-Qt-Appearance.patch @@ -0,0 +1,65 @@ +From cf96f7cecf8d02722e303687c1f30361a4a55cd0 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 12:06:29 +0200 +Subject: [PATCH 03/25] Add enum class Qt::Appearance + +It has been decided to add an appearance property in QStyleHints, which +will be propagated to classes that do not include QPlatformTheme. + +Therefore an appearance enum class is added to the Qt namespace, thus +being available to all Qt classes. +--- + src/corelib/global/qnamespace.h | 7 +++++++ + src/corelib/global/qnamespace.qdoc | 11 +++++++++++ + 2 files changed, 18 insertions(+) + +diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h +index bf19b1627b..acf2c26368 100644 +--- a/src/corelib/global/qnamespace.h ++++ b/src/corelib/global/qnamespace.h +@@ -123,6 +123,12 @@ public: + UNICODE_ACCEL = 0x00000000 + }; + ++ enum class Appearance { ++ Unknown = 0x0000, ++ Light = 0x0001, ++ Dark = 0x0002 ++ }; ++ + enum MouseButton { + NoButton = 0x00000000, + LeftButton = 0x00000001, +@@ -1820,6 +1826,7 @@ public: + QT_Q_ENUM(DayOfWeek) + QT_Q_ENUM(CursorShape) + QT_Q_ENUM(GlobalColor) ++ QT_Q_ENUM(Appearance) + QT_Q_ENUM(AspectRatioMode) + QT_Q_ENUM(TransformationMode) + QT_Q_FLAG(ImageConversionFlags) +diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc +index dbb9469bba..70cf8290f5 100644 +--- a/src/corelib/global/qnamespace.qdoc ++++ b/src/corelib/global/qnamespace.qdoc +@@ -841,6 +841,17 @@ + \sa QDockWidget::setAllowedAreas, QDockWidget::isAreaAllowed + */ + ++/*! ++ \enum Qt::Appearance ++ ++ Represents the appearance of an application's theme, ++ defined by QGuiApplication::palette(). ++ ++ \value Unknown The appearance is unknown. ++ \value Light The background colors are lighter than the text color, i.e. the theme is light. ++ \value Dark The background colors are darker than the text color, i.e. the theme is dark. ++*/ ++ + /*! + \enum Qt::ImageConversionFlag + +-- +2.41.0 + diff --git a/0004-QGtk3Theme-implement-appearance-function-to-detect-d.patch b/0004-QGtk3Theme-implement-appearance-function-to-detect-d.patch new file mode 100644 index 0000000..1f05257 --- /dev/null +++ b/0004-QGtk3Theme-implement-appearance-function-to-detect-d.patch @@ -0,0 +1,89 @@ +From 1d693df019b1ebf54980a015000b01ae947f82ee Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 12:26:39 +0200 +Subject: [PATCH 04/25] QGtk3Theme: implement appearance function to detect + dark themes + +This allows Qt Quick Controls to detect if a dark theme is in use, +and if so, use a dark theme of the current style (if available). +--- + .../platformthemes/gtk3/qgtk3theme.cpp | 42 +++++++++++++++++++ + src/plugins/platformthemes/gtk3/qgtk3theme.h | 2 + + 2 files changed, 44 insertions(+) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +index 93520344f8..a47720384c 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +@@ -41,6 +41,7 @@ + #include "qgtk3dialoghelpers.h" + #include "qgtk3menu.h" + #include ++#include + + #undef signals + #include +@@ -147,6 +148,47 @@ QString QGtk3Theme::gtkFontName() const + return QGnomeTheme::gtkFontName(); + } + ++QPlatformTheme::Appearance QGtk3Theme::appearance() const ++{ ++ /* ++ 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"); ++ const QRegularExpression darkRegex(QStringLiteral("[:-]dark"), QRegularExpression::CaseInsensitiveOption); ++ if (!themeName.isEmpty()) ++ return darkRegex.match(themeName).hasMatch() ? Appearance::Dark : 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("gtk-application-prefer-dark-theme"); ++ if (preferDark) ++ return Appearance::Dark; ++ ++ /* ++ https://docs.gtk.org/gtk3/property.Settings.gtk-theme-name.html ++ */ ++ themeName = gtkSetting("gtk-theme-name"); ++ if (!themeName.isEmpty()) ++ return darkRegex.match(themeName).hasMatch() ? Appearance::Dark : Appearance::Light; ++ ++ return Appearance::Unknown; ++} ++ + bool QGtk3Theme::usePlatformNativeDialog(DialogType type) const + { + switch (type) { +diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.h b/src/plugins/platformthemes/gtk3/qgtk3theme.h +index 54296d2ff1..5f439067af 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3theme.h ++++ b/src/plugins/platformthemes/gtk3/qgtk3theme.h +@@ -52,6 +52,8 @@ public: + virtual QVariant themeHint(ThemeHint hint) const override; + virtual QString gtkFontName() const override; + ++ Appearance appearance() const override; ++ + bool usePlatformNativeDialog(DialogType type) const override; + QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override; + +-- +2.41.0 + diff --git a/0005-Account-for-dark-system-themes-in-qt_fusionPalette.patch b/0005-Account-for-dark-system-themes-in-qt_fusionPalette.patch new file mode 100644 index 0000000..3339773 --- /dev/null +++ b/0005-Account-for-dark-system-themes-in-qt_fusionPalette.patch @@ -0,0 +1,128 @@ +From 3d8874aaf5384818a51f158cd2880b7976c9a93a Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 12:41:53 +0200 +Subject: [PATCH 05/25] Account for dark system themes in qt_fusionPalette + +On Ubuntu (Gnome), the Fusion style didn't account for when a dark +theme was set in the system's settings. Neither QGtk3Theme, nor +QGnomeTheme (which it derives from) provide a palette() implementation, +so they'd fall back to QPlatformTheme's. This default implementation +calls QPlatformThemePrivate::initializeSystemPalette(), which uses +qt_fusionPalette(). + +This patch accounts for QPlatformTheme::appearance() in +qt_fusionPalette(), adjusting the palette roles accordingly +to look correct when run under a dark theme. + +This also fixes the same issue for the Fusion style in Qt Quick +Controls. +--- + src/gui/kernel/qpalette.cpp | 36 ------------------------- + src/gui/kernel/qplatformtheme.cpp | 44 ++++++++++++++++++++++++++++++- + 2 files changed, 43 insertions(+), 37 deletions(-) + +diff --git a/src/gui/kernel/qpalette.cpp b/src/gui/kernel/qpalette.cpp +index a192b7aef4..0549f19699 100644 +--- a/src/gui/kernel/qpalette.cpp ++++ b/src/gui/kernel/qpalette.cpp +@@ -1164,42 +1164,6 @@ void QPalette::setColorGroup(ColorGroup cg, const QBrush &foreground, const QBru + setBrush(cg, ToolTipText, toolTipText); + } + +-Q_GUI_EXPORT QPalette qt_fusionPalette() +-{ +- QColor backGround(239, 239, 239); +- QColor light = backGround.lighter(150); +- QColor mid(backGround.darker(130)); +- QColor midLight = mid.lighter(110); +- QColor base = Qt::white; +- QColor disabledBase(backGround); +- QColor dark = backGround.darker(150); +- QColor darkDisabled = QColor(209, 209, 209).darker(110); +- QColor text = Qt::black; +- QColor hightlightedText = Qt::white; +- QColor disabledText = QColor(190, 190, 190); +- QColor button = backGround; +- QColor shadow = dark.darker(135); +- QColor disabledShadow = shadow.lighter(150); +- +- QPalette fusionPalette(Qt::black,backGround,light,dark,mid,text,base); +- fusionPalette.setBrush(QPalette::Midlight, midLight); +- fusionPalette.setBrush(QPalette::Button, button); +- fusionPalette.setBrush(QPalette::Shadow, shadow); +- fusionPalette.setBrush(QPalette::HighlightedText, hightlightedText); +- +- fusionPalette.setBrush(QPalette::Disabled, QPalette::Text, disabledText); +- fusionPalette.setBrush(QPalette::Disabled, QPalette::WindowText, disabledText); +- fusionPalette.setBrush(QPalette::Disabled, QPalette::ButtonText, disabledText); +- fusionPalette.setBrush(QPalette::Disabled, QPalette::Base, disabledBase); +- fusionPalette.setBrush(QPalette::Disabled, QPalette::Dark, darkDisabled); +- fusionPalette.setBrush(QPalette::Disabled, QPalette::Shadow, disabledShadow); +- +- fusionPalette.setBrush(QPalette::Active, QPalette::Highlight, QColor(48, 140, 198)); +- fusionPalette.setBrush(QPalette::Inactive, QPalette::Highlight, QColor(48, 140, 198)); +- fusionPalette.setBrush(QPalette::Disabled, QPalette::Highlight, QColor(145, 145, 145)); +- return fusionPalette; +-} +- + #ifndef QT_NO_DEBUG_STREAM + QDebug operator<<(QDebug dbg, const QPalette &p) + { +diff --git a/src/gui/kernel/qplatformtheme.cpp b/src/gui/kernel/qplatformtheme.cpp +index 662fef1426..62f569bbef 100644 +--- a/src/gui/kernel/qplatformtheme.cpp ++++ b/src/gui/kernel/qplatformtheme.cpp +@@ -364,7 +364,49 @@ QPlatformThemePrivate::~QPlatformThemePrivate() + delete systemPalette; + } + +-Q_GUI_EXPORT QPalette qt_fusionPalette(); ++Q_GUI_EXPORT QPalette qt_fusionPalette() ++{ ++ const bool darkAppearance = QGuiApplicationPrivate::platformTheme()->appearance() ++ == QPlatformTheme::Appearance::Dark; ++ const QColor windowText = darkAppearance ? QColor(240, 240, 240) : Qt::black; ++ const QColor backGround = darkAppearance ? QColor(50, 50, 50) : QColor(239, 239, 239); ++ const QColor light = backGround.lighter(150); ++ const QColor mid = (backGround.darker(130)); ++ const QColor midLight = mid.lighter(110); ++ const QColor base = darkAppearance ? backGround.darker(140) : Qt::white; ++ const QColor disabledBase(backGround); ++ const QColor dark = backGround.darker(150); ++ const QColor darkDisabled = QColor(209, 209, 209).darker(110); ++ const QColor text = darkAppearance ? windowText : Qt::black; ++ const QColor hightlightedText = darkAppearance ? windowText : Qt::white; ++ const QColor disabledText = darkAppearance ? QColor(130, 130, 130) : QColor(190, 190, 190); ++ const QColor button = backGround; ++ const QColor shadow = dark.darker(135); ++ const QColor disabledShadow = shadow.lighter(150); ++ QColor placeholder = text; ++ placeholder.setAlpha(128); ++ ++ QPalette fusionPalette(windowText, backGround, light, dark, mid, text, base); ++ fusionPalette.setBrush(QPalette::Midlight, midLight); ++ fusionPalette.setBrush(QPalette::Button, button); ++ fusionPalette.setBrush(QPalette::Shadow, shadow); ++ fusionPalette.setBrush(QPalette::HighlightedText, hightlightedText); ++ ++ fusionPalette.setBrush(QPalette::Disabled, QPalette::Text, disabledText); ++ fusionPalette.setBrush(QPalette::Disabled, QPalette::WindowText, disabledText); ++ fusionPalette.setBrush(QPalette::Disabled, QPalette::ButtonText, disabledText); ++ fusionPalette.setBrush(QPalette::Disabled, QPalette::Base, disabledBase); ++ fusionPalette.setBrush(QPalette::Disabled, QPalette::Dark, darkDisabled); ++ fusionPalette.setBrush(QPalette::Disabled, QPalette::Shadow, disabledShadow); ++ ++ fusionPalette.setBrush(QPalette::Active, QPalette::Highlight, QColor(48, 140, 198)); ++ fusionPalette.setBrush(QPalette::Inactive, QPalette::Highlight, QColor(48, 140, 198)); ++ fusionPalette.setBrush(QPalette::Disabled, QPalette::Highlight, QColor(145, 145, 145)); ++ ++ fusionPalette.setBrush(QPalette::PlaceholderText, placeholder); ++ ++ return fusionPalette; ++} + + void QPlatformThemePrivate::initializeSystemPalette() + { +-- +2.41.0 + diff --git a/0006-qt_fusionPalette-make-links-more-legible-on-dark-bac.patch b/0006-qt_fusionPalette-make-links-more-legible-on-dark-bac.patch new file mode 100644 index 0000000..9fabfaf --- /dev/null +++ b/0006-qt_fusionPalette-make-links-more-legible-on-dark-bac.patch @@ -0,0 +1,49 @@ +From 84b0d114d98c58ecf00e20987321182e8e2ba4c1 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 12:44:00 +0200 +Subject: [PATCH 06/25] qt_fusionPalette: make links more legible on dark + backgrounds + +QPalette's default for Link is Qt::blue, which is difficult to read +on dark backgrounds. + +Use the same blue that is already used for Highlight, as it is +consistent and easy to read. +--- + src/gui/kernel/qplatformtheme.cpp | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/src/gui/kernel/qplatformtheme.cpp b/src/gui/kernel/qplatformtheme.cpp +index 62f569bbef..14fdf94755 100644 +--- a/src/gui/kernel/qplatformtheme.cpp ++++ b/src/gui/kernel/qplatformtheme.cpp +@@ -378,6 +378,7 @@ Q_GUI_EXPORT QPalette qt_fusionPalette() + const QColor dark = backGround.darker(150); + const QColor darkDisabled = QColor(209, 209, 209).darker(110); + const QColor text = darkAppearance ? windowText : Qt::black; ++ const QColor highlight = QColor(48, 140, 198); + const QColor hightlightedText = darkAppearance ? windowText : Qt::white; + const QColor disabledText = darkAppearance ? QColor(130, 130, 130) : QColor(190, 190, 190); + const QColor button = backGround; +@@ -399,12 +400,16 @@ Q_GUI_EXPORT QPalette qt_fusionPalette() + fusionPalette.setBrush(QPalette::Disabled, QPalette::Dark, darkDisabled); + fusionPalette.setBrush(QPalette::Disabled, QPalette::Shadow, disabledShadow); + +- fusionPalette.setBrush(QPalette::Active, QPalette::Highlight, QColor(48, 140, 198)); +- fusionPalette.setBrush(QPalette::Inactive, QPalette::Highlight, QColor(48, 140, 198)); ++ fusionPalette.setBrush(QPalette::Active, QPalette::Highlight, highlight); ++ fusionPalette.setBrush(QPalette::Inactive, QPalette::Highlight, highlight); + fusionPalette.setBrush(QPalette::Disabled, QPalette::Highlight, QColor(145, 145, 145)); + + fusionPalette.setBrush(QPalette::PlaceholderText, placeholder); + ++ // Use a more legible light blue on dark backgrounds than the default Qt::blue. ++ if (darkAppearance) ++ fusionPalette.setBrush(QPalette::Link, highlight); ++ + return fusionPalette; + } + +-- +2.41.0 + diff --git a/0007-Add-nullptr-check-for-theme-when-initializing-palett.patch b/0007-Add-nullptr-check-for-theme-when-initializing-palett.patch new file mode 100644 index 0000000..3dc1a9f --- /dev/null +++ b/0007-Add-nullptr-check-for-theme-when-initializing-palett.patch @@ -0,0 +1,29 @@ +From dff40b9fed2b91244d6664342daf859b3aa0375f Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 12:44:37 +0200 +Subject: [PATCH 07/25] Add nullptr check for theme when initializing palette + +--- + src/gui/kernel/qplatformtheme.cpp | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/gui/kernel/qplatformtheme.cpp b/src/gui/kernel/qplatformtheme.cpp +index 14fdf94755..827829c4a5 100644 +--- a/src/gui/kernel/qplatformtheme.cpp ++++ b/src/gui/kernel/qplatformtheme.cpp +@@ -366,8 +366,10 @@ QPlatformThemePrivate::~QPlatformThemePrivate() + + Q_GUI_EXPORT QPalette qt_fusionPalette() + { +- const bool darkAppearance = QGuiApplicationPrivate::platformTheme()->appearance() +- == QPlatformTheme::Appearance::Dark; ++ auto theme = QGuiApplicationPrivate::platformTheme(); ++ const bool darkAppearance = theme ++ ? theme->appearance() == QPlatformTheme::Appearance::Dark ++ : false; + const QColor windowText = darkAppearance ? QColor(240, 240, 240) : Qt::black; + const QColor backGround = darkAppearance ? QColor(50, 50, 50) : QColor(239, 239, 239); + const QColor light = backGround.lighter(150); +-- +2.41.0 + diff --git a/0008-Replace-QPlatformTheme-Appearance-by-Qt-Appearance.patch b/0008-Replace-QPlatformTheme-Appearance-by-Qt-Appearance.patch new file mode 100644 index 0000000..6e67912 --- /dev/null +++ b/0008-Replace-QPlatformTheme-Appearance-by-Qt-Appearance.patch @@ -0,0 +1,202 @@ +From 240ce954220d713968e608f2766144c7657bceed Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 12:56:06 +0200 +Subject: [PATCH 08/25] Replace QPlatformTheme::Appearance by Qt:Appearance + +With the introduction of Qt:Appearance, its predecessor in +QPlatformTheme has become redundant. + +This patch replaces all occurrences of QPlatformTheme::Appearance with +the new enum class. +--- + src/gui/kernel/qplatformtheme.cpp | 6 +++--- + src/gui/kernel/qplatformtheme.h | 8 +------- + src/plugins/platforms/cocoa/qcocoatheme.h | 2 +- + src/plugins/platforms/cocoa/qcocoatheme.mm | 4 ++-- + src/plugins/platforms/windows/qwindowstheme.cpp | 4 ++-- + src/plugins/platforms/windows/qwindowstheme.h | 2 +- + src/plugins/platformthemes/gtk3/qgtk3theme.cpp | 10 +++++----- + src/plugins/platformthemes/gtk3/qgtk3theme.h | 2 +- + src/plugins/styles/mac/qmacstyle_mac.mm | 2 +- + 9 files changed, 17 insertions(+), 23 deletions(-) + +diff --git a/src/gui/kernel/qplatformtheme.cpp b/src/gui/kernel/qplatformtheme.cpp +index 827829c4a5..6dd7d5c923 100644 +--- a/src/gui/kernel/qplatformtheme.cpp ++++ b/src/gui/kernel/qplatformtheme.cpp +@@ -368,7 +368,7 @@ Q_GUI_EXPORT QPalette qt_fusionPalette() + { + auto theme = QGuiApplicationPrivate::platformTheme(); + const bool darkAppearance = theme +- ? theme->appearance() == QPlatformTheme::Appearance::Dark ++ ? theme->appearance() == Qt::Appearance::Dark + : false; + const QColor windowText = darkAppearance ? QColor(240, 240, 240) : Qt::black; + const QColor backGround = darkAppearance ? QColor(50, 50, 50) : QColor(239, 239, 239); +@@ -448,9 +448,9 @@ QPlatformDialogHelper *QPlatformTheme::createPlatformDialogHelper(DialogType typ + return nullptr; + } + +-QPlatformTheme::Appearance QPlatformTheme::appearance() const ++Qt::Appearance QPlatformTheme::appearance() const + { +- return Appearance::Unknown; ++ return Qt::Appearance::Unknown; + } + + const QPalette *QPlatformTheme::palette(Palette type) const +diff --git a/src/gui/kernel/qplatformtheme.h b/src/gui/kernel/qplatformtheme.h +index c0dee4c581..41213bf32b 100644 +--- a/src/gui/kernel/qplatformtheme.h ++++ b/src/gui/kernel/qplatformtheme.h +@@ -131,12 +131,6 @@ public: + MessageDialog + }; + +- enum class Appearance { +- Unknown = 0x0000, +- Light = 0x0001, +- Dark = 0x0002 +- }; +- + enum Palette { + SystemPalette, + ToolTipPalette, +@@ -326,7 +320,7 @@ public: + virtual QString standardButtonText(int button) const; + virtual QKeySequence standardButtonShortcut(int button) const; + +- virtual Appearance appearance() const; ++ virtual Qt::Appearance appearance() const; + + static QVariant defaultThemeHint(ThemeHint hint); + static QString defaultStandardButtonText(int button); +diff --git a/src/plugins/platforms/cocoa/qcocoatheme.h b/src/plugins/platforms/cocoa/qcocoatheme.h +index f719fd943b..78ff7cb51c 100644 +--- a/src/plugins/platforms/cocoa/qcocoatheme.h ++++ b/src/plugins/platforms/cocoa/qcocoatheme.h +@@ -73,7 +73,7 @@ public: + QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions options = {}) const override; + + QVariant themeHint(ThemeHint hint) const override; +- Appearance appearance() const override; ++ Qt::Appearance appearance() const override; + QString standardButtonText(int button) const override; + QKeySequence standardButtonShortcut(int button) const override; + +diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm +index a79fa4e4b9..cd2e7ef90a 100644 +--- a/src/plugins/platforms/cocoa/qcocoatheme.mm ++++ b/src/plugins/platforms/cocoa/qcocoatheme.mm +@@ -544,9 +544,9 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const + return QPlatformTheme::themeHint(hint); + } + +-QPlatformTheme::Appearance QCocoaTheme::appearance() const ++Qt::Appearance QCocoaTheme::appearance() const + { +- return qt_mac_applicationIsInDarkMode() ? Appearance::Dark : Appearance::Light; ++ return qt_mac_applicationIsInDarkMode() ? Qt::Appearance::Dark : Qt::Appearance::Light; + } + + QString QCocoaTheme::standardButtonText(int button) const +diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp +index 510b8746da..5d006e6ba9 100644 +--- a/src/plugins/platforms/windows/qwindowstheme.cpp ++++ b/src/plugins/platforms/windows/qwindowstheme.cpp +@@ -520,9 +520,9 @@ QVariant QWindowsTheme::themeHint(ThemeHint hint) const + return QPlatformTheme::themeHint(hint); + } + +-QPlatformTheme::Appearance QWindowsTheme::appearance() const ++Qt::Appearance QWindowsTheme::appearance() const + { +- return QWindowsContext::isDarkMode() ? Appearance::Dark : Appearance::Light; ++ return QWindowsContext::isDarkMode() ? Qt::Appearance::Dark : Qt::Appearance::Light; + } + + void QWindowsTheme::clearPalettes() +diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h +index 9d5fcc92fe..ea72c96049 100644 +--- a/src/plugins/platforms/windows/qwindowstheme.h ++++ b/src/plugins/platforms/windows/qwindowstheme.h +@@ -65,7 +65,7 @@ public: + #endif + QVariant themeHint(ThemeHint) const override; + +- Appearance appearance() const override; ++ Qt::Appearance appearance() const override; + + const QPalette *palette(Palette type = SystemPalette) const override + { return m_palettes[type]; } +diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +index a47720384c..2a70d5f3dd 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +@@ -148,7 +148,7 @@ QString QGtk3Theme::gtkFontName() const + return QGnomeTheme::gtkFontName(); + } + +-QPlatformTheme::Appearance QGtk3Theme::appearance() const ++Qt::Appearance QGtk3Theme::appearance() const + { + /* + https://docs.gtk.org/gtk3/running.html +@@ -166,7 +166,7 @@ QPlatformTheme::Appearance QGtk3Theme::appearance() const + QString themeName = qEnvironmentVariable("GTK_THEME"); + const QRegularExpression darkRegex(QStringLiteral("[:-]dark"), QRegularExpression::CaseInsensitiveOption); + if (!themeName.isEmpty()) +- return darkRegex.match(themeName).hasMatch() ? Appearance::Dark : Appearance::Light; ++ return darkRegex.match(themeName).hasMatch() ? Qt::Appearance::Dark : Qt::Appearance::Light; + + /* + https://docs.gtk.org/gtk3/property.Settings.gtk-application-prefer-dark-theme.html +@@ -177,16 +177,16 @@ QPlatformTheme::Appearance QGtk3Theme::appearance() const + */ + const auto preferDark = gtkSetting("gtk-application-prefer-dark-theme"); + if (preferDark) +- return Appearance::Dark; ++ return Qt::Appearance::Dark; + + /* + https://docs.gtk.org/gtk3/property.Settings.gtk-theme-name.html + */ + themeName = gtkSetting("gtk-theme-name"); + if (!themeName.isEmpty()) +- return darkRegex.match(themeName).hasMatch() ? Appearance::Dark : Appearance::Light; ++ return darkRegex.match(themeName).hasMatch() ? Qt::Appearance::Dark : Qt::Appearance::Light; + +- return Appearance::Unknown; ++ return Qt::Appearance::Unknown; + } + + bool QGtk3Theme::usePlatformNativeDialog(DialogType type) const +diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.h b/src/plugins/platformthemes/gtk3/qgtk3theme.h +index 5f439067af..89a3b98994 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3theme.h ++++ b/src/plugins/platformthemes/gtk3/qgtk3theme.h +@@ -52,7 +52,7 @@ public: + virtual QVariant themeHint(ThemeHint hint) const override; + virtual QString gtkFontName() const override; + +- Appearance appearance() const override; ++ Qt::Appearance appearance() const override; + + bool usePlatformNativeDialog(DialogType type) const override; + QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override; +diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm +index 4171e08286..fa748eb38a 100644 +--- a/src/plugins/styles/mac/qmacstyle_mac.mm ++++ b/src/plugins/styles/mac/qmacstyle_mac.mm +@@ -303,7 +303,7 @@ static const qreal titleBarButtonSpacing = 8; + // active: window is active + // selected: tab is selected + // hovered: tab is hovered +-bool isDarkMode() { return qt_mac_applicationIsInDarkMode(); } ++bool isDarkMode() { return QGuiApplicationPrivate::platformTheme()->appearance() == Qt::Appearance::Dark; } + + #if QT_CONFIG(tabbar) + static const QColor lightTabBarTabBackgroundActive(190, 190, 190); +-- +2.41.0 + diff --git a/0009-Rename-QGuiApplicationPrivate-notifyThemeChanged-to-.patch b/0009-Rename-QGuiApplicationPrivate-notifyThemeChanged-to-.patch new file mode 100644 index 0000000..c743682 --- /dev/null +++ b/0009-Rename-QGuiApplicationPrivate-notifyThemeChanged-to-.patch @@ -0,0 +1,110 @@ +From 168ca696f509e0437550cddb80ea61a5d9ee0f1b Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 13:04:13 +0200 +Subject: [PATCH 09/25] Rename QGuiApplicationPrivate::notifyThemeChanged to + handleThemeChanged + +The work done by QGuiApplicationPrivate in response to a theme change +goes beyond notifying. +--- + src/gui/kernel/qguiapplication.cpp | 26 +++++++++++++------------- + src/gui/kernel/qguiapplication_p.h | 2 +- + src/widgets/kernel/qapplication.cpp | 4 ++-- + src/widgets/kernel/qapplication_p.h | 2 +- + 4 files changed, 17 insertions(+), 17 deletions(-) + +diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp +index a217719ea8..ddde913064 100644 +--- a/src/gui/kernel/qguiapplication.cpp ++++ b/src/gui/kernel/qguiapplication.cpp +@@ -2563,13 +2563,25 @@ void QGuiApplicationPrivate::processSafeAreaMarginsChangedEvent(QWindowSystemInt + void QGuiApplicationPrivate::processThemeChanged(QWindowSystemInterfacePrivate::ThemeChangeEvent *tce) + { + if (self) +- self->notifyThemeChanged(); ++ self->handleThemeChanged(); + if (QWindow *window = tce->window.data()) { + QEvent e(QEvent::ThemeChange); + QGuiApplication::sendSpontaneousEvent(window, &e); + } + } + ++void QGuiApplicationPrivate::handleThemeChanged() ++{ ++ updatePalette(); ++ ++ if (!(applicationResourceFlags & ApplicationFontExplicitlySet)) { ++ const auto locker = qt_scoped_lock(applicationFontMutex); ++ clearFontUnlocked(); ++ initFontUnlocked(); ++ } ++ initThemeHints(); ++} ++ + void QGuiApplicationPrivate::processGeometryChangeEvent(QWindowSystemInterfacePrivate::GeometryChangeEvent *e) + { + if (e->window.isNull()) +@@ -4249,18 +4261,6 @@ QPixmap QGuiApplicationPrivate::getPixmapCursor(Qt::CursorShape cshape) + return QPixmap(); + } + +-void QGuiApplicationPrivate::notifyThemeChanged() +-{ +- updatePalette(); +- +- if (!(applicationResourceFlags & ApplicationFontExplicitlySet)) { +- const auto locker = qt_scoped_lock(applicationFontMutex); +- clearFontUnlocked(); +- initFontUnlocked(); +- } +- initThemeHints(); +-} +- + #if QT_CONFIG(draganddrop) + void QGuiApplicationPrivate::notifyDragStarted(const QDrag *drag) + { +diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h +index 261add1ba4..a13a797ec3 100644 +--- a/src/gui/kernel/qguiapplication_p.h ++++ b/src/gui/kernel/qguiapplication_p.h +@@ -327,7 +327,7 @@ public: + static void updatePalette(); + + protected: +- virtual void notifyThemeChanged(); ++ virtual void handleThemeChanged(); + + static bool setPalette(const QPalette &palette); + virtual QPalette basePalette() const; +diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp +index 1a0ac0cc3c..2d3ed99a83 100644 +--- a/src/widgets/kernel/qapplication.cpp ++++ b/src/widgets/kernel/qapplication.cpp +@@ -4371,9 +4371,9 @@ void QApplicationPrivate::translateTouchCancel(QTouchDevice *device, ulong times + } + } + +-void QApplicationPrivate::notifyThemeChanged() ++void QApplicationPrivate::handleThemeChanged() + { +- QGuiApplicationPrivate::notifyThemeChanged(); ++ QGuiApplicationPrivate::handleThemeChanged(); + + qt_init_tooltip_palette(); + } +diff --git a/src/widgets/kernel/qapplication_p.h b/src/widgets/kernel/qapplication_p.h +index ab6d85aeb9..3a7214d01e 100644 +--- a/src/widgets/kernel/qapplication_p.h ++++ b/src/widgets/kernel/qapplication_p.h +@@ -160,7 +160,7 @@ public: + static QStyle *app_style; + + protected: +- void notifyThemeChanged() override; ++ void handleThemeChanged() override; + + QPalette basePalette() const override; + void handlePaletteChanged(const char *className = nullptr) override; +-- +2.41.0 + diff --git a/0010-Send-ThemeChange-event-to-all-windows-when-system-th.patch b/0010-Send-ThemeChange-event-to-all-windows-when-system-th.patch new file mode 100644 index 0000000..a0ee5fc --- /dev/null +++ b/0010-Send-ThemeChange-event-to-all-windows-when-system-th.patch @@ -0,0 +1,102 @@ +From df8ef75321929d429c1f912453b63e85e98455c5 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 13:30:15 +0200 +Subject: [PATCH 10/25] Send ThemeChange event to all windows when system theme + changes + +The QWSI event for theme change has an optional window parameter to +specify the window affected, but most platform react to global theme +changes, and end up passing nullptr into the event. + +The reasonable thing to do in QGuiApplication in that case is send +a theme change event to every QWindow, so that they are all notified +about the situation. + +This approach is what the Windows platform plugin was doing already, +but did so by iterating manually over the windows, resulting in multiple +calls to QGuiApplicationPrivate::handleThemeChanged -- one for each QWSI +event. +--- + src/gui/kernel/qguiapplication.cpp | 9 +++++---- + src/gui/kernel/qwindowsysteminterface.h | 2 +- + src/plugins/platforms/cocoa/qcocoatheme.mm | 2 +- + src/plugins/platforms/ios/qiosscreen.mm | 2 +- + src/plugins/platforms/windows/qwindowscontext.cpp | 3 +-- + 5 files changed, 9 insertions(+), 9 deletions(-) + +diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp +index ddde913064..64e44406fb 100644 +--- a/src/gui/kernel/qguiapplication.cpp ++++ b/src/gui/kernel/qguiapplication.cpp +@@ -2564,10 +2564,11 @@ void QGuiApplicationPrivate::processThemeChanged(QWindowSystemInterfacePrivate:: + { + if (self) + self->handleThemeChanged(); +- if (QWindow *window = tce->window.data()) { +- QEvent e(QEvent::ThemeChange); +- QGuiApplication::sendSpontaneousEvent(window, &e); +- } ++ ++ QEvent themeChangeEvent(QEvent::ThemeChange); ++ const QWindowList windows = tce->window ? QWindowList{tce->window} : window_list; ++ for (auto *window : windows) ++ QGuiApplication::sendSpontaneousEvent(window, &themeChangeEvent); + } + + void QGuiApplicationPrivate::handleThemeChanged() +diff --git a/src/gui/kernel/qwindowsysteminterface.h b/src/gui/kernel/qwindowsysteminterface.h +index 20baef12c1..5967c8f580 100644 +--- a/src/gui/kernel/qwindowsysteminterface.h ++++ b/src/gui/kernel/qwindowsysteminterface.h +@@ -250,7 +250,7 @@ public: + static void handleScreenRefreshRateChange(QScreen *screen, qreal newRefreshRate); + + template +- static void handleThemeChange(QWindow *window); ++ static void handleThemeChange(QWindow *window = nullptr); + + static void handleFileOpenEvent(const QString& fileName); + static void handleFileOpenEvent(const QUrl &url); +diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm +index cd2e7ef90a..269b580361 100644 +--- a/src/plugins/platforms/cocoa/qcocoatheme.mm ++++ b/src/plugins/platforms/cocoa/qcocoatheme.mm +@@ -322,7 +322,7 @@ void QCocoaTheme::handleSystemThemeChange() + QFontCache::instance()->clear(); + } + +- QWindowSystemInterface::handleThemeChange(nullptr); ++ QWindowSystemInterface::handleThemeChange(); + } + + bool QCocoaTheme::usePlatformNativeDialog(DialogType dialogType) const +diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm +index a83d495043..6f0858a4a0 100644 +--- a/src/plugins/platforms/ios/qiosscreen.mm ++++ b/src/plugins/platforms/ios/qiosscreen.mm +@@ -216,7 +216,7 @@ static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen) + if (self.screen == UIScreen.mainScreen) { + if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle) { + QIOSTheme::initializeSystemPalette(); +- QWindowSystemInterface::handleThemeChange(nullptr); ++ QWindowSystemInterface::handleThemeChange(); + } + } + } +diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp +index d5c3022080..cc9857673b 100644 +--- a/src/plugins/platforms/windows/qwindowscontext.cpp ++++ b/src/plugins/platforms/windows/qwindowscontext.cpp +@@ -1237,8 +1237,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, + } + if ((options & QWindowsIntegration::DarkModeStyle) != 0) { + QWindowsTheme::instance()->refresh(); +- for (QWindowsWindow *w : d->m_windows) +- QWindowSystemInterface::handleThemeChange(w->window()); ++ QWindowSystemInterface::handleThemeChange(); + } + } + return d->m_screenManager.handleScreenChanges(); +-- +2.41.0 + diff --git a/0011-Propagate-appearance-property-from-QPlatformTheme-to.patch b/0011-Propagate-appearance-property-from-QPlatformTheme-to.patch new file mode 100644 index 0000000..9e943b6 --- /dev/null +++ b/0011-Propagate-appearance-property-from-QPlatformTheme-to.patch @@ -0,0 +1,261 @@ +From dc53c50184021944e26a3986236f37a7868951ad Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 13:38:50 +0200 +Subject: [PATCH 11/25] Propagate appearance property from QPlatformTheme to + QStyleHints + +Implement appearance property, getter and notifier in QStyleHints. + +Update appearance property in QStyleHints when handling theme +change in QGuiApplicationPrivate. +--- + .../5.15.10/QtGui/private/qstylehints_p.h | 1 + + src/gui/kernel/kernel.pri | 1 + + src/gui/kernel/qguiapplication.cpp | 16 ++++++ + src/gui/kernel/qguiapplication_p.h | 2 + + src/gui/kernel/qstylehints.cpp | 51 +++++++++++------- + src/gui/kernel/qstylehints.h | 3 ++ + src/gui/kernel/qstylehints_p.h | 53 +++++++++++++++++++ + 7 files changed, 108 insertions(+), 19 deletions(-) + create mode 100644 include/QtGui/5.15.10/QtGui/private/qstylehints_p.h + create mode 100644 src/gui/kernel/qstylehints_p.h + +diff --git a/include/QtGui/5.15.10/QtGui/private/qstylehints_p.h b/include/QtGui/5.15.10/QtGui/private/qstylehints_p.h +new file mode 100644 +index 0000000000..fa8b8505a6 +--- /dev/null ++++ b/include/QtGui/5.15.10/QtGui/private/qstylehints_p.h +@@ -0,0 +1 @@ ++#include "../../../../../src/gui/kernel/qstylehints_p.h" +diff --git a/src/gui/kernel/kernel.pri b/src/gui/kernel/kernel.pri +index 9c80f1e2cc..af9385b120 100644 +--- a/src/gui/kernel/kernel.pri ++++ b/src/gui/kernel/kernel.pri +@@ -59,6 +59,7 @@ HEADERS += \ + kernel/qscreen.h \ + kernel/qscreen_p.h \ + kernel/qstylehints.h \ ++ kernel/qstylehints_p.h \ + kernel/qtouchdevice.h \ + kernel/qtouchdevice_p.h \ + kernel/qplatformsharedgraphicscache.h \ +diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp +index 64e44406fb..7c942dc233 100644 +--- a/src/gui/kernel/qguiapplication.cpp ++++ b/src/gui/kernel/qguiapplication.cpp +@@ -74,6 +74,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -2569,6 +2570,21 @@ void QGuiApplicationPrivate::processThemeChanged(QWindowSystemInterfacePrivate:: + const QWindowList windows = tce->window ? QWindowList{tce->window} : window_list; + for (auto *window : windows) + QGuiApplication::sendSpontaneousEvent(window, &themeChangeEvent); ++ ++ QStyleHintsPrivate::get(QGuiApplication::styleHints())->setAppearance(appearance()); ++} ++ ++/*! ++ \internal ++ \brief QGuiApplicationPrivate::appearance ++ \return the platform theme's appearance ++ or Qt::Appearance::Unknown if a platform theme cannot be established ++ Qt::Appearance. ++ */ ++Qt::Appearance QGuiApplicationPrivate::appearance() ++{ ++ return platformTheme() ? platformTheme()->appearance() ++ : Qt::Appearance::Unknown; + } + + void QGuiApplicationPrivate::handleThemeChanged() +diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h +index a13a797ec3..1e7f826c5b 100644 +--- a/src/gui/kernel/qguiapplication_p.h ++++ b/src/gui/kernel/qguiapplication_p.h +@@ -343,6 +343,8 @@ private: + + friend class QDragManager; + ++ static Qt::Appearance appearance(); ++ + static QGuiApplicationPrivate *self; + static QTouchDevice *m_fakeTouchDevice; + static int m_fakeMouseSourcePointId; +diff --git a/src/gui/kernel/qstylehints.cpp b/src/gui/kernel/qstylehints.cpp +index 288bdf1b3c..d4766227ef 100644 +--- a/src/gui/kernel/qstylehints.cpp ++++ b/src/gui/kernel/qstylehints.cpp +@@ -38,6 +38,7 @@ + ****************************************************************************/ + + #include ++#include "qstylehints_p.h" + #include + #include + #include +@@ -79,25 +80,6 @@ static inline QVariant themeableHint(QPlatformTheme::ThemeHint th) + return QPlatformTheme::defaultThemeHint(th); + } + +-class QStyleHintsPrivate : public QObjectPrivate +-{ +- Q_DECLARE_PUBLIC(QStyleHints) +-public: +- int m_mouseDoubleClickInterval = -1; +- int m_mousePressAndHoldInterval = -1; +- int m_startDragDistance = -1; +- int m_startDragTime = -1; +- int m_keyboardInputInterval = -1; +- int m_cursorFlashTime = -1; +- int m_tabFocusBehavior = -1; +- int m_uiEffects = -1; +- int m_showShortcutsInContextMenus = -1; +- int m_wheelScrollLines = -1; +- int m_mouseQuickSelectionThreshold = -1; +- int m_mouseDoubleClickDistance = -1; +- int m_touchDoubleTapDistance = -1; +-}; +- + /*! + \class QStyleHints + \since 5.0 +@@ -176,6 +158,17 @@ int QStyleHints::touchDoubleTapDistance() const + themeableHint(QPlatformTheme::TouchDoubleTapDistance).toInt(); + } + ++/*! ++ \property QStyleHints::appearance ++ \brief the appearance of the platform theme ++ \sa Qt::Appearance ++ \since 6.5 ++*/ ++Qt::Appearance QStyleHints::appearance() const ++{ ++ return d_func()->appearance(); ++} ++ + /*! + Sets the \a mousePressAndHoldInterval. + \internal +@@ -620,6 +613,26 @@ int QStyleHints::mouseQuickSelectionThreshold() const + return themeableHint(QPlatformTheme::MouseQuickSelectionThreshold, QPlatformIntegration::MouseQuickSelectionThreshold).toInt(); + } + ++/*! ++ \internal ++ QStyleHintsPrivate::setAppearance - set a new appearance. ++ Set \a appearance as the new appearance of the QStyleHints. ++ The appearanceChanged signal will be emitted if present and new appearance differ. ++ */ ++void QStyleHintsPrivate::setAppearance(Qt::Appearance appearance) ++{ ++ if (m_appearance != appearance) { ++ m_appearance = appearance; ++ emit q_func()->appearanceChanged(appearance); ++ } ++} ++ ++QStyleHintsPrivate *QStyleHintsPrivate::get(QStyleHints *q) ++{ ++ Q_ASSERT(q); ++ return q->d_func(); ++} ++ + QT_END_NAMESPACE + + #include "moc_qstylehints.cpp" +diff --git a/src/gui/kernel/qstylehints.h b/src/gui/kernel/qstylehints.h +index 30d8fdc64d..3cd540b27a 100644 +--- a/src/gui/kernel/qstylehints.h ++++ b/src/gui/kernel/qstylehints.h +@@ -76,6 +76,7 @@ class Q_GUI_EXPORT QStyleHints : public QObject + Q_PROPERTY(int mouseQuickSelectionThreshold READ mouseQuickSelectionThreshold WRITE setMouseQuickSelectionThreshold NOTIFY mouseQuickSelectionThresholdChanged FINAL) + Q_PROPERTY(int mouseDoubleClickDistance READ mouseDoubleClickDistance STORED false CONSTANT FINAL) + Q_PROPERTY(int touchDoubleTapDistance READ touchDoubleTapDistance STORED false CONSTANT FINAL) ++ Q_PROPERTY(Qt::Appearance appearance READ appearance NOTIFY appearanceChanged FINAL) + + public: + void setMouseDoubleClickInterval(int mouseDoubleClickInterval); +@@ -112,6 +113,7 @@ public: + void setWheelScrollLines(int scrollLines); + void setMouseQuickSelectionThreshold(int threshold); + int mouseQuickSelectionThreshold() const; ++ Qt::Appearance appearance() const; + + Q_SIGNALS: + void cursorFlashTimeChanged(int cursorFlashTime); +@@ -125,6 +127,7 @@ Q_SIGNALS: + void showShortcutsInContextMenusChanged(bool); + void wheelScrollLinesChanged(int scrollLines); + void mouseQuickSelectionThresholdChanged(int threshold); ++ void appearanceChanged(Qt::Appearance appearance); + + private: + friend class QGuiApplication; +diff --git a/src/gui/kernel/qstylehints_p.h b/src/gui/kernel/qstylehints_p.h +new file mode 100644 +index 0000000000..4a16fbef01 +--- /dev/null ++++ b/src/gui/kernel/qstylehints_p.h +@@ -0,0 +1,53 @@ ++// Copyright (C) 2022 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 QSTYLEHINTS_P_H ++#define QSTYLEHINTS_P_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 ++#include ++#include ++#include "qstylehints.h" ++ ++QT_BEGIN_NAMESPACE ++ ++class QStyleHintsPrivate : public QObjectPrivate ++{ ++ Q_DECLARE_PUBLIC(QStyleHints) ++public: ++ int m_mouseDoubleClickInterval = -1; ++ int m_mousePressAndHoldInterval = -1; ++ int m_startDragDistance = -1; ++ int m_startDragTime = -1; ++ int m_keyboardInputInterval = -1; ++ int m_cursorFlashTime = -1; ++ int m_tabFocusBehavior = -1; ++ int m_uiEffects = -1; ++ int m_showShortcutsInContextMenus = -1; ++ int m_wheelScrollLines = -1; ++ int m_mouseQuickSelectionThreshold = -1; ++ int m_mouseDoubleClickDistance = -1; ++ int m_touchDoubleTapDistance = -1; ++ ++ Qt::Appearance appearance() const { return m_appearance; }; ++ void setAppearance(Qt::Appearance appearance); ++ static QStyleHintsPrivate *get(QStyleHints *q); ++ ++private: ++ Qt::Appearance m_appearance = Qt::Appearance::Unknown; ++}; ++ ++QT_END_NAMESPACE ++ ++#endif +-- +2.41.0 + diff --git a/0012-Sync-and-assert-StandardPixmap-enums-in-QPlatformThe.patch b/0012-Sync-and-assert-StandardPixmap-enums-in-QPlatformThe.patch new file mode 100644 index 0000000..240da56 --- /dev/null +++ b/0012-Sync-and-assert-StandardPixmap-enums-in-QPlatformThe.patch @@ -0,0 +1,78 @@ +From 8c9d7b5f33707803b67c737afa18c80e5a4cf229 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 13:43:29 +0200 +Subject: [PATCH 12/25] Sync and assert StandardPixmap enums in QPlatformTheme + and QStyle + +Add missing enum values in QPlatformTheme::standardPixmap to sync with +QStyle::standardPixmap changes from: +785d2b9d0728bbbc0d2a92b7d4186a3114d54128 +aa5a595a98f1af4a514485268a18e6cb9cfec783 + +Add enum values NStandardPixmap at the bottom of both enums as well +as an assertion in QStyle constructor that these values are identical. +Add omitvalue for NStandardPixmap in QStyle (QPlatformTheme enum is +not documented). +--- + src/gui/kernel/qplatformtheme.h | 9 +++++++++ + src/widgets/styles/qstyle.cpp | 4 ++++ + src/widgets/styles/qstyle.h | 1 + + 3 files changed, 14 insertions(+) + +diff --git a/src/gui/kernel/qplatformtheme.h b/src/gui/kernel/qplatformtheme.h +index 41213bf32b..210bd40afa 100644 +--- a/src/gui/kernel/qplatformtheme.h ++++ b/src/gui/kernel/qplatformtheme.h +@@ -256,6 +256,15 @@ public: + MediaVolume, + MediaVolumeMuted, + LineEditClearButton, ++ DialogYesToAllButton, ++ DialogNoToAllButton, ++ DialogSaveAllButton, ++ DialogAbortButton, ++ DialogRetryButton, ++ DialogIgnoreButton, ++ RestoreDefaultsButton, ++ NStandardPixmap, // assertion value for sync with QStyle::StandardPixmap ++ + // do not add any values below/greater than this + CustomBase = 0xf0000000 + }; +diff --git a/src/widgets/styles/qstyle.cpp b/src/widgets/styles/qstyle.cpp +index 669f158b7b..18d21a843d 100644 +--- a/src/widgets/styles/qstyle.cpp ++++ b/src/widgets/styles/qstyle.cpp +@@ -412,6 +412,9 @@ QStyle::QStyle(QStylePrivate &dd) + { + Q_D(QStyle); + d->proxyStyle = this; ++ Q_STATIC_ASSERT_X(int(StandardPixmap::NStandardPixmap) == ++ int(QPlatformTheme::StandardPixmap::NStandardPixmap), ++ "StandardPixmap in QPlatformTheme and QStyle out of sync"); + } + + /*! +@@ -2117,6 +2120,7 @@ void QStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, + This enum value was added in Qt 5.14. + \value SP_RestoreDefaultsButton Icon for a standard RestoreDefaults button in a QDialogButtonBox. + This enum value was added in Qt 5.14. ++ \omitvalue NStandardPixmap + \value SP_CustomBase Base value for custom standard pixmaps; + custom values must be greater than this value. + +diff --git a/src/widgets/styles/qstyle.h b/src/widgets/styles/qstyle.h +index 5be1b4b290..9d98f76152 100644 +--- a/src/widgets/styles/qstyle.h ++++ b/src/widgets/styles/qstyle.h +@@ -845,6 +845,7 @@ public: + SP_DialogRetryButton, + SP_DialogIgnoreButton, + SP_RestoreDefaultsButton, ++ NStandardPixmap, // assertion value for sync with QPlatformTheme::StandardPixmap + // do not add any values below/greater than this + SP_CustomBase = 0xf0000000 + }; +-- +2.41.0 + diff --git a/0013-QGtk3Theme-subscribe-to-theme-hint-changes.patch b/0013-QGtk3Theme-subscribe-to-theme-hint-changes.patch new file mode 100644 index 0000000..d40adb8 --- /dev/null +++ b/0013-QGtk3Theme-subscribe-to-theme-hint-changes.patch @@ -0,0 +1,50 @@ +From 544c063ad74bfcc5c8e9f7d6c2f4e7b6301df497 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 15:38:13 +0200 +Subject: [PATCH 13/25] QGtk3Theme: subscribe to theme hint changes + +--- + .../platformthemes/gtk3/qgtk3theme.cpp | 20 +++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +index 2a70d5f3dd..0e940ae690 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +@@ -40,6 +40,7 @@ + #include "qgtk3theme.h" + #include "qgtk3dialoghelpers.h" + #include "qgtk3menu.h" ++#include "qpa/qwindowsysteminterface.h" + #include + #include + +@@ -101,6 +102,25 @@ QGtk3Theme::QGtk3Theme() + + /* Use our custom log handler. */ + g_log_set_handler("Gtk", G_LOG_LEVEL_MESSAGE, gtkMessageHandler, nullptr); ++ ++#define SETTING_CONNECT(setting) g_signal_connect(settings, "notify::" setting, G_CALLBACK(notifyThemeChanged), nullptr) ++ auto notifyThemeChanged = [] { ++ QWindowSystemInterface::handleThemeChange(nullptr); ++ }; ++ ++ GtkSettings *settings = gtk_settings_get_default(); ++ SETTING_CONNECT("gtk-cursor-blink-time"); ++ SETTING_CONNECT("gtk-double-click-distance"); ++ SETTING_CONNECT("gtk-double-click-time"); ++ SETTING_CONNECT("gtk-long-press-time"); ++ SETTING_CONNECT("gtk-entry-password-hint-timeout"); ++ SETTING_CONNECT("gtk-dnd-drag-threshold"); ++ SETTING_CONNECT("gtk-icon-theme-name"); ++ SETTING_CONNECT("gtk-fallback-icon-theme"); ++ SETTING_CONNECT("gtk-font-name"); ++ SETTING_CONNECT("gtk-application-prefer-dark-theme"); ++ SETTING_CONNECT("gtk-theme-name"); ++#undef SETTING_CONNECT + } + + static inline QVariant gtkGetLongPressTime() +-- +2.41.0 + diff --git a/0014-Gtk3Theme-set-XCURSOR_SIZE-and-XCURSOR_THEME-for-way.patch b/0014-Gtk3Theme-set-XCURSOR_SIZE-and-XCURSOR_THEME-for-way.patch new file mode 100644 index 0000000..2fc49a4 --- /dev/null +++ b/0014-Gtk3Theme-set-XCURSOR_SIZE-and-XCURSOR_THEME-for-way.patch @@ -0,0 +1,54 @@ +From ff4c01410f56001c59ed9628d7365a44019ce85c Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 15:38:38 +0200 +Subject: [PATCH 14/25] Gtk3Theme: set XCURSOR_SIZE and XCURSOR_THEME for + wayland sessions + +GNOME doesn't set these for Wayland session and without those env +variables set users might experience broken cursor with Qt apps +as QWayland reads them to setup QWaylandInputDevice. + +There is no cursor protocol available on Wayland yet, see also +https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/58 + +Qt Wayland QPA plugin still tries to load from those two envs. +--- + src/plugins/platformthemes/gtk3/qgtk3theme.cpp | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +index 0e940ae690..8688fe205e 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +@@ -41,6 +41,7 @@ + #include "qgtk3dialoghelpers.h" + #include "qgtk3menu.h" + #include "qpa/qwindowsysteminterface.h" ++#include + #include + #include + +@@ -121,6 +122,20 @@ QGtk3Theme::QGtk3Theme() + SETTING_CONNECT("gtk-application-prefer-dark-theme"); + SETTING_CONNECT("gtk-theme-name"); + #undef SETTING_CONNECT ++ ++ /* Set XCURSOR_SIZE and XCURSOR_THEME for Wayland sessions */ ++ if (QGuiApplication::platformName().startsWith("wayland")) { ++ if (qEnvironmentVariableIsEmpty("XCURSOR_SIZE")) { ++ const int cursorSize = gtkSetting("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()); ++ } ++ } + } + + static inline QVariant gtkGetLongPressTime() +-- +2.41.0 + diff --git a/0015-Gtk3-fix-stack-smashing-on-mismatch-between-bool-and.patch b/0015-Gtk3-fix-stack-smashing-on-mismatch-between-bool-and.patch new file mode 100644 index 0000000..d862f9e --- /dev/null +++ b/0015-Gtk3-fix-stack-smashing-on-mismatch-between-bool-and.patch @@ -0,0 +1,29 @@ +From fc0d325fee69cc3fa5f415b1e83592376adeb061 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 15:39:31 +0200 +Subject: [PATCH 15/25] Gtk3: fix stack smashing on mismatch between bool and + gboolean + +Glib is written in C and predates C99 (though not really, glib 2.0 was +released in 2002), so it defines gboolean as int, a 4-byte type. C++'s +bool is a 1-byte type, so this caused a buffer overflow. +--- + src/plugins/platformthemes/gtk3/qgtk3theme.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +index 8688fe205e..c01947e402 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +@@ -210,7 +210,7 @@ Qt::Appearance QGtk3Theme::appearance() const + gtk-theme-name provides both light and dark variants. We can save a + regex check by testing this property first. + */ +- const auto preferDark = gtkSetting("gtk-application-prefer-dark-theme"); ++ const auto preferDark = gtkSetting("gtk-application-prefer-dark-theme"); + if (preferDark) + return Qt::Appearance::Dark; + +-- +2.41.0 + diff --git a/0016-Re-implement-palette-standardPixmap-file-icons-fonts.patch b/0016-Re-implement-palette-standardPixmap-file-icons-fonts.patch new file mode 100644 index 0000000..9043d0b --- /dev/null +++ b/0016-Re-implement-palette-standardPixmap-file-icons-fonts.patch @@ -0,0 +1,3323 @@ +From 152d72c30a089a08282c05fe9216ac00542dfd46 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 11:54:44 +0200 +Subject: [PATCH 16/25] Re-implement palette, standardPixmap, file icons, fonts + in QGtk3Theme + +Read theme colors from GTK3 style context and build platform theme +palettes in Qt. +React to runtime theme changes. +Re-implement methods to retrieve GTK3 styled standardPixmaps, fonts +and file icons. +--- + .../5.15.10/QtCore/private/qflatmap_p.h | 2 + + src/corelib/tools/qflatmap_p.h | 1107 +++++++++++++++++ + src/plugins/platformthemes/gtk3/gtk3.pro | 6 + + .../platformthemes/gtk3/qgtk3interface.cpp | 558 +++++++++ + .../platformthemes/gtk3/qgtk3interface_p.h | 170 +++ + src/plugins/platformthemes/gtk3/qgtk3json.cpp | 475 +++++++ + src/plugins/platformthemes/gtk3/qgtk3json_p.h | 102 ++ + .../platformthemes/gtk3/qgtk3storage.cpp | 470 +++++++ + .../platformthemes/gtk3/qgtk3storage_p.h | 234 ++++ + .../platformthemes/gtk3/qgtk3theme.cpp | 32 +- + src/plugins/platformthemes/gtk3/qgtk3theme.h | 8 + + 11 files changed, 3161 insertions(+), 3 deletions(-) + create mode 100644 include/QtCore/5.15.10/QtCore/private/qflatmap_p.h + create mode 100644 src/corelib/tools/qflatmap_p.h + create mode 100644 src/plugins/platformthemes/gtk3/qgtk3interface.cpp + create mode 100644 src/plugins/platformthemes/gtk3/qgtk3interface_p.h + create mode 100644 src/plugins/platformthemes/gtk3/qgtk3json.cpp + create mode 100644 src/plugins/platformthemes/gtk3/qgtk3json_p.h + create mode 100644 src/plugins/platformthemes/gtk3/qgtk3storage.cpp + create mode 100644 src/plugins/platformthemes/gtk3/qgtk3storage_p.h + +diff --git a/include/QtCore/5.15.10/QtCore/private/qflatmap_p.h b/include/QtCore/5.15.10/QtCore/private/qflatmap_p.h +new file mode 100644 +index 0000000000..e629799f72 +--- /dev/null ++++ b/include/QtCore/5.15.10/QtCore/private/qflatmap_p.h +@@ -0,0 +1,2 @@ ++#include "../../../../../src/corelib/tools/qflatmap_p.h" ++ +diff --git a/src/corelib/tools/qflatmap_p.h b/src/corelib/tools/qflatmap_p.h +new file mode 100644 +index 0000000000..45153e23db +--- /dev/null ++++ b/src/corelib/tools/qflatmap_p.h +@@ -0,0 +1,1107 @@ ++// Copyright (C) 2022 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 QFLATMAP_P_H ++#define QFLATMAP_P_H ++ ++// ++// W A R N I N G ++// ------------- ++// ++// This file is not part of the Qt API. It exists for the convenience ++// of a number of Qt sources files. This header file may change from ++// version to version without notice, or even be removed. ++// ++// We mean it. ++// ++ ++#include "qlist.h" ++#include "private/qglobal_p.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++QT_BEGIN_NAMESPACE ++ ++/* ++ QFlatMap provides an associative container backed by sorted sequential ++ containers. By default, QList is used. ++ ++ Keys and values are stored in two separate containers. This provides improved ++ cache locality for key iteration and makes keys() and values() fast ++ operations. ++ ++ One can customize the underlying container type by passing the KeyContainer ++ and MappedContainer template arguments: ++ QFlatMap, std::vector, std::vector> ++*/ ++ ++// Qt 6.4: ++// - removed QFlatMap API which was incompatible with STL semantics ++// - will be released with said API disabled, to catch any out-of-tree users ++// - also allows opting in to the new API using QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT ++// Qt 6.5 ++// - will make QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT the default: ++ ++#ifndef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT ++# define QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT ++#endif ++ ++namespace Qt { ++ ++struct OrderedUniqueRange_t {}; ++constexpr OrderedUniqueRange_t OrderedUniqueRange = {}; ++ ++} // namespace Qt ++ ++template ++class QFlatMapValueCompare : protected Compare ++{ ++public: ++ QFlatMapValueCompare() = default; ++ QFlatMapValueCompare(const Compare &key_compare) ++ : Compare(key_compare) ++ { ++ } ++ ++ using value_type = std::pair; ++ static constexpr bool is_comparator_noexcept = noexcept( ++ std::declval()(std::declval(), std::declval())); ++ ++ bool operator()(const value_type &lhs, const value_type &rhs) const ++ noexcept(is_comparator_noexcept) ++ { ++ return Compare::operator()(lhs.first, rhs.first); ++ } ++}; ++ ++namespace qflatmap { ++namespace detail { ++template ++class QFlatMapMockPointer ++{ ++ T ref; ++public: ++ QFlatMapMockPointer(T r) ++ : ref(r) ++ { ++ } ++ ++ T *operator->() ++ { ++ return &ref; ++ } ++}; ++} // namespace detail ++} // namespace qflatmap ++ ++template, class KeyContainer = QList, ++ class MappedContainer = QList> ++class QFlatMap : private QFlatMapValueCompare ++{ ++ static_assert(std::is_nothrow_destructible_v, "Types with throwing destructors are not supported in Qt containers."); ++ ++ template ++ using mock_pointer = qflatmap::detail::QFlatMapMockPointer; ++ ++public: ++ using key_type = Key; ++ using mapped_type = T; ++ using value_compare = QFlatMapValueCompare; ++ using value_type = typename value_compare::value_type; ++ using key_container_type = KeyContainer; ++ using mapped_container_type = MappedContainer; ++ using size_type = typename key_container_type::size_type; ++ using key_compare = Compare; ++ ++ struct containers ++ { ++ key_container_type keys; ++ mapped_container_type values; ++ }; ++ ++ class iterator ++ { ++ public: ++ using difference_type = ptrdiff_t; ++ using value_type = std::pair; ++ using reference = std::pair; ++ using pointer = mock_pointer; ++ using iterator_category = std::random_access_iterator_tag; ++ ++ iterator() = default; ++ ++ iterator(containers *ac, size_type ai) ++ : c(ac), i(ai) ++ { ++ } ++ ++ reference operator*() const ++ { ++ return { c->keys[i], c->values[i] }; ++ } ++ ++ pointer operator->() const ++ { ++ return { operator*() }; ++ } ++ ++ bool operator==(const iterator &o) const ++ { ++ return c == o.c && i == o.i; ++ } ++ ++ bool operator!=(const iterator &o) const ++ { ++ return !operator==(o); ++ } ++ ++ iterator &operator++() ++ { ++ ++i; ++ return *this; ++ } ++ ++ iterator operator++(int) ++ { ++ ++ iterator r = *this; ++ ++*this; ++ return r; ++ } ++ ++ iterator &operator--() ++ { ++ --i; ++ return *this; ++ } ++ ++ iterator operator--(int) ++ { ++ iterator r = *this; ++ --*this; ++ return r; ++ } ++ ++ iterator &operator+=(size_type n) ++ { ++ i += n; ++ return *this; ++ } ++ ++ friend iterator operator+(size_type n, const iterator a) ++ { ++ iterator ret = a; ++ return ret += n; ++ } ++ ++ friend iterator operator+(const iterator a, size_type n) ++ { ++ return n + a; ++ } ++ ++ iterator &operator-=(size_type n) ++ { ++ i -= n; ++ return *this; ++ } ++ ++ friend iterator operator-(const iterator a, size_type n) ++ { ++ iterator ret = a; ++ return ret -= n; ++ } ++ ++ friend difference_type operator-(const iterator b, const iterator a) ++ { ++ return b.i - a.i; ++ } ++ ++ reference operator[](size_type n) const ++ { ++ size_type k = i + n; ++ return { c->keys[k], c->values[k] }; ++ } ++ ++ bool operator<(const iterator &other) const ++ { ++ return i < other.i; ++ } ++ ++ bool operator>(const iterator &other) const ++ { ++ return i > other.i; ++ } ++ ++ bool operator<=(const iterator &other) const ++ { ++ return i <= other.i; ++ } ++ ++ bool operator>=(const iterator &other) const ++ { ++ return i >= other.i; ++ } ++ ++ const Key &key() const { return c->keys[i]; } ++ T &value() const { return c->values[i]; } ++ ++ private: ++ containers *c = nullptr; ++ size_type i = 0; ++ friend QFlatMap; ++ }; ++ ++ class const_iterator ++ { ++ public: ++ using difference_type = ptrdiff_t; ++ using value_type = std::pair; ++ using reference = std::pair; ++ using pointer = mock_pointer; ++ using iterator_category = std::random_access_iterator_tag; ++ ++ const_iterator() = default; ++ ++ const_iterator(const containers *ac, size_type ai) ++ : c(ac), i(ai) ++ { ++ } ++ ++ const_iterator(iterator o) ++ : c(o.c), i(o.i) ++ { ++ } ++ ++ reference operator*() const ++ { ++ return { c->keys[i], c->values[i] }; ++ } ++ ++ pointer operator->() const ++ { ++ return { operator*() }; ++ } ++ ++ bool operator==(const const_iterator &o) const ++ { ++ return c == o.c && i == o.i; ++ } ++ ++ bool operator!=(const const_iterator &o) const ++ { ++ return !operator==(o); ++ } ++ ++ const_iterator &operator++() ++ { ++ ++i; ++ return *this; ++ } ++ ++ const_iterator operator++(int) ++ { ++ ++ const_iterator r = *this; ++ ++*this; ++ return r; ++ } ++ ++ const_iterator &operator--() ++ { ++ --i; ++ return *this; ++ } ++ ++ const_iterator operator--(int) ++ { ++ const_iterator r = *this; ++ --*this; ++ return r; ++ } ++ ++ const_iterator &operator+=(size_type n) ++ { ++ i += n; ++ return *this; ++ } ++ ++ friend const_iterator operator+(size_type n, const const_iterator a) ++ { ++ const_iterator ret = a; ++ return ret += n; ++ } ++ ++ friend const_iterator operator+(const const_iterator a, size_type n) ++ { ++ return n + a; ++ } ++ ++ const_iterator &operator-=(size_type n) ++ { ++ i -= n; ++ return *this; ++ } ++ ++ friend const_iterator operator-(const const_iterator a, size_type n) ++ { ++ const_iterator ret = a; ++ return ret -= n; ++ } ++ ++ friend difference_type operator-(const const_iterator b, const const_iterator a) ++ { ++ return b.i - a.i; ++ } ++ ++ reference operator[](size_type n) const ++ { ++ size_type k = i + n; ++ return { c->keys[k], c->values[k] }; ++ } ++ ++ bool operator<(const const_iterator &other) const ++ { ++ return i < other.i; ++ } ++ ++ bool operator>(const const_iterator &other) const ++ { ++ return i > other.i; ++ } ++ ++ bool operator<=(const const_iterator &other) const ++ { ++ return i <= other.i; ++ } ++ ++ bool operator>=(const const_iterator &other) const ++ { ++ return i >= other.i; ++ } ++ ++ const Key &key() const { return c->keys[i]; } ++ const T &value() const { return c->values[i]; } ++ ++ private: ++ const containers *c = nullptr; ++ size_type i = 0; ++ friend QFlatMap; ++ }; ++ ++private: ++ template ++ struct is_marked_transparent_type : std::false_type { }; ++ ++ template ++ struct is_marked_transparent_type> : std::true_type { }; ++ ++ template ++ using is_marked_transparent = typename std::enable_if< ++ is_marked_transparent_type::value>::type *; ++ ++ template ++ using is_compatible_iterator = typename std::enable_if< ++ std::is_same::value_type>::value>::type *; ++ ++public: ++ QFlatMap() = default; ++ ++#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT ++ explicit QFlatMap(const key_container_type &keys, const mapped_container_type &values) ++ : c{keys, values} ++ { ++ ensureOrderedUnique(); ++ } ++ ++ explicit QFlatMap(key_container_type &&keys, const mapped_container_type &values) ++ : c{std::move(keys), values} ++ { ++ ensureOrderedUnique(); ++ } ++ ++ explicit QFlatMap(const key_container_type &keys, mapped_container_type &&values) ++ : c{keys, std::move(values)} ++ { ++ ensureOrderedUnique(); ++ } ++ ++ explicit QFlatMap(key_container_type &&keys, mapped_container_type &&values) ++ : c{std::move(keys), std::move(values)} ++ { ++ ensureOrderedUnique(); ++ } ++ ++ explicit QFlatMap(std::initializer_list lst) ++ : QFlatMap(lst.begin(), lst.end()) ++ { ++ } ++ ++ template = nullptr> ++ explicit QFlatMap(InputIt first, InputIt last) ++ { ++ initWithRange(first, last); ++ ensureOrderedUnique(); ++ } ++#endif ++ ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, const key_container_type &keys, ++ const mapped_container_type &values) ++ : c{keys, values} ++ { ++ } ++ ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, key_container_type &&keys, ++ const mapped_container_type &values) ++ : c{std::move(keys), values} ++ { ++ } ++ ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, const key_container_type &keys, ++ mapped_container_type &&values) ++ : c{keys, std::move(values)} ++ { ++ } ++ ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, key_container_type &&keys, ++ mapped_container_type &&values) ++ : c{std::move(keys), std::move(values)} ++ { ++ } ++ ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, std::initializer_list lst) ++ : QFlatMap(Qt::OrderedUniqueRange, lst.begin(), lst.end()) ++ { ++ } ++ ++ template = nullptr> ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, InputIt first, InputIt last) ++ { ++ initWithRange(first, last); ++ } ++ ++ explicit QFlatMap(const Compare &compare) ++ : value_compare(compare) ++ { ++ } ++ ++#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT ++ explicit QFlatMap(const key_container_type &keys, const mapped_container_type &values, ++ const Compare &compare) ++ : value_compare(compare), c{keys, values} ++ { ++ ensureOrderedUnique(); ++ } ++ ++ explicit QFlatMap(key_container_type &&keys, const mapped_container_type &values, ++ const Compare &compare) ++ : value_compare(compare), c{std::move(keys), values} ++ { ++ ensureOrderedUnique(); ++ } ++ ++ explicit QFlatMap(const key_container_type &keys, mapped_container_type &&values, ++ const Compare &compare) ++ : value_compare(compare), c{keys, std::move(values)} ++ { ++ ensureOrderedUnique(); ++ } ++ ++ explicit QFlatMap(key_container_type &&keys, mapped_container_type &&values, ++ const Compare &compare) ++ : value_compare(compare), c{std::move(keys), std::move(values)} ++ { ++ ensureOrderedUnique(); ++ } ++ ++ explicit QFlatMap(std::initializer_list lst, const Compare &compare) ++ : QFlatMap(lst.begin(), lst.end(), compare) ++ { ++ } ++ ++ template = nullptr> ++ explicit QFlatMap(InputIt first, InputIt last, const Compare &compare) ++ : value_compare(compare) ++ { ++ initWithRange(first, last); ++ ensureOrderedUnique(); ++ } ++#endif ++ ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, const key_container_type &keys, ++ const mapped_container_type &values, const Compare &compare) ++ : value_compare(compare), c{keys, values} ++ { ++ } ++ ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, key_container_type &&keys, ++ const mapped_container_type &values, const Compare &compare) ++ : value_compare(compare), c{std::move(keys), values} ++ { ++ } ++ ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, const key_container_type &keys, ++ mapped_container_type &&values, const Compare &compare) ++ : value_compare(compare), c{keys, std::move(values)} ++ { ++ } ++ ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, key_container_type &&keys, ++ mapped_container_type &&values, const Compare &compare) ++ : value_compare(compare), c{std::move(keys), std::move(values)} ++ { ++ } ++ ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, std::initializer_list lst, ++ const Compare &compare) ++ : QFlatMap(Qt::OrderedUniqueRange, lst.begin(), lst.end(), compare) ++ { ++ } ++ ++ template = nullptr> ++ explicit QFlatMap(Qt::OrderedUniqueRange_t, InputIt first, InputIt last, const Compare &compare) ++ : value_compare(compare) ++ { ++ initWithRange(first, last); ++ } ++ ++ size_type count() const noexcept { return c.keys.size(); } ++ size_type size() const noexcept { return c.keys.size(); } ++ size_type capacity() const noexcept { return c.keys.capacity(); } ++ bool isEmpty() const noexcept { return c.keys.empty(); } ++ bool empty() const noexcept { return c.keys.empty(); } ++ containers extract() && { return std::move(c); } ++ const key_container_type &keys() const noexcept { return c.keys; } ++ const mapped_container_type &values() const noexcept { return c.values; } ++ ++ void reserve(size_type s) ++ { ++ c.keys.reserve(s); ++ c.values.reserve(s); ++ } ++ ++ void clear() ++ { ++ c.keys.clear(); ++ c.values.clear(); ++ } ++ ++ bool remove(const Key &key) ++ { ++ return do_remove(find(key)); ++ } ++ ++ template = nullptr> ++ bool remove(const X &key) ++ { ++ return do_remove(find(key)); ++ } ++ ++ iterator erase(iterator it) ++ { ++ c.values.erase(toValuesIterator(it)); ++ return fromKeysIterator(c.keys.erase(toKeysIterator(it))); ++ } ++ ++ T take(const Key &key) ++ { ++ return do_take(find(key)); ++ } ++ ++ template = nullptr> ++ T take(const X &key) ++ { ++ return do_take(find(key)); ++ } ++ ++ bool contains(const Key &key) const ++ { ++ return find(key) != end(); ++ } ++ ++ template = nullptr> ++ bool contains(const X &key) const ++ { ++ return find(key) != end(); ++ } ++ ++ T value(const Key &key, const T &defaultValue) const ++ { ++ auto it = find(key); ++ return it == end() ? defaultValue : it.value(); ++ } ++ ++ template = nullptr> ++ T value(const X &key, const T &defaultValue) const ++ { ++ auto it = find(key); ++ return it == end() ? defaultValue : it.value(); ++ } ++ ++ T value(const Key &key) const ++ { ++ auto it = find(key); ++ return it == end() ? T() : it.value(); ++ } ++ ++ template = nullptr> ++ T value(const X &key) const ++ { ++ auto it = find(key); ++ return it == end() ? T() : it.value(); ++ } ++ ++ T &operator[](const Key &key) ++ { ++ return try_emplace(key).first.value(); ++ } ++ ++ T &operator[](Key &&key) ++ { ++ return try_emplace(std::move(key)).first.value(); ++ } ++ ++ T operator[](const Key &key) const ++ { ++ return value(key); ++ } ++ ++#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT ++ std::pair insert(const Key &key, const T &value) ++ { ++ return try_emplace(key, value); ++ } ++ ++ std::pair insert(Key &&key, const T &value) ++ { ++ return try_emplace(std::move(key), value); ++ } ++ ++ std::pair insert(const Key &key, T &&value) ++ { ++ return try_emplace(key, std::move(value)); ++ } ++ ++ std::pair insert(Key &&key, T &&value) ++ { ++ return try_emplace(std::move(key), std::move(value)); ++ } ++#endif ++ ++ template ++ std::pair try_emplace(const Key &key, Args&&...args) ++ { ++ auto it = lower_bound(key); ++ if (it == end() || key_compare::operator()(key, it.key())) { ++ c.values.insert(toValuesIterator(it), std::forward(args)...); ++ return { fromKeysIterator(c.keys.insert(toKeysIterator(it), key)), true }; ++ } else { ++ return {it, false}; ++ } ++ } ++ ++ template ++ std::pair try_emplace(Key &&key, Args&&...args) ++ { ++ auto it = lower_bound(key); ++ if (it == end() || key_compare::operator()(key, it.key())) { ++ c.values.insert(toValuesIterator(it), std::forward(args)...); ++ return { fromKeysIterator(c.keys.insert(toKeysIterator(it), std::move(key))), true }; ++ } else { ++ return {it, false}; ++ } ++ } ++ ++ template ++ std::pair insert_or_assign(const Key &key, M &&obj) ++ { ++ auto r = try_emplace(key, std::forward(obj)); ++ if (!r.second) ++ *toValuesIterator(r.first) = std::forward(obj); ++ return r; ++ } ++ ++ template ++ std::pair insert_or_assign(Key &&key, M &&obj) ++ { ++ auto r = try_emplace(std::move(key), std::forward(obj)); ++ if (!r.second) ++ *toValuesIterator(r.first) = std::forward(obj); ++ return r; ++ } ++ ++#ifdef QFLATMAP_ENABLE_STL_COMPATIBLE_INSERT ++ template = nullptr> ++ void insert(InputIt first, InputIt last) ++ { ++ insertRange(first, last); ++ } ++ ++ // ### Merge with the templated version above ++ // once we can use std::disjunction in is_compatible_iterator. ++ void insert(const value_type *first, const value_type *last) ++ { ++ insertRange(first, last); ++ } ++ ++ template = nullptr> ++ void insert(Qt::OrderedUniqueRange_t, InputIt first, InputIt last) ++ { ++ insertOrderedUniqueRange(first, last); ++ } ++ ++ // ### Merge with the templated version above ++ // once we can use std::disjunction in is_compatible_iterator. ++ void insert(Qt::OrderedUniqueRange_t, const value_type *first, const value_type *last) ++ { ++ insertOrderedUniqueRange(first, last); ++ } ++#endif ++ ++ iterator begin() { return { &c, 0 }; } ++ const_iterator begin() const { return { &c, 0 }; } ++ const_iterator cbegin() const { return begin(); } ++ const_iterator constBegin() const { return cbegin(); } ++ iterator end() { return { &c, c.keys.size() }; } ++ const_iterator end() const { return { &c, c.keys.size() }; } ++ const_iterator cend() const { return end(); } ++ const_iterator constEnd() const { return cend(); } ++ std::reverse_iterator rbegin() { return std::reverse_iterator(end()); } ++ std::reverse_iterator rbegin() const ++ { ++ return std::reverse_iterator(end()); ++ } ++ std::reverse_iterator crbegin() const { return rbegin(); } ++ std::reverse_iterator rend() { ++ return std::reverse_iterator(begin()); ++ } ++ std::reverse_iterator rend() const ++ { ++ return std::reverse_iterator(begin()); ++ } ++ std::reverse_iterator crend() const { return rend(); } ++ ++ iterator lower_bound(const Key &key) ++ { ++ auto cit = std::as_const(*this).lower_bound(key); ++ return { &c, cit.i }; ++ } ++ ++ template = nullptr> ++ iterator lower_bound(const X &key) ++ { ++ auto cit = std::as_const(*this).lower_bound(key); ++ return { &c, cit.i }; ++ } ++ ++ const_iterator lower_bound(const Key &key) const ++ { ++ return fromKeysIterator(std::lower_bound(c.keys.begin(), c.keys.end(), key, key_comp())); ++ } ++ ++ template = nullptr> ++ const_iterator lower_bound(const X &key) const ++ { ++ return fromKeysIterator(std::lower_bound(c.keys.begin(), c.keys.end(), key, key_comp())); ++ } ++ ++ iterator find(const Key &key) ++ { ++ return { &c, std::as_const(*this).find(key).i }; ++ } ++ ++ template = nullptr> ++ iterator find(const X &key) ++ { ++ return { &c, std::as_const(*this).find(key).i }; ++ } ++ ++ const_iterator find(const Key &key) const ++ { ++ auto it = lower_bound(key); ++ if (it != end()) { ++ if (!key_compare::operator()(key, it.key())) ++ return it; ++ it = end(); ++ } ++ return it; ++ } ++ ++ template = nullptr> ++ const_iterator find(const X &key) const ++ { ++ auto it = lower_bound(key); ++ if (it != end()) { ++ if (!key_compare::operator()(key, it.key())) ++ return it; ++ it = end(); ++ } ++ return it; ++ } ++ ++ template ++ size_type remove_if(Predicate pred) ++ { ++ const auto indirect_call_to_pred = [pred = std::move(pred)](iterator it) { ++ using Pair = decltype(*it); ++ using K = decltype(it.key()); ++ using V = decltype(it.value()); ++ using P = Predicate; ++ if constexpr (std::is_invocable_v) { ++ return pred(it.key(), it.value()); ++ } else if constexpr (std::is_invocable_v && !std::is_invocable_v) { ++ return pred(*it); ++ } else if constexpr (std::is_invocable_v && !std::is_invocable_v) { ++ return pred(it.key()); ++ } else { ++ static_assert(std::false_type(), ++ "Don't know how to call the predicate.\n" ++ "Options:\n" ++ "- pred(*it)\n" ++ "- pred(it.key(), it.value())\n" ++ "- pred(it.key())"); ++ } ++ }; ++ ++ auto first = begin(); ++ const auto last = end(); ++ ++ // find_if prefix loop ++ while (first != last && !indirect_call_to_pred(first)) ++ ++first; ++ ++ if (first == last) ++ return 0; // nothing to do ++ ++ // we know that we need to remove *first ++ ++ auto kdest = toKeysIterator(first); ++ auto vdest = toValuesIterator(first); ++ ++ ++first; ++ ++ auto k = std::next(kdest); ++ auto v = std::next(vdest); ++ ++ // Main Loop ++ // - first is used only for indirect_call_to_pred ++ // - operations are done on k, v ++ // Loop invariants: ++ // - first, k, v are pointing to the same element ++ // - [begin(), first[, [c.keys.begin(), k[, [c.values.begin(), v[: already processed ++ // - [first, end()[, [k, c.keys.end()[, [v, c.values.end()[: still to be processed ++ // - [c.keys.begin(), kdest[ and [c.values.begin(), vdest[ are keepers ++ // - [kdest, k[, [vdest, v[ are considered removed ++ // - kdest is not c.keys.end() ++ // - vdest is not v.values.end() ++ while (first != last) { ++ if (!indirect_call_to_pred(first)) { ++ // keep *first, aka {*k, *v} ++ *kdest = std::move(*k); ++ *vdest = std::move(*v); ++ ++kdest; ++ ++vdest; ++ } ++ ++k; ++ ++v; ++ ++first; ++ } ++ ++ const size_type r = std::distance(kdest, c.keys.end()); ++ c.keys.erase(kdest, c.keys.end()); ++ c.values.erase(vdest, c.values.end()); ++ return r; ++ } ++ ++ key_compare key_comp() const noexcept ++ { ++ return static_cast(*this); ++ } ++ ++ value_compare value_comp() const noexcept ++ { ++ return static_cast(*this); ++ } ++ ++private: ++ bool do_remove(iterator it) ++ { ++ if (it != end()) { ++ erase(it); ++ return true; ++ } ++ return false; ++ } ++ ++ T do_take(iterator it) ++ { ++ if (it != end()) { ++ T result = std::move(it.value()); ++ erase(it); ++ return result; ++ } ++ return {}; ++ } ++ ++ template = nullptr> ++ void initWithRange(InputIt first, InputIt last) ++ { ++ QtPrivate::reserveIfForwardIterator(this, first, last); ++ while (first != last) { ++ c.keys.push_back(first->first); ++ c.values.push_back(first->second); ++ ++first; ++ } ++ } ++ ++ iterator fromKeysIterator(typename key_container_type::iterator kit) ++ { ++ return { &c, static_cast(std::distance(c.keys.begin(), kit)) }; ++ } ++ ++ const_iterator fromKeysIterator(typename key_container_type::const_iterator kit) const ++ { ++ return { &c, static_cast(std::distance(c.keys.begin(), kit)) }; ++ } ++ ++ typename key_container_type::iterator toKeysIterator(iterator it) ++ { ++ return c.keys.begin() + it.i; ++ } ++ ++ typename mapped_container_type::iterator toValuesIterator(iterator it) ++ { ++ return c.values.begin() + it.i; ++ } ++ ++ template ++ void insertRange(InputIt first, InputIt last) ++ { ++ size_type i = c.keys.size(); ++ c.keys.resize(i + std::distance(first, last)); ++ c.values.resize(c.keys.size()); ++ for (; first != last; ++first, ++i) { ++ c.keys[i] = first->first; ++ c.values[i] = first->second; ++ } ++ ensureOrderedUnique(); ++ } ++ ++ class IndexedKeyComparator ++ { ++ public: ++ IndexedKeyComparator(const QFlatMap *am) ++ : m(am) ++ { ++ } ++ ++ bool operator()(size_type i, size_type k) const ++ { ++ return m->key_comp()(m->c.keys[i], m->c.keys[k]); ++ } ++ ++ private: ++ const QFlatMap *m; ++ }; ++ ++ template ++ void insertOrderedUniqueRange(InputIt first, InputIt last) ++ { ++ const size_type s = c.keys.size(); ++ c.keys.resize(s + std::distance(first, last)); ++ c.values.resize(c.keys.size()); ++ for (size_type i = s; first != last; ++first, ++i) { ++ c.keys[i] = first->first; ++ c.values[i] = first->second; ++ } ++ ++ std::vector p(size_t(c.keys.size())); ++ std::iota(p.begin(), p.end(), 0); ++ std::inplace_merge(p.begin(), p.begin() + s, p.end(), IndexedKeyComparator(this)); ++ applyPermutation(p); ++ makeUnique(); ++ } ++ ++ void ensureOrderedUnique() ++ { ++ std::vector p(size_t(c.keys.size())); ++ std::iota(p.begin(), p.end(), 0); ++ std::stable_sort(p.begin(), p.end(), IndexedKeyComparator(this)); ++ applyPermutation(p); ++ makeUnique(); ++ } ++ ++ void applyPermutation(const std::vector &p) ++ { ++ const size_type s = c.keys.size(); ++ std::vector done(s); ++ for (size_type i = 0; i < s; ++i) { ++ if (done[i]) ++ continue; ++ done[i] = true; ++ size_type j = i; ++ size_type k = p[i]; ++ while (i != k) { ++ qSwap(c.keys[j], c.keys[k]); ++ qSwap(c.values[j], c.values[k]); ++ done[k] = true; ++ j = k; ++ k = p[j]; ++ } ++ } ++ } ++ ++ void makeUnique() ++ { ++ // std::unique, but over two ranges ++ auto equivalent = [this](const auto &lhs, const auto &rhs) { ++ return !key_compare::operator()(lhs, rhs) && !key_compare::operator()(rhs, lhs); ++ }; ++ const auto kb = c.keys.begin(); ++ const auto ke = c.keys.end(); ++ auto k = std::adjacent_find(kb, ke, equivalent); ++ if (k == ke) ++ return; ++ ++ // equivalent keys found, we need to do actual work: ++ auto v = std::next(c.values.begin(), std::distance(kb, k)); ++ ++ auto kdest = k; ++ auto vdest = v; ++ ++ ++k; ++ ++v; ++ ++ // Loop Invariants: ++ // ++ // - [keys.begin(), kdest] and [values.begin(), vdest] are unique ++ // - k is not keys.end(), v is not values.end() ++ // - [next(k), keys.end()[ and [next(v), values.end()[ still need to be checked ++ while ((++v, ++k) != ke) { ++ if (!equivalent(*kdest, *k)) { ++ *++kdest = std::move(*k); ++ *++vdest = std::move(*v); ++ } ++ } ++ ++ c.keys.erase(std::next(kdest), ke); ++ c.values.erase(std::next(vdest), c.values.end()); ++ } ++ ++ containers c; ++}; ++ ++template> ++using QVarLengthFlatMap = QFlatMap, QVarLengthArray>; ++ ++QT_END_NAMESPACE ++ ++#endif // QFLATMAP_P_H ++ +diff --git a/src/plugins/platformthemes/gtk3/gtk3.pro b/src/plugins/platformthemes/gtk3/gtk3.pro +index 8d217396d3..c442b24a0a 100644 +--- a/src/plugins/platformthemes/gtk3/gtk3.pro ++++ b/src/plugins/platformthemes/gtk3/gtk3.pro +@@ -15,11 +15,17 @@ QMAKE_CXXFLAGS_WARN_ON += -Wno-error=parentheses + + HEADERS += \ + qgtk3dialoghelpers.h \ ++ qgtk3interface_p.h \ ++ qgtk3json_p.h \ + qgtk3menu.h \ ++ qgtk3storage_p.h \ + qgtk3theme.h + + SOURCES += \ + main.cpp \ + qgtk3dialoghelpers.cpp \ ++ qgtk3interface.cpp \ ++ qgtk3json.cpp \ + qgtk3menu.cpp \ ++ qgtk3storage.cpp \ + qgtk3theme.cpp +diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface.cpp b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp +new file mode 100644 +index 0000000000..d932b250a5 +--- /dev/null ++++ b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp +@@ -0,0 +1,558 @@ ++// Copyright (C) 2022 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 "qgtk3interface_p.h" ++#include "qgtk3storage_p.h" ++#include ++#include ++#include ++#include ++#include ++ ++QT_BEGIN_NAMESPACE ++Q_LOGGING_CATEGORY(lcQGtk3Interface, "qt.qpa.gtk"); ++ ++ ++// Callback for gnome event loop has to be static ++static QGtk3Storage *m_storage = nullptr; ++ ++QGtk3Interface::QGtk3Interface(QGtk3Storage *s) ++{ ++ initColorMap(); ++ ++ if (!s) { ++ qCDebug(lcQGtk3Interface) << "QGtk3Interface instantiated without QGtk3Storage." ++ << "No reaction to runtime theme changes."; ++ return; ++ } ++ ++ // Connect to the GTK settings changed signal ++ auto handleThemeChange = [] { ++ if (m_storage) ++ m_storage->handleThemeChange(); ++ }; ++ ++ GtkSettings *settings = gtk_settings_get_default(); ++ const gboolean success = g_signal_connect(settings, "notify::gtk-theme-name", ++ G_CALLBACK(handleThemeChange), nullptr); ++ if (success == FALSE) { ++ qCDebug(lcQGtk3Interface) << "Connection to theme change signal failed." ++ << "No reaction to runtime theme changes."; ++ } else { ++ m_storage = s; ++ } ++} ++ ++QGtk3Interface::~QGtk3Interface() ++{ ++ // Ignore theme changes when destructor is reached ++ m_storage = nullptr; ++ ++ // QGtkWidgets have to be destroyed manually ++ for (auto v : cache) ++ gtk_widget_destroy(v.second); ++} ++ ++int QGtk3Interface::toGtkState(const QString &state) ++{ ++#define CASE(x) \ ++ if (QLatin1String(QByteArray(state.toLatin1())) == #x) \ ++ return GTK_STATE_FLAG_ ##x ++ ++#define CONVERT\ ++ CASE(NORMAL);\ ++ CASE(ACTIVE);\ ++ CASE(PRELIGHT);\ ++ CASE(SELECTED);\ ++ CASE(INSENSITIVE);\ ++ CASE(INCONSISTENT);\ ++ CASE(FOCUSED);\ ++ CASE(BACKDROP);\ ++ CASE(DIR_LTR);\ ++ CASE(DIR_RTL);\ ++ CASE(LINK);\ ++ CASE(VISITED);\ ++ CASE(CHECKED);\ ++ CASE(DROP_ACTIVE) ++ ++ CONVERT; ++ return -1; ++#undef CASE ++} ++ ++const QLatin1String QGtk3Interface::fromGtkState(GtkStateFlags state) ++{ ++#define CASE(x) case GTK_STATE_FLAG_ ##x: return QLatin1String(#x) ++ switch (state) { ++ CONVERT; ++ } ++ Q_UNREACHABLE(); ++#undef CASE ++#undef CONVERT ++} ++ ++void QGtk3Interface::initColorMap() ++{ ++ // Populate map with default values ++ #define SAVE(src, state, prop, def)\ ++ {ColorKey({QGtkColorSource::src, GTK_STATE_FLAG_ ##state}), ColorValue({#prop, QGtkColorDefault::def})} ++ ++ gtkColorMap = ColorMap { ++ SAVE(Foreground, NORMAL, theme_fg_color, Foreground), ++ SAVE(Foreground, BACKDROP, theme_unfocused_selected_fg_color, Foreground), ++ SAVE(Foreground, INSENSITIVE, insensitive_fg_color, Foreground), ++ SAVE(Foreground, SELECTED, theme_selected_fg_color, Foreground), ++ SAVE(Foreground, ACTIVE, theme_unfocused_fg_color, Foreground), ++ SAVE(Text, NORMAL, theme_text_color, Foreground), ++ SAVE(Text, ACTIVE, theme_unfocused_text_color, Foreground), ++ SAVE(Base, NORMAL, theme_base_color, Background), ++ SAVE(Base, INSENSITIVE, insensitive_base_color, Background), ++ SAVE(Background, NORMAL, theme_bg_color, Background), ++ SAVE(Background, SELECTED, theme_selected_bg_color, Background), ++ SAVE(Background, INSENSITIVE, insensitive_bg_color, Background), ++ SAVE(Background, ACTIVE, theme_unfocused_bg_color, Background), ++ SAVE(Background, BACKDROP, theme_unfocused_selected_bg_color, Background), ++ SAVE(Border, NORMAL, borders, Border), ++ SAVE(Border, ACTIVE, unfocused_borders, Border) ++ }; ++#undef SAVE ++ ++ 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 ++QImage QGtk3Interface::standardPixmap(QPlatformTheme::StandardPixmap standardPixmap) const ++{ ++ switch (standardPixmap) { ++ case QPlatformTheme::DialogDiscardButton: ++ return qt_gtk_get_icon(GTK_STOCK_DELETE); ++ case QPlatformTheme::DialogOkButton: ++ return qt_gtk_get_icon(GTK_STOCK_OK); ++ case QPlatformTheme::DialogCancelButton: ++ return qt_gtk_get_icon(GTK_STOCK_CANCEL); ++ case QPlatformTheme::DialogYesButton: ++ return qt_gtk_get_icon(GTK_STOCK_YES); ++ case QPlatformTheme::DialogNoButton: ++ return qt_gtk_get_icon(GTK_STOCK_NO); ++ case QPlatformTheme::DialogOpenButton: ++ return qt_gtk_get_icon(GTK_STOCK_OPEN); ++ case QPlatformTheme::DialogCloseButton: ++ return qt_gtk_get_icon(GTK_STOCK_CLOSE); ++ case QPlatformTheme::DialogApplyButton: ++ return qt_gtk_get_icon(GTK_STOCK_APPLY); ++ case QPlatformTheme::DialogSaveButton: ++ return qt_gtk_get_icon(GTK_STOCK_SAVE); ++ case QPlatformTheme::MessageBoxWarning: ++ return qt_gtk_get_icon(GTK_STOCK_DIALOG_WARNING); ++ case QPlatformTheme::MessageBoxQuestion: ++ return qt_gtk_get_icon(GTK_STOCK_DIALOG_QUESTION); ++ case QPlatformTheme::MessageBoxInformation: ++ return qt_gtk_get_icon(GTK_STOCK_DIALOG_INFO); ++ case QPlatformTheme::MessageBoxCritical: ++ return qt_gtk_get_icon(GTK_STOCK_DIALOG_ERROR); ++ case QPlatformTheme::CustomBase: ++ case QPlatformTheme::TitleBarMenuButton: ++ case QPlatformTheme::TitleBarMinButton: ++ case QPlatformTheme::TitleBarMaxButton: ++ case QPlatformTheme::TitleBarCloseButton: ++ case QPlatformTheme::TitleBarNormalButton: ++ case QPlatformTheme::TitleBarShadeButton: ++ case QPlatformTheme::TitleBarUnshadeButton: ++ case QPlatformTheme::TitleBarContextHelpButton: ++ case QPlatformTheme::DockWidgetCloseButton: ++ case QPlatformTheme::DesktopIcon: ++ case QPlatformTheme::TrashIcon: ++ case QPlatformTheme::ComputerIcon: ++ case QPlatformTheme::DriveFDIcon: ++ case QPlatformTheme::DriveHDIcon: ++ case QPlatformTheme::DriveCDIcon: ++ case QPlatformTheme::DriveDVDIcon: ++ case QPlatformTheme::DriveNetIcon: ++ case QPlatformTheme::DirOpenIcon: ++ case QPlatformTheme::DirClosedIcon: ++ case QPlatformTheme::DirLinkIcon: ++ case QPlatformTheme::DirLinkOpenIcon: ++ case QPlatformTheme::FileIcon: ++ case QPlatformTheme::FileLinkIcon: ++ case QPlatformTheme::ToolBarHorizontalExtensionButton: ++ case QPlatformTheme::ToolBarVerticalExtensionButton: ++ case QPlatformTheme::FileDialogStart: ++ case QPlatformTheme::FileDialogEnd: ++ case QPlatformTheme::FileDialogToParent: ++ case QPlatformTheme::FileDialogNewFolder: ++ case QPlatformTheme::FileDialogDetailedView: ++ case QPlatformTheme::FileDialogInfoView: ++ case QPlatformTheme::FileDialogContentsView: ++ case QPlatformTheme::FileDialogListView: ++ case QPlatformTheme::FileDialogBack: ++ case QPlatformTheme::DirIcon: ++ case QPlatformTheme::DialogHelpButton: ++ case QPlatformTheme::DialogResetButton: ++ case QPlatformTheme::ArrowUp: ++ case QPlatformTheme::ArrowDown: ++ case QPlatformTheme::ArrowLeft: ++ case QPlatformTheme::ArrowRight: ++ case QPlatformTheme::ArrowBack: ++ case QPlatformTheme::ArrowForward: ++ case QPlatformTheme::DirHomeIcon: ++ case QPlatformTheme::CommandLink: ++ case QPlatformTheme::VistaShield: ++ case QPlatformTheme::BrowserReload: ++ case QPlatformTheme::BrowserStop: ++ case QPlatformTheme::MediaPlay: ++ case QPlatformTheme::MediaStop: ++ case QPlatformTheme::MediaPause: ++ case QPlatformTheme::MediaSkipForward: ++ case QPlatformTheme::MediaSkipBackward: ++ case QPlatformTheme::MediaSeekForward: ++ case QPlatformTheme::MediaSeekBackward: ++ case QPlatformTheme::MediaVolume: ++ case QPlatformTheme::MediaVolumeMuted: ++ case QPlatformTheme::LineEditClearButton: ++ case QPlatformTheme::DialogYesToAllButton: ++ case QPlatformTheme::DialogNoToAllButton: ++ case QPlatformTheme::DialogSaveAllButton: ++ case QPlatformTheme::DialogAbortButton: ++ case QPlatformTheme::DialogRetryButton: ++ case QPlatformTheme::DialogIgnoreButton: ++ case QPlatformTheme::RestoreDefaultsButton: ++ case QPlatformTheme::NStandardPixmap: ++ return QImage(); ++ } ++ Q_UNREACHABLE(); ++} ++ ++QImage QGtk3Interface::qt_gtk_get_icon(const char* iconName) const ++{ ++ GtkIconSet* iconSet = gtk_icon_factory_lookup_default (iconName); ++ GdkPixbuf* icon = gtk_icon_set_render_icon_pixbuf(iconSet, context(), GTK_ICON_SIZE_DIALOG); ++ return qt_convert_gdk_pixbuf(icon); ++} ++ ++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::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); ++ const uchar *data = static_cast(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 ++} ++ ++GtkWidget *QGtk3Interface::qt_new_gtkWidget(QGtkWidget type) const ++{ ++#define CASE(Type)\ ++ case QGtkWidget::Type: return Type ##_new(); ++#define CASEN(Type)\ ++ case QGtkWidget::Type: return Type ##_new(nullptr); ++ ++ switch (type) { ++ CASE(gtk_menu_bar) ++ CASE(gtk_menu) ++ CASE(gtk_button) ++ case QGtkWidget::gtk_button_box: return gtk_button_box_new(GtkOrientation::GTK_ORIENTATION_HORIZONTAL); ++ CASE(gtk_check_button) ++ CASEN(gtk_radio_button) ++ CASEN(gtk_frame) ++ CASE(gtk_statusbar) ++ CASE(gtk_entry) ++ case QGtkWidget::gtk_popup: return gtk_window_new(GTK_WINDOW_POPUP); ++ CASE(gtk_notebook) ++ CASE(gtk_toolbar) ++ CASE(gtk_tree_view) ++ CASE(gtk_combo_box) ++ CASE(gtk_combo_box_text) ++ CASE(gtk_progress_bar) ++ CASE(gtk_fixed) ++ CASE(gtk_separator_menu_item) ++ CASE(gtk_offscreen_window) ++ case QGtkWidget::gtk_Default: return nullptr; ++ } ++#undef CASE ++#undef CASEN ++ Q_UNREACHABLE(); ++} ++ ++GdkRGBA QGtk3Interface::genericColor(GtkStyleContext *con, GtkStateFlags state, QGtkColorDefault def) const ++{ ++ GdkRGBA color; ++ ++#define CASE(def, call)\ ++ case QGtkColorDefault::def:\ ++ gtk_style_context_get_ ##call(con, state, &color);\ ++ break; ++ ++ switch (def) { ++ CASE(Foreground, color) ++ CASE(Background, background_color) ++ CASE(Border, border_color) ++ } ++ return color; ++#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 ++QColor QGtk3Interface::color(GtkWidget *widget, QGtkColorSource source, GtkStateFlags state) const ++{ ++ GdkRGBA col; ++ GtkStyleContext *con = context(widget); ++ ++#define CASE(src, def)\ ++ case QGtkColorSource::src: {\ ++ const ColorKey key = ColorKey({QGtkColorSource::src, state});\ ++ if (gtkColorMap.contains(key)) {\ ++ const ColorValue val = gtkColorMap.value(key);\ ++ if (!gtk_style_context_lookup_color(con, val.propertyName.toUtf8().constData(), &col)) {\ ++ col = genericColor(con, state, val.genericSource);\ ++ qCDebug(lcQGtk3Interface) << "Property name" << val.propertyName << "not found.\n"\ ++ << "Falling back to " << val.genericSource;\ ++ }\ ++ } else {\ ++ col = genericColor(con, state, QGtkColorDefault::def);\ ++ qCDebug(lcQGtk3Interface) << "No color source found for" << QGtkColorSource::src\ ++ << fromGtkState(state) << "\n Falling back to"\ ++ << QGtkColorDefault::def;\ ++ }\ ++ }\ ++ break; ++ ++ switch (source) { ++ CASE(Foreground, Foreground) ++ CASE(Background, Background) ++ CASE(Text, Foreground) ++ CASE(Base, Background) ++ CASE(Border, Border) ++ } ++ ++ return fromGdkColor(col); ++#undef CASE ++} ++ ++// Deliver a widget pointer ++GtkWidget *QGtk3Interface::widget(QGtkWidget type) const ++{ ++ if (type == QGtkWidget::gtk_Default) ++ return nullptr; ++ ++ // Return from cache ++ if (GtkWidget *w = cache.value(type)) ++ return w; ++ ++ // Create new item and cache it ++ GtkWidget *w = qt_new_gtkWidget(type); ++ cache.insert(type, w); ++ return w; ++} ++ ++// Return widget syle context or default style ++GtkStyleContext *QGtk3Interface::context(GtkWidget *w) const ++{ ++ if (w) ++ return gtk_widget_get_style_context(w); ++ ++ 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. ++QBrush QGtk3Interface::brush(QGtkWidget wtype, QGtkColorSource source, GtkStateFlags state) const ++{ ++ return QBrush(color(widget(wtype), source, state)); ++} ++ ++const QString QGtk3Interface::themeName() const ++{ ++ gchar *theme_name; ++ GtkSettings *settings = gtk_settings_get_default(); ++ if (!settings) ++ return QString(); ++ ++ g_object_get(settings, "gtk-theme-name", &theme_name, nullptr); ++ return QLatin1String(theme_name); ++} ++ ++inline constexpr QGtk3Interface::QGtkWidget QGtk3Interface::toWidgetType(QPlatformTheme::Font type) ++{ ++ switch (type) { ++ case QPlatformTheme::SystemFont: return QGtkWidget::gtk_Default; ++ case QPlatformTheme::MenuFont: return QGtkWidget::gtk_menu; ++ case QPlatformTheme::MenuBarFont: return QGtkWidget::gtk_menu_bar; ++ case QPlatformTheme::MenuItemFont: return QGtkWidget::gtk_menu; ++ case QPlatformTheme::MessageBoxFont: return QGtkWidget::gtk_popup; ++ case QPlatformTheme::LabelFont: return QGtkWidget::gtk_popup; ++ case QPlatformTheme::TipLabelFont: return QGtkWidget::gtk_Default; ++ case QPlatformTheme::StatusBarFont: return QGtkWidget::gtk_statusbar; ++ case QPlatformTheme::TitleBarFont: return QGtkWidget::gtk_Default; ++ case QPlatformTheme::MdiSubWindowTitleFont: return QGtkWidget::gtk_Default; ++ case QPlatformTheme::DockWidgetTitleFont: return QGtkWidget::gtk_Default; ++ case QPlatformTheme::PushButtonFont: return QGtkWidget::gtk_button; ++ case QPlatformTheme::CheckBoxFont: return QGtkWidget::gtk_check_button; ++ case QPlatformTheme::RadioButtonFont: return QGtkWidget::gtk_radio_button; ++ 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::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::GroupBoxTitleFont: return QGtkWidget::gtk_Default; ++ case QPlatformTheme::TabButtonFont: return QGtkWidget::gtk_button; ++ case QPlatformTheme::EditorFont: return QGtkWidget::gtk_entry; ++ case QPlatformTheme::NFonts: return QGtkWidget::gtk_Default; ++ } ++ Q_UNREACHABLE(); ++} ++ ++inline constexpr QFont::Style QGtk3Interface::toFontStyle(PangoStyle style) ++{ ++ switch (style) { ++ case PANGO_STYLE_ITALIC: return QFont::StyleItalic; ++ case PANGO_STYLE_OBLIQUE: return QFont::StyleOblique; ++ case PANGO_STYLE_NORMAL: return QFont::StyleNormal; ++ } ++ // This is reached when GTK has introduced a new font style ++ Q_UNREACHABLE(); ++} ++ ++inline constexpr int QGtk3Interface::toFontWeight(PangoWeight weight) ++{ ++ // GTK PangoWeight can be directly converted to QFont::Weight ++ // unless one of the enums changes. ++ static_assert(PANGO_WEIGHT_THIN == 100 && PANGO_WEIGHT_ULTRAHEAVY == 1000, ++ "Pango font weight enum changed. Fix conversion."); ++ ++ return qBound(1, static_cast(weight), 1000); ++} ++ ++inline constexpr QFont::Weight QGtk3Interface::toQFontWeight(int weight) ++{ ++ if (weight <= 100) ++ return QFont::Thin; ++ else if (weight <= 200) ++ return QFont::ExtraLight; ++ else if (weight <= 300) ++ return QFont::Light; ++ else if (weight <= 400) ++ return QFont::Normal; ++ else if (weight <= 500) ++ return QFont::Medium; ++ else if (weight <= 600) ++ return QFont::DemiBold; ++ else if (weight <= 700) ++ return QFont::Bold; ++ else if (weight <= 800) ++ return QFont::ExtraBold; ++ else ++ return QFont::Black; ++} ++ ++QFont QGtk3Interface::font(QPlatformTheme::Font type) const ++{ ++ GtkStyleContext *con = context(widget(toWidgetType(type))); ++ if (!con) ++ return QFont(); ++ ++ const PangoFontDescription *gtkFont = gtk_style_context_get_font(con, GTK_STATE_FLAG_NORMAL); ++ if (!gtkFont) ++ return QFont(); ++ ++ const QString family = QString::fromLatin1(pango_font_description_get_family(gtkFont)); ++ if (family.isEmpty()) ++ return QFont(); ++ ++ const int weight = toFontWeight(pango_font_description_get_weight(gtkFont)); ++ ++ // Creating a QFont() creates a futex lockup on a theme change ++ // QFont doesn't have a constructor with float point size ++ // => create a dummy point size and set it later. ++ QFont font(family, 1, toQFontWeight(weight)); ++ font.setPointSizeF(static_cast(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()) { ++ qCDebug(lcQGtk3Interface) << "No fixed pitch font found in font family" ++ << font.family() << ". falling back to a default" ++ << "fixed pitch font"; ++ font.setFamily("monospace"); ++ } ++ } ++ return font; ++} ++ ++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, ++ G_FILE_QUERY_INFO_NONE, nullptr, nullptr); ++ if (!info) { ++ g_object_unref(file); ++ return QIcon(); ++ } ++ ++ GIcon *icon = g_file_info_get_icon(info); ++ if (!icon) { ++ g_object_unref(file); ++ g_object_unref(info); ++ return QIcon(); ++ } ++ ++ GtkIconTheme *theme = gtk_icon_theme_get_default(); ++ GtkIconInfo *iconInfo = gtk_icon_theme_lookup_by_gicon(theme, icon, GTK_ICON_SIZE_BUTTON, ++ GTK_ICON_LOOKUP_FORCE_SIZE); ++ if (!iconInfo) { ++ g_object_unref(file); ++ g_object_unref(info); ++ g_object_unref(icon); ++ return QIcon(); ++ } ++ ++ GdkPixbuf *buf = gtk_icon_info_load_icon(iconInfo, nullptr); ++ 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)); ++} ++ ++QT_END_NAMESPACE +diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface_p.h b/src/plugins/platformthemes/gtk3/qgtk3interface_p.h +new file mode 100644 +index 0000000000..8997a64e76 +--- /dev/null ++++ b/src/plugins/platformthemes/gtk3/qgtk3interface_p.h +@@ -0,0 +1,170 @@ ++// Copyright (C) 2022 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 QGTK3INTERFACE_H ++#define QGTK3INTERFACE_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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#undef signals // Collides with GTK symbols ++#include ++#include ++#include ++ ++QT_BEGIN_NAMESPACE ++ ++Q_DECLARE_LOGGING_CATEGORY(lcQGtk3Interface); ++ ++class QGtk3Storage; ++class QGtk3Interface ++{ ++ Q_GADGET ++public: ++ QGtk3Interface(QGtk3Storage *); ++ ~QGtk3Interface(); ++ ++ // Enum representing GTK widget types ++ enum class QGtkWidget { ++ gtk_menu_bar, ++ gtk_menu, ++ gtk_button, ++ gtk_button_box, ++ gtk_check_button, ++ gtk_radio_button, ++ gtk_frame, ++ gtk_statusbar, ++ gtk_entry, ++ gtk_popup, ++ gtk_notebook, ++ gtk_toolbar, ++ gtk_tree_view, ++ gtk_combo_box, ++ gtk_combo_box_text, ++ gtk_progress_bar, ++ gtk_fixed, ++ gtk_separator_menu_item, ++ gtk_Default, ++ gtk_offscreen_window ++ }; ++ Q_ENUM(QGtkWidget) ++ ++ // Enum representing color sources of a GTK theme ++ enum class QGtkColorSource { ++ Foreground, ++ Background, ++ Text, ++ Base, ++ Border ++ }; ++ Q_ENUM(QGtkColorSource) ++ ++ // Enum for default color getter ++ enum class QGtkColorDefault { ++ Foreground, ++ Background, ++ Border ++ }; ++ Q_ENUM(QGtkColorDefault) ++ ++ // Create a brush from GTK widget type, color source and color state ++ QBrush brush(QGtkWidget wtype, QGtkColorSource source, GtkStateFlags state) const; ++ ++ // Font & icon getters ++ QImage standardPixmap(QPlatformTheme::StandardPixmap standardPixmap) const; ++ QFont font(QPlatformTheme::Font type) const; ++ QIcon fileIcon(const QFileInfo &fileInfo) const; ++ ++ // Return current GTK theme name ++ const QString themeName() const; ++ ++ // Convert GTK state to/from string ++ static int toGtkState(const QString &state); ++ static const QLatin1String fromGtkState(GtkStateFlags state); ++ ++private: ++ ++ // Map colors to GTK property names and default to generic color getters ++ struct ColorKey { ++ QGtkColorSource colorSource = QGtkColorSource::Background; ++ GtkStateFlags state = GTK_STATE_FLAG_NORMAL; ++ ++ // struct becomes key of a map, so operator< is needed ++ bool operator<(const ColorKey& other) const { ++ return std::tie(colorSource, state) < ++ std::tie(other.colorSource, other.state); ++ } ++ ++ QDebug operator<<(QDebug dbg) ++ { ++ return dbg << "QGtk3Interface::ColorKey(colorSource=" << colorSource << ", GTK state=" << fromGtkState(state) << ")"; ++ } ++ }; ++ ++ struct ColorValue { ++ QString propertyName = QString(); ++ QGtkColorDefault genericSource = QGtkColorDefault::Background; ++ ++ QDebug operator<<(QDebug dbg) ++ { ++ return dbg << "QGtk3Interface::ColorValue(propertyName=" << propertyName << ", genericSource=" << genericSource << ")"; ++ } ++ }; ++ ++ typedef QFlatMap ColorMap; ++ ColorMap gtkColorMap; ++ void initColorMap(); ++ ++ GdkRGBA genericColor(GtkStyleContext *con, GtkStateFlags state, QGtkColorDefault def) const; ++ ++ // Cache for GTK widgets ++ mutable QFlatMap cache; ++ ++ // Converters for GTK icon and GDK pixbuf ++ QImage qt_gtk_get_icon(const char *iconName) const; ++ QImage qt_convert_gdk_pixbuf(GdkPixbuf *buf) const; ++ ++ // Create new GTK widget object ++ GtkWidget *qt_new_gtkWidget(QGtkWidget type) const; ++ ++ // Deliver GTK Widget from cache or create new ++ GtkWidget *widget(QGtkWidget type) const; ++ ++ // Get a GTK widget's style context. Default settings style context if nullptr ++ GtkStyleContext *context(GtkWidget *widget = nullptr) const; ++ ++ // Convert GTK color into QColor ++ static inline QColor fromGdkColor (const GdkRGBA &c) ++ { return QColor::fromRgbF(c.red, c.green, c.blue, c.alpha); } ++ ++ // get a QColor of a GTK widget (default settings style if nullptr) ++ QColor color (GtkWidget *widget, QGtkColorSource source, GtkStateFlags state) const; ++ ++ // Mappings for GTK fonts ++ inline static constexpr QGtkWidget toWidgetType(QPlatformTheme::Font); ++ inline static constexpr QFont::Style toFontStyle(PangoStyle style); ++ inline static constexpr int toFontWeight(PangoWeight weight); ++ inline static constexpr QFont::Weight toQFontWeight(int weight); ++ ++}; ++QT_END_NAMESPACE ++#endif // QGTK3INTERFACE_H +diff --git a/src/plugins/platformthemes/gtk3/qgtk3json.cpp b/src/plugins/platformthemes/gtk3/qgtk3json.cpp +new file mode 100644 +index 0000000000..f4d5b50ec5 +--- /dev/null ++++ b/src/plugins/platformthemes/gtk3/qgtk3json.cpp +@@ -0,0 +1,475 @@ ++// Copyright (C) 2022 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 "qgtk3json_p.h" ++#include ++#include ++ ++QT_BEGIN_NAMESPACE ++ ++QLatin1String QGtk3Json::fromPalette(QPlatformTheme::Palette palette) ++{ ++ switch (palette) { ++ case QPlatformTheme::SystemPalette: ++ return QLatin1String("SystemPalette"); ++ case QPlatformTheme::ToolTipPalette: ++ return QLatin1String("ToolTipPalette"); ++ case QPlatformTheme::ToolButtonPalette: ++ return QLatin1String("ToolButtonPalette"); ++ case QPlatformTheme::ButtonPalette: ++ return QLatin1String("ButtonPalette"); ++ case QPlatformTheme::CheckBoxPalette: ++ return QLatin1String("CheckBoxPalette"); ++ case QPlatformTheme::RadioButtonPalette: ++ return QLatin1String("RadioButtonPalette"); ++ case QPlatformTheme::HeaderPalette: ++ return QLatin1String("HeaderPalette"); ++ case QPlatformTheme::ComboBoxPalette: ++ return QLatin1String("ComboBoxPalette"); ++ case QPlatformTheme::ItemViewPalette: ++ return QLatin1String("ItemViewPalette"); ++ case QPlatformTheme::MessageBoxLabelPalette: ++ return QLatin1String("MessageBoxLabelPalette"); ++ case QPlatformTheme::TabBarPalette: ++ return QLatin1String("TabBarPalette"); ++ case QPlatformTheme::LabelPalette: ++ return QLatin1String("LabelPalette"); ++ case QPlatformTheme::GroupBoxPalette: ++ return QLatin1String("GroupBoxPalette"); ++ case QPlatformTheme::MenuPalette: ++ return QLatin1String("MenuPalette"); ++ case QPlatformTheme::MenuBarPalette: ++ return QLatin1String("MenuBarPalette"); ++ case QPlatformTheme::TextEditPalette: ++ return QLatin1String("TextEditPalette"); ++ case QPlatformTheme::TextLineEditPalette: ++ return QLatin1String("TextLineEditPalette"); ++ default: ++ return QLatin1String(); ++ } ++ return QLatin1String(); ++} ++ ++QLatin1String QGtk3Json::fromGtkState(GtkStateFlags state) ++{ ++ return QGtk3Interface::fromGtkState(state); ++} ++ ++QLatin1String fromColor(const QColor &color) ++{ ++ return QLatin1String(QByteArray(color.name(QColor::HexRgb).toLatin1())); ++} ++ ++QLatin1String QGtk3Json::fromColorRole(QPalette::ColorRole role) ++{ ++ return QLatin1String(QMetaEnum::fromType().valueToKey(static_cast(role))); ++} ++ ++QLatin1String QGtk3Json::fromColorGroup(QPalette::ColorGroup group) ++{ ++ return QLatin1String(QMetaEnum::fromType().valueToKey(static_cast(group))); ++} ++ ++QLatin1String QGtk3Json::fromGdkSource(QGtk3Interface::QGtkColorSource source) ++{ ++ return QLatin1String(QMetaEnum::fromType().valueToKey(static_cast(source))); ++} ++ ++QLatin1String QGtk3Json::fromWidgetType(QGtk3Interface::QGtkWidget widgetType) ++{ ++ return QLatin1String(QMetaEnum::fromType().valueToKey(static_cast(widgetType))); ++} ++ ++QLatin1String QGtk3Json::fromAppearance(Qt::Appearance app) ++{ ++ return QLatin1String(QMetaEnum::fromType().valueToKey(static_cast(app))); ++} ++ ++#define CONVERT(type, key, def)\ ++ bool ok;\ ++ const int intVal = QMetaEnum::fromType().keyToValue(key.toLatin1().constData(), &ok);\ ++ return ok ? static_cast(intVal) : type::def ++ ++Qt::Appearance QGtk3Json::toAppearance(const QString &appearance) ++{ ++ CONVERT(Qt::Appearance, appearance, Unknown); ++} ++ ++QPlatformTheme::Palette QGtk3Json::toPalette(const QString &palette) ++{ ++ if (palette == QLatin1String("SystemPalette")) ++ return QPlatformTheme::SystemPalette; ++ else if (palette == QLatin1String("ToolTipPalette")) ++ return QPlatformTheme::ToolTipPalette; ++ else if (palette == QLatin1String("ToolButtonPalette")) ++ return QPlatformTheme::ToolButtonPalette; ++ else if (palette == QLatin1String("ButtonPalette")) ++ return QPlatformTheme::ButtonPalette; ++ else if (palette == QLatin1String("CheckBoxPalette")) ++ return QPlatformTheme::CheckBoxPalette; ++ else if (palette == QLatin1String("RadioButtonPalette")) ++ return QPlatformTheme::RadioButtonPalette; ++ else if (palette == QLatin1String("HeaderPalette")) ++ return QPlatformTheme::HeaderPalette; ++ else if (palette == QLatin1String("ComboBoxPalette")) ++ return QPlatformTheme::ComboBoxPalette; ++ else if (palette == QLatin1String("ItemViewPalette")) ++ return QPlatformTheme::ItemViewPalette; ++ else if (palette == QLatin1String("MessageBoxLabelPelette")) ++ return QPlatformTheme::MessageBoxLabelPelette; ++ else if (palette == QLatin1String("TabBarPalette")) ++ return QPlatformTheme::TabBarPalette; ++ else if (palette == QLatin1String("LabelPalette")) ++ return QPlatformTheme::LabelPalette; ++ else if (palette == QLatin1String("GroupBoxPalette")) ++ return QPlatformTheme::GroupBoxPalette; ++ else if (palette == QLatin1String("MenuPalette")) ++ return QPlatformTheme::MenuPalette; ++ else if (palette == QLatin1String("MenuBarPalette")) ++ return QPlatformTheme::MenuBarPalette; ++ else if (palette == QLatin1String("TextEditPalette")) ++ return QPlatformTheme::TextEditPalette; ++ else if (palette == QLatin1String("TextLineEditPalette")) ++ return QPlatformTheme::TextLineEditPalette; ++ ++ return QPlatformTheme::NPalettes; ++} ++ ++GtkStateFlags QGtk3Json::toGtkState(const QString &type) ++{ ++ int i = QGtk3Interface::toGtkState(type); ++ if (i < 0) ++ return GTK_STATE_FLAG_NORMAL; ++ return static_cast(i); ++} ++ ++QColor toColor(const QStringView &color) ++{ ++ return QColor(color); ++} ++ ++QPalette::ColorRole QGtk3Json::toColorRole(const QString &role) ++{ ++ CONVERT(QPalette::ColorRole, role, NColorRoles); ++} ++ ++QPalette::ColorGroup QGtk3Json::toColorGroup(const QString &group) ++{ ++ CONVERT(QPalette::ColorGroup, group, NColorGroups); ++} ++ ++QGtk3Interface::QGtkColorSource QGtk3Json::toGdkSource(const QString &source) ++{ ++ CONVERT(QGtk3Interface::QGtkColorSource, source, Background); ++} ++ ++QLatin1String QGtk3Json::fromSourceType(QGtk3Storage::SourceType sourceType) ++{ ++ return QLatin1String(QMetaEnum::fromType().valueToKey(static_cast(sourceType))); ++} ++ ++QGtk3Storage::SourceType QGtk3Json::toSourceType(const QString &sourceType) ++{ ++ CONVERT(QGtk3Storage::SourceType, sourceType, Invalid); ++} ++ ++QGtk3Interface::QGtkWidget QGtk3Json::toWidgetType(const QString &widgetType) ++{ ++ CONVERT(QGtk3Interface::QGtkWidget, widgetType, gtk_offscreen_window); ++} ++ ++#undef CONVERT ++ ++bool QGtk3Json::save(const QGtk3Storage::PaletteMap &map, const QString &fileName, ++ QJsonDocument::JsonFormat format) ++{ ++ QJsonDocument doc = save(map); ++ if (doc.isEmpty()) { ++ qWarning() << "Nothing to save to" << fileName; ++ return false; ++ } ++ ++ QFile file(fileName); ++ if (!file.open(QIODevice::WriteOnly)) { ++ qWarning() << "Unable to open file" << fileName << "for writing."; ++ return false; ++ } ++ ++ if (!file.write(doc.toJson(format))) { ++ qWarning() << "Unable to serialize Json document."; ++ return false; ++ } ++ ++ file.close(); ++ qInfo() << "Saved mapping data to" << fileName; ++ return true; ++} ++ ++const QJsonDocument QGtk3Json::save(const QGtk3Storage::PaletteMap &map) ++{ ++ QJsonObject paletteObject; ++ for (auto paletteIterator = map.constBegin(); paletteIterator != map.constEnd(); ++ ++paletteIterator) { ++ const QGtk3Storage::BrushMap &bm = paletteIterator.value(); ++ QFlatMap brushMaps; ++ for (auto brushIterator = bm.constBegin(); brushIterator != bm.constEnd(); ++ ++brushIterator) { ++ const QPalette::ColorRole role = brushIterator.key().colorRole; ++ if (brushMaps.contains(role)) { ++ brushMaps.value(role).insert(brushIterator.key(), brushIterator.value()); ++ } else { ++ QGtk3Storage::BrushMap newMap; ++ newMap.insert(brushIterator.key(), brushIterator.value()); ++ brushMaps.insert(role, newMap); ++ } ++ } ++ ++ QJsonObject brushArrayObject; ++ for (auto brushMapIterator = brushMaps.constBegin(); ++ brushMapIterator != brushMaps.constEnd(); ++brushMapIterator) { ++ ++ QJsonArray brushArray; ++ int brushIndex = 0; ++ const QGtk3Storage::BrushMap &bm = brushMapIterator.value(); ++ for (auto brushIterator = bm.constBegin(); brushIterator != bm.constEnd(); ++ ++brushIterator) { ++ QJsonObject brushObject; ++ 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(ceSourceType, fromSourceType(s.sourceType)); ++ ++ QJsonObject sourceObject; ++ switch (s.sourceType) { ++ case QGtk3Storage::SourceType::Gtk: { ++ sourceObject.insert(ceGtkWidget, fromWidgetType(s.gtk3.gtkWidgetType)); ++ sourceObject.insert(ceGdkSource, fromGdkSource(s.gtk3.source)); ++ sourceObject.insert(ceGtkState, fromGtkState(s.gtk3.state)); ++ sourceObject.insert(ceWidth, s.gtk3.width); ++ sourceObject.insert(ceHeight, s.gtk3.height); ++ } ++ break; ++ ++ case QGtk3Storage::SourceType::Fixed: { ++ QJsonObject fixedObject; ++ fixedObject.insert(ceColor, s.fix.fixedBrush.color().name()); ++ fixedObject.insert(ceWidth, s.fix.fixedBrush.texture().width()); ++ fixedObject.insert(ceHeight, s.fix.fixedBrush.texture().height()); ++ sourceObject.insert(ceBrush, fixedObject); ++ } ++ break; ++ ++ 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(ceRed, s.rec.deltaRed); ++ sourceObject.insert(ceGreen, s.rec.deltaGreen); ++ sourceObject.insert(ceBlue, s.rec.deltaBlue); ++ sourceObject.insert(ceWidth, s.rec.width); ++ sourceObject.insert(ceHeight, s.rec.height); ++ sourceObject.insert(ceLighter, s.rec.lighter); ++ } ++ break; ++ ++ case QGtk3Storage::SourceType::Invalid: ++ break; ++ } ++ ++ brushObject.insert(ceData, sourceObject); ++ brushArray.insert(brushIndex, brushObject); ++ ++brushIndex; ++ } ++ brushArrayObject.insert(fromColorRole(brushMapIterator.key()), brushArray); ++ } ++ paletteObject.insert(fromPalette(paletteIterator.key()), brushArrayObject); ++ } ++ ++ QJsonObject top; ++ top.insert(cePalettes, paletteObject); ++ return paletteObject.keys().isEmpty() ? QJsonDocument() : QJsonDocument(top); ++} ++ ++bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QString &fileName) ++{ ++ QFile file(fileName); ++ if (!file.open(QIODevice::ReadOnly)) { ++ qCWarning(lcQGtk3Interface) << "Unable to open file:" << fileName; ++ return false; ++ } ++ ++ QJsonParseError err; ++ QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &err); ++ if (err.error != QJsonParseError::NoError) { ++ qWarning(lcQGtk3Interface) << "Unable to parse Json document from" << fileName ++ << err.error << err.errorString(); ++ return false; ++ } ++ ++ if (Q_LIKELY(load(map, doc))) { ++ qInfo() << "GTK mapping successfully imported from" << fileName; ++ return true; ++ } ++ ++ qWarning() << "File" << fileName << "could not be loaded."; ++ return false; ++} ++ ++bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) ++{ ++#define GETSTR(obj, key)\ ++ if (!obj.contains(key)) {\ ++ qCDebug(lcQGtk3Interface) << key << "missing for palette" << paletteName\ ++ << ", Brush" << colorRoleName;\ ++ return false;\ ++ }\ ++ value = obj[key].toString() ++ ++#define GETINT(obj, key, var) GETSTR(obj, key);\ ++ if (!obj[key].isDouble()) {\ ++ qCDebug(lcQGtk3Interface) << key << "type mismatch" << value\ ++ << "is not an integer!"\ ++ << "(Palette" << paletteName << "), Brush" << colorRoleName;\ ++ return false;\ ++ }\ ++ const int var = obj[key].toInt() ++ ++ map.clear(); ++ const QJsonObject top(doc.object()); ++ if (doc.isEmpty() || top.isEmpty() || !top.contains(cePalettes)) { ++ qCDebug(lcQGtk3Interface) << "Document does not contain Palettes."; ++ return false; ++ } ++ ++ const QStringList &paletteList = top[cePalettes].toObject().keys(); ++ for (const QString &paletteName : paletteList) { ++ bool ok; ++ const QPlatformTheme::Palette paletteType = toPalette(paletteName); ++ if (paletteType == QPlatformTheme::NPalettes) { ++ qCDebug(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"; ++ return false; ++ } ++ ++ QGtk3Storage::BrushMap brushes; ++ const QStringList &colorRoles = paletteObject.keys(); ++ for (const QString &colorRoleName : colorRoles) { ++ const int intVal = QMetaEnum::fromType().keyToValue(colorRoleName ++ .toLatin1().constData(), &ok); ++ if (!ok) { ++ qCDebug(lcQGtk3Interface) << "Palette" << paletteName ++ << "contains invalid color role" << colorRoleName; ++ return false; ++ } ++ const QPalette::ColorRole colorRole = static_cast(intVal); ++ const QJsonArray &brushArray = paletteObject[colorRoleName].toArray(); ++ 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" ++ << paletteName << ", Brush" << colorRoleName; ++ return false; ++ } ++ ++ QString value; ++ GETSTR(brushObject, ceSourceType); ++ 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); ++ QGtk3Storage::Source s; ++ ++ if (!brushObject.contains(ceData) || !brushObject[ceData].isObject()) { ++ qCDebug(lcQGtk3Interface) << "Source specification missing for palette" << paletteName ++ << "Brush" << colorRoleName; ++ return false; ++ } ++ const QJsonObject &sourceObject = brushObject[ceData].toObject(); ++ ++ switch (sourceType) { ++ case QGtk3Storage::SourceType::Gtk: { ++ GETSTR(sourceObject, ceGdkSource); ++ const QGtk3Interface::QGtkColorSource gtkSource = toGdkSource(value); ++ GETSTR(sourceObject, ceGtkState); ++ const GtkStateFlags gtkState = toGtkState(value); ++ GETSTR(sourceObject, ceGtkWidget); ++ const QGtk3Interface::QGtkWidget widgetType = toWidgetType(value); ++ GETINT(sourceObject, ceHeight, height); ++ GETINT(sourceObject, ceWidth, width); ++ s = QGtk3Storage::Source(widgetType, gtkSource, gtkState, width, height); ++ } ++ break; ++ ++ case QGtk3Storage::SourceType::Fixed: { ++ if (!sourceObject.contains(ceBrush)) { ++ qCDebug(lcQGtk3Interface) << "Fixed brush specification missing for palette" << paletteName ++ << "Brush" << colorRoleName; ++ return false; ++ } ++ const QJsonObject &fixedSource = sourceObject[ceBrush].toObject(); ++ GETINT(fixedSource, ceWidth, width); ++ GETINT(fixedSource, ceHeight, height); ++ GETSTR(fixedSource, ceColor); ++ const QColor color(value); ++ if (!color.isValid()) { ++ qCDebug(lcQGtk3Interface) << "Color" << value << "can't be parsed for:" << paletteName ++ << "Brush" << colorRoleName; ++ return false; ++ } ++ const QBrush fixedBrush = (width < 0 && height < 0) ++ ? QBrush(color, QPixmap(width, height)) ++ : QBrush(color); ++ s = QGtk3Storage::Source(fixedBrush); ++ } ++ break; ++ ++ case QGtk3Storage::SourceType::Modified: { ++ GETSTR(sourceObject, ceColorGroup); ++ const QPalette::ColorGroup colorGroup = toColorGroup(value); ++ GETSTR(sourceObject, ceColorRole); ++ const QPalette::ColorRole colorRole = toColorRole(value); ++ GETSTR(sourceObject, ceAppearance); ++ const Qt::Appearance appearance = toAppearance(value); ++ GETINT(sourceObject, ceLighter, lighter); ++ GETINT(sourceObject, ceRed, red); ++ GETINT(sourceObject, ceBlue, blue); ++ GETINT(sourceObject, ceGreen, green); ++ s = QGtk3Storage::Source(colorGroup, colorRole, appearance, ++ lighter, red, green, blue); ++ } ++ break; ++ ++ case QGtk3Storage::SourceType::Invalid: ++ qCDebug(lcQGtk3Interface) << "Invalid source type for palette" << paletteName ++ << "Brush." << colorRoleName; ++ return false; ++ } ++ brushes.insert(tb, s); ++ } ++ } ++ map.insert(paletteType, brushes); ++ } ++ return true; ++} ++ ++QT_END_NAMESPACE ++ +diff --git a/src/plugins/platformthemes/gtk3/qgtk3json_p.h b/src/plugins/platformthemes/gtk3/qgtk3json_p.h +new file mode 100644 +index 0000000000..b3680eb7dc +--- /dev/null ++++ b/src/plugins/platformthemes/gtk3/qgtk3json_p.h +@@ -0,0 +1,102 @@ ++// Copyright (C) 2022 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 QGTK3JSON_P_H ++#define QGTK3JSON_P_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include "qgtk3interface_p.h" ++#include "qgtk3storage_p.h" ++ ++#undef signals // Collides with GTK symbols ++#include ++#include ++#include ++ ++// ++// 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. ++// ++ ++QT_BEGIN_NAMESPACE ++ ++class QGtk3Json ++{ ++ Q_GADGET ++private: ++ QGtk3Json(){}; ++ ++public: ++ // Convert enums to strings ++ static QLatin1String fromPalette(QPlatformTheme::Palette palette); ++ static QLatin1String fromGtkState(GtkStateFlags type); ++ static QLatin1String fromColor(const QColor &Color); ++ static QLatin1String fromColorRole(QPalette::ColorRole role); ++ static QLatin1String fromColorGroup(QPalette::ColorGroup group); ++ static QLatin1String fromGdkSource(QGtk3Interface::QGtkColorSource source); ++ static QLatin1String fromSourceType(QGtk3Storage::SourceType sourceType); ++ static QLatin1String fromWidgetType(QGtk3Interface::QGtkWidget widgetType); ++ static QLatin1String fromAppearance(Qt::Appearance app); ++ ++ // Convert strings to enums ++ static QPlatformTheme::Palette toPalette(const QString &palette); ++ static GtkStateFlags toGtkState(const QString &type); ++ static QColor toColor(const QString &Color); ++ static QPalette::ColorRole toColorRole(const QString &role); ++ static QPalette::ColorGroup toColorGroup(const QString &group); ++ 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); ++ ++ // Json keys ++ static constexpr QStringView cePalettes = u"QtGtk3Palettes"; ++ static constexpr QStringView cePalette = u"PaletteType"; ++ static constexpr QStringView ceGtkState = u"GtkStateType"; ++ static constexpr QStringView ceGtkWidget = u"GtkWidgetType"; ++ static constexpr QStringView ceColor = u"Color"; ++ static constexpr QStringView ceColorRole = u"ColorRole"; ++ static constexpr QStringView ceColorGroup = u"ColorGroup"; ++ static constexpr QStringView ceGdkSource = u"GdkSource"; ++ static constexpr QStringView ceSourceType = u"SourceType"; ++ static constexpr QStringView ceLighter = u"Lighter"; ++ static constexpr QStringView ceRed = u"DeltaRed"; ++ static constexpr QStringView ceGreen = u"DeltaGreen"; ++ static constexpr QStringView ceBlue = u"DeltaBlue"; ++ static constexpr QStringView ceWidth = u"Width"; ++ static constexpr QStringView ceHeight = u"Height"; ++ static constexpr QStringView ceBrush = u"FixedBrush"; ++ static constexpr QStringView ceData = u"SourceData"; ++ static constexpr QStringView ceBrushes = u"Brushes"; ++ static constexpr QStringView ceAppearance = u"Appearance"; ++ ++ // Save to a file ++ static bool save(const QGtk3Storage::PaletteMap &map, const QString &fileName, ++ QJsonDocument::JsonFormat format = QJsonDocument::Indented); ++ ++ // Save to a Json document ++ static const QJsonDocument save(const QGtk3Storage::PaletteMap &map); ++ ++ // Load from a file ++ static bool load(QGtk3Storage::PaletteMap &map, const QString &fileName); ++ ++ // Load from a Json document ++ static bool load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc); ++}; ++ ++QT_END_NAMESPACE ++#endif // QGTK3JSON_P_H +diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +new file mode 100644 +index 0000000000..55c7c8eff8 +--- /dev/null ++++ b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +@@ -0,0 +1,470 @@ ++// Copyright (C) 2022 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 "qgtk3json_p.h" ++#include "qgtk3storage_p.h" ++#include ++ ++QT_BEGIN_NAMESPACE ++ ++QGtk3Storage::QGtk3Storage() ++{ ++ m_interface.reset(new QGtk3Interface(this)); ++ populateMap(); ++} ++ ++// Set a brush from a source and resolve recursions ++QBrush QGtk3Storage::brush(const Source &source, const BrushMap &map) const ++{ ++ switch (source.sourceType) { ++ case SourceType::Gtk: ++ return m_interface ? QBrush(m_interface->brush(source.gtk3.gtkWidgetType, ++ source.gtk3.source, source.gtk3.state)) ++ : QBrush(); ++ ++ 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); ++ ++ if (!recSource.isValid() || (recSource.sourceType == SourceType::Modified)) ++ return QBrush(); ++ ++ // Set brush and alter color ++ QBrush b = brush(recSource, map); ++ if (source.rec.width > 0 && source.rec.height > 0) ++ b.setTexture(QPixmap(source.rec.width, source.rec.height)); ++ QColor c = b.color().lighter(source.rec.lighter); ++ c = QColor((c.red() + source.rec.deltaRed), ++ (c.green() + source.rec.deltaGreen), ++ (c.blue() + source.rec.deltaBlue)); ++ b.setColor(c); ++ return b; ++ } ++ ++ case SourceType::Fixed: ++ return source.fix.fixedBrush; ++ ++ case SourceType::Invalid: ++ return QBrush(); ++ } ++ ++ // needed because of the scope after recursive ++ Q_UNREACHABLE(); ++} ++ ++// Find source for a recursion and take dark/light/unknown into consideration ++QGtk3Storage::Source QGtk3Storage::brush(const TargetBrush &b, const BrushMap &map) const ++{ ++#define FIND(brush) if (map.contains(brush))\ ++ return map.value(brush) ++ ++ // 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)); ++ } ++ ++ // Color group All can always be found ++ if (b.colorGroup != QPalette::All) ++ return brush(TargetBrush(QPalette::All, b.colorRole, b.appearance), map); ++ ++ // Brush not found ++ return Source(); ++#undef FIND ++} ++ ++// Create a simple standard palette ++QPalette QGtk3Storage::standardPalette() ++{ ++ QColor backgroundColor(0xd4, 0xd0, 0xc8); ++ QColor lightColor(backgroundColor.lighter()); ++ QColor darkColor(backgroundColor.darker()); ++ const QBrush darkBrush(darkColor); ++ QColor midColor(Qt::gray); ++ QPalette palette(Qt::black, backgroundColor, lightColor, darkColor, ++ midColor, Qt::black, Qt::white); ++ palette.setBrush(QPalette::Disabled, QPalette::WindowText, darkBrush); ++ palette.setBrush(QPalette::Disabled, QPalette::Text, darkBrush); ++ palette.setBrush(QPalette::Disabled, QPalette::ButtonText, darkBrush); ++ palette.setBrush(QPalette::Disabled, QPalette::Base, QBrush(backgroundColor)); ++ return palette; ++} ++ ++// Deliver a palette styled according to the current GTK Theme ++const QPalette *QGtk3Storage::palette(QPlatformTheme::Palette type) const ++{ ++ if (type >= QPlatformTheme::NPalettes) ++ return nullptr; ++ ++ if (m_paletteCache[type].has_value()) { ++ qCDebug(lcQGtk3Interface) << "Returning palette from cache:" ++ << QGtk3Json::fromPalette(type); ++ ++ return &m_paletteCache[type].value(); ++ } ++ ++ // Read system palette as a baseline first ++ if (!m_paletteCache[QPlatformTheme::SystemPalette].has_value() && type != QPlatformTheme::SystemPalette) ++ palette(); ++ ++ // Fall back to system palette for unknown types ++ if (!m_palettes.contains(type) && type != QPlatformTheme::SystemPalette) { ++ qCDebug(lcQGtk3Interface) << "Returning system palette for unknown type" ++ << QGtk3Json::fromPalette(type); ++ return palette(); ++ } ++ ++ BrushMap brushes = m_palettes.value(type); ++ ++ // Standard palette is base for system palette. System palette is base for all others. ++ QPalette p = QPalette( type == QPlatformTheme::SystemPalette ? standardPalette() ++ : m_paletteCache[QPlatformTheme::SystemPalette].value()); ++ ++ qCDebug(lcQGtk3Interface) << "Creating palette:" << QGtk3Json::fromPalette(type); ++ for (auto i = brushes.begin(); i != brushes.end(); ++i) { ++ Source source = i.value(); ++ ++ // Brush is set if ++ // - theme and source appearance match ++ // - or either of them is unknown ++ const auto appSource = i.key().appearance; ++ const auto appTheme = appearance(); ++ const bool setBrush = (appSource == appTheme) || ++ (appSource == Qt::Appearance::Unknown) || ++ (appTheme == Qt::Appearance::Unknown); ++ ++ if (setBrush) { ++ p.setBrush(i.key().colorGroup, i.key().colorRole, brush(source, brushes)); ++ } ++ } ++ ++ m_paletteCache[type].emplace(p); ++ if (type == QPlatformTheme::SystemPalette) ++ qCDebug(lcQGtk3Interface) << "System Palette defined" << themeName() << appearance() << p; ++ ++ return &m_paletteCache[type].value(); ++} ++ ++const QFont *QGtk3Storage::font(QPlatformTheme::Font type) const ++{ ++ if (m_fontCache[type].has_value()) ++ return &m_fontCache[type].value(); ++ ++ m_fontCache[type].emplace(m_interface->font(type)); ++ return &m_fontCache[type].value(); ++} ++ ++QPixmap QGtk3Storage::standardPixmap(QPlatformTheme::StandardPixmap standardPixmap, ++ const QSizeF &size) const ++{ ++ if (m_pixmapCache.contains(standardPixmap)) ++ return QPixmap::fromImage(m_pixmapCache.object(standardPixmap)->scaled(size.toSize())); ++ ++ if (!m_interface) ++ return QPixmap(); ++ ++ QImage image = m_interface->standardPixmap(standardPixmap); ++ if (image.isNull()) ++ return QPixmap(); ++ ++ m_pixmapCache.insert(standardPixmap, new QImage(image)); ++ return QPixmap::fromImage(image.scaled(size.toSize())); ++} ++ ++QIcon QGtk3Storage::fileIcon(const QFileInfo &fileInfo) const ++{ ++ return m_interface ? m_interface->fileIcon(fileInfo) : QIcon(); ++} ++ ++void QGtk3Storage::clear() ++{ ++ m_appearance = Qt::Appearance::Unknown; ++ m_palettes.clear(); ++ for (auto &cache : m_paletteCache) ++ cache.reset(); ++ ++ for (auto &cache : m_fontCache) ++ cache.reset(); ++} ++ ++void QGtk3Storage::handleThemeChange() ++{ ++ clear(); ++ populateMap(); ++ QWindowSystemInterface::handleThemeChange(); ++} ++ ++void QGtk3Storage::populateMap() ++{ ++ static QString m_themeName; ++ ++ // Distiguish initialization, theme change or call without theme change ++ const QString newThemeName = themeName(); ++ if (m_themeName == newThemeName) ++ return; ++ ++ clear(); ++ ++ // Derive appearance from theme name ++ m_appearance = newThemeName.contains("dark", Qt::CaseInsensitive) ++ ? Qt::Appearance::Dark : Qt::Appearance::Light; ++ ++ if (m_themeName.isEmpty()) { ++ qCDebug(lcQGtk3Interface) << "GTK theme initialized:" << newThemeName << m_appearance; ++ } else { ++ qCDebug(lcQGtk3Interface) << "GTK theme changed to:" << newThemeName << m_appearance; ++ } ++ m_themeName = newThemeName; ++ ++ // create standard mapping or load from Json file? ++ const QString jsonInput = qEnvironmentVariable("QT_GUI_GTK_JSON"); ++ if (!jsonInput.isEmpty()) { ++ if (load(jsonInput)) { ++ return; ++ } else { ++ qWarning() << "Falling back to standard GTK mapping."; ++ } ++ } ++ ++ createMapping(); ++ ++ const QString jsonOutput = qEnvironmentVariable("QT_GUI_GTK_JSON_SAVE"); ++ if (!jsonOutput.isEmpty() && !save(jsonOutput)) ++ qWarning() << "File" << jsonOutput << "could not be saved.\n"; ++} ++ ++const QGtk3Storage::PaletteMap QGtk3Storage::savePalettes() const ++{ ++ const QString hard = qEnvironmentVariable("QT_GUI_GTK_JSON_HARDCODED"); ++ if (!hard.contains("true", Qt::CaseInsensitive)) ++ return m_palettes; ++ ++ // Json output is supposed to be readable without GTK connection ++ // convert palette map into hard coded brushes ++ PaletteMap map = m_palettes; ++ for (auto paletteIterator = map.begin(); paletteIterator != map.end(); ++ ++paletteIterator) { ++ QGtk3Storage::BrushMap &bm = paletteIterator.value(); ++ for (auto brushIterator = bm.begin(); brushIterator != bm.end(); ++ ++brushIterator) { ++ QGtk3Storage::Source &s = brushIterator.value(); ++ switch (s.sourceType) { ++ ++ // Read the brush and convert it into a fixed brush ++ case SourceType::Gtk: { ++ const QBrush fixedBrush = brush(s, bm); ++ s.fix.fixedBrush = fixedBrush; ++ s.sourceType = SourceType::Fixed; ++ } ++ break; ++ case SourceType::Fixed: ++ case SourceType::Modified: ++ case SourceType::Invalid: ++ break; ++ } ++ } ++ } ++ return map; ++} ++ ++bool QGtk3Storage::save(const QString &filename, QJsonDocument::JsonFormat f) const ++{ ++ return QGtk3Json::save(savePalettes(), filename, f); ++} ++ ++QJsonDocument QGtk3Storage::save() const ++{ ++ return QGtk3Json::save(savePalettes()); ++} ++ ++bool QGtk3Storage::load(const QString &filename) ++{ ++ return QGtk3Json::load(m_palettes, filename); ++} ++ ++void QGtk3Storage::createMapping() ++{ ++ // Hard code standard mapping ++ BrushMap map; ++ Source source; ++ ++ // Define a GTK source ++#define GTK(wtype, colorSource, state)\ ++ source = Source(QGtk3Interface::QGtkWidget::gtk_ ##wtype,\ ++ QGtk3Interface::QGtkColorSource::colorSource, GTK_STATE_FLAG_ ##state) ++ ++ // Define a modified source ++#define LIGHTER(group, role, lighter)\ ++ source = Source(QPalette::group, QPalette::role,\ ++ Qt::Appearance::Unknown, lighter) ++#define MODIFY(group, role, red, green, blue)\ ++ source = Source(QPalette::group, QPalette::role,\ ++ Qt::Appearance::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 ++#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); ++#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 ++#define SAVE(palette) m_palettes.insert(QPlatformTheme::palette, map) ++ // Clear brushes to start next palette ++#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. ++ */ ++ ++ ++ // 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; ++ ++#undef GTK ++#undef REC ++#undef FIX ++#undef ADD ++#undef ADD_2 ++#undef ADD_3 ++#undef ADD_X ++#undef SAVE ++#undef LOAD ++} ++ ++QT_END_NAMESPACE +diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage_p.h b/src/plugins/platformthemes/gtk3/qgtk3storage_p.h +new file mode 100644 +index 0000000000..57f6aeea96 +--- /dev/null ++++ b/src/plugins/platformthemes/gtk3/qgtk3storage_p.h +@@ -0,0 +1,234 @@ ++// Copyright (C) 2022 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 QGTK3STORAGE_P_H ++#define QGTK3STORAGE_P_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 "qgtk3interface_p.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++QT_BEGIN_NAMESPACE ++class QGtk3Storage ++{ ++ Q_GADGET ++public: ++ QGtk3Storage(); ++ ++ enum class SourceType { ++ Gtk, ++ Fixed, ++ Modified, ++ Invalid ++ }; ++ Q_ENUM(SourceType) ++ ++ // Standard GTK source: Populate a brush from GTK ++ struct Gtk3Source { ++ QGtk3Interface::QGtkWidget gtkWidgetType; ++ QGtk3Interface::QGtkColorSource source; ++ GtkStateFlags state; ++ int width = -1; ++ int height = -1; ++ QDebug operator<<(QDebug dbg) ++ { ++ return dbg << "QGtkStorage::Gtk3Source(gtkwidgetType=" << gtkWidgetType << ", source=" ++ << source << ", state=" << state << ", width=" << width << ", height=" ++ << height << ")"; ++ } ++ }; ++ ++ // Recursive source: Populate a brush by altering another source ++ struct RecursiveSource { ++ QPalette::ColorGroup colorGroup; ++ QPalette::ColorRole colorRole; ++ Qt::Appearance appearance; ++ int lighter = 100; ++ int deltaRed = 0; ++ int deltaGreen = 0; ++ int deltaBlue = 0; ++ int width = -1; ++ int height = -1; ++ QDebug operator<<(QDebug dbg) ++ { ++ return dbg << "QGtkStorage::RecursiceSource(colorGroup=" << colorGroup << ", colorRole=" ++ << colorRole << ", appearance=" << appearance << ", lighter=" << lighter ++ << ", deltaRed="<< deltaRed << "deltaBlue =" << deltaBlue << "deltaGreen=" ++ << deltaGreen << ", width=" << width << ", height=" << height << ")"; ++ } ++ }; ++ ++ // Fixed source: Populate a brush with fixed values rather than reading GTK ++ struct FixedSource { ++ QBrush fixedBrush; ++ QDebug operator<<(QDebug dbg) ++ { ++ return dbg << "QGtkStorage::FixedSource(" << fixedBrush << ")"; ++ } ++ }; ++ ++ // Data source for brushes ++ struct Source { ++ SourceType sourceType = SourceType::Invalid; ++ Gtk3Source gtk3; ++ RecursiveSource rec; ++ FixedSource fix; ++ ++ // GTK constructor ++ Source(QGtk3Interface::QGtkWidget wtype, QGtk3Interface::QGtkColorSource csource, ++ GtkStateFlags cstate, int bwidth = -1, int bheight = -1) : sourceType(SourceType::Gtk) ++ { ++ gtk3.gtkWidgetType = wtype; ++ gtk3.source = csource; ++ gtk3.state = cstate; ++ gtk3.width = bwidth; ++ gtk3.height = bheight; ++ } ++ ++ // Recursive constructor for darker/lighter colors ++ Source(QPalette::ColorGroup group, QPalette::ColorRole role, ++ Qt::Appearance app, int p_lighter = 100) ++ : sourceType(SourceType::Modified) ++ { ++ rec.colorGroup = group; ++ rec.colorRole = role; ++ rec.appearance = app; ++ 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) ++ : sourceType(SourceType::Modified) ++ { ++ rec.colorGroup = group; ++ rec.colorRole = role; ++ rec.appearance = app; ++ rec.deltaRed = p_red; ++ rec.deltaGreen = p_green; ++ rec.deltaBlue = p_blue; ++ } ++ ++ // Recursive constructor for all: color modification and darker/lighter ++ Source(QPalette::ColorGroup group, QPalette::ColorRole role, ++ Qt::Appearance app, 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.lighter = p_lighter; ++ rec.deltaRed = p_red; ++ rec.deltaGreen = p_green; ++ rec.deltaBlue = p_blue; ++ } ++ ++ // Fixed Source constructor ++ Source(const QBrush &brush) : sourceType(SourceType::Fixed) ++ { ++ fix.fixedBrush = brush; ++ }; ++ ++ // Invalid constructor and getter ++ Source() : sourceType(SourceType::Invalid) {}; ++ bool isValid() const { return sourceType != SourceType::Invalid; } ++ ++ // Debug ++ QDebug operator<<(QDebug dbg) ++ { ++ return dbg << "QGtk3Storage::Source(sourceType=" << sourceType << ")"; ++ } ++ }; ++ ++ // Struct with key attributes to identify a brush: color group, color role and appearance ++ struct TargetBrush { ++ QPalette::ColorGroup colorGroup; ++ QPalette::ColorRole colorRole; ++ Qt::Appearance appearance; ++ ++ // Generic constructor ++ TargetBrush(QPalette::ColorGroup group, QPalette::ColorRole role, ++ Qt::Appearance app = Qt::Appearance::Unknown) : ++ colorGroup(group), colorRole(role), appearance(app) {}; ++ ++ // 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) {}; ++ ++ // 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); ++ } ++ }; ++ ++ // Mapping a palette's brushes to their GTK sources ++ typedef QFlatMap BrushMap; ++ ++ // Storage of palettes and their GTK sources ++ typedef QFlatMap PaletteMap; ++ ++ // 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; }; ++ static QPalette standardPalette(); ++ const QString themeName() const { return m_interface ? m_interface->themeName() : QString(); }; ++ const QFont *font(QPlatformTheme::Font type) const; ++ QIcon fileIcon(const QFileInfo &fileInfo) const; ++ ++ // Initialization ++ void populateMap(); ++ void handleThemeChange(); ++ ++private: ++ // Storage for palettes and their brushes ++ PaletteMap m_palettes; ++ ++ std::unique_ptr m_interface; ++ ++ ++ Qt::Appearance m_appearance = Qt::Appearance::Unknown; ++ ++ // Caches for Pixmaps, fonts and palettes ++ mutable QCache m_pixmapCache; ++ mutable std::array, QPlatformTheme::Palette::NPalettes> m_paletteCache; ++ mutable std::array, QPlatformTheme::NFonts> m_fontCache; ++ ++ // Search brush with a given GTK3 source ++ QBrush brush(const Source &source, const BrushMap &map) const; ++ ++ // Get GTK3 source for a target brush ++ Source brush (const TargetBrush &brush, const BrushMap &map) const; ++ ++ // clear cache, palettes and appearance ++ void clear(); ++ ++ // Data creation, import & export ++ void createMapping (); ++ const PaletteMap savePalettes() const; ++ bool save(const QString &filename, const QJsonDocument::JsonFormat f = QJsonDocument::Indented) const; ++ QJsonDocument save() const; ++ bool load(const QString &filename); ++}; ++ ++QT_END_NAMESPACE ++#endif // QGTK3STORAGE_H +diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +index c01947e402..5d9fc24d71 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +@@ -136,6 +136,8 @@ QGtk3Theme::QGtk3Theme() + qputenv("XCURSOR_THEME", cursorTheme.toUtf8()); + } + } ++ ++ m_storage.reset(new QGtk3Storage); + } + + static inline QVariant gtkGetLongPressTime() +@@ -185,6 +187,8 @@ QString QGtk3Theme::gtkFontName() const + + Qt::Appearance QGtk3Theme::appearance() const + { ++ if (m_storage) ++ return m_storage->appearance(); + /* + https://docs.gtk.org/gtk3/running.html + +@@ -199,9 +203,9 @@ Qt::Appearance QGtk3Theme::appearance() const + to override any other settings. + */ + QString themeName = qEnvironmentVariable("GTK_THEME"); +- const QRegularExpression darkRegex(QStringLiteral("[:-]dark"), QRegularExpression::CaseInsensitiveOption); + if (!themeName.isEmpty()) +- return darkRegex.match(themeName).hasMatch() ? Qt::Appearance::Dark : Qt::Appearance::Light; ++ return themeName.contains("dark", Qt::CaseInsensitive) ++ ? Qt::Appearance::Dark : Qt::Appearance::Light; + + /* + https://docs.gtk.org/gtk3/property.Settings.gtk-application-prefer-dark-theme.html +@@ -219,7 +223,8 @@ Qt::Appearance QGtk3Theme::appearance() const + */ + themeName = gtkSetting("gtk-theme-name"); + if (!themeName.isEmpty()) +- return darkRegex.match(themeName).hasMatch() ? Qt::Appearance::Dark : Qt::Appearance::Light; ++ return themeName.contains("dark", Qt::CaseInsensitive) ++ ? Qt::Appearance::Dark : Qt::Appearance::Light; + + return Qt::Appearance::Unknown; + } +@@ -277,4 +282,25 @@ bool QGtk3Theme::useNativeFileDialog() + return gtk_check_version(3, 15, 5) == nullptr; + } + ++const QPalette *QGtk3Theme::palette(Palette type) const ++{ ++ return m_storage ? m_storage->palette(type) : QPlatformTheme::palette(type); ++} ++ ++QPixmap QGtk3Theme::standardPixmap(StandardPixmap sp, const QSizeF &size) const ++{ ++ return m_storage ? m_storage->standardPixmap(sp, size) : QPlatformTheme::standardPixmap(sp, size); ++} ++ ++const QFont *QGtk3Theme::font(Font type) const ++{ ++ return m_storage ? m_storage->font(type) : QGnomeTheme::font(type); ++} ++ ++QIcon QGtk3Theme::fileIcon(const QFileInfo &fileInfo, ++ QPlatformTheme::IconOptions iconOptions) const ++{ ++ return m_storage ? m_storage->fileIcon(fileInfo) : QGnomeTheme::fileIcon(fileInfo, iconOptions); ++} ++ + QT_END_NAMESPACE +diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.h b/src/plugins/platformthemes/gtk3/qgtk3theme.h +index 89a3b98994..73f4399894 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3theme.h ++++ b/src/plugins/platformthemes/gtk3/qgtk3theme.h +@@ -41,6 +41,7 @@ + #define QGTK3THEME_H + + #include ++#include "qgtk3storage_p.h" + + QT_BEGIN_NAMESPACE + +@@ -60,9 +61,16 @@ public: + QPlatformMenu* createPlatformMenu() const override; + QPlatformMenuItem* createPlatformMenuItem() const override; + ++ const QPalette *palette(Palette type = SystemPalette) const override; ++ const QFont *font(Font type = SystemFont) const override; ++ QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override; ++ QIcon fileIcon(const QFileInfo &fileInfo, ++ QPlatformTheme::IconOptions iconOptions = { }) const override; ++ + static const char *name; + private: + static bool useNativeFileDialog(); ++ std::unique_ptr m_storage; + }; + + QT_END_NAMESPACE +-- +2.41.0 + diff --git a/0017-GTK3-theme-simplify-code.patch b/0017-GTK3-theme-simplify-code.patch new file mode 100644 index 0000000..a661f18 --- /dev/null +++ b/0017-GTK3-theme-simplify-code.patch @@ -0,0 +1,27 @@ +From 01ae45ce9cca19e96875eda74bf6b9168f90e464 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:35:44 +0200 +Subject: [PATCH 17/25] GTK3 theme: simplify code + +There's no need to first convert to QString and then convert back to +QByteArray. +--- + src/plugins/platformthemes/gtk3/qgtk3theme.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +index 5d9fc24d71..fcd466f768 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +@@ -128,7 +128,7 @@ QGtk3Theme::QGtk3Theme() + if (qEnvironmentVariableIsEmpty("XCURSOR_SIZE")) { + const int cursorSize = gtkSetting("gtk-cursor-theme-size"); + if (cursorSize > 0) +- qputenv("XCURSOR_SIZE", QString::number(cursorSize).toUtf8()); ++ qputenv("XCURSOR_SIZE", QByteArray::number(cursorSize)); + } + if (qEnvironmentVariableIsEmpty("XCURSOR_THEME")) { + const QString cursorTheme = gtkSetting("gtk-cursor-theme-name"); +-- +2.41.0 + diff --git a/0018-Fix-checkbox-and-radiobutton-background-in-QGtk3Them.patch b/0018-Fix-checkbox-and-radiobutton-background-in-QGtk3Them.patch new file mode 100644 index 0000000..1e3e830 --- /dev/null +++ b/0018-Fix-checkbox-and-radiobutton-background-in-QGtk3Them.patch @@ -0,0 +1,48 @@ +From f44986a54facefafeed851a7db902867f701208b Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:36:14 +0200 +Subject: [PATCH 18/25] Fix checkbox and radiobutton background in QGtk3Theme + +The background color for radio buttons and checkboxes was not +correctly read from the current GTK3 theme in light mode. +This has lead to identical colors for indicators and background of +radio buttons and checkboxes for certain GTK themes (e.g. Breeze). + +This patch sets the GTK default foreground color to the base color of +palettes for checkboxes and radio buttons. +--- + src/plugins/platformthemes/gtk3/qgtk3storage.cpp | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +index 55c7c8eff8..1a9f88f6df 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +@@ -377,7 +377,6 @@ void QGtk3Storage::createMapping() + ADD(Normal, Button); + ADD(Normal, Base); + ADD(Inactive, Base); +- ADD(Normal, Window); // redundant + ADD(Inactive, Window); + LIGHTER(Normal, Window, 125); + ADD(Normal, Light); +@@ -391,7 +390,6 @@ void QGtk3Storage::createMapping() + LIGHTER(Normal, WindowText, 50); + ADD(Disabled, Text); + ADD(Disabled, WindowText); +- //ADD(Normal, ButtonText); + ADD(Inactive, ButtonText); + GTK(button, Text, NORMAL); + ADD(Disabled, ButtonText); +@@ -427,6 +425,8 @@ void QGtk3Storage::createMapping() + // Checkbox and Radio Button + GTK(button, Text, ACTIVE); + ADD(Normal, Base, Dark); ++ GTK(Default, Background, NORMAL); ++ ADD(All, Base); + GTK(button, Text, NORMAL); + ADD(Normal, Base, Light); + SAVE(CheckBoxPalette); +-- +2.41.0 + diff --git a/0019-Cleanup-QGtk3Theme.patch b/0019-Cleanup-QGtk3Theme.patch new file mode 100644 index 0000000..473e04a --- /dev/null +++ b/0019-Cleanup-QGtk3Theme.patch @@ -0,0 +1,108 @@ +From e35349100e0f8fc21643c0fa514af5f6f8950097 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:38:53 +0200 +Subject: [PATCH 19/25] Cleanup QGtk3Theme + +1. Remove unused include. +2. Replace unnecessary null checks with asserts. +3. Remove dead code after the cleanup. +--- + .../platformthemes/gtk3/qgtk3theme.cpp | 55 ++++--------------- + 1 file changed, 10 insertions(+), 45 deletions(-) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +index fcd466f768..d3383097fc 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +@@ -43,7 +43,6 @@ + #include "qpa/qwindowsysteminterface.h" + #include + #include +-#include + + #undef signals + #include +@@ -187,46 +186,8 @@ QString QGtk3Theme::gtkFontName() const + + Qt::Appearance QGtk3Theme::appearance() 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", 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("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", Qt::CaseInsensitive) +- ? Qt::Appearance::Dark : Qt::Appearance::Light; +- +- return Qt::Appearance::Unknown; ++ Q_ASSERT(m_storage); ++ return m_storage->appearance(); + } + + bool QGtk3Theme::usePlatformNativeDialog(DialogType type) const +@@ -284,23 +245,27 @@ 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_ASSERT(m_storage); ++ return m_storage->fileIcon(fileInfo); + } + + QT_END_NAMESPACE +-- +2.41.0 + diff --git a/0020-Detect-appearance-by-colors-unless-GTK-theme-name-co.patch b/0020-Detect-appearance-by-colors-unless-GTK-theme-name-co.patch new file mode 100644 index 0000000..fbefcf0 --- /dev/null +++ b/0020-Detect-appearance-by-colors-unless-GTK-theme-name-co.patch @@ -0,0 +1,85 @@ +From 45a629fb6495c2469c89e1bf797ee84214e7e661 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:40:32 +0200 +Subject: [PATCH 20/25] Detect appearance by colors unless GTK theme name + contains "dark" + +QGtk3Theme detects the appearance property by theme name: If the name +contains the keyword "dark", the theme is considered to be dark and +otherwise light. + +This detection logic fails, when the GTK theme is dark without +containing the "dark" keyword, e.g. the dark theme "Adapta-Nokto". +While QGtk3Theme imports the right colors in that case, it wrongly +identifies a light theme. + +This patch adapts the detection logic: If the theme name contains the +"dark" keyword, it is considered a dark theme without further checks. +If it doesn't, the current GTK3 theme's default background and +foreground colors will be read. If the foreground is lighter than the +background, the theme is considered dark. If the background is lighter +than the foreground, the theme is considered light. If both colors are +identical, the appearance will be Qt::Appearance::Unknown. +--- + .../platformthemes/gtk3/qgtk3interface.cpp | 16 ++++++++++++++++ + .../platformthemes/gtk3/qgtk3interface_p.h | 3 +++ + src/plugins/platformthemes/gtk3/qgtk3storage.cpp | 2 +- + 3 files changed, 20 insertions(+), 1 deletion(-) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface.cpp b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp +index d932b250a5..e2444197da 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3interface.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp +@@ -400,6 +400,22 @@ const QString QGtk3Interface::themeName() const + return QLatin1String(theme_name); + } + ++Qt::Appearance QGtk3Interface::appearanceByColors() 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::Appearance::Dark; ++ if (foreground.lightness() < background.lightness()) ++ return Qt::Appearance::Light; ++ return Qt::Appearance::Unknown; ++} ++ + inline constexpr QGtk3Interface::QGtkWidget QGtk3Interface::toWidgetType(QPlatformTheme::Font type) + { + switch (type) { +diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface_p.h b/src/plugins/platformthemes/gtk3/qgtk3interface_p.h +index 8997a64e76..e04025923d 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3interface_p.h ++++ b/src/plugins/platformthemes/gtk3/qgtk3interface_p.h +@@ -97,6 +97,9 @@ public: + // Return current GTK theme name + const QString themeName() const; + ++ // Derive appearance from default colors ++ Qt::Appearance appearanceByColors() const; ++ + // Convert GTK state to/from string + static int toGtkState(const QString &state); + static const QLatin1String fromGtkState(GtkStateFlags state); +diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +index 1a9f88f6df..c206b4d3b5 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +@@ -222,7 +222,7 @@ void QGtk3Storage::populateMap() + + // Derive appearance from theme name + m_appearance = newThemeName.contains("dark", Qt::CaseInsensitive) +- ? Qt::Appearance::Dark : Qt::Appearance::Light; ++ ? Qt::Appearance::Dark : m_interface->appearanceByColors(); + + if (m_themeName.isEmpty()) { + qCDebug(lcQGtk3Interface) << "GTK theme initialized:" << newThemeName << m_appearance; +-- +2.41.0 + diff --git a/0021-Change-parsing-log-output-in-QGtk3Json-from-qCDebug-.patch b/0021-Change-parsing-log-output-in-QGtk3Json-from-qCDebug-.patch new file mode 100644 index 0000000..61923cd --- /dev/null +++ b/0021-Change-parsing-log-output-in-QGtk3Json-from-qCDebug-.patch @@ -0,0 +1,120 @@ +From 3491415f1e2f60cae47273af4810db1bfda81394 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:41:06 +0200 +Subject: [PATCH 21/25] Change parsing log output in QGtk3Json from qCDebug to + qCInfo + +When a palette mapping is imported from a Json file, parsing errors are +logged with qCDebug. This prevents errors from being logged in release +builds. + +This patch replaces qCDebug with qCInfo for Json parsing to make errors +visible when the logging category qt.qpa.gtk is activated. +--- + src/plugins/platformthemes/gtk3/qgtk3json.cpp | 23 +++++++++---------- + 1 file changed, 11 insertions(+), 12 deletions(-) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3json.cpp b/src/plugins/platformthemes/gtk3/qgtk3json.cpp +index f4d5b50ec5..9db1ea3d20 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3json.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3json.cpp +@@ -331,7 +331,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;\ + }\ +@@ -339,7 +339,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;\ +@@ -349,7 +349,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; + } + +@@ -358,13 +358,12 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) + bool ok; + const QPlatformTheme::Palette paletteType = toPalette(paletteName); + if (paletteType == QPlatformTheme::NPalettes) { +- qCDebug(lcQGtk3Interface) << "Invalid Palette name:" << paletteName; +- return false; ++ qCInfo(lcQGtk3Interface) << "Invalid Palette name:" << paletteName; + } + 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; + } + +@@ -374,7 +373,7 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) + const int intVal = QMetaEnum::fromType().keyToValue(colorRoleName + .toLatin1().constData(), &ok); + if (!ok) { +- qCDebug(lcQGtk3Interface) << "Palette" << paletteName ++ qCInfo(lcQGtk3Interface) << "Palette" << paletteName + << "contains invalid color role" << colorRoleName; + return false; + } +@@ -383,7 +382,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; + } +@@ -399,7 +398,7 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) + 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; + } +@@ -421,7 +420,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; + } +@@ -431,7 +430,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; + } +@@ -459,7 +458,7 @@ bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc) + 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; + } +-- +2.41.0 + diff --git a/0022-Document-QGtk3Interface.patch b/0022-Document-QGtk3Interface.patch new file mode 100644 index 0000000..852f86c --- /dev/null +++ b/0022-Document-QGtk3Interface.patch @@ -0,0 +1,400 @@ +From e22d4e2a63976fe6f88266d8f2bde002b12b0744 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:42:04 +0200 +Subject: [PATCH 22/25] Document QGtk3Interface + +Add internal documentation to header and implementation of +QGtk3Interface +--- + .../platformthemes/gtk3/qgtk3interface.cpp | 161 ++++++++++++++++-- + .../platformthemes/gtk3/qgtk3interface_p.h | 43 ++++- + 2 files changed, 183 insertions(+), 21 deletions(-) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface.cpp b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp +index e2444197da..0fab1220b4 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3interface.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp +@@ -65,6 +65,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) \ +@@ -92,6 +100,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) +@@ -103,9 +115,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, QGtkColorDefault::def})} + +@@ -132,8 +147,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 +259,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,14 +270,23 @@ 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::value, + "guint8 has diverted from uchar. Code needs fixing."); +@@ -264,6 +301,13 @@ QImage QGtk3Interface::qt_convert_gdk_pixbuf(GdkPixbuf *buf) const + return converted.copy(); // detatch to survive lifetime of buf + } + ++/*! ++ \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 +342,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 +368,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 +414,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 +438,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,15 +454,28 @@ 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)); + } + ++/*! ++ \internal ++ \brief Returns the name of the current GTK theme. ++ */ + const QString QGtk3Interface::themeName() const + { + gchar *theme_name; +@@ -400,6 +487,15 @@ const QString QGtk3Interface::themeName() const + return QLatin1String(theme_name); + } + ++/*! ++ \internal ++ \brief Determine appearance by colors. ++ ++ Returns the appearance 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::Appearance QGtk3Interface::appearanceByColors() const + { + const QColor background = color(widget(QGtkWidget::gtk_Default), +@@ -416,6 +512,12 @@ Qt::Appearance QGtk3Interface::appearanceByColors() const + return Qt::Appearance::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) { +@@ -451,6 +553,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) { +@@ -462,6 +568,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 +@@ -494,6 +607,17 @@ inline constexpr QFont::Weight QGtk3Interface::toQFontWeight(int weight) + return QFont::Black; + } + ++/*! ++ \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))); +@@ -517,9 +641,6 @@ QFont QGtk3Interface::font(QPlatformTheme::Font type) const + font.setPointSizeF(static_cast(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()) { +@@ -532,6 +653,10 @@ QFont QGtk3Interface::font(QPlatformTheme::Font type) const + 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()); +diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface_p.h b/src/plugins/platformthemes/gtk3/qgtk3interface_p.h +index e04025923d..42643e72ef 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3interface_p.h ++++ b/src/plugins/platformthemes/gtk3/qgtk3interface_p.h +@@ -36,6 +36,18 @@ QT_BEGIN_NAMESPACE + Q_DECLARE_LOGGING_CATEGORY(lcQGtk3Interface); + + 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 +@@ -43,7 +55,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, +@@ -68,7 +86,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, +@@ -78,7 +104,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, +-- +2.41.0 + diff --git a/0023-Document-QGtk3Storage.patch b/0023-Document-QGtk3Storage.patch new file mode 100644 index 0000000..1ad8387 --- /dev/null +++ b/0023-Document-QGtk3Storage.patch @@ -0,0 +1,366 @@ +From 4d4cb6a17e5890ef1ee7ac0398d7b5ecdf77d48f Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:42:49 +0200 +Subject: [PATCH 23/25] Document QGtk3Storage + +Add internal documentation to header and implementation of +QGtk3Storage +--- + .../platformthemes/gtk3/qgtk3storage.cpp | 231 +++++++++++++++--- + .../platformthemes/gtk3/qgtk3storage_p.h | 1 + + 2 files changed, 193 insertions(+), 39 deletions(-) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +index c206b4d3b5..0f53d526b8 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +@@ -24,7 +24,26 @@ QGtk3Storage::QGtk3Storage() + 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) { +@@ -64,7 +83,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))\ +@@ -88,7 +114,16 @@ QGtk3Storage::Source QGtk3Storage::brush(const TargetBrush &b, const BrushMap &m + #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 +140,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) +@@ -160,6 +201,12 @@ const QPalette *QGtk3Storage::palette(QPlatformTheme::Palette type) const + 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 +216,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,11 +240,19 @@ 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; +@@ -202,6 +264,13 @@ 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(); +@@ -209,6 +278,54 @@ void QGtk3Storage::handleThemeChange() + 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::Appearance ++ - 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; +@@ -248,6 +365,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 +408,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 +@@ -332,41 +487,39 @@ 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, 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. + */ + + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage_p.h b/src/plugins/platformthemes/gtk3/qgtk3storage_p.h +index 57f6aeea96..af628d49ff 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3storage_p.h ++++ b/src/plugins/platformthemes/gtk3/qgtk3storage_p.h +@@ -33,6 +33,7 @@ class QGtk3Storage + public: + QGtk3Storage(); + ++ // Enum documented in cpp file. Please keep it in line with updates made here. + enum class SourceType { + Gtk, + Fixed, +-- +2.41.0 + diff --git a/0024-QGtk3Theme-Improve-fixed-font-delivery.patch b/0024-QGtk3Theme-Improve-fixed-font-delivery.patch new file mode 100644 index 0000000..12a2fc3 --- /dev/null +++ b/0024-QGtk3Theme-Improve-fixed-font-delivery.patch @@ -0,0 +1,84 @@ +From 5ad394475f26725d854a0c4c733ffcde8f3bbf15 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:44:11 +0200 +Subject: [PATCH 24/25] QGtk3Theme: Improve fixed font delivery + +The gtk_fixed widget was used as a reference to obtain a fixed font +and HeaderViewFont. + +This is a mistake, because the gtk_fixed widget is a container for +other widgets with fixed geometries and no layouting. + +This patch makes the default style being used for a fixed font and, as +a drive-by, the combo box as a reference for a header view font. +A monospace based css provider as explicitly added to the style +context, in case a fixed font is requested. The provider is removed +afterwards. +--- + .../platformthemes/gtk3/qgtk3interface.cpp | 24 +++++++++++++++++-- + 1 file changed, 22 insertions(+), 2 deletions(-) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface.cpp b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp +index 0fab1220b4..21abea81cf 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3interface.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp +@@ -18,6 +18,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -538,13 +539,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; +@@ -624,6 +625,24 @@ QFont QGtk3Interface::font(QPlatformTheme::Font type) const + if (!con) + return QFont(); + ++ // explicitly add provider for fixed font ++ GtkCssProvider *cssProvider = nullptr; ++ if (type == QPlatformTheme::FixedFont) { ++ cssProvider = gtk_css_provider_new(); ++ 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(); +@@ -650,6 +669,7 @@ QFont QGtk3Interface::font(QPlatformTheme::Font type) const + font.setFamily("monospace"); + } + } ++ + return font; + } + +-- +2.41.0 + diff --git a/0025-QGtk3Theme-Do-not-default-Active-WindowText-to-butto.patch b/0025-QGtk3Theme-Do-not-default-Active-WindowText-to-butto.patch new file mode 100644 index 0000000..295c8fe --- /dev/null +++ b/0025-QGtk3Theme-Do-not-default-Active-WindowText-to-butto.patch @@ -0,0 +1,56 @@ +From 7b52f959ccc772b399fb32c9a4eabe37dc572db6 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:44:31 +0200 +Subject: [PATCH 25/25] QGtk3Theme: Do not default Active WindowText to button + foreground + +QGtk3Theme uses the GTK button foreground as a default for the +WindowText color role. When a GTK3 theme has no specific color for the +entry text, this can lead to text on certain assets looking darker +and thus disabled. + +This discontinues usage of the button foreground for the window text. + +Finding the WindowText color role in QPlatformTheme::SystemPalette now +follows the following logic: +(1) GTK normal entry text is used if specified. This is the preferred +option, copying GTK behavior. +(2) If (1) is not specified, the GTK default text color is used, making +WindowText equal to Text. +(3) If neither (1), nor (2) are specified, the WindowText color role is +taken from qt_fusionPalette, where it is also equal to Text. + +The SystemPalette is used as a default template for all other control +or widget speicific palettes. The rules above therefor apply to all +screen assets (unless they use a JSON file to specify a their +individual WindowText). + +[ChangeLog][QGtk3Theme][SystemPalette][WindowText] Default to GTK +Entry Text / Normal Text / qt_fusionPalette +--- + src/plugins/platformthemes/gtk3/qgtk3storage.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +index 0f53d526b8..df7f7c77b8 100644 +--- a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp ++++ b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp +@@ -538,7 +538,6 @@ void QGtk3Storage::createMapping() + LIGHTER(Normal, Window, 80); + ADD(Normal, Dark); + GTK(button, Foreground, ACTIVE); +- ADD(Normal, WindowText); + ADD(Inactive, WindowText); + LIGHTER(Normal, WindowText, 50); + ADD(Disabled, Text); +@@ -562,6 +561,7 @@ void QGtk3Storage::createMapping() + ADD(Disabled, HighlightedText); + GTK(Default, Text, NORMAL); + ADD(Normal, Text); ++ ADD(Normal, WindowText); + ADD(Inactive, Text); + ADD(Normal, HighlightedText); + LIGHTER(Normal, Base, 93); +-- +2.41.0 + diff --git a/qt5-qtbase.spec b/qt5-qtbase.spec index c1221fc..2a53c3d 100644 --- a/qt5-qtbase.spec +++ b/qt5-qtbase.spec @@ -57,7 +57,7 @@ Name: qt5-qtbase Summary: Qt5 - QtBase components Version: 5.15.10 -Release: 5%{?dist} +Release: 6%{?dist} # See LGPL_EXCEPTIONS.txt, for exception details License: LGPL-3.0-only OR GPL-3.0-only WITH Qt-GPL-exception-1.0 @@ -125,11 +125,6 @@ Patch64: qt5-qtbase-5.12.1-firebird-4.0.0.patch # fix for new mariadb Patch65: qtbase-opensource-src-5.9.0-mysql.patch - -# https://fedoraproject.org/wiki/Changes/Qt_Wayland_By_Default_On_Gnome -# https://bugzilla.redhat.com/show_bug.cgi?id=1732129 -Patch80: qtbase-use-wayland-on-gnome.patch - # gcc-11 Patch90: %{name}-gcc11.patch @@ -152,7 +147,42 @@ Patch103: qtbase-QTBUG-112136.patch Patch104: qtbase-QTBUG-103393.patch # upstream security fixes -Patch150: CVE-2023-37369-qtbase-5.15.diff +Patch110: CVE-2023-37369-qtbase-5.15.diff + +## Qt 6 backports for better Gtk/GNOME integration +# https://fedoraproject.org/wiki/Changes/Qt_Wayland_By_Default_On_Gnome +# https://bugzilla.redhat.com/show_bug.cgi?id=1732129 +Patch150: 0001-Use-Wayland-by-default-on-GNOME.patch + +# https://fedoraproject.org/wiki/Changes/NoCustomQtThemingForWorkstation +# https://bugzilla.redhat.com/show_bug.cgi?id=2226797 +Patch151: 0002-Add-QPlatformTheme-Appearance-for-detecting-light-da.patch +Patch152: 0003-Add-enum-class-Qt-Appearance.patch +Patch153: 0004-QGtk3Theme-implement-appearance-function-to-detect-d.patch +Patch154: 0005-Account-for-dark-system-themes-in-qt_fusionPalette.patch +Patch155: 0006-qt_fusionPalette-make-links-more-legible-on-dark-bac.patch +Patch156: 0007-Add-nullptr-check-for-theme-when-initializing-palett.patch +Patch157: 0008-Replace-QPlatformTheme-Appearance-by-Qt-Appearance.patch +Patch158: 0009-Rename-QGuiApplicationPrivate-notifyThemeChanged-to-.patch +Patch159: 0010-Send-ThemeChange-event-to-all-windows-when-system-th.patch +Patch160: 0011-Propagate-appearance-property-from-QPlatformTheme-to.patch +Patch161: 0012-Sync-and-assert-StandardPixmap-enums-in-QPlatformThe.patch +Patch162: 0013-QGtk3Theme-subscribe-to-theme-hint-changes.patch +Patch163: 0014-Gtk3Theme-set-XCURSOR_SIZE-and-XCURSOR_THEME-for-way.patch +Patch164: 0015-Gtk3-fix-stack-smashing-on-mismatch-between-bool-and.patch +Patch165: 0016-Re-implement-palette-standardPixmap-file-icons-fonts.patch +Patch166: 0017-GTK3-theme-simplify-code.patch +Patch167: 0018-Fix-checkbox-and-radiobutton-background-in-QGtk3Them.patch +Patch168: 0019-Cleanup-QGtk3Theme.patch +Patch169: 0020-Detect-appearance-by-colors-unless-GTK-theme-name-co.patch +Patch170: 0021-Change-parsing-log-output-in-QGtk3Json-from-qCDebug-.patch +Patch171: 0022-Document-QGtk3Interface.patch +Patch172: 0023-Document-QGtk3Storage.patch +Patch173: 0024-QGtk3Theme-Improve-fixed-font-delivery.patch +Patch174: 0025-QGtk3Theme-Do-not-default-Active-WindowText-to-butto.patch + +# Latest QGnomePlatform needs to be specified to be used +Patch200: qtbase-use-qgnomeplatform-as-default-platform-theme-on-gnome.patch # Do not check any files in %%{_qt5_plugindir}/platformthemes/ for requires. # Those themes are there for platform integration. If the required libraries are @@ -422,10 +452,6 @@ Qt5 libraries used for drawing widgets and OpenGL items. %patch -P65 -p1 -b .mysql %endif -%if 0%{?fedora} > 30 || 0%{?rhel} > 8 -%patch -P80 -p1 -b .use-wayland-on-gnome.patch -%endif - %patch -P90 -p1 -b .gcc11 ## upstream patches @@ -434,7 +460,43 @@ Qt5 libraries used for drawing widgets and OpenGL items. %patch -P102 -p1 %patch -P103 -p1 %patch -P104 -p1 -%patch -P150 -p1 +%patch -P110 -p1 + +## Qt 6 backports +%if 0%{?fedora} > 30 || 0%{?rhel} > 8 +%patch -P150 -p1 -b .use-wayland-on-gnome.patch +%endif +%if 0%{?fedora} > 38 || 0%{?rhel} > 9 +%patch -P151 -p1 +%patch -P152 -p1 +%patch -P153 -p1 +%patch -P154 -p1 +%patch -P155 -p1 +%patch -P156 -p1 +%patch -P157 -p1 +%patch -P158 -p1 +%patch -P159 -p1 +%patch -P160 -p1 +%patch -P161 -p1 +%patch -P162 -p1 +%patch -P163 -p1 +%patch -P164 -p1 +%patch -P165 -p1 +%patch -P166 -p1 +%patch -P167 -p1 +%patch -P168 -p1 +%patch -P169 -p1 +%patch -P170 -p1 +%patch -P171 -p1 +%patch -P172 -p1 +%patch -P173 -p1 +%patch -P174 -p1 +%endif + +%if 0%{?fedora} < 39 +# Use QGnomePlatform by default +%patch -P200 -p1 +%endif # move some bundled libs to ensure they're not accidentally used pushd src/3rdparty @@ -1114,6 +1176,11 @@ fi %changelog +* Mon Aug 21 2023 Jan Grulich - 5.15.10-6 +- Backport Qt 6 improvements to QGtkStyle for better Gtk/GNOME integration +- Use QGnomePlatform by default on F38 and older + Resolves: #2226797 + * Wed Aug 16 2023 Than Ngo - 5.15.10-5 - Fixed bz#2232359, CVE-2023-37369 qtbase: buffer overflow in QXmlStreamReader diff --git a/qtbase-use-qgnomeplatform-as-default-platform-theme-on-gnome.patch b/qtbase-use-qgnomeplatform-as-default-platform-theme-on-gnome.patch new file mode 100644 index 0000000..bff7412 --- /dev/null +++ b/qtbase-use-qgnomeplatform-as-default-platform-theme-on-gnome.patch @@ -0,0 +1,13 @@ +diff --git a/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp b/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp +index 6e01af052c..fc67477ba9 100644 +--- a/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp ++++ b/src/platformsupport/themes/genericunix/qgenericunixthemes.cpp +@@ -868,6 +868,8 @@ QStringList QGenericUnixTheme::themeNames() + result.push_back(QLatin1String(QKdeTheme::name)); + #endif + } else if (gtkBasedEnvironments.contains(desktopName)) { ++ // prefer the QGnomePlatform theme ++ result.push_back(QStringLiteral("qgnomeplatform")); + // prefer the GTK3 theme implementation with native dialogs etc. + result.push_back(QStringLiteral("gtk3")); + // fallback to the generic Gnome theme if loading the GTK3 theme fails