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