summaryrefslogtreecommitdiffstats
path: root/tests/auto/widgets/widgets
diff options
context:
space:
mode:
authorVolker Hilsheimer <volker.hilsheimer@qt.io>2021-10-23 10:53:51 +0200
committerVolker Hilsheimer <volker.hilsheimer@qt.io>2021-11-15 16:23:42 +0100
commit61aa482241a7e81116224b2a5ae642df49526982 (patch)
tree1c3743ae61c372e7be8951d8e1ca08e725a2539a /tests/auto/widgets/widgets
parentee4f69205a375f821c32b4d2dd77e8d603910820 (diff)
QTabBar: Support scrolling with a kinetic wheel
QTabBar implements wheelEvent to move the current index up or down. This is useful for clicky mouse wheels, but a bad user experience when using a kinetic wheel or touch pad, as every pixel movement will change the current index. Instead, scroll the entire tab bar when the wheel event comes from a device that supports scroll phases, without changing the current index. As drive-by's, fix the test introduced in aa09bea00ca88c587cfb1f56 to not leak memory or leave a test-specific style set on the QApplication instance, which can break other tests. Also, make relevant layout code in QTabBar respect the usesScrollButtons property, const'ify local variables, and return an accepted QWheelEvent if the event resulted in a change. [ChangeLog][QtWidgets][QTabBar] Scrolling with a kinetic wheel or touch pad scrolls the entire tab bar, without changing the current index. Change-Id: I990e51466dd25c741877bbf0e197449f897a9efb Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
Diffstat (limited to 'tests/auto/widgets/widgets')
-rw-r--r--tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp195
1 files changed, 179 insertions, 16 deletions
diff --git a/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp b/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
index 41f1cb5e64..630b5c1167 100644
--- a/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
+++ b/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
@@ -101,6 +101,8 @@ private slots:
void mouseReleaseOutsideTabBar();
void mouseWheel();
+ void kineticWheel_data();
+ void kineticWheel();
void scrollButtons_data();
void scrollButtons();
@@ -889,20 +891,25 @@ void tst_QTabBar::checkPositions(const TabBar &tabbar, const QList<int> &positio
}
#if QT_CONFIG(wheelevent)
-// defined to be 120 by the wheel mouse vendors according to the docs
-#define WHEEL_DELTA 120
class TabBarScrollingProxyStyle : public QProxyStyle
{
public:
- TabBarScrollingProxyStyle() : QProxyStyle(), scrolling(true)
+ TabBarScrollingProxyStyle(const QString &defStyle = {})
+ : QProxyStyle(defStyle), scrolling(true)
{ }
int styleHint(StyleHint hint, const QStyleOption *option = 0,
const QWidget *widget = 0, QStyleHintReturn *returnData = 0) const override
{
- if (hint == QStyle::SH_TabBar_AllowWheelScrolling)
+ switch (hint) {
+ case QStyle::SH_TabBar_AllowWheelScrolling:
return scrolling;
+ case SH_TabBar_ElideMode:
+ return Qt::ElideNone;
+ default:
+ break;
+ }
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
@@ -912,37 +919,193 @@ public:
void tst_QTabBar::mouseWheel()
{
+ TabBar tabbar;
- // apply custom style to app, which can toggle tabbar scrolling behavior
- QCoreApplication *applicationInstance = QApplication::instance();
- QVERIFY(applicationInstance != 0);
- auto *proxyStyle = new TabBarScrollingProxyStyle;
- QApplication::setStyle(proxyStyle);
+ // apply custom style to the tabbar, which can toggle tabbar scrolling behavior
+ TabBarScrollingProxyStyle proxyStyle;
+ tabbar.setStyle(&proxyStyle);
// make tabbar with three tabs, select the middle one
- TabBar tabbar;
tabbar.addTab("one");
tabbar.addTab("two");
tabbar.addTab("three");
int startIndex = 1;
tabbar.setCurrentIndex(startIndex);
+ const auto systemId = QPointingDevice::primaryPointingDevice()->systemId() + 1;
+ QPointingDevice clickyWheel("test clicky wheel", systemId, QInputDevice::DeviceType::Mouse,
+ QPointingDevice::PointerType::Generic,
+ QInputDevice::Capability::Position | QInputDevice::Capability::Scroll,
+ 1, 3);
+
// define scroll event
const QPoint wheelPoint = tabbar.rect().bottomRight();
- QWheelEvent event(wheelPoint, tabbar.mapToGlobal(wheelPoint), QPoint(), QPoint(0, WHEEL_DELTA),
- Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false);
+ QWheelEvent event(wheelPoint, tabbar.mapToGlobal(wheelPoint),
+ QPoint(), QPoint(0, QWheelEvent::DefaultDeltasPerStep),
+ Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false,
+ Qt::MouseEventSynthesizedByApplication, &clickyWheel);
// disable scrolling, send scroll event, confirm that tab did not change
- proxyStyle->scrolling = false;
- QVERIFY(applicationInstance->sendEvent(&tabbar, &event));
+ proxyStyle.scrolling = false;
+ QVERIFY(QApplication::sendEvent(&tabbar, &event));
QVERIFY(tabbar.currentIndex() == startIndex);
// enable scrolling, send scroll event, confirm that tab changed
- proxyStyle->scrolling = true;
- QVERIFY(applicationInstance->sendEvent(&tabbar, &event));
+ proxyStyle.scrolling = true;
+ QVERIFY(QApplication::sendEvent(&tabbar, &event));
QVERIFY(tabbar.currentIndex() != startIndex);
}
+void tst_QTabBar::kineticWheel_data()
+{
+ QTest::addColumn<QTabBar::Shape>("tabShape");
+
+ QTest::addRow("North") << QTabBar::RoundedNorth;
+ QTest::addRow("East") << QTabBar::RoundedEast;
+ QTest::addRow("South") << QTabBar::RoundedSouth;
+ QTest::addRow("West") << QTabBar::RoundedWest;
+}
+
+void tst_QTabBar::kineticWheel()
+{
+ const auto systemId = QPointingDevice::primaryPointingDevice()->systemId() + 1;
+ QPointingDevice pixelPad("test pixel pad", systemId, QInputDevice::DeviceType::TouchPad,
+ QPointingDevice::PointerType::Generic,
+ QInputDevice::Capability::Position | QInputDevice::Capability::PixelScroll,
+ 1, 3);
+
+ QFETCH(QTabBar::Shape, tabShape);
+ QWidget window;
+ TabBar tabbar(&window);
+ // Since the macOS style makes sure that all tabs are always visible, we
+ // replace it with the windows style for this test, and use the proxy that
+ // makes sure that scrolling is enabled and that tab texts are not elided.
+ QString defaultStyle;
+ if (QApplication::style()->name() == QStringLiteral("macos"))
+ defaultStyle = "windows";
+ TabBarScrollingProxyStyle proxyStyle(defaultStyle);
+ tabbar.setStyle(&proxyStyle);
+
+ tabbar.addTab("long tab text 1");
+ tabbar.addTab("long tab text 2");
+ tabbar.addTab("long tab text 3");
+
+ // Make sure we don't have enough space for the tabs and need to scroll
+ const int tabbarLength = tabbar.tabRect(0).width() * 2;
+
+ tabbar.setShape(tabShape);
+ const bool horizontal = tabShape == QTabBar::RoundedNorth
+ || tabShape == QTabBar::RoundedSouth;
+ if (horizontal)
+ tabbar.setFixedWidth(tabbarLength);
+ else
+ tabbar.setFixedHeight(tabbarLength);
+
+ // start with the middle tab, QTabBar will scroll to make it visible
+ const int startIndex = 1;
+ tabbar.setCurrentIndex(startIndex);
+
+ window.setMinimumSize(tabbarLength, tabbarLength);
+ window.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&window));
+
+ const auto *leftButton = tabbar.findChild<QAbstractButton*>(u"ScrollLeftButton"_qs);
+ const auto *rightButton = tabbar.findChild<QAbstractButton*>(u"ScrollRightButton"_qs);
+ QVERIFY(leftButton && rightButton);
+ QVERIFY(leftButton->isEnabled() && rightButton->isEnabled());
+
+ // Figure out if any of the buttons is laid out to be in front of the tabs.
+ // We can't use setUsesScrollButtons(false), as then several styles will enforce
+ // a minimum size for the tab bar.
+ const bool leftInFront = ((horizontal && leftButton->pos().x() < tabbar.rect().center().x())
+ || (!horizontal && leftButton->pos().y() < tabbar.rect().center().y()));
+ const bool rightInFront = ((horizontal && rightButton->pos().x() < tabbar.rect().center().x())
+ || (!horizontal && rightButton->pos().y() < tabbar.rect().center().y()));
+ QPoint leftEdge;
+ QPoint rightEdge;
+ if (leftInFront && rightInFront) { // both on the left
+ leftEdge = rightButton->geometry().bottomRight();
+ rightEdge = tabbar.rect().bottomRight();
+ } else if (leftInFront && !rightInFront) {
+ leftEdge = leftButton->geometry().bottomRight();
+ rightEdge = rightButton->geometry().topLeft();
+ } else { // both on the right
+ leftEdge = QPoint(0, 0);
+ rightEdge = leftButton->geometry().topLeft();
+ }
+ // avoid border lines
+ leftEdge += QPoint(2, 2);
+ if (horizontal) {
+ rightEdge += QPoint(-2, 2);
+ } else {
+ rightEdge += QPoint(2, -2);
+ }
+
+ QCOMPARE(tabbar.tabAt(leftEdge), 0);
+ QCOMPARE(tabbar.tabAt(rightEdge), 1);
+
+ const QPoint delta = horizontal ? QPoint(10, 0) : QPoint(0, 10);
+ const QPoint wheelPoint = tabbar.rect().center();
+
+ bool accepted = true;
+ Qt::ScrollPhase phase = Qt::ScrollBegin;
+ // scroll all the way to the end
+ while (accepted) {
+ QWheelEvent event(wheelPoint, tabbar.mapToGlobal(wheelPoint), -delta, -delta,
+ Qt::NoButton, Qt::NoModifier, phase, false,
+ Qt::MouseEventSynthesizedByApplication, &pixelPad);
+ if (phase == Qt::ScrollBegin)
+ phase = Qt::ScrollUpdate;
+ QApplication::sendEvent(&tabbar, &event);
+ accepted = event.isAccepted();
+ }
+ QCOMPARE(tabbar.tabAt(leftEdge), 1);
+ QCOMPARE(tabbar.tabAt(rightEdge), 2);
+ QVERIFY(leftButton->isEnabled());
+ QVERIFY(!rightButton->isEnabled());
+ // kinetic wheel events don't change the current index
+ QVERIFY(tabbar.currentIndex() == startIndex);
+
+ // scroll all the way to the beginning
+ accepted = true;
+ while (accepted) {
+ QWheelEvent event(wheelPoint, tabbar.mapToGlobal(wheelPoint), delta, delta,
+ Qt::NoButton, Qt::NoModifier, phase, false,
+ Qt::MouseEventSynthesizedByApplication, &pixelPad);
+ QApplication::sendEvent(&tabbar, &event);
+ accepted = event.isAccepted();
+ }
+
+ QCOMPARE(tabbar.tabAt(leftEdge), 0);
+ QCOMPARE(tabbar.tabAt(rightEdge), 1);
+ QVERIFY(!leftButton->isEnabled());
+ QVERIFY(rightButton->isEnabled());
+ // kinetic wheel events don't change the current index
+ QVERIFY(tabbar.currentIndex() == startIndex);
+
+ // make tabs small so that we have enough space, and verify sure we can't scroll
+ tabbar.setTabText(0, "A");
+ tabbar.setTabText(1, "B");
+ tabbar.setTabText(2, "C");
+ QVERIFY(tabbar.sizeHint().width() <= tabbar.width() && tabbar.sizeHint().height() <= tabbar.height());
+
+ {
+ QWheelEvent event(wheelPoint, tabbar.mapToGlobal(wheelPoint), -delta, -delta,
+ Qt::NoButton, Qt::NoModifier, phase, false,
+ Qt::MouseEventSynthesizedByApplication, &pixelPad);
+ QApplication::sendEvent(&tabbar, &event);
+ QVERIFY(!event.isAccepted());
+ }
+
+ {
+ QWheelEvent event(wheelPoint, tabbar.mapToGlobal(wheelPoint), delta, delta,
+ Qt::NoButton, Qt::NoModifier, phase, false,
+ Qt::MouseEventSynthesizedByApplication, &pixelPad);
+ QApplication::sendEvent(&tabbar, &event);
+ QVERIFY(!event.isAccepted());
+ }
+}
+
#endif // QT_CONFIG(wheelevent)
void tst_QTabBar::scrollButtons_data()