summaryrefslogtreecommitdiffstats
path: root/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp')
-rw-r--r--tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp797
1 files changed, 732 insertions, 65 deletions
diff --git a/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp b/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
index 7f713ebe6d..03131cebe4 100644
--- a/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
+++ b/tests/auto/widgets/widgets/qtabbar/tst_qtabbar.cpp
@@ -1,40 +1,22 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QSignalSpy>
#include <QApplication>
#include <QTabBar>
#include <QPushButton>
+#include <QLabel>
#include <QStyle>
#include <QStyleOptionTab>
#include <QProxyStyle>
#include <QTimer>
+#include <QScreen>
+#include <QWindow>
+
+#include <QtWidgets/private/qtabbar_p.h>
+
+using namespace Qt::StringLiterals;
class TabBar;
@@ -68,6 +50,7 @@ private slots:
void hideTab_data();
void hideTab();
void hideAllTabs();
+ void checkHiddenTab();
void setElideMode_data();
void setElideMode();
@@ -77,6 +60,7 @@ private slots:
void setUsesScrollButtons();
void removeLastTab();
+ void removeLastVisibleTab();
void closeButton();
@@ -100,6 +84,25 @@ private slots:
void mouseReleaseOutsideTabBar();
void mouseWheel();
+ void kineticWheel_data();
+ void kineticWheel();
+ void highResolutionWheel_data();
+ void highResolutionWheel();
+
+ void scrollButtons_data();
+ void scrollButtons();
+
+ void currentTabLargeFont();
+
+ void hoverTab_data();
+ void hoverTab();
+
+ void resizeKeepsScroll_data();
+ void resizeKeepsScroll();
+ void changeTabTextKeepsScroll();
+ void settingCurrentTabBeforeShowDoesntScroll();
+ void checkPositionsAfterShapeChange();
+ void checkScrollOffsetAfterTabRemoval();
private:
void checkPositions(const TabBar &tabbar, const QList<int> &positions);
@@ -216,7 +219,7 @@ void tst_QTabBar::testCurrentChanged()
QCOMPARE(tabBar.currentIndex(), 0);
tabBar.setCurrentIndex(tabToSet);
QCOMPARE(tabBar.currentIndex(), tabToSet);
- QCOMPARE(spy.count(), expectedCount);
+ QCOMPARE(spy.size(), expectedCount);
}
class TabBar : public QTabBar
@@ -225,6 +228,7 @@ public:
using QTabBar::initStyleOption;
using QTabBar::moveTab;
using QTabBar::QTabBar;
+ using QTabBar::tabSizeHint;
};
void tst_QTabBar::insertAtCurrentIndex()
@@ -289,7 +293,7 @@ void tst_QTabBar::removeTab()
tabbar.setCurrentIndex(currentIndex);
QSignalSpy spy(&tabbar, SIGNAL(currentChanged(int)));
tabbar.removeTab(deleteIndex);
- QTEST(int(spy.count()), "spyCount");
+ QTEST(int(spy.size()), "spyCount");
QTEST(tabbar.currentIndex(), "finalIndex");
}
@@ -320,7 +324,7 @@ void tst_QTabBar::hideTab()
tabbar.setCurrentIndex(currentIndex);
QSignalSpy spy(&tabbar, &QTabBar::currentChanged);
tabbar.setTabVisible(hideIndex, false);
- QTEST(int(spy.count()), "spyCount");
+ QTEST(int(spy.size()), "spyCount");
QTEST(tabbar.currentIndex(), "finalIndex");
}
@@ -366,6 +370,25 @@ void tst_QTabBar::hideAllTabs()
QVERIFY(sizeHint.width() < prevSizeHint.width());
}
+void tst_QTabBar::checkHiddenTab()
+{
+ QTabBar tabbar;
+
+ tabbar.addTab("foo");
+ tabbar.addTab("bar");
+ tabbar.addTab("baz");
+ tabbar.setCurrentIndex(0);
+ tabbar.setTabVisible(1, false);
+
+ QKeyEvent keyRight(QKeyEvent::KeyPress, Qt::Key_Right, Qt::NoModifier);
+ QVERIFY(QApplication::sendEvent(&tabbar, &keyRight));
+ QCOMPARE(tabbar.currentIndex(), 2);
+
+ QKeyEvent keyLeft(QKeyEvent::KeyPress, Qt::Key_Left, Qt::NoModifier);
+ QVERIFY(QApplication::sendEvent(&tabbar, &keyLeft));
+ QCOMPARE(tabbar.currentIndex(), 0);
+}
+
void tst_QTabBar::setElideMode_data()
{
QTest::addColumn<int>("tabElideMode");
@@ -464,16 +487,49 @@ void tst_QTabBar::removeLastTab()
QTabBar tabbar;
QSignalSpy spy(&tabbar, SIGNAL(currentChanged(int)));
int index = tabbar.addTab("foo");
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).at(0).toInt(), index);
spy.clear();
tabbar.removeTab(index);
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
QCOMPARE(spy.at(0).at(0).toInt(), -1);
spy.clear();
}
+void tst_QTabBar::removeLastVisibleTab()
+{
+ QTabBar tabbar;
+ tabbar.setSelectionBehaviorOnRemove(QTabBar::SelectionBehavior::SelectRightTab);
+
+ int invisible = tabbar.addTab("invisible");
+ int visible = tabbar.addTab("visible");
+ tabbar.setCurrentIndex(visible);
+ tabbar.adjustSize();
+
+ tabbar.setTabVisible(invisible, false);
+ {
+ QSignalSpy spy(&tabbar, SIGNAL(currentChanged(int)));
+ tabbar.removeTab(visible);
+ QCOMPARE(spy.size(), 1);
+ QCOMPARE(spy.at(0).at(0).toInt(), -1);
+ QCOMPARE(tabbar.currentIndex(), -1);
+ }
+
+ tabbar.setSelectionBehaviorOnRemove(QTabBar::SelectionBehavior::SelectLeftTab);
+ visible = tabbar.insertTab(0, "visible");
+ ++invisible;
+ QVERIFY(!tabbar.isTabVisible(invisible));
+ tabbar.setCurrentIndex(visible);
+ {
+ QSignalSpy spy(&tabbar, SIGNAL(currentChanged(int)));
+ tabbar.removeTab(visible);
+ QCOMPARE(spy.size(), 1);
+ QCOMPARE(spy.at(0).at(0).toInt(), -1);
+ QCOMPARE(tabbar.currentIndex(), -1);
+ }
+}
+
void tst_QTabBar::closeButton()
{
QTabBar tabbar;
@@ -492,7 +548,7 @@ void tst_QTabBar::closeButton()
QSignalSpy spy(&tabbar, SIGNAL(tabCloseRequested(int)));
button->click();
QCOMPARE(tabbar.count(), 1);
- QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.size(), 1);
}
Q_DECLARE_METATYPE(QTabBar::ButtonPosition)
@@ -598,6 +654,8 @@ void tst_QTabBar::selectionBehaviorOnRemove_data()
// every other one
QTest::newRow("previous-10") << QTabBar::SelectPreviousTab << 7 << (IntList() << 0 << 2 << 4 << 6) << (IntList() << 6 << 4) << 2;
+ // QTBUG-94352
+ QTest::newRow("QTBUG-94352") << QTabBar::SelectPreviousTab << 4 << (IntList() << 3) << (IntList() << 2 << 2) << 0;
}
@@ -762,36 +820,38 @@ void tst_QTabBar::tabBarClicked()
QSignalSpy clickSpy(&tabBar, SIGNAL(tabBarClicked(int)));
QSignalSpy doubleClickSpy(&tabBar, SIGNAL(tabBarDoubleClicked(int)));
- QCOMPARE(clickSpy.count(), 0);
- QCOMPARE(doubleClickSpy.count(), 0);
+ QCOMPARE(clickSpy.size(), 0);
+ QCOMPARE(doubleClickSpy.size(), 0);
Qt::MouseButton button = Qt::LeftButton;
while (button <= Qt::MaxMouseButton) {
const QPoint tabPos = tabBar.tabRect(0).center();
QTest::mouseClick(&tabBar, button, {}, tabPos);
- QCOMPARE(clickSpy.count(), 1);
+ QCOMPARE(clickSpy.size(), 1);
QCOMPARE(clickSpy.takeFirst().takeFirst().toInt(), 0);
- QCOMPARE(doubleClickSpy.count(), 0);
+ QCOMPARE(doubleClickSpy.size(), 0);
QTest::mouseDClick(&tabBar, button, {}, tabPos);
- QCOMPARE(clickSpy.count(), 1);
+ QCOMPARE(clickSpy.size(), 1);
QCOMPARE(clickSpy.takeFirst().takeFirst().toInt(), 0);
- QCOMPARE(doubleClickSpy.count(), 1);
+ QCOMPARE(doubleClickSpy.size(), 1);
QCOMPARE(doubleClickSpy.takeFirst().takeFirst().toInt(), 0);
+ QTest::mouseRelease(&tabBar, button, {}, tabPos);
const QPoint barPos(tabBar.tabRect(0).right() + 5, tabBar.tabRect(0).center().y());
QTest::mouseClick(&tabBar, button, {}, barPos);
- QCOMPARE(clickSpy.count(), 1);
+ QCOMPARE(clickSpy.size(), 1);
QCOMPARE(clickSpy.takeFirst().takeFirst().toInt(), -1);
- QCOMPARE(doubleClickSpy.count(), 0);
+ QCOMPARE(doubleClickSpy.size(), 0);
QTest::mouseDClick(&tabBar, button, {}, barPos);
- QCOMPARE(clickSpy.count(), 1);
+ QCOMPARE(clickSpy.size(), 1);
QCOMPARE(clickSpy.takeFirst().takeFirst().toInt(), -1);
- QCOMPARE(doubleClickSpy.count(), 1);
+ QCOMPARE(doubleClickSpy.size(), 1);
QCOMPARE(doubleClickSpy.takeFirst().takeFirst().toInt(), -1);
+ QTest::mouseRelease(&tabBar, button, {}, barPos);
button = Qt::MouseButton(button << 1);
}
@@ -833,9 +893,10 @@ void tst_QTabBar::mouseReleaseOutsideTabBar()
QRect rectToBeRepainted;
bool eventFilter(QObject *, QEvent *event) override
{
- if (event->type() == QEvent::Paint
- && rectToBeRepainted.contains(static_cast<QPaintEvent *>(event)->rect()))
+ if (event->type() == QEvent::Paint &&
+ static_cast<QPaintEvent *>(event)->rect().contains(rectToBeRepainted)) {
repainted = true;
+ }
return false;
}
} repaintChecker;
@@ -850,14 +911,15 @@ void tst_QTabBar::mouseReleaseOutsideTabBar()
QRect tabRect = tabBar.tabRect(1);
QPoint tabCenter = tabRect.center();
+ repaintChecker.rectToBeRepainted = tabRect;
+ // if a press repaints the tab...
QTest::mousePress(&tabBar, Qt::LeftButton, {}, tabCenter);
- QTest::mouseEvent(QTest::MouseMove, &tabBar, Qt::LeftButton, {}, tabCenter + QPoint(tabCenter.x(), tabCenter.y() + tabRect.height()));
+ const bool pressRepainted = QTest::qWaitFor([&]{ return repaintChecker.repainted; }, 250);
- // make sure the holding tab is repainted after releasing the mouse
+ // ... then releasing the mouse outside the tabbar should repaint it as well
repaintChecker.repainted = false;
- repaintChecker.rectToBeRepainted = tabRect;
QTest::mouseRelease(&tabBar, Qt::LeftButton, {}, tabCenter + QPoint(tabCenter.x(), tabCenter.y() + tabRect.height()));
- QTRY_VERIFY(repaintChecker.repainted);
+ QTRY_COMPARE(repaintChecker.repainted, pressRepainted);
}
void tst_QTabBar::checkPositions(const TabBar &tabbar, const QList<int> &positions)
@@ -873,20 +935,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);
}
@@ -896,38 +963,638 @@ 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"_s);
+ const auto *rightButton = tabbar.findChild<QAbstractButton*>(u"ScrollRightButton"_s);
+ 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());
+ }
+}
+
+void tst_QTabBar::highResolutionWheel_data()
+{
+ QTest::addColumn<int>("angleDelta");
+ // Smallest angleDelta for a Logitech MX Master 3 with Linux/X11/Libinput
+ QTest::addRow("increment index") << -16;
+ QTest::addRow("decrement index") << 16;
+}
+
+void tst_QTabBar::highResolutionWheel()
+{
+ TabBar tabbar;
+ TabBarScrollingProxyStyle proxyStyle;
+ tabbar.setStyle(&proxyStyle);
+
+ tabbar.addTab("tab1");
+ tabbar.addTab("tab2");
+ QFETCH(int, angleDelta);
+ // Negative values increment, positive values decrement
+ int startIndex = angleDelta < 0 ? 0 : 1;
+ tabbar.setCurrentIndex(startIndex);
+
+ const auto systemId = QPointingDevice::primaryPointingDevice()->systemId() + 1;
+ QPointingDevice hiResWheel(
+ "test high resolution wheel", systemId, QInputDevice::DeviceType::Mouse,
+ QPointingDevice::PointerType::Generic,
+ QInputDevice::Capability::Position | QInputDevice::Capability::Scroll, 1, 3);
+
+ const QPoint wheelPoint = tabbar.rect().bottomRight();
+ QWheelEvent event(wheelPoint, tabbar.mapToGlobal(wheelPoint), QPoint(),
+ QPoint(angleDelta, angleDelta), Qt::NoButton, Qt::NoModifier,
+ Qt::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication,
+ &hiResWheel);
+
+ proxyStyle.scrolling = true;
+ for (int accumulated = 0; accumulated < QWheelEvent::DefaultDeltasPerStep;
+ accumulated += qAbs(angleDelta)) {
+ // verify that nothing has changed until the threshold has been reached
+ QVERIFY(tabbar.currentIndex() == startIndex);
+ QVERIFY(QApplication::sendEvent(&tabbar, &event));
+ }
QVERIFY(tabbar.currentIndex() != startIndex);
}
#endif // QT_CONFIG(wheelevent)
+void tst_QTabBar::scrollButtons_data()
+{
+ QTest::addColumn<QTabWidget::TabPosition>("tabPosition");
+ QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
+
+ for (auto ld : {Qt::LeftToRight, Qt::RightToLeft}) {
+ const char *ldStr = ld == Qt::LeftToRight ? "LTR" : "RTL";
+ QTest::addRow("North, %s", ldStr) << QTabWidget::North << ld;
+ QTest::addRow("South, %s", ldStr) << QTabWidget::South << ld;
+ QTest::addRow("West, %s", ldStr) << QTabWidget::West << ld;
+ QTest::addRow("East, %s", ldStr) << QTabWidget::East << ld;
+ }
+}
+
+void tst_QTabBar::scrollButtons()
+{
+ QFETCH(QTabWidget::TabPosition, tabPosition);
+ QFETCH(Qt::LayoutDirection, layoutDirection);
+
+ QWidget window;
+ QTabWidget tabWidget(&window);
+ tabWidget.setLayoutDirection(layoutDirection);
+ tabWidget.setTabPosition(tabPosition);
+ tabWidget.setElideMode(Qt::ElideNone);
+ tabWidget.setUsesScrollButtons(true);
+
+ const int tabCount = 5;
+ for (int i = 0; i < tabCount; ++i)
+ {
+ const QString num = QString::number(i);
+ tabWidget.addTab(new QLabel(num), num + " - Really long tab name to force arrows");
+ }
+ tabWidget.move(0, 0);
+ tabWidget.resize(tabWidget.minimumSizeHint());
+ window.show();
+ QVERIFY(QTest::qWaitForWindowActive(&window));
+
+ auto *leftB = tabWidget.tabBar()->findChild<QAbstractButton*>(u"ScrollLeftButton"_s);
+ auto *rightB = tabWidget.tabBar()->findChild<QAbstractButton*>(u"ScrollRightButton"_s);
+
+ QVERIFY(leftB->isVisible());
+ QVERIFY(!leftB->isEnabled());
+ QVERIFY(rightB->isVisible());
+ QVERIFY(rightB->isEnabled());
+ QVERIFY(!tabWidget.tabBar()->tabRect(1).intersects(tabWidget.tabBar()->rect()));
+
+ int index = 0;
+ for (; index < tabWidget.count(); ++index) {
+ QCOMPARE(leftB->isEnabled(), index > 0);
+ QCOMPARE(rightB->isEnabled(), index < tabWidget.count() - 1);
+ QVERIFY(tabWidget.tabBar()->tabRect(index).intersects(tabWidget.tabBar()->rect()));
+ QCOMPARE(tabWidget.tabBar()->tabAt(tabWidget.tabBar()->rect().center()), index);
+ if (rightB->isEnabled())
+ rightB->click();
+ }
+ for (--index; index >= 0; --index) {
+ QCOMPARE(leftB->isEnabled(), index >= 0);
+ QCOMPARE(rightB->isEnabled(), index < tabWidget.count() - 1);
+
+ QVERIFY(tabWidget.tabBar()->tabRect(index).intersects(tabWidget.tabBar()->rect()));
+ if (leftB->isEnabled())
+ leftB->click();
+ }
+ QVERIFY(!leftB->isEnabled());
+}
+
+void tst_QTabBar::currentTabLargeFont()
+{
+ TabBar tabBar;
+ tabBar.setStyleSheet(R"(
+ QTabBar::tab::selected {
+ font-size: 24pt;
+ }
+ )");
+
+ tabBar.addTab("Tab Item 1");
+ tabBar.addTab("Tab Item 2");
+ tabBar.addTab("Tab Item 3");
+
+ tabBar.setCurrentIndex(0);
+ tabBar.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&tabBar));
+
+ QList<QRect> oldTabRects;
+ oldTabRects << tabBar.tabRect(0) << tabBar.tabRect(1) << tabBar.tabRect(2);
+ tabBar.setCurrentIndex(1);
+ QList<QRect> newTabRects;
+ newTabRects << tabBar.tabRect(0) << tabBar.tabRect(1) << tabBar.tabRect(2);
+ QVERIFY(oldTabRects != newTabRects);
+}
+
+void tst_QTabBar::hoverTab_data()
+{
+ // Move the cursor away from the widget as not to interfere.
+ // skip this test if we can't
+ const QPoint topLeft = QGuiApplication::primaryScreen()->availableGeometry().topLeft();
+ const QPoint cursorPos = topLeft + QPoint(10, 10);
+ QCursor::setPos(cursorPos);
+ if (!QTest::qWaitFor([cursorPos]{ return QCursor::pos() == cursorPos; }, 500))
+ QSKIP("Can't move mouse");
+
+ QTest::addColumn<bool>("documentMode");
+ QTest::addRow("normal mode") << true;
+ QTest::addRow("document mode") << true;
+}
+
+void tst_QTabBar::hoverTab()
+{
+ QFETCH(bool, documentMode);
+ QWidget topLevel;
+ class TabBar : public QTabBar
+ {
+ public:
+ using QTabBar::QTabBar;
+ void initStyleOption(QStyleOptionTab *option, int tabIndex) const override
+ {
+ QTabBar::initStyleOption(option, tabIndex);
+ styleOptions[tabIndex] = *option;
+ }
+ mutable QHash<int, QStyleOptionTab> styleOptions;
+ } tabbar(&topLevel);
+
+ tabbar.setDocumentMode(documentMode);
+ tabbar.addTab("A");
+ tabbar.addTab("B");
+ tabbar.addTab("C");
+ tabbar.addTab("D");
+
+ tabbar.move(0,0);
+ const QSize size = tabbar.sizeHint();
+ const auto center = topLevel.screen()->availableGeometry().center();
+ topLevel.move(center - QPoint{size.width(), size.height()} / 2);
+ topLevel.setMinimumSize(size);
+ tabbar.ensurePolished();
+
+ // some styles set those flags, some don't. If not, use a style sheet
+ if (!(tabbar.testAttribute(Qt::WA_Hover) || tabbar.hasMouseTracking())) {
+ tabbar.setStyleSheet(R"(
+ QTabBar::tab { background: blue; }
+ QTabBar::tab::hover { background: yellow; }
+ QTabBar::tab::selected { background: red; }
+ )");
+ }
+
+ topLevel.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&topLevel));
+ auto *window = topLevel.windowHandle();
+
+ auto pos = tabbar.mapToParent(tabbar.tabRect(0).center());
+ QTest::mouseMove(window, pos);
+ QTRY_VERIFY(tabbar.styleOptions[0].state & QStyle::State_Selected);
+ QTRY_COMPARE(tabbar.styleOptions[1].state & QStyle::State_MouseOver, QStyle::State_None);
+ QTRY_COMPARE(tabbar.styleOptions[2].state & QStyle::State_MouseOver, QStyle::State_None);
+ QTRY_COMPARE(tabbar.styleOptions[3].state & QStyle::State_MouseOver, QStyle::State_None);
+
+ pos = tabbar.mapToParent(tabbar.tabRect(1).center());
+ QTest::mouseMove(window, pos);
+ QTRY_COMPARE(tabbar.styleOptions[1].state & QStyle::State_MouseOver, QStyle::State_MouseOver);
+ QCOMPARE(tabbar.styleOptions[2].state & QStyle::State_MouseOver, QStyle::State_None);
+ QCOMPARE(tabbar.styleOptions[3].state & QStyle::State_MouseOver, QStyle::State_None);
+
+ pos = tabbar.mapToParent(tabbar.tabRect(2).center());
+ QTest::mouseMove(window, pos);
+ QTRY_COMPARE(tabbar.styleOptions[2].state & QStyle::State_MouseOver, QStyle::State_MouseOver);
+ QCOMPARE(tabbar.styleOptions[1].state & QStyle::State_MouseOver, QStyle::State_None);
+ QCOMPARE(tabbar.styleOptions[3].state & QStyle::State_MouseOver, QStyle::State_None);
+
+ // removing tab 2 lays the tabs out so that they stretch across the
+ // tab bar; tab 1 is now where the cursor was. What matters is that a
+ // different tab is now hovered (rather than none).
+ tabbar.removeTab(2);
+ QTRY_COMPARE(tabbar.styleOptions[1].state & QStyle::State_MouseOver, QStyle::State_MouseOver);
+ QCOMPARE(tabbar.styleOptions[2].state & QStyle::State_MouseOver, QStyle::State_None);
+
+ // inserting a tab at index 2 again should paint the new tab hovered
+ tabbar.insertTab(2, "C2");
+ QTRY_COMPARE(tabbar.styleOptions[2].state & QStyle::State_MouseOver, QStyle::State_MouseOver);
+ QCOMPARE(tabbar.styleOptions[1].state & QStyle::State_MouseOver, QStyle::State_None);
+}
+
+
+void tst_QTabBar::resizeKeepsScroll_data()
+{
+ QTest::addColumn<QTabBar::Shape>("tabShape");
+ QTest::addColumn<bool>("expanding");
+
+ QTest::addRow("North, expanding") << QTabBar::RoundedNorth << true;
+ QTest::addRow("East, expanding") << QTabBar::RoundedEast << true;
+ QTest::addRow("South, expanding") << QTabBar::RoundedSouth << true;
+ QTest::addRow("West, expanding") << QTabBar::RoundedWest << true;
+
+ QTest::addRow("North, not expanding") << QTabBar::RoundedNorth << false;
+ QTest::addRow("South, not expanding") << QTabBar::RoundedSouth << false;
+}
+
+void tst_QTabBar::resizeKeepsScroll()
+{
+ QFETCH(QTabBar::Shape, tabShape);
+ QFETCH(const bool, expanding);
+
+ QTabBar tabBar;
+ TabBarScrollingProxyStyle proxyStyle;
+ tabBar.setStyle(&proxyStyle);
+
+ for (int i = 0; i < 10; ++i)
+ tabBar.addTab(u"Tab Number %1"_s.arg(i));
+
+ tabBar.setShape(tabShape);
+ tabBar.setUsesScrollButtons(true);
+ tabBar.setExpanding(expanding);
+
+ // resize to half
+ const QSize fullSize = tabBar.sizeHint();
+ const bool horizontal = fullSize.width() > fullSize.height();
+ if (horizontal)
+ tabBar.resize(fullSize.width() / 2, fullSize.height());
+ else
+ tabBar.resize(fullSize.width(), fullSize.height() / 2);
+
+ tabBar.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&tabBar));
+
+ const auto getScrollOffset = [&]() -> int {
+ return static_cast<QTabBarPrivate *>(QObjectPrivate::get(&tabBar))->scrollOffset;
+ };
+
+ // select a tab outside, this will scroll
+ tabBar.setCurrentIndex(6);
+ // the first tab is now scrolled out
+ const int scrollOffset = getScrollOffset();
+ QCOMPARE_GT(scrollOffset, 0);
+ // the current index is now fully visible, with margin on both sides
+ tabBar.setCurrentIndex(5);
+
+ // make the tab bar a bit larger, by the width of a tab
+ if (horizontal)
+ tabBar.resize(tabBar.width() + tabBar.tabRect(5).width(), tabBar.height());
+ else
+ tabBar.resize(tabBar.width(), tabBar.height() + tabBar.tabRect(5).height());
+
+ // this should not change the scroll
+ QCOMPARE(getScrollOffset(), scrollOffset);
+
+ // make the tab bar large enough to fit everything with extra space
+ tabBar.resize(fullSize + QSize(50, 50));
+
+ // there should be no scroll
+ QCOMPARE(getScrollOffset(), 0);
+
+ for (int i = 0; i < tabBar.count(); ++i) {
+ tabBar.setCurrentIndex(i);
+ QCOMPARE(getScrollOffset(), 0);
+ }
+}
+
+void tst_QTabBar::changeTabTextKeepsScroll()
+{
+ QTabBar tabBar;
+ TabBarScrollingProxyStyle proxyStyle;
+ tabBar.setStyle(&proxyStyle);
+
+ for (int i = 0; i < 6; ++i)
+ tabBar.addTab(u"Tab Number %1"_s.arg(i));
+
+ const QSize fullSize = tabBar.sizeHint();
+ tabBar.resize(fullSize.width() / 2, fullSize.height());
+
+ tabBar.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&tabBar));
+
+ const auto getScrollOffset = [&]() -> int {
+ return static_cast<QTabBarPrivate *>(QObjectPrivate::get(&tabBar))->scrollOffset;
+ };
+
+ tabBar.setCurrentIndex(3);
+ const int scrollOffset = getScrollOffset();
+ tabBar.setTabText(3, "New title");
+ QCOMPARE(getScrollOffset(), scrollOffset);
+}
+
+void tst_QTabBar::settingCurrentTabBeforeShowDoesntScroll()
+{
+ QTabBar tabBar;
+ TabBarScrollingProxyStyle proxyStyle;
+ tabBar.setStyle(&proxyStyle);
+
+ for (int i = 0; i < 6; ++i)
+ tabBar.addTab(u"Tab Number %1"_s.arg(i));
+
+ const auto getScrollOffset = [&]() -> int {
+ return static_cast<QTabBarPrivate *>(QObjectPrivate::get(&tabBar))->scrollOffset;
+ };
+
+ tabBar.setCurrentIndex(5);
+
+ // changing the current index while the tab bar isn't visible shouldn't scroll yet
+ QCOMPARE(getScrollOffset(), 0);
+
+ // now show the tab bar with a size that's too small to fit the current index
+ const QSize fullSize = tabBar.sizeHint();
+ tabBar.resize(fullSize.width() / 2, fullSize.height());
+
+ tabBar.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&tabBar));
+
+ // this should scroll
+ QCOMPARE_GT(getScrollOffset(), 0);
+}
+
+void tst_QTabBar::checkPositionsAfterShapeChange()
+{
+ class TabWidget : public QTabWidget
+ {
+ public:
+ using QTabWidget::QTabWidget;
+ using QTabWidget::setTabBar;
+ };
+
+ class TabBar : public QTabBar
+ {
+ public:
+ using QTabBar::initStyleOption;
+ void resizeEvent(QResizeEvent *e) override
+ {
+ QTabBar::resizeEvent(e);
+ resized = true;
+ }
+ bool resized = false;
+ };
+
+ TabWidget tabWidget;
+ auto *tabBar = new TabBar;
+ tabWidget.setTabBar(tabBar);
+ for (int i = 0; i < 3; ++i)
+ tabWidget.addTab(new QWidget, u"Tab %1"_s.arg(i));
+ tabWidget.setTabPosition(QTabWidget::North);
+ tabWidget.setCurrentIndex(2);
+ tabWidget.resize(300, 300);
+ tabWidget.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&tabWidget));
+
+ tabBar->resized = false;
+ tabWidget.setTabPosition(QTabWidget::East);
+ QVERIFY(QTest::qWaitFor([&]() { return tabBar->resized; }));
+ QStyleOptionTab opt;
+ tabBar->initStyleOption(&opt, 2);
+ QVERIFY(opt.rect.top() > 0);
+}
+
+void tst_QTabBar::checkScrollOffsetAfterTabRemoval()
+{
+ QTabWidget tabWidget;
+ QTabBar *tabBar = tabWidget.tabBar();
+ for (int i = 0; i < 10; ++i)
+ tabWidget.addTab(new QWidget, u"Tab %1"_s.arg(i));
+ tabWidget.setTabPosition(QTabWidget::North);
+ tabWidget.resize(300, 300);
+ tabWidget.setCurrentIndex(0);
+ tabWidget.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&tabWidget));
+
+ auto *rightButton = tabBar->findChild<QAbstractButton *>(u"ScrollRightButton"_s);
+ auto *leftButton = tabBar->findChild<QAbstractButton *>(u"ScrollLeftButton"_s);
+ QVERIFY(leftButton);
+ QVERIFY(rightButton);
+ QVERIFY(rightButton->isEnabled());
+ QVERIFY(!leftButton->isEnabled());
+ // scroll to the right
+ tabBar->setCurrentIndex(9);
+ QVERIFY(!rightButton->isEnabled());
+ QVERIFY(leftButton->isEnabled());
+ // scroll to the center
+ tabBar->setCurrentIndex(2);
+ QVERIFY(rightButton->isEnabled());
+ QVERIFY(leftButton->isEnabled());
+
+ const auto getScrollOffset = [&]() -> int {
+ return static_cast<QTabBarPrivate *>(QObjectPrivate::get(tabBar))->scrollOffset;
+ };
+ // the scroll offset should not change when a tab right outside
+ // the scroll rect is removed
+ auto oldOffset = getScrollOffset();
+ tabWidget.removeTab(9);
+ QCOMPARE(getScrollOffset(), oldOffset);
+ // the scroll offset must change when a tab left outside
+ // the scroll rect is removed
+ oldOffset = getScrollOffset();
+ tabWidget.removeTab(0);
+ QVERIFY(getScrollOffset() < oldOffset);
+
+ // the scroll offset must change when there is empty
+ // place in the right after tab removal
+ oldOffset = getScrollOffset();
+ QVERIFY(oldOffset > 0);
+ for (int i : { 7, 6, 5, 4, 3 })
+ tabWidget.removeTab(i);
+ QCOMPARE(getScrollOffset(), 0);
+ QVERIFY(!rightButton->isVisible());
+ QVERIFY(!leftButton->isVisible());
+}
+
QTEST_MAIN(tst_QTabBar)
#include "tst_qtabbar.moc"