summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVolker Hilsheimer <volker.hilsheimer@qt.io>2021-10-07 17:56:37 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2021-10-11 14:15:50 +0000
commit07901558cdd5967549104f1ae80b0be85f328260 (patch)
tree3b3ab7e85be73b5eaaa84ac0a36635e4456b4e65
parenta3489a3bc315387b5f3fa9b88a40f4686c2722f6 (diff)
Reduce the width of a hfw-widget if scrollbar would be flipping
For a widget that implements height-for-width, the vertical scrollbar becoming visible might be just enough to make the scrollbar unnecessary. In that situation, the scrollbar flips on and off continuously. To avoid that situation, make the width of the widget smaller until the height fits without scrollbar, up to the point where we have space for the scrollbar anyway. The calcuation here is assumed to be cheap, but depends on the heightForWidth implementation in the widget. Running the while-loop a few dozen times should have no performance impact during resizing and laying out the scroll area contents. Add a test that confirms that within a brief period of time we only get the one hide-event we expect. Done-with: Zou Ya <zouya@uniontech.com> Fixes: QTBUG-92958 Change-Id: I0faeb5f9b1a226aada958c18333d9c2ac8203dd1 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io> (cherry picked from commit 6c4dc722cb9bf765904feefff4fb00bdb0b3dc9f) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/widgets/widgets/qscrollarea.cpp15
-rw-r--r--tests/auto/widgets/widgets/qscrollarea/tst_qscrollarea.cpp58
2 files changed, 72 insertions, 1 deletions
diff --git a/src/widgets/widgets/qscrollarea.cpp b/src/widgets/widgets/qscrollarea.cpp
index e538cae583..174bdf68ef 100644
--- a/src/widgets/widgets/qscrollarea.cpp
+++ b/src/widgets/widgets/qscrollarea.cpp
@@ -196,7 +196,20 @@ void QScrollAreaPrivate::updateScrollBars()
if (resizable) {
if ((widget->layout() ? widget->layout()->hasHeightForWidth() : widget->sizePolicy().hasHeightForWidth())) {
QSize p_hfw = p.expandedTo(min).boundedTo(max);
- int h = widget->heightForWidth( p_hfw.width() );
+ int h = widget->heightForWidth(p_hfw.width());
+ // If the height we calculated requires a vertical scrollbar,
+ // then we need to constrain the width and calculate the height again,
+ // otherwise we end up flipping the scrollbar on and off all the time.
+ if (vbarpolicy == Qt::ScrollBarAsNeeded) {
+ int vbarWidth = vbar->sizeHint().width();
+ QSize m_hfw = m.expandedTo(min).boundedTo(max);
+ while (h > m.height() && vbarWidth) {
+ --vbarWidth;
+ --m_hfw.rwidth();
+ h = widget->heightForWidth(m_hfw.width());
+ }
+ max = QSize(m_hfw.width(), qMax(m_hfw.height(), h));
+ }
min = QSize(p_hfw.width(), qMax(p_hfw.height(), h));
}
}
diff --git a/tests/auto/widgets/widgets/qscrollarea/tst_qscrollarea.cpp b/tests/auto/widgets/widgets/qscrollarea/tst_qscrollarea.cpp
index 9f08bd337b..d817d84710 100644
--- a/tests/auto/widgets/widgets/qscrollarea/tst_qscrollarea.cpp
+++ b/tests/auto/widgets/widgets/qscrollarea/tst_qscrollarea.cpp
@@ -33,6 +33,7 @@
#include <qdebug.h>
#include <qscrollarea.h>
#include <qlayout.h>
+#include <qscrollbar.h>
class tst_QScrollArea : public QObject
{
@@ -46,6 +47,7 @@ private slots:
void getSetCheck();
void ensureMicroFocusVisible_Task_167838();
void checkHFW_Task_197736();
+ void stableHeightForWidth();
};
tst_QScrollArea::tst_QScrollArea()
@@ -165,5 +167,61 @@ void tst_QScrollArea::checkHFW_Task_197736()
QCOMPARE(w->height(), 200);
}
+
+/*
+ If the scroll area rides the size where, due to the height-for-width
+ implementation of the widget, the vertical scrollbar is needed only
+ if the vertical scrollbar is visible, then we don't want it to flip
+ back and forth, but rather constrain the width of the widget.
+ See QTBUG-92958.
+*/
+void tst_QScrollArea::stableHeightForWidth()
+{
+ struct HeightForWidthWidget : public QWidget
+ {
+ HeightForWidthWidget()
+ {
+ QSizePolicy policy = sizePolicy();
+ policy.setHeightForWidth(true);
+ setSizePolicy(policy);
+ }
+ // Aspect ratio 1:1
+ int heightForWidth(int width) const override { return width; }
+ };
+
+ class HeightForWidthArea : public QScrollArea
+ {
+ public:
+ HeightForWidthArea()
+ {
+ this->verticalScrollBar()->installEventFilter(this);
+ }
+ protected:
+ bool eventFilter(QObject *obj, QEvent *e) override
+ {
+ if (obj == verticalScrollBar() && e->type() == QEvent::Hide)
+ ++m_hideCount;
+ return QScrollArea::eventFilter(obj,e);
+ }
+ public:
+ int m_hideCount = 0;
+ };
+
+ HeightForWidthArea area;
+ HeightForWidthWidget equalWHWidget;
+ area.setWidget(&equalWHWidget);
+ area.setWidgetResizable(true);
+ // at this size, the widget wants to be 501 pixels high,
+ // requiring a vertical scrollbar in a 499 pixel high area.
+ // but the width resulting from showing the scrollbar would
+ // be less than 499, so no scrollbars would be needed anymore.
+ area.resize(501, 499);
+ area.show();
+ QTest::qWait(500);
+ // if the scrollbar got hidden more than once, then the layout
+ // isn't stable.
+ QVERIFY(area.m_hideCount <= 1);
+}
+
QTEST_MAIN(tst_QScrollArea)
#include "tst_qscrollarea.moc"