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// Qt-Security score:significant reason:default
4
5#include <QtCore/qt_windows.h>
6
8#include "qwindowsmenu.h"
13#if QT_CONFIG(systemtrayicon)
14# include "qwindowssystemtrayicon.h"
15#endif
16#include "qwindowsscreen.h"
17#include "qwindowswindow.h"
19#include <commctrl.h>
20#include <objbase.h>
21#include <commoncontrols.h>
22#include <shellapi.h>
23
24#include <QtCore/qapplicationstatic.h>
25#include <QtCore/qvariant.h>
26#include <QtCore/qcoreapplication.h>
27#include <QtCore/qdebug.h>
28#include <QtCore/qsysinfo.h>
29#include <QtCore/qcache.h>
30#include <QtCore/qthread.h>
31#include <QtCore/qqueue.h>
32#include <QtCore/qmutex.h>
33#include <QtCore/qwaitcondition.h>
34#include <QtCore/qoperatingsystemversion.h>
35#include <QtGui/qcolor.h>
36#include <QtGui/qpalette.h>
37#include <QtGui/qguiapplication.h>
38#include <QtGui/qpainter.h>
39#include <QtGui/qpixmapcache.h>
40#include <qpa/qwindowsysteminterface.h>
41#include <QtGui/private/qabstractfileiconengine_p.h>
42#include <QtGui/private/qwindowsfontdatabase_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 // Or if the "Animation Effect" accessibility setting changed
514 if (!((wParam == 0) && (lParam != 0) // lParam sometimes may be NULL.
515 && (wcscmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0))
516 && wParam != SPI_SETCLIENTAREAANIMATION)
517 break;
518 Q_FALLTHROUGH();
519 case WM_THEMECHANGED:
522 qCDebug(lcQpaTheme, "Handling theme change due to %ls",
523 qUtf16Printable(qt_decodeMSG(MSG{hwnd, message, wParam, lParam, 0, {0, 0}}).trimmed()));
524 QWindowsTheme::handleThemeChange();
525
526 MSG msg; // Clear the message queue, we've already reacted to the change
527 while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE));
528
529 // FIXME: Despite clearing the message queue above, Windows will send
530 // us redundant theme change events for our single window. We want the
531 // theme change delivery to be synchronous, so we can't easily debounce
532 // them by peeking into the QWSI event queue, but perhaps there are other
533 // ways.
534
535 break;
536 default:
537 break;
538 }
539
540 return DefWindowProc(hwnd, message, wParam, lParam);
541}
542
543QWindowsTheme::QWindowsTheme()
544{
545 m_instance = this;
546 s_colorScheme = Qt::ColorScheme::Unknown; // Used inside QWindowsTheme::effectiveColorScheme();
547 s_colorScheme = QWindowsTheme::effectiveColorScheme();
548 std::fill(m_fonts, m_fonts + NFonts, nullptr);
549 std::fill(m_palettes, m_palettes + NPalettes, nullptr);
550 refresh();
551 refreshIconPixmapSizes();
552
553 auto className = QWindowsWindowClassRegistry::instance()->registerWindowClass(
554 "ThemeChangeObserverWindow"_L1,
555 qThemeChangeObserverWndProc);
556 // HWND_MESSAGE windows do not get the required theme events,
557 // so we use a real top-level window that we never show.
558 m_themeChangeObserver = CreateWindowEx(0, reinterpret_cast<LPCWSTR>(className.utf16()),
559 nullptr, WS_TILED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
560 nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
561 Q_ASSERT(m_themeChangeObserver);
562}
563
565{
566 clearPalettes();
567 clearFonts();
568 m_instance = nullptr;
569}
570
571void QWindowsTheme::destroyThemeChangeWindow()
572{
573 qCDebug(lcQpaTheme) << "Destroying theme change window";
574 DestroyWindow(m_themeChangeObserver);
575 m_themeChangeObserver = nullptr;
576}
577
579{
580 const QFileInfo appDir(QCoreApplication::applicationDirPath() + "/icons"_L1);
581 return appDir.isDir() ? QStringList(appDir.absoluteFilePath()) : QStringList();
582}
583
584static inline QStringList styleNames()
585{
586 QStringList styles = { QStringLiteral("WindowsVista"), QStringLiteral("Windows") };
587 if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11)
588 styles.prepend(QStringLiteral("Windows11"));
589 return styles;
590}
591
592static inline int uiEffects()
593{
594 int result = QPlatformTheme::HoverEffect;
595 if (booleanSystemParametersInfo(SPI_GETUIEFFECTS, false))
596 result |= QPlatformTheme::GeneralUiEffect;
597 if (booleanSystemParametersInfo(SPI_GETMENUANIMATION, false))
598 result |= QPlatformTheme::AnimateMenuUiEffect;
599 if (booleanSystemParametersInfo(SPI_GETMENUFADE, false))
600 result |= QPlatformTheme::FadeMenuUiEffect;
601 if (booleanSystemParametersInfo(SPI_GETCOMBOBOXANIMATION, false))
602 result |= QPlatformTheme::AnimateComboUiEffect;
603 if (booleanSystemParametersInfo(SPI_GETTOOLTIPANIMATION, false))
604 result |= QPlatformTheme::AnimateTooltipUiEffect;
605 return result;
606}
607
608QVariant QWindowsTheme::themeHint(ThemeHint hint) const
609{
610 switch (hint) {
611 case UseFullScreenForPopupMenu:
612 return QVariant(true);
613 case DialogButtonBoxLayout:
614 return QVariant(QPlatformDialogHelper::WinLayout);
615 case IconThemeSearchPaths:
616 return QVariant(iconThemeSearchPaths());
617 case StyleNames:
618 return QVariant(styleNames());
619 case TextCursorWidth:
620 return QVariant(int(dWordSystemParametersInfo(SPI_GETCARETWIDTH, 1u)));
621 case DropShadow:
622 return QVariant(booleanSystemParametersInfo(SPI_GETDROPSHADOW, false));
623 case MaximumScrollBarDragDistance:
624 return QVariant(qRound(qreal(QWindowsContext::instance()->defaultDPI()) * 1.375));
625 case KeyboardScheme:
626 return QVariant(int(WindowsKeyboardScheme));
627 case UiEffects:
628 return QVariant(uiEffects());
629 case IconPixmapSizes:
630 return QVariant::fromValue(m_fileIconSizes);
631 case DialogSnapToDefaultButton:
632 return QVariant(booleanSystemParametersInfo(SPI_GETSNAPTODEFBUTTON, false));
633 case ContextMenuOnMouseRelease:
634 return QVariant(true);
635 case WheelScrollLines: {
636 int result = 3;
637 const DWORD scrollLines = dWordSystemParametersInfo(SPI_GETWHEELSCROLLLINES, DWORD(result));
638 if (scrollLines != DWORD(-1)) // Special value meaning "scroll one screen", unimplemented in Qt.
639 result = int(scrollLines);
640 return QVariant(result);
641 }
642 case MouseDoubleClickDistance:
643 return GetSystemMetrics(SM_CXDOUBLECLK);
644 case MenuBarFocusOnAltPressRelease:
645 return true;
646 case MenuActivationButtons:
647 return Qt::LeftButton;
648 default:
649 break;
650 }
651 return QPlatformTheme::themeHint(hint);
652}
653
655{
656 return QWindowsTheme::effectiveColorScheme();
657}
658
659Qt::ColorScheme QWindowsTheme::effectiveColorScheme()
660{
661 auto integration = QWindowsIntegration::instance();
662 if (queryHighContrast())
663 return Qt::ColorScheme::Unknown;
664 if (s_colorSchemeOverride != Qt::ColorScheme::Unknown)
665 return s_colorSchemeOverride;
666 if (s_colorScheme != Qt::ColorScheme::Unknown)
667 return s_colorScheme;
668 if (!integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle))
669 return Qt::ColorScheme::Light;
670 return queryColorScheme();
671}
672
673void QWindowsTheme::requestColorScheme(Qt::ColorScheme scheme)
674{
675 s_colorSchemeOverride = scheme;
677}
678
680{
681 return queryHighContrast() ? Qt::ContrastPreference::HighContrast
682 : Qt::ContrastPreference::NoPreference;
683}
684
686{
687 return booleanSystemParametersInfo(SPI_GETCLIENTAREAANIMATION, false) ? Qt::MotionPreference::NoPreference
688 : Qt::MotionPreference::ReducedMotion;
689}
690
692{
693 auto *integration = QWindowsIntegration::instance();
694 if (!integration)
695 return;
696
697 QWindowsThemeCache::clearAllThemeCaches();
698
699 const auto oldColorScheme = s_colorScheme;
700 s_colorScheme = Qt::ColorScheme::Unknown; // make effectiveColorScheme() query registry
701 s_colorScheme = effectiveColorScheme();
702 if (s_colorScheme != oldColorScheme) {
703 // Only propagate color scheme changes if the scheme actually changed
705
706 for (QWindowsWindow *w : std::as_const(QWindowsContext::instance()->windows()))
707 w->setDarkBorder(s_colorScheme == Qt::ColorScheme::Dark);
708 }
709
710 // But always reset palette and fonts, and signal the theme
711 // change, as other parts of the theme could have changed,
712 // such as the accent color.
714 QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>();
715}
716
717void QWindowsTheme::clearPalettes()
718{
719 qDeleteAll(m_palettes, m_palettes + NPalettes);
720 std::fill(m_palettes, m_palettes + NPalettes, nullptr);
721}
722
723void QWindowsTheme::refreshPalettes()
724{
725 if (!QGuiApplication::desktopSettingsAware())
726 return;
727 const bool light =
728 effectiveColorScheme() != Qt::ColorScheme::Dark
729 || !QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle);
730 clearPalettes();
731 m_palettes[SystemPalette] = new QPalette(QWindowsTheme::systemPalette(s_colorScheme));
732 m_palettes[ToolTipPalette] = new QPalette(toolTipPalette(*m_palettes[SystemPalette], light, queryHighContrast()));
733 m_palettes[MenuPalette] = new QPalette(menuPalette(*m_palettes[SystemPalette], light));
734 m_palettes[MenuBarPalette] = menuBarPalette(*m_palettes[MenuPalette], light);
735 if (!light) {
736 m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]);
737 m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, qt_accentColor(AccentColorNormal));
738 m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, qt_accentColor(AccentColorLighter));
739 m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, qt_accentColor(AccentColorDarkest));
740 m_palettes[RadioButtonPalette] = new QPalette(*m_palettes[CheckBoxPalette]);
741 }
742}
743
744QPalette QWindowsTheme::systemPalette(Qt::ColorScheme colorScheme)
745{
746 QPalette result = standardPalette();
747
748 switch (colorScheme) {
749 case Qt::ColorScheme::Unknown:
750 // when a high-contrast theme is active or when we fail to read, assume light
751 Q_FALLTHROUGH();
752 case Qt::ColorScheme::Light:
753 populateLightSystemBasePalette(result);
754 break;
755 case Qt::ColorScheme::Dark:
756 populateDarkSystemBasePalette(result);
757 break;
758 }
759
760 if (result.window() != result.base()) {
761 result.setColor(QPalette::Inactive, QPalette::Highlight,
762 result.color(QPalette::Inactive, QPalette::Window));
763 result.setColor(QPalette::Inactive, QPalette::HighlightedText,
764 result.color(QPalette::Inactive, QPalette::Text));
765 result.setColor(QPalette::Inactive, QPalette::Accent,
766 result.color(QPalette::Inactive, QPalette::Window));
767 }
768
769 const QColor disabled = mixColors(result.windowText().color(), result.button().color());
770
771 result.setColorGroup(QPalette::Disabled, result.windowText(), result.button(),
772 result.light(), result.dark(), result.mid(),
773 result.text(), result.brightText(), result.base(),
774 result.window());
775
776 const bool highContrastEnabled = queryHighContrast();
777 const QColor disabledTextColor = highContrastEnabled ? getSysColor(COLOR_GRAYTEXT) : disabled;
778 result.setColor(QPalette::Disabled, QPalette::WindowText, disabledTextColor);
779 result.setColor(QPalette::Disabled, QPalette::Text, disabledTextColor);
780 result.setColor(QPalette::Disabled, QPalette::ButtonText, disabledTextColor);
781 if (highContrastEnabled)
782 result.setColor(QPalette::Disabled, QPalette::Button, result.button().color().darker(150));
783
784 result.setColor(QPalette::Disabled, QPalette::Highlight, result.color(QPalette::Highlight));
785 result.setColor(QPalette::Disabled, QPalette::HighlightedText, result.color(QPalette::HighlightedText));
786 result.setColor(QPalette::Disabled, QPalette::Accent, disabled);
787 result.setColor(QPalette::Disabled, QPalette::Base, result.window().color());
788 return result;
789}
790
791void QWindowsTheme::clearFonts()
792{
793 qDeleteAll(m_fonts, m_fonts + NFonts);
794 std::fill(m_fonts, m_fonts + NFonts, nullptr);
795}
796
798{
799 refreshPalettes();
801}
802
803#ifndef QT_NO_DEBUG_STREAM
804QDebug operator<<(QDebug d, const NONCLIENTMETRICS &m)
805{
806 QDebugStateSaver saver(d);
807 d.nospace();
808 d.noquote();
809 d << "NONCLIENTMETRICS(iMenu=" << m.iMenuWidth << 'x' << m.iMenuHeight
810 << ", lfCaptionFont=";
811 QWindowsFontDatabase::debugFormat(d, m.lfCaptionFont);
812 d << ", lfSmCaptionFont=";
813 QWindowsFontDatabase::debugFormat(d, m.lfSmCaptionFont);
814 d << ", lfMenuFont=";
815 QWindowsFontDatabase::debugFormat(d, m.lfMenuFont);
816 d << ", lfMessageFont=";
817 QWindowsFontDatabase::debugFormat(d, m.lfMessageFont);
818 d <<", lfStatusFont=";
819 QWindowsFontDatabase::debugFormat(d, m.lfStatusFont);
820 d << ')';
821 return d;
822}
823#endif // QT_NO_DEBUG_STREAM
824
826{
827 clearFonts();
828 if (!QGuiApplication::desktopSettingsAware())
829 return;
830
831 const int dpi = 96;
832 NONCLIENTMETRICS ncm;
833 QWindowsContext::nonClientMetrics(&ncm, dpi);
834 qCDebug(lcQpaWindow) << __FUNCTION__ << ncm;
835
836 const QFont menuFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMenuFont, dpi);
837 const QFont messageBoxFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont, dpi);
838 const QFont statusFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfStatusFont, dpi);
839 const QFont titleFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfCaptionFont, dpi);
840 QFont fixedFont(QStringLiteral("Courier New"), messageBoxFont.pointSize());
841 fixedFont.setStyleHint(QFont::TypeWriter);
842
843 LOGFONT lfIconTitleFont;
844 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi);
845 const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont, dpi);
846
847 m_fonts[SystemFont] = new QFont(QWindowsFontDatabase::systemDefaultFont());
848 m_fonts[MenuFont] = new QFont(menuFont);
849 m_fonts[MenuBarFont] = new QFont(menuFont);
850 m_fonts[MessageBoxFont] = new QFont(messageBoxFont);
851 m_fonts[TipLabelFont] = new QFont(statusFont);
852 m_fonts[StatusBarFont] = new QFont(statusFont);
853 m_fonts[MdiSubWindowTitleFont] = new QFont(titleFont);
854 m_fonts[DockWidgetTitleFont] = new QFont(titleFont);
855 m_fonts[ItemViewFont] = new QFont(iconTitleFont);
856 m_fonts[FixedFont] = new QFont(fixedFont);
857}
858
860 // Standard icons obtainable via shGetFileInfo(), SHGFI_SMALLICON, SHGFI_LARGEICON
862 // Larger icons obtainable via SHGetImageList()
864 JumboFileIcon, // Vista onwards
866};
867
868bool QWindowsTheme::usePlatformNativeDialog(DialogType type) const
869{
870 return QWindowsDialogs::useHelper(type);
871}
872
874{
875 return QWindowsDialogs::createHelper(type);
876}
877
878#if QT_CONFIG(systemtrayicon)
879QPlatformSystemTrayIcon *QWindowsTheme::createPlatformSystemTrayIcon() const
880{
881 return new QWindowsSystemTrayIcon;
882}
883#endif
884
886
887void QWindowsTheme::refreshIconPixmapSizes()
888{
889 // Standard sizes: 16, 32, 48, 256
890 fileIconSizes[SmallFileIcon] = GetSystemMetrics(SM_CXSMICON); // corresponds to SHGFI_SMALLICON);
891 fileIconSizes[LargeFileIcon] = GetSystemMetrics(SM_CXICON); // corresponds to SHGFI_LARGEICON
894 fileIconSizes[JumboFileIcon] = 8 * fileIconSizes[LargeFileIcon]; // empirical, has not been observed to work
895
896 int *availEnd = fileIconSizes + JumboFileIcon + 1;
897 m_fileIconSizes = QAbstractFileIconEngine::toSizeList(fileIconSizes, availEnd);
898 qCDebug(lcQpaWindow) << __FUNCTION__ << m_fileIconSizes;
899}
900
901// Defined in qpixmap_win.cpp
902Q_GUI_EXPORT QPixmap qt_pixmapFromWinHICON(HICON icon);
903
904static QPixmap loadIconFromShell32(int resourceId, QSizeF size)
905{
906 HMODULE shell32 = ::GetModuleHandleW(L"shell32.dll");
907 Q_ASSERT(shell32);
908 auto iconHandle =
909 static_cast<HICON>(LoadImage(shell32, MAKEINTRESOURCE(resourceId),
910 IMAGE_ICON, int(size.width()), int(size.height()), 0));
911 if (iconHandle) {
912 QPixmap iconpixmap = qt_pixmapFromWinHICON(iconHandle);
913 DestroyIcon(iconHandle);
914 return iconpixmap;
915 }
916 return QPixmap();
917}
918
919QPixmap QWindowsTheme::standardPixmap(StandardPixmap sp, const QSizeF &pixmapSize) const
920{
921 int resourceId = -1;
922 SHSTOCKICONID stockId = SIID_INVALID;
923 LPCTSTR iconName = nullptr;
924 switch (sp) {
925 case DriveCDIcon:
926 stockId = SIID_DRIVECD;
927 resourceId = 12;
928 break;
929 case DriveDVDIcon:
930 stockId = SIID_DRIVEDVD;
931 resourceId = 12;
932 break;
933 case DriveNetIcon:
934 stockId = SIID_DRIVENET;
935 resourceId = 10;
936 break;
937 case DriveHDIcon:
938 stockId = SIID_DRIVEFIXED;
939 resourceId = 9;
940 break;
941 case DriveFDIcon:
942 stockId = SIID_DRIVE35;
943 resourceId = 7;
944 break;
945 case FileLinkIcon:
946 Q_FALLTHROUGH();
947 case FileIcon:
948 stockId = SIID_DOCNOASSOC;
949 resourceId = 1;
950 break;
951 case DirLinkIcon:
952 Q_FALLTHROUGH();
953 case DirClosedIcon:
954 case DirIcon:
955 stockId = SIID_FOLDER;
956 resourceId = 4;
957 break;
958 case DesktopIcon:
959 resourceId = 35;
960 break;
961 case ComputerIcon:
962 resourceId = 16;
963 break;
964 case DirLinkOpenIcon:
965 Q_FALLTHROUGH();
966 case DirOpenIcon:
967 stockId = SIID_FOLDEROPEN;
968 resourceId = 5;
969 break;
970 case FileDialogNewFolder:
971 stockId = SIID_FOLDER;
972 resourceId = 319;
973 break;
974 case DirHomeIcon:
975 resourceId = 235;
976 break;
977 case TrashIcon:
978 stockId = SIID_RECYCLER;
979 resourceId = 191;
980 break;
981 case MessageBoxInformation:
982 stockId = SIID_INFO;
983 iconName = IDI_INFORMATION;
984 break;
985 case MessageBoxWarning:
986 stockId = SIID_WARNING;
987 iconName = IDI_WARNING;
988 break;
989 case MessageBoxCritical:
990 stockId = SIID_ERROR;
991 iconName = IDI_ERROR;
992 break;
993 case MessageBoxQuestion:
994 stockId = SIID_HELP;
995 iconName = IDI_QUESTION;
996 break;
997 case VistaShield:
998 stockId = SIID_SHIELD;
999 break;
1000 default:
1001 break;
1002 }
1003
1004 // Even with SHGSI_LINKOVERLAY flag set, loaded Icon with SHDefExtractIcon doesn't have
1005 // any overlay, so we avoid SHGSI_LINKOVERLAY flag and draw it manually (QTBUG-131843)
1006 const auto drawLinkOverlayIconIfNeeded = [](StandardPixmap sp, QPixmap &pixmap, QSizeF pixmapSize) {
1007 if (sp == FileLinkIcon || sp == DirLinkIcon || sp == DirLinkOpenIcon) {
1008 QPainter painter(&pixmap);
1009 const QSizeF linkSize = pixmapSize / (pixmapSize.height() >= 48 ? 3 : 2);
1010 static constexpr auto LinkOverlayIconId = 16769;
1011 const QPixmap link = loadIconFromShell32(LinkOverlayIconId, linkSize.toSize());
1012 const int yPos = pixmap.height() - link.size().height();
1013 painter.drawPixmap(0, yPos, int(linkSize.width()), int(linkSize.height()), link);
1014 }
1015 };
1016
1017 if (stockId != SIID_INVALID) {
1018 SHSTOCKICONINFO iconInfo;
1019 memset(&iconInfo, 0, sizeof(iconInfo));
1020 iconInfo.cbSize = sizeof(iconInfo);
1021 constexpr UINT stockFlags = SHGSI_ICONLOCATION;
1022 if (SHGetStockIconInfo(stockId, stockFlags, &iconInfo) == S_OK) {
1023 const auto iconSize = pixmapSize.width();
1024 HICON icon;
1025 if (SHDefExtractIcon(iconInfo.szPath, iconInfo.iIcon, 0, &icon, nullptr, iconSize) == S_OK) {
1026 QPixmap pixmap = qt_pixmapFromWinHICON(icon);
1027 DestroyIcon(icon);
1028 if (!pixmap.isNull()) {
1029 drawLinkOverlayIconIfNeeded(sp, pixmap, pixmap.size());
1030 return pixmap;
1031 }
1032 }
1033 }
1034 }
1035
1036 if (resourceId != -1) {
1037 QPixmap pixmap = loadIconFromShell32(resourceId, pixmapSize);
1038 if (!pixmap.isNull()) {
1039 drawLinkOverlayIconIfNeeded(sp, pixmap, pixmapSize);
1040 return pixmap;
1041 }
1042 }
1043
1044 if (iconName) {
1045 HICON iconHandle = LoadIcon(nullptr, iconName);
1046 QPixmap pixmap = qt_pixmapFromWinHICON(iconHandle);
1047 DestroyIcon(iconHandle);
1048 if (!pixmap.isNull())
1049 return pixmap;
1050 }
1051
1052 return QPlatformTheme::standardPixmap(sp, pixmapSize);
1053}
1054
1055enum { // Shell image list ids
1056 sHIL_EXTRALARGE = 0x2, // 48x48 or user-defined
1057 sHIL_JUMBO = 0x4 // 256x256 (Vista or later)
1058};
1059
1060static QString dirIconPixmapCacheKey(int iIcon, int iconSize, int imageListSize)
1061{
1062 QString key = "qt_dir_"_L1 + QString::number(iIcon);
1063 if (iconSize == SHGFI_LARGEICON)
1064 key += u'l';
1065 switch (imageListSize) {
1066 case sHIL_EXTRALARGE:
1067 key += u'e';
1068 break;
1069 case sHIL_JUMBO:
1070 key += u'j';
1071 break;
1072 }
1073 return key;
1074}
1075
1076template <typename T>
1078{
1079public:
1080
1081 static_assert(sizeof(T) <= sizeof(void *), "FakePointers can only go that far.");
1082
1083 static FakePointer *create(T thing)
1084 {
1085 return reinterpret_cast<FakePointer *>(qintptr(thing));
1086 }
1087
1088 T operator * () const
1089 {
1090 return T(qintptr(this));
1091 }
1092
1093 void operator delete (void *) {}
1094};
1095
1096// Shell image list helper functions.
1097
1098static QPixmap pixmapFromShellImageList(int iImageList, int iIcon)
1099{
1100 QPixmap result;
1101 // For MinGW:
1102 static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}};
1103
1104 IImageList *imageList = nullptr;
1105 HRESULT hr = SHGetImageList(iImageList, iID_IImageList, reinterpret_cast<void **>(&imageList));
1106 if (hr != S_OK)
1107 return result;
1108 HICON hIcon;
1109 hr = imageList->GetIcon(iIcon, ILD_TRANSPARENT, &hIcon);
1110 if (hr == S_OK) {
1111 result = qt_pixmapFromWinHICON(hIcon);
1112 DestroyIcon(hIcon);
1113 }
1114 imageList->Release();
1115 return result;
1116}
1117
1119{
1120public:
1121 explicit QWindowsFileIconEngine(const QFileInfo &info, QPlatformTheme::IconOptions opts) :
1123
1124 QList<QSize> availableSizes(QIcon::Mode = QIcon::Normal, QIcon::State = QIcon::Off) override
1125 { return QWindowsTheme::instance()->availableFileIconSizes(); }
1126
1127protected:
1128 QString cacheKey() const override;
1129 QPixmap filePixmap(const QSize &size, QIcon::Mode mode, QIcon::State) override;
1130};
1131
1133{
1134 // Cache directories unless custom or drives, which have custom icons depending on type
1135 if ((options() & QPlatformTheme::DontUseCustomDirectoryIcons) && fileInfo().isDir() && !fileInfo().isRoot())
1136 return QStringLiteral("qt_/directory/");
1137 if (!fileInfo().isFile())
1138 return QString();
1139 // Return "" for .exe, .lnk and .ico extensions.
1140 // It is faster to just look at the file extensions;
1141 // avoiding slow QFileInfo::isExecutable() (QTBUG-13182)
1142 QString suffix = fileInfo().suffix();
1143 if (!suffix.compare(u"exe", Qt::CaseInsensitive)
1144 || !suffix.compare(u"lnk", Qt::CaseInsensitive)
1145 || !suffix.compare(u"ico", Qt::CaseInsensitive)) {
1146 return QString();
1147 }
1148 return "qt_."_L1
1149 + (suffix.isEmpty() ? fileInfo().fileName() : std::move(suffix).toUpper()); // handle "Makefile" ;)
1150}
1151
1153{
1154 QComHelper comHelper;
1155
1156 static QCache<QString, FakePointer<int> > dirIconEntryCache(1000);
1157 Q_CONSTINIT static QMutex mx;
1158 static int defaultFolderIIcon = -1;
1159 const bool useDefaultFolderIcon = options() & QPlatformTheme::DontUseCustomDirectoryIcons;
1160
1161 QPixmap pixmap;
1162 const QString filePath = QDir::toNativeSeparators(fileInfo().filePath());
1163 const int width = int(size.width());
1164 const int iconSize = width > fileIconSizes[SmallFileIcon] ? SHGFI_LARGEICON : SHGFI_SMALLICON;
1165 const int requestedImageListSize =
1167 ? sHIL_JUMBO
1168 : (width > fileIconSizes[LargeFileIcon] ? sHIL_EXTRALARGE : 0);
1169 bool cacheableDirIcon = fileInfo().isDir() && !fileInfo().isRoot();
1170 if (cacheableDirIcon) {
1171 QMutexLocker locker(&mx);
1172 int iIcon = (useDefaultFolderIcon && defaultFolderIIcon >= 0) ? defaultFolderIIcon
1173 : **dirIconEntryCache.object(filePath);
1174 if (iIcon) {
1175 QPixmapCache::find(dirIconPixmapCacheKey(iIcon, iconSize, requestedImageListSize),
1176 &pixmap);
1177 if (pixmap.isNull()) // Let's keep both caches in sync
1178 dirIconEntryCache.remove(filePath);
1179 else
1180 return pixmap;
1181 }
1182 }
1183
1184 unsigned int flags = SHGFI_ICON | iconSize | SHGFI_SYSICONINDEX | SHGFI_ADDOVERLAYS | SHGFI_OVERLAYINDEX;
1185 DWORD attributes = 0;
1186 QString path = filePath;
1187 if (cacheableDirIcon && useDefaultFolderIcon) {
1188 flags |= SHGFI_USEFILEATTRIBUTES;
1189 attributes |= FILE_ATTRIBUTE_DIRECTORY;
1190 path = QStringLiteral("dummy");
1191 } else if (!fileInfo().exists()) {
1192 flags |= SHGFI_USEFILEATTRIBUTES;
1193 attributes |= FILE_ATTRIBUTE_NORMAL;
1194 }
1195 auto task = QSharedPointer<QShGetFileInfoThread::Task>(
1196 new QShGetFileInfoThread::Task(path, attributes, flags));
1197 s_shGetFileInfoThread()->runWithParams(task);
1198 // Even if GetFileInfo returns a valid result, hIcon can be empty in some cases
1199 if (task->resultValid()) {
1200 QString key;
1201 if (cacheableDirIcon) {
1202 if (useDefaultFolderIcon && defaultFolderIIcon < 0)
1203 defaultFolderIIcon = task->iIcon;
1204
1205 //using the unique icon index provided by windows save us from duplicate keys
1206 key = dirIconPixmapCacheKey(task->iIcon, iconSize, requestedImageListSize);
1207 QPixmapCache::find(key, &pixmap);
1208 if (!pixmap.isNull()) {
1209 QMutexLocker locker(&mx);
1210 dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
1211 }
1212 }
1213
1214 if (pixmap.isNull()) {
1215 if (requestedImageListSize) {
1216 pixmap = pixmapFromShellImageList(requestedImageListSize, task->iIcon);
1217 if (pixmap.isNull() && requestedImageListSize == sHIL_JUMBO)
1218 pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, task->iIcon);
1219 }
1220 if (pixmap.isNull())
1221 pixmap = qt_pixmapFromWinHICON(task->hIcon);
1222 if (!pixmap.isNull()) {
1223 if (cacheableDirIcon) {
1224 QMutexLocker locker(&mx);
1225 QPixmapCache::insert(key, pixmap);
1226 dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
1227 }
1228 } else {
1229 qWarning("QWindowsTheme::fileIconPixmap() no icon found");
1230 }
1231 }
1232 }
1233
1234 return pixmap;
1235}
1236
1237QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions) const
1238{
1239 return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions));
1240}
1241
1242QIconEngine *QWindowsTheme::createIconEngine(const QString &iconName) const
1243{
1244 return new QWindowsIconEngine(iconName);
1245}
1246
1247static inline bool doUseNativeMenus()
1248{
1249 const unsigned options = QWindowsIntegration::instance()->options();
1250 if ((options & QWindowsIntegration::NoNativeMenus) != 0)
1251 return false;
1252 if ((options & QWindowsIntegration::AlwaysUseNativeMenus) != 0)
1253 return true;
1254 // "Auto" mode: For non-widget or Quick Controls 2 applications
1255 if (!QCoreApplication::instance()->inherits("QApplication"))
1256 return true;
1257 const QWindowList &topLevels = QGuiApplication::topLevelWindows();
1258 for (const QWindow *t : topLevels) {
1259 if (t->inherits("QQuickApplicationWindow"))
1260 return true;
1261 }
1262 return false;
1263}
1264
1266{
1267 static const bool result = doUseNativeMenus();
1268 return result;
1269}
1270
1271Qt::ColorScheme QWindowsTheme::queryColorScheme()
1272{
1273 if (queryHighContrast())
1274 return Qt::ColorScheme::Unknown;
1275
1276 QWinRegistryKey personalizeKey{
1277 HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)"
1278 };
1279 const bool useDarkTheme = personalizeKey.value<DWORD>(L"AppsUseLightTheme") == 0;
1280 return useDarkTheme ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
1281}
1282
1284{
1285 HIGHCONTRAST hcf = {};
1286 hcf.cbSize = static_cast<UINT>(sizeof(HIGHCONTRAST));
1287 if (SystemParametersInfo(SPI_GETHIGHCONTRAST, hcf.cbSize, &hcf, FALSE))
1288 return hcf.dwFlags & HCF_HIGHCONTRASTON;
1289 return false;
1290}
1291
1293{
1294 qCDebug(lcQpaMenus) << __FUNCTION__;
1296}
1297
1299{
1300 qCDebug(lcQpaMenus) << __FUNCTION__;
1301 // We create a popup menu here, since it will likely be used as context
1302 // menu. Submenus should be created the factory functions of
1303 // QPlatformMenu/Bar. Note though that Quick Controls 1 will use this
1304 // function for submenus as well, but this has been found to work.
1306}
1307
1309{
1310 qCDebug(lcQpaMenus) << __FUNCTION__;
1311 return QWindowsTheme::useNativeMenus() ? new QWindowsMenuBar : nullptr;
1312}
1313
1315{
1316 qCDebug(lcQpaMenus) << __FUNCTION__;
1317}
1318
1319QT_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:28
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
Qt::MotionPreference motionPreference() const 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)