Add initial implementation of macOS and iOS icon theme implementations
From macOS 13 on, AppKit provides an API to get a scalable system image from a symbolic icon name. We can map those icon names to the XDG-based icon names we support in Qt, and render the NSImage with palette-based coloring when needed, in an appropriate scale. On iOS, we can use the equivalent UIKit APIs. Coloring functionality is only available from iOS 15 on. Implement a QAppleIconEngine that does that in its scaledPixmap implementation. Use basic caching to store a single QPixmap version of the native vector image. We regenerate the pixmap whenever a different size, mode, or state is requested. Add a manual test for browsing all icons we can get from the various Qt APIs that: standard icons and pixmaps from QStyle, QPlatformTheme, and QIcon::fromTheme, in addition to showing all icon variations for a single QIcon. Task-number: QTBUG-102346 Change-Id: If5ab683ec18d140bd8700ac99b0edada980de9b4 Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
parent
fdc2036640
commit
5ae6355487
@ -385,6 +385,7 @@ qt_internal_extend_target(Gui CONDITION APPLE
|
||||
platform/darwin/qmacmimeregistry.mm platform/darwin/qmacmimeregistry_p.h
|
||||
platform/darwin/qutimimeconverter.mm platform/darwin/qutimimeconverter.h
|
||||
platform/darwin/qapplekeymapper.mm platform/darwin/qapplekeymapper_p.h
|
||||
platform/darwin/qappleiconengine.mm platform/darwin/qappleiconengine_p.h
|
||||
text/coretext/qcoretextfontdatabase.mm text/coretext/qcoretextfontdatabase_p.h
|
||||
text/coretext/qfontengine_coretext.mm text/coretext/qfontengine_coretext_p.h
|
||||
LIBRARIES
|
||||
|
@ -162,6 +162,9 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
QPixmap qt_mac_toQPixmap(const NSImage *image, const QSizeF &size)
|
||||
{
|
||||
// ### TODO: add parameter so that we can decide whether to maintain the aspect
|
||||
// ratio of the image (positioning the image inside the pixmap of size \a size),
|
||||
// or whether we want to fill the resulting pixmap by stretching the image.
|
||||
const NSSize pixmapSize = NSMakeSize(size.width(), size.height());
|
||||
QPixmap pixmap(pixmapSize.width, pixmapSize.height);
|
||||
pixmap.fill(Qt::transparent);
|
||||
@ -186,6 +189,7 @@ QPixmap qt_mac_toQPixmap(const NSImage *image, const QSizeF &size)
|
||||
|
||||
QImage qt_mac_toQImage(const UIImage *image, QSizeF size)
|
||||
{
|
||||
// ### TODO: same as above
|
||||
QImage ret(size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
|
||||
ret.fill(Qt::transparent);
|
||||
QMacCGContext ctx(&ret);
|
||||
|
230
src/gui/platform/darwin/qappleiconengine.mm
Normal file
230
src/gui/platform/darwin/qappleiconengine.mm
Normal file
@ -0,0 +1,230 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "qappleiconengine_p.h"
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
# include <AppKit/AppKit.h>
|
||||
#elif defined (Q_OS_IOS)
|
||||
# include <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#include <QtGui/qpainter.h>
|
||||
#include <QtGui/qpalette.h>
|
||||
#include <QtGui/qstylehints.h>
|
||||
|
||||
#include <QtGui/private/qcoregraphics_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
namespace {
|
||||
auto *loadImage(const QString &iconName)
|
||||
{
|
||||
static constexpr std::pair<QStringView, NSString *> iconMap[] = {
|
||||
{u"address-book-new", @"folder.circle"},
|
||||
{u"application-exit", @"xmark.circle"},
|
||||
{u"appointment-new", @"hourglass.badge.plus"},
|
||||
{u"call-start", @"phone.arrow.up.right"},
|
||||
{u"call-stop", @"phone.arrow.down.left"},
|
||||
{u"edit-clear", @"clear"},
|
||||
{u"edit-copy", @"doc.on.doc"},
|
||||
{u"edit-cut", @"scissors"},
|
||||
{u"edit-delete", @"delete.left"},
|
||||
{u"edit-find", @"magnifyingglass"},
|
||||
{u"edit-find-replace", @"arrow.up.left.and.down.right.magnifyingglass"},
|
||||
{u"edit-paste", @"clipboard"},
|
||||
{u"edit-redo", @"arrowshape.turn.up.right"},
|
||||
{u"edit-select-all", @""},
|
||||
{u"edit-undo", @"arrowshape.turn.up.left"},
|
||||
};
|
||||
const auto it = std::find_if(std::begin(iconMap), std::end(iconMap), [iconName](const auto &c){
|
||||
return c.first == iconName;
|
||||
});
|
||||
NSString *systemIconName = it != std::end(iconMap) ? it->second : iconName.toNSString();
|
||||
#if defined(Q_OS_MACOS)
|
||||
return [NSImage imageWithSystemSymbolName:systemIconName accessibilityDescription:nil];
|
||||
#elif defined(Q_OS_IOS)
|
||||
return [UIImage systemImageNamed:systemIconName];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
QAppleIconEngine::QAppleIconEngine(const QString &iconName)
|
||||
: m_iconName(iconName), m_image(loadImage(iconName))
|
||||
{
|
||||
if (m_image)
|
||||
[m_image retain];
|
||||
}
|
||||
|
||||
QAppleIconEngine::~QAppleIconEngine()
|
||||
{
|
||||
if (m_image)
|
||||
[m_image release];
|
||||
}
|
||||
|
||||
QIconEngine *QAppleIconEngine::clone() const
|
||||
{
|
||||
return new QAppleIconEngine(m_iconName);
|
||||
}
|
||||
|
||||
QString QAppleIconEngine::key() const
|
||||
{
|
||||
return u"QAppleIconEngine"_s;
|
||||
}
|
||||
|
||||
QString QAppleIconEngine::iconName()
|
||||
{
|
||||
return m_iconName;
|
||||
}
|
||||
|
||||
bool QAppleIconEngine::isNull()
|
||||
{
|
||||
return m_image == nullptr;
|
||||
}
|
||||
|
||||
QList<QSize> QAppleIconEngine::availableIconSizes()
|
||||
{
|
||||
const qreal devicePixelRatio = qGuiApp->devicePixelRatio();
|
||||
const QList<QSize> sizes = {
|
||||
{qRound(16 * devicePixelRatio), qRound(16 * devicePixelRatio)},
|
||||
{qRound(32 * devicePixelRatio), qRound(32 * devicePixelRatio)},
|
||||
{qRound(64 * devicePixelRatio), qRound(64 * devicePixelRatio)},
|
||||
{qRound(128 * devicePixelRatio), qRound(128 * devicePixelRatio)},
|
||||
{qRound(256 * devicePixelRatio), qRound(256 * devicePixelRatio)},
|
||||
};
|
||||
return sizes;
|
||||
}
|
||||
|
||||
QList<QSize> QAppleIconEngine::availableSizes(QIcon::Mode, QIcon::State)
|
||||
{
|
||||
return availableIconSizes();
|
||||
}
|
||||
|
||||
QSize QAppleIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
return QIconEngine::actualSize(size, mode, state);
|
||||
}
|
||||
|
||||
QPixmap QAppleIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
return scaledPixmap(size, mode, state, 1.0);
|
||||
}
|
||||
|
||||
namespace {
|
||||
#if defined(Q_OS_MACOS)
|
||||
auto *configuredImage(const NSImage *image, const QColor &color)
|
||||
{
|
||||
auto *config = [NSImageSymbolConfiguration configurationWithPointSize:48
|
||||
weight:NSFontWeightRegular
|
||||
scale:NSImageSymbolScaleLarge];
|
||||
if (@available(macOS 12, *)) {
|
||||
auto *primaryColor = [NSColor colorWithSRGBRed:color.redF()
|
||||
green:color.greenF()
|
||||
blue:color.blueF()
|
||||
alpha:color.alphaF()];
|
||||
|
||||
auto *colorConfig = [NSImageSymbolConfiguration configurationWithHierarchicalColor:primaryColor];
|
||||
config = [config configurationByApplyingConfiguration:colorConfig];
|
||||
}
|
||||
|
||||
return [image imageWithSymbolConfiguration:config];
|
||||
}
|
||||
#elif defined(Q_OS_IOS)
|
||||
auto *configuredImage(const UIImage *image, const QColor &color)
|
||||
{
|
||||
auto *config = [UIImageSymbolConfiguration configurationWithPointSize:48
|
||||
weight:UIImageSymbolWeightRegular
|
||||
scale:UIImageSymbolScaleLarge];
|
||||
|
||||
if (@available(iOS 15, *)) {
|
||||
auto *primaryColor = [UIColor colorWithRed:color.redF()
|
||||
green:color.greenF()
|
||||
blue:color.blueF()
|
||||
alpha:color.alphaF()];
|
||||
|
||||
auto *colorConfig = [UIImageSymbolConfiguration configurationWithHierarchicalColor:primaryColor];
|
||||
config = [config configurationByApplyingConfiguration:colorConfig];
|
||||
}
|
||||
return [image imageByApplyingSymbolConfiguration:config];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename Image>
|
||||
QPixmap imageToPixmap(const Image *image, QSizeF renderSize)
|
||||
{
|
||||
if constexpr (std::is_same_v<Image, NSImage>)
|
||||
return qt_mac_toQPixmap(image, renderSize.toSize());
|
||||
else
|
||||
return QPixmap::fromImage(qt_mac_toQImage(image, renderSize.toSize()));
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap QAppleIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
|
||||
{
|
||||
const quint64 cacheKey = calculateCacheKey(mode, state);
|
||||
if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) {
|
||||
QColor color;
|
||||
const QPalette palette;
|
||||
switch (mode) {
|
||||
case QIcon::Normal:
|
||||
color = palette.color(QPalette::Inactive, QPalette::Text);
|
||||
break;
|
||||
case QIcon::Disabled:
|
||||
color = palette.color(QPalette::Disabled, QPalette::Text);
|
||||
break;
|
||||
case QIcon::Active:
|
||||
color = palette.color(QPalette::Active, QPalette::Text);
|
||||
break;
|
||||
case QIcon::Selected:
|
||||
color = palette.color(QPalette::Active, QPalette::HighlightedText);
|
||||
break;
|
||||
}
|
||||
const auto *image = configuredImage(m_image, color);
|
||||
|
||||
// the size we want is typically square, but the icon might not be. So
|
||||
// ask for a pixmap with the same aspect ratio as the icon, and then
|
||||
// center that within a pixmap of the requested size.
|
||||
QSizeF renderSize = size * scale;
|
||||
const bool aspectRatioAdjusted = image.size.width != image.size.height;
|
||||
if (aspectRatioAdjusted) {
|
||||
double aspectRatio = image.size.width / image.size.height;
|
||||
// don't grow
|
||||
if (aspectRatio < 1)
|
||||
renderSize.rwidth() = renderSize.height() * aspectRatio;
|
||||
else
|
||||
renderSize.rheight() = renderSize.width() / aspectRatio;
|
||||
}
|
||||
|
||||
QPixmap iconPixmap = imageToPixmap(image, renderSize);
|
||||
iconPixmap.setDevicePixelRatio(scale);
|
||||
|
||||
if (aspectRatioAdjusted) {
|
||||
m_pixmap = QPixmap(size * scale);
|
||||
m_pixmap.fill(Qt::transparent);
|
||||
m_pixmap.setDevicePixelRatio(scale);
|
||||
|
||||
QPainter painter(&m_pixmap);
|
||||
const QSize offset = ((m_pixmap.deviceIndependentSize()
|
||||
- iconPixmap.deviceIndependentSize()) / 2).toSize();
|
||||
painter.drawPixmap(offset.width(), offset.height(), iconPixmap);
|
||||
} else {
|
||||
m_pixmap = iconPixmap;
|
||||
}
|
||||
m_cacheKey = cacheKey;
|
||||
}
|
||||
return m_pixmap;
|
||||
}
|
||||
|
||||
void QAppleIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
const qreal scale = painter->device()->devicePixelRatio();
|
||||
// TODO: render the image directly if we don't have the pixmap yet and paint on an image
|
||||
painter->drawPixmap(rect, scaledPixmap(rect.size(), mode, state, scale));
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
64
src/gui/platform/darwin/qappleiconengine_p.h
Normal file
64
src/gui/platform/darwin/qappleiconengine_p.h
Normal file
@ -0,0 +1,64 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#ifndef QAPPLEICONENGINE_P_H
|
||||
#define QAPPLEICONENGINE_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include <QtGui/qiconengine.h>
|
||||
|
||||
#include <QtCore/private/qcore_mac_p.h>
|
||||
|
||||
Q_FORWARD_DECLARE_OBJC_CLASS(UIImage);
|
||||
Q_FORWARD_DECLARE_OBJC_CLASS(NSImage);
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class Q_GUI_EXPORT QAppleIconEngine : public QIconEngine
|
||||
{
|
||||
public:
|
||||
QAppleIconEngine(const QString &iconName);
|
||||
~QAppleIconEngine();
|
||||
QIconEngine *clone() const override;
|
||||
QString key() const override;
|
||||
QString iconName() override;
|
||||
bool isNull() override;
|
||||
|
||||
QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override;
|
||||
QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
|
||||
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
|
||||
QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
|
||||
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
|
||||
|
||||
static QList<QSize> availableIconSizes();
|
||||
|
||||
private:
|
||||
static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state)
|
||||
{
|
||||
return (quint64(mode) << 32) | state;
|
||||
}
|
||||
|
||||
const QString m_iconName;
|
||||
#if defined(Q_OS_MACOS)
|
||||
const NSImage *m_image;
|
||||
#elif defined(Q_OS_IOS)
|
||||
const UIImage *m_image;
|
||||
#endif
|
||||
mutable QPixmap m_pixmap;
|
||||
mutable quint64 m_cacheKey = {};
|
||||
};
|
||||
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QAPPLEICONENGINE_P_H
|
@ -35,6 +35,7 @@ public:
|
||||
const QFont *font(Font type = SystemFont) const override;
|
||||
QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override;
|
||||
QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions options = {}) const override;
|
||||
QIconEngine *createIconEngine(const QString &iconName) const override;
|
||||
|
||||
QVariant themeHint(ThemeHint hint) const override;
|
||||
Qt::ColorScheme colorScheme() const override;
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <QtGui/qpainter.h>
|
||||
#include <QtGui/qtextformat.h>
|
||||
#include <QtGui/private/qcoretextfontdatabase_p.h>
|
||||
#include <QtGui/private/qappleiconengine_p.h>
|
||||
#include <QtGui/private/qfontengine_coretext_p.h>
|
||||
#include <QtGui/private/qabstractfileiconengine_p.h>
|
||||
#include <qpa/qplatformdialoghelper.h>
|
||||
@ -409,19 +410,8 @@ public:
|
||||
QPlatformTheme::IconOptions opts) :
|
||||
QAbstractFileIconEngine(info, opts) {}
|
||||
|
||||
static QList<QSize> availableIconSizes()
|
||||
{
|
||||
const qreal devicePixelRatio = qGuiApp->devicePixelRatio();
|
||||
const int sizes[] = {
|
||||
qRound(16 * devicePixelRatio), qRound(32 * devicePixelRatio),
|
||||
qRound(64 * devicePixelRatio), qRound(128 * devicePixelRatio),
|
||||
qRound(256 * devicePixelRatio)
|
||||
};
|
||||
return QAbstractFileIconEngine::toSizeList(sizes, sizes + sizeof(sizes) / sizeof(sizes[0]));
|
||||
}
|
||||
|
||||
QList<QSize> availableSizes(QIcon::Mode = QIcon::Normal, QIcon::State = QIcon::Off) override
|
||||
{ return QCocoaFileIconEngine::availableIconSizes(); }
|
||||
{ return QAppleIconEngine::availableIconSizes(); }
|
||||
|
||||
protected:
|
||||
QPixmap filePixmap(const QSize &size, QIcon::Mode, QIcon::State) override
|
||||
@ -440,6 +430,14 @@ QIcon QCocoaTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptio
|
||||
return QIcon(new QCocoaFileIconEngine(fileInfo, iconOptions));
|
||||
}
|
||||
|
||||
QIconEngine *QCocoaTheme::createIconEngine(const QString &iconName) const
|
||||
{
|
||||
static bool experimentalIconEngines = qEnvironmentVariableIsSet("QT_ENABLE_EXPERIMENTAL_ICON_ENGINES");
|
||||
if (experimentalIconEngines)
|
||||
return new QAppleIconEngine(iconName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QVariant QCocoaTheme::themeHint(ThemeHint hint) const
|
||||
{
|
||||
switch (hint) {
|
||||
@ -453,7 +451,7 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const
|
||||
return QVariant([[NSApplication sharedApplication] isFullKeyboardAccessEnabled] ?
|
||||
int(Qt::TabFocusAllControls) : int(Qt::TabFocusTextControls | Qt::TabFocusListControls));
|
||||
case IconPixmapSizes:
|
||||
return QVariant::fromValue(QCocoaFileIconEngine::availableIconSizes());
|
||||
return QVariant::fromValue(QAppleIconEngine::availableIconSizes());
|
||||
case QPlatformTheme::PasswordMaskCharacter:
|
||||
return QVariant(QChar(0x2022));
|
||||
case QPlatformTheme::UiEffects:
|
||||
|
@ -30,6 +30,7 @@ public:
|
||||
QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override;
|
||||
|
||||
const QFont *font(Font type = SystemFont) const override;
|
||||
QIconEngine *createIconEngine(const QString &iconName) const override;
|
||||
|
||||
static const char *name;
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <QtGui/private/qcoregraphics_p.h>
|
||||
|
||||
#include <QtGui/private/qcoretextfontdatabase_p.h>
|
||||
#include <QtGui/private/qappleiconengine_p.h>
|
||||
#include <QtGui/private/qguiapplication_p.h>
|
||||
#include <qpa/qplatformintegration.h>
|
||||
|
||||
@ -171,4 +172,12 @@ const QFont *QIOSTheme::font(Font type) const
|
||||
return coreTextFontDatabase->themeFont(type);
|
||||
}
|
||||
|
||||
QIconEngine *QIOSTheme::createIconEngine(const QString &iconName) const
|
||||
{
|
||||
static bool experimentalIconEngines = qEnvironmentVariableIsSet("QT_ENABLE_EXPERIMENTAL_ICON_ENGINES");
|
||||
if (experimentalIconEngines)
|
||||
return new QAppleIconEngine(iconName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
15
tests/manual/iconbrowser/CMakeLists.txt
Normal file
15
tests/manual/iconbrowser/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
# Copyright (C) 2023 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Gui Widgets)
|
||||
|
||||
qt_internal_add_manual_test(iconbrowser
|
||||
GUI
|
||||
SOURCES
|
||||
main.cpp
|
||||
LIBRARIES
|
||||
Qt::Gui
|
||||
Qt::GuiPrivate
|
||||
Qt::Widgets
|
||||
Qt::WidgetsPrivate
|
||||
)
|
548
tests/manual/iconbrowser/main.cpp
Normal file
548
tests/manual/iconbrowser/main.cpp
Normal file
@ -0,0 +1,548 @@
|
||||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#include <QtWidgets/private/qapplication_p.h>
|
||||
#include <QtGui/qpa/qplatformtheme.h>
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
class IconModel : public QAbstractItemModel
|
||||
{
|
||||
const QStringList themedIcons = {
|
||||
u"address-book-new"_s,
|
||||
u"application-exit"_s,
|
||||
u"appointment-new"_s,
|
||||
u"call-start"_s,
|
||||
u"call-stop"_s,
|
||||
u"contact-new"_s,
|
||||
u"document-new"_s,
|
||||
u"document-open"_s,
|
||||
u"document-open-recent"_s,
|
||||
u"document-page-setup"_s,
|
||||
u"document-print"_s,
|
||||
u"document-print-preview"_s,
|
||||
u"document-properties"_s,
|
||||
u"document-revert"_s,
|
||||
u"document-save"_s,
|
||||
u"document-save-as"_s,
|
||||
u"document-send"_s,
|
||||
u"edit-clear"_s,
|
||||
u"edit-copy"_s,
|
||||
u"edit-cut"_s,
|
||||
u"edit-delete"_s,
|
||||
u"edit-find"_s,
|
||||
u"edit-find-replace"_s,
|
||||
u"edit-paste"_s,
|
||||
u"edit-redo"_s,
|
||||
u"edit-select-all"_s,
|
||||
u"edit-undo"_s,
|
||||
u"folder-new"_s,
|
||||
u"format-indent-less"_s,
|
||||
u"format-indent-more"_s,
|
||||
u"format-justify-center"_s,
|
||||
u"format-justify-fill"_s,
|
||||
u"format-justify-left"_s,
|
||||
u"format-justify-right"_s,
|
||||
u"format-text-direction-ltr"_s,
|
||||
u"format-text-direction-rtl"_s,
|
||||
u"format-text-bold"_s,
|
||||
u"format-text-italic"_s,
|
||||
u"format-text-underline"_s,
|
||||
u"format-text-strikethrough"_s,
|
||||
u"go-bottom"_s,
|
||||
u"go-down"_s,
|
||||
u"go-first"_s,
|
||||
u"go-home"_s,
|
||||
u"go-jump"_s,
|
||||
u"go-last"_s,
|
||||
u"go-next"_s,
|
||||
u"go-previous"_s,
|
||||
u"go-top"_s,
|
||||
u"go-up"_s,
|
||||
u"help-about"_s,
|
||||
u"help-contents"_s,
|
||||
u"help-faq"_s,
|
||||
u"insert-image"_s,
|
||||
u"insert-link"_s,
|
||||
u"insert-object"_s,
|
||||
u"insert-text"_s,
|
||||
u"list-add"_s,
|
||||
u"list-remove"_s,
|
||||
u"mail-forward"_s,
|
||||
u"mail-mark-important"_s,
|
||||
u"mail-mark-junk"_s,
|
||||
u"mail-mark-notjunk"_s,
|
||||
u"mail-mark-read"_s,
|
||||
u"mail-mark-unread"_s,
|
||||
u"mail-message-new"_s,
|
||||
u"mail-reply-all"_s,
|
||||
u"mail-reply-sender"_s,
|
||||
u"mail-send"_s,
|
||||
u"mail-send-receive"_s,
|
||||
u"media-eject"_s,
|
||||
u"media-playback-pause"_s,
|
||||
u"media-playback-start"_s,
|
||||
u"media-playback-stop"_s,
|
||||
u"media-record"_s,
|
||||
u"media-seek-backward"_s,
|
||||
u"media-seek-forward"_s,
|
||||
u"media-skip-backward"_s,
|
||||
u"media-skip-forward"_s,
|
||||
u"object-flip-horizontal"_s,
|
||||
u"object-flip-vertical"_s,
|
||||
u"object-rotate-left"_s,
|
||||
u"object-rotate-right"_s,
|
||||
u"process-stop"_s,
|
||||
u"system-lock-screen"_s,
|
||||
u"system-log-out"_s,
|
||||
u"system-run"_s,
|
||||
u"system-search"_s,
|
||||
u"system-reboot"_s,
|
||||
u"system-shutdown"_s,
|
||||
u"tools-check-spelling"_s,
|
||||
u"view-fullscreen"_s,
|
||||
u"view-refresh"_s,
|
||||
u"view-restore"_s,
|
||||
u"view-sort-ascending"_s,
|
||||
u"view-sort-descending"_s,
|
||||
u"window-close"_s,
|
||||
u"window-new"_s,
|
||||
u"zoom-fit-best"_s,
|
||||
u"zoom-in"_s,
|
||||
u"zoom-original"_s,
|
||||
u"zoom-out"_s,
|
||||
|
||||
|
||||
u"process-working"_s,
|
||||
|
||||
|
||||
u"accessories-calculator"_s,
|
||||
u"accessories-character-map"_s,
|
||||
u"accessories-dictionary"_s,
|
||||
u"accessories-text-editor"_s,
|
||||
u"help-browser"_s,
|
||||
u"multimedia-volume-control"_s,
|
||||
u"preferences-desktop-accessibility"_s,
|
||||
u"preferences-desktop-font"_s,
|
||||
u"preferences-desktop-keyboard"_s,
|
||||
u"preferences-desktop-locale"_s,
|
||||
u"preferences-desktop-multimedia"_s,
|
||||
u"preferences-desktop-screensaver"_s,
|
||||
u"preferences-desktop-theme"_s,
|
||||
u"preferences-desktop-wallpaper"_s,
|
||||
u"system-file-manager"_s,
|
||||
u"system-software-install"_s,
|
||||
u"system-software-update"_s,
|
||||
u"utilities-system-monitor"_s,
|
||||
u"utilities-terminal"_s,
|
||||
|
||||
|
||||
u"applications-accessories"_s,
|
||||
u"applications-development"_s,
|
||||
u"applications-engineering"_s,
|
||||
u"applications-games"_s,
|
||||
u"applications-graphics"_s,
|
||||
u"applications-internet"_s,
|
||||
u"applications-multimedia"_s,
|
||||
u"applications-office"_s,
|
||||
u"applications-other"_s,
|
||||
u"applications-science"_s,
|
||||
u"applications-system"_s,
|
||||
u"applications-utilities"_s,
|
||||
u"preferences-desktop"_s,
|
||||
u"preferences-desktop-peripherals"_s,
|
||||
u"preferences-desktop-personal"_s,
|
||||
u"preferences-other"_s,
|
||||
u"preferences-system"_s,
|
||||
u"preferences-system-network"_s,
|
||||
u"system-help"_s,
|
||||
|
||||
|
||||
u"audio-card"_s,
|
||||
u"audio-input-microphone"_s,
|
||||
u"battery"_s,
|
||||
u"camera-photo"_s,
|
||||
u"camera-video"_s,
|
||||
u"camera-web"_s,
|
||||
u"computer"_s,
|
||||
u"drive-harddisk"_s,
|
||||
u"drive-optical"_s,
|
||||
u"drive-removable-media"_s,
|
||||
u"input-gaming"_s,
|
||||
u"input-keyboard"_s,
|
||||
u"input-mouse"_s,
|
||||
u"input-tablet"_s,
|
||||
u"media-flash"_s,
|
||||
u"media-floppy"_s,
|
||||
u"media-optical"_s,
|
||||
u"media-tape"_s,
|
||||
u"modem"_s,
|
||||
u"multimedia-player"_s,
|
||||
u"network-wired"_s,
|
||||
u"network-wireless"_s,
|
||||
u"pda"_s,
|
||||
u"phone"_s,
|
||||
u"printer"_s,
|
||||
u"scanner"_s,
|
||||
u"video-display"_s,
|
||||
|
||||
|
||||
u"emblem-default"_s,
|
||||
u"emblem-documents"_s,
|
||||
u"emblem-downloads"_s,
|
||||
u"emblem-favorite"_s,
|
||||
u"emblem-important"_s,
|
||||
u"emblem-mail"_s,
|
||||
u"emblem-photos"_s,
|
||||
u"emblem-readonly"_s,
|
||||
u"emblem-shared"_s,
|
||||
u"emblem-symbolic-link"_s,
|
||||
u"emblem-synchronized"_s,
|
||||
u"emblem-system"_s,
|
||||
u"emblem-unreadable"_s,
|
||||
|
||||
|
||||
u"face-angel"_s,
|
||||
u"face-angry"_s,
|
||||
u"face-cool"_s,
|
||||
u"face-crying"_s,
|
||||
u"face-devilish"_s,
|
||||
u"face-embarrassed"_s,
|
||||
u"face-kiss"_s,
|
||||
u"face-laugh"_s,
|
||||
u"face-monkey"_s,
|
||||
u"face-plain"_s,
|
||||
u"face-raspberry"_s,
|
||||
u"face-sad"_s,
|
||||
u"face-sick"_s,
|
||||
u"face-smile"_s,
|
||||
u"face-smile-big"_s,
|
||||
u"face-smirk"_s,
|
||||
u"face-surprise"_s,
|
||||
u"face-tired"_s,
|
||||
u"face-uncertain"_s,
|
||||
u"face-wink"_s,
|
||||
u"face-worried"_s,
|
||||
|
||||
|
||||
u"flag-aa"_s,
|
||||
|
||||
|
||||
u"application-x-executable"_s,
|
||||
u"audio-x-generic"_s,
|
||||
u"font-x-generic"_s,
|
||||
u"image-x-generic"_s,
|
||||
u"package-x-generic"_s,
|
||||
u"text-html"_s,
|
||||
u"text-x-generic"_s,
|
||||
u"text-x-generic-template"_s,
|
||||
u"text-x-script"_s,
|
||||
u"video-x-generic"_s,
|
||||
u"x-office-address-book"_s,
|
||||
u"x-office-calendar"_s,
|
||||
u"x-office-document"_s,
|
||||
u"x-office-presentation"_s,
|
||||
u"x-office-spreadsheet"_s,
|
||||
|
||||
|
||||
u"folder"_s,
|
||||
u"folder-remote"_s,
|
||||
u"network-server"_s,
|
||||
u"network-workgroup"_s,
|
||||
u"start-here"_s,
|
||||
u"user-bookmarks"_s,
|
||||
u"user-desktop"_s,
|
||||
u"user-home"_s,
|
||||
u"user-trash"_s,
|
||||
|
||||
|
||||
u"appointment-missed"_s,
|
||||
u"appointment-soon"_s,
|
||||
u"audio-volume-high"_s,
|
||||
u"audio-volume-low"_s,
|
||||
u"audio-volume-medium"_s,
|
||||
u"audio-volume-muted"_s,
|
||||
u"battery-caution"_s,
|
||||
u"battery-low"_s,
|
||||
u"dialog-error"_s,
|
||||
u"dialog-information"_s,
|
||||
u"dialog-password"_s,
|
||||
u"dialog-question"_s,
|
||||
u"dialog-warning"_s,
|
||||
u"folder-drag-accept"_s,
|
||||
u"folder-open"_s,
|
||||
u"folder-visiting"_s,
|
||||
u"image-loading"_s,
|
||||
u"image-missing"_s,
|
||||
u"mail-attachment"_s,
|
||||
u"mail-unread"_s,
|
||||
u"mail-read"_s,
|
||||
u"mail-replied"_s,
|
||||
u"mail-signed"_s,
|
||||
u"mail-signed-verified"_s,
|
||||
u"media-playlist-repeat"_s,
|
||||
u"media-playlist-shuffle"_s,
|
||||
u"network-error"_s,
|
||||
u"network-idle"_s,
|
||||
u"network-offline"_s,
|
||||
u"network-receive"_s,
|
||||
u"network-transmit"_s,
|
||||
u"network-transmit-receive"_s,
|
||||
u"printer-error"_s,
|
||||
u"printer-printing"_s,
|
||||
u"security-high"_s,
|
||||
u"security-medium"_s,
|
||||
u"security-low"_s,
|
||||
u"software-update-available"_s,
|
||||
u"software-update-urgent"_s,
|
||||
u"sync-error"_s,
|
||||
u"sync-synchronizing"_s,
|
||||
u"task-due"_s,
|
||||
u"task-past-due"_s,
|
||||
u"user-available"_s,
|
||||
u"user-away"_s,
|
||||
u"user-idle"_s,
|
||||
u"user-offline"_s,
|
||||
u"user-trash-full"_s,
|
||||
u"weather-clear"_s,
|
||||
u"weather-clear-night"_s,
|
||||
u"weather-few-clouds"_s,
|
||||
u"weather-few-clouds-night"_s,
|
||||
u"weather-fog"_s,
|
||||
u"weather-overcast"_s,
|
||||
u"weather-severe-alert"_s,
|
||||
u"weather-showers"_s,
|
||||
u"weather-showers-scattered"_s,
|
||||
u"weather-snow"_s,
|
||||
u"weather-storm"_s,
|
||||
};
|
||||
public:
|
||||
using QAbstractItemModel::QAbstractItemModel;
|
||||
|
||||
enum Columns {
|
||||
Name,
|
||||
Style,
|
||||
Theme,
|
||||
Icon
|
||||
};
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
return themedIcons.size() + QStyle::NStandardPixmap;
|
||||
}
|
||||
int columnCount(const QModelIndex &parent) const override
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
return Icon + 1;
|
||||
}
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent) const override
|
||||
{
|
||||
if (parent.isValid())
|
||||
return {};
|
||||
if (column > columnCount(parent) || row > rowCount(parent))
|
||||
return {};
|
||||
return createIndex(row, column, quintptr(row));
|
||||
}
|
||||
QModelIndex parent(const QModelIndex &) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override
|
||||
{
|
||||
int row = index.row();
|
||||
const Columns column = Columns(index.column());
|
||||
if (!index.isValid() || row >= rowCount(index.parent()) || column >= columnCount(index.parent()))
|
||||
return {};
|
||||
const bool fromIcon = row < themedIcons.size();
|
||||
if (!fromIcon)
|
||||
row -= themedIcons.size();
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
if (fromIcon) {
|
||||
return themedIcons.at(row);
|
||||
} else {
|
||||
const QMetaObject *styleMO = &QStyle::staticMetaObject;
|
||||
const int pixmapIndex = styleMO->indexOfEnumerator("StandardPixmap");
|
||||
Q_ASSERT(pixmapIndex >= 0);
|
||||
const QMetaEnum pixmapEnum = styleMO->enumerator(pixmapIndex);
|
||||
const QString pixmapName = QString::fromUtf8(pixmapEnum.key(row));
|
||||
return QVariant(pixmapName);
|
||||
}
|
||||
break;
|
||||
case Qt::DecorationRole:
|
||||
switch (index.column()) {
|
||||
case Name:
|
||||
break;
|
||||
case Style:
|
||||
if (fromIcon)
|
||||
break;
|
||||
return QApplication::style()->standardIcon(QStyle::StandardPixmap(row));
|
||||
case Theme:
|
||||
if (fromIcon)
|
||||
break;
|
||||
return QIcon(QApplicationPrivate::platformTheme()->standardPixmap(QPlatformTheme::StandardPixmap(row), {36, 36}));
|
||||
case Icon:
|
||||
if (fromIcon)
|
||||
return QIcon::fromTheme(themedIcons.at(row));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
|
||||
{
|
||||
switch (orientation) {
|
||||
case Qt::Vertical:
|
||||
break;
|
||||
case Qt::Horizontal:
|
||||
if (role == Qt::DisplayRole) {
|
||||
switch (section) {
|
||||
case Name:
|
||||
return "Name";
|
||||
case Style:
|
||||
return "Style";
|
||||
case Theme:
|
||||
return "Theme";
|
||||
case Icon:
|
||||
return"Icon";
|
||||
}
|
||||
}
|
||||
}
|
||||
return QAbstractItemModel::headerData(section, orientation, role);
|
||||
}
|
||||
};
|
||||
|
||||
template<IconModel::Columns Column>
|
||||
struct ColumnModel : public QSortFilterProxyModel
|
||||
{
|
||||
bool filterAcceptsColumn(int sourceColumn, const QModelIndex &) const override
|
||||
{
|
||||
return sourceColumn == Column;
|
||||
}
|
||||
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
|
||||
{
|
||||
const QModelIndex sourceIndex = sourceModel()->index(sourceRow, Column, sourceParent);
|
||||
const QIcon iconData = sourceModel()->data(sourceIndex, Qt::DecorationRole).template value<QIcon>();
|
||||
return !iconData.isNull();
|
||||
}
|
||||
};
|
||||
|
||||
template<IconModel::Columns Column>
|
||||
struct IconView : public QListView
|
||||
{
|
||||
ColumnModel<Column> proxyModel;
|
||||
|
||||
IconView(QAbstractItemModel *model)
|
||||
{
|
||||
setViewMode(QListView::IconMode);
|
||||
setUniformItemSizes(true);
|
||||
proxyModel.setSourceModel(model);
|
||||
setModel(&proxyModel);
|
||||
}
|
||||
};
|
||||
|
||||
class IconInspector : public QFrame
|
||||
{
|
||||
public:
|
||||
IconInspector()
|
||||
{
|
||||
setFrameShape(QFrame::StyledPanel);
|
||||
|
||||
QLineEdit *lineEdit = new QLineEdit;
|
||||
connect(lineEdit, &QLineEdit::textChanged,
|
||||
this, &IconInspector::updateIcon);
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
vbox->addStretch(10);
|
||||
vbox->addWidget(lineEdit);
|
||||
setLayout(vbox);
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.fillRect(event->rect(), palette().window());
|
||||
if (!icon.isNull()) {
|
||||
const QString modeLabels[] = { u"Normal"_s, u"Disabled"_s, u"Active"_s, u"Selected"_s};
|
||||
const QString stateLabels[] = { u"On"_s, u"Off"_s};
|
||||
const int labelWidth = fontMetrics().horizontalAdvance(u"Disabled"_s);
|
||||
const int labelHeight = fontMetrics().height();
|
||||
int labelYs[4] = {};
|
||||
int labelXs[2] = {};
|
||||
|
||||
painter.save();
|
||||
painter.translate(labelWidth + contentsMargins().left(), labelHeight * 2);
|
||||
const QBrush brush(palette().base().color(), Qt::CrossPattern);
|
||||
|
||||
QPoint point;
|
||||
for (const auto &mode : {QIcon::Normal, QIcon::Disabled, QIcon::Active, QIcon::Selected}) {
|
||||
int height = 0;
|
||||
for (const auto &state : {QIcon::On, QIcon::Off}) {
|
||||
int totalWidth = 0;
|
||||
const int relativeX = point.x();
|
||||
const auto sizes = icon.availableSizes(mode, state);
|
||||
for (const auto &size : sizes) {
|
||||
if (size.width() > 256)
|
||||
continue;
|
||||
const QRect iconRect(point, size);
|
||||
painter.fillRect(iconRect, brush);
|
||||
icon.paint(&painter, iconRect, Qt::AlignCenter, mode, state);
|
||||
totalWidth += size.width();
|
||||
point.rx() += size.width();
|
||||
height = std::max(height, size.height());
|
||||
}
|
||||
labelXs[state] = relativeX + totalWidth / 2;
|
||||
}
|
||||
point.rx() = 0;
|
||||
labelYs[mode] = point.ry() + height / 2;
|
||||
point.ry() += height;
|
||||
}
|
||||
painter.restore();
|
||||
|
||||
painter.translate(contentsMargins().left(), labelHeight);
|
||||
for (const auto &mode : {QIcon::Normal, QIcon::Disabled, QIcon::Active, QIcon::Selected})
|
||||
painter.drawText(QPoint(0, labelYs[mode]), modeLabels[mode]);
|
||||
painter.translate(labelWidth, 0);
|
||||
for (const auto &state : {QIcon::On, QIcon::Off})
|
||||
painter.drawText(QPoint(labelXs[state], 0), stateLabels[state]);
|
||||
}
|
||||
QFrame::paintEvent(event);
|
||||
}
|
||||
private:
|
||||
QIcon icon;
|
||||
void updateIcon(const QString &iconName)
|
||||
{
|
||||
icon = QIcon::fromTheme(iconName);
|
||||
update();
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
qputenv("QT_ENABLE_EXPERIMENTAL_ICON_ENGINES", "1");
|
||||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
IconModel model;
|
||||
|
||||
QTabWidget widget;
|
||||
widget.setTabPosition(QTabWidget::West);
|
||||
widget.addTab(new IconInspector, "Inspect");
|
||||
widget.addTab(new IconView<IconModel::Icon>(&model), "QIcon::fromTheme");
|
||||
widget.addTab(new IconView<IconModel::Style>(&model), "QStyle");
|
||||
widget.addTab(new IconView<IconModel::Theme>(&model), "QPlatformTheme");
|
||||
|
||||
widget.show();
|
||||
return app.exec();
|
||||
}
|
Loading…
Reference in New Issue
Block a user