From 12ab19e10b742cf71039de865560994492c1b76d Mon Sep 17 00:00:00 2001 From: Jan Grulich Date: Aug 21 2023 17:29:04 +0000 Subject: Drop some unnecessary backports --- diff --git a/0001-Use-Wayland-by-default-on-GNOME.patch b/0001-Use-Wayland-by-default-on-GNOME.patch index 92f04b4..f0060da 100644 --- a/0001-Use-Wayland-by-default-on-GNOME.patch +++ b/0001-Use-Wayland-by-default-on-GNOME.patch @@ -1,7 +1,7 @@ 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 +Subject: [PATCH 01/22] Use Wayland by default on GNOME --- src/gui/kernel/qguiapplication.cpp | 9 +-------- diff --git a/0002-Add-QPlatformTheme-Appearance-for-detecting-light-da.patch b/0002-Add-QPlatformTheme-Appearance-for-detecting-light-da.patch index 5ae90bc..8e2c2b1 100644 --- a/0002-Add-QPlatformTheme-Appearance-for-detecting-light-da.patch +++ b/0002-Add-QPlatformTheme-Appearance-for-detecting-light-da.patch @@ -1,7 +1,7 @@ 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 +Subject: [PATCH 02/22] Add QPlatformTheme::Appearance for detecting light/dark modes And implement it on Windows and macOS. diff --git a/0003-Add-enum-class-Qt-Appearance.patch b/0003-Add-enum-class-Qt-Appearance.patch index 254b880..7578b6d 100644 --- a/0003-Add-enum-class-Qt-Appearance.patch +++ b/0003-Add-enum-class-Qt-Appearance.patch @@ -1,7 +1,7 @@ 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 +Subject: [PATCH 03/22] 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. diff --git a/0004-QGtk3Theme-implement-appearance-function-to-detect-d.patch b/0004-QGtk3Theme-implement-appearance-function-to-detect-d.patch index 1f05257..4ed058a 100644 --- a/0004-QGtk3Theme-implement-appearance-function-to-detect-d.patch +++ b/0004-QGtk3Theme-implement-appearance-function-to-detect-d.patch @@ -1,7 +1,7 @@ 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 +Subject: [PATCH 04/22] QGtk3Theme: implement appearance function to detect dark themes This allows Qt Quick Controls to detect if a dark theme is in use, diff --git a/0005-Account-for-dark-system-themes-in-qt_fusionPalette.patch b/0005-Account-for-dark-system-themes-in-qt_fusionPalette.patch index 3339773..3951e00 100644 --- a/0005-Account-for-dark-system-themes-in-qt_fusionPalette.patch +++ b/0005-Account-for-dark-system-themes-in-qt_fusionPalette.patch @@ -1,7 +1,7 @@ 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 +Subject: [PATCH 05/22] 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 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 index 9fabfaf..13b74e4 100644 --- a/0006-qt_fusionPalette-make-links-more-legible-on-dark-bac.patch +++ b/0006-qt_fusionPalette-make-links-more-legible-on-dark-bac.patch @@ -1,7 +1,7 @@ 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 +Subject: [PATCH 06/22] qt_fusionPalette: make links more legible on dark backgrounds QPalette's default for Link is Qt::blue, which is difficult to read diff --git a/0007-Add-nullptr-check-for-theme-when-initializing-palett.patch b/0007-Add-nullptr-check-for-theme-when-initializing-palett.patch index 3dc1a9f..42ea827 100644 --- a/0007-Add-nullptr-check-for-theme-when-initializing-palett.patch +++ b/0007-Add-nullptr-check-for-theme-when-initializing-palett.patch @@ -1,7 +1,7 @@ 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 +Subject: [PATCH 07/22] Add nullptr check for theme when initializing palette --- src/gui/kernel/qplatformtheme.cpp | 6 ++++-- diff --git a/0008-Replace-QPlatformTheme-Appearance-by-Qt-Appearance.patch b/0008-Replace-QPlatformTheme-Appearance-by-Qt-Appearance.patch index 6e67912..12657c5 100644 --- a/0008-Replace-QPlatformTheme-Appearance-by-Qt-Appearance.patch +++ b/0008-Replace-QPlatformTheme-Appearance-by-Qt-Appearance.patch @@ -1,7 +1,7 @@ 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 +Subject: [PATCH 08/22] Replace QPlatformTheme::Appearance by Qt:Appearance With the introduction of Qt:Appearance, its predecessor in QPlatformTheme has become redundant. diff --git a/0009-Rename-QGuiApplicationPrivate-notifyThemeChanged-to-.patch b/0009-Rename-QGuiApplicationPrivate-notifyThemeChanged-to-.patch deleted file mode 100644 index c743682..0000000 --- a/0009-Rename-QGuiApplicationPrivate-notifyThemeChanged-to-.patch +++ /dev/null @@ -1,110 +0,0 @@ -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/0009-Sync-and-assert-StandardPixmap-enums-in-QPlatformThe.patch b/0009-Sync-and-assert-StandardPixmap-enums-in-QPlatformThe.patch new file mode 100644 index 0000000..df196e5 --- /dev/null +++ b/0009-Sync-and-assert-StandardPixmap-enums-in-QPlatformThe.patch @@ -0,0 +1,78 @@ +From d0f0010b49eb40477ea42496063766199e857312 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 13:43:29 +0200 +Subject: [PATCH 09/22] 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/0010-QGtk3Theme-subscribe-to-theme-hint-changes.patch b/0010-QGtk3Theme-subscribe-to-theme-hint-changes.patch new file mode 100644 index 0000000..28344ff --- /dev/null +++ b/0010-QGtk3Theme-subscribe-to-theme-hint-changes.patch @@ -0,0 +1,50 @@ +From b6bf671270aea7bddbb98c69ff395ae649d0edf8 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 15:38:13 +0200 +Subject: [PATCH 10/22] 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/0010-Send-ThemeChange-event-to-all-windows-when-system-th.patch b/0010-Send-ThemeChange-event-to-all-windows-when-system-th.patch deleted file mode 100644 index a0ee5fc..0000000 --- a/0010-Send-ThemeChange-event-to-all-windows-when-system-th.patch +++ /dev/null @@ -1,102 +0,0 @@ -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-Gtk3Theme-set-XCURSOR_SIZE-and-XCURSOR_THEME-for-way.patch b/0011-Gtk3Theme-set-XCURSOR_SIZE-and-XCURSOR_THEME-for-way.patch new file mode 100644 index 0000000..f5362ec --- /dev/null +++ b/0011-Gtk3Theme-set-XCURSOR_SIZE-and-XCURSOR_THEME-for-way.patch @@ -0,0 +1,54 @@ +From 232211f5867878e64bed10d4dff0f94a6326a94f Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 15:38:38 +0200 +Subject: [PATCH 11/22] 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/0011-Propagate-appearance-property-from-QPlatformTheme-to.patch b/0011-Propagate-appearance-property-from-QPlatformTheme-to.patch deleted file mode 100644 index 9e943b6..0000000 --- a/0011-Propagate-appearance-property-from-QPlatformTheme-to.patch +++ /dev/null @@ -1,261 +0,0 @@ -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-Gtk3-fix-stack-smashing-on-mismatch-between-bool-and.patch b/0012-Gtk3-fix-stack-smashing-on-mismatch-between-bool-and.patch new file mode 100644 index 0000000..059ee26 --- /dev/null +++ b/0012-Gtk3-fix-stack-smashing-on-mismatch-between-bool-and.patch @@ -0,0 +1,29 @@ +From dc373fc0d7dc14107e06aca4b5664c830d681494 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Wed, 26 Jul 2023 15:39:31 +0200 +Subject: [PATCH 12/22] 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/0012-Sync-and-assert-StandardPixmap-enums-in-QPlatformThe.patch b/0012-Sync-and-assert-StandardPixmap-enums-in-QPlatformThe.patch deleted file mode 100644 index 240da56..0000000 --- a/0012-Sync-and-assert-StandardPixmap-enums-in-QPlatformThe.patch +++ /dev/null @@ -1,78 +0,0 @@ -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 deleted file mode 100644 index d40adb8..0000000 --- a/0013-QGtk3Theme-subscribe-to-theme-hint-changes.patch +++ /dev/null @@ -1,50 +0,0 @@ -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/0013-Re-implement-palette-standardPixmap-file-icons-fonts.patch b/0013-Re-implement-palette-standardPixmap-file-icons-fonts.patch new file mode 100644 index 0000000..da43ee1 --- /dev/null +++ b/0013-Re-implement-palette-standardPixmap-file-icons-fonts.patch @@ -0,0 +1,3323 @@ +From 753c0dea114812a2d3b721cb72fda1b49a3c38c2 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 11:54:44 +0200 +Subject: [PATCH 13/22] 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..0a1fa6ef97 +--- /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(nullptr); ++} ++ ++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/0014-GTK3-theme-simplify-code.patch b/0014-GTK3-theme-simplify-code.patch new file mode 100644 index 0000000..5bf7e57 --- /dev/null +++ b/0014-GTK3-theme-simplify-code.patch @@ -0,0 +1,27 @@ +From 376815da4038c757a42f316c4cb7e2a6793fec00 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:35:44 +0200 +Subject: [PATCH 14/22] 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/0014-Gtk3Theme-set-XCURSOR_SIZE-and-XCURSOR_THEME-for-way.patch b/0014-Gtk3Theme-set-XCURSOR_SIZE-and-XCURSOR_THEME-for-way.patch deleted file mode 100644 index 2fc49a4..0000000 --- a/0014-Gtk3Theme-set-XCURSOR_SIZE-and-XCURSOR_THEME-for-way.patch +++ /dev/null @@ -1,54 +0,0 @@ -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-Fix-checkbox-and-radiobutton-background-in-QGtk3Them.patch b/0015-Fix-checkbox-and-radiobutton-background-in-QGtk3Them.patch new file mode 100644 index 0000000..8a61ff7 --- /dev/null +++ b/0015-Fix-checkbox-and-radiobutton-background-in-QGtk3Them.patch @@ -0,0 +1,48 @@ +From 40d1e750fd1b0f646724b383928b8f70a7a3abe8 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:36:14 +0200 +Subject: [PATCH 15/22] 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 0a1fa6ef97..d5d0e2c8e6 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/0015-Gtk3-fix-stack-smashing-on-mismatch-between-bool-and.patch b/0015-Gtk3-fix-stack-smashing-on-mismatch-between-bool-and.patch deleted file mode 100644 index d862f9e..0000000 --- a/0015-Gtk3-fix-stack-smashing-on-mismatch-between-bool-and.patch +++ /dev/null @@ -1,29 +0,0 @@ -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-Cleanup-QGtk3Theme.patch b/0016-Cleanup-QGtk3Theme.patch new file mode 100644 index 0000000..877a211 --- /dev/null +++ b/0016-Cleanup-QGtk3Theme.patch @@ -0,0 +1,108 @@ +From 615966a0e855712898ba92681a08825f4b322e4b Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:38:53 +0200 +Subject: [PATCH 16/22] 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/0016-Re-implement-palette-standardPixmap-file-icons-fonts.patch b/0016-Re-implement-palette-standardPixmap-file-icons-fonts.patch deleted file mode 100644 index 9043d0b..0000000 --- a/0016-Re-implement-palette-standardPixmap-file-icons-fonts.patch +++ /dev/null @@ -1,3323 +0,0 @@ -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-Detect-appearance-by-colors-unless-GTK-theme-name-co.patch b/0017-Detect-appearance-by-colors-unless-GTK-theme-name-co.patch new file mode 100644 index 0000000..bf79a72 --- /dev/null +++ b/0017-Detect-appearance-by-colors-unless-GTK-theme-name-co.patch @@ -0,0 +1,85 @@ +From 8f256a33fa60b249ee6fc7347f917f29a11c1e32 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:40:32 +0200 +Subject: [PATCH 17/22] 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 d5d0e2c8e6..0b6b8e8523 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/0017-GTK3-theme-simplify-code.patch b/0017-GTK3-theme-simplify-code.patch deleted file mode 100644 index a661f18..0000000 --- a/0017-GTK3-theme-simplify-code.patch +++ /dev/null @@ -1,27 +0,0 @@ -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-Change-parsing-log-output-in-QGtk3Json-from-qCDebug-.patch b/0018-Change-parsing-log-output-in-QGtk3Json-from-qCDebug-.patch new file mode 100644 index 0000000..c15fe27 --- /dev/null +++ b/0018-Change-parsing-log-output-in-QGtk3Json-from-qCDebug-.patch @@ -0,0 +1,120 @@ +From d998c808ece0fb3e243bb28913a27bbedf0db974 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:41:06 +0200 +Subject: [PATCH 18/22] 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/0018-Fix-checkbox-and-radiobutton-background-in-QGtk3Them.patch b/0018-Fix-checkbox-and-radiobutton-background-in-QGtk3Them.patch deleted file mode 100644 index 1e3e830..0000000 --- a/0018-Fix-checkbox-and-radiobutton-background-in-QGtk3Them.patch +++ /dev/null @@ -1,48 +0,0 @@ -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 deleted file mode 100644 index 473e04a..0000000 --- a/0019-Cleanup-QGtk3Theme.patch +++ /dev/null @@ -1,108 +0,0 @@ -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/0019-Document-QGtk3Interface.patch b/0019-Document-QGtk3Interface.patch new file mode 100644 index 0000000..e2646c8 --- /dev/null +++ b/0019-Document-QGtk3Interface.patch @@ -0,0 +1,400 @@ +From fa0c84d609a44aa630dbbb498ffe3bf11326647e Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:42:04 +0200 +Subject: [PATCH 19/22] 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/0020-Detect-appearance-by-colors-unless-GTK-theme-name-co.patch b/0020-Detect-appearance-by-colors-unless-GTK-theme-name-co.patch deleted file mode 100644 index fbefcf0..0000000 --- a/0020-Detect-appearance-by-colors-unless-GTK-theme-name-co.patch +++ /dev/null @@ -1,85 +0,0 @@ -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/0020-Document-QGtk3Storage.patch b/0020-Document-QGtk3Storage.patch new file mode 100644 index 0000000..7c5b626 --- /dev/null +++ b/0020-Document-QGtk3Storage.patch @@ -0,0 +1,366 @@ +From 65aff839101ef829c14b69a815a34eeda00abd3e Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:42:49 +0200 +Subject: [PATCH 20/22] 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 0b6b8e8523..7775ac66e4 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(nullptr); + } + ++/*! ++ \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/0021-Change-parsing-log-output-in-QGtk3Json-from-qCDebug-.patch b/0021-Change-parsing-log-output-in-QGtk3Json-from-qCDebug-.patch deleted file mode 100644 index 61923cd..0000000 --- a/0021-Change-parsing-log-output-in-QGtk3Json-from-qCDebug-.patch +++ /dev/null @@ -1,120 +0,0 @@ -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/0021-QGtk3Theme-Improve-fixed-font-delivery.patch b/0021-QGtk3Theme-Improve-fixed-font-delivery.patch new file mode 100644 index 0000000..9b6aec2 --- /dev/null +++ b/0021-QGtk3Theme-Improve-fixed-font-delivery.patch @@ -0,0 +1,84 @@ +From dfb10f3d3b689c172e53a94074b696d1eb9e6acd Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:44:11 +0200 +Subject: [PATCH 21/22] 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/0022-Document-QGtk3Interface.patch b/0022-Document-QGtk3Interface.patch deleted file mode 100644 index 852f86c..0000000 --- a/0022-Document-QGtk3Interface.patch +++ /dev/null @@ -1,400 +0,0 @@ -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/0022-QGtk3Theme-Do-not-default-Active-WindowText-to-butto.patch b/0022-QGtk3Theme-Do-not-default-Active-WindowText-to-butto.patch new file mode 100644 index 0000000..5bd62b2 --- /dev/null +++ b/0022-QGtk3Theme-Do-not-default-Active-WindowText-to-butto.patch @@ -0,0 +1,56 @@ +From 410808ea61b30e886830bc8bdc0db562eb0b56b4 Mon Sep 17 00:00:00 2001 +From: Jan Grulich +Date: Thu, 27 Jul 2023 12:44:31 +0200 +Subject: [PATCH 22/22] 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 7775ac66e4..fb4069ba3c 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/0023-Document-QGtk3Storage.patch b/0023-Document-QGtk3Storage.patch deleted file mode 100644 index 1ad8387..0000000 --- a/0023-Document-QGtk3Storage.patch +++ /dev/null @@ -1,366 +0,0 @@ -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 deleted file mode 100644 index 12a2fc3..0000000 --- a/0024-QGtk3Theme-Improve-fixed-font-delivery.patch +++ /dev/null @@ -1,84 +0,0 @@ -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 deleted file mode 100644 index 295c8fe..0000000 --- a/0025-QGtk3Theme-Do-not-default-Active-WindowText-to-butto.patch +++ /dev/null @@ -1,56 +0,0 @@ -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 2a53c3d..cce9b19 100644 --- a/qt5-qtbase.spec +++ b/qt5-qtbase.spec @@ -163,23 +163,20 @@ 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 +Patch158: 0009-Sync-and-assert-StandardPixmap-enums-in-QPlatformThe.patch +Patch159: 0010-QGtk3Theme-subscribe-to-theme-hint-changes.patch +Patch160: 0011-Gtk3Theme-set-XCURSOR_SIZE-and-XCURSOR_THEME-for-way.patch +Patch161: 0012-Gtk3-fix-stack-smashing-on-mismatch-between-bool-and.patch +Patch162: 0013-Re-implement-palette-standardPixmap-file-icons-fonts.patch +Patch163: 0014-GTK3-theme-simplify-code.patch +Patch164: 0015-Fix-checkbox-and-radiobutton-background-in-QGtk3Them.patch +Patch165: 0016-Cleanup-QGtk3Theme.patch +Patch166: 0017-Detect-appearance-by-colors-unless-GTK-theme-name-co.patch +Patch167: 0018-Change-parsing-log-output-in-QGtk3Json-from-qCDebug-.patch +Patch168: 0019-Document-QGtk3Interface.patch +Patch169: 0020-Document-QGtk3Storage.patch +Patch170: 0021-QGtk3Theme-Improve-fixed-font-delivery.patch +Patch171: 0022-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 @@ -488,9 +485,6 @@ Qt5 libraries used for drawing widgets and OpenGL items. %patch -P169 -p1 %patch -P170 -p1 %patch -P171 -p1 -%patch -P172 -p1 -%patch -P173 -p1 -%patch -P174 -p1 %endif %if 0%{?fedora} < 39