diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index eb574fd9d61..9f2d3b48824 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 00000000000..3cf72f4757e --- /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 +#include + +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 ThemeArray; +typedef QHash 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 00000000000..bc065d47f30 --- /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 +#include + +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 3776fe63c17..6ce20140715 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -51,6 +51,7 @@ #include #include +#include #include #include @@ -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 c2782fc1761..8dea0a01213 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include // QWINDOWSIZE_MAX #include #include @@ -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 d462118b132..985bce3a2d6 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 a441d4691ce..e277298921b 100644 --- a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp +++ b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "qdrawutil.h" // for now #include @@ -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( - 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 e8364678e0e..053e98b68dd 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 8f3d9d6d9a1..8fa44e5796d 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)