Correctly support style sheet for toolbutton menus and arrows

QToolButton can show an arrow instead of an icon, and that arrow can, as
per the documentation, styled via the ::down/up/left/right-arrow pseudo
element. This was not working at all, as the implementation confused the
down-arrow with the menu arrow. Implement this correctly for all arrow
types.

A QToolButton can also have different ways to show a menu, either by
using a separate section of the button that can be clicked; this section
can be styled via ::menu-button and ::menu-arrow.
Or by instant or delayed menu popup when clicking the button itself, in
which case the button shows an indicator in the button itelf; that
indicator can be styled via the ::menu-indicator pseudo element.

The old implementation confused the various options, and the name of the
PseudeoElement_ToolButonDownArrow didn't help with that. So rename that
element to PseudoElement_ToolButtonMenuIndicator, and render it when
there is no separate drop down.

Fixes: QTBUG-27640
Pick-to: 6.1 6.2
Change-Id: Ia142a5d7498fa717e70f4e5382305e305b29effa
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
Volker Hilsheimer 2021-06-09 10:28:43 +02:00
parent 308845cd72
commit 2b2e7b2ac5

View File

@ -194,7 +194,7 @@ enum PseudoElement {
PseudoElement_GroupBoxIndicator,
PseudoElement_ToolButtonMenu,
PseudoElement_ToolButtonMenuArrow,
PseudoElement_ToolButtonDownArrow,
PseudoElement_ToolButtonMenuIndicator,
PseudoElement_ToolBoxTab,
PseudoElement_ScrollBarSlider,
PseudoElement_ScrollBarAddPage,
@ -1899,7 +1899,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)
@ -2188,7 +2188,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 +2229,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 +2249,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 +2307,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);
@ -3237,8 +3243,20 @@ 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 customArrow = tool->features & QStyleOptionToolButton::Arrow;
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;
};
bool customDropDown = tool->features & QStyleOptionToolButton::MenuButtonPopup;
bool customDropDownArrow = false;
bool customMenuIndicator = !customDropDown && (tool->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,12 +3270,19 @@ 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);
customArrow = customArrow && hasStyleRule(w, customArrowElement());
if (customArrow)
toolOpt.features &= ~QStyleOptionToolButton::HasMenu;
toolOpt.features &= ~QStyleOptionToolButton::Arrow;
customDropDown = customDropDown && hasStyleRule(w, PseudoElement_ToolButtonMenu);
if (customDropDown)
if (customDropDown) {
toolOpt.subControls &= ~QStyle::SC_ToolButtonMenu;
customDropDownArrow = hasStyleRule(w, PseudoElement_ToolButtonMenuArrow);
if (customDropDownArrow)
toolOpt.features &= ~(QStyleOptionToolButton::Menu | QStyleOptionToolButton::HasMenu);
}
customMenuIndicator = customMenuIndicator && hasStyleRule(w, PseudoElement_ToolButtonMenuIndicator);
if (customMenuIndicator)
toolOpt.features &= ~QStyleOptionToolButton::HasMenu;
if (rule.baseStyleCanDraw() && !(tool->features & QStyleOptionToolButton::Arrow)) {
baseStyle()->drawComplexControl(cc, &toolOpt, p, w);
@ -3265,7 +3290,7 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC
QWindowsStyle::drawComplexControl(cc, &toolOpt, p, w);
}
if (!customArrow && !customDropDown)
if (!customArrow && !customDropDown && !customMenuIndicator)
return;
} else {
rule.drawRule(p, opt->rect);
@ -3275,30 +3300,65 @@ 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);
const QRect cr = toolOpt.rect;
if (customDropDown) {
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) {
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 (customMenuIndicator) {
QRenderRule subRule = renderRule(w, opt, PseudoElement_ToolButtonMenuIndicator);
QRect r = subRule.hasGeometry()
? positionRect(w, subRule, PseudoElement_ToolButtonMenuIndicator, toolOpt.rect, toolOpt.direction)
: subRule.contentsRect(opt->rect);
if (subRule.hasDrawable()) {
subRule.drawRule(p, r);
} else {
toolOpt.rect = r;
baseStyle()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &toolOpt, p, w);
}
}
toolOpt.rect = cr;
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);
const auto arrowElement = customArrowElement();
QRenderRule subRule = renderRule(w, opt, arrowElement);
QRect r = subRule.hasGeometry() ? positionRect(w, subRule, arrowElement, toolOpt.rect, toolOpt.direction)
: subRule.contentsRect(toolOpt.rect);
if (subRule.hasDrawable()) {
subRule.drawRule(p, r);
} else {
toolOpt.rect = r2;
baseStyle()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &toolOpt, p, w);
toolOpt.rect = r;
const auto arrowElement = [&toolOpt] {
switch (toolOpt.arrowType) {
case Qt::DownArrow: return QStyle::PE_IndicatorArrowDown;
case Qt::UpArrow: return QStyle::PE_IndicatorArrowUp;
case Qt::LeftArrow: return QStyle::PE_IndicatorArrowLeft;
case Qt::RightArrow: return QStyle::PE_IndicatorArrowRight;
case Qt::NoArrow: break;
}
return QStyle::PE_IndicatorArrowDown; // never happens
};
baseStyle()->drawPrimitive(arrowElement(), &toolOpt, p, w);
}
}
@ -4786,8 +4846,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())