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