Don't use QCursor::pos in QTabBar and fix hover handling
Relying on QCursor::pos makes tests fragile and prevents multi-seat support. Instead, record the mouse position in the already existing event handling, and use that instead. Styles might use either WA_Hover or enable mouse tracking for the widget to enable hover-effects, so we need to support both. Fix the scenario where a newly inserted tab ends up under the mouse, which was previously not handled correctly (only the case of removing a tab was). Clean up the repaint management when the hovered tab changes; just call update on the old rect, and then later update on the new rect; there's no need to make a copy first, updates are posted and compressed. Add a unit test that makes sure that we paint tabs that should be under the mouse in the hovered state. Since not all styles enable hovering and/or mouse tracking in all cases, use a style sheet for those styles that don't. Change-Id: I7cdbb18e9e04b52651e273680fec87b50cb81e05 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io> Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
This commit is contained in:
parent
c5409964b0
commit
02164b292f
@ -42,8 +42,6 @@
|
||||
#include "qabstractitemdelegate.h"
|
||||
#endif
|
||||
#include "qapplication.h"
|
||||
#include "qbitmap.h"
|
||||
#include "qcursor.h"
|
||||
#include "qevent.h"
|
||||
#include "qpainter.h"
|
||||
#include "qstyle.h"
|
||||
@ -1020,6 +1018,11 @@ int QTabBar::insertTab(int index, const QIcon& icon, const QString &text)
|
||||
++tab->lastTab;
|
||||
}
|
||||
|
||||
if (tabAt(d->mousePosition) == index) {
|
||||
d->hoverIndex = index;
|
||||
d->hoverRect = tabRect(index);
|
||||
}
|
||||
|
||||
tabInserted(index);
|
||||
d->autoHideTabs();
|
||||
return index;
|
||||
@ -1104,16 +1107,15 @@ void QTabBar::removeTab(int index)
|
||||
}
|
||||
d->refresh();
|
||||
d->autoHideTabs();
|
||||
if (!d->hoverRect.isEmpty()) {
|
||||
for (int i = 0; i < d->tabList.count(); ++i) {
|
||||
const QRect area = tabRect(i);
|
||||
if (area.contains(mapFromGlobal(QCursor::pos()))) {
|
||||
d->hoverIndex = i;
|
||||
d->hoverRect = area;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (d->hoverRect.isValid()) {
|
||||
update(d->hoverRect);
|
||||
d->hoverIndex = tabAt(d->mousePosition);
|
||||
if (d->validIndex(d->hoverIndex)) {
|
||||
d->hoverRect = tabRect(d->hoverIndex);
|
||||
update(d->hoverRect);
|
||||
} else {
|
||||
d->hoverRect = QRect();
|
||||
}
|
||||
}
|
||||
tabRemoved(index);
|
||||
}
|
||||
@ -1696,33 +1698,26 @@ bool QTabBar::event(QEvent *event)
|
||||
case QEvent::HoverMove:
|
||||
case QEvent::HoverEnter: {
|
||||
QHoverEvent *he = static_cast<QHoverEvent *>(event);
|
||||
if (!d->hoverRect.contains(he->position().toPoint())) {
|
||||
QRect oldHoverRect = d->hoverRect;
|
||||
bool cursorOverTabs = false;
|
||||
for (int i = 0; i < d->tabList.count(); ++i) {
|
||||
QRect area = tabRect(i);
|
||||
if (area.contains(he->position().toPoint())) {
|
||||
d->hoverIndex = i;
|
||||
d->hoverRect = area;
|
||||
cursorOverTabs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!cursorOverTabs) {
|
||||
d->hoverIndex = -1;
|
||||
d->mousePosition = he->position().toPoint();
|
||||
if (!d->hoverRect.contains(d->mousePosition)) {
|
||||
if (d->hoverRect.isValid())
|
||||
update(d->hoverRect);
|
||||
d->hoverIndex = tabAt(d->mousePosition);
|
||||
if (d->validIndex(d->hoverIndex)) {
|
||||
d->hoverRect = tabRect(d->hoverIndex);
|
||||
update(d->hoverRect);
|
||||
} else {
|
||||
d->hoverRect = QRect();
|
||||
}
|
||||
if (he->oldPos() != QPoint(-1, -1))
|
||||
update(oldHoverRect);
|
||||
update(d->hoverRect);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case QEvent::HoverLeave: {
|
||||
QRect oldHoverRect = d->hoverRect;
|
||||
d->mousePosition = {-1, -1};
|
||||
if (d->hoverRect.isValid())
|
||||
update(d->hoverRect);
|
||||
d->hoverIndex = -1;
|
||||
d->hoverRect = QRect();
|
||||
update(oldHoverRect);
|
||||
return true;
|
||||
}
|
||||
#if QT_CONFIG(tooltip)
|
||||
@ -1796,6 +1791,7 @@ bool QTabBar::event(QEvent *event)
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::MouseButtonRelease:
|
||||
case QEvent::MouseMove:
|
||||
d->mousePosition = static_cast<QMouseEvent *>(event)->position().toPoint();
|
||||
d->mouseButtons = static_cast<QMouseEvent *>(event)->buttons();
|
||||
break;
|
||||
default:
|
||||
|
@ -101,6 +101,7 @@ public:
|
||||
|
||||
QRect hoverRect;
|
||||
QPoint dragStartPosition;
|
||||
QPoint mousePosition = {-1, -1};
|
||||
QSize iconSize;
|
||||
QToolButton* rightB = nullptr; // right or bottom
|
||||
QToolButton* leftB = nullptr; // left or top
|
||||
|
@ -107,6 +107,9 @@ private slots:
|
||||
|
||||
void currentTabLargeFont();
|
||||
|
||||
void hoverTab_data();
|
||||
void hoverTab();
|
||||
|
||||
private:
|
||||
void checkPositions(const TabBar &tabbar, const QList<int> &positions);
|
||||
};
|
||||
@ -1033,5 +1036,86 @@ void tst_QTabBar::currentTabLargeFont()
|
||||
QVERIFY(oldTabRects != newTabRects);
|
||||
}
|
||||
|
||||
void tst_QTabBar::hoverTab_data()
|
||||
{
|
||||
// Since we still rely on moving the mouse via QCursor::setPos in QTest::mouseMove,
|
||||
// skip this test if we can't
|
||||
const QPoint cursorPos = QCursor::pos() + 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 window;
|
||||
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(&window);
|
||||
|
||||
tabbar.setDocumentMode(documentMode);
|
||||
tabbar.addTab("A");
|
||||
tabbar.addTab("B");
|
||||
tabbar.addTab("C");
|
||||
tabbar.addTab("D");
|
||||
|
||||
tabbar.move(0,0);
|
||||
window.setMinimumSize(tabbar.sizeHint());
|
||||
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; }
|
||||
)");
|
||||
}
|
||||
|
||||
window.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
||||
|
||||
QTest::mouseMove(&tabbar, tabbar.tabRect(0).center());
|
||||
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);
|
||||
|
||||
QTest::mouseMove(&tabbar, tabbar.tabRect(1).center());
|
||||
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);
|
||||
|
||||
QTest::mouseMove(&tabbar, tabbar.tabRect(2).center());
|
||||
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);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QTabBar)
|
||||
#include "tst_qtabbar.moc"
|
||||
|
Loading…
Reference in New Issue
Block a user