ColorScheme: make QStyleHints::colorScheme writable for applications

Applications can request the color scheme to be either explicitly light
or dark, or to follow the system default by setting the scheme to
Qt::ColorScheme::Unknown.

Setting the color scheme will make the request to the QPlatformTheme
implementation, which can then use the appropriate implementation to
set the application's appearance so that both palette and window
decoration follow the requested color scheme. This should trigger
theme change and palette change events. A change to the effective
scheme should then call back into QStyleHintsPrivate::updateColorScheme,
which will emit the changed signal for the property.

Implement this for macOS (Cocoa), iOS, Android, and Windows.

On macOS, we have to use deprecated AppKit APIs; the replacements for
those APIs are not suitable for this use case. On iOS, the setting is
for each UIWindow, which we can update or initialize based on an
explicitly requested scheme.

On Android we can piggy-back on the logic added when dark theme support
was introduced in b4a9bb1f6a40e6d504c1f48f0d9ea2b70ab1a9f0.

On Windows, we have to fake a dark palette if the dark scheme is
requested on a light system, as there is no API to read a dark palette.
However, we also have to ignore any application preference if a high-
contrast accessibility theme is selected by the user (we report the
color scheme as unknown; there are both light and dark high-contrast
themes), and read the system palette using the GetSysColor API, which
is used for light mode. And we need to initialize windows with the
correct frame if the application explicitly overrides the system color
scheme.

Add an auto-test to the QApplication test, as that gives us the most
coverage to confirm that QStyleHints emits the changed signal, and that
Theme- and PaletteChange events are received by the toplevel widget
when the color scheme actually changes. This test has to be skipped
on platforms where we cannot set the color scheme programmatically.

Add the option to explicitly select the color scheme to the widget
gallery example, and default it to dark mode.

Fixes: QTBUG-124490
Change-Id: I7302993c0121284bf9d3b72e3149c6abbe6bd261
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Volker Hilsheimer 2024-04-19 16:08:18 +02:00
parent 4641945e45
commit 95d4e6baba
17 changed files with 341 additions and 34 deletions

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QApplication>
#include <QStyleHints>
#include "widgetgallery.h"

View File

@ -26,6 +26,7 @@
#include <QSpinBox>
#include <QStandardItemModel>
#include <QStyle>
#include <QStyleHints>
#include <QStyleFactory>
#include <QTextBrowser>
#include <QTreeView>
@ -135,6 +136,21 @@ WidgetGallery::WidgetGallery(QWidget *parent)
auto styleLabel = createWidget1<QLabel>(tr("&Style:"), "styleLabel");
styleLabel->setBuddy(styleComboBox);
auto colorSchemeComboBox = createWidget<QComboBox>("colorSchemeComboBox");
colorSchemeComboBox->addItem(tr("Auto"));
colorSchemeComboBox->addItem(tr("Light"));
colorSchemeComboBox->addItem(tr("Dark"));
// override the color scheme to dark
qApp->styleHints()->setColorScheme(Qt::ColorScheme::Dark);
colorSchemeComboBox->setCurrentIndex(2);
auto colorSchemeLabel = createWidget1<QLabel>(tr("&Color Scheme:"), "colorSchemeLabel");
colorSchemeLabel->setBuddy(colorSchemeComboBox);
connect(colorSchemeComboBox, &QComboBox::currentIndexChanged, this, [](int index){
QGuiApplication::styleHints()->setColorScheme(static_cast<Qt::ColorScheme>(index));
});
auto helpLabel = createWidget1<QLabel>(tr("Press F1 over a widget to see Documentation"), "helpLabel");
auto disableWidgetsCheckBox = createWidget1<QCheckBox>(tr("&Disable widgets"), "disableWidgetsCheckBox");
@ -156,8 +172,12 @@ WidgetGallery::WidgetGallery(QWidget *parent)
simpleInputWidgetsGroupBox, &QWidget::setDisabled);
auto topLayout = new QHBoxLayout;
topLayout->addWidget(styleLabel);
topLayout->addWidget(styleComboBox);
auto appearanceLayout = new QGridLayout;
appearanceLayout->addWidget(styleLabel, 0, 0);
appearanceLayout->addWidget(styleComboBox, 0, 1);
appearanceLayout->addWidget(colorSchemeLabel, 1, 0);
appearanceLayout->addWidget(colorSchemeComboBox, 1, 1);
topLayout->addLayout(appearanceLayout);
topLayout->addStretch(1);
topLayout->addWidget(helpLabel);
topLayout->addStretch(1);

View File

@ -447,6 +447,11 @@ Qt::ColorScheme QPlatformTheme::colorScheme() const
return Qt::ColorScheme::Unknown;
}
void QPlatformTheme::requestColorScheme(Qt::ColorScheme scheme)
{
Q_UNUSED(scheme);
}
const QPalette *QPlatformTheme::palette(Palette type) const
{
Q_D(const QPlatformTheme);

View File

@ -295,6 +295,7 @@ public:
#endif
virtual Qt::ColorScheme colorScheme() const;
virtual void requestColorScheme(Qt::ColorScheme scheme);
virtual const QPalette *palette(Palette type = SystemPalette) const;

View File

@ -123,8 +123,29 @@ int QStyleHints::touchDoubleTapDistance() const
/*!
\property QStyleHints::colorScheme
\brief the color scheme of the platform theme.
\sa Qt::ColorScheme
\brief the color scheme used by the application.
By default, this follows the system's default color scheme (also known as appearance),
and changes when the system color scheme changes (e.g. during dusk or dawn).
Setting the color scheme to an explicit value will override the system setting and
ignore any changes to the system's color scheme. However, doing so is a hint to the
system, and overriding the color scheme is not supported on all platforms.
Resetting this property, or setting it to \l{Qt::ColorScheme::Unknown}, will remove
the override and make the application follow the system default again. The property
value will change to the color scheme the system currently has.
When this property changes, Qt will read the system palette and update the default
palette, but won't overwrite palette entries that have been explicitly set by the
application. When the colorSchemeChange() signal gets emitted, the old palette is
still in effect.
Application-specific colors should be selected to work well with the effective
palette, taking the current color scheme into account. To update application-
specific colors when the effective palette changes, handle
\l{QEvent::}{PaletteChange} or \l{QEvent::}{ApplicationPaletteChange} events.
\sa Qt::ColorScheme, QGuiApplication::palette(), QEvent::PaletteChange
\since 6.5
*/
Qt::ColorScheme QStyleHints::colorScheme() const
@ -133,6 +154,30 @@ Qt::ColorScheme QStyleHints::colorScheme() const
return d->colorScheme();
}
/*!
\since 6.8
Sets the color scheme used by the application to an explicit \a scheme, or
revert to the system's current color scheme if \a scheme is Qt::ColorScheme::Unknown.
*/
void QStyleHints::setColorScheme(Qt::ColorScheme scheme)
{
if (!QCoreApplication::instance()) {
qWarning("Must construct a QGuiApplication before accessing a platform theme hint.");
return;
}
if (QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme())
theme->requestColorScheme(scheme);
}
/*!
\fn void QStyleHints::unsetColorScheme()
\since 6.8
Restores the color scheme to the system's current color scheme.
*/
/*!
Sets the \a mousePressAndHoldInterval.
\internal

View File

@ -52,7 +52,8 @@ class Q_GUI_EXPORT QStyleHints : public QObject
Q_PROPERTY(int mouseDoubleClickDistance READ mouseDoubleClickDistance STORED false CONSTANT
FINAL)
Q_PROPERTY(int touchDoubleTapDistance READ touchDoubleTapDistance STORED false CONSTANT FINAL)
Q_PROPERTY(Qt::ColorScheme colorScheme READ colorScheme NOTIFY colorSchemeChanged FINAL)
Q_PROPERTY(Qt::ColorScheme colorScheme READ colorScheme WRITE setColorScheme
RESET unsetColorScheme NOTIFY colorSchemeChanged FINAL)
public:
void setMouseDoubleClickInterval(int mouseDoubleClickInterval);
@ -94,6 +95,8 @@ public:
void setMouseQuickSelectionThreshold(int threshold);
int mouseQuickSelectionThreshold() const;
Qt::ColorScheme colorScheme() const;
void setColorScheme(Qt::ColorScheme scheme);
void unsetColorScheme() { setColorScheme(Qt::ColorScheme::Unknown); }
Q_SIGNALS:
void cursorFlashTimeChanged(int cursorFlashTime);

View File

@ -161,7 +161,10 @@ QJsonObject AndroidStyle::loadStyleData()
if (!stylePath.isEmpty() && !stylePath.endsWith(slashChar))
stylePath += slashChar;
if (QAndroidPlatformIntegration::colorScheme() == Qt::ColorScheme::Dark)
const Qt::ColorScheme colorScheme = QAndroidPlatformTheme::instance()
? QAndroidPlatformTheme::instance()->colorScheme()
: QAndroidPlatformIntegration::colorScheme();
if (colorScheme == Qt::ColorScheme::Dark)
stylePath += "darkUiMode/"_L1;
Q_ASSERT(!stylePath.isEmpty());
@ -423,9 +426,19 @@ void QAndroidPlatformTheme::showPlatformMenuBar()
Qt::ColorScheme QAndroidPlatformTheme::colorScheme() const
{
if (m_colorSchemeOverride != Qt::ColorScheme::Unknown)
return m_colorSchemeOverride;
return QAndroidPlatformIntegration::colorScheme();
}
void QAndroidPlatformTheme::requestColorScheme(Qt::ColorScheme scheme)
{
m_colorSchemeOverride = scheme;
QMetaObject::invokeMethod(qGuiApp, [this]{
updateColorScheme();
});
}
static inline int paletteType(QPlatformTheme::Palette type)
{
switch (type) {

View File

@ -40,6 +40,8 @@ public:
QPlatformMenuItem *createPlatformMenuItem() const override;
void showPlatformMenuBar() override;
Qt::ColorScheme colorScheme() const override;
void requestColorScheme(Qt::ColorScheme scheme) override;
const QPalette *palette(Palette type = SystemPalette) const override;
const QFont *font(Font type = SystemFont) const override;
QIconEngine *createIconEngine(const QString &iconName) const override;
@ -57,6 +59,7 @@ private:
std::shared_ptr<AndroidStyle> m_androidStyleData;
QPalette m_defaultPalette;
QFont m_systemFont;
Qt::ColorScheme m_colorSchemeOverride = Qt::ColorScheme::Unknown;
};
QT_END_NAMESPACE

View File

@ -44,6 +44,7 @@ public:
static const char *name;
void requestColorScheme(Qt::ColorScheme scheme) override;
void handleSystemThemeChange();
#ifndef QT_NO_SHORTCUT

View File

@ -478,6 +478,23 @@ Qt::ColorScheme QCocoaTheme::colorScheme() const
return m_colorScheme;
}
void QCocoaTheme::requestColorScheme(Qt::ColorScheme scheme)
{
NSAppearance *appearance = nil;
switch (scheme) {
case Qt::ColorScheme::Dark:
appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
break;
case Qt::ColorScheme::Light:
appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
break;
case Qt::ColorScheme::Unknown:
break;
}
if (appearance != NSApp.effectiveAppearance)
NSApplication.sharedApplication.appearance = appearance;
}
/*
Update the theme's color scheme based on the current appearance.

View File

@ -4,6 +4,8 @@
#ifndef QIOSTHEME_H
#define QIOSTHEME_H
#import <UIKit/UIKit.h>
#include <QtCore/QHash>
#include <QtGui/QPalette>
#include <qpa/qplatformtheme.h>
@ -22,6 +24,7 @@ public:
QVariant themeHint(ThemeHint hint) const override;
Qt::ColorScheme colorScheme() const override;
void requestColorScheme(Qt::ColorScheme scheme) override;
#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
QPlatformMenuItem* createPlatformMenuItem() const override;
@ -37,9 +40,11 @@ public:
static const char *name;
static void initializeSystemPalette();
static void applyTheme(UIWindow *window);
private:
static QPalette s_systemPalette;
static inline Qt::ColorScheme s_colorSchemeOverride = Qt::ColorScheme::Unknown;
QMacNotificationObserver m_contentSizeCategoryObserver;
};

View File

@ -154,6 +154,9 @@ Qt::ColorScheme QIOSTheme::colorScheme() const
// the OS reports itself as always being in dark mode.
return Qt::ColorScheme::Dark;
#else
if (s_colorSchemeOverride != Qt::ColorScheme::Unknown)
return s_colorSchemeOverride;
// Set the appearance based on the QUIWindow
// Fallback to the UIScreen if no window is created yet
UIUserInterfaceStyle appearance = UIScreen.mainScreen.traitCollection.userInterfaceStyle;
@ -171,6 +174,38 @@ Qt::ColorScheme QIOSTheme::colorScheme() const
#endif
}
void QIOSTheme::requestColorScheme(Qt::ColorScheme scheme)
{
#if defined(Q_OS_VISIONOS)
Q_UNUSED(scheme);
#else
s_colorSchemeOverride = scheme;
const NSArray<UIWindow *> *windows = qt_apple_sharedApplication().windows;
for (UIWindow *window in windows) {
// don't apply a theme to windows we don't own
if (qt_objc_cast<QUIWindow*>(window))
applyTheme(window);
}
#endif
}
void QIOSTheme::applyTheme(UIWindow *window)
{
const UIUserInterfaceStyle style = []{
switch (s_colorSchemeOverride) {
case Qt::ColorScheme::Dark:
return UIUserInterfaceStyleDark;
case Qt::ColorScheme::Light:
return UIUserInterfaceStyleLight;
case Qt::ColorScheme::Unknown:
return UIUserInterfaceStyleUnspecified;
}
}();
window.overrideUserInterfaceStyle = style;
}
const QFont *QIOSTheme::font(Font type) const
{
const auto *platformIntegration = QGuiApplicationPrivate::platformIntegration();

View File

@ -22,6 +22,15 @@
return self;
}
- (instancetype)initWithWindowScene:(UIWindowScene *)windowScene
{
if ((self = [super initWithWindowScene:windowScene]))
self->_sendingEvent = NO;
QIOSTheme::applyTheme(self);
return self;
}
- (void)sendEvent:(UIEvent *)event
{
QScopedValueRollback<BOOL> sendingEvent(self->_sendingEvent, YES);

View File

@ -301,6 +301,9 @@ void QWindowsTheme::populateLightSystemBasePalette(QPalette &result)
void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result)
{
QColor foreground, background,
accent, accentDark, accentDarker, accentDarkest,
accentLight, accentLighter, accentLightest;
#if QT_CONFIG(cpp_winrt)
using namespace winrt::Windows::UI::ViewManagement;
const auto settings = UISettings();
@ -308,32 +311,37 @@ void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result)
// We have to craft a palette from these colors. The settings.UIElementColor(UIElementType) API
// returns the old system colors, not the dark mode colors. If the background is black (which it
// usually), then override it with a dark gray instead so that we can go up and down the lightness.
const QColor foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground));
const QColor background = [&settings]() -> QColor {
auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background));
if (systemBackground == Qt::black)
systemBackground = QColor(0x1E, 0x1E, 0x1E);
return systemBackground;
}();
if (QWindowsTheme::queryColorScheme() == Qt::ColorScheme::Dark) {
// the system is actually running in dark mode, so UISettings will give us dark colors
foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground));
background = [&settings]() -> QColor {
auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background));
if (systemBackground == Qt::black)
systemBackground = QColor(0x1E, 0x1E, 0x1E);
return systemBackground;
}();
const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
const QColor accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1));
const QColor accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2));
const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
const QColor accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2));
const QColor accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3));
#else
const QColor foreground = Qt::white;
const QColor background = QColor(0x1E, 0x1E, 0x1E);
const QColor accent = qt_accentColor(AccentColorNormal);
const QColor accentDark = accent.darker(120);
const QColor accentDarker = accentDark.darker(120);
const QColor accentDarkest = accentDarker.darker(120);
const QColor accentLight = accent.lighter(120);
const QColor accentLighter = accentLight.lighter(120);
const QColor accentLightest = accentLighter.lighter(120);
accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1));
accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2));
accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2));
accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3));
} else
#endif
{
// If the system is running in light mode, then we need to make up our own dark palette
foreground = Qt::white;
background = QColor(0x1E, 0x1E, 0x1E);
accent = qt_accentColor(AccentColorNormal);
accentDark = accent.darker(120);
accentDarker = accentDark.darker(120);
accentDarkest = accentDarker.darker(120);
accentLight = accent.lighter(120);
accentLighter = accentLight.lighter(120);
accentLightest = accentLighter.lighter(120);
}
const QColor linkColor = accent;
const QColor buttonColor = background.lighter(200);
@ -549,13 +557,25 @@ Qt::ColorScheme QWindowsTheme::effectiveColorScheme()
{
if (queryHighContrast())
return Qt::ColorScheme::Unknown;
return s_colorScheme;
if (s_colorSchemeOverride != Qt::ColorScheme::Unknown)
return s_colorSchemeOverride;
if (s_colorScheme != Qt::ColorScheme::Unknown)
return s_colorScheme;
return queryColorScheme();
}
void QWindowsTheme::requestColorScheme(Qt::ColorScheme scheme)
{
s_colorSchemeOverride = scheme;
handleSettingsChanged();
}
void QWindowsTheme::handleSettingsChanged()
{
const auto newColorScheme = QWindowsTheme::queryColorScheme();
const bool colorSchemeChanged = newColorScheme != QWindowsTheme::s_colorScheme;
const auto oldColorScheme = s_colorScheme;
s_colorScheme = Qt::ColorScheme::Unknown; // make effectiveColorScheme() query registry
const auto newColorScheme = effectiveColorScheme();
const bool colorSchemeChanged = newColorScheme != oldColorScheme;
s_colorScheme = newColorScheme;
auto integration = QWindowsIntegration::instance();
integration->updateApplicationBadge();

View File

@ -32,6 +32,8 @@ public:
QVariant themeHint(ThemeHint) const override;
Qt::ColorScheme colorScheme() const override;
void requestColorScheme(Qt::ColorScheme scheme) override;
Qt::ColorScheme requestedColorScheme() const { return s_colorSchemeOverride; }
static void handleSettingsChanged();
@ -79,6 +81,8 @@ private:
static QWindowsTheme *m_instance;
static inline Qt::ColorScheme s_colorScheme = Qt::ColorScheme::Unknown;
static inline Qt::ColorScheme s_colorSchemeOverride = Qt::ColorScheme::Unknown;
QPalette *m_palettes[NPalettes];
QFont *m_fonts[NFonts];
QList<QSize> m_fileIconSizes;

View File

@ -850,10 +850,17 @@ static inline bool shouldApplyDarkFrame(const QWindow *w)
{
if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
return false;
// the application has explicitly opted out of dark frames
// the user of the application has explicitly opted out of dark frames
if (!QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames))
return false;
// the application explicitly overrides the color scheme
if (const auto requestedColorScheme = QWindowsTheme::instance()->requestedColorScheme();
requestedColorScheme != Qt::ColorScheme::Unknown) {
return requestedColorScheme == Qt::ColorScheme::Dark;
}
// if the application supports a dark border, and the palette is dark (window background color
// is darker than the text), then turn dark-border support on, otherwise use a light border.
auto *dWindow = QWindowPrivate::get(const_cast<QWindow*>(w));

View File

@ -23,6 +23,7 @@
#include <QtGui/QFontDatabase>
#include <QtGui/QClipboard>
#include <QtGui/QStyleHints>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMessageBox>
@ -119,6 +120,7 @@ private slots:
void style();
void applicationPalettePolish();
void setColorScheme();
void allWidgets();
void topLevelWidgets();
@ -2051,6 +2053,122 @@ void tst_QApplication::applicationPalettePolish()
}
}
void tst_QApplication::setColorScheme()
{
int argc = 1;
QApplication app(argc, &argv0);
if (QStringList{"minimal", "offscreen", "wayland", "xcb", "wasm", "webassembly"}
.contains(QGuiApplication::platformName(), Qt::CaseInsensitive)) {
QSKIP("Setting the colorScheme is not implemented on this platform.");
}
qDebug() << "Testing setColorScheme on platform" << QGuiApplication::platformName();
if (QByteArrayView(app.style()->metaObject()->className()) == "QWindowsVistaStyle")
QSKIP("Setting the colorScheme is not supported with the Windows Vista style.");
const Qt::ColorScheme defaultColorScheme = QApplication::styleHints()->colorScheme();
// if we implement setColorScheme, then we must be able to read it
QVERIFY(defaultColorScheme != Qt::ColorScheme::Unknown);
const Qt::ColorScheme newColorScheme = defaultColorScheme == Qt::ColorScheme::Light
? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
class TopLevelWidget : public QWidget
{
QList<QEvent::Type> events;
public:
TopLevelWidget()
{
setObjectName("colorScheme TopLevelWidget");
}
void clearEvents()
{
events.clear();
}
qsizetype eventCount(QEvent::Type type) const
{
return events.count(type);
}
protected:
bool event(QEvent *event) override
{
switch (event->type()) {
case QEvent::ApplicationPaletteChange:
case QEvent::PaletteChange:
case QEvent::ThemeChange:
events << event->type();
break;
default:
break;
}
return QWidget::event(event);
}
} topLevelWidget;
topLevelWidget.show();
QVERIFY(QTest::qWaitForWindowExposed(&topLevelWidget));
QSignalSpy colorSchemeChangedSpy(app.styleHints(), &QStyleHints::colorSchemeChanged);
// always start with a clean list
topLevelWidget.clearEvents();
const QPalette defaultPalette = topLevelWidget.palette();
bool oldPaletteWhenSchemeChanged = false;
connect(app.styleHints(), &QStyleHints::colorSchemeChanged, this,
[defaultPalette, &topLevelWidget, &oldPaletteWhenSchemeChanged]{
oldPaletteWhenSchemeChanged = defaultPalette == topLevelWidget.palette();
});
app.styleHints()->setColorScheme(newColorScheme);
QTRY_COMPARE(colorSchemeChangedSpy.count(), 1);
// We have not yet updated the palette when we emit the colorSchemeChanged
// signal, so the toplevel widget should still use the previous palette
QVERIFY(oldPaletteWhenSchemeChanged);
QCOMPARE(topLevelWidget.eventCount(QEvent::ThemeChange), 1);
// We can't guarantee that there is only one ApplicationPaletteChange,
// and they might arrive asynchronously in response to ThemeChange
QTRY_COMPARE_GE(topLevelWidget.eventCount(QEvent::ApplicationPaletteChange), 1);
// But we can guarantee a single PaletteChange event for the widget
QCOMPARE(topLevelWidget.eventCount(QEvent::PaletteChange), 1);
// The palette should have changed
QCOMPARE_NE(topLevelWidget.palette(), defaultPalette);
topLevelWidget.clearEvents();
colorSchemeChangedSpy.clear();
// verify that a widget shown with a color scheme override in place respect that
QWidget newWidget;
newWidget.show();
QVERIFY(QTest::qWaitForWindowExposed(&newWidget));
QCOMPARE(newWidget.palette(), topLevelWidget.palette());
// Setting to Unknown should follow the system preference again
app.styleHints()->setColorScheme(Qt::ColorScheme::Unknown);
QTRY_COMPARE(colorSchemeChangedSpy.count(), 1);
QCOMPARE(app.styleHints()->colorScheme(), defaultColorScheme);
QTRY_COMPARE(topLevelWidget.eventCount(QEvent::PaletteChange), 1);
auto debugPalette = qScopeGuard([defaultPalette, &topLevelWidget]{
qDebug() << "Inspecting palettes for differences";
const QPalette palette = topLevelWidget.palette();
for (int g = 0; g < QPalette::NColorGroups; ++g) {
for (int r = 0; r < QPalette::NColorRoles; ++r) {
const auto group = static_cast<QPalette::ColorGroup>(g);
const auto role = static_cast<QPalette::ColorRole>(r);
qDebug() << "...Checking" << group << role;
const auto actualBrush = palette.brush(group, role);
const auto expectedBrush = defaultPalette.brush(group, role);
if (palette.brush(group, role) != defaultPalette.brush(group, role))
qWarning() << "...Difference in" << group << role << actualBrush << expectedBrush;
}
}
});
QCOMPARE(topLevelWidget.palette(), defaultPalette);
debugPalette.dismiss();
}
void tst_QApplication::allWidgets()
{
int argc = 1;