QStyleSheetStyle: add new property to QPushButton: icon

There is currently no proper way to change the icon of a
pushbutton from css. But there is a need for doing so
(QTBUG-2982), and the typical work-around is to instead use
the css property 'qproperty-icon'. But setting qproperties
from the style is not a good idea in the first place, since
it modifies the state of the widget it draws. Moreover, such
properties are only set once (in QStyle::polish()), and
will not have any effect on pseudo states, like hover.

To close this gap, this patch will add a css property
'icon' that can be set on a QPushButton. This property
will follow normal css cascading, and respect pseudo
states, equal to any other css property.

[ChangeLog][QtWidgets][QStyle] You can now set the
CSS property 'icon' on a QPushButton to override
which icon to draw.

Fixes: QTBUG-79137
Change-Id: Ie7e0b0fa4f19471f51108cd4ca931356219d562e
Reviewed-by: Christian Ehrlicher <ch.ehrlicher@gmx.de>
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
This commit is contained in:
Richard Moe Gustavsen 2019-10-10 14:33:53 +02:00
parent a4751f8824
commit 01ec11507d
4 changed files with 86 additions and 16 deletions

View File

@ -123,6 +123,7 @@ static const QCssKnownValue properties[NumProperties - 1] = {
{ "font-variant", FontVariant },
{ "font-weight", FontWeight },
{ "height", Height },
{ "icon", QtIcon },
{ "image", QtImage },
{ "image-position", QtImageAlignment },
{ "left", Left },
@ -1379,6 +1380,37 @@ bool ValueExtractor::extractImage(QIcon *icon, Qt::Alignment *a, QSize *size)
return hit;
}
bool ValueExtractor::extractIcon(QIcon *icon, QSize *size)
{
// Find last declaration that specifies an icon
const auto declaration = std::find_if(
declarations.rbegin(), declarations.rend(),
[](const Declaration &decl) { return decl.d->propertyId == QtIcon; });
if (declaration == declarations.rend())
return false;
*icon = declaration->iconValue();
// If the value contains a URI, try to get the size of the icon
if (declaration->d->values.isEmpty())
return true;
const auto &propertyValue = declaration->d->values.constFirst();
if (propertyValue.type != Value::Uri)
return true;
// First try to read just the size from the image without loading it
const QString url(propertyValue.variant.toString());
QImageReader imageReader(url);
*size = imageReader.size();
if (!size->isNull())
return true;
// Get the size by loading the image instead
*size = imageReader.read().size();
return true;
}
///////////////////////////////////////////////////////////////////////////////
// Declaration
QColor Declaration::colorValue(const QPalette &pal) const

View File

@ -198,6 +198,7 @@ enum Property {
QtLineHeightType,
FontKerning,
QtForegroundTextureCacheKey,
QtIcon,
NumProperties
};
@ -855,6 +856,7 @@ struct Q_GUI_EXPORT ValueExtractor
bool extractPalette(QBrush *fg, QBrush *sfg, QBrush *sbg, QBrush *abg);
int extractStyleFeatures();
bool extractImage(QIcon *icon, Qt::Alignment *a, QSize *size);
bool extractIcon(QIcon *icon, QSize *size);
int lengthValue(const Declaration &decl);

View File

@ -993,6 +993,9 @@
\li Supports the \l{box model}. Supports the \l{#default-ps}{:default},
\l{#flat-ps}{:flat}, \l{#checked-ps}{:checked} pseudo states.
Since 5.15, the \l{#icon-prop}{icon} property can be set to
override the button icon.
For QPushButton with a menu, the menu indicator is styled
using the \l{#menu-indicator-sub}{::menu-indicator}
subcontrol. Appearance of checkable push buttons can be
@ -1945,6 +1948,20 @@
See also \l{#width-prop}{width}.
\row
\li \b{\c icon} \target icon-prop
\li \l{#Url}{Url}+
\li The icon that is used, for widgets that have an icon.
The only widget currently supporting this property is QPushButton.
\note It's the application's responsibilty to assign an icon to a
button (using the QAbstractButton API), and not the style's. So be
careful setting it unless your stylesheet is targeting a specific
application.
Available since 5.15.
\row
\li \b{\c icon-size} \target icon-size-prop
\li \l{#Length}{Length}

View File

@ -534,6 +534,7 @@ public:
const QStyleSheetOutlineData *outline() const { return ou; }
const QStyleSheetGeometryData *geometry() const { return geo; }
const QStyleSheetPositionData *position() const { return p; }
const QStyleSheetImageData *icon() const { return iconPtr; }
bool hasModification() const;
@ -569,6 +570,7 @@ public:
bool hasGeometry() const { return geo != 0; }
bool hasDrawable() const { return !hasNativeBorder() || hasBackground() || hasImage(); }
bool hasImage() const { return img != 0; }
bool hasIcon() const { return iconPtr != 0; }
QSize minimumContentsSize() const
{ return geo ? QSize(geo->minWidth, geo->minHeight) : QSize(0, 0); }
@ -628,6 +630,7 @@ public:
QSharedDataPointer<QStyleSheetGeometryData> geo;
QSharedDataPointer<QStyleSheetPositionData> p;
QSharedDataPointer<QStyleSheetImageData> img;
QSharedDataPointer<QStyleSheetImageData> iconPtr;
int clipset;
QPainterPath clipPath;
@ -969,11 +972,16 @@ QRenderRule::QRenderRule(const QVector<Declaration> &declarations, const QObject
if (v.extractPalette(&fg, &sfg, &sbg, &abg))
pal = new QStyleSheetPaletteData(fg, sfg, sbg, abg);
QIcon icon;
QIcon imgIcon;
alignment = Qt::AlignCenter;
QSize imgSize;
if (v.extractImage(&imgIcon, &alignment, &imgSize))
img = new QStyleSheetImageData(imgIcon, alignment, imgSize);
QIcon icon;
QSize size;
if (v.extractImage(&icon, &alignment, &size))
img = new QStyleSheetImageData(icon, alignment, size);
if (v.extractIcon(&icon, &size))
iconPtr = new QStyleSheetImageData(icon, Qt::AlignCenter, size);
int adj = -255;
hasFont = v.extractFont(&font, &adj);
@ -3521,15 +3529,25 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q
if (rule.hasFont)
p->setFont(rule.font.resolve(p->font()));
if (rule.hasPosition() && rule.position()->textAlignment != 0) {
Qt::Alignment textAlignment = rule.position()->textAlignment;
QRect textRect = button->rect;
if (rule.hasPosition() || rule.hasIcon()) {
uint tf = Qt::TextShowMnemonic;
QRect textRect = button->rect;
const uint horizontalAlignMask = Qt::AlignHCenter | Qt::AlignLeft | Qt::AlignRight;
const uint verticalAlignMask = Qt::AlignVCenter | Qt::AlignTop | Qt::AlignLeft;
tf |= (textAlignment & verticalAlignMask) ? (textAlignment & verticalAlignMask) : Qt::AlignVCenter;
if (!styleHint(SH_UnderlineShortcut, button, w))
tf |= Qt::TextHideMnemonic;
if (!button->icon.isNull()) {
if (rule.hasPosition() && rule.position()->textAlignment != 0) {
Qt::Alignment textAlignment = rule.position()->textAlignment;
tf |= (textAlignment & verticalAlignMask) ? (textAlignment & verticalAlignMask) : Qt::AlignVCenter;
tf |= (textAlignment & horizontalAlignMask) ? (textAlignment & horizontalAlignMask) : Qt::AlignHCenter;
if (!styleHint(SH_UnderlineShortcut, button, w))
tf |= Qt::TextHideMnemonic;
} else {
tf |= Qt::AlignVCenter | Qt::AlignHCenter;
}
QIcon icon = rule.hasIcon() ? rule.icon()->icon : button->icon;
if (!icon.isNull()) {
//Group both icon and text
QRect iconRect;
QIcon::Mode mode = button->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
@ -3539,7 +3557,7 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q
if (button->state & State_On)
state = QIcon::On;
QPixmap pixmap = button->icon.pixmap(button->iconSize, mode, state);
QPixmap pixmap = icon.pixmap(button->iconSize, mode, state);
int pixmapWidth = pixmap.width() / pixmap.devicePixelRatio();
int pixmapHeight = pixmap.height() / pixmap.devicePixelRatio();
int labelWidth = pixmapWidth;
@ -3550,10 +3568,10 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q
labelWidth += (textWidth + iconSpacing);
//Determine label alignment:
if (textAlignment & Qt::AlignLeft) { /*left*/
if (tf & Qt::AlignLeft) { /*left*/
iconRect = QRect(textRect.x(), textRect.y() + (textRect.height() - labelHeight) / 2,
pixmapWidth, pixmapHeight);
} else if (textAlignment & Qt::AlignHCenter) { /* center */
} else if (tf & Qt::AlignHCenter) { /* center */
iconRect = QRect(textRect.x() + (textRect.width() - labelWidth) / 2,
textRect.y() + (textRect.height() - labelHeight) / 2,
pixmapWidth, pixmapHeight);
@ -3565,7 +3583,9 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q
iconRect = visualRect(button->direction, textRect, iconRect);
tf |= Qt::AlignLeft; //left align, we adjust the text-rect instead
// Left align, adjust the text-rect according to the icon instead
tf &= ~horizontalAlignMask;
tf |= Qt::AlignLeft;
if (button->direction == Qt::RightToLeft)
textRect.setRight(iconRect.left() - iconSpacing);
@ -3576,9 +3596,8 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q
iconRect.translate(pixelMetric(PM_ButtonShiftHorizontal, opt, w),
pixelMetric(PM_ButtonShiftVertical, opt, w));
p->drawPixmap(iconRect, pixmap);
} else {
tf |= textAlignment;
}
if (button->state & (State_On | State_Sunken))
textRect.translate(pixelMetric(PM_ButtonShiftHorizontal, opt, w),
pixelMetric(PM_ButtonShiftVertical, opt, w));