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 default:
647 break;
648 }
649 return QPlatformTheme::themeHint(hint);
650}
651
653{
654 return QWindowsTheme::effectiveColorScheme();
655}
656
657Qt::ColorScheme QWindowsTheme::effectiveColorScheme()
658{
659 auto integration = QWindowsIntegration::instance();
660 if (queryHighContrast())
661 return Qt::ColorScheme::Unknown;
662 if (s_colorSchemeOverride != Qt::ColorScheme::Unknown)
663 return s_colorSchemeOverride;
664 if (s_colorScheme != Qt::ColorScheme::Unknown)
665 return s_colorScheme;
666 if (!integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle))
667 return Qt::ColorScheme::Light;
668 return queryColorScheme();
669}
670
671void QWindowsTheme::requestColorScheme(Qt::ColorScheme scheme)
672{
673 s_colorSchemeOverride = scheme;
675}
676
678{
679 return queryHighContrast() ? Qt::ContrastPreference::HighContrast
680 : Qt::ContrastPreference::NoPreference;
681}
682
684{
685 return booleanSystemParametersInfo(SPI_GETCLIENTAREAANIMATION, false) ? Qt::MotionPreference::NoPreference
686 : Qt::MotionPreference::ReducedMotion;
687}
688
690{
691 auto *integration = QWindowsIntegration::instance();
692 if (!integration)
693 return;
694
695 QWindowsThemeCache::clearAllThemeCaches();
696
697 const auto oldColorScheme = s_colorScheme;
698 s_colorScheme = Qt::ColorScheme::Unknown; // make effectiveColorScheme() query registry
699 s_colorScheme = effectiveColorScheme();
700 if (s_colorScheme != oldColorScheme) {
701 // Only propagate color scheme changes if the scheme actually changed
703
704 for (QWindowsWindow *w : std::as_const(QWindowsContext::instance()->windows()))
705 w->setDarkBorder(s_colorScheme == Qt::ColorScheme::Dark);
706 }
707
708 // But always reset palette and fonts, and signal the theme
709 // change, as other parts of the theme could have changed,
710 // such as the accent color.
712 QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>();
713}
714
715void QWindowsTheme::clearPalettes()
716{
717 qDeleteAll(m_palettes, m_palettes + NPalettes);
718 std::fill(m_palettes, m_palettes + NPalettes, nullptr);
719}
720
721void QWindowsTheme::refreshPalettes()
722{
723 if (!QGuiApplication::desktopSettingsAware())
724 return;
725 const bool light =
726 effectiveColorScheme() != Qt::ColorScheme::Dark
727 || !QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle);
728 clearPalettes();
729 m_palettes[SystemPalette] = new QPalette(QWindowsTheme::systemPalette(s_colorScheme));
730 m_palettes[ToolTipPalette] = new QPalette(toolTipPalette(*m_palettes[SystemPalette], light, queryHighContrast()));
731 m_palettes[MenuPalette] = new QPalette(menuPalette(*m_palettes[SystemPalette], light));
732 m_palettes[MenuBarPalette] = menuBarPalette(*m_palettes[MenuPalette], light);
733 if (!light) {
734 m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]);
735 m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, qt_accentColor(AccentColorNormal));
736 m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, qt_accentColor(AccentColorLighter));
737 m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, qt_accentColor(AccentColorDarkest));
738 m_palettes[RadioButtonPalette] = new QPalette(*m_palettes[CheckBoxPalette]);
739 }
740}
741
742QPalette QWindowsTheme::systemPalette(Qt::ColorScheme colorScheme)
743{
744 QPalette result = standardPalette();
745
746 switch (colorScheme) {
747 case Qt::ColorScheme::Unknown:
748 // when a high-contrast theme is active or when we fail to read, assume light
749 Q_FALLTHROUGH();
750 case Qt::ColorScheme::Light:
751 populateLightSystemBasePalette(result);
752 break;
753 case Qt::ColorScheme::Dark:
754 populateDarkSystemBasePalette(result);
755 break;
756 }
757
758 if (result.window() != result.base()) {
759 result.setColor(QPalette::Inactive, QPalette::Highlight,
760 result.color(QPalette::Inactive, QPalette::Window));
761 result.setColor(QPalette::Inactive, QPalette::HighlightedText,
762 result.color(QPalette::Inactive, QPalette::Text));
763 result.setColor(QPalette::Inactive, QPalette::Accent,
764 result.color(QPalette::Inactive, QPalette::Window));
765 }
766
767 const QColor disabled = mixColors(result.windowText().color(), result.button().color());
768
769 result.setColorGroup(QPalette::Disabled, result.windowText(), result.button(),
770 result.light(), result.dark(), result.mid(),
771 result.text(), result.brightText(), result.base(),
772 result.window());
773
774 const bool highContrastEnabled = queryHighContrast();
775 const QColor disabledTextColor = highContrastEnabled ? getSysColor(COLOR_GRAYTEXT) : disabled;
776 result.setColor(QPalette::Disabled, QPalette::WindowText, disabledTextColor);
777 result.setColor(QPalette::Disabled, QPalette::Text, disabledTextColor);
778 result.setColor(QPalette::Disabled, QPalette::ButtonText, disabledTextColor);
779 if (highContrastEnabled)
780 result.setColor(QPalette::Disabled, QPalette::Button, result.button().color().darker(150));
781
782 result.setColor(QPalette::Disabled, QPalette::Highlight, result.color(QPalette::Highlight));
783 result.setColor(QPalette::Disabled, QPalette::HighlightedText, result.color(QPalette::HighlightedText));
784 result.setColor(QPalette::Disabled, QPalette::Accent, disabled);
785 result.setColor(QPalette::Disabled, QPalette::Base, result.window().color());
786 return result;
787}
788
789void QWindowsTheme::clearFonts()
790{
791 qDeleteAll(m_fonts, m_fonts + NFonts);
792 std::fill(m_fonts, m_fonts + NFonts, nullptr);
793}
794
796{
797 refreshPalettes();
799}
800
801#ifndef QT_NO_DEBUG_STREAM
802QDebug operator<<(QDebug d, const NONCLIENTMETRICS &m)
803{
804 QDebugStateSaver saver(d);
805 d.nospace();
806 d.noquote();
807 d << "NONCLIENTMETRICS(iMenu=" << m.iMenuWidth << 'x' << m.iMenuHeight
808 << ", lfCaptionFont=";
809 QWindowsFontDatabase::debugFormat(d, m.lfCaptionFont);
810 d << ", lfSmCaptionFont=";
811 QWindowsFontDatabase::debugFormat(d, m.lfSmCaptionFont);
812 d << ", lfMenuFont=";
813 QWindowsFontDatabase::debugFormat(d, m.lfMenuFont);
814 d << ", lfMessageFont=";
815 QWindowsFontDatabase::debugFormat(d, m.lfMessageFont);
816 d <<", lfStatusFont=";
817 QWindowsFontDatabase::debugFormat(d, m.lfStatusFont);
818 d << ')';
819 return d;
820}
821#endif // QT_NO_DEBUG_STREAM
822
824{
825 clearFonts();
826 if (!QGuiApplication::desktopSettingsAware())
827 return;
828
829 const int dpi = 96;
830 NONCLIENTMETRICS ncm;
831 QWindowsContext::nonClientMetrics(&ncm, dpi);
832 qCDebug(lcQpaWindow) << __FUNCTION__ << ncm;
833
834 const QFont menuFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMenuFont, dpi);
835 const QFont messageBoxFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont, dpi);
836 const QFont statusFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfStatusFont, dpi);
837 const QFont titleFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfCaptionFont, dpi);
838 QFont fixedFont(QStringLiteral("Courier New"), messageBoxFont.pointSize());
839 fixedFont.setStyleHint(QFont::TypeWriter);
840
841 LOGFONT lfIconTitleFont;
842 SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi);
843 const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont, dpi);
844
845 m_fonts[SystemFont] = new QFont(QWindowsFontDatabase::systemDefaultFont());
846 m_fonts[MenuFont] = new QFont(menuFont);
847 m_fonts[MenuBarFont] = new QFont(menuFont);
848 m_fonts[MessageBoxFont] = new QFont(messageBoxFont);
849 m_fonts[TipLabelFont] = new QFont(statusFont);
850 m_fonts[StatusBarFont] = new QFont(statusFont);
851 m_fonts[MdiSubWindowTitleFont] = new QFont(titleFont);
852 m_fonts[DockWidgetTitleFont] = new QFont(titleFont);
853 m_fonts[ItemViewFont] = new QFont(iconTitleFont);
854 m_fonts[FixedFont] = new QFont(fixedFont);
855}
856
858 // Standard icons obtainable via shGetFileInfo(), SHGFI_SMALLICON, SHGFI_LARGEICON
860 // Larger icons obtainable via SHGetImageList()
862 JumboFileIcon, // Vista onwards
864};
865
866bool QWindowsTheme::usePlatformNativeDialog(DialogType type) const
867{
868 return QWindowsDialogs::useHelper(type);
869}
870
872{
873 return QWindowsDialogs::createHelper(type);
874}
875
876#if QT_CONFIG(systemtrayicon)
877QPlatformSystemTrayIcon *QWindowsTheme::createPlatformSystemTrayIcon() const
878{
879 return new QWindowsSystemTrayIcon;
880}
881#endif
882
884
885void QWindowsTheme::refreshIconPixmapSizes()
886{
887 // Standard sizes: 16, 32, 48, 256
888 fileIconSizes[SmallFileIcon] = GetSystemMetrics(SM_CXSMICON); // corresponds to SHGFI_SMALLICON);
889 fileIconSizes[LargeFileIcon] = GetSystemMetrics(SM_CXICON); // corresponds to SHGFI_LARGEICON
892 fileIconSizes[JumboFileIcon] = 8 * fileIconSizes[LargeFileIcon]; // empirical, has not been observed to work
893
894 int *availEnd = fileIconSizes + JumboFileIcon + 1;
895 m_fileIconSizes = QAbstractFileIconEngine::toSizeList(fileIconSizes, availEnd);
896 qCDebug(lcQpaWindow) << __FUNCTION__ << m_fileIconSizes;
897}
898
899// Defined in qpixmap_win.cpp
900Q_GUI_EXPORT QPixmap qt_pixmapFromWinHICON(HICON icon);
901
902static QPixmap loadIconFromShell32(int resourceId, QSizeF size)
903{
904 HMODULE shell32 = ::GetModuleHandleW(L"shell32.dll");
905 Q_ASSERT(shell32);
906 auto iconHandle =
907 static_cast<HICON>(LoadImage(shell32, MAKEINTRESOURCE(resourceId),
908 IMAGE_ICON, int(size.width()), int(size.height()), 0));
909 if (iconHandle) {
910 QPixmap iconpixmap = qt_pixmapFromWinHICON(iconHandle);
911 DestroyIcon(iconHandle);
912 return iconpixmap;
913 }
914 return QPixmap();
915}
916
917QPixmap QWindowsTheme::standardPixmap(StandardPixmap sp, const QSizeF &pixmapSize) const
918{
919 int resourceId = -1;
920 SHSTOCKICONID stockId = SIID_INVALID;
921 LPCTSTR iconName = nullptr;
922 switch (sp) {
923 case DriveCDIcon:
924 stockId = SIID_DRIVECD;
925 resourceId = 12;
926 break;
927 case DriveDVDIcon:
928 stockId = SIID_DRIVEDVD;
929 resourceId = 12;
930 break;
931 case DriveNetIcon:
932 stockId = SIID_DRIVENET;
933 resourceId = 10;
934 break;
935 case DriveHDIcon:
936 stockId = SIID_DRIVEFIXED;
937 resourceId = 9;
938 break;
939 case DriveFDIcon:
940 stockId = SIID_DRIVE35;
941 resourceId = 7;
942 break;
943 case FileLinkIcon:
944 Q_FALLTHROUGH();
945 case FileIcon:
946 stockId = SIID_DOCNOASSOC;
947 resourceId = 1;
948 break;
949 case DirLinkIcon:
950 Q_FALLTHROUGH();
951 case DirClosedIcon:
952 case DirIcon:
953 stockId = SIID_FOLDER;
954 resourceId = 4;
955 break;
956 case DesktopIcon:
957 resourceId = 35;
958 break;
959 case ComputerIcon:
960 resourceId = 16;
961 break;
962 case DirLinkOpenIcon:
963 Q_FALLTHROUGH();
964 case DirOpenIcon:
965 stockId = SIID_FOLDEROPEN;
966 resourceId = 5;
967 break;
968 case FileDialogNewFolder:
969 stockId = SIID_FOLDER;
970 resourceId = 319;
971 break;
972 case DirHomeIcon:
973 resourceId = 235;
974 break;
975 case TrashIcon:
976 stockId = SIID_RECYCLER;
977 resourceId = 191;
978 break;
979 case MessageBoxInformation:
980 stockId = SIID_INFO;
981 iconName = IDI_INFORMATION;
982 break;
983 case MessageBoxWarning:
984 stockId = SIID_WARNING;
985 iconName = IDI_WARNING;
986 break;
987 case MessageBoxCritical:
988 stockId = SIID_ERROR;
989 iconName = IDI_ERROR;
990 break;
991 case MessageBoxQuestion:
992 stockId = SIID_HELP;
993 iconName = IDI_QUESTION;
994 break;
995 case VistaShield:
996 stockId = SIID_SHIELD;
997 break;
998 default:
999 break;
1000 }
1001
1002 // Even with SHGSI_LINKOVERLAY flag set, loaded Icon with SHDefExtractIcon doesn't have
1003 // any overlay, so we avoid SHGSI_LINKOVERLAY flag and draw it manually (QTBUG-131843)
1004 const auto drawLinkOverlayIconIfNeeded = [](StandardPixmap sp, QPixmap &pixmap, QSizeF pixmapSize) {
1005 if (sp == FileLinkIcon || sp == DirLinkIcon || sp == DirLinkOpenIcon) {
1006 QPainter painter(&pixmap);
1007 const QSizeF linkSize = pixmapSize / (pixmapSize.height() >= 48 ? 3 : 2);
1008 static constexpr auto LinkOverlayIconId = 16769;
1009 const QPixmap link = loadIconFromShell32(LinkOverlayIconId, linkSize.toSize());
1010 const int yPos = pixmap.height() - link.size().height();
1011 painter.drawPixmap(0, yPos, int(linkSize.width()), int(linkSize.height()), link);
1012 }
1013 };
1014
1015 if (stockId != SIID_INVALID) {
1016 SHSTOCKICONINFO iconInfo;
1017 memset(&iconInfo, 0, sizeof(iconInfo));
1018 iconInfo.cbSize = sizeof(iconInfo);
1019 constexpr UINT stockFlags = SHGSI_ICONLOCATION;
1020 if (SHGetStockIconInfo(stockId, stockFlags, &iconInfo) == S_OK) {
1021 const auto iconSize = pixmapSize.width();
1022 HICON icon;
1023 if (SHDefExtractIcon(iconInfo.szPath, iconInfo.iIcon, 0, &icon, nullptr, iconSize) == S_OK) {
1024 QPixmap pixmap = qt_pixmapFromWinHICON(icon);
1025 DestroyIcon(icon);
1026 if (!pixmap.isNull()) {
1027 drawLinkOverlayIconIfNeeded(sp, pixmap, pixmap.size());
1028 return pixmap;
1029 }
1030 }
1031 }
1032 }
1033
1034 if (resourceId != -1) {
1035 QPixmap pixmap = loadIconFromShell32(resourceId, pixmapSize);
1036 if (!pixmap.isNull()) {
1037 drawLinkOverlayIconIfNeeded(sp, pixmap, pixmapSize);
1038 return pixmap;
1039 }
1040 }
1041
1042 if (iconName) {
1043 HICON iconHandle = LoadIcon(nullptr, iconName);
1044 QPixmap pixmap = qt_pixmapFromWinHICON(iconHandle);
1045 DestroyIcon(iconHandle);
1046 if (!pixmap.isNull())
1047 return pixmap;
1048 }
1049
1050 return QPlatformTheme::standardPixmap(sp, pixmapSize);
1051}
1052
1053enum { // Shell image list ids
1054 sHIL_EXTRALARGE = 0x2, // 48x48 or user-defined
1055 sHIL_JUMBO = 0x4 // 256x256 (Vista or later)
1056};
1057
1058static QString dirIconPixmapCacheKey(int iIcon, int iconSize, int imageListSize)
1059{
1060 QString key = "qt_dir_"_L1 + QString::number(iIcon);
1061 if (iconSize == SHGFI_LARGEICON)
1062 key += u'l';
1063 switch (imageListSize) {
1064 case sHIL_EXTRALARGE:
1065 key += u'e';
1066 break;
1067 case sHIL_JUMBO:
1068 key += u'j';
1069 break;
1070 }
1071 return key;
1072}
1073
1074template <typename T>
1076{
1077public:
1078
1079 static_assert(sizeof(T) <= sizeof(void *), "FakePointers can only go that far.");
1080
1081 static FakePointer *create(T thing)
1082 {
1083 return reinterpret_cast<FakePointer *>(qintptr(thing));
1084 }
1085
1086 T operator * () const
1087 {
1088 return T(qintptr(this));
1089 }
1090
1091 void operator delete (void *) {}
1092};
1093
1094// Shell image list helper functions.
1095
1096static QPixmap pixmapFromShellImageList(int iImageList, int iIcon)
1097{
1098 QPixmap result;
1099 // For MinGW:
1100 static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}};
1101
1102 IImageList *imageList = nullptr;
1103 HRESULT hr = SHGetImageList(iImageList, iID_IImageList, reinterpret_cast<void **>(&imageList));
1104 if (hr != S_OK)
1105 return result;
1106 HICON hIcon;
1107 hr = imageList->GetIcon(iIcon, ILD_TRANSPARENT, &hIcon);
1108 if (hr == S_OK) {
1109 result = qt_pixmapFromWinHICON(hIcon);
1110 DestroyIcon(hIcon);
1111 }
1112 imageList->Release();
1113 return result;
1114}
1115
1117{
1118public:
1119 explicit QWindowsFileIconEngine(const QFileInfo &info, QPlatformTheme::IconOptions opts) :
1121
1122 QList<QSize> availableSizes(QIcon::Mode = QIcon::Normal, QIcon::State = QIcon::Off) override
1123 { return QWindowsTheme::instance()->availableFileIconSizes(); }
1124
1125protected:
1126 QString cacheKey() const override;
1127 QPixmap filePixmap(const QSize &size, QIcon::Mode mode, QIcon::State) override;
1128};
1129
1131{
1132 // Cache directories unless custom or drives, which have custom icons depending on type
1133 if ((options() & QPlatformTheme::DontUseCustomDirectoryIcons) && fileInfo().isDir() && !fileInfo().isRoot())
1134 return QStringLiteral("qt_/directory/");
1135 if (!fileInfo().isFile())
1136 return QString();
1137 // Return "" for .exe, .lnk and .ico extensions.
1138 // It is faster to just look at the file extensions;
1139 // avoiding slow QFileInfo::isExecutable() (QTBUG-13182)
1140 QString suffix = fileInfo().suffix();
1141 if (!suffix.compare(u"exe", Qt::CaseInsensitive)
1142 || !suffix.compare(u"lnk", Qt::CaseInsensitive)
1143 || !suffix.compare(u"ico", Qt::CaseInsensitive)) {
1144 return QString();
1145 }
1146 return "qt_."_L1
1147 + (suffix.isEmpty() ? fileInfo().fileName() : std::move(suffix).toUpper()); // handle "Makefile" ;)
1148}
1149
1151{
1152 QComHelper comHelper;
1153
1154 static QCache<QString, FakePointer<int> > dirIconEntryCache(1000);
1155 Q_CONSTINIT static QMutex mx;
1156 static int defaultFolderIIcon = -1;
1157 const bool useDefaultFolderIcon = options() & QPlatformTheme::DontUseCustomDirectoryIcons;
1158
1159 QPixmap pixmap;
1160 const QString filePath = QDir::toNativeSeparators(fileInfo().filePath());
1161 const int width = int(size.width());
1162 const int iconSize = width > fileIconSizes[SmallFileIcon] ? SHGFI_LARGEICON : SHGFI_SMALLICON;
1163 const int requestedImageListSize =
1165 ? sHIL_JUMBO
1166 : (width > fileIconSizes[LargeFileIcon] ? sHIL_EXTRALARGE : 0);
1167 bool cacheableDirIcon = fileInfo().isDir() && !fileInfo().isRoot();
1168 if (cacheableDirIcon) {
1169 QMutexLocker locker(&mx);
1170 int iIcon = (useDefaultFolderIcon && defaultFolderIIcon >= 0) ? defaultFolderIIcon
1171 : **dirIconEntryCache.object(filePath);
1172 if (iIcon) {
1173 QPixmapCache::find(dirIconPixmapCacheKey(iIcon, iconSize, requestedImageListSize),
1174 &pixmap);
1175 if (pixmap.isNull()) // Let's keep both caches in sync
1176 dirIconEntryCache.remove(filePath);
1177 else
1178 return pixmap;
1179 }
1180 }
1181
1182 unsigned int flags = SHGFI_ICON | iconSize | SHGFI_SYSICONINDEX | SHGFI_ADDOVERLAYS | SHGFI_OVERLAYINDEX;
1183 DWORD attributes = 0;
1184 QString path = filePath;
1185 if (cacheableDirIcon && useDefaultFolderIcon) {
1186 flags |= SHGFI_USEFILEATTRIBUTES;
1187 attributes |= FILE_ATTRIBUTE_DIRECTORY;
1188 path = QStringLiteral("dummy");
1189 } else if (!fileInfo().exists()) {
1190 flags |= SHGFI_USEFILEATTRIBUTES;
1191 attributes |= FILE_ATTRIBUTE_NORMAL;
1192 }
1193 auto task = QSharedPointer<QShGetFileInfoThread::Task>(
1194 new QShGetFileInfoThread::Task(path, attributes, flags));
1195 s_shGetFileInfoThread()->runWithParams(task);
1196 // Even if GetFileInfo returns a valid result, hIcon can be empty in some cases
1197 if (task->resultValid()) {
1198 QString key;
1199 if (cacheableDirIcon) {
1200 if (useDefaultFolderIcon && defaultFolderIIcon < 0)
1201 defaultFolderIIcon = task->iIcon;
1202
1203 //using the unique icon index provided by windows save us from duplicate keys
1204 key = dirIconPixmapCacheKey(task->iIcon, iconSize, requestedImageListSize);
1205 QPixmapCache::find(key, &pixmap);
1206 if (!pixmap.isNull()) {
1207 QMutexLocker locker(&mx);
1208 dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
1209 }
1210 }
1211
1212 if (pixmap.isNull()) {
1213 if (requestedImageListSize) {
1214 pixmap = pixmapFromShellImageList(requestedImageListSize, task->iIcon);
1215 if (pixmap.isNull() && requestedImageListSize == sHIL_JUMBO)
1216 pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, task->iIcon);
1217 }
1218 if (pixmap.isNull())
1219 pixmap = qt_pixmapFromWinHICON(task->hIcon);
1220 if (!pixmap.isNull()) {
1221 if (cacheableDirIcon) {
1222 QMutexLocker locker(&mx);
1223 QPixmapCache::insert(key, pixmap);
1224 dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
1225 }
1226 } else {
1227 qWarning("QWindowsTheme::fileIconPixmap() no icon found");
1228 }
1229 }
1230 }
1231
1232 return pixmap;
1233}
1234
1235QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions) const
1236{
1237 return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions));
1238}
1239
1240QIconEngine *QWindowsTheme::createIconEngine(const QString &iconName) const
1241{
1242 return new QWindowsIconEngine(iconName);
1243}
1244
1245static inline bool doUseNativeMenus()
1246{
1247 const unsigned options = QWindowsIntegration::instance()->options();
1248 if ((options & QWindowsIntegration::NoNativeMenus) != 0)
1249 return false;
1250 if ((options & QWindowsIntegration::AlwaysUseNativeMenus) != 0)
1251 return true;
1252 // "Auto" mode: For non-widget or Quick Controls 2 applications
1253 if (!QCoreApplication::instance()->inherits("QApplication"))
1254 return true;
1255 const QWindowList &topLevels = QGuiApplication::topLevelWindows();
1256 for (const QWindow *t : topLevels) {
1257 if (t->inherits("QQuickApplicationWindow"))
1258 return true;
1259 }
1260 return false;
1261}
1262
1264{
1265 static const bool result = doUseNativeMenus();
1266 return result;
1267}
1268
1269Qt::ColorScheme QWindowsTheme::queryColorScheme()
1270{
1271 if (queryHighContrast())
1272 return Qt::ColorScheme::Unknown;
1273
1274 QWinRegistryKey personalizeKey{
1275 HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)"
1276 };
1277 const bool useDarkTheme = personalizeKey.value<DWORD>(L"AppsUseLightTheme") == 0;
1278 return useDarkTheme ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
1279}
1280
1282{
1283 HIGHCONTRAST hcf = {};
1284 hcf.cbSize = static_cast<UINT>(sizeof(HIGHCONTRAST));
1285 if (SystemParametersInfo(SPI_GETHIGHCONTRAST, hcf.cbSize, &hcf, FALSE))
1286 return hcf.dwFlags & HCF_HIGHCONTRASTON;
1287 return false;
1288}
1289
1291{
1292 qCDebug(lcQpaMenus) << __FUNCTION__;
1294}
1295
1297{
1298 qCDebug(lcQpaMenus) << __FUNCTION__;
1299 // We create a popup menu here, since it will likely be used as context
1300 // menu. Submenus should be created the factory functions of
1301 // QPlatformMenu/Bar. Note though that Quick Controls 1 will use this
1302 // function for submenus as well, but this has been found to work.
1304}
1305
1307{
1308 qCDebug(lcQpaMenus) << __FUNCTION__;
1309 return QWindowsTheme::useNativeMenus() ? new QWindowsMenuBar : nullptr;
1310}
1311
1313{
1314 qCDebug(lcQpaMenus) << __FUNCTION__;
1315}
1316
1317QT_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
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)