control scrolling of QTabBar using StyleHint

Mouse wheel/touchpad scroll signals sent to the tab bar trigger
cycling through the tabs. In applications where the tab bar is
close to "mouse click hotspots", the cursor may accidentally be
left over the tab bar instead of the main content of the window.
When the user wants to scroll up/down the main conten, the
scroll signals are thus sent to the tab bar and instead of
scrolling, the focus switches to another tab. This is
confusing to the user, because not only does the application
not carry out the desired action (scrolling through the main
content), it jumps to a different tab. Two common examples of
applications affected by this nuisance are Konsole and any kind
of browser (file browser or web browser), where the address bar
is right below the tab bar. Moreover, on macOS, scroll events
do not have an effect on the tab bar widget of the native UI.
Currently, the code makes use of preprocessor directives to
achieve consistent behavior on macOS (`#ifndef Q_OS_MAC`). This
patch implements the check of a StyleHint in order to determine
if scroll events on the tabbar should have an effect. This
approach is more consistent with Qt coding style than
OS-dependent preprocessor directives and, in addition, makes
the behavior configurable according to the user's preferences.

[ChangeLog][QtWidgets][QStyle] Added
SH_TabBar_AllowWheelScrolling as a style hint to enable/disable
cycling through tabs using the scroll wheel. This defaults to
true in all styles except the macOS one so there is no change in
existing behavior.

Change-Id: I99eeb5a1aab03cbc574fac7187d85a8a2d60cf34
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
This commit is contained in:
Sophie Kums 2021-01-06 16:34:06 +01:00
parent 65cc6ec16b
commit aa09bea00c
6 changed files with 79 additions and 9 deletions

View File

@ -2902,6 +2902,9 @@ int QMacStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w
case SH_Table_GridLineColor:
ret = int(qt_mac_toQColor(NSColor.gridColor).rgba());
break;
case SH_TabBar_AllowWheelScrolling:
ret = false;
break;
default:
ret = QCommonStyle::styleHint(sh, opt, w, hret);
break;

View File

@ -5407,6 +5407,9 @@ int QCommonStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget
case SH_SpinBox_StepModifier:
ret = Qt::ControlModifier;
break;
case SH_TabBar_AllowWheelScrolling:
ret = true;
break;
default:
ret = 0;
break;

View File

@ -2005,6 +2005,11 @@ void QStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment,
disables this feature.
This enum value has been introduced in Qt 5.12.
\value SH_TabBar_AllowWheelScrolling
Determines if the mouse wheel can be used to cycle through the tabs
of a QTabBar.
This enum value has been introduced in Qt 6.1.
\sa styleHint()
*/

View File

@ -733,6 +733,7 @@ public:
SH_ComboBox_AllowWheelScrolling,
SH_SpinBox_ButtonsInsideFrame,
SH_SpinBox_StepModifier,
SH_TabBar_AllowWheelScrolling,
// Add new style hint values here
SH_CustomBase = 0xf0000000

View File

@ -2363,16 +2363,14 @@ void QTabBar::keyPressEvent(QKeyEvent *event)
#if QT_CONFIG(wheelevent)
void QTabBar::wheelEvent(QWheelEvent *event)
{
#ifndef Q_OS_MAC
Q_D(QTabBar);
int delta = (qAbs(event->angleDelta().x()) > qAbs(event->angleDelta().y()) ?
event->angleDelta().x() : event->angleDelta().y());
int offset = delta > 0 ? -1 : 1;
d->setCurrentNextEnabledIndex(offset);
QWidget::wheelEvent(event);
#else
Q_UNUSED(event);
#endif
if (style()->styleHint(QStyle::SH_TabBar_AllowWheelScrolling)) {
int delta = (qAbs(event->angleDelta().x()) > qAbs(event->angleDelta().y()) ?
event->angleDelta().x() : event->angleDelta().y());
int offset = delta > 0 ? -1 : 1;
d->setCurrentNextEnabledIndex(offset);
QWidget::wheelEvent(event);
}
}
#endif // QT_CONFIG(wheelevent)

View File

@ -33,6 +33,7 @@
#include <QPushButton>
#include <QStyle>
#include <QStyleOptionTab>
#include <QProxyStyle>
#include <QTimer>
class TabBar;
@ -98,6 +99,8 @@ private slots:
void mouseReleaseOutsideTabBar();
void mouseWheel();
private:
void checkPositions(const TabBar &tabbar, const QList<int> &positions);
};
@ -869,5 +872,62 @@ 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)
{ }
int styleHint(StyleHint hint, const QStyleOption *option = 0,
const QWidget *widget = 0, QStyleHintReturn *returnData = 0) const override
{
if (hint == QStyle::SH_TabBar_AllowWheelScrolling)
return scrolling;
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
bool scrolling;
};
void tst_QTabBar::mouseWheel()
{
// 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);
// 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);
// 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);
// disable scrolling, send scroll event, confirm that tab did not change
proxyStyle->scrolling = false;
QVERIFY(applicationInstance->sendEvent(&tabbar, &event));
QVERIFY(tabbar.currentIndex() == startIndex);
// enable scrolling, send scroll event, confirm that tab changed
proxyStyle->scrolling = true;
QVERIFY(applicationInstance->sendEvent(&tabbar, &event));
QVERIFY(tabbar.currentIndex() != startIndex);
}
#endif // QT_CONFIG(wheelevent)
QTEST_MAIN(tst_QTabBar)
#include "tst_qtabbar.moc"