1 year ago
#386055
Matteo Italia
How to fix a broken sizeHint in core controls modified by a stylesheet?
I have an application that styles QAbstractSpinBox
using an application-wide stylesheet, that goes like this:
QAbstractSpinBox {
border: 2px inset grey;
text-align: right;
padding-left: 1px;
padding-right: 1px;
qproperty-buttonSymbols: PlusMinus;
}
QAbstractSpinBox::up-button
{
subcontrol-origin: margin;
subcontrol-position: right;
width: 25px;
height: 21px;
right: 1px;
}
QAbstractSpinBox::down-button
{
subcontrol-origin: margin;
subcontrol-position: left;
width: 25px;
height: 21px;
left: 2px;
}
The objective here is to obtain spinboxes with +/- buttons on the sides, like this:
While the look is ok, the control does not behave correctly in layouts; in particular, the sizeHint()
is wrong, as can be seen here for the QDoubleSpinBox
:
Experimenting a bit, it turned out that it's not taking into account the size needed for one of the buttons; I don't want to replace each and every QAbstractSpinBox
derived classes with my own subclassed version that fixes the sizeHint()
, so I went at the bottom of the issue and checked how QAbstractSpinBox
implements its sizeHint()
so look for hints:
QSize QAbstractSpinBox::sizeHint() const
{
Q_D(const QAbstractSpinBox);
if (d->cachedSizeHint.isEmpty()) {
ensurePolished();
const QFontMetrics fm(fontMetrics());
int h = d->edit->sizeHint().height();
int w = 0;
QString s;
QString fixedContent = d->prefix + d->suffix + QLatin1Char(' ');
s = d->textFromValue(d->minimum);
s.truncate(18);
s += fixedContent;
w = qMax(w, fm.horizontalAdvance(s));
s = d->textFromValue(d->maximum);
s.truncate(18);
s += fixedContent;
w = qMax(w, fm.horizontalAdvance(s));
if (d->specialValueText.size()) {
s = d->specialValueText;
w = qMax(w, fm.horizontalAdvance(s));
}
w += 2; // cursor blinking space
QStyleOptionSpinBox opt;
initStyleOption(&opt);
QSize hint(w, h);
d->cachedSizeHint = style()->sizeFromContents(QStyle::CT_SpinBox, &opt, hint, this)
.expandedTo(QApplication::globalStrut());
}
return d->cachedSizeHint;
}
Which essentially calculates a size for the internal textbox part considering the maximum width that the content can assume (using the max/min/special text), and then delegates calculating the full size to QStyle::sizeFromContents
from the current style. In turn, the relevant portion in QStyleSheetStyle::sizeFromContents
is this:
case CT_SpinBox:
if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) {
if (spinbox->buttonSymbols != QAbstractSpinBox::NoButtons) {
// Add some space for the up/down buttons
QRenderRule subRule = renderRule(w, opt, PseudoElement_SpinBoxUpButton);
if (subRule.hasDrawable()) {
QRect r = positionRect(w, rule, subRule, PseudoElement_SpinBoxUpButton,
opt->rect, opt->direction);
sz.rwidth() += r.width();
} else {
QSize defaultUpSize = defaultSize(w, subRule.size(), spinbox->rect, PseudoElement_SpinBoxUpButton);
sz.rwidth() += defaultUpSize.width();
}
}
if (rule.hasBox() || rule.hasBorder() || !rule.hasNativeBorder())
sz = rule.boxSize(sz);
return sz;
}
break;
Without going into the details, it's clear that only the Up button is considered, and only for its width, with the underlying assumption that the two buttons are stacked vertically; the down button is ignored completely.
I tried to work around the problem by means of a custom QStyle
:
struct GLProxyStyle : QProxyStyle {
virtual QSize sizeFromContents(ContentsType ct, const QStyleOption *opt, const QSize &contentsSize, const QWidget *w) const override {
QSize ret = QProxyStyle::sizeFromContents(ct, opt, contentsSize, w);
if (ct == CT_SpinBox) {
if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(opt)) {
if (spinbox->buttonSymbols != QAbstractSpinBox::NoButtons) {
QRect downRect = subControlRect(CC_SpinBox, spinbox, SC_SpinBoxDown, w);
QRect upRect = subControlRect(CC_SpinBox, spinbox, SC_SpinBoxDown, w);
if (downRect.left() != upRect.left()) {
ret.rwidth() += downRect.width();
}
}
}
}
return ret;
}
};
but unfortunately setting a stylesheet makes it override QStyle
.
For now I'm forcing a minimum size depending on an adjusted size hint after building all widgets, but that's a pain and, most importantly, it introduces a relayout, so I'd like to find a way to fix the sizeHint()
globally.
So, coming to my question:
- is there a way to override the
QStyleSheetStyle
implementation ofsizeFromContents
? - or, more in general: is there some other way to fix this that I missed?
c++
qt
qtstylesheets
qtwidgets
0 Answers
Your Answer