Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowstheme.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtCore/qt_windows.h>
5
7#include "qwindowsmenu.h"
12#if QT_CONFIG(systemtrayicon)
13# include "qwindowssystemtrayicon.h"
14#endif
15#include "qwindowsscreen.h"
16#include "qwindowswindow.h"
18#include <commctrl.h>
19#include <objbase.h>
20#include <commoncontrols.h>
21#include <shellapi.h>
22
23#include <QtCore/qapplicationstatic.h>
24#include <QtCore/qvariant.h>
25#include <QtCore/qcoreapplication.h>
26#include <QtCore/qdebug.h>
27#include <QtCore/qsysinfo.h>
28#include <QtCore/qcache.h>
29#include <QtCore/qthread.h>
30#include <QtCore/qqueue.h>
31#include <QtCore/qmutex.h>
32#include <QtCore/qwaitcondition.h>
33#include <QtCore/qoperatingsystemversion.h>
34#include <QtGui/qcolor.h>
35#include <QtGui/qpalette.h>
36#include <QtGui/qguiapplication.h>
37#include <QtGui/qpainter.h>
38#include <QtGui/qpixmapcache.h>
39#include <qpa/qwindowsysteminterface.h>
40#include <QtGui/private/qabstractfileiconengine_p.h>
41#include <QtGui/private/qwindowsfontdatabase_p.h>
42#include <private/qhighdpiscaling_p.h>
43#include <private/qwinregistry_p.h>
44#include <QtCore/private/qfunctions_win_p.h>
45#include <QtGui/private/qwindowsthemecache_p.h>
46
47#include <algorithm>
48
49#if QT_CONFIG(cpp_winrt)
50# include <QtCore/private/qt_winrtbase_p.h>
51
52# include <winrt/Windows.UI.ViewManagement.h>
53#endif // QT_CONFIG(cpp_winrt)
54
55QT_BEGIN_NAMESPACE
56
57using namespace Qt::StringLiterals;
58
59static inline bool booleanSystemParametersInfo(UINT what, bool defaultValue)
60{
61 BOOL result;
62 if (SystemParametersInfo(what, 0, &result, 0))
63 return result != FALSE;
64 return defaultValue;
65}
66
67static inline DWORD dWordSystemParametersInfo(UINT what, DWORD defaultValue)
68{
69 DWORD result;
70 if (SystemParametersInfo(what, 0, &result, 0))
71 return result;
72 return defaultValue;
73}
74
75static inline QColor mixColors(const QColor &c1, const QColor &c2)
76{
77 return {(c1.red() + c2.red()) / 2,
78 (c1.green() + c2.green()) / 2,
79 (c1.blue() + c2.blue()) / 2};
80}
81
91
92#if QT_CONFIG(cpp_winrt)
93static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color)
94{
95 return QColor(color.R, color.G, color.B, color.A);
96}
97#endif
98
99static inline QColor getSysColor(int index)
100{
101 COLORREF cr = GetSysColor(index);
102 return QColor(GetRValue(cr), GetGValue(cr), GetBValue(cr));
103}
104
105[[maybe_unused]] [[nodiscard]] static inline QColor qt_accentColor(AccentColorLevel level)
106{
107 QColor accent;
108 QColor accentLight;
109 QColor accentLighter;
110 QColor accentLightest;
111 QColor accentDark;
112 QColor accentDarker;
113 QColor accentDarkest;
114
115#if QT_CONFIG(cpp_winrt)
116 QT_TRY {
117 using namespace winrt::Windows::UI::ViewManagement;
118 const auto settings = UISettings();
119 accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
120 accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
121 accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2));
122 accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3));
123 accentDark = getSysColor(settings.GetColorValue(UIColorType::AccentDark1));
124 accentDarker = getSysColor(settings.GetColorValue(UIColorType::AccentDark2));
125 accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
126 } QT_CATCH(...) {
127 // pass, just fall back to WIN32 API implementation
128 }
129#endif
130
131 if (!accent.isValid()) {
132 accent = []()->QColor {
133 // MS uses the aquatic contrast theme as an example in the URL below:
134 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor#windows-1011-system-colors
135 if (QWindowsTheme::queryHighContrast())
136 return getSysColor(COLOR_HIGHLIGHT);
137 const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)");
138 if (!registry.isValid())
139 return {};
140 const QVariant value = registry.value(L"AccentColor");
141 if (!value.isValid())
142 return {};
143 // The retrieved value is in the #AABBGGRR format, we need to
144 // convert it to the #AARRGGBB format which Qt expects.
145 const QColor abgr = QColor::fromRgba(qvariant_cast<DWORD>(value));
146 if (!abgr.isValid())
147 return {};
148 return QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha());
149 }();
150 if (!accent.isValid())
151 return {};
152 accentLight = accent.lighter(120);
153 accentLighter = accentLight.lighter(120);
154 accentLightest = accentLighter.lighter(120);
155 accentDark = accent.darker(120);
156 accentDarker = accentDark.darker(120);
157 accentDarkest = accentDarker.darker(120);
158 }
159
160 switch (level) {
162 return accentDarkest;
164 return accentDarker;
165 case AccentColorDark:
166 return accentDark;
167 case AccentColorLight:
168 return accentLight;
170 return accentLighter;
172 return accentLightest;
173 default:
174 return accent;
175 }
176}
177
178// QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system
179// models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the
180// behavior by running it in a thread.
182{
183public:
184 struct Task
185 {
186 Task(const QString &fn, DWORD a, UINT f)
188 {}
191 {
193 hIcon = 0;
194 }
195 // Request
198 const UINT flags;
199 // Result
201 int iIcon = -1;
202 bool finished = false;
203 bool resultValid() const { return hIcon != 0 && iIcon >= 0 && finished; }
204 };
205
207 : QThread()
208 {
209 start();
210 }
211
213 {
214 cancel();
215 wait();
216 }
217
219 {
220 QMutexLocker l(&m_waitForTaskMutex);
221 while (!isInterruptionRequested()) {
222 if (!m_taskQueue.isEmpty())
223 return m_taskQueue.dequeue();
224 m_waitForTaskCondition.wait(&m_waitForTaskMutex);
225 }
226 return nullptr;
227 }
228
230 {
231 QComHelper comHelper(COINIT_MULTITHREADED);
232
233 while (!isInterruptionRequested()) {
234 auto task = getNextTask();
235 if (task) {
236 SHFILEINFO info;
237 const bool result = SHGetFileInfo(reinterpret_cast<const wchar_t *>(task->fileName.utf16()),
238 task->attributes, &info, sizeof(SHFILEINFO),
239 task->flags);
240 if (result) {
241 task->hIcon = info.hIcon;
242 task->iIcon = info.iIcon;
243 }
244 task->finished = true;
245 m_doneCondition.wakeAll();
246 }
247 }
248 }
249
250 void runWithParams(const QSharedPointer<Task> &task,
251 std::chrono::milliseconds timeout = std::chrono::milliseconds(5000))
252 {
253 {
254 QMutexLocker l(&m_waitForTaskMutex);
255 m_taskQueue.enqueue(task);
256 m_waitForTaskCondition.wakeAll();
257 }
258
259 QMutexLocker doneLocker(&m_doneMutex);
260 while (!task->finished && !isInterruptionRequested()) {
261 if (!m_doneCondition.wait(&m_doneMutex, QDeadlineTimer(timeout)))
262 return;
263 }
264 }
265
266 void cancel()
267 {
268 requestInterruption();
269 m_doneCondition.wakeAll();
270 m_waitForTaskCondition.wakeAll();
271 }
272
273private:
274 QQueue<QSharedPointer<Task>> m_taskQueue;
275 QWaitCondition m_doneCondition;
276 QWaitCondition m_waitForTaskCondition;
277 QMutex m_doneMutex;
278 QMutex m_waitForTaskMutex;
279};
280Q_APPLICATION_STATIC(QShGetFileInfoThread, s_shGetFileInfoThread)
281
282// from QStyle::standardPalette
284{
285 QColor backgroundColor(0xd4, 0xd0, 0xc8); // win 2000 grey
286 QColor lightColor(backgroundColor.lighter());
287 QColor darkColor(backgroundColor.darker());
288 const QBrush darkBrush(darkColor);
289 QColor midColor(Qt::gray);
290 QPalette palette(Qt::black, backgroundColor, lightColor, darkColor,
291 midColor, Qt::black, Qt::white);
292 palette.setBrush(QPalette::Disabled, QPalette::WindowText, darkBrush);
293 palette.setBrush(QPalette::Disabled, QPalette::Text, darkBrush);
294 palette.setBrush(QPalette::Disabled, QPalette::ButtonText, darkBrush);
295 palette.setBrush(QPalette::Disabled, QPalette::Base, QBrush(backgroundColor));
296 return palette;
297}
298
299static QColor placeHolderColor(QColor textColor)
300{
301 textColor.setAlpha(128);
302 return textColor;
303}
304
305/*
306 This is used when the theme is light mode, and when the theme is dark but the
307 application doesn't support dark mode. In the latter case, we need to check.
308*/
309void QWindowsTheme::populateLightSystemBasePalette(QPalette &result)
310{
311 const bool highContrastEnabled = queryHighContrast();
312
313 const QColor background = getSysColor(COLOR_BTNFACE);
314 const QColor textColor = getSysColor(COLOR_WINDOWTEXT);
315
316 const QColor accent = qt_accentColor(AccentColorNormal);
317 const QColor accentDark = qt_accentColor(AccentColorDark);
318 const QColor accentDarker = qt_accentColor(AccentColorDarker);
319 const QColor accentDarkest = qt_accentColor(AccentColorDarkest);
320
321 const QColor linkColor = highContrastEnabled ? getSysColor(COLOR_HOTLIGHT) : accentDarker;
322 const QColor linkColorVisited = highContrastEnabled ? linkColor.darker(120) : accentDarkest;
323 const QColor btnFace = background;
324 const QColor btnHighlight = getSysColor(COLOR_BTNHIGHLIGHT);
325
326 result.setColor(QPalette::Highlight, accent);
327 result.setColor(QPalette::WindowText, getSysColor(COLOR_WINDOWTEXT));
328 result.setColor(QPalette::Button, btnFace);
329 result.setColor(QPalette::Light, btnHighlight);
330 result.setColor(QPalette::Dark, getSysColor(COLOR_BTNSHADOW));
331 result.setColor(QPalette::Mid, result.button().color().darker(150));
332 result.setColor(QPalette::Text, textColor);
333 result.setColor(QPalette::PlaceholderText, placeHolderColor(textColor));
334 result.setColor(QPalette::BrightText, btnHighlight);
335 result.setColor(QPalette::Base, getSysColor(COLOR_WINDOW));
336 result.setColor(QPalette::Window, highContrastEnabled ? getSysColor(COLOR_WINDOW) : btnFace);
337 result.setColor(QPalette::ButtonText, getSysColor(COLOR_BTNTEXT));
338 result.setColor(QPalette::Midlight, getSysColor(COLOR_3DLIGHT));
339 result.setColor(QPalette::Shadow, getSysColor(COLOR_3DDKSHADOW));
340 result.setColor(QPalette::HighlightedText, getSysColor(COLOR_HIGHLIGHTTEXT));
341 result.setColor(QPalette::Accent, accentDark); // default accent color for controls on Light mode is AccentDark1
342
343 result.setColor(QPalette::Link, linkColor);
344 result.setColor(QPalette::LinkVisited, linkColorVisited);
345 result.setColor(QPalette::Inactive, QPalette::Button, result.button().color());
346 result.setColor(QPalette::Inactive, QPalette::Window, result.window().color());
347 result.setColor(QPalette::Inactive, QPalette::Light, result.light().color());
348 result.setColor(QPalette::Inactive, QPalette::Dark, result.dark().color());
349
350 if (highContrastEnabled)
351 result.setColor(QPalette::Inactive, QPalette::WindowText, getSysColor(COLOR_GRAYTEXT));
352
353 if (result.midlight() == result.button())
354 result.setColor(QPalette::Midlight, result.button().color().lighter(110));
355}
356
357void QWindowsTheme::populateDarkSystemBasePalette(QPalette &result)
358{
359 QColor foreground;
360 QColor background;
361#if QT_CONFIG(cpp_winrt)
362 // We have to craft a palette from these colors. The settings.UIElementColor(UIElementType) API
363 // returns the old system colors, not the dark mode colors. If the background is black (which it
364 // usually), then override it with a dark gray instead so that we can go up and down the lightness.
365 if (QWindowsTheme::queryColorScheme() == Qt::ColorScheme::Dark) {
366 QT_TRY {
367 using namespace winrt::Windows::UI::ViewManagement;
368 const auto settings = UISettings();
369 // the system is actually running in dark mode, so UISettings will give us dark colors
370 foreground = getSysColor(settings.GetColorValue(UIColorType::Foreground));
371 background = [&settings]() -> QColor {
372 auto systemBackground = getSysColor(settings.GetColorValue(UIColorType::Background));
373 if (systemBackground == Qt::black)
374 systemBackground = QColor(0x1E, 0x1E, 0x1E);
375 return systemBackground;
376 }();
377 } QT_CATCH(...) {
378 // pass, just fall back to WIN32 API implementation
379 }
380 }
381#endif
382 if (!background.isValid()) {
383 // If the system is running in light mode, then we need to make up our own dark palette
384 foreground = Qt::white;
385 background = QColor(0x1E, 0x1E, 0x1E);
386 }
387
388 const QColor accent = qt_accentColor(AccentColorNormal);
389 const QColor accentDarkest = qt_accentColor(AccentColorDarkest);
390 const QColor accentLighter = qt_accentColor(AccentColorLighter);
391 const QColor accentLightest = qt_accentColor(AccentColorLightest);
392 const QColor linkColor = accentLightest;
393 const QColor buttonColor = background.lighter(200);
394
395 result.setColor(QPalette::All, QPalette::WindowText, foreground);
396 result.setColor(QPalette::All, QPalette::Text, foreground);
397 result.setColor(QPalette::All, QPalette::BrightText, accentLightest);
398
399 result.setColor(QPalette::All, QPalette::Button, buttonColor);
400 result.setColor(QPalette::All, QPalette::ButtonText, foreground);
401 result.setColor(QPalette::All, QPalette::Light, buttonColor.lighter(200));
402 result.setColor(QPalette::All, QPalette::Midlight, buttonColor.lighter(150));
403 result.setColor(QPalette::All, QPalette::Dark, buttonColor.darker(200));
404 result.setColor(QPalette::All, QPalette::Mid, buttonColor.darker(150));
405 result.setColor(QPalette::All, QPalette::Shadow, Qt::black);
406
407 result.setColor(QPalette::All, QPalette::Base, background.lighter(150));
408 result.setColor(QPalette::All, QPalette::Window, background);
409
410 result.setColor(QPalette::All, QPalette::Highlight, accent);
411 result.setColor(QPalette::All, QPalette::HighlightedText, accent.lightness() > 128 ? Qt::black : Qt::white);
412 result.setColor(QPalette::All, QPalette::Link, linkColor);
413 result.setColor(QPalette::All, QPalette::LinkVisited, accentLighter);
414 result.setColor(QPalette::All, QPalette::AlternateBase, accentDarkest);
415 result.setColor(QPalette::All, QPalette::ToolTipBase, buttonColor);
416 result.setColor(QPalette::All, QPalette::ToolTipText, foreground.darker(120));
417 result.setColor(QPalette::All, QPalette::PlaceholderText, placeHolderColor(foreground));
418 result.setColor(QPalette::All, QPalette::Accent, accentLighter);
419}
420
421static inline QPalette toolTipPalette(const QPalette &systemPalette, bool light, bool highContrastEnabled)
422{
423 QPalette result(systemPalette);
424 static const bool isWindows11 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11;
425 const QColor tipBgColor = highContrastEnabled ? (isWindows11 ? getSysColor(COLOR_WINDOW) : getSysColor(COLOR_BTNFACE)) :
426 (light ? getSysColor(COLOR_INFOBK) : systemPalette.button().color());
427 const QColor tipTextColor = highContrastEnabled ? (isWindows11 ? getSysColor(COLOR_WINDOWTEXT) : getSysColor(COLOR_BTNTEXT)) :
428 (light ? getSysColor(COLOR_INFOTEXT) : systemPalette.buttonText().color().darker(120));
429
430 result.setColor(QPalette::All, QPalette::Button, tipBgColor);
431 result.setColor(QPalette::All, QPalette::Window, tipBgColor);
432 result.setColor(QPalette::All, QPalette::Text, tipTextColor);
433 result.setColor(QPalette::All, QPalette::WindowText, tipTextColor);
434 result.setColor(QPalette::All, QPalette::ButtonText, tipTextColor);
435 result.setColor(QPalette::All, QPalette::Button, tipBgColor);
436 result.setColor(QPalette::All, QPalette::Window, tipBgColor);
437 result.setColor(QPalette::All, QPalette::Text, tipTextColor);
438 result.setColor(QPalette::All, QPalette::WindowText, tipTextColor);
439 result.setColor(QPalette::All, QPalette::ButtonText, tipTextColor);
440 result.setColor(QPalette::All, QPalette::ToolTipBase, tipBgColor);
441 result.setColor(QPalette::All, QPalette::ToolTipText, tipTextColor);
442 const QColor disabled = mixColors(result.windowText().color(), result.button().color());
443 result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
444 result.setColor(QPalette::Disabled, QPalette::Text, disabled);
445 result.setColor(QPalette::Disabled, QPalette::ToolTipText, disabled);
446 result.setColor(QPalette::Disabled, QPalette::Base, Qt::white);
447 result.setColor(QPalette::Disabled, QPalette::BrightText, Qt::white);
448 result.setColor(QPalette::Disabled, QPalette::ToolTipBase, Qt::white);
449 return result;
450}
451
452static inline QPalette menuPalette(const QPalette &systemPalette, bool light)
453{
454 if (!light)
455 return systemPalette;
456
457 QPalette result(systemPalette);
458 const QColor menuColor = getSysColor(COLOR_MENU);
459 const QColor menuTextColor = getSysColor(COLOR_MENUTEXT);
460 const QColor disabled = getSysColor(COLOR_GRAYTEXT);
461 // we might need a special color group for the result.
462 result.setColor(QPalette::Active, QPalette::Button, menuColor);
463 result.setColor(QPalette::Active, QPalette::Text, menuTextColor);
464 result.setColor(QPalette::Active, QPalette::WindowText, menuTextColor);
465 result.setColor(QPalette::Active, QPalette::ButtonText, menuTextColor);
466 result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
467 result.setColor(QPalette::Disabled, QPalette::Text, disabled);
468 const bool isFlat = booleanSystemParametersInfo(SPI_GETFLATMENU, false);
469 const QColor highlightColor = getSysColor(isFlat ? COLOR_MENUHILIGHT : COLOR_HIGHLIGHT);
470 result.setColor(QPalette::Disabled, QPalette::Highlight, highlightColor);
471 result.setColor(QPalette::Disabled, QPalette::HighlightedText, disabled);
472 result.setColor(QPalette::Disabled, QPalette::Button,
473 result.color(QPalette::Active, QPalette::Button));
474 result.setColor(QPalette::Inactive, QPalette::Button,
475 result.color(QPalette::Active, QPalette::Button));
476 result.setColor(QPalette::Inactive, QPalette::Text,
477 result.color(QPalette::Active, QPalette::Text));
478 result.setColor(QPalette::Inactive, QPalette::WindowText,
479 result.color(QPalette::Active, QPalette::WindowText));
480 result.setColor(QPalette::Inactive, QPalette::ButtonText,
481 result.color(QPalette::Active, QPalette::ButtonText));
482 result.setColor(QPalette::Inactive, QPalette::Highlight,
483 result.color(QPalette::Active, QPalette::Highlight));
484 result.setColor(QPalette::Inactive, QPalette::HighlightedText,
485 result.color(QPalette::Active, QPalette::HighlightedText));
486 result.setColor(QPalette::Inactive, QPalette::ButtonText,
487 systemPalette.color(QPalette::Inactive, QPalette::Dark));
488 return result;
489}
490
491static inline QPalette *menuBarPalette(const QPalette &menuPalette, bool light)
492{
493 QPalette *result = nullptr;
494 if (!light || !booleanSystemParametersInfo(SPI_GETFLATMENU, false))
495 return result;
496
497 result = new QPalette(menuPalette);
498 const QColor menubar(getSysColor(COLOR_MENUBAR));
499 result->setColor(QPalette::Active, QPalette::Button, menubar);
500 result->setColor(QPalette::Disabled, QPalette::Button, menubar);
501 result->setColor(QPalette::Inactive, QPalette::Button, menubar);
502 return result;
503}
504
505const char *QWindowsTheme::name = "windows";
506QWindowsTheme *QWindowsTheme::m_instance = nullptr;
507
508LRESULT QT_WIN_CALLBACK qThemeChangeObserverWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
509{
510 switch (message) {
511 case WM_SETTINGCHANGE:
512 // Only refresh the theme if the user changes the personalize settings
513 if (!((wParam == 0) && (lParam != 0) // lParam sometimes may be NULL.
514 && (wcscmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0)))
515 break;
516 Q_FALLTHROUGH();
517 case WM_THEMECHANGED:
520 qCDebug(lcQpaTheme) << "Handling theme change due to"
521 << qUtf8Printable(decodeMSG(MSG{hwnd, message, wParam, lParam, 0, {0, 0}}).trimmed());
522 QWindowsTheme::handleThemeChange();
523
524 MSG msg; // Clear the message queue, we've already reacted to the change
525 while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE));
526
527 // FIXME: Despite clearing the message queue above, Windows will send
528 // us redundant theme change events for our single window. We want the
529 // theme change delivery to be synchronous, so we can't easily debounce
530 // them by peeking into the QWSI event queue, but perhaps there are other
531 // ways.
532
533 break;
534 default:
535 break;
536 }
537
538 return DefWindowProc(hwnd, message, wParam, lParam);
539}
540
541QWindowsTheme::QWindowsTheme()
542{
543 m_instance = this;
544 s_colorScheme = Qt::ColorScheme::Unknown; // Used inside QWindowsTheme::effectiveColorScheme();
545 s_colorScheme = QWindowsTheme::effectiveColorScheme();
546 std::fill(m_fonts, m_fonts + NFonts, nullptr);
547 std::fill(m_palettes, m_palettes + NPalettes, nullptr);
548 refresh();
549 refreshIconPixmapSizes();
550
551 auto className = QWindowsWindowClassRegistry::instance()->registerWindowClass(
552 "ThemeChangeObserverWindow"_L1,
553 qThemeChangeObserverWndProc);
554 // HWND_MESSAGE windows do not get the required theme events,
555 // so we use a real top-level window that we never show.
556 m_themeChangeObserver = CreateWindowEx(0, reinterpret_cast<LPCWSTR>(className.utf16()),
557 nullptr, WS_TILED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
558 nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
559 Q_ASSERT(m_themeChangeObserver);
560}
561
563{
564 clearPalettes();
565 clearFonts();
566 m_instance = nullptr;
567}
568
569void QWindowsTheme::destroyThemeChangeWindow()
570{
571 qCDebug(lcQpaTheme) << "Destroying theme change window";
572 DestroyWindow(m_themeChangeObserver);
573 m_themeChangeObserver = nullptr;
574}
575
577{
578 const QFileInfo appDir(QCoreApplication::applicationDirPath() + "/icons"_L1);
579 return appDir.isDir() ? QStringList(appDir.absoluteFilePath()) : QStringList();
580}
581
582static inline QStringList styleNames()
583{
584 QStringList styles = { QStringLiteral("WindowsVista"), QStringLiteral("Windows") };
585 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11)
586 styles.prepend(QStringLiteral("Windows11"));
587 return styles;
588}
589
590static inline int uiEffects()
591{
592 int result = QPlatformTheme::HoverEffect;
593 if (booleanSystemParametersInfo(SPI_GETUIEFFECTS, false))
594 result |= QPlatformTheme::GeneralUiEffect;
595 if (booleanSystemParametersInfo(SPI_GETMENUANIMATION, false))
596 result |= QPlatformTheme::AnimateMenuUiEffect;
597 if (booleanSystemParametersInfo(SPI_GETMENUFADE, false))
598 result |= QPlatformTheme::FadeMenuUiEffect;
599 if (booleanSystemParametersInfo(SPI_GETCOMBOBOXANIMATION, false))
600 result |= QPlatformTheme::AnimateComboUiEffect;
601 if (booleanSystemParametersInfo(SPI_GETTOOLTIPANIMATION, false))
602 result |= QPlatformTheme::AnimateTooltipUiEffect;
603 return result;
604}
605
606QVariant QWindowsTheme::themeHint(ThemeHint hint) const
607{
608 switch (hint) {
609 case UseFullScreenForPopupMenu:
610 return QVariant(true);
611 case DialogButtonBoxLayout:
612 return QVariant(QPlatformDialogHelper::WinLayout);
613 case IconThemeSearchPaths:
614 return QVariant(iconThemeSearchPaths());
615 case StyleNames:
616 return QVariant(styleNames());
617 case TextCursorWidth:
618 return QVariant(int(dWordSystemParametersInfo(SPI_GETCARETWIDTH, 1u)));
619 case DropShadow:
620 return QVariant(booleanSystemParametersInfo(SPI_GETDROPSHADOW, false));
621 case MaximumScrollBarDragDistance:
622 return QVariant(qRound(qreal(QWindowsContext::instance()->defaultDPI()) * 1.375));
623 case KeyboardScheme:
624 return QVariant(int(WindowsKeyboardScheme));
625 case UiEffects:
626 return QVariant(uiEffects());
627 case IconPixmapSizes:
628 return QVariant::fromValue(m_fileIconSizes);
629 case DialogSnapToDefaultButton:
630 return QVariant(booleanSystemParametersInfo(SPI_GETSNAPTODEFBUTTON, false));
631 case ContextMenuOnMouseRelease:
632 return QVariant(true);
633 case WheelScrollLines: {
634 int result = 3;
635 const DWORD scrollLines = dWordSystemParametersInfo(SPI_GETWHEELSCROLLLINES, DWORD(result));
636 if (scrollLines != DWORD(-1)) // Special value meaning "scroll one screen", unimplemented in Qt.
637 result = int(scrollLines);
638 return QVariant(result);
639 }
640 case MouseDoubleClickDistance:
641 return GetSystemMetrics(SM_CXDOUBLECLK);
642 case MenuBarFocusOnAltPressRelease:
643 return true;
644 default:
645 break;
646 }
647 return QPlatformTheme::themeHint(hint);
648}
649
651{
652 return QWindowsTheme::effectiveColorScheme();
653}
654
655Qt::ColorScheme QWindowsTheme::effectiveColorScheme()
656{
657 auto integration = QWindowsIntegration::instance();
658 if (queryHighContrast())
659 return Qt::ColorScheme::Unknown;
660 if (s_colorSchemeOverride != Qt::ColorScheme::Unknown)
661 return s_colorSchemeOverride;
662 if (s_colorScheme != Qt::ColorScheme::Unknown)
663 return s_colorScheme;
664 if (!integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle))
665 return Qt::ColorScheme::Light;
666 return queryColorScheme();
667}
668
669void QWindowsTheme::requestColorScheme(Qt::ColorScheme scheme)
670{
671 s_colorSchemeOverride = scheme;
673}
674
676{
677 return queryHighContrast() ? Qt::ContrastPreference::HighContrast
678 : Qt::ContrastPreference::NoPreference;
679}
680
682{
683 auto *integration = QWindowsIntegration::instance();
684 if (!integration)
685 return;
686
687 QWindowsThemeCache::clearAllThemeCaches();
688
689 const auto oldColorScheme = s_colorScheme;
690 s_colorScheme = Qt::ColorScheme::Unknown; // make effectiveColorScheme() query registry
691 s_colorScheme = effectiveColorScheme();
692 if (s_colorScheme != oldColorScheme) {
693 // Only propagate color scheme changes if the scheme actually changed
695
696 for (QWindowsWindow *w : std::as_const(QWindowsContext::instance()->windows()))
697 w->setDarkBorder(s_colorScheme == Qt::ColorScheme::Dark);
698 }
699
700 // But always reset palette and fonts, and signal the theme
701 // change, as other parts of the theme could have changed,
702 // such as the accent color.
704 QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>();
705}
706
707void QWindowsTheme::clearPalettes()
708{
709 qDeleteAll(m_palettes, m_palettes + NPalettes);
710 std::fill(m_palettes, m_palettes + NPalettes, nullptr);
711}
712
713void QWindowsTheme::refreshPalettes()
714{
715 if (!QGuiApplication::desktopSettingsAware())
716 return;
717 const bool light =
718 effectiveColorScheme() != Qt::ColorScheme::Dark
719 || !QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle);
720 clearPalettes();
721 m_palettes[SystemPalette] = new QPalette(QWindowsTheme::systemPalette(s_colorScheme));
722 m_palettes[ToolTipPalette] = new QPalette(toolTipPalette(*m_palettes[SystemPalette], light, queryHighContrast()));
723 m_palettes[MenuPalette] = new QPalette(menuPalette(*m_palettes[SystemPalette], light));
724 m_palettes[MenuBarPalette] = menuBarPalette(*m_palettes[MenuPalette], light);
725 if (!light) {
726 m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]);
727 m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, qt_accentColor(AccentColorNormal));
728 m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, qt_accentColor(AccentColorLighter));
729 m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, qt_accentColor(AccentColorDarkest));
730 m_palettes[RadioButtonPalette] = new QPalette(*m_palettes[CheckBoxPalette]);
731 }
732}
733
734QPalette QWindowsTheme::systemPalette(Qt::ColorScheme colorScheme)
735{
736 QPalette result = standardPalette();
737
738 switch (colorScheme) {
739 case Qt::ColorScheme::Unknown:
740 // when a high-contrast theme is active or when we fail to read, assume light
741 Q_FALLTHROUGH();
742 case Qt::ColorScheme::Light:
743 populateLightSystemBasePalette(result);
744 break;
745 case Qt::ColorScheme::Dark:
746 populateDarkSystemBasePalette(result);
747 break;
748 }
749
750 if (result.window() != result.base()) {
751 result.setColor(QPalette::Inactive, QPalette::Highlight,
752 result.color(QPalette::Inactive, QPalette::Window));
753 result.setColor(QPalette::Inactive, QPalette::HighlightedText,
754 result.color(QPalette::Inactive, QPalette::Text));
755 result.setColor(QPalette::Inactive, QPalette::Accent,
756 result.color(QPalette::Inactive, QPalette::Window));
757 }
758
759 const QColor disabled = mixColors(result.windowText().color(), result.button().color());
760
761 result.setColorGroup(QPalette::Disabled, result.windowText(), result.button(),
762 result.light(), result.dark(), result.mid(),
763 result.text(), result.brightText(), result.base(),
764 result.window());
765
766 const bool highContrastEnabled = queryHighContrast();
767 const QColor disabledTextColor = highContrastEnabled ? getSysColor(COLOR_GRAYTEXT) : disabled;
768 result.setColor(QPalette::Disabled, QPalette::WindowText, disabledTextColor);
769 result.setColor(QPalette::Disabled, QPalette::Text, disabledTextColor);
770 result.setColor(QPalette::Disabled, QPalette::ButtonText, disabledTextColor);
771 if (highContrastEnabled)
772 result.setColor(QPalette::Disabled, QPalette::Button, result.button().color().darker(150));
773
774 result.setColor(QPalette::Disabled, QPalette::Highlight, result.color(QPalette::Highlight));
775 result.setColor(QPalette::Disabled, QPalette::HighlightedText, result.color(QPalette::HighlightedText));
776 result.setColor(QPalette::Disabled, QPalette::Accent, disabled);
777 result.setColor(QPalette::Disabled, QPalette::Base, result.window().color());
778 return result;
779}
780
781void QWindowsTheme::clearFonts()
782{
783 qDeleteAll(m_fonts, m_fonts + NFonts);
784 std::fill(m_fonts, m_fonts + NFonts, nullptr);
785}
786
788{
789 refreshPalettes();
791}
792
793#ifndef QT_NO_DEBUG_STREAM
794QDebug operator<<(QDebug d, const NONCLIENTMETRICS &m)
795{
796 QDebugStateSaver saver(d);
797 d.nospace();
798 d.noquote();
799 d << "NONCLIENTMETRICS(iMenu=" << m.iMenuWidth << 'x' << m.iMenuHeight
800 << ", lfCaptionFont=";
801 QWindowsFontDatabase::debugFormat(d, m.lfCaptionFont);
802 d << ", lfSmCaptionFont=";
803 QWindowsFontDatabase::debugFormat(d, m.lfSmCaptionFont);
804 d << ", lfMenuFont=";
805 QWindowsFontDatabase::debugFormat(d, m.lfMenuFont);
806 d << ", lfMessageFont=";
807 QWindowsFontDatabase::debugFormat(d, m.lfMessageFont);
808 d <<", lfStatusFont=";
809 QWindowsFontDatabase::debugFormat(d, m.lfStatusFont);
810 d << ')';
811 return d;
812}
813#endif // QT_NO_DEBUG_STREAM
814
816{
817 clearFonts();
818 if (!QGuiApplication::desktopSettingsAware())
819 return;
820
821 const int dpi = 96;
822 NONCLIENTMETRICS ncm;
823 QWindowsContext::nonClientMetrics(&ncm, dpi);
824 qCDebug(lcQpaWindow) << __FUNCTION__ << ncm;
825
826 const QFont menuFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMenuFont, dpi);
827 const QFont messageBoxFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont, dpi);
828 const QFont statusFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfStatusFont, dpi);
829 const QFont titleFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfCaptionFont, dpi);
830 QFont fixedFont(QStringLiteral("Courier New"), messageBoxFont.pointSize());
831 fixedFont.setStyleHint(QFont::TypeWriter);
832
833 LOGFONT lfIconTitleFont;
834 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi);
835 const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont, dpi);
836
837 m_fonts[SystemFont] = new QFont(QWindowsFontDatabase::systemDefaultFont());
838 m_fonts[MenuFont] = new QFont(menuFont);
839 m_fonts[MenuBarFont] = new QFont(menuFont);
840 m_fonts[MessageBoxFont] = new QFont(messageBoxFont);
841 m_fonts[TipLabelFont] = new QFont(statusFont);
842 m_fonts[StatusBarFont] = new QFont(statusFont);
843 m_fonts[MdiSubWindowTitleFont] = new QFont(titleFont);
844 m_fonts[DockWidgetTitleFont] = new QFont(titleFont);
845 m_fonts[ItemViewFont] = new QFont(iconTitleFont);
846 m_fonts[FixedFont] = new QFont(fixedFont);
847}
848
850 // Standard icons obtainable via shGetFileInfo(), SHGFI_SMALLICON, SHGFI_LARGEICON
852 // Larger icons obtainable via SHGetImageList()
854 JumboFileIcon, // Vista onwards
856};
857
858bool QWindowsTheme::usePlatformNativeDialog(DialogType type) const
859{
860 return QWindowsDialogs::useHelper(type);
861}
862
864{
865 return QWindowsDialogs::createHelper(type);
866}
867
868#if QT_CONFIG(systemtrayicon)
869QPlatformSystemTrayIcon *QWindowsTheme::createPlatformSystemTrayIcon() const
870{
871 return new QWindowsSystemTrayIcon;
872}
873#endif
874
876
877void QWindowsTheme::refreshIconPixmapSizes()
878{
879 // Standard sizes: 16, 32, 48, 256
880 fileIconSizes[SmallFileIcon] = GetSystemMetrics(SM_CXSMICON); // corresponds to SHGFI_SMALLICON);
881 fileIconSizes[LargeFileIcon] = GetSystemMetrics(SM_CXICON); // corresponds to SHGFI_LARGEICON
884 fileIconSizes[JumboFileIcon] = 8 * fileIconSizes[LargeFileIcon]; // empirical, has not been observed to work
885
886 int *availEnd = fileIconSizes + JumboFileIcon + 1;
887 m_fileIconSizes = QAbstractFileIconEngine::toSizeList(fileIconSizes, availEnd);
888 qCDebug(lcQpaWindow) << __FUNCTION__ << m_fileIconSizes;
889}
890
891// Defined in qpixmap_win.cpp
892Q_GUI_EXPORT QPixmap qt_pixmapFromWinHICON(HICON icon);
893
894static QPixmap loadIconFromShell32(int resourceId, QSizeF size)
895{
896 HMODULE shell32 = ::GetModuleHandleW(L"shell32.dll");
897 Q_ASSERT(shell32);
898 auto iconHandle =
899 static_cast<HICON>(LoadImage(shell32, MAKEINTRESOURCE(resourceId),
900 IMAGE_ICON, int(size.width()), int(size.height()), 0));
901 if (iconHandle) {
902 QPixmap iconpixmap = qt_pixmapFromWinHICON(iconHandle);
903 DestroyIcon(iconHandle);
904 return iconpixmap;
905 }
906 return QPixmap();
907}
908
909QPixmap QWindowsTheme::standardPixmap(StandardPixmap sp, const QSizeF &pixmapSize) const
910{
911 int resourceId = -1;
912 SHSTOCKICONID stockId = SIID_INVALID;
913 LPCTSTR iconName = nullptr;
914 switch (sp) {
915 case DriveCDIcon:
916 stockId = SIID_DRIVECD;
917 resourceId = 12;
918 break;
919 case DriveDVDIcon:
920 stockId = SIID_DRIVEDVD;
921 resourceId = 12;
922 break;
923 case DriveNetIcon:
924 stockId = SIID_DRIVENET;
925 resourceId = 10;
926 break;
927 case DriveHDIcon:
928 stockId = SIID_DRIVEFIXED;
929 resourceId = 9;
930 break;
931 case DriveFDIcon:
932 stockId = SIID_DRIVE35;
933 resourceId = 7;
934 break;
935 case FileLinkIcon:
936 Q_FALLTHROUGH();
937 case FileIcon:
938 stockId = SIID_DOCNOASSOC;
939 resourceId = 1;
940 break;
941 case DirLinkIcon:
942 Q_FALLTHROUGH();
943 case DirClosedIcon:
944 case DirIcon:
945 stockId = SIID_FOLDER;
946 resourceId = 4;
947 break;
948 case DesktopIcon:
949 resourceId = 35;
950 break;
951 case ComputerIcon:
952 resourceId = 16;
953 break;
954 case DirLinkOpenIcon:
955 Q_FALLTHROUGH();
956 case DirOpenIcon:
957 stockId = SIID_FOLDEROPEN;
958 resourceId = 5;
959 break;
960 case FileDialogNewFolder:
961 stockId = SIID_FOLDER;
962 resourceId = 319;
963 break;
964 case DirHomeIcon:
965 resourceId = 235;
966 break;
967 case TrashIcon:
968 stockId = SIID_RECYCLER;
969 resourceId = 191;
970 break;
971 case MessageBoxInformation:
972 stockId = SIID_INFO;
973 iconName = IDI_INFORMATION;
974 break;
975 case MessageBoxWarning:
976 stockId = SIID_WARNING;
977 iconName = IDI_WARNING;
978 break;
979 case MessageBoxCritical:
980 stockId = SIID_ERROR;
981 iconName = IDI_ERROR;
982 break;
983 case MessageBoxQuestion:
984 stockId = SIID_HELP;
985 iconName = IDI_QUESTION;
986 break;
987 case VistaShield:
988 stockId = SIID_SHIELD;
989 break;
990 default:
991 break;
992 }
993
994 // Even with SHGSI_LINKOVERLAY flag set, loaded Icon with SHDefExtractIcon doesn't have
995 // any overlay, so we avoid SHGSI_LINKOVERLAY flag and draw it manually (QTBUG-131843)
996 const auto drawLinkOverlayIconIfNeeded = [](StandardPixmap sp, QPixmap &pixmap, QSizeF pixmapSize) {
997 if (sp == FileLinkIcon || sp == DirLinkIcon || sp == DirLinkOpenIcon) {
998 QPainter painter(&pixmap);
999 const QSizeF linkSize = pixmapSize / (pixmapSize.height() >= 48 ? 3 : 2);
1000 static constexpr auto LinkOverlayIconId = 16769;
1001 const QPixmap link = loadIconFromShell32(LinkOverlayIconId, linkSize.toSize());
1002 const int yPos = pixmap.height() - link.size().height();
1003 painter.drawPixmap(0, yPos, int(linkSize.width()), int(linkSize.height()), link);
1004 }
1005 };
1006
1007 if (stockId != SIID_INVALID) {
1008 SHSTOCKICONINFO iconInfo;
1009 memset(&iconInfo, 0, sizeof(iconInfo));
1010 iconInfo.cbSize = sizeof(iconInfo);
1011 constexpr UINT stockFlags = SHGSI_ICONLOCATION;
1012 if (SHGetStockIconInfo(stockId, stockFlags, &iconInfo) == S_OK) {
1013 const auto iconSize = pixmapSize.width();
1014 HICON icon;
1015 if (SHDefExtractIcon(iconInfo.szPath, iconInfo.iIcon, 0, &icon, nullptr, iconSize) == S_OK) {
1016 QPixmap pixmap = qt_pixmapFromWinHICON(icon);
1017 DestroyIcon(icon);
1018 if (!pixmap.isNull()) {
1019 drawLinkOverlayIconIfNeeded(sp, pixmap, pixmap.size());
1020 return pixmap;
1021 }
1022 }
1023 }
1024 }
1025
1026 if (resourceId != -1) {
1027 QPixmap pixmap = loadIconFromShell32(resourceId, pixmapSize);
1028 if (!pixmap.isNull()) {
1029 drawLinkOverlayIconIfNeeded(sp, pixmap, pixmapSize);
1030 return pixmap;
1031 }
1032 }
1033
1034 if (iconName) {
1035 HICON iconHandle = LoadIcon(nullptr, iconName);
1036 QPixmap pixmap = qt_pixmapFromWinHICON(iconHandle);
1037 DestroyIcon(iconHandle);
1038 if (!pixmap.isNull())
1039 return pixmap;
1040 }
1041
1042 return QPlatformTheme::standardPixmap(sp, pixmapSize);
1043}
1044
1045enum { // Shell image list ids
1046 sHIL_EXTRALARGE = 0x2, // 48x48 or user-defined
1047 sHIL_JUMBO = 0x4 // 256x256 (Vista or later)
1048};
1049
1050static QString dirIconPixmapCacheKey(int iIcon, int iconSize, int imageListSize)
1051{
1052 QString key = "qt_dir_"_L1 + QString::number(iIcon);
1053 if (iconSize == SHGFI_LARGEICON)
1054 key += u'l';
1055 switch (imageListSize) {
1056 case sHIL_EXTRALARGE:
1057 key += u'e';
1058 break;
1059 case sHIL_JUMBO:
1060 key += u'j';
1061 break;
1062 }
1063 return key;
1064}
1065
1066template <typename T>
1068{
1069public:
1070
1071 static_assert(sizeof(T) <= sizeof(void *), "FakePointers can only go that far.");
1072
1073 static FakePointer *create(T thing)
1074 {
1075 return reinterpret_cast<FakePointer *>(qintptr(thing));
1076 }
1077
1078 T operator * () const
1079 {
1080 return T(qintptr(this));
1081 }
1082
1083 void operator delete (void *) {}
1084};
1085
1086// Shell image list helper functions.
1087
1088static QPixmap pixmapFromShellImageList(int iImageList, int iIcon)
1089{
1090 QPixmap result;
1091 // For MinGW:
1092 static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}};
1093
1094 IImageList *imageList = nullptr;
1095 HRESULT hr = SHGetImageList(iImageList, iID_IImageList, reinterpret_cast<void **>(&imageList));
1096 if (hr != S_OK)
1097 return result;
1098 HICON hIcon;
1099 hr = imageList->GetIcon(iIcon, ILD_TRANSPARENT, &hIcon);
1100 if (hr == S_OK) {
1101 result = qt_pixmapFromWinHICON(hIcon);
1102 DestroyIcon(hIcon);
1103 }
1104 imageList->Release();
1105 return result;
1106}
1107
1109{
1110public:
1111 explicit QWindowsFileIconEngine(const QFileInfo &info, QPlatformTheme::IconOptions opts) :
1113
1114 QList<QSize> availableSizes(QIcon::Mode = QIcon::Normal, QIcon::State = QIcon::Off) override
1115 { return QWindowsTheme::instance()->availableFileIconSizes(); }
1116
1117protected:
1118 QString cacheKey() const override;
1119 QPixmap filePixmap(const QSize &size, QIcon::Mode mode, QIcon::State) override;
1120};
1121
1123{
1124 // Cache directories unless custom or drives, which have custom icons depending on type
1125 if ((options() & QPlatformTheme::DontUseCustomDirectoryIcons) && fileInfo().isDir() && !fileInfo().isRoot())
1126 return QStringLiteral("qt_/directory/");
1127 if (!fileInfo().isFile())
1128 return QString();
1129 // Return "" for .exe, .lnk and .ico extensions.
1130 // It is faster to just look at the file extensions;
1131 // avoiding slow QFileInfo::isExecutable() (QTBUG-13182)
1132 QString suffix = fileInfo().suffix();
1133 if (!suffix.compare(u"exe", Qt::CaseInsensitive)
1134 || !suffix.compare(u"lnk", Qt::CaseInsensitive)
1135 || !suffix.compare(u"ico", Qt::CaseInsensitive)) {
1136 return QString();
1137 }
1138 return "qt_."_L1
1139 + (suffix.isEmpty() ? fileInfo().fileName() : std::move(suffix).toUpper()); // handle "Makefile" ;)
1140}
1141
1143{
1144 QComHelper comHelper;
1145
1146 static QCache<QString, FakePointer<int> > dirIconEntryCache(1000);
1147 Q_CONSTINIT static QMutex mx;
1148 static int defaultFolderIIcon = -1;
1149 const bool useDefaultFolderIcon = options() & QPlatformTheme::DontUseCustomDirectoryIcons;
1150
1151 QPixmap pixmap;
1152 const QString filePath = QDir::toNativeSeparators(fileInfo().filePath());
1153 const int width = int(size.width());
1154 const int iconSize = width > fileIconSizes[SmallFileIcon] ? SHGFI_LARGEICON : SHGFI_SMALLICON;
1155 const int requestedImageListSize =
1157 ? sHIL_JUMBO
1158 : (width > fileIconSizes[LargeFileIcon] ? sHIL_EXTRALARGE : 0);
1159 bool cacheableDirIcon = fileInfo().isDir() && !fileInfo().isRoot();
1160 if (cacheableDirIcon) {
1161 QMutexLocker locker(&mx);
1162 int iIcon = (useDefaultFolderIcon && defaultFolderIIcon >= 0) ? defaultFolderIIcon
1163 : **dirIconEntryCache.object(filePath);
1164 if (iIcon) {
1165 QPixmapCache::find(dirIconPixmapCacheKey(iIcon, iconSize, requestedImageListSize),
1166 &pixmap);
1167 if (pixmap.isNull()) // Let's keep both caches in sync
1168 dirIconEntryCache.remove(filePath);
1169 else
1170 return pixmap;
1171 }
1172 }
1173
1174 unsigned int flags = SHGFI_ICON | iconSize | SHGFI_SYSICONINDEX | SHGFI_ADDOVERLAYS | SHGFI_OVERLAYINDEX;
1175 DWORD attributes = 0;
1176 QString path = filePath;
1177 if (cacheableDirIcon && useDefaultFolderIcon) {
1178 flags |= SHGFI_USEFILEATTRIBUTES;
1179 attributes |= FILE_ATTRIBUTE_DIRECTORY;
1180 path = QStringLiteral("dummy");
1181 } else if (!fileInfo().exists()) {
1182 flags |= SHGFI_USEFILEATTRIBUTES;
1183 attributes |= FILE_ATTRIBUTE_NORMAL;
1184 }
1185 auto task = QSharedPointer<QShGetFileInfoThread::Task>(
1186 new QShGetFileInfoThread::Task(path, attributes, flags));
1187 s_shGetFileInfoThread()->runWithParams(task);
1188 // Even if GetFileInfo returns a valid result, hIcon can be empty in some cases
1189 if (task->resultValid()) {
1190 QString key;
1191 if (cacheableDirIcon) {
1192 if (useDefaultFolderIcon && defaultFolderIIcon < 0)
1193 defaultFolderIIcon = task->iIcon;
1194
1195 //using the unique icon index provided by windows save us from duplicate keys
1196 key = dirIconPixmapCacheKey(task->iIcon, iconSize, requestedImageListSize);
1197 QPixmapCache::find(key, &pixmap);
1198 if (!pixmap.isNull()) {
1199 QMutexLocker locker(&mx);
1200 dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
1201 }
1202 }
1203
1204 if (pixmap.isNull()) {
1205 if (requestedImageListSize) {
1206 pixmap = pixmapFromShellImageList(requestedImageListSize, task->iIcon);
1207 if (pixmap.isNull() && requestedImageListSize == sHIL_JUMBO)
1208 pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, task->iIcon);
1209 }
1210 if (pixmap.isNull())
1211 pixmap = qt_pixmapFromWinHICON(task->hIcon);
1212 if (!pixmap.isNull()) {
1213 if (cacheableDirIcon) {
1214 QMutexLocker locker(&mx);
1215 QPixmapCache::insert(key, pixmap);
1216 dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
1217 }
1218 } else {
1219 qWarning("QWindowsTheme::fileIconPixmap() no icon found");
1220 }
1221 }
1222 }
1223
1224 return pixmap;
1225}
1226
1227QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions) const
1228{
1229 return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions));
1230}
1231
1232QIconEngine *QWindowsTheme::createIconEngine(const QString &iconName) const
1233{
1234 return new QWindowsIconEngine(iconName);
1235}
1236
1237static inline bool doUseNativeMenus()
1238{
1239 const unsigned options = QWindowsIntegration::instance()->options();
1240 if ((options & QWindowsIntegration::NoNativeMenus) != 0)
1241 return false;
1242 if ((options & QWindowsIntegration::AlwaysUseNativeMenus) != 0)
1243 return true;
1244 // "Auto" mode: For non-widget or Quick Controls 2 applications
1245 if (!QCoreApplication::instance()->inherits("QApplication"))
1246 return true;
1247 const QWindowList &topLevels = QGuiApplication::topLevelWindows();
1248 for (const QWindow *t : topLevels) {
1249 if (t->inherits("QQuickApplicationWindow"))
1250 return true;
1251 }
1252 return false;
1253}
1254
1256{
1257 static const bool result = doUseNativeMenus();
1258 return result;
1259}
1260
1261Qt::ColorScheme QWindowsTheme::queryColorScheme()
1262{
1263 if (queryHighContrast())
1264 return Qt::ColorScheme::Unknown;
1265
1266 QWinRegistryKey personalizeKey{
1267 HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)"
1268 };
1269 const bool useDarkTheme = personalizeKey.value<DWORD>(L"AppsUseLightTheme") == 0;
1270 return useDarkTheme ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
1271}
1272
1274{
1275 HIGHCONTRAST hcf = {};
1276 hcf.cbSize = static_cast<UINT>(sizeof(HIGHCONTRAST));
1277 if (SystemParametersInfo(SPI_GETHIGHCONTRAST, hcf.cbSize, &hcf, FALSE))
1278 return hcf.dwFlags & HCF_HIGHCONTRASTON;
1279 return false;
1280}
1281
1283{
1284 qCDebug(lcQpaMenus) << __FUNCTION__;
1286}
1287
1289{
1290 qCDebug(lcQpaMenus) << __FUNCTION__;
1291 // We create a popup menu here, since it will likely be used as context
1292 // menu. Submenus should be created the factory functions of
1293 // QPlatformMenu/Bar. Note though that Quick Controls 1 will use this
1294 // function for submenus as well, but this has been found to work.
1296}
1297
1299{
1300 qCDebug(lcQpaMenus) << __FUNCTION__;
1301 return QWindowsTheme::useNativeMenus() ? new QWindowsMenuBar : nullptr;
1302}
1303
1305{
1306 qCDebug(lcQpaMenus) << __FUNCTION__;
1307}
1308
1309QT_END_NAMESPACE
T operator*() const
void operator delete(void *)
static FakePointer * create(T thing)
Returns a copy of the pixmap that is transformed using the given transformation transform and transfo...
Definition qpixmap.h:27
void runWithParams(const QSharedPointer< Task > &task, std::chrono::milliseconds timeout=std::chrono::milliseconds(5000))
QSharedPointer< Task > getNextTask()
Singleton container for all relevant information.
static QWindowsContext * instance()
QList< QSize > availableSizes(QIcon::Mode=QIcon::Normal, QIcon::State=QIcon::Off) override
Returns sizes of all images that are contained in the engine for the specific mode and state.
QPixmap filePixmap(const QSize &size, QIcon::Mode mode, QIcon::State) override
QWindowsFileIconEngine(const QFileInfo &info, QPlatformTheme::IconOptions opts)
QString cacheKey() const override
static QWindowsIntegration * instance()
Windows native menu bar.
static bool useNativeMenus()
void requestColorScheme(Qt::ColorScheme scheme) override
void showPlatformMenuBar() override
QIconEngine * createIconEngine(const QString &iconName) const override
Factory function for the QIconEngine used by QIcon::fromTheme().
QPlatformMenuBar * createPlatformMenuBar() const override
static void handleThemeChange()
bool usePlatformNativeDialog(DialogType type) const override
static const char * name
QPlatformMenuItem * createPlatformMenuItem() const override
Qt::ColorScheme colorScheme() const override
QVariant themeHint(ThemeHint) const override
~QWindowsTheme() override
static bool queryHighContrast()
QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions={}) const override
Return an icon for fileInfo, observing iconOptions.
QPlatformDialogHelper * createPlatformDialogHelper(DialogType type) const override
static QWindowsTheme * instance()
QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override
Return a pixmap for standardPixmap, at the given size.
QPlatformMenu * createPlatformMenu() const override
Qt::ContrastPreference contrastPreference() const override
static QWindowsWindowClassRegistry * instance()
#define WM_SYSCOLORCHANGE
#define WM_DWMCOLORIZATIONCOLORCHANGED
static QPixmap pixmapFromShellImageList(int iImageList, int iIcon)
@ sHIL_EXTRALARGE
@ sHIL_JUMBO
static int fileIconSizes[FileIconSizeCount]
static QStringList styleNames()
static bool doUseNativeMenus()
AccentColorLevel
@ AccentColorLightest
@ AccentColorDark
@ AccentColorDarker
@ AccentColorLight
@ AccentColorLighter
@ AccentColorNormal
@ AccentColorDarkest
static QPalette toolTipPalette(const QPalette &systemPalette, bool light, bool highContrastEnabled)
static QStringList iconThemeSearchPaths()
static QColor qt_accentColor(AccentColorLevel level)
static QColor getSysColor(int index)
static QPalette standardPalette()
static QString dirIconPixmapCacheKey(int iIcon, int iconSize, int imageListSize)
static int uiEffects()
static QColor mixColors(const QColor &c1, const QColor &c2)
FileIconSize
@ JumboFileIcon
@ SmallFileIcon
@ LargeFileIcon
@ FileIconSizeCount
@ ExtraLargeFileIcon
static QColor placeHolderColor(QColor textColor)
static DWORD dWordSystemParametersInfo(UINT what, DWORD defaultValue)
static QPalette menuPalette(const QPalette &systemPalette, bool light)
static QPixmap loadIconFromShell32(int resourceId, QSizeF size)
static QPalette * menuBarPalette(const QPalette &menuPalette, bool light)
static bool booleanSystemParametersInfo(UINT what, bool defaultValue)
Task(const QString &fn, DWORD a, UINT f)