blob: 40f9652cbdbb709787ca8a76f510497569f78bd5 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
// SHSTOCKICONINFO is only available since Vista
#if _WIN32_WINNT < 0x0601
# undef _WIN32_WINNT
# define _WIN32_WINNT 0x0601
#endif
#include "qwindowstheme.h"
#include "qwindowsmenu.h"
#include "qwindowsdialoghelpers.h"
#include "qwindowscontext.h"
#include "qwindowsintegration.h"
#if QT_CONFIG(systemtrayicon)
# include "qwindowssystemtrayicon.h"
#endif
#include "qt_windows.h"
#include <commctrl.h>
#include <objbase.h>
#ifndef Q_CC_MINGW
# include <commoncontrols.h>
#endif
#include <shellapi.h>
#include <QtCore/qvariant.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdebug.h>
#include <QtCore/qtextstream.h>
#include <QtCore/qsysinfo.h>
#include <QtCore/qcache.h>
#include <QtCore/qthread.h>
#include <QtCore/qmutex.h>
#include <QtCore/qwaitcondition.h>
#include <QtGui/qcolor.h>
#include <QtGui/qpalette.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qpainter.h>
#include <QtGui/qpixmapcache.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtThemeSupport/private/qabstractfileiconengine_p.h>
#include <QtFontDatabaseSupport/private/qwindowsfontdatabase_p.h>
#include <private/qhighdpiscaling_p.h>
#include <private/qsystemlibrary_p.h>
#include <algorithm>
#if defined(__IImageList_INTERFACE_DEFINED__) && defined(__IID_DEFINED__)
# define USE_IIMAGELIST
#endif
QT_BEGIN_NAMESPACE
static inline QColor COLORREFToQColor(COLORREF cr)
{
return QColor(GetRValue(cr), GetGValue(cr), GetBValue(cr));
}
static inline QTextStream& operator<<(QTextStream &str, const QColor &c)
{
str.setIntegerBase(16);
str.setFieldWidth(2);
str.setPadChar(QLatin1Char('0'));
str << " rgb: #" << c.red() << c.green() << c.blue();
str.setIntegerBase(10);
str.setFieldWidth(0);
return str;
}
static inline bool booleanSystemParametersInfo(UINT what, bool defaultValue)
{
BOOL result;
if (SystemParametersInfo(what, 0, &result, 0))
return result != FALSE;
return defaultValue;
}
static inline DWORD dWordSystemParametersInfo(UINT what, DWORD defaultValue)
{
DWORD result;
if (SystemParametersInfo(what, 0, &result, 0))
return result;
return defaultValue;
}
static inline QColor mixColors(const QColor &c1, const QColor &c2)
{
return {(c1.red() + c2.red()) / 2,
(c1.green() + c2.green()) / 2,
(c1.blue() + c2.blue()) / 2};
}
static inline QColor getSysColor(int index)
{
return COLORREFToQColor(GetSysColor(index));
}
// QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system
// models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the
// behavior by running it in a thread.
struct QShGetFileInfoParams
{
QShGetFileInfoParams(const QString &fn, DWORD a, SHFILEINFO *i, UINT f, bool *r)
: fileName(fn), attributes(a), flags(f), info(i), result(r)
{ }
const QString &fileName;
const DWORD attributes;
const UINT flags;
SHFILEINFO *const info;
bool *const result;
};
class QShGetFileInfoThread : public QThread
{
public:
explicit QShGetFileInfoThread()
: QThread(), m_params(nullptr)
{
connect(this, &QThread::finished, this, &QObject::deleteLater);
}
void run() override
{
m_init = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
QMutexLocker readyLocker(&m_readyMutex);
while (!m_cancelled.loadRelaxed()) {
if (!m_params && !m_cancelled.loadRelaxed()
&& !m_readyCondition.wait(&m_readyMutex, 1000))
continue;
if (m_params) {
const QString fileName = m_params->fileName;
SHFILEINFO info;
const bool result = SHGetFileInfo(reinterpret_cast<const wchar_t *>(fileName.utf16()),
m_params->attributes, &info, sizeof(SHFILEINFO),
m_params->flags);
m_doneMutex.lock();
if (!m_cancelled.loadRelaxed()) {
*m_params->result = result;
memcpy(m_params->info, &info, sizeof(SHFILEINFO));
}
m_params = nullptr;
m_doneCondition.wakeAll();
m_doneMutex.unlock();
}
}
if (m_init != S_FALSE)
CoUninitialize();
}
bool runWithParams(QShGetFileInfoParams *params, unsigned long timeOutMSecs)
{
QMutexLocker doneLocker(&m_doneMutex);
m_readyMutex.lock();
m_params = params;
m_readyCondition.wakeAll();
m_readyMutex.unlock();
return m_doneCondition.wait(&m_doneMutex, timeOutMSecs);
}
void cancel()
{
QMutexLocker doneLocker(&m_doneMutex);
m_cancelled.storeRelaxed(1);
m_readyCondition.wakeAll();
}
private:
HRESULT m_init;
QShGetFileInfoParams *m_params;
QAtomicInt m_cancelled;
QWaitCondition m_readyCondition;
QWaitCondition m_doneCondition;
QMutex m_readyMutex;
QMutex m_doneMutex;
};
static bool shGetFileInfoBackground(const QString &fileName, DWORD attributes,
SHFILEINFO *info, UINT flags,
unsigned long timeOutMSecs = 5000)
{
static QShGetFileInfoThread *getFileInfoThread = nullptr;
if (!getFileInfoThread) {
getFileInfoThread = new QShGetFileInfoThread;
getFileInfoThread->start();
}
bool result = false;
QShGetFileInfoParams params(fileName, attributes, info, flags, &result);
if (!getFileInfoThread->runWithParams(&params, timeOutMSecs)) {
// Cancel and reset getFileInfoThread. It'll
// be reinitialized the next time we get called.
getFileInfoThread->cancel();
getFileInfoThread = nullptr;
qWarning().noquote() << "SHGetFileInfo() timed out for " << fileName;
return false;
}
return result;
}
// from QStyle::standardPalette
static inline QPalette standardPalette()
{
QColor backgroundColor(0xd4, 0xd0, 0xc8); // win 2000 grey
QColor lightColor(backgroundColor.lighter());
QColor darkColor(backgroundColor.darker());
const QBrush darkBrush(darkColor);
QColor midColor(Qt::gray);
QPalette palette(Qt::black, backgroundColor, lightColor, darkColor,
midColor, Qt::black, Qt::white);
palette.setBrush(QPalette::Disabled, QPalette::WindowText, darkBrush);
palette.setBrush(QPalette::Disabled, QPalette::Text, darkBrush);
palette.setBrush(QPalette::Disabled, QPalette::ButtonText, darkBrush);
palette.setBrush(QPalette::Disabled, QPalette::Base, QBrush(backgroundColor));
return palette;
}
static inline QPalette systemPalette()
{
QPalette result = standardPalette();
result.setColor(QPalette::WindowText, getSysColor(COLOR_WINDOWTEXT));
result.setColor(QPalette::Button, getSysColor(COLOR_BTNFACE));
result.setColor(QPalette::Light, getSysColor(COLOR_BTNHIGHLIGHT));
result.setColor(QPalette::Dark, getSysColor(COLOR_BTNSHADOW));
result.setColor(QPalette::Mid, result.button().color().darker(150));
result.setColor(QPalette::Text, getSysColor(COLOR_WINDOWTEXT));
result.setColor(QPalette::BrightText, getSysColor(COLOR_BTNHIGHLIGHT));
result.setColor(QPalette::Base, getSysColor(COLOR_WINDOW));
result.setColor(QPalette::Window, getSysColor(COLOR_BTNFACE));
result.setColor(QPalette::ButtonText, getSysColor(COLOR_BTNTEXT));
result.setColor(QPalette::Midlight, getSysColor(COLOR_3DLIGHT));
result.setColor(QPalette::Shadow, getSysColor(COLOR_3DDKSHADOW));
result.setColor(QPalette::Highlight, getSysColor(COLOR_HIGHLIGHT));
result.setColor(QPalette::HighlightedText, getSysColor(COLOR_HIGHLIGHTTEXT));
result.setColor(QPalette::Link, Qt::blue);
result.setColor(QPalette::LinkVisited, Qt::magenta);
result.setColor(QPalette::Inactive, QPalette::Button, result.button().color());
result.setColor(QPalette::Inactive, QPalette::Window, result.window().color());
result.setColor(QPalette::Inactive, QPalette::Light, result.light().color());
result.setColor(QPalette::Inactive, QPalette::Dark, result.dark().color());
if (result.midlight() == result.button())
result.setColor(QPalette::Midlight, result.button().color().lighter(110));
if (result.window() != result.base()) {
result.setColor(QPalette::Inactive, QPalette::Highlight, result.color(QPalette::Inactive, QPalette::Window));
result.setColor(QPalette::Inactive, QPalette::HighlightedText, result.color(QPalette::Inactive, QPalette::Text));
}
const QColor disabled =
mixColors(result.windowText().color(), result.button().color());
result.setColorGroup(QPalette::Disabled, result.windowText(), result.button(),
result.light(), result.dark(), result.mid(),
result.text(), result.brightText(), result.base(),
result.window());
result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
result.setColor(QPalette::Disabled, QPalette::Text, disabled);
result.setColor(QPalette::Disabled, QPalette::ButtonText, disabled);
result.setColor(QPalette::Disabled, QPalette::Highlight,
getSysColor(COLOR_HIGHLIGHT));
result.setColor(QPalette::Disabled, QPalette::HighlightedText,
getSysColor(COLOR_HIGHLIGHTTEXT));
result.setColor(QPalette::Disabled, QPalette::Base,
result.window().color());
return result;
}
static inline QPalette toolTipPalette(const QPalette &systemPalette)
{
QPalette result(systemPalette);
const QColor tipBgColor(getSysColor(COLOR_INFOBK));
const QColor tipTextColor(getSysColor(COLOR_INFOTEXT));
result.setColor(QPalette::All, QPalette::Button, tipBgColor);
result.setColor(QPalette::All, QPalette::Window, tipBgColor);
result.setColor(QPalette::All, QPalette::Text, tipTextColor);
result.setColor(QPalette::All, QPalette::WindowText, tipTextColor);
result.setColor(QPalette::All, QPalette::ButtonText, tipTextColor);
result.setColor(QPalette::All, QPalette::Button, tipBgColor);
result.setColor(QPalette::All, QPalette::Window, tipBgColor);
result.setColor(QPalette::All, QPalette::Text, tipTextColor);
result.setColor(QPalette::All, QPalette::WindowText, tipTextColor);
result.setColor(QPalette::All, QPalette::ButtonText, tipTextColor);
result.setColor(QPalette::All, QPalette::ToolTipBase, tipBgColor);
result.setColor(QPalette::All, QPalette::ToolTipText, tipTextColor);
const QColor disabled =
mixColors(result.windowText().color(), result.button().color());
result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
result.setColor(QPalette::Disabled, QPalette::Text, disabled);
result.setColor(QPalette::Disabled, QPalette::ToolTipText, disabled);
result.setColor(QPalette::Disabled, QPalette::Base, Qt::white);
result.setColor(QPalette::Disabled, QPalette::BrightText, Qt::white);
result.setColor(QPalette::Disabled, QPalette::ToolTipBase, Qt::white);
return result;
}
static inline QPalette menuPalette(const QPalette &systemPalette)
{
QPalette result(systemPalette);
const QColor menuColor(getSysColor(COLOR_MENU));
const QColor menuTextColor(getSysColor(COLOR_MENUTEXT));
const QColor disabled(getSysColor(COLOR_GRAYTEXT));
// we might need a special color group for the result.
result.setColor(QPalette::Active, QPalette::Button, menuColor);
result.setColor(QPalette::Active, QPalette::Text, menuTextColor);
result.setColor(QPalette::Active, QPalette::WindowText, menuTextColor);
result.setColor(QPalette::Active, QPalette::ButtonText, menuTextColor);
result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
result.setColor(QPalette::Disabled, QPalette::Text, disabled);
const bool isFlat = booleanSystemParametersInfo(SPI_GETFLATMENU, false);
result.setColor(QPalette::Disabled, QPalette::Highlight,
getSysColor(isFlat ? COLOR_MENUHILIGHT : COLOR_HIGHLIGHT));
result.setColor(QPalette::Disabled, QPalette::HighlightedText, disabled);
result.setColor(QPalette::Disabled, QPalette::Button,
result.color(QPalette::Active, QPalette::Button));
result.setColor(QPalette::Inactive, QPalette::Button,
result.color(QPalette::Active, QPalette::Button));
result.setColor(QPalette::Inactive, QPalette::Text,
result.color(QPalette::Active, QPalette::Text));
result.setColor(QPalette::Inactive, QPalette::WindowText,
result.color(QPalette::Active, QPalette::WindowText));
result.setColor(QPalette::Inactive, QPalette::ButtonText,
result.color(QPalette::Active, QPalette::ButtonText));
result.setColor(QPalette::Inactive, QPalette::Highlight,
result.color(QPalette::Active, QPalette::Highlight));
result.setColor(QPalette::Inactive, QPalette::HighlightedText,
result.color(QPalette::Active, QPalette::HighlightedText));
result.setColor(QPalette::Inactive, QPalette::ButtonText,
systemPalette.color(QPalette::Inactive, QPalette::Dark));
return result;
}
static inline QPalette *menuBarPalette(const QPalette &menuPalette)
{
QPalette *result = nullptr;
if (booleanSystemParametersInfo(SPI_GETFLATMENU, false)) {
result = new QPalette(menuPalette);
const QColor menubar(getSysColor(COLOR_MENUBAR));
result->setColor(QPalette::Active, QPalette::Button, menubar);
result->setColor(QPalette::Disabled, QPalette::Button, menubar);
result->setColor(QPalette::Inactive, QPalette::Button, menubar);
}
return result;
}
const char *QWindowsTheme::name = "windows";
QWindowsTheme *QWindowsTheme::m_instance = nullptr;
QWindowsTheme::QWindowsTheme()
{
m_instance = this;
std::fill(m_fonts, m_fonts + NFonts, nullptr);
std::fill(m_palettes, m_palettes + NPalettes, nullptr);
refresh();
refreshIconPixmapSizes();
}
QWindowsTheme::~QWindowsTheme()
{
clearPalettes();
clearFonts();
m_instance = nullptr;
}
static inline QStringList iconThemeSearchPaths()
{
const QFileInfo appDir(QCoreApplication::applicationDirPath() + QLatin1String("/icons"));
return appDir.isDir() ? QStringList(appDir.absoluteFilePath()) : QStringList();
}
static inline QStringList styleNames()
{
return { QStringLiteral("WindowsVista"), QStringLiteral("Windows") };
}
static inline int uiEffects()
{
int result = QPlatformTheme::HoverEffect;
if (booleanSystemParametersInfo(SPI_GETUIEFFECTS, false))
result |= QPlatformTheme::GeneralUiEffect;
if (booleanSystemParametersInfo(SPI_GETMENUANIMATION, false))
result |= QPlatformTheme::AnimateMenuUiEffect;
if (booleanSystemParametersInfo(SPI_GETMENUFADE, false))
result |= QPlatformTheme::FadeMenuUiEffect;
if (booleanSystemParametersInfo(SPI_GETCOMBOBOXANIMATION, false))
result |= QPlatformTheme::AnimateComboUiEffect;
if (booleanSystemParametersInfo(SPI_GETTOOLTIPANIMATION, false))
result |= QPlatformTheme::AnimateTooltipUiEffect;
return result;
}
QVariant QWindowsTheme::themeHint(ThemeHint hint) const
{
switch (hint) {
case UseFullScreenForPopupMenu:
return QVariant(true);
case DialogButtonBoxLayout:
return QVariant(QPlatformDialogHelper::WinLayout);
case IconThemeSearchPaths:
return QVariant(iconThemeSearchPaths());
case StyleNames:
return QVariant(styleNames());
case TextCursorWidth:
return QVariant(int(dWordSystemParametersInfo(SPI_GETCARETWIDTH, 1u)));
case DropShadow:
return QVariant(booleanSystemParametersInfo(SPI_GETDROPSHADOW, false));
case MaximumScrollBarDragDistance:
return QVariant(qRound(qreal(QWindowsContext::instance()->defaultDPI()) * 1.375));
case KeyboardScheme:
return QVariant(int(WindowsKeyboardScheme));
case UiEffects:
return QVariant(uiEffects());
case IconPixmapSizes:
return QVariant::fromValue(m_fileIconSizes);
case DialogSnapToDefaultButton:
return QVariant(booleanSystemParametersInfo(SPI_GETSNAPTODEFBUTTON, false));
case ContextMenuOnMouseRelease:
return QVariant(true);
case WheelScrollLines: {
int result = 3;
const DWORD scrollLines = dWordSystemParametersInfo(SPI_GETWHEELSCROLLLINES, DWORD(result));
if (scrollLines != DWORD(-1)) // Special value meaning "scroll one screen", unimplemented in Qt.
result = int(scrollLines);
return QVariant(result);
}
case MouseDoubleClickDistance:
return GetSystemMetrics(SM_CXDOUBLECLK);
default:
break;
}
return QPlatformTheme::themeHint(hint);
}
void QWindowsTheme::clearPalettes()
{
qDeleteAll(m_palettes, m_palettes + NPalettes);
std::fill(m_palettes, m_palettes + NPalettes, nullptr);
}
void QWindowsTheme::refreshPalettes()
{
if (!QGuiApplication::desktopSettingsAware())
return;
m_palettes[SystemPalette] = new QPalette(systemPalette());
m_palettes[ToolTipPalette] = new QPalette(toolTipPalette(*m_palettes[SystemPalette]));
m_palettes[MenuPalette] = new QPalette(menuPalette(*m_palettes[SystemPalette]));
m_palettes[MenuBarPalette] = menuBarPalette(*m_palettes[MenuPalette]);
}
void QWindowsTheme::clearFonts()
{
qDeleteAll(m_fonts, m_fonts + NFonts);
std::fill(m_fonts, m_fonts + NFonts, nullptr);
}
void QWindowsTheme::refreshFonts()
{
clearFonts();
if (!QGuiApplication::desktopSettingsAware())
return;
NONCLIENTMETRICS ncm;
QWindowsContext::nonClientMetrics(&ncm);
const QFont menuFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMenuFont);
const QFont messageBoxFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont);
const QFont statusFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfStatusFont);
const QFont titleFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfCaptionFont);
QFont fixedFont(QStringLiteral("Courier New"), messageBoxFont.pointSize());
fixedFont.setStyleHint(QFont::TypeWriter);
LOGFONT lfIconTitleFont;
SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0);
const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont);
m_fonts[SystemFont] = new QFont(QWindowsFontDatabase::systemDefaultFont());
m_fonts[MenuFont] = new QFont(menuFont);
m_fonts[MenuBarFont] = new QFont(menuFont);
m_fonts[MessageBoxFont] = new QFont(messageBoxFont);
m_fonts[TipLabelFont] = new QFont(statusFont);
m_fonts[StatusBarFont] = new QFont(statusFont);
m_fonts[MdiSubWindowTitleFont] = new QFont(titleFont);
m_fonts[DockWidgetTitleFont] = new QFont(titleFont);
m_fonts[ItemViewFont] = new QFont(iconTitleFont);
m_fonts[FixedFont] = new QFont(fixedFont);
}
enum FileIconSize {
// Standard icons obtainable via shGetFileInfo(), SHGFI_SMALLICON, SHGFI_LARGEICON
SmallFileIcon, LargeFileIcon,
// Larger icons obtainable via SHGetImageList()
ExtraLargeFileIcon,
JumboFileIcon, // Vista onwards
FileIconSizeCount
};
bool QWindowsTheme::usePlatformNativeDialog(DialogType type) const
{
return QWindowsDialogs::useHelper(type);
}
QPlatformDialogHelper *QWindowsTheme::createPlatformDialogHelper(DialogType type) const
{
return QWindowsDialogs::createHelper(type);
}
#if QT_CONFIG(systemtrayicon)
QPlatformSystemTrayIcon *QWindowsTheme::createPlatformSystemTrayIcon() const
{
return new QWindowsSystemTrayIcon;
}
#endif
void QWindowsTheme::windowsThemeChanged(QWindow * window)
{
refresh();
QWindowSystemInterface::handleThemeChange(window);
}
static int fileIconSizes[FileIconSizeCount];
void QWindowsTheme::refreshIconPixmapSizes()
{
// Standard sizes: 16, 32, 48, 256
fileIconSizes[SmallFileIcon] = GetSystemMetrics(SM_CXSMICON); // corresponds to SHGFI_SMALLICON);
fileIconSizes[LargeFileIcon] = GetSystemMetrics(SM_CXICON); // corresponds to SHGFI_LARGEICON
fileIconSizes[ExtraLargeFileIcon] =
fileIconSizes[LargeFileIcon] + fileIconSizes[LargeFileIcon] / 2;
fileIconSizes[JumboFileIcon] = 8 * fileIconSizes[LargeFileIcon]; // empirical, has not been observed to work
#ifdef USE_IIMAGELIST
int *availEnd = fileIconSizes + JumboFileIcon + 1;
#else
int *availEnd = fileIconSizes + LargeFileIcon + 1;
#endif // USE_IIMAGELIST
m_fileIconSizes = QAbstractFileIconEngine::toSizeList(fileIconSizes, availEnd);
qCDebug(lcQpaWindows) << __FUNCTION__ << m_fileIconSizes;
}
// Defined in qpixmap_win.cpp
Q_GUI_EXPORT QPixmap qt_pixmapFromWinHICON(HICON icon);
static QPixmap loadIconFromShell32(int resourceId, QSizeF size)
{
if (const HMODULE hmod = QSystemLibrary::load(L"shell32")) {
auto iconHandle =
static_cast<HICON>(LoadImage(hmod, MAKEINTRESOURCE(resourceId),
IMAGE_ICON, int(size.width()), int(size.height()), 0));
if (iconHandle) {
QPixmap iconpixmap = qt_pixmapFromWinHICON(iconHandle);
DestroyIcon(iconHandle);
return iconpixmap;
}
}
return QPixmap();
}
QPixmap QWindowsTheme::standardPixmap(StandardPixmap sp, const QSizeF &pixmapSize) const
{
int resourceId = -1;
SHSTOCKICONID stockId = SIID_INVALID;
UINT stockFlags = 0;
LPCTSTR iconName = nullptr;
switch (sp) {
case DriveCDIcon:
stockId = SIID_DRIVECD;
resourceId = 12;
break;
case DriveDVDIcon:
stockId = SIID_DRIVEDVD;
resourceId = 12;
break;
case DriveNetIcon:
stockId = SIID_DRIVENET;
resourceId = 10;
break;
case DriveHDIcon:
stockId = SIID_DRIVEFIXED;
resourceId = 9;
break;
case DriveFDIcon:
stockId = SIID_DRIVE35;
resourceId = 7;
break;
case FileLinkIcon:
stockFlags = SHGSI_LINKOVERLAY;
Q_FALLTHROUGH();
case FileIcon:
stockId = SIID_DOCNOASSOC;
resourceId = 1;
break;
case DirLinkIcon:
stockFlags = SHGSI_LINKOVERLAY;
Q_FALLTHROUGH();
case DirClosedIcon:
case DirIcon:
stockId = SIID_FOLDER;
resourceId = 4;
break;
case DesktopIcon:
resourceId = 35;
break;
case ComputerIcon:
resourceId = 16;
break;
case DirLinkOpenIcon:
stockFlags = SHGSI_LINKOVERLAY;
Q_FALLTHROUGH();
case DirOpenIcon:
stockId = SIID_FOLDEROPEN;
resourceId = 5;
break;
case FileDialogNewFolder:
stockId = SIID_FOLDER;
resourceId = 319;
break;
case DirHomeIcon:
resourceId = 235;
break;
case TrashIcon:
stockId = SIID_RECYCLER;
resourceId = 191;
break;
case MessageBoxInformation:
stockId = SIID_INFO;
iconName = IDI_INFORMATION;
break;
case MessageBoxWarning:
stockId = SIID_WARNING;
iconName = IDI_WARNING;
break;
case MessageBoxCritical:
stockId = SIID_ERROR;
iconName = IDI_ERROR;
break;
case MessageBoxQuestion:
stockId = SIID_HELP;
iconName = IDI_QUESTION;
break;
case VistaShield:
stockId = SIID_SHIELD;
break;
default:
break;
}
if (stockId != SIID_INVALID) {
QPixmap pixmap;
SHSTOCKICONINFO iconInfo;
memset(&iconInfo, 0, sizeof(iconInfo));
iconInfo.cbSize = sizeof(iconInfo);
stockFlags |= (pixmapSize.width() > 16 ? SHGFI_LARGEICON : SHGFI_SMALLICON);
if (SHGetStockIconInfo(stockId, SHGFI_ICON | stockFlags, &iconInfo) == S_OK) {
pixmap = qt_pixmapFromWinHICON(iconInfo.hIcon);
DestroyIcon(iconInfo.hIcon);
return pixmap;
}
}
if (resourceId != -1) {
QPixmap pixmap = loadIconFromShell32(resourceId, pixmapSize);
if (!pixmap.isNull()) {
if (sp == FileLinkIcon || sp == DirLinkIcon || sp == DirLinkOpenIcon) {
QPainter painter(&pixmap);
QPixmap link = loadIconFromShell32(30, pixmapSize);
painter.drawPixmap(0, 0, int(pixmapSize.width()), int(pixmapSize.height()), link);
}
return pixmap;
}
}
if (iconName) {
HICON iconHandle = LoadIcon(nullptr, iconName);
QPixmap pixmap = qt_pixmapFromWinHICON(iconHandle);
DestroyIcon(iconHandle);
if (!pixmap.isNull())
return pixmap;
}
return QPlatformTheme::standardPixmap(sp, pixmapSize);
}
enum { // Shell image list ids
sHIL_EXTRALARGE = 0x2, // 48x48 or user-defined
sHIL_JUMBO = 0x4 // 256x256 (Vista or later)
};
static QString dirIconPixmapCacheKey(int iIcon, int iconSize, int imageListSize)
{
QString key = QLatin1String("qt_dir_") + QString::number(iIcon);
if (iconSize == SHGFI_LARGEICON)
key += QLatin1Char('l');
switch (imageListSize) {
case sHIL_EXTRALARGE:
key += QLatin1Char('e');
break;
case sHIL_JUMBO:
key += QLatin1Char('j');
break;
}
return key;
}
template <typename T>
class FakePointer
{
public:
Q_STATIC_ASSERT_X(sizeof(T) <= sizeof(void *), "FakePointers can only go that far.");
static FakePointer *create(T thing)
{
return reinterpret_cast<FakePointer *>(qintptr(thing));
}
T operator * () const
{
return T(qintptr(this));
}
void operator delete (void *) {}
};
// Shell image list helper functions.
static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info)
{
QPixmap result;
#ifdef USE_IIMAGELIST
// For MinGW:
static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}};
IImageList *imageList = nullptr;
HRESULT hr = SHGetImageList(iImageList, iID_IImageList, reinterpret_cast<void **>(&imageList));
if (hr != S_OK)
return result;
HICON hIcon;
hr = imageList->GetIcon(info.iIcon, ILD_TRANSPARENT, &hIcon);
if (hr == S_OK) {
result = qt_pixmapFromWinHICON(hIcon);
DestroyIcon(hIcon);
}
imageList->Release();
#else
Q_UNUSED(iImageList)
Q_UNUSED(info)
#endif // USE_IIMAGELIST
return result;
}
class QWindowsFileIconEngine : public QAbstractFileIconEngine
{
public:
explicit QWindowsFileIconEngine(const QFileInfo &info, QPlatformTheme::IconOptions opts) :
QAbstractFileIconEngine(info, opts) {}
QList<QSize> availableSizes(QIcon::Mode = QIcon::Normal, QIcon::State = QIcon::Off) const override
{ return QWindowsTheme::instance()->availableFileIconSizes(); }
protected:
QString cacheKey() const override;
QPixmap filePixmap(const QSize &size, QIcon::Mode mode, QIcon::State) override;
};
QString QWindowsFileIconEngine::cacheKey() const
{
// Cache directories unless custom or drives, which have custom icons depending on type
if ((options() & QPlatformTheme::DontUseCustomDirectoryIcons) && fileInfo().isDir() && !fileInfo().isRoot())
return QStringLiteral("qt_/directory/");
if (!fileInfo().isFile())
return QString();
// Return "" for .exe, .lnk and .ico extensions.
// It is faster to just look at the file extensions;
// avoiding slow QFileInfo::isExecutable() (QTBUG-13182)
QString suffix = fileInfo().suffix();
if (!suffix.compare(QLatin1String("exe"), Qt::CaseInsensitive)
|| !suffix.compare(QLatin1String("lnk"), Qt::CaseInsensitive)
|| !suffix.compare(QLatin1String("ico"), Qt::CaseInsensitive)) {
return QString();
}
return QLatin1String("qt_.")
+ (suffix.isEmpty() ? fileInfo().fileName() : std::move(suffix).toUpper()); // handle "Makefile" ;)
}
QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon::State)
{
/* We don't use the variable, but by storing it statically, we
* ensure CoInitialize is only called once. */
static HRESULT comInit = CoInitialize(nullptr);
Q_UNUSED(comInit);
static QCache<QString, FakePointer<int> > dirIconEntryCache(1000);
static QMutex mx;
static int defaultFolderIIcon = -1;
const bool useDefaultFolderIcon = options() & QPlatformTheme::DontUseCustomDirectoryIcons;
QPixmap pixmap;
const QString filePath = QDir::toNativeSeparators(fileInfo().filePath());
const int width = int(size.width());
const int iconSize = width > fileIconSizes[SmallFileIcon] ? SHGFI_LARGEICON : SHGFI_SMALLICON;
const int requestedImageListSize =
#ifdef USE_IIMAGELIST
width > fileIconSizes[ExtraLargeFileIcon]
? sHIL_JUMBO
: (width > fileIconSizes[LargeFileIcon] ? sHIL_EXTRALARGE : 0);
#else
0;
#endif // !USE_IIMAGELIST
bool cacheableDirIcon = fileInfo().isDir() && !fileInfo().isRoot();
if (cacheableDirIcon) {
QMutexLocker locker(&mx);
int iIcon = (useDefaultFolderIcon && defaultFolderIIcon >= 0) ? defaultFolderIIcon
: **dirIconEntryCache.object(filePath);
if (iIcon) {
QPixmapCache::find(dirIconPixmapCacheKey(iIcon, iconSize, requestedImageListSize),
&pixmap);
if (pixmap.isNull()) // Let's keep both caches in sync
dirIconEntryCache.remove(filePath);
else
return pixmap;
}
}
SHFILEINFO info;
unsigned int flags = SHGFI_ICON | iconSize | SHGFI_SYSICONINDEX | SHGFI_ADDOVERLAYS | SHGFI_OVERLAYINDEX;
DWORD attributes = 0;
QString path = filePath;
if (cacheableDirIcon && useDefaultFolderIcon) {
flags |= SHGFI_USEFILEATTRIBUTES;
attributes |= FILE_ATTRIBUTE_DIRECTORY;
path = QStringLiteral("dummy");
} else if (!fileInfo().exists()) {
flags |= SHGFI_USEFILEATTRIBUTES;
attributes |= FILE_ATTRIBUTE_NORMAL;
}
const bool val = shGetFileInfoBackground(path, attributes, &info, flags);
// Even if GetFileInfo returns a valid result, hIcon can be empty in some cases
if (val && info.hIcon) {
QString key;
if (cacheableDirIcon) {
if (useDefaultFolderIcon && defaultFolderIIcon < 0)
defaultFolderIIcon = info.iIcon;
//using the unique icon index provided by windows save us from duplicate keys
key = dirIconPixmapCacheKey(info.iIcon, iconSize, requestedImageListSize);
QPixmapCache::find(key, &pixmap);
if (!pixmap.isNull()) {
QMutexLocker locker(&mx);
dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon));
}
}
if (pixmap.isNull()) {
if (requestedImageListSize) {
pixmap = pixmapFromShellImageList(requestedImageListSize, info);
if (pixmap.isNull() && requestedImageListSize == sHIL_JUMBO)
pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, info);
}
if (pixmap.isNull())
pixmap = qt_pixmapFromWinHICON(info.hIcon);
if (!pixmap.isNull()) {
if (cacheableDirIcon) {
QMutexLocker locker(&mx);
QPixmapCache::insert(key, pixmap);
dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon));
}
} else {
qWarning("QWindowsTheme::fileIconPixmap() no icon found");
}
}
DestroyIcon(info.hIcon);
}
return pixmap;
}
QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions) const
{
return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions));
}
static inline bool doUseNativeMenus()
{
const unsigned options = QWindowsIntegration::instance()->options();
if ((options & QWindowsIntegration::NoNativeMenus) != 0)
return false;
if ((options & QWindowsIntegration::AlwaysUseNativeMenus) != 0)
return true;
// "Auto" mode: For non-widget or Quick Controls 2 applications
if (!QCoreApplication::instance()->inherits("QApplication"))
return true;
const QWindowList &topLevels = QGuiApplication::topLevelWindows();
for (const QWindow *t : topLevels) {
if (t->inherits("QQuickApplicationWindow"))
return true;
}
return false;
}
bool QWindowsTheme::useNativeMenus()
{
static const bool result = doUseNativeMenus();
return result;
}
QPlatformMenuItem *QWindowsTheme::createPlatformMenuItem() const
{
qCDebug(lcQpaMenus) << __FUNCTION__;
return QWindowsTheme::useNativeMenus() ? new QWindowsMenuItem : nullptr;
}
QPlatformMenu *QWindowsTheme::createPlatformMenu() const
{
qCDebug(lcQpaMenus) << __FUNCTION__;
// We create a popup menu here, since it will likely be used as context
// menu. Submenus should be created the factory functions of
// QPlatformMenu/Bar. Note though that Quick Controls 1 will use this
// function for submenus as well, but this has been found to work.
return QWindowsTheme::useNativeMenus() ? new QWindowsPopupMenu : nullptr;
}
QPlatformMenuBar *QWindowsTheme::createPlatformMenuBar() const
{
qCDebug(lcQpaMenus) << __FUNCTION__;
return QWindowsTheme::useNativeMenus() ? new QWindowsMenuBar : nullptr;
}
void QWindowsTheme::showPlatformMenuBar()
{
qCDebug(lcQpaMenus) << __FUNCTION__;
}
QT_END_NAMESPACE