diff options
Diffstat (limited to 'src/widgets/styles/qstylesheetstyle.cpp')
-rw-r--r-- | src/widgets/styles/qstylesheetstyle.cpp | 1061 |
1 files changed, 659 insertions, 402 deletions
diff --git a/src/widgets/styles/qstylesheetstyle.cpp b/src/widgets/styles/qstylesheetstyle.cpp index a45401fd1f..655b224617 100644 --- a/src/widgets/styles/qstylesheetstyle.cpp +++ b/src/widgets/styles/qstylesheetstyle.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtWidgets module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 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 <qglobal.h> #include "qstylesheetstyle_p.h" @@ -95,7 +59,7 @@ #include <private/qstyleanimation_p.h> #endif #if QT_CONFIG(tabbar) -#include <qtabbar.h> +#include <private/qtabbar_p.h> #endif #include <QMetaProperty> #if QT_CONFIG(mainwindow) @@ -134,6 +98,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + using namespace QCss; @@ -151,7 +117,7 @@ static QStyleSheetStyleCaches *styleSheetCaches = nullptr; * the QStyleSheetStyle is a proxy. If used with others proxy style, we may end up with something like: * QStyleSheetStyle -> ProxyStyle -> QStyleSheetStyle -> OriginalStyle * Recursion may happen if the style call the widget()->style() again. - * Not to mention the performence penalty of having two lookup of rules. + * Not to mention the performance penalty of having two lookup of rules. * * The first instance of QStyleSheetStyle will set globalStyleSheetStyle to itself. The second one * will notice the globalStyleSheetStyle is not istelf and call its base style directly. @@ -172,8 +138,6 @@ class QStyleSheetStyleRecursionGuard if (globalStyleSheetStyle != 0 && globalStyleSheetStyle != this) { RETURN; } \ QStyleSheetStyleRecursionGuard recursion_guard(this); -#define ceil(x) ((int)(x) + ((x) > 0 && (x) != (int)(x))) - enum PseudoElement { PseudoElement_None, PseudoElement_DownArrow, @@ -194,7 +158,7 @@ enum PseudoElement { PseudoElement_GroupBoxIndicator, PseudoElement_ToolButtonMenu, PseudoElement_ToolButtonMenuArrow, - PseudoElement_ToolButtonDownArrow, + PseudoElement_ToolButtonMenuIndicator, PseudoElement_ToolBoxTab, PseudoElement_ScrollBarSlider, PseudoElement_ScrollBarAddPage, @@ -470,15 +434,26 @@ struct QStyleSheetBoxData : public QSharedData struct QStyleSheetPaletteData : public QSharedData { - QStyleSheetPaletteData(const QBrush &fg, const QBrush &sfg, const QBrush &sbg, - const QBrush &abg) - : foreground(fg), selectionForeground(sfg), selectionBackground(sbg), - alternateBackground(abg) { } + QStyleSheetPaletteData(const QBrush &foreground, + const QBrush &selectedForeground, + const QBrush &selectedBackground, + const QBrush &alternateBackground, + const QBrush &placeHolderTextForeground, + const QBrush &accent) + : foreground(foreground) + , selectionForeground(selectedForeground) + , selectionBackground(selectedBackground) + , alternateBackground(alternateBackground) + , placeholderForeground(placeHolderTextForeground) + , accent(accent) + { } QBrush foreground; QBrush selectionForeground; QBrush selectionBackground; QBrush alternateBackground; + QBrush placeholderForeground; + QBrush accent; }; struct QStyleSheetGeometryData : public QSharedData @@ -650,7 +625,7 @@ public: Q_DECLARE_TYPEINFO(QRenderRule, Q_RELOCATABLE_TYPE); /////////////////////////////////////////////////////////////////////////////////////////// -static const char knownStyleHints[][45] = { +static constexpr std::array<const char*, 90> knownStyleHints = { "activate-on-singleclick", "alignment", "arrow-keys-navigate-into-children", @@ -743,13 +718,10 @@ static const char knownStyleHints[][45] = { "widget-animation-duration" }; -static const int numKnownStyleHints = sizeof(knownStyleHints)/sizeof(knownStyleHints[0]); - -static QList<QVariant> subControlLayout(const QString& layout) +static QList<QVariant> subControlLayout(QByteArrayView layout) { QList<QVariant> buttons; - for (int i = 0; i < layout.count(); i++) { - int button = layout[i].toLatin1(); + for (int button : layout) { switch (button) { case 'm': buttons.append(PseudoElement_MdiMinButton); @@ -804,17 +776,16 @@ QHash<QStyle::SubControl, QRect> QStyleSheetStyle::titleBarLayout(const QWidget const bool isMaximized = tb->titleBarState & Qt::WindowMaximized; QRenderRule subRule = renderRule(w, tb); QRect cr = subRule.contentsRect(tb->rect); - QList<QVariant> layout = subRule.styleHint(QLatin1String("button-layout")).toList(); + QList<QVariant> layout = subRule.styleHint("button-layout"_L1).toList(); if (layout.isEmpty()) - layout = subControlLayout(QLatin1String("I(T)HSmMX")); + layout = subControlLayout("I(T)HSmMX"_L1); int offsets[3] = { 0, 0, 0 }; enum Where { Left, Right, Center, NoWhere } where = Left; QList<ButtonInfo> infos; - const int numLayouts = layout.size(); - infos.reserve(numLayouts); - for (int i = 0; i < numLayouts; i++) { - const int element = layout[i].toInt(); + infos.reserve(layout.size()); + for (const QVariant &val : std::as_const(layout)) { + const int element = val.toInt(); if (element == '(') { where = Center; } else if (element == ')') { @@ -873,8 +844,7 @@ QHash<QStyle::SubControl, QRect> QStyleSheetStyle::titleBarLayout(const QWidget } } - for (int i = 0; i < infos.size(); i++) { - const ButtonInfo &info = infos[i]; + for (const ButtonInfo &info : std::as_const(infos)) { QRect lr = cr; switch (info.where) { case Center: { @@ -990,10 +960,17 @@ QRenderRule::QRenderRule(const QList<Declaration> &declarations, const QObject * bg = new QStyleSheetBackgroundData(brush, pixmap, repeat, alignment, origin, attachment, clip); } - QBrush sfg, fg; - QBrush sbg, abg; - if (v.extractPalette(&fg, &sfg, &sbg, &abg)) - pal = new QStyleSheetPaletteData(fg, sfg, sbg, abg); + QBrush foreground; + QBrush selectedForeground; + QBrush selectedBackground; + QBrush alternateBackground; + QBrush placeHolderTextForeground; + QBrush accent; + if (v.extractPalette(&foreground, &selectedForeground, &selectedBackground, + &alternateBackground, &placeHolderTextForeground, &accent)) { + pal = new QStyleSheetPaletteData(foreground, selectedForeground, selectedBackground, + alternateBackground, placeHolderTextForeground, accent); + } QIcon imgIcon; alignment = Qt::AlignCenter; @@ -1014,7 +991,7 @@ QRenderRule::QRenderRule(const QList<Declaration> &declarations, const QObject * palette = QToolTip::palette(); #endif - for (int i = 0; i < declarations.count(); i++) { + for (int i = 0; i < declarations.size(); i++) { const Declaration& decl = declarations.at(i); if (decl.d->propertyId == BorderImage) { QString uri; @@ -1022,7 +999,7 @@ QRenderRule::QRenderRule(const QList<Declaration> &declarations, const QObject * int cuts[4]; decl.borderImageValue(&uri, cuts, &horizStretch, &vertStretch); - if (uri.isEmpty() || uri == QLatin1String("none")) { + if (uri.isEmpty() || uri == "none"_L1) { if (bd && bd->bi) bd->bi->pixmap = QPixmap(); } else { @@ -1044,38 +1021,66 @@ QRenderRule::QRenderRule(const QList<Declaration> &declarations, const QObject * int role = decl.d->values.at(0).variant.toInt(); if (role >= Value_FirstColorRole && role <= Value_LastColorRole) defaultBackground = palette.color((QPalette::ColorRole)(role-Value_FirstColorRole)); - } else if (decl.d->property.startsWith(QLatin1String("qproperty-"), Qt::CaseInsensitive)) { + } else if (decl.d->property.startsWith("qproperty-"_L1, Qt::CaseInsensitive)) { // intentionally left blank... } else if (decl.d->propertyId == UnknownProperty) { bool knownStyleHint = false; - for (int i = 0; i < numKnownStyleHints; i++) { - QLatin1String styleHint(knownStyleHints[i]); + for (const auto sh : knownStyleHints) { + QLatin1StringView styleHint(sh); if (decl.d->property.compare(styleHint) == 0) { - QString hintName = QString(styleHint); - QVariant hintValue; - if (hintName.endsWith(QLatin1String("alignment"))) { - hintValue = (int) decl.alignmentValue(); - } else if (hintName.endsWith(QLatin1String("color"))) { - hintValue = (int) decl.colorValue().rgba(); - } else if (hintName.endsWith(QLatin1String("size"))) { - hintValue = decl.sizeValue(); - } else if (hintName.endsWith(QLatin1String("icon"))) { - hintValue = decl.iconValue(); - } else if (hintName == QLatin1String("button-layout") - && decl.d->values.count() != 0 && decl.d->values.at(0).type == Value::String) { - hintValue = subControlLayout(decl.d->values.at(0).variant.toString()); - } else { - int integer; - decl.intValue(&integer); - hintValue = integer; - } - styleHints[decl.d->property] = hintValue; - knownStyleHint = true; - break; + QString hintName = QString(styleHint); + QVariant hintValue; + if (hintName.endsWith("alignment"_L1)) { + hintValue = (int) decl.alignmentValue(); + } else if (hintName.endsWith("color"_L1)) { + hintValue = (int) decl.colorValue().rgba(); + } else if (hintName.endsWith("size"_L1)) { + // Check only for the 'em' case + const QString valueString = decl.d->values.at(0).variant.toString(); + const bool isEmSize = valueString.endsWith(u"em", Qt::CaseInsensitive); + if (isEmSize || valueString.endsWith(u"ex", Qt::CaseInsensitive)) { + // 1em == size of font; 1ex == xHeight of font + // See lengthValueFromData helper in qcssparser.cpp + QFont fontForSize(font); + // if no font is specified, then use the widget font if possible + if (const QWidget *widget; !hasFont && (widget = qobject_cast<const QWidget*>(object))) + fontForSize = widget->font(); + + const QFontMetrics fontMetrics(fontForSize); + qreal pixelSize = isEmSize ? fontMetrics.height() : fontMetrics.xHeight(); + + // Transform size according to the 'em'/'ex' value + qreal emexSize = {}; + if (decl.realValue(&emexSize, isEmSize ? "em" : "ex") && emexSize > 0) { + pixelSize *= emexSize; + const QSizeF newSize(pixelSize, pixelSize); + decl.d->parsed = QVariant::fromValue<QSizeF>(newSize); + hintValue = newSize; + } else { + qWarning("Invalid '%s' size for %s. Skipping.", + isEmSize ? "em" : "ex", qPrintable(valueString)); + } + } else { + // Normal case where we receive a 'px' or 'pt' unit + hintValue = decl.sizeValue(); + } + } else if (hintName.endsWith("icon"_L1)) { + hintValue = decl.iconValue(); + } else if (hintName == "button-layout"_L1 && decl.d->values.size() != 0 + && decl.d->values.at(0).type == QCss::Value::String) { + hintValue = subControlLayout(decl.d->values.at(0).variant.toString().toLatin1()); + } else { + int integer; + decl.intValue(&integer); + hintValue = integer; + } + styleHints[decl.d->property] = hintValue; + knownStyleHint = true; + break; } } if (!knownStyleHint) - qDebug("Unknown property %s", qPrintable(decl.d->property)); + qWarning("Unknown property %s", qPrintable(decl.d->property)); } } @@ -1390,7 +1395,7 @@ void QRenderRule::drawBackground(QPainter *p, const QRect& rect, const QPoint& o // ### fix for gradients const QPainterPath &borderPath = borderClip(originRect(rect, origin)); if (!borderPath.isEmpty()) { - // Drawn intead of being used as clipping path for better visual quality + // Drawn instead of being used as clipping path for better visual quality bool wasAntialiased = p->renderHints() & QPainter::Antialiasing; p->setRenderHint(QPainter::Antialiasing); p->fillPath(borderPath, brush); @@ -1455,6 +1460,16 @@ void QRenderRule::configurePalette(QPalette *p, QPalette::ColorRole fr, QPalette p->setBrush(QPalette::AlternateBase, pal->alternateBackground); } +void setDefault(QPalette *palette, QPalette::ColorGroup group, QPalette::ColorRole role, + const QBrush &defaultBrush, const QWidget *widget) +{ + const QPalette &widgetPalette = widget->palette(); + if (widgetPalette.isBrushSet(group, role)) + palette->setBrush(group, role, widgetPalette.brush(group, role)); + else + palette->setBrush(group, role, defaultBrush); +} + void QRenderRule::configurePalette(QPalette *p, QPalette::ColorGroup cg, const QWidget *w, bool embedded) { if (bg && bg->brush.style() != Qt::NoBrush) { @@ -1476,11 +1491,15 @@ void QRenderRule::configurePalette(QPalette *p, QPalette::ColorGroup cg, const Q return; if (pal->foreground.style() != Qt::NoBrush) { - p->setBrush(cg, QPalette::ButtonText, pal->foreground); - p->setBrush(cg, w->foregroundRole(), pal->foreground); - p->setBrush(cg, QPalette::WindowText, pal->foreground); - p->setBrush(cg, QPalette::Text, pal->foreground); - p->setBrush(cg, QPalette::PlaceholderText, pal->foreground); + setDefault(p, cg, QPalette::ButtonText, pal->foreground, w); + setDefault(p, cg, w->foregroundRole(), pal->foreground, w); + setDefault(p, cg, QPalette::WindowText, pal->foreground, w); + setDefault(p, cg, QPalette::Text, pal->foreground, w); + QColor phColor(pal->foreground.color()); + phColor.setAlpha((phColor.alpha() + 1) / 2); + QBrush placeholder = pal->foreground; + placeholder.setColor(phColor); + setDefault(p, cg, QPalette::PlaceholderText, placeholder, w); } if (pal->selectionBackground.style() != Qt::NoBrush) p->setBrush(cg, QPalette::Highlight, pal->selectionBackground); @@ -1488,6 +1507,10 @@ void QRenderRule::configurePalette(QPalette *p, QPalette::ColorGroup cg, const Q p->setBrush(cg, QPalette::HighlightedText, pal->selectionForeground); if (pal->alternateBackground.style() != Qt::NoBrush) p->setBrush(cg, QPalette::AlternateBase, pal->alternateBackground); + if (pal->placeholderForeground.style() != Qt::NoBrush) + p->setBrush(cg, QPalette::PlaceholderText, pal->placeholderForeground); + if (pal->accent.style() != Qt::NoBrush) + p->setBrush(cg, QPalette::Accent, pal->accent); } bool QRenderRule::hasModification() const @@ -1533,48 +1556,62 @@ public: const QMetaObject *metaObject = OBJECT_PTR(node)->metaObject(); #if QT_CONFIG(tooltip) if (qstrcmp(metaObject->className(), "QTipLabel") == 0) - return QStringList(QLatin1String("QToolTip")); + return QStringList("QToolTip"_L1); #endif QStringList result; do { - result += QString::fromLatin1(metaObject->className()).replace(QLatin1Char(':'), QLatin1Char('-')); + result += QString::fromLatin1(metaObject->className()).replace(u':', u'-'); metaObject = metaObject->superClass(); } while (metaObject != nullptr); return result; } - QString attribute(NodePtr node, const QString& name) const override + QString attributeValue(NodePtr node, const QCss::AttributeSelector& aSelector) const override { if (isNullNode(node)) return QString(); + const QString &name = aSelector.name; QHash<QString, QString> &cache = m_attributeCache[OBJECT_PTR(node)]; QHash<QString, QString>::const_iterator cacheIt = cache.constFind(name); if (cacheIt != cache.constEnd()) return cacheIt.value(); + QVariant value; + QString valueStr; QObject *obj = OBJECT_PTR(node); - QVariant value = obj->property(name.toLatin1()); - if (!value.isValid()) { - if (name == QLatin1String("class")) { - QString className = QString::fromLatin1(obj->metaObject()->className()); - if (className.contains(QLatin1Char(':'))) - className.replace(QLatin1Char(':'), QLatin1Char('-')); - cache[name] = className; - return className; - } else if (name == QLatin1String("style")) { - QWidget *w = qobject_cast<QWidget *>(obj); - QStyleSheetStyle *proxy = w ? qt_styleSheet(w->style()) : nullptr; - if (proxy) { - QString styleName = QString::fromLatin1(proxy->baseStyle()->metaObject()->className()); - cache[name] = styleName; - return styleName; + const int propertyIndex = obj->metaObject()->indexOfProperty(name.toLatin1()); + if (propertyIndex == -1) { + value = obj->property(name.toLatin1()); // might be a dynamic property + if (!value.isValid()) { + if (name == "class"_L1) { + QString className = QString::fromLatin1(obj->metaObject()->className()); + if (className.contains(u':')) + className.replace(u':', u'-'); + valueStr = className; + } else if (name == "style"_L1) { + QWidget *w = qobject_cast<QWidget *>(obj); + QStyleSheetStyle *proxy = w ? qt_styleSheet(w->style()) : nullptr; + if (proxy) + valueStr = QString::fromLatin1(proxy->baseStyle()->metaObject()->className()); } } + } else { + const QMetaProperty property = obj->metaObject()->property(propertyIndex); + value = property.read(obj); + // support Qt 5 selector syntax, which required the integer enum value + if (property.isEnumType()) { + bool isNumber; + aSelector.value.toInt(&isNumber); + if (isNumber) + value.convert(QMetaType::fromType<int>()); + } + } + if (value.isValid()) { + valueStr = (value.userType() == QMetaType::QStringList + || value.userType() == QMetaType::QVariantList) + ? value.toStringList().join(u' ') + : value.toString(); } - QString valueStr = (value.userType() == QMetaType::QStringList - || value.userType() == QMetaType::QVariantList) - ? value.toStringList().join(QLatin1Char(' ')) - : value.toString(); cache[name] = valueStr; return valueStr; } @@ -1585,11 +1622,11 @@ public: const QMetaObject *metaObject = OBJECT_PTR(node)->metaObject(); #if QT_CONFIG(tooltip) if (qstrcmp(metaObject->className(), "QTipLabel") == 0) - return nodeName == QLatin1String("QToolTip"); + return nodeName == "QToolTip"_L1; #endif do { - const ushort *uc = (const ushort *)nodeName.constData(); - const ushort *e = uc + nodeName.length(); + const auto *uc = reinterpret_cast<const char16_t *>(nodeName.constData()); + const auto *e = uc + nodeName.size(); const uchar *c = (const uchar *)metaObject->className(); while (*c && uc != e && (*uc == *c || (*c == ':' && *uc == '-'))) { ++uc; @@ -1639,7 +1676,8 @@ QList<QCss::StyleRule> QStyleSheetStyle::styleRules(const QObject *obj) const defaultSs = getDefaultStyleSheet(); QStyle *bs = baseStyle(); styleSheetCaches->styleSheetCache.insert(bs, defaultSs); - QObject::connect(bs, SIGNAL(destroyed(QObject*)), styleSheetCaches, SLOT(styleDestroyed(QObject*)), Qt::UniqueConnection); + QObject::connect(bs, &QStyle::destroyed, styleSheetCaches, + &QStyleSheetStyleCaches::styleDestroyed); } else { defaultSs = defaultCacheIt.value(); } @@ -1650,7 +1688,7 @@ QList<QCss::StyleRule> QStyleSheetStyle::styleRules(const QObject *obj) const QHash<const void *, StyleSheet>::const_iterator appCacheIt = styleSheetCaches->styleSheetCache.constFind(qApp); if (appCacheIt == styleSheetCaches->styleSheetCache.constEnd()) { QString ss = qApp->styleSheet(); - if (ss.startsWith(QLatin1String("file:///"))) + if (ss.startsWith("file:///"_L1)) ss.remove(0, 8); parser.init(ss, qApp->styleSheet() != ss); if (Q_UNLIKELY(!parser.parse(&appSs))) @@ -1674,7 +1712,7 @@ QList<QCss::StyleRule> QStyleSheetStyle::styleRules(const QObject *obj) const if (objCacheIt == styleSheetCaches->styleSheetCache.constEnd()) { parser.init(styleSheet); if (!parser.parse(&ss)) { - parser.init(QLatin1String("* {") + styleSheet + QLatin1Char('}')); + parser.init("* {"_L1 + styleSheet + u'}'); if (Q_UNLIKELY(!parser.parse(&ss))) qWarning() << "Could not parse stylesheet of object" << o; } @@ -1686,8 +1724,8 @@ QList<QCss::StyleRule> QStyleSheetStyle::styleRules(const QObject *obj) const objectSs.append(ss); } - for (int i = 0; i < objectSs.count(); i++) - objectSs[i].depth = objectSs.count() - i + 2; + for (int i = 0; i < objectSs.size(); i++) + objectSs[i].depth = objectSs.size() - i + 2; styleSelector.styleSheets += objectSs; @@ -1704,7 +1742,7 @@ static QList<Declaration> declarations(const QList<StyleRule> &styleRules, const quint64 pseudoClass = PseudoClass_Unspecified) { QList<Declaration> decls; - for (int i = 0; i < styleRules.count(); i++) { + for (int i = 0; i < styleRules.size(); i++) { const Selector& selector = styleRules.at(i).selectors.at(0); // Rules with pseudo elements don't cascade. This is an intentional // diversion for CSS @@ -1811,9 +1849,9 @@ static void qt_check_if_internal_object(const QObject **obj, int *element) Q_UNUSED(element); #else if (*obj && qstrcmp((*obj)->metaObject()->className(), "QDockWidgetTitleButton") == 0) { - if ((*obj)->objectName() == QLatin1String("qt_dockwidget_closebutton")) { + if ((*obj)->objectName() == "qt_dockwidget_closebutton"_L1) { *element = PseudoElement_DockWidgetCloseButton; - } else if ((*obj)->objectName() == QLatin1String("qt_dockwidget_floatbutton")) { + } else if ((*obj)->objectName() == "qt_dockwidget_floatbutton"_L1) { *element = PseudoElement_DockWidgetFloatButton; } *obj = (*obj)->parent(); @@ -1834,7 +1872,7 @@ QRenderRule QStyleSheetStyle::renderRule(const QObject *obj, int element, quint6 quint64 stateMask = 0; const QList<StyleRule> rules = styleRules(obj); - for (int i = 0; i < rules.count(); i++) { + for (int i = 0; i < rules.size(); i++) { const Selector& selector = rules.at(i).selectors.at(0); quint64 negated = 0; stateMask |= selector.pseudoClass(&negated); @@ -1849,7 +1887,7 @@ QRenderRule QStyleSheetStyle::renderRule(const QObject *obj, int element, quint6 } - const QString part = QLatin1String(knownPseudoElements[element].name); + const QString part = QLatin1StringView(knownPseudoElements[element].name); QList<Declaration> decls = declarations(rules, part, state); QRenderRule newRule(decls, obj); cache[state] = newRule; @@ -1899,7 +1937,7 @@ QRenderRule QStyleSheetStyle::renderRule(const QObject *obj, const QStyleOption break; case PseudoElement_ToolButtonMenu: case PseudoElement_ToolButtonMenuArrow: - case PseudoElement_ToolButtonDownArrow: + case PseudoElement_ToolButtonMenuIndicator: state |= complex->state & QStyle::State_MouseOver; if (complex->state & QStyle::State_Sunken || complex->activeSubControls & QStyle::SC_ToolButtonMenu) @@ -2155,8 +2193,8 @@ bool QStyleSheetStyle::hasStyleRule(const QObject *obj, int part) const return result; } - QString pseudoElement = QLatin1String(knownPseudoElements[part].name); - for (int i = 0; i < rules.count(); i++) { + auto pseudoElement = QLatin1StringView(knownPseudoElements[part].name); + for (int i = 0; i < rules.size(); i++) { const Selector& selector = rules.at(i).selectors.at(0); if (pseudoElement.compare(selector.pseudoElement(), Qt::CaseInsensitive) == 0) { cache[part] = true; @@ -2188,7 +2226,7 @@ static Origin defaultOrigin(int pe) case PseudoElement_SpinBoxDownButton: case PseudoElement_PushButtonMenuIndicator: case PseudoElement_ComboBoxDropDown: - case PseudoElement_ToolButtonDownArrow: + case PseudoElement_ToolButtonMenuIndicator: case PseudoElement_MenuCheckMark: case PseudoElement_MenuIcon: case PseudoElement_MenuRightArrow: @@ -2229,7 +2267,7 @@ static Qt::Alignment defaultPosition(int pe) case PseudoElement_ScrollBarLast: case PseudoElement_SpinBoxDownButton: case PseudoElement_PushButtonMenuIndicator: - case PseudoElement_ToolButtonDownArrow: + case PseudoElement_ToolButtonMenuIndicator: return Qt::AlignRight | Qt::AlignBottom; case PseudoElement_ScrollBarSubLine: @@ -2249,6 +2287,9 @@ static Qt::Alignment defaultPosition(int pe) case PseudoElement_SpinBoxDownArrow: case PseudoElement_ComboBoxArrow: case PseudoElement_DownArrow: + case PseudoElement_UpArrow: + case PseudoElement_LeftArrow: + case PseudoElement_RightArrow: case PseudoElement_ToolButtonMenuArrow: case PseudoElement_SliderGroove: return Qt::AlignCenter; @@ -2304,8 +2345,11 @@ QSize QStyleSheetStyle::defaultSize(const QWidget *w, QSize sz, const QRect& rec case PseudoElement_ComboBoxArrow: case PseudoElement_DownArrow: + case PseudoElement_UpArrow: + case PseudoElement_LeftArrow: + case PseudoElement_RightArrow: case PseudoElement_ToolButtonMenuArrow: - case PseudoElement_ToolButtonDownArrow: + case PseudoElement_ToolButtonMenuIndicator: case PseudoElement_MenuRightArrow: if (sz.width() == -1) sz.setWidth(13); @@ -2457,11 +2501,12 @@ static QWidget *embeddedWidget(QWidget *w) } /** \internal - in case w is an embedded widget, return the container widget - (i.e, the widget for which the rules actualy apply) - (exemple, if w is a lineedit embedded in a combobox, return the combobox) + Returns the widget whose style rules apply to \a w. + + When \a w is an embedded widget, this is the container widget. + For example, if w is a line edit embedded in a combobox, this returns the combobox. + When \a w is not embedded, this function return \a w itself. - if w is not embedded, return w itself */ static QWidget *containerWidget(const QWidget *w) { @@ -2549,8 +2594,9 @@ static quint64 extendedPseudoClass(const QWidget *w) } else if (const QPlainTextEdit *edit = qobject_cast<const QPlainTextEdit *>(w)) { pc |= (edit->isReadOnly() ? PseudoClass_ReadOnly : PseudoClass_Editable); - } + } else #endif + {} return pc; } @@ -2619,18 +2665,17 @@ void QStyleSheetStyle::setProperties(QWidget *w) { // scan decls for final occurrence of each "qproperty" - QDuplicateTracker<QString> propertySet; - propertySet.reserve(decls.size()); - for (int i = decls.count() - 1; i >= 0; --i) { + QDuplicateTracker<QString> propertySet(decls.size()); + for (int i = decls.size() - 1; i >= 0; --i) { const QString property = decls.at(i).d->property; - if (!property.startsWith(QLatin1String("qproperty-"), Qt::CaseInsensitive)) + if (!property.startsWith("qproperty-"_L1, Qt::CaseInsensitive)) continue; if (!propertySet.hasSeen(property)) finals.append(i); } } - for (int i = finals.count() - 1; i >= 0; --i) { + for (int i = finals.size() - 1; i >= 0; --i) { const Declaration &decl = decls.at(finals[i]); QStringView property = decl.d->property; property = property.mid(10); // strip "qproperty-" @@ -2664,6 +2709,9 @@ void QStyleSheetStyle::setProperties(QWidget *w) default: v = decl.d->values.at(0).variant; break; } + if (propertyL1 == QByteArrayView("styleSheet") && value == v) + continue; + w->setProperty(propertyL1, v); } } @@ -2784,7 +2832,7 @@ static void updateObjects(const QList<const QObject *>& objects) QCoreApplication::sendEvent(widget, &event); QList<const QObject *> children; children.reserve(widget->children().size() + 1); - for (auto child: qAsConst(widget->children())) + for (auto child: std::as_const(widget->children())) children.append(child); updateObjects(children); } @@ -2852,7 +2900,9 @@ bool QStyleSheetStyle::initObject(const QObject *obj) const const_cast<QWidget *>(w)->setAttribute(Qt::WA_StyleSheet, true); } - QObject::connect(obj, SIGNAL(destroyed(QObject*)), styleSheetCaches, SLOT(objectDestroyed(QObject*)), Qt::UniqueConnection); + connect(obj, &QObject::destroyed, + styleSheetCaches, &QStyleSheetStyleCaches::objectDestroyed, + Qt::UniqueConnection); return true; } @@ -2866,7 +2916,7 @@ void QStyleSheetStyle::polish(QWidget *w) if (styleSheetCaches->styleRulesCache.contains(w)) { // the widget accessed its style pointer before polish (or repolish) - // (exemple: the QAbstractSpinBox constructor ask for the stylehint) + // (example: the QAbstractSpinBox constructor ask for the stylehint) styleSheetCaches->styleRulesCache.remove(w); styleSheetCaches->hasStyleRuleCache.remove(w); styleSheetCaches->renderRulesCache.remove(w); @@ -2879,7 +2929,7 @@ void QStyleSheetStyle::polish(QWidget *w) //set the WA_Hover attribute if one of the selector depends of the hover state QList<StyleRule> rules = styleRules(w); - for (int i = 0; i < rules.count(); i++) { + for (int i = 0; i < rules.size(); i++) { const Selector& selector = rules.at(i).selectors.at(0); quint64 negated = 0; quint64 cssClass = selector.pseudoClass(&negated); @@ -2896,10 +2946,10 @@ void QStyleSheetStyle::polish(QWidget *w) QRenderRule rule = renderRule(sa, PseudoElement_None, PseudoClass_Enabled); if ((rule.hasBorder() && rule.border()->hasBorderImage()) || (rule.hasBackground() && !rule.background()->pixmap.isNull())) { - QObject::connect(sa->horizontalScrollBar(), SIGNAL(valueChanged(int)), - sa, SLOT(update()), Qt::UniqueConnection); - QObject::connect(sa->verticalScrollBar(), SIGNAL(valueChanged(int)), - sa, SLOT(update()), Qt::UniqueConnection); + connect(sa->horizontalScrollBar(), &QScrollBar::valueChanged, + sa, QOverload<>::of(&QAbstractScrollArea::update), Qt::UniqueConnection); + connect(sa->verticalScrollBar(), &QScrollBar::valueChanged, + sa, QOverload<>::of(&QAbstractScrollArea::update), Qt::UniqueConnection); } } #endif @@ -2969,7 +3019,7 @@ void QStyleSheetStyle::repolish(QWidget *w) { QList<const QObject *> children; children.reserve(w->children().size() + 1); - for (auto child: qAsConst(w->children())) + for (auto child: std::as_const(w->children())) children.append(child); children.append(w); styleSheetCaches->styleSheetCache.remove(w); @@ -3002,13 +3052,13 @@ void QStyleSheetStyle::unpolish(QWidget *w) setGeometry(w); w->setAttribute(Qt::WA_StyleSheetTarget, false); w->setAttribute(Qt::WA_StyleSheet, false); - QObject::disconnect(w, nullptr, this, nullptr); + w->disconnect(this); #if QT_CONFIG(scrollarea) if (QAbstractScrollArea *sa = qobject_cast<QAbstractScrollArea *>(w)) { - QObject::disconnect(sa->horizontalScrollBar(), SIGNAL(valueChanged(int)), - sa, SLOT(update())); - QObject::disconnect(sa->verticalScrollBar(), SIGNAL(valueChanged(int)), - sa, SLOT(update())); + disconnect(sa->horizontalScrollBar(), &QScrollBar::valueChanged, + sa, QOverload<>::of(&QAbstractScrollArea::update)); + disconnect(sa->verticalScrollBar(), &QScrollBar::valueChanged, + sa, QOverload<>::of(&QAbstractScrollArea::update)); } #endif baseStyle()->unpolish(w); @@ -3024,16 +3074,6 @@ void QStyleSheetStyle::unpolish(QApplication *app) styleSheetCaches->styleSheetCache.remove(qApp); } -#if QT_CONFIG(tabbar) -inline static bool verticalTabs(QTabBar::Shape shape) -{ - return shape == QTabBar::RoundedWest - || shape == QTabBar::RoundedEast - || shape == QTabBar::TriangularWest - || shape == QTabBar::TriangularEast; -} -#endif // QT_CONFIG(tabbar) - void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *w) const { @@ -3237,8 +3277,39 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC rule.configurePalette(&toolOpt.palette, QPalette::ButtonText, QPalette::Button); toolOpt.font = rule.font.resolve(toolOpt.font); toolOpt.rect = rule.borderRect(opt->rect); - bool customArrow = (tool->features & (QStyleOptionToolButton::HasMenu | QStyleOptionToolButton::MenuButtonPopup)); - bool customDropDown = tool->features & QStyleOptionToolButton::MenuButtonPopup; + const auto customArrowElement = [tool]{ + switch (tool->arrowType) { + case Qt::DownArrow: return PseudoElement_DownArrow; + case Qt::UpArrow: return PseudoElement_UpArrow; + case Qt::LeftArrow: return PseudoElement_LeftArrow; + case Qt::RightArrow: return PseudoElement_RightArrow; + default: break; + } + return PseudoElement_None; + }; + // if arrow/menu/indicators are requested, either draw them using the available rule, + // or let the base style draw them; but not both + const bool drawArrow = tool->features & QStyleOptionToolButton::Arrow; + bool customArrow = drawArrow && hasStyleRule(w, customArrowElement()); + if (customArrow) { + toolOpt.features &= ~QStyleOptionToolButton::Arrow; + toolOpt.text = QString(); // we need to draw the arrow and the text ourselves + } + bool drawDropDown = tool->features & QStyleOptionToolButton::MenuButtonPopup; + bool customDropDown = drawDropDown && hasStyleRule(w, PseudoElement_ToolButtonMenu); + bool customDropDownArrow = false; + bool drawMenuIndicator = tool->features & QStyleOptionToolButton::HasMenu; + if (customDropDown) { + toolOpt.subControls &= ~QStyle::SC_ToolButtonMenu; + customDropDownArrow = hasStyleRule(w, PseudoElement_ToolButtonMenuArrow); + if (customDropDownArrow) + toolOpt.features &= ~(QStyleOptionToolButton::Menu | QStyleOptionToolButton::HasMenu); + } + const bool customMenuIndicator = (!drawDropDown && drawMenuIndicator) + && hasStyleRule(w, PseudoElement_ToolButtonMenuIndicator); + if (customMenuIndicator) + toolOpt.features &= ~QStyleOptionToolButton::HasMenu; + if (rule.hasNativeBorder()) { if (tool->subControls & SC_ToolButton) { //in some case (eg. the button is "auto raised") the style doesn't draw the background @@ -3252,21 +3323,23 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC if (!(bflags & (State_Sunken | State_On | State_Raised))) rule.drawBackground(p, toolOpt.rect); } - customArrow = customArrow && hasStyleRule(w, PseudoElement_ToolButtonDownArrow); - if (customArrow) - toolOpt.features &= ~QStyleOptionToolButton::HasMenu; - customDropDown = customDropDown && hasStyleRule(w, PseudoElement_ToolButtonMenu); - if (customDropDown) - toolOpt.subControls &= ~QStyle::SC_ToolButtonMenu; - - if (rule.baseStyleCanDraw() && !(tool->features & QStyleOptionToolButton::Arrow)) { - baseStyle()->drawComplexControl(cc, &toolOpt, p, w); - } else { - QWindowsStyle::drawComplexControl(cc, &toolOpt, p, w); - } - if (!customArrow && !customDropDown) - return; + QStyleOptionToolButton nativeToolOpt(toolOpt); + // don't draw natively if we have a custom rule for menu indicators and/or buttons + if (customMenuIndicator) + nativeToolOpt.features &= ~(QStyleOptionToolButton::Menu | QStyleOptionToolButton::HasMenu); + if (customDropDown || customDropDownArrow) + nativeToolOpt.features &= ~(QStyleOptionToolButton::Menu | QStyleOptionToolButton::HasMenu | QStyleOptionToolButton::MenuButtonPopup); + // Let base or windows style draw the button, which will include the menu-button + if (rule.baseStyleCanDraw() && !(tool->features & QStyleOptionToolButton::Arrow)) + baseStyle()->drawComplexControl(cc, &nativeToolOpt, p, w); + else + QWindowsStyle::drawComplexControl(cc, &nativeToolOpt, p, w); + // if we did draw natively, don't draw custom + if (nativeToolOpt.features & (QStyleOptionToolButton::Menu | QStyleOptionToolButton::HasMenu)) + drawMenuIndicator = false; + if (nativeToolOpt.features & QStyleOptionToolButton::MenuButtonPopup && !customDropDownArrow) + drawDropDown = false; } else { rule.drawRule(p, opt->rect); toolOpt.rect = rule.contentsRect(opt->rect); @@ -3275,33 +3348,78 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC drawControl(CE_ToolButtonLabel, &toolOpt, p, w); } - QRenderRule subRule = renderRule(w, opt, PseudoElement_ToolButtonMenu); - QRect r = subControlRect(CC_ToolButton, opt, QStyle::SC_ToolButtonMenu, w); - if (customDropDown) { + const QRect cr = toolOpt.rect; + // Draw DropDownButton unless drawn before + if (drawDropDown) { if (opt->subControls & QStyle::SC_ToolButtonMenu) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ToolButtonMenu); + QRect menuButtonRect = subControlRect(CC_ToolButton, opt, QStyle::SC_ToolButtonMenu, w); if (subRule.hasDrawable()) { - subRule.drawRule(p, r); + subRule.drawRule(p, menuButtonRect); } else { - toolOpt.rect = r; + toolOpt.rect = menuButtonRect; baseStyle()->drawPrimitive(PE_IndicatorButtonDropDown, &toolOpt, p, w); } + + if (customDropDownArrow || drawMenuIndicator) { + QRenderRule arrowRule = renderRule(w, opt, PseudoElement_ToolButtonMenuArrow); + QRect arrowRect = arrowRule.hasGeometry() + ? positionRect(w, arrowRule, PseudoElement_ToolButtonMenuArrow, menuButtonRect, toolOpt.direction) + : arrowRule.contentsRect(menuButtonRect); + if (arrowRule.hasDrawable()) { + arrowRule.drawRule(p, arrowRect); + } else { + toolOpt.rect = arrowRect; + baseStyle()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &toolOpt, p, w); + } + } } - } + } else if (drawMenuIndicator) { + QRenderRule subRule = renderRule(w, opt, PseudoElement_ToolButtonMenuIndicator); - if (customArrow) { - QRenderRule subRule2 = customDropDown ? renderRule(w, opt, PseudoElement_ToolButtonMenuArrow) - : renderRule(w, opt, PseudoElement_ToolButtonDownArrow); - QRect r2 = customDropDown - ? positionRect(w, subRule, subRule2, PseudoElement_ToolButtonMenuArrow, r, opt->direction) - : positionRect(w, rule, subRule2, PseudoElement_ToolButtonDownArrow, opt->rect, opt->direction); - if (subRule2.hasDrawable()) { - subRule2.drawRule(p, r2); + // content padding does not impact the indicator, so use the original rect to + // calculate position of the sub element within the toplevel rule + QRect r = positionRect(w, rule, subRule, PseudoElement_ToolButtonMenuIndicator, opt->rect, toolOpt.direction); + if (subRule.hasDrawable()) { + subRule.drawRule(p, r); } else { - toolOpt.rect = r2; + toolOpt.rect = r; baseStyle()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &toolOpt, p, w); } } + toolOpt.rect = cr; + // If we don't have a custom arrow, then the arrow will have been rendered + // already by the base style when drawing the label. + if (customArrow) { + const auto arrowElement = customArrowElement(); + QRenderRule subRule = renderRule(w, opt, arrowElement); + QRect arrowRect = subRule.hasGeometry() ? positionRect(w, subRule, arrowElement, toolOpt.rect, toolOpt.direction) + : subRule.contentsRect(toolOpt.rect); + + switch (toolOpt.toolButtonStyle) { + case Qt::ToolButtonIconOnly: + break; + case Qt::ToolButtonTextOnly: + case Qt::ToolButtonTextBesideIcon: + case Qt::ToolButtonTextUnderIcon: { + // The base style needs to lay out the contents and will render the styled + // arrow icons, unless the geometry is defined in the style sheet. + toolOpt.text = tool->text; + if (!subRule.hasGeometry()) + toolOpt.features |= QStyleOptionToolButton::Arrow; + drawControl(CE_ToolButtonLabel, &toolOpt, p, w); + if (!subRule.hasGeometry()) + return; + break; + } + case Qt::ToolButtonFollowStyle: + // QToolButton handles this, so must never happen + Q_ASSERT(false); + break; + } + subRule.drawRule(p, arrowRect); + } return; } break; @@ -3383,14 +3501,14 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC if (hasStyleRule(w, PseudoElement_MdiCloseButton) || hasStyleRule(w, PseudoElement_MdiNormalButton) || hasStyleRule(w, PseudoElement_MdiMinButton)) { - QList<QVariant> layout = rule.styleHint(QLatin1String("button-layout")).toList(); + QList<QVariant> layout = rule.styleHint("button-layout"_L1).toList(); if (layout.isEmpty()) - layout = subControlLayout(QLatin1String("mNX")); + layout = subControlLayout("mNX"); QStyleOptionComplex optCopy(*opt); optCopy.subControls = { }; - for (int i = 0; i < layout.count(); i++) { - int layoutButton = layout[i].toInt(); + for (const QVariant &val : std::as_const(layout)) { + int layoutButton = val.toInt(); if (layoutButton < PseudoElement_MdiCloseButton || layoutButton > PseudoElement_MdiNormalButton) continue; @@ -3421,6 +3539,7 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC break; subRule.drawRule(p, opt->rect); QHash<QStyle::SubControl, QRect> layout = titleBarLayout(w, tb); + const auto paintDeviceDpr = p->device()->devicePixelRatio(); QRect ir; ir = layout[SC_TitleBarLabel]; @@ -3431,8 +3550,6 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC p->drawText(ir.x(), ir.y(), ir.width(), ir.height(), Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb->text); } - QPixmap pm; - ir = layout[SC_TitleBarSysMenu]; if (ir.isValid()) { QRenderRule subSubRule = renderRule(w, opt, PseudoElement_TitleBarSysMenu); @@ -3442,7 +3559,9 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC tb->icon.paint(p, ir); } else { int iconSize = pixelMetric(PM_SmallIconSize, tb, w); - pm = standardIcon(SP_TitleBarMenuButton, nullptr, w).pixmap(iconSize, iconSize); + const QSize sz(iconSize, iconSize); + const auto pm = standardIcon(SP_TitleBarMenuButton, nullptr, w) + .pixmap(sz, paintDeviceDpr); drawItemPixmap(p, ir, Qt::AlignCenter, pm); } } @@ -3452,15 +3571,14 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC QRenderRule subSubRule = renderRule(w, opt, PseudoElement_TitleBarCloseButton); subSubRule.drawRule(p, ir); - QSize sz = subSubRule.contentsRect(ir).size(); - if ((tb->titleBarFlags & Qt::WindowType_Mask) == Qt::Tool) - pm = standardIcon(SP_DockWidgetCloseButton, nullptr, w).pixmap(sz); - else - pm = standardIcon(SP_TitleBarCloseButton, nullptr, w).pixmap(sz); + const QSize sz = subSubRule.contentsRect(ir).size(); + const auto type = ((tb->titleBarFlags & Qt::WindowType_Mask) == Qt::Tool) + ? SP_DockWidgetCloseButton : SP_TitleBarCloseButton; + const auto pm = standardIcon(type, nullptr, w).pixmap(sz, paintDeviceDpr); drawItemPixmap(p, ir, Qt::AlignCenter, pm); } - int pes[] = { + constexpr std::array<int, 6> pes = { PseudoElement_TitleBarMaxButton, PseudoElement_TitleBarMinButton, PseudoElement_TitleBarNormalButton, @@ -3469,15 +3587,15 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC PseudoElement_TitleBarContextHelpButton }; - for (unsigned int i = 0; i < sizeof(pes)/sizeof(int); i++) { - int pe = pes[i]; + for (int pe : pes) { QStyle::SubControl sc = knownPseudoElements[pe].subControl; ir = layout[sc]; if (!ir.isValid()) continue; QRenderRule subSubRule = renderRule(w, opt, pe); subSubRule.drawRule(p, ir); - pm = standardIcon(subControlIcon(pe), nullptr, w).pixmap(subSubRule.contentsRect(ir).size()); + const QSize sz = subSubRule.contentsRect(ir).size(); + const auto pm = standardIcon(subControlIcon(pe), nullptr, w).pixmap(sz, paintDeviceDpr); drawItemPixmap(p, ir, Qt::AlignCenter, pm); } @@ -3493,6 +3611,37 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC baseStyle()->drawComplexControl(cc, opt, p, w); } +void QStyleSheetStyle::renderMenuItemIcon(const QStyleOptionMenuItem *mi, QPainter *p, const QWidget *w, + const QRect &rect, QRenderRule &subRule) const +{ + const QIcon::Mode mode = mi->state & QStyle::State_Enabled + ? (mi->state & QStyle::State_Selected ? QIcon::Active : QIcon::Normal) + : QIcon::Disabled; + const bool checked = mi->checkType != QStyleOptionMenuItem::NotCheckable && mi->checked; + const auto iconSize = pixelMetric(PM_SmallIconSize, mi, w); + const QSize sz(iconSize, iconSize); + const QPixmap pixmap(mi->icon.pixmap(sz, p->device()->devicePixelRatio(), mode, + checked ? QIcon::On : QIcon::Off)); + const int pixw = pixmap.width() / pixmap.devicePixelRatio(); + const int pixh = pixmap.height() / pixmap.devicePixelRatio(); + QRenderRule iconRule = renderRule(w, mi, PseudoElement_MenuIcon); + if (!iconRule.hasGeometry()) { + iconRule.geo = new QStyleSheetGeometryData(pixw, pixh, pixw, pixh, -1, -1); + } else { + iconRule.geo->width = pixw; + iconRule.geo->height = pixh; + } + QRect iconRect = positionRect(w, subRule, iconRule, PseudoElement_MenuIcon, rect, mi->direction); + if (mi->direction == Qt::LeftToRight) + iconRect.moveLeft(iconRect.left()); + else + iconRect.moveRight(iconRect.right()); + iconRule.drawRule(p, iconRect); + QRect pmr(0, 0, pixw, pixh); + pmr.moveCenter(iconRect.center()); + p->drawPixmap(pmr.topLeft(), pixmap); +} + void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter *p, const QWidget *w) const { @@ -3606,7 +3755,8 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q if (button->state & State_On) state = QIcon::On; - QPixmap pixmap = icon.pixmap(button->iconSize, mode, state); + const auto paintDeviceDpr = p->device()->devicePixelRatio(); + QPixmap pixmap = icon.pixmap(button->iconSize, paintDeviceDpr, mode, state); int pixmapWidth = pixmap.width() / pixmap.devicePixelRatio(); int pixmapHeight = pixmap.height() / pixmap.devicePixelRatio(); int labelWidth = pixmapWidth; @@ -3773,42 +3923,18 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q } mi.palette.setBrush(QPalette::HighlightedText, mi.palette.brush(QPalette::ButtonText)); - bool checkable = mi.checkType != QStyleOptionMenuItem::NotCheckable; - bool checked = checkable ? mi.checked : false; - - bool dis = !(opt->state & QStyle::State_Enabled), - act = opt->state & QStyle::State_Selected; - int textRectOffset = m->maxIconWidth; if (!mi.icon.isNull()) { - QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal; - if (act && !dis) - mode = QIcon::Active; - const QPixmap pixmap(mi.icon.pixmap(pixelMetric(PM_SmallIconSize), mode, checked ? QIcon::On : QIcon::Off)); - const int pixw = pixmap.width() / pixmap.devicePixelRatio(); - const int pixh = pixmap.height() / pixmap.devicePixelRatio(); - QRenderRule iconRule = renderRule(w, opt, PseudoElement_MenuIcon); - if (!iconRule.hasGeometry()) { - iconRule.geo = new QStyleSheetGeometryData(pixw, pixh, pixw, pixh, -1, -1); - } else { - iconRule.geo->width = pixw; - iconRule.geo->height = pixh; - } - QRect iconRect = positionRect(w, subRule, iconRule, PseudoElement_MenuIcon, opt->rect, opt->direction); - if (opt->direction == Qt::LeftToRight) - iconRect.moveLeft(iconRect.left()); - else - iconRect.moveRight(iconRect.right()); - iconRule.drawRule(p, iconRect); - QRect pmr(0, 0, pixw, pixh); - pmr.moveCenter(iconRect.center()); - p->drawPixmap(pmr.topLeft(), pixmap); + renderMenuItemIcon(&mi, p, w, opt->rect, subRule); } else if (mi.menuHasCheckableItems) { - QRenderRule subSubRule = renderRule(w, opt, PseudoElement_MenuCheckMark); + const bool checkable = mi.checkType != QStyleOptionMenuItem::NotCheckable; + const bool checked = checkable ? mi.checked : false; + + const QRenderRule subSubRule = renderRule(w, opt, PseudoElement_MenuCheckMark); const QRect cmRect = positionRect(w, subRule, subSubRule, PseudoElement_MenuCheckMark, opt->rect, opt->direction); if (checkable && (subSubRule.hasDrawable() || checked)) { QStyleOptionMenuItem newMi = mi; - if (!dis) + if (opt->state & QStyle::State_Enabled) newMi.state |= State_Enabled; if (mi.checked) newMi.state |= State_On; @@ -3829,7 +3955,7 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q int text_flags = Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine; if (!styleHint(SH_UnderlineShortcut, &mi, w)) text_flags |= Qt::TextHideMnemonic; - int t = s.indexOf(QLatin1Char('\t')); + qsizetype t = s.indexOf(u'\t'); if (t >= 0) { QRect vShortcutRect = visualRect(opt->direction, mi.rect, QRect(textRect.topRight(), QPoint(mi.rect.right(), textRect.bottom()))); @@ -3845,11 +3971,22 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q mi.rect = positionRect(w, subRule, subRule2, PseudoElement_MenuRightArrow, opt->rect, mi.direction); drawPrimitive(arrow, &mi, p, w); } + } else if (!mi.icon.isNull() && hasStyleRule(w, PseudoElement_MenuIcon)) { + // we wouldn't be here if the item itself would be styled, so now we only want + // the text from the default style, and then draw the icon ourselves. + QStyleOptionMenuItem newMi = mi; + newMi.icon = {}; + newMi.checkType = QStyleOptionMenuItem::NotCheckable; + if (rule.baseStyleCanDraw() && subRule.baseStyleCanDraw()) + baseStyle()->drawControl(ce, &newMi, p, w); + else + ParentStyle::drawControl(ce, &newMi, p, w); + renderMenuItemIcon(&mi, p, w, opt->rect, subRule); } else if (hasStyleRule(w, PseudoElement_MenuCheckMark) || hasStyleRule(w, PseudoElement_MenuRightArrow)) { QWindowsStyle::drawControl(ce, &mi, p, w); if (mi.checkType != QStyleOptionMenuItem::NotCheckable && !mi.checked) { // We have a style defined, but QWindowsStyle won't draw anything if not checked. - // So we mimick what QWindowsStyle would do. + // So we mimic what QWindowsStyle would do. int checkcol = qMax<int>(mi.maxIconWidth, QWindowsStylePrivate::windowsCheckMarkWidth); QRect vCheckRect = visualRect(opt->direction, mi.rect, QRect(mi.rect.x(), mi.rect.y(), checkcol, mi.rect.height())); if (mi.state.testFlag(State_Enabled) && mi.state.testFlag(State_Selected)) { @@ -3921,7 +4058,8 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q if (spacing == -1) spacing = 6; QIcon::Mode mode = cb->state & State_Enabled ? QIcon::Normal : QIcon::Disabled; - QPixmap pixmap = cb->currentIcon.pixmap(cb->iconSize, mode); + const auto paintDeviceDpr = p->device()->devicePixelRatio(); + QPixmap pixmap = cb->currentIcon.pixmap(cb->iconSize, paintDeviceDpr, mode); QRect iconRect(editRect); iconRect.setWidth(cb->iconSize.width()); iconRect = alignedRect(cb->direction, @@ -3982,8 +4120,23 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q case CE_HeaderLabel: if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { - QStyleOptionHeader hdr(*header); + QStyleOptionHeaderV2 hdr; + QStyleOptionHeader &v1Copy = hdr; + if (auto v2 = qstyleoption_cast<const QStyleOptionHeaderV2 *>(opt)) + hdr = *v2; + else + v1Copy = *header; QRenderRule subRule = renderRule(w, opt, PseudoElement_HeaderViewSection); + if (hasStyleRule(w, PseudoElement_HeaderViewUpArrow) + || hasStyleRule(w, PseudoElement_HeaderViewDownArrow)) { + if (hdr.sortIndicator != QStyleOptionHeader::None) { + const QRect arrowRect = subElementRect(SE_HeaderArrow, opt, w); + if (hdr.orientation == Qt::Horizontal) + hdr.rect.setWidth(hdr.rect.width() - arrowRect.width()); + else + hdr.rect.setHeight(hdr.rect.height() - arrowRect.height()); + } + } subRule.configurePalette(&hdr.palette, QPalette::ButtonText, QPalette::Button); if (subRule.hasFont) { QFont oldFont = p->font(); @@ -4082,6 +4235,7 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q --chunkCount; }; } else if (chunkWidth > 0) { + const auto ceil = [](qreal x) { return int(x) + (x > 0 && x != int(x)); }; const int chunkCount = ceil(qreal(fillWidth)/chunkWidth); int x = reverse ? r.left() + r.width() - chunkWidth : r.x(); @@ -4122,12 +4276,11 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q if (rule.hasDrawable()) { rule.drawFrame(p, opt->rect); p->save(); - switch (sgOpt->corner) { - case Qt::BottomRightCorner: break; - case Qt::BottomLeftCorner: p->rotate(90); break; - case Qt::TopLeftCorner: p->rotate(180); break; - case Qt::TopRightCorner: p->rotate(270); break; - default: break; + static constexpr int rotation[] = { 180, 270, 90, 0 }; + if (rotation[sgOpt->corner]) { + p->translate(opt->rect.center()); + p->rotate(rotation[sgOpt->corner]); + p->translate(-opt->rect.center()); } rule.drawImage(p, opt->rect); p->restore(); @@ -4211,15 +4364,47 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q case CE_ItemViewItem: if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) { QRenderRule subRule = renderRule(w, opt, PseudoElement_ViewItem); - if (subRule.hasDrawable() || hasStyleRule(w, PseudoElement_Indicator)) { - QStyleOptionViewItem optCopy(*vopt); + QStyleOptionViewItem optCopy(*vopt); + if (subRule.hasDrawable()) { subRule.configurePalette(&optCopy.palette, vopt->state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text, vopt->state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base); QWindowsStyle::drawControl(ce, &optCopy, p, w); } else { - QStyleOptionViewItem voptCopy(*vopt); - subRule.configurePalette(&voptCopy.palette, QPalette::Text, QPalette::NoRole); - baseStyle()->drawControl(ce, &voptCopy, p, w); + p->save(); + if (hasStyleRule(w, PseudoElement_Indicator)) { + // there is a rule for the indicator, but no rule for the item itself (otherwise + // the previous path would have been taken); only draw the indicator using the + // rule (via QWindows/QCommonStyle), then let the base style handle the rest. + QStyleOptionViewItem optIndicator(*vopt); + subRule.configurePalette(&optIndicator.palette, + vopt->state & QStyle::State_Selected + ? QPalette::HighlightedText + : QPalette::Text, + vopt->state & QStyle::State_Selected + ? QPalette::Highlight + : QPalette::Base); + // only draw the indicator; no text, icon or background + optIndicator.backgroundBrush = Qt::NoBrush; // no background + optIndicator.text.clear(); + optIndicator.icon = QIcon(); + QWindowsStyle::drawControl(ce, &optIndicator, p, w); + + // Now draw text, background,icon, and highlight, but not the indicator with + // the base style. Since we can't turn off HasCheckIndicator to prevent the base + // style from drawing the check indicator again (it would change how the item + // gets laid out) we have to clip the indicator that's already been painted. + const QRect crStyle = subElementRect(QStyle::SE_ItemViewItemCheckIndicator, + &optIndicator, w); + const QRect crBase = baseStyle()->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, + &optIndicator, w); + const QRegion clipRegion = QRegion(p->hasClipping() ? p->clipRegion() + : QRegion(optIndicator.rect)) + - crStyle.united(crBase); + p->setClipRegion(clipRegion); + } + subRule.configurePalette(&optCopy.palette, QPalette::Text, QPalette::NoRole); + baseStyle()->drawControl(ce, &optCopy, p, w); + p->restore(); } return; } @@ -4237,6 +4422,7 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q case CE_TabBarTabLabel: case CE_TabBarTabShape: if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(opt)) { + const auto foregroundRole = w ? w->foregroundRole() : QPalette::WindowText; QRenderRule subRule = renderRule(w, opt, PseudoElement_TabBarTab); QRect r = positionRect(w, subRule, PseudoElement_TabBarTab, opt->rect, opt->direction); if (ce == CE_TabBarTabShape && subRule.hasDrawable() && tab->shape < QTabBar::TriangularNorth) { @@ -4244,7 +4430,7 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q return; } QStyleOptionTab tabCopy(*tab); - subRule.configurePalette(&tabCopy.palette, QPalette::WindowText, QPalette::Base); + subRule.configurePalette(&tabCopy.palette, foregroundRole, QPalette::Base); QFont oldFont = p->font(); if (subRule.hasFont) p->setFont(subRule.font.resolve(p->font())); @@ -4302,7 +4488,7 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q QString titleText = p->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, r.width()); drawItemText(p, r, - alignment, dwOpt->palette, + alignment | Qt::TextHideMnemonic, dwOpt->palette, dwOpt->state & State_Enabled, titleText, QPalette::WindowText); @@ -4464,14 +4650,16 @@ void QStyleSheetStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *op case PE_PanelLineEdit: if (const QStyleOptionFrame *frm = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { -#if QT_CONFIG(spinbox) - if (w && qobject_cast<const QAbstractSpinBox *>(w->parentWidget())) { - QRenderRule spinboxRule = renderRule(w->parentWidget(), opt); - if (!spinboxRule.hasNativeBorder() || !spinboxRule.baseStyleCanDraw()) - return; - rule = spinboxRule; + // Fall back to container widget's render rule + if (w) { + if (QWidget *container = containerWidget(w); container != w) { + QRenderRule containerRule = renderRule(container, opt); + if (!containerRule.hasNativeBorder() || !containerRule.baseStyleCanDraw()) + return; + rule = containerRule; + } } -#endif + if (rule.hasNativeBorder()) { QStyleOptionFrame frmOpt(*frm); rule.configurePalette(&frmOpt.palette, QPalette::Text, QPalette::Base); @@ -4580,10 +4768,7 @@ void QStyleSheetStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *op if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(opt)) { QRenderRule subRule = renderRule(w, opt, PseudoElement_TreeViewBranch); if (subRule.hasDrawable()) { - if ((vopt->state & QStyle::State_Selected) && vopt->showDecorationSelected) - p->fillRect(vopt->rect, vopt->palette.highlight()); - else if (vopt->features & QStyleOptionViewItem::Alternate) - p->fillRect(vopt->rect, vopt->palette.alternateBase()); + proxy()->drawPrimitive(PE_PanelItemViewRow, vopt, p, w); subRule.drawRule(p, opt->rect); } else { baseStyle()->drawPrimitive(pe, vopt, p, w); @@ -4651,6 +4836,17 @@ void QStyleSheetStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *op pseudoElement = PseudoElement_DockWidgetSeparator; break; + case PE_PanelItemViewRow: + // For compatibility reasons, QTreeView draws different parts of + // the background of an item row separately, before calling the + // delegate to draw the item. The row background of an item is + // however not separately styleable through a style sheet, but + // only indirectly through the background of the item. To get the + // same background for all parts drawn by QTreeView, we have to + // use the background rule for the item here. + if (renderRule(w, opt, PseudoElement_ViewItem).hasBackground()) + pseudoElement = PseudoElement_ViewItem; + break; case PE_PanelItemViewItem: pseudoElement = PseudoElement_ViewItem; break; @@ -4677,6 +4873,7 @@ void QStyleSheetStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *op w = w->parentWidget(); //match on the QTabBar instead of the CloseButton } pseudoElement = PseudoElement_TabBarTabCloseButton; + break; #endif default: @@ -4778,8 +4975,19 @@ int QStyleSheetStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const case PM_MenuButtonIndicator: #if QT_CONFIG(toolbutton) // QToolButton adds this directly to the width - if (qobject_cast<const QToolButton *>(w) && (rule.hasBox() || !rule.hasNativeBorder())) - return 0; + if (qobject_cast<const QToolButton *>(w)) { + if (rule.hasBox() || !rule.hasNativeBorder()) + return 0; + if (const auto *tbOpt = qstyleoption_cast<const QStyleOptionToolButton*>(opt)) { + if (tbOpt->features & QStyleOptionToolButton::MenuButtonPopup) + subRule = renderRule(w, opt, PseudoElement_ToolButtonMenu); + else + subRule = renderRule(w, opt, PseudoElement_ToolButtonMenuIndicator); + if (subRule.hasContentsSize()) + return subRule.size().width(); + } + break; + } #endif subRule = renderRule(w, opt, PseudoElement_PushButtonMenuIndicator); if (subRule.hasContentsSize()) @@ -4924,8 +5132,14 @@ int QStyleSheetStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const if (!rule.hasNativeBorder() || rule.hasBox()) return 0; break; + + case PM_ScrollView_ScrollBarOverlap: + if (!proxy()->styleHint(SH_ScrollBar_Transient, opt, w)) + return 0; + break; #endif // QT_CONFIG(scrollbar) + case PM_ProgressBarChunkWidth: subRule = renderRule(w, opt, PseudoElement_ProgressBarChunk); if (subRule.hasContentsSize()) { @@ -4998,9 +5212,8 @@ int QStyleSheetStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const case PM_MessageBoxIconSize: case PM_ButtonIconSize: case PM_SmallIconSize: - if (rule.hasStyleHint(QLatin1String("icon-size"))) { - return rule.styleHint(QLatin1String("icon-size")).toSize().width(); - } + if (rule.hasStyleHint("icon-size"_L1)) + return rule.styleHint("icon-size"_L1).toSize().width(); break; case PM_DockWidgetTitleMargin: { @@ -5132,8 +5345,17 @@ QSize QStyleSheetStyle::sizeFromContents(ContentsType ct, const QStyleOption *op } return subRule.size(sz); } - return subRule.baseStyleCanDraw() ? baseStyle()->sizeFromContents(ct, opt, sz, w) - : QWindowsStyle::sizeFromContents(ct, opt, sz, w); + sz = subRule.baseStyleCanDraw() ? baseStyle()->sizeFromContents(ct, opt, sz, w) + : QWindowsStyle::sizeFromContents(ct, opt, sz, w); + if (hasStyleRule(w, PseudoElement_HeaderViewDownArrow) + || hasStyleRule(w, PseudoElement_HeaderViewUpArrow)) { + const QRect arrowRect = subElementRect(SE_HeaderArrow, opt, w); + if (hdr->orientation == Qt::Horizontal) + sz.rwidth() += arrowRect.width(); + else + sz.rheight() += arrowRect.height(); + } + return sz; } } break; @@ -5184,20 +5406,21 @@ QSize QStyleSheetStyle::sizeFromContents(ContentsType ct, const QStyleOption *op } if ((pe == PseudoElement_Item) && (subRule.hasBox() || subRule.hasBorder() || subRule.hasFont)) { QSize sz(csz); - if (mi->text.contains(QLatin1Char('\t'))) + if (mi->text.contains(u'\t')) sz.rwidth() += 12; //as in QCommonStyle - bool checkable = mi->checkType != QStyleOptionMenuItem::NotCheckable; if (!mi->icon.isNull()) { const int pmSmall = pixelMetric(PM_SmallIconSize); const QSize pmSize = mi->icon.actualSize(QSize(pmSmall, pmSmall)); - sz.rwidth() += pmSize.width() + 4; - } else if (checkable) { + sz.rwidth() += std::max(mi->maxIconWidth, pmSize.width()) + 4; + } else if (mi->menuHasCheckableItems) { QRenderRule subSubRule = renderRule(w, opt, PseudoElement_MenuCheckMark); QRect checkmarkRect = positionRect(w, subRule, subSubRule, PseudoElement_MenuCheckMark, opt->rect, opt->direction); sz.rwidth() += std::max(mi->maxIconWidth, checkmarkRect.width()) + 4; + } else { + sz.rwidth() += mi->maxIconWidth; } if (subRule.hasFont) { - QFontMetrics fm(subRule.font); + QFontMetrics fm(subRule.font.resolve(mi->font)); const QRect r = fm.boundingRect(QRect(), Qt::TextSingleLine | Qt::TextShowMnemonic, mi->text); sz = sz.expandedTo(r.size()); } @@ -5243,13 +5466,25 @@ QSize QStyleSheetStyle::sizeFromContents(ContentsType ct, const QStyleOption *op if (subRule.hasBox() || !subRule.hasNativeBorder()) sz = csz + QSize(vertical ? 0 : spaceForIcon, vertical ? spaceForIcon : 0); if (subRule.hasFont) { - QFont ruleFont = subRule.font.resolve(w->font()); - QFontMetrics fm(ruleFont); - const QSize textSize = fm.size(Qt::TextShowMnemonic, text) - + QSize(pixelMetric(PM_TabBarTabHSpace, opt, w), - pixelMetric(PM_TabBarTabVSpace, opt, w)); - sz = sz.expandedTo(vertical ? textSize.transposed() : textSize); + // first we remove the space needed for the text using the default font + const QSize oldTextSize = opt->fontMetrics.size(Qt::TextShowMnemonic, text); + (vertical ? sz.rheight() : sz.rwidth()) -= oldTextSize.width(); + + // then we add the space needed when using the rule font to the relevant + // dimension, and constraint the other dimension to the maximum to make + // sure we don't grow, but also don't clip icons or buttons. + const QFont ruleFont = subRule.font.resolve(w->font()); + const QFontMetrics fm(ruleFont); + const QSize textSize = fm.size(Qt::TextShowMnemonic, text); + if (vertical) { + sz.rheight() += textSize.width(); + sz.rwidth() = qMax(textSize.height(), sz.width()); + } else { + sz.rwidth() += textSize.width(); + sz.rheight() = qMax(textSize.height(), sz.height()); + } } + return subRule.boxSize(subRule.adjustSize(sz)); } sz = subRule.adjustSize(csz); @@ -5264,13 +5499,13 @@ QSize QStyleSheetStyle::sizeFromContents(ContentsType ct, const QStyleOption *op && !hasStyleRule(w, PseudoElement_MdiMinButton)) break; - QList<QVariant> layout = rule.styleHint(QLatin1String("button-layout")).toList(); + QList<QVariant> layout = rule.styleHint("button-layout"_L1).toList(); if (layout.isEmpty()) - layout = subControlLayout(QLatin1String("mNX")); + layout = subControlLayout("mNX"); int width = 0, height = 0; - for (int i = 0; i < layout.count(); i++) { - int layoutButton = layout[i].toInt(); + for (const QVariant &val : std::as_const(layout)) { + int layoutButton = val.toInt(); if (layoutButton < PseudoElement_MdiCloseButton || layoutButton > PseudoElement_MdiNormalButton) continue; @@ -5308,65 +5543,65 @@ QSize QStyleSheetStyle::sizeFromContents(ContentsType ct, const QStyleOption *op /*! \internal */ -static QLatin1String propertyNameForStandardPixmap(QStyle::StandardPixmap sp) +static QLatin1StringView propertyNameForStandardPixmap(QStyle::StandardPixmap sp) { switch (sp) { - case QStyle::SP_TitleBarMenuButton: return QLatin1String("titlebar-menu-icon"); - case QStyle::SP_TitleBarMinButton: return QLatin1String("titlebar-minimize-icon"); - case QStyle::SP_TitleBarMaxButton: return QLatin1String("titlebar-maximize-icon"); - case QStyle::SP_TitleBarCloseButton: return QLatin1String("titlebar-close-icon"); - case QStyle::SP_TitleBarNormalButton: return QLatin1String("titlebar-normal-icon"); - case QStyle::SP_TitleBarShadeButton: return QLatin1String("titlebar-shade-icon"); - case QStyle::SP_TitleBarUnshadeButton: return QLatin1String("titlebar-unshade-icon"); - case QStyle::SP_TitleBarContextHelpButton: return QLatin1String("titlebar-contexthelp-icon"); - case QStyle::SP_DockWidgetCloseButton: return QLatin1String("dockwidget-close-icon"); - case QStyle::SP_MessageBoxInformation: return QLatin1String("messagebox-information-icon"); - case QStyle::SP_MessageBoxWarning: return QLatin1String("messagebox-warning-icon"); - case QStyle::SP_MessageBoxCritical: return QLatin1String("messagebox-critical-icon"); - case QStyle::SP_MessageBoxQuestion: return QLatin1String("messagebox-question-icon"); - case QStyle::SP_DesktopIcon: return QLatin1String("desktop-icon"); - case QStyle::SP_TrashIcon: return QLatin1String("trash-icon"); - case QStyle::SP_ComputerIcon: return QLatin1String("computer-icon"); - case QStyle::SP_DriveFDIcon: return QLatin1String("floppy-icon"); - case QStyle::SP_DriveHDIcon: return QLatin1String("harddisk-icon"); - case QStyle::SP_DriveCDIcon: return QLatin1String("cd-icon"); - case QStyle::SP_DriveDVDIcon: return QLatin1String("dvd-icon"); - case QStyle::SP_DriveNetIcon: return QLatin1String("network-icon"); - case QStyle::SP_DirOpenIcon: return QLatin1String("directory-open-icon"); - case QStyle::SP_DirClosedIcon: return QLatin1String("directory-closed-icon"); - case QStyle::SP_DirLinkIcon: return QLatin1String("directory-link-icon"); - case QStyle::SP_FileIcon: return QLatin1String("file-icon"); - case QStyle::SP_FileLinkIcon: return QLatin1String("file-link-icon"); - case QStyle::SP_FileDialogStart: return QLatin1String("filedialog-start-icon"); - case QStyle::SP_FileDialogEnd: return QLatin1String("filedialog-end-icon"); - case QStyle::SP_FileDialogToParent: return QLatin1String("filedialog-parent-directory-icon"); - case QStyle::SP_FileDialogNewFolder: return QLatin1String("filedialog-new-directory-icon"); - case QStyle::SP_FileDialogDetailedView: return QLatin1String("filedialog-detailedview-icon"); - case QStyle::SP_FileDialogInfoView: return QLatin1String("filedialog-infoview-icon"); - case QStyle::SP_FileDialogContentsView: return QLatin1String("filedialog-contentsview-icon"); - case QStyle::SP_FileDialogListView: return QLatin1String("filedialog-listview-icon"); - case QStyle::SP_FileDialogBack: return QLatin1String("filedialog-backward-icon"); - case QStyle::SP_DirIcon: return QLatin1String("directory-icon"); - case QStyle::SP_DialogOkButton: return QLatin1String("dialog-ok-icon"); - case QStyle::SP_DialogCancelButton: return QLatin1String("dialog-cancel-icon"); - case QStyle::SP_DialogHelpButton: return QLatin1String("dialog-help-icon"); - case QStyle::SP_DialogOpenButton: return QLatin1String("dialog-open-icon"); - case QStyle::SP_DialogSaveButton: return QLatin1String("dialog-save-icon"); - case QStyle::SP_DialogCloseButton: return QLatin1String("dialog-close-icon"); - case QStyle::SP_DialogApplyButton: return QLatin1String("dialog-apply-icon"); - case QStyle::SP_DialogResetButton: return QLatin1String("dialog-reset-icon"); - case QStyle::SP_DialogDiscardButton: return QLatin1String("dialog-discard-icon"); - case QStyle::SP_DialogYesButton: return QLatin1String("dialog-yes-icon"); - case QStyle::SP_DialogNoButton: return QLatin1String("dialog-no-icon"); - case QStyle::SP_ArrowUp: return QLatin1String("uparrow-icon"); - case QStyle::SP_ArrowDown: return QLatin1String("downarrow-icon"); - case QStyle::SP_ArrowLeft: return QLatin1String("leftarrow-icon"); - case QStyle::SP_ArrowRight: return QLatin1String("rightarrow-icon"); - case QStyle::SP_ArrowBack: return QLatin1String("backward-icon"); - case QStyle::SP_ArrowForward: return QLatin1String("forward-icon"); - case QStyle::SP_DirHomeIcon: return QLatin1String("home-icon"); - case QStyle::SP_LineEditClearButton: return QLatin1String("lineedit-clear-button-icon"); - default: return QLatin1String(""); + case QStyle::SP_TitleBarMenuButton: return "titlebar-menu-icon"_L1; + case QStyle::SP_TitleBarMinButton: return "titlebar-minimize-icon"_L1; + case QStyle::SP_TitleBarMaxButton: return "titlebar-maximize-icon"_L1; + case QStyle::SP_TitleBarCloseButton: return "titlebar-close-icon"_L1; + case QStyle::SP_TitleBarNormalButton: return "titlebar-normal-icon"_L1; + case QStyle::SP_TitleBarShadeButton: return "titlebar-shade-icon"_L1; + case QStyle::SP_TitleBarUnshadeButton: return "titlebar-unshade-icon"_L1; + case QStyle::SP_TitleBarContextHelpButton: return "titlebar-contexthelp-icon"_L1; + case QStyle::SP_DockWidgetCloseButton: return "dockwidget-close-icon"_L1; + case QStyle::SP_MessageBoxInformation: return "messagebox-information-icon"_L1; + case QStyle::SP_MessageBoxWarning: return "messagebox-warning-icon"_L1; + case QStyle::SP_MessageBoxCritical: return "messagebox-critical-icon"_L1; + case QStyle::SP_MessageBoxQuestion: return "messagebox-question-icon"_L1; + case QStyle::SP_DesktopIcon: return "desktop-icon"_L1; + case QStyle::SP_TrashIcon: return "trash-icon"_L1; + case QStyle::SP_ComputerIcon: return "computer-icon"_L1; + case QStyle::SP_DriveFDIcon: return "floppy-icon"_L1; + case QStyle::SP_DriveHDIcon: return "harddisk-icon"_L1; + case QStyle::SP_DriveCDIcon: return "cd-icon"_L1; + case QStyle::SP_DriveDVDIcon: return "dvd-icon"_L1; + case QStyle::SP_DriveNetIcon: return "network-icon"_L1; + case QStyle::SP_DirOpenIcon: return "directory-open-icon"_L1; + case QStyle::SP_DirClosedIcon: return "directory-closed-icon"_L1; + case QStyle::SP_DirLinkIcon: return "directory-link-icon"_L1; + case QStyle::SP_FileIcon: return "file-icon"_L1; + case QStyle::SP_FileLinkIcon: return "file-link-icon"_L1; + case QStyle::SP_FileDialogStart: return "filedialog-start-icon"_L1; + case QStyle::SP_FileDialogEnd: return "filedialog-end-icon"_L1; + case QStyle::SP_FileDialogToParent: return "filedialog-parent-directory-icon"_L1; + case QStyle::SP_FileDialogNewFolder: return "filedialog-new-directory-icon"_L1; + case QStyle::SP_FileDialogDetailedView: return "filedialog-detailedview-icon"_L1; + case QStyle::SP_FileDialogInfoView: return "filedialog-infoview-icon"_L1; + case QStyle::SP_FileDialogContentsView: return "filedialog-contentsview-icon"_L1; + case QStyle::SP_FileDialogListView: return "filedialog-listview-icon"_L1; + case QStyle::SP_FileDialogBack: return "filedialog-backward-icon"_L1; + case QStyle::SP_DirIcon: return "directory-icon"_L1; + case QStyle::SP_DialogOkButton: return "dialog-ok-icon"_L1; + case QStyle::SP_DialogCancelButton: return "dialog-cancel-icon"_L1; + case QStyle::SP_DialogHelpButton: return "dialog-help-icon"_L1; + case QStyle::SP_DialogOpenButton: return "dialog-open-icon"_L1; + case QStyle::SP_DialogSaveButton: return "dialog-save-icon"_L1; + case QStyle::SP_DialogCloseButton: return "dialog-close-icon"_L1; + case QStyle::SP_DialogApplyButton: return "dialog-apply-icon"_L1; + case QStyle::SP_DialogResetButton: return "dialog-reset-icon"_L1; + case QStyle::SP_DialogDiscardButton: return "dialog-discard-icon"_L1; + case QStyle::SP_DialogYesButton: return "dialog-yes-icon"_L1; + case QStyle::SP_DialogNoButton: return "dialog-no-icon"_L1; + case QStyle::SP_ArrowUp: return "uparrow-icon"_L1; + case QStyle::SP_ArrowDown: return "downarrow-icon"_L1; + case QStyle::SP_ArrowLeft: return "leftarrow-icon"_L1; + case QStyle::SP_ArrowRight: return "rightarrow-icon"_L1; + case QStyle::SP_ArrowBack: return "backward-icon"_L1; + case QStyle::SP_ArrowForward: return "forward-icon"_L1; + case QStyle::SP_DirHomeIcon: return "home-icon"_L1; + case QStyle::SP_LineEditClearButton: return "lineedit-clear-button-icon"_L1; + default: return ""_L1; } } @@ -5397,7 +5632,8 @@ QPixmap QStyleSheetStyle::standardPixmap(StandardPixmap standardPixmap, const QS QRenderRule rule = renderRule(w, opt); if (rule.hasStyleHint(s)) { QIcon icon = qvariant_cast<QIcon>(rule.styleHint(s)); - return icon.pixmap(16, 16); // ###: unhard-code this if someone complains + const auto dpr = w ? w->devicePixelRatio() : qApp->devicePixelRatio(); + return icon.pixmap(QSize(16, 16), dpr); } } return baseStyle()->standardPixmap(standardPixmap, opt, w); @@ -5422,25 +5658,25 @@ int QStyleSheetStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWi QRenderRule rule = renderRule(w, opt); QString s; switch (sh) { - case SH_LineEdit_PasswordCharacter: s = QLatin1String("lineedit-password-character"); break; - case SH_LineEdit_PasswordMaskDelay: s = QLatin1String("lineedit-password-mask-delay"); break; - case SH_DitherDisabledText: s = QLatin1String("dither-disabled-text"); break; - case SH_EtchDisabledText: s = QLatin1String("etch-disabled-text"); break; - case SH_ItemView_ActivateItemOnSingleClick: s = QLatin1String("activate-on-singleclick"); break; - case SH_ItemView_ShowDecorationSelected: s = QLatin1String("show-decoration-selected"); break; - case SH_Table_GridLineColor: s = QLatin1String("gridline-color"); break; - case SH_DialogButtonLayout: s = QLatin1String("button-layout"); break; - case SH_ToolTipLabel_Opacity: s = QLatin1String("opacity"); break; - case SH_ComboBox_Popup: s = QLatin1String("combobox-popup"); break; - case SH_ComboBox_ListMouseTracking: s = QLatin1String("combobox-list-mousetracking"); break; - case SH_MenuBar_AltKeyNavigation: s = QLatin1String("menubar-altkey-navigation"); break; - case SH_Menu_Scrollable: s = QLatin1String("menu-scrollable"); break; - case SH_DrawMenuBarSeparator: s = QLatin1String("menubar-separator"); break; - case SH_MenuBar_MouseTracking: s = QLatin1String("mouse-tracking"); break; - case SH_SpinBox_ClickAutoRepeatRate: s = QLatin1String("spinbox-click-autorepeat-rate"); break; - case SH_SpinControls_DisableOnBounds: s = QLatin1String("spincontrol-disable-on-bounds"); break; - case SH_MessageBox_TextInteractionFlags: s = QLatin1String("messagebox-text-interaction-flags"); break; - case SH_ToolButton_PopupDelay: s = QLatin1String("toolbutton-popup-delay"); break; + case SH_LineEdit_PasswordCharacter: s = "lineedit-password-character"_L1; break; + case SH_LineEdit_PasswordMaskDelay: s = "lineedit-password-mask-delay"_L1; break; + case SH_DitherDisabledText: s = "dither-disabled-text"_L1; break; + case SH_EtchDisabledText: s = "etch-disabled-text"_L1; break; + case SH_ItemView_ActivateItemOnSingleClick: s = "activate-on-singleclick"_L1; break; + case SH_ItemView_ShowDecorationSelected: s = "show-decoration-selected"_L1; break; + case SH_Table_GridLineColor: s = "gridline-color"_L1; break; + case SH_DialogButtonLayout: s = "button-layout"_L1; break; + case SH_ToolTipLabel_Opacity: s = "opacity"_L1; break; + case SH_ComboBox_Popup: s = "combobox-popup"_L1; break; + case SH_ComboBox_ListMouseTracking: s = "combobox-list-mousetracking"_L1; break; + case SH_MenuBar_AltKeyNavigation: s = "menubar-altkey-navigation"_L1; break; + case SH_Menu_Scrollable: s = "menu-scrollable"_L1; break; + case SH_DrawMenuBarSeparator: s = "menubar-separator"_L1; break; + case SH_MenuBar_MouseTracking: s = "mouse-tracking"_L1; break; + case SH_SpinBox_ClickAutoRepeatRate: s = "spinbox-click-autorepeat-rate"_L1; break; + case SH_SpinControls_DisableOnBounds: s = "spincontrol-disable-on-bounds"_L1; break; + case SH_MessageBox_TextInteractionFlags: s = "messagebox-text-interaction-flags"_L1; break; + case SH_ToolButton_PopupDelay: s = "toolbutton-popup-delay"_L1; break; case SH_ToolBox_SelectedPageTitleBold: if (renderRule(w, opt, PseudoElement_ToolBoxTab).hasFont) return 0; @@ -5449,12 +5685,12 @@ int QStyleSheetStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWi if (rule.hasPalette() && rule.palette()->foreground.style() != Qt::NoBrush) return rule.palette()->foreground.color().rgba(); break; - case SH_ScrollView_FrameOnlyAroundContents: s = QLatin1String("scrollview-frame-around-contents"); break; - case SH_ScrollBar_ContextMenu: s = QLatin1String("scrollbar-contextmenu"); break; - case SH_ScrollBar_LeftClickAbsolutePosition: s = QLatin1String("scrollbar-leftclick-absolute-position"); break; - case SH_ScrollBar_MiddleClickAbsolutePosition: s = QLatin1String("scrollbar-middleclick-absolute-position"); break; - case SH_ScrollBar_RollBetweenButtons: s = QLatin1String("scrollbar-roll-between-buttons"); break; - case SH_ScrollBar_ScrollWhenPointerLeavesControl: s = QLatin1String("scrollbar-scroll-when-pointer-leaves-control"); break; + case SH_ScrollView_FrameOnlyAroundContents: s = "scrollview-frame-around-contents"_L1; break; + case SH_ScrollBar_ContextMenu: s = "scrollbar-contextmenu"_L1; break; + case SH_ScrollBar_LeftClickAbsolutePosition: s = "scrollbar-leftclick-absolute-position"_L1; break; + case SH_ScrollBar_MiddleClickAbsolutePosition: s = "scrollbar-middleclick-absolute-position"_L1; break; + case SH_ScrollBar_RollBetweenButtons: s = "scrollbar-roll-between-buttons"_L1; break; + case SH_ScrollBar_ScrollWhenPointerLeavesControl: s = "scrollbar-scroll-when-pointer-leaves-control"_L1; break; case SH_TabBar_Alignment: #if QT_CONFIG(tabwidget) if (qobject_cast<const QTabWidget *>(w)) { @@ -5463,7 +5699,7 @@ int QStyleSheetStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWi return rule.position()->position; } #endif // QT_CONFIG(tabwidget) - s = QLatin1String("alignment"); + s = "alignment"_L1; break; #if QT_CONFIG(tabbar) case SH_TabBar_CloseButtonPosition: @@ -5477,8 +5713,8 @@ int QStyleSheetStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWi } break; #endif - case SH_TabBar_ElideMode: s = QLatin1String("tabbar-elide-mode"); break; - case SH_TabBar_PreferNoArrows: s = QLatin1String("tabbar-prefer-no-arrows"); break; + case SH_TabBar_ElideMode: s = "tabbar-elide-mode"_L1; break; + case SH_TabBar_PreferNoArrows: s = "tabbar-prefer-no-arrows"_L1; break; case SH_ComboBox_PopupFrameStyle: #if QT_CONFIG(combobox) if (qobject_cast<const QComboBox *>(w)) { @@ -5492,8 +5728,8 @@ int QStyleSheetStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWi } #endif // QT_CONFIG(combobox) break; - case SH_DialogButtonBox_ButtonsHaveIcons: s = QLatin1String("dialogbuttonbox-buttons-have-icons"); break; - case SH_Workspace_FillSpaceOnMaximize: s = QLatin1String("mdi-fill-space-on-maximize"); break; + case SH_DialogButtonBox_ButtonsHaveIcons: s = "dialogbuttonbox-buttons-have-icons"_L1; break; + case SH_Workspace_FillSpaceOnMaximize: s = "mdi-fill-space-on-maximize"_L1; break; case SH_TitleBar_NoBorder: if (rule.hasBorder()) return !rule.border()->borders[LeftEdge]; @@ -5504,10 +5740,14 @@ int QStyleSheetStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWi return 1; break; } - case SH_ItemView_ArrowKeysNavigateIntoChildren: s = QLatin1String("arrow-keys-navigate-into-children"); break; - case SH_ItemView_PaintAlternatingRowColorsForEmptyArea: s = QLatin1String("paint-alternating-row-colors-for-empty-area"); break; - case SH_TitleBar_ShowToolTipsOnButtons: s = QLatin1String("titlebar-show-tooltips-on-buttons"); break; - case SH_Widget_Animation_Duration: s = QLatin1String("widget-animation-duration"); break; + case SH_ItemView_ArrowKeysNavigateIntoChildren: s = "arrow-keys-navigate-into-children"_L1; break; + case SH_ItemView_PaintAlternatingRowColorsForEmptyArea: s = "paint-alternating-row-colors-for-empty-area"_L1; break; + case SH_TitleBar_ShowToolTipsOnButtons: s = "titlebar-show-tooltips-on-buttons"_L1; break; + case SH_Widget_Animation_Duration: s = "widget-animation-duration"_L1; break; + case SH_ScrollBar_Transient: + if (!rule.hasNativeBorder() || rule.hasBox() || rule.hasDrawable()) + return 0; + break; default: break; } if (!s.isEmpty() && rule.hasStyleHint(s)) { @@ -5826,14 +6066,14 @@ QRect QStyleSheetStyle::subControlRect(ComplexControl cc, const QStyleOptionComp if (hasStyleRule(w, PseudoElement_MdiCloseButton) || hasStyleRule(w, PseudoElement_MdiNormalButton) || hasStyleRule(w, PseudoElement_MdiMinButton)) { - QList<QVariant> layout = rule.styleHint(QLatin1String("button-layout")).toList(); + QList<QVariant> layout = rule.styleHint("button-layout"_L1).toList(); if (layout.isEmpty()) - layout = subControlLayout(QLatin1String("mNX")); + layout = subControlLayout("mNX"); int x = 0, width = 0; QRenderRule subRule; - for (int i = 0; i < layout.count(); i++) { - int layoutButton = layout[i].toInt(); + for (const QVariant &val : std::as_const(layout)) { + int layoutButton = val.toInt(); if (layoutButton < PseudoElement_MdiCloseButton || layoutButton > PseudoElement_MdiNormalButton) continue; @@ -5989,8 +6229,22 @@ QRect QStyleSheetStyle::subElementRect(SubElement se, const QStyleOption *opt, c case SE_HeaderLabel: { QRenderRule subRule = renderRule(w, opt, PseudoElement_HeaderViewSection); - if (subRule.hasBox() || !subRule.hasNativeBorder()) - return subRule.contentsRect(opt->rect); + if (subRule.hasBox() || !subRule.hasNativeBorder()) { + auto r = subRule.contentsRect(opt->rect); + if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { + // Subtract width needed for arrow, if there is one + if (header->sortIndicator != QStyleOptionHeader::None) { + const auto arrowRect = subElementRect(SE_HeaderArrow, opt, w); + if (arrowRect.isValid()) { + if (opt->state & State_Horizontal) + r.setWidth(r.width() - arrowRect.width()); + else + r.setHeight(r.height() - arrowRect.height()); + } + } + } + return r; + } } break; @@ -6118,7 +6372,7 @@ QRect QStyleSheetStyle::subElementRect(SubElement se, const QStyleOption *opt, c #endif // QT_CONFIG(toolbar) // On mac we make pixel adjustments to layouts which are not - // desireable when you have custom style sheets on them + // desirable when you have custom style sheets on them case SE_CheckBoxLayoutItem: case SE_ComboBoxLayoutItem: case SE_DateTimeEditLayoutItem: @@ -6152,7 +6406,7 @@ void QStyleSheetStyle::updateStyleSheetFont(QWidget* w) const { // Qt's fontDialog relies on the font of the sample edit for its selection, // we should never override it. - if (w->objectName() == QLatin1String("qt_fontDialog_sampleEdit")) + if (w->objectName() == "qt_fontDialog_sampleEdit"_L1) return; QWidget *container = containerWidget(w); @@ -6250,7 +6504,7 @@ Qt::Alignment QStyleSheetStyle::resolveAlignment(Qt::LayoutDirection layDir, Qt: // (and hence has the correct object name). bool QStyleSheetStyle::isNaturalChild(const QObject *obj) { - if (obj->objectName().startsWith(QLatin1String("qt_"))) + if (obj->objectName().startsWith("qt_"_L1)) return true; return false; @@ -6258,6 +6512,9 @@ bool QStyleSheetStyle::isNaturalChild(const QObject *obj) QPixmap QStyleSheetStyle::loadPixmap(const QString &fileName, const QObject *context) { + if (fileName.isEmpty()) + return {}; + qreal ratio = -1.0; if (const QWidget *widget = qobject_cast<const QWidget *>(context)) { if (QScreen *screen = QApplication::screenAt(widget->mapToGlobal(QPoint(0, 0)))) |