QGraphicsScene: Make focusing on touchBegin optional

Usually we focus in when we receive a click or equivalent.
QGraphicsScene by default also transfers the focus when you start a
touch on a trackpad or similar. Most of the time this also generates a
synthetic mouse click, so people don't necessary notice. However, at
least on macOS you can configure this behavior. With focusOnTouch
switched off, QGraphicsScene behaves as one would expect on macOS.

Fixes: QTBUG-59442
Change-Id: Ib87112640eef6b77892ad2490d80eedd055e6dce
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
Reviewed-by: Venugopal Shivashankar <Venugopal.Shivashankar@qt.io>
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
bb10
Ulf Hermann 2018-09-26 17:36:04 +02:00
parent d37d8e962a
commit 21355b3630
4 changed files with 107 additions and 27 deletions

View File

@ -297,6 +297,7 @@ QGraphicsScenePrivate::QGraphicsScenePrivate()
painterStateProtection(true),
sortCacheEnabled(false),
allItemsIgnoreTouchEvents(true),
focusOnTouch(true),
minimumRenderSize(0.0),
selectionChanging(0),
rectAdjust(2),
@ -2393,6 +2394,7 @@ void QGraphicsScene::clear()
d->allItemsIgnoreHoverEvents = true;
d->allItemsUseDefaultCursor = true;
d->allItemsIgnoreTouchEvents = true;
d->focusOnTouch = true;
}
/*!
@ -5854,6 +5856,41 @@ void QGraphicsScene::setMinimumRenderSize(qreal minSize)
update();
}
/*!
\property QGraphicsScene::focusOnTouch
\since 5.12
\brief whether items gain focus when receiving a \e {touch begin} event.
The usual behavior is to transfer focus only when an item is clicked. Often
a tap on a touchpad is interpreted as equivalent to a mouse click by the
operating system, generating a synthesized click event in response. However,
at least on macOS you can configure this behavior.
By default, QGraphicsScene also transfers focus when you touch on a trackpad
or similar. If the operating system is configured to not generate a
synthetic mouse click on tapping the trackpad, this is surprising. If the
operating system does generate synthetic mouse clicks on tapping the
trackpad, the focus transfer on starting a touch gesture is unnecessary.
With focusOnTouch switched off, QGraphicsScene behaves as one would expect
on macOS.
The default value is \c true, ensuring that the default behavior is just as
in Qt versions prior to 5.12. Set to \c false to prevent touch events from
triggering focus changes.
*/
bool QGraphicsScene::focusOnTouch() const
{
Q_D(const QGraphicsScene);
return d->focusOnTouch;
}
void QGraphicsScene::setFocusOnTouch(bool enabled)
{
Q_D(QGraphicsScene);
d->focusOnTouch = enabled;
}
void QGraphicsScenePrivate::addView(QGraphicsView *view)
{
views << view;
@ -6033,39 +6070,41 @@ bool QGraphicsScenePrivate::sendTouchBeginEvent(QGraphicsItem *origin, QTouchEve
{
Q_Q(QGraphicsScene);
if (cachedItemsUnderMouse.isEmpty() || cachedItemsUnderMouse.constFirst() != origin) {
const QTouchEvent::TouchPoint &firstTouchPoint = touchEvent->touchPoints().first();
cachedItemsUnderMouse = itemsAtPosition(firstTouchPoint.screenPos().toPoint(),
firstTouchPoint.scenePos(),
static_cast<QWidget *>(touchEvent->target()));
}
if (focusOnTouch) {
if (cachedItemsUnderMouse.isEmpty() || cachedItemsUnderMouse.constFirst() != origin) {
const QTouchEvent::TouchPoint &firstTouchPoint = touchEvent->touchPoints().first();
cachedItemsUnderMouse = itemsAtPosition(firstTouchPoint.screenPos().toPoint(),
firstTouchPoint.scenePos(),
static_cast<QWidget *>(touchEvent->target()));
}
// Set focus on the topmost enabled item that can take focus.
bool setFocus = false;
// Set focus on the topmost enabled item that can take focus.
bool setFocus = false;
foreach (QGraphicsItem *item, cachedItemsUnderMouse) {
if (item->isEnabled() && ((item->flags() & QGraphicsItem::ItemIsFocusable) && item->d_ptr->mouseSetsFocus)) {
if (!item->isWidget() || ((QGraphicsWidget *)item)->focusPolicy() & Qt::ClickFocus) {
foreach (QGraphicsItem *item, cachedItemsUnderMouse) {
if (item->isEnabled() && ((item->flags() & QGraphicsItem::ItemIsFocusable) && item->d_ptr->mouseSetsFocus)) {
if (!item->isWidget() || ((QGraphicsWidget *)item)->focusPolicy() & Qt::ClickFocus) {
setFocus = true;
if (item != q->focusItem())
q->setFocusItem(item, Qt::MouseFocusReason);
break;
}
}
if (item->isPanel())
break;
if (item->d_ptr->flags & QGraphicsItem::ItemStopsClickFocusPropagation)
break;
if (item->d_ptr->flags & QGraphicsItem::ItemStopsFocusHandling) {
// Make sure we don't clear focus.
setFocus = true;
if (item != q->focusItem())
q->setFocusItem(item, Qt::MouseFocusReason);
break;
}
}
if (item->isPanel())
break;
if (item->d_ptr->flags & QGraphicsItem::ItemStopsClickFocusPropagation)
break;
if (item->d_ptr->flags & QGraphicsItem::ItemStopsFocusHandling) {
// Make sure we don't clear focus.
setFocus = true;
break;
}
}
// If nobody could take focus, clear it.
if (!stickyFocus && !setFocus)
q->setFocusItem(0, Qt::MouseFocusReason);
// If nobody could take focus, clear it.
if (!stickyFocus && !setFocus)
q->setFocusItem(0, Qt::MouseFocusReason);
}
bool res = false;
bool eventAccepted = touchEvent->isAccepted();

View File

@ -106,6 +106,7 @@ class Q_WIDGETS_EXPORT QGraphicsScene : public QObject
Q_PROPERTY(bool sortCacheEnabled READ isSortCacheEnabled WRITE setSortCacheEnabled)
Q_PROPERTY(bool stickyFocus READ stickyFocus WRITE setStickyFocus)
Q_PROPERTY(qreal minimumRenderSize READ minimumRenderSize WRITE setMinimumRenderSize)
Q_PROPERTY(bool focusOnTouch READ focusOnTouch WRITE setFocusOnTouch)
public:
enum ItemIndexMethod {
@ -253,6 +254,9 @@ public:
qreal minimumRenderSize() const;
void setMinimumRenderSize(qreal minSize);
bool focusOnTouch() const;
void setFocusOnTouch(bool enabled);
public Q_SLOTS:
void update(const QRectF &rect = QRectF());
void invalidate(const QRectF &rect = QRectF(), SceneLayers layers = AllLayers);

View File

@ -114,7 +114,8 @@ public:
quint32 painterStateProtection : 1;
quint32 sortCacheEnabled : 1; // for compatibility
quint32 allItemsIgnoreTouchEvents : 1;
quint32 padding : 15;
quint32 focusOnTouch : 1;
quint32 padding : 14;
qreal minimumRenderSize;

View File

@ -254,6 +254,7 @@ private slots:
void zeroScale();
void focusItemChangedSignal();
void minimumRenderSize();
void focusOnTouch();
// task specific tests below me
void task139710_bspTreeCrash();
@ -4758,6 +4759,41 @@ void tst_QGraphicsScene::minimumRenderSize()
QVERIFY(smallChild->repaints > smallerGrandChild->repaints);
}
void tst_QGraphicsScene::focusOnTouch()
{
QGraphicsScene scene;
QGraphicsView view(&scene);
scene.setSceneRect(0, 0, 100, 100);
QGraphicsRectItem *rect = scene.addRect(0, 0, 100, 100);
rect->setFlag(QGraphicsItem::ItemIsFocusable, true);
view.show();
QApplication::setActiveWindow(&view);
QVERIFY(QTest::qWaitForWindowActive(&view));
QVERIFY(!rect->hasFocus());
scene.setFocusOnTouch(false);
QTouchDevice device;
device.setType(QTouchDevice::TouchPad);
QList<QTouchEvent::TouchPoint> touchPoints;
QTouchEvent::TouchPoint point;
point.setScenePos(QPointF(10, 10));
point.setState(Qt::TouchPointPressed);
touchPoints.append(point);
QTouchEvent event(QEvent::TouchBegin, &device, Qt::NoModifier, Qt::TouchPointStates(),
touchPoints);
QApplication::sendEvent(&scene, &event);
QVERIFY(!rect->hasFocus());
scene.setFocusOnTouch(true);
QApplication::sendEvent(&scene, &event);
QVERIFY(rect->hasFocus());
}
void tst_QGraphicsScene::taskQTBUG_15977_renderWithDeviceCoordinateCache()
{
QGraphicsScene scene;