summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Sørvig <morten.sorvig@qt.io>2023-11-01 09:51:06 +0100
committerMorten Sørvig <morten.sorvig@qt.io>2023-11-15 21:52:24 +0100
commit6017695bfa98d8d9a6e5977ec80aafa1a4d3ee4d (patch)
treef8d3f0808cda281da02705b8e35c33491afa7bde
parentd6861926a12e3ee8eb3e1c8c90e9c6483b997d45 (diff)
Windows: Improve hidpi style drawing and metrics
Follow-up change from enabling DPI awareness, which caused some style elements (for instance check boxes) to be rendered incorrectly on non-primary displays, when there is a difference in DPI between displays. Use two approaches to get system metrics and themes: * Use forDpi() API variants and query at 96 DPI for style metrics, that are in device independent pixels. These are metrics which are used for layout calculations. * Get theme metrics at the target display DPI, and scale to device independent pixels when needed. This is used for OpenThemeData(), since this theme is used for drawing as well and needs to be in device pixels. One approach is not used any more: * Get metrics for the main display, and scale by the ratio between the main and target display. Change the theme cache to cache themes per window handle (HWND). This is required since OpenThemeData() returns theme data for a specific DPI, which means we can no longer use a shared cache. Clear the cache on theme change, DPI change, and when the window is destroyed. This handles cache invalidation when the window is moved to a different screen, and also when the DPI for a screen is changed. Move the cache implementation to QWindowsStyleSupport in QtGui, where it can be accessed by both the style and windows platform plugins. Task-number: QTBUG-110681 Change-Id: I5a4ff3a3753762bad8a51d08e51e8013bc7816a1 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Jøger Hansegård <joger.hansegard@qt.io> Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
-rw-r--r--src/gui/CMakeLists.txt2
-rw-r--r--src/gui/platform/windows/qwindowsthemecache.cpp79
-rw-r--r--src/gui/platform/windows/qwindowsthemecache_p.h33
-rw-r--r--src/plugins/platforms/windows/qwindowscontext.cpp2
-rw-r--r--src/plugins/platforms/windows/qwindowswindow.cpp5
-rw-r--r--src/plugins/styles/modernwindows/CMakeLists.txt1
-rw-r--r--src/plugins/styles/modernwindows/qwindowsvistastyle.cpp63
-rw-r--r--src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h4
-rw-r--r--src/widgets/styles/qwindowsstyle.cpp36
9 files changed, 142 insertions, 83 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index eb574fd9d6..9f2d3b4882 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -408,6 +408,7 @@ qt_internal_extend_target(Gui CONDITION WIN32
platform/windows/qwindowsguieventdispatcher.cpp platform/windows/qwindowsguieventdispatcher_p.h
platform/windows/qwindowsmimeconverter.h platform/windows/qwindowsmimeconverter.cpp
platform/windows/qwindowsnativeinterface.cpp
+ platform/windows/qwindowsthemecache.cpp platform/windows/qwindowsthemecache_p.h
rhi/qrhid3d11.cpp rhi/qrhid3d11_p.h
rhi/qrhid3dhelpers.cpp rhi/qrhid3dhelpers_p.h
rhi/vs_test_p.h
@@ -425,6 +426,7 @@ qt_internal_extend_target(Gui CONDITION WIN32
ole32
shell32
user32
+ uxtheme
PUBLIC_LIBRARIES
d3d11
dxgi
diff --git a/src/gui/platform/windows/qwindowsthemecache.cpp b/src/gui/platform/windows/qwindowsthemecache.cpp
new file mode 100644
index 0000000000..3cf72f4757
--- /dev/null
+++ b/src/gui/platform/windows/qwindowsthemecache.cpp
@@ -0,0 +1,79 @@
+// Copyright (C) 2023 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
+
+#include "qwindowsthemecache_p.h"
+#include <QtCore/qdebug.h>
+#include <QtCore/qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+// Theme names matching the QWindowsVistaStylePrivate::Theme enumeration.
+constexpr const wchar_t *themeNames[] = {
+ L"BUTTON", L"COMBOBOX", L"EDIT", L"HEADER", L"LISTVIEW",
+ L"MENU", L"PROGRESS", L"REBAR", L"SCROLLBAR", L"SPIN",
+ L"TAB", L"TASKDIALOG", L"TOOLBAR", L"TOOLTIP", L"TRACKBAR",
+ L"WINDOW", L"STATUS", L"TREEVIEW"
+};
+
+typedef std::array<HTHEME, std::size(themeNames)> ThemeArray;
+typedef QHash<HWND, ThemeArray> ThemesCache;
+Q_GLOBAL_STATIC(ThemesCache, themesCache);
+
+QString QWindowsThemeCache::themeName(int theme)
+{
+ return theme >= 0 && theme < int(std::size(themeNames))
+ ? QString::fromWCharArray(::themeNames[theme]) : QString();
+}
+
+HTHEME QWindowsThemeCache::createTheme(int theme, HWND hwnd)
+{
+ if (Q_UNLIKELY(theme < 0 || theme >= int(std::size(themeNames)) || !hwnd)) {
+ qWarning("Invalid parameters #%d, %p", theme, hwnd);
+ return nullptr;
+ }
+
+ // Get or create themes array for this window.
+ ThemesCache *cache = themesCache();
+ auto it = cache->find(hwnd);
+ if (it == cache->end())
+ it = cache->insert(hwnd, ThemeArray {});
+
+ // Get or create theme data
+ ThemeArray &themes = *it;
+ if (!themes[theme]) {
+ const wchar_t *name = themeNames[theme];
+ themes[theme] = OpenThemeData(hwnd, name);
+ if (Q_UNLIKELY(!themes[theme]))
+ qErrnoWarning("OpenThemeData() failed for theme %d (%s).",
+ theme, qPrintable(themeName(theme)));
+ }
+ return themes[theme];
+}
+
+static void clearThemes(ThemeArray &themes)
+{
+ for (auto &theme : themes) {
+ if (theme) {
+ CloseThemeData(theme);
+ theme = nullptr;
+ }
+ }
+}
+
+void QWindowsThemeCache::clearThemeCache(HWND hwnd)
+{
+ ThemesCache *cache = themesCache();
+ auto it = cache->find(hwnd);
+ if (it == cache->end())
+ return;
+ clearThemes(*it);
+}
+
+void QWindowsThemeCache::clearAllThemeCaches()
+{
+ ThemesCache *cache = themesCache();
+ for (auto &themeArray : *cache)
+ clearThemes(themeArray);
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/platform/windows/qwindowsthemecache_p.h b/src/gui/platform/windows/qwindowsthemecache_p.h
new file mode 100644
index 0000000000..bc065d47f3
--- /dev/null
+++ b/src/gui/platform/windows/qwindowsthemecache_p.h
@@ -0,0 +1,33 @@
+// Copyright (C) 2023 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 QWINDOWSTHEME_CACHE_P_H
+#define QWINDOWSTHEME_CACHE_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 <QtCore/qt_windows.h>
+#include <uxtheme.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QWindowsThemeCache
+{
+ Q_GUI_EXPORT QString themeName(int theme);
+ Q_GUI_EXPORT HTHEME createTheme(int theme, HWND hwnd);
+ Q_GUI_EXPORT void clearThemeCache(HWND hwnd);
+ Q_GUI_EXPORT void clearAllThemeCaches();
+}
+
+QT_END_NAMESPACE
+
+#endif // QWINDOWSTHEME_CACHE_P_H
diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp
index 3776fe63c1..6ce2014071 100644
--- a/src/plugins/platforms/windows/qwindowscontext.cpp
+++ b/src/plugins/platforms/windows/qwindowscontext.cpp
@@ -51,6 +51,7 @@
#include <QtCore/private/qsystemerror_p.h>
#include <QtGui/private/qwindowsguieventdispatcher_p.h>
+#include <QtGui/private/qwindowsthemecache_p.h>
#include <stdlib.h>
#include <stdio.h>
@@ -1275,6 +1276,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
QWindowSystemInterface::handleCloseEvent(platformWindow->window());
return true;
case QtWindows::ThemeChanged: {
+ QWindowsThemeCache::clearThemeCache(platformWindow->handle());
// Switch from Aero to Classic changes margins.
if (QWindowsTheme *theme = QWindowsTheme::instance())
theme->windowsThemeChanged(platformWindow->window());
diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
index c2782fc176..8dea0a0121 100644
--- a/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
@@ -27,6 +27,7 @@
#include <QtGui/qwindow.h>
#include <QtGui/qregion.h>
#include <QtGui/qopenglcontext.h>
+#include <QtGui/private/qwindowsthemecache_p.h>
#include <private/qwindow_p.h> // QWINDOWSIZE_MAX
#include <private/qguiapplication_p.h>
#include <private/qhighdpiscaling_p.h>
@@ -1524,6 +1525,7 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data)
QWindowsWindow::~QWindowsWindow()
{
setFlag(WithinDestroy);
+ QWindowsThemeCache::clearThemeCache(m_data.hwnd);
if (testFlag(TouchRegistered))
UnregisterTouchWindow(m_data.hwnd);
destroyWindow();
@@ -2002,6 +2004,9 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam)
const UINT dpi = HIWORD(wParam);
const qreal scale = dpiRelativeScale(dpi);
setSavedDpi(dpi);
+
+ QWindowsThemeCache::clearThemeCache(hwnd);
+
// Send screen change first, so that the new screen is set during any following resize
checkForScreenChanged(QWindowsWindow::FromDpiChange);
diff --git a/src/plugins/styles/modernwindows/CMakeLists.txt b/src/plugins/styles/modernwindows/CMakeLists.txt
index d462118b13..985bce3a2d 100644
--- a/src/plugins/styles/modernwindows/CMakeLists.txt
+++ b/src/plugins/styles/modernwindows/CMakeLists.txt
@@ -22,5 +22,6 @@ qt_internal_add_plugin(QModernWindowsStylePlugin
uxtheme
Qt::Core
Qt::Gui
+ Qt::GuiPrivate
Qt::WidgetsPrivate
)
diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp
index a441d4691c..e277298921 100644
--- a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp
+++ b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp
@@ -13,6 +13,7 @@
#include <qpa/qplatformnativeinterface.h>
#include <private/qapplication_p.h>
#include <private/qsystemlibrary_p.h>
+#include <private/qwindowsthemecache_p.h>
#include "qdrawutil.h" // for now
#include <qbackingstore.h>
@@ -51,23 +52,9 @@ static const int windowsRightBorder = 15; // right border on windows
# define CMDLGS_DISABLED 4
#endif
-/* \internal
- Checks if we should use Vista style , or if we should
- fall back to Windows style.
-*/
-// Theme names matching the QWindowsVistaStylePrivate::Theme enumeration.
-static const wchar_t *themeNames[QWindowsVistaStylePrivate::NThemes] =
-{
- L"BUTTON", L"COMBOBOX", L"EDIT", L"HEADER", L"LISTVIEW",
- L"MENU", L"PROGRESS", L"REBAR", L"SCROLLBAR", L"SPIN",
- L"TAB", L"TASKDIALOG", L"TOOLBAR", L"TOOLTIP", L"TRACKBAR",
- L"WINDOW", L"STATUS", L"TREEVIEW"
-};
-
// QWindowsVistaStylePrivate -------------------------------------------------------------------------
// Static initializations
HWND QWindowsVistaStylePrivate::m_vistaTreeViewHelper = nullptr;
-HTHEME QWindowsVistaStylePrivate::m_themes[NThemes];
bool QWindowsVistaStylePrivate::useVistaTheme = false;
Q_CONSTINIT QBasicAtomicInt QWindowsVistaStylePrivate::ref = Q_BASIC_ATOMIC_INITIALIZER(-1); // -1 based refcounting
@@ -184,7 +171,6 @@ void QWindowsVistaStylePrivate::init(bool force)
ref.ref();
useVista(true);
- std::fill(m_themes, m_themes + NThemes, nullptr);
}
/* \internal
@@ -223,25 +209,6 @@ bool QWindowsVistaStylePrivate::transitionsEnabled() const
return false;
}
-HTHEME QWindowsVistaStylePrivate::openThemeForPrimaryScreenDpi(HWND hwnd, const wchar_t *name)
-{
- // We want to call OpenThemeDataForDpi, but it won't link with MinGW (11.2.0), so we
- // dynamically load this.
- // Only try to initialize pOpenThemeDataForDpi once. If it fails, it will likely keep failing.
- static const auto pOpenThemeDataForDpi =
- reinterpret_cast<decltype(&::OpenThemeDataForDpi)>(
- QSystemLibrary::resolve(u"uxtheme"_s, "OpenThemeDataForDpi"));
-
- // If we have screens and the OpenThemeDataForDpi function then use it :).
- if (pOpenThemeDataForDpi && QGuiApplication::primaryScreen()) {
- const int dpi = qRound(QGuiApplication::primaryScreen()->handle()->logicalDpi().first);
- return pOpenThemeDataForDpi(hwnd, name, dpi);
- }
-
- // In case of any issues we fall back to use the plain/old OpenThemeData.
- return OpenThemeData(hwnd, name);
-}
-
int QWindowsVistaStylePrivate::pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *option, const QWidget *widget)
{
switch (pm) {
@@ -323,32 +290,15 @@ void QWindowsVistaStylePrivate::cleanupVistaTreeViewTheming()
*/
void QWindowsVistaStylePrivate::cleanupHandleMap()
{
- for (auto &theme : m_themes) {
- if (theme) {
- CloseThemeData(theme);
- theme = nullptr;
- }
- }
+ QWindowsThemeCache::clearAllThemeCaches();
QWindowsVistaStylePrivate::cleanupVistaTreeViewTheming();
}
HTHEME QWindowsVistaStylePrivate::createTheme(int theme, HWND hwnd)
{
- if (Q_UNLIKELY(theme < 0 || theme >= NThemes || !hwnd)) {
- qWarning("Invalid parameters #%d, %p", theme, hwnd);
- return nullptr;
- }
- if (!m_themes[theme]) {
- const wchar_t *name = themeNames[theme];
- if (theme == VistaTreeViewTheme && QWindowsVistaStylePrivate::initVistaTreeViewTheming())
- hwnd = QWindowsVistaStylePrivate::m_vistaTreeViewHelper;
- // Use dpi from primary screen in theme.
- m_themes[theme] = openThemeForPrimaryScreenDpi(hwnd, name);
- if (Q_UNLIKELY(!m_themes[theme]))
- qErrnoWarning("OpenThemeData() failed for theme %d (%s).",
- theme, qPrintable(themeName(theme)));
- }
- return m_themes[theme];
+ if (theme == VistaTreeViewTheme && QWindowsVistaStylePrivate::initVistaTreeViewTheming())
+ hwnd = QWindowsVistaStylePrivate::m_vistaTreeViewHelper;
+ return QWindowsThemeCache::createTheme(theme, hwnd);
}
QBackingStore *QWindowsVistaStylePrivate::backingStoreForWidget(const QWidget *widget)
@@ -373,8 +323,7 @@ HDC QWindowsVistaStylePrivate::hdcForWidgetBackingStore(const QWidget *widget)
QString QWindowsVistaStylePrivate::themeName(int theme)
{
- return theme >= 0 && theme < NThemes
- ? QString::fromWCharArray(themeNames[theme]) : QString();
+ return QWindowsThemeCache::themeName(theme);
}
bool QWindowsVistaStylePrivate::isItemViewDelegateLineEdit(const QWidget *widget)
diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h b/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h
index e8364678e0..053e98b68d 100644
--- a/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h
+++ b/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h
@@ -116,7 +116,6 @@ public:
static HTHEME createTheme(int theme, HWND hwnd);
static QString themeName(int theme);
- static inline bool hasTheme(int theme) { return theme >= 0 && theme < NThemes && m_themes[theme]; }
static bool isItemViewDelegateLineEdit(const QWidget *widget);
static int pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *option = nullptr, const QWidget *widget = nullptr);
static int fixedPixelMetric(QStyle::PixelMetric pm);
@@ -154,8 +153,6 @@ public:
QTime animationTime() const;
bool transitionsEnabled() const;
- static HTHEME openThemeForPrimaryScreenDpi(HWND hwnd, const wchar_t *name);
-
private:
static bool initVistaTreeViewTheming();
static void cleanupVistaTreeViewTheming();
@@ -172,7 +169,6 @@ private:
int bufferH = 0;
static HWND m_vistaTreeViewHelper;
- static HTHEME m_themes[NThemes];
};
QT_END_NAMESPACE
diff --git a/src/widgets/styles/qwindowsstyle.cpp b/src/widgets/styles/qwindowsstyle.cpp
index 8f3d9d6d9a..8fa44e5796 100644
--- a/src/widgets/styles/qwindowsstyle.cpp
+++ b/src/widgets/styles/qwindowsstyle.cpp
@@ -262,29 +262,33 @@ void QWindowsStyle::polish(QPalette &pal)
int QWindowsStylePrivate::pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *, const QWidget *widget)
{
#if defined(Q_OS_WIN)
+ // The pixel metrics are in device indepentent pixels;
+ // hardcode DPI to 1x 96 DPI.
+ const int dpi = 96;
+
switch (pm) {
case QStyle::PM_DockWidgetFrameWidth:
- return GetSystemMetrics(SM_CXFRAME);
+ return GetSystemMetricsForDpi(SM_CXFRAME, dpi);
case QStyle::PM_TitleBarHeight: {
const int resizeBorderThickness =
- GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
+ GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);
if (widget && (widget->windowType() == Qt::Tool))
- return GetSystemMetrics(SM_CYSMCAPTION) + resizeBorderThickness;
- return GetSystemMetrics(SM_CYCAPTION) + resizeBorderThickness;
+ return GetSystemMetricsForDpi(SM_CYSMCAPTION, dpi) + resizeBorderThickness;
+ return GetSystemMetricsForDpi(SM_CYCAPTION, dpi) + resizeBorderThickness;
}
case QStyle::PM_ScrollBarExtent:
{
NONCLIENTMETRICS ncm;
- ncm.cbSize = FIELD_OFFSET(NONCLIENTMETRICS, lfMessageFont) + sizeof(LOGFONT);
- if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0))
+ ncm.cbSize = sizeof(NONCLIENTMETRICS);
+ if (SystemParametersInfoForDpi(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0, dpi))
return qMax(ncm.iScrollHeight, ncm.iScrollWidth);
}
break;
case QStyle::PM_MdiSubWindowFrameWidth:
- return GetSystemMetrics(SM_CYFRAME);
+ return GetSystemMetricsForDpi(SM_CYFRAME, dpi);
default:
break;
@@ -356,22 +360,10 @@ static QScreen *screenOf(const QWidget *w)
}
// Calculate the overall scale factor to obtain Qt Device Independent
-// Pixels from a native Windows size. Divide by devicePixelRatio
-// and account for secondary screens with differing logical DPI.
+// Pixels from a native Windows size.
qreal QWindowsStylePrivate::nativeMetricScaleFactor(const QWidget *widget)
{
- qreal scale = QHighDpiScaling::factor(screenOf(widget));
- qreal result = qreal(1) / scale;
- if (QGuiApplicationPrivate::screen_list.size() > 1) {
- const QScreen *primaryScreen = QGuiApplication::primaryScreen();
- const QScreen *screen = screenOf(widget);
- if (screen != primaryScreen) {
- qreal primaryScale = QHighDpiScaling::factor(primaryScreen);
- if (!qFuzzyCompare(scale, primaryScale))
- result *= scale / primaryScale;
- }
- }
- return result;
+ return qreal(1) / QHighDpiScaling::factor(screenOf(widget));
}
/*!
@@ -381,7 +373,7 @@ int QWindowsStyle::pixelMetric(PixelMetric pm, const QStyleOption *opt, const QW
{
int ret = QWindowsStylePrivate::pixelMetricFromSystemDp(pm, opt, widget);
if (ret != QWindowsStylePrivate::InvalidMetric)
- return qRound(qreal(ret) * QWindowsStylePrivate::nativeMetricScaleFactor(widget));
+ return ret;
ret = QWindowsStylePrivate::fixedPixelMetric(pm);
if (ret != QWindowsStylePrivate::InvalidMetric)