From 9cf2b77328fce9b7663876966112af6ed374df5b Mon Sep 17 00:00:00 2001 From: Denis Dzyubenko Date: Tue, 20 Oct 2009 21:01:56 +0200 Subject: Implemented gesture event delivery and propagation inside QGraphicsView. Reviewed-by: Thomas Zander --- src/gui/graphicsview/qgraphicsscene.cpp | 249 +++++++++++++++++++++++++++++--- src/gui/graphicsview/qgraphicsscene_p.h | 5 + tests/auto/gestures/tst_gestures.cpp | 134 ++++++++++++++--- 3 files changed, 350 insertions(+), 38 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsscene.cpp b/src/gui/graphicsview/qgraphicsscene.cpp index 373ee89..005563e 100644 --- a/src/gui/graphicsview/qgraphicsscene.cpp +++ b/src/gui/graphicsview/qgraphicsscene.cpp @@ -252,6 +252,13 @@ #include #include +// #define GESTURE_DEBUG +#ifndef GESTURE_DEBUG +# define DEBUG if (0) qDebug +#else +# define DEBUG qDebug +#endif + QT_BEGIN_NAMESPACE bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); @@ -5711,28 +5718,234 @@ void QGraphicsScenePrivate::leaveModal(QGraphicsItem *panel) dispatchHoverEvent(&hoverEvent); } +void QGraphicsScenePrivate::getGestureTargets(const QSet &gestures, + QWidget *viewport, + QMap *conflictedGestures, + QList > *conflictedItems, + QHash *normalGestures) +{ + foreach (QGesture *gesture, gestures) { + Qt::GestureType gestureType = gesture->gestureType(); + if (gesture->hasHotSpot()) { + QPoint screenPos = gesture->hotSpot().toPoint(); + QList items = itemsAtPosition(screenPos, QPointF(), viewport); + QList result; + for (int j = 0; j < items.size(); ++j) { + QGraphicsObject *item = items.at(j)->toGraphicsObject(); + if (!item) + continue; + QGraphicsItemPrivate *d = item->QGraphicsItem::d_func(); + if (d->gestureContext.contains(gestureType)) { + result.append(item); + } + } + DEBUG() << "QGraphicsScenePrivate::getGestureTargets:" + << gesture << result; + if (result.size() == 1) { + normalGestures->insert(gesture, result.first()); + } else if (!result.isEmpty()) { + conflictedGestures->insert(gestureType, gesture); + conflictedItems->append(result); + } + } + } +} + void QGraphicsScenePrivate::gestureEventHandler(QGestureEvent *event) { QWidget *viewport = event->widget(); - QList gestures = event->allGestures(); - for (int i = 0; i < gestures.size(); ++i) { - QGesture *gesture = gestures.at(i); - Qt::GestureType gestureType = gesture->gestureType(); - QPoint screenPos = gesture->hotSpot().toPoint(); - QList items = itemsAtPosition(screenPos, QPointF(), viewport); - for (int j = 0; j < items.size(); ++j) { - QGraphicsObject *item = items.at(j)->toGraphicsObject(); - if (!item) - continue; - QGraphicsItemPrivate *d = item->QGraphicsItem::d_func(); - if (d->gestureContext.contains(gestureType)) { - QGestureEvent ev(QList() << gesture); - ev.t = event->t; - ev.spont = event->spont; - ev.setWidget(event->widget()); - sendEvent(item, &ev); - break; + if (!viewport) + return; + QList allGestures = event->allGestures(); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "Delivering gestures:" << allGestures; + + typedef QHash > GesturesPerItem; + GesturesPerItem gesturesPerItem; + + QSet startedGestures; + foreach (QGesture *gesture, allGestures) { + QGraphicsObject *target = gestureTargets.value(gesture, 0); + if (!target) { + // when we are not in started mode but don't have a target + // then the only one interested in gesture is the view/scene + if (gesture->state() == Qt::GestureStarted) + startedGestures.insert(gesture); + } else { + gesturesPerItem[target].append(gesture); + } + } + + QMap conflictedGestures; + QList > conflictedItems; + QHash normalGestures; + getGestureTargets(startedGestures, viewport, &conflictedGestures, &conflictedItems, + &normalGestures); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "Conflicting gestures:" << conflictedGestures.values() << conflictedItems; + Q_ASSERT((conflictedGestures.isEmpty() && conflictedItems.isEmpty()) || + (!conflictedGestures.isEmpty() && !conflictedItems.isEmpty())); + + // gestures that were sent as override events, but no one accepted them + QHash ignoredConflictedGestures; + + // deliver conflicted gestures as override events first + while (!conflictedGestures.isEmpty() && !conflictedItems.isEmpty()) { + // get the topmost item to deliver the override event + Q_ASSERT(!conflictedItems.isEmpty()); + Q_ASSERT(!conflictedItems.first().isEmpty()); + QGraphicsObject *topmost = conflictedItems.first().first(); + for (int i = 1; i < conflictedItems.size(); ++i) { + QGraphicsObject *item = conflictedItems.at(i).first(); + if (qt_closestItemFirst(item, topmost)) { + topmost = item; + } + } + // get a list of gestures to send to the item + QList grabbedGestures = + topmost->QGraphicsItem::d_func()->gestureContext.keys(); + QList gestures; + for (int i = 0; i < grabbedGestures.size(); ++i) { + if (QGesture *g = conflictedGestures.value(grabbedGestures.at(i), 0)) { + gestures.append(g); + if (!ignoredConflictedGestures.contains(g)) + ignoredConflictedGestures.insert(g, topmost); + } + } + + // send gesture override to the topmost item + QGestureEvent ev(gestures); + ev.t = QEvent::GestureOverride; + ev.setWidget(event->widget()); + // mark event and individual gestures as ignored + ev.ignore(); + foreach(QGesture *g, gestures) + ev.setAccepted(g, false); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "delivering override to" + << topmost << gestures; + sendEvent(topmost, &ev); + // mark all accepted gestures to deliver them as normal gesture events + foreach (QGesture *g, gestures) { + if (ev.isAccepted() || ev.isAccepted(g)) { + conflictedGestures.remove(g->gestureType()); + gestureTargets.remove(g); + // add the gesture to the list of normal delivered gestures + normalGestures.insert(g, topmost); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "override was accepted:" + << g << topmost; + ignoredConflictedGestures.remove(g); + } + } + // remove the item that we've already delivered from the list + for (int i = 0; i < conflictedItems.size(); ) { + QList &items = conflictedItems[i]; + if (items.first() == topmost) { + items.removeFirst(); + if (items.isEmpty()) { + conflictedItems.removeAt(i); + continue; + } } + ++i; + } + } + + // put back those started gestures that are not in the conflicted state + // and remember their targets + QHash::const_iterator it = normalGestures.begin(), + e = normalGestures.end(); + for (; it != e; ++it) { + QGesture *g = it.key(); + QGraphicsObject *receiver = it.value(); + Q_ASSERT(!gestureTargets.contains(g)); + gestureTargets.insert(g, receiver); + gesturesPerItem[receiver].append(g); + } + it = ignoredConflictedGestures.begin(); + e = ignoredConflictedGestures.end(); + for (; it != e; ++it) { + QGesture *g = it.key(); + QGraphicsObject *receiver = it.value(); + Q_ASSERT(!gestureTargets.contains(g)); + gestureTargets.insert(g, receiver); + gesturesPerItem[receiver].append(g); + } + + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "Started gestures:" << normalGestures.keys() + << "All gestures:" << gesturesPerItem.values(); + + // deliver all events + QList alreadyIgnoredGestures; + QHash > itemIgnoredGestures; + QList targetItems = gesturesPerItem.keys(); + qSort(targetItems.begin(), targetItems.end(), qt_closestItemFirst); + for (int i = 0; i < targetItems.size(); ++i) { + QGraphicsObject *item = targetItems.at(i); + QList gestures = gesturesPerItem.value(item); + // remove gestures that were already delivered once and were ignored + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "already ignored gestures for item" + << item << ":" << itemIgnoredGestures.value(item); + + if (itemIgnoredGestures.contains(item)) // don't deliver twice to the same item + continue; + + QGraphicsItemPrivate *gid = item->QGraphicsItem::d_func(); + foreach(QGesture *g, alreadyIgnoredGestures) { + if (gid->gestureContext.contains(g->gestureType())) + gestures += g; + } + if (gestures.isEmpty()) + continue; + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "delivering to" + << item << gestures; + QGestureEvent ev(gestures); + ev.setWidget(event->widget()); + sendEvent(item, &ev); + QSet ignoredGestures; + foreach (QGesture *g, gestures) { + if (!ev.isAccepted() && !ev.isAccepted(g)) + ignoredGestures.insert(g); + } + if (!ignoredGestures.isEmpty()) { + // get a list of items under the (current) hotspot of each ignored + // gesture and start delivery again from the beginning + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "item has ignored the event, will propagate." + << item << ignoredGestures; + itemIgnoredGestures[item] += ignoredGestures; + QMap conflictedGestures; + QList > itemsForConflictedGestures; + QHash normalGestures; + getGestureTargets(ignoredGestures, viewport, + &conflictedGestures, &itemsForConflictedGestures, + &normalGestures); + QSet itemsSet = targetItems.toSet(); + for (int k = 0; k < itemsForConflictedGestures.size(); ++k) + itemsSet += itemsForConflictedGestures.at(k).toSet(); + targetItems = itemsSet.toList(); + qSort(targetItems.begin(), targetItems.end(), qt_closestItemFirst); + alreadyIgnoredGestures = conflictedGestures.values(); + DEBUG() << "QGraphicsScenePrivate::gestureEventHandler:" + << "new targets:" << targetItems; + i = -1; // start delivery again + continue; + } + } + + // forget about targets for gestures that have ended + foreach (QGesture *g, allGestures) { + switch (g->state()) { + case Qt::GestureFinished: + case Qt::GestureCanceled: + gestureTargets.remove(g); + break; + default: + break; } } } diff --git a/src/gui/graphicsview/qgraphicsscene_p.h b/src/gui/graphicsview/qgraphicsscene_p.h index 4c82b49..cd20fd0 100644 --- a/src/gui/graphicsview/qgraphicsscene_p.h +++ b/src/gui/graphicsview/qgraphicsscene_p.h @@ -282,7 +282,12 @@ public: bool allItemsIgnoreTouchEvents; void enableTouchEventsOnViews(); + QHash gestureTargets; void gestureEventHandler(QGestureEvent *event); + void getGestureTargets(const QSet &gestures, QWidget *viewport, + QMap *conflictedGestures, + QList > *conflictedItems, + QHash *normalGestures); void updateInputMethodSensitivityInViews(); diff --git a/tests/auto/gestures/tst_gestures.cpp b/tests/auto/gestures/tst_gestures.cpp index 09da7d6..92f979f 100644 --- a/tests/auto/gestures/tst_gestures.cpp +++ b/tests/auto/gestures/tst_gestures.cpp @@ -315,6 +315,7 @@ private slots: void finishedWithoutStarted(); void unknownGesture(); void graphicsItemGesture(); + void graphicsItemTreeGesture(); void explicitGraphicsObjectTarget(); void gestureOverChildGraphicsItem(); void twoGesturesOnDifferentLevel(); @@ -583,11 +584,20 @@ void tst_Gestures::unknownGesture() QCOMPARE(widget.gestureEventsReceived, TotalGestureEventsCount); } +static const QColor InstanceColors[] = { + Qt::blue, Qt::red, Qt::green, Qt::gray, Qt::yellow +}; + class GestureItem : public QGraphicsObject { + static int InstanceCount; + public: - GestureItem() + GestureItem(const char *name = 0) { + instanceNumber = InstanceCount++; + if (name) + setObjectName(QLatin1String(name)); size = QRectF(0, 0, 100, 100); customEventsReceived = 0; gestureEventsReceived = 0; @@ -596,6 +606,10 @@ public: overrideEvents.clear(); acceptGestureOverride = false; } + ~GestureItem() + { + --InstanceCount; + } int customEventsReceived; int gestureEventsReceived; @@ -619,8 +633,10 @@ public: } events, overrideEvents; bool acceptGestureOverride; + QSet ignoredGestures; QRectF size; + int instanceNumber; void reset() { @@ -629,6 +645,7 @@ public: gestureOverrideEventsReceived = 0; events.clear(); overrideEvents.clear(); + ignoredGestures.clear(); } protected: @@ -638,7 +655,8 @@ protected: } void paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) { - p->fillRect(boundingRect(), Qt::blue); + QColor color = InstanceColors[instanceNumber % (sizeof(InstanceColors)/sizeof(InstanceColors[0]))]; + p->fillRect(boundingRect(), color); } bool event(QEvent *event) @@ -647,6 +665,9 @@ protected: if (event->type() == QEvent::Gesture) { ++gestureEventsReceived; eventsPtr = &events; + QGestureEvent *e = static_cast(event); + foreach(Qt::GestureType type, ignoredGestures) + e->ignore(e->gesture(type)); } else if (event->type() == QEvent::GestureOverride) { ++gestureOverrideEventsReceived; eventsPtr = &overrideEvents; @@ -683,13 +704,14 @@ protected: return true; } }; +int GestureItem::InstanceCount = 0; void tst_Gestures::graphicsItemGesture() { QGraphicsScene scene; QGraphicsView view(&scene); - GestureItem *item = new GestureItem; + GestureItem *item = new GestureItem("item"); scene.addItem(item); item->setPos(100, 100); @@ -732,6 +754,75 @@ void tst_Gestures::graphicsItemGesture() QCOMPARE(item->events.updated.size(), TotalGestureEventsCount - 2); QCOMPARE(item->events.finished.size(), 1); QCOMPARE(item->events.canceled.size(), 0); + + item->reset(); + + // send gesture to the item which ignores it. + item->ignoredGestures << CustomGesture::GestureType; + + event.hotSpot = mapToGlobal(QPointF(10, 10), item, &view); + event.hasHotSpot = true; + sendCustomGesture(&event, item, &scene); + QCOMPARE(item->customEventsReceived, TotalCustomEventsCount); + QCOMPARE(item->gestureEventsReceived, TotalGestureEventsCount); + QCOMPARE(item->gestureOverrideEventsReceived, 0); +} + +void tst_Gestures::graphicsItemTreeGesture() +{ + QGraphicsScene scene; + QGraphicsView view(&scene); + + GestureItem *item1 = new GestureItem("item1"); + item1->setPos(100, 100); + item1->size = QRectF(0, 0, 350, 200); + scene.addItem(item1); + + GestureItem *item1_child1 = new GestureItem("item1_child1"); + item1_child1->setPos(50, 50); + item1_child1->size = QRectF(0, 0, 100, 100); + item1_child1->setParentItem(item1); + + GestureItem *item1_child2 = new GestureItem("item1_child2"); + item1_child2->size = QRectF(0, 0, 100, 100); + item1_child2->setPos(200, 50); + item1_child2->setParentItem(item1); + + view.show(); + QTest::qWaitForWindowShown(&view); + view.ensureVisible(scene.sceneRect()); + + view.viewport()->grabGesture(CustomGesture::GestureType, Qt::WidgetGesture); + item1->grabGesture(CustomGesture::GestureType); + + static const int TotalGestureEventsCount = CustomGesture::SerialFinishedThreshold - CustomGesture::SerialStartedThreshold + 1; + + CustomEvent event; + event.hotSpot = mapToGlobal(QPointF(10, 10), item1_child1, &view); + event.hasHotSpot = true; + + item1->ignoredGestures << CustomGesture::GestureType; + sendCustomGesture(&event, item1_child1, &scene); + QCOMPARE(item1_child1->gestureOverrideEventsReceived, 0); + QCOMPARE(item1_child1->gestureEventsReceived, 0); + QCOMPARE(item1_child2->gestureEventsReceived, 0); + QCOMPARE(item1_child2->gestureOverrideEventsReceived, 0); + QCOMPARE(item1->gestureOverrideEventsReceived, 0); + QCOMPARE(item1->gestureEventsReceived, TotalGestureEventsCount); + + item1->reset(); item1_child1->reset(); item1_child2->reset(); + + item1_child1->grabGesture(CustomGesture::GestureType); + + item1->ignoredGestures << CustomGesture::GestureType; + item1_child1->ignoredGestures << CustomGesture::GestureType; + sendCustomGesture(&event, item1_child1, &scene); + QCOMPARE(item1_child1->gestureOverrideEventsReceived, 1); + QCOMPARE(item1_child1->gestureEventsReceived, TotalGestureEventsCount); + QCOMPARE(item1_child2->gestureEventsReceived, 0); + QCOMPARE(item1_child2->gestureOverrideEventsReceived, 0); + QCOMPARE(item1->gestureOverrideEventsReceived, 1); + QCOMPARE(item1->gestureEventsReceived, TotalGestureEventsCount); } void tst_Gestures::explicitGraphicsObjectTarget() @@ -739,15 +830,17 @@ void tst_Gestures::explicitGraphicsObjectTarget() QGraphicsScene scene; QGraphicsView view(&scene); - GestureItem *item1 = new GestureItem; + GestureItem *item1 = new GestureItem("item1"); scene.addItem(item1); item1->setPos(100, 100); + item1->setZValue(1); - GestureItem *item2 = new GestureItem; + GestureItem *item2 = new GestureItem("item2"); scene.addItem(item2); item2->setPos(100, 100); + item2->setZValue(5); - GestureItem *item2_child1 = new GestureItem; + GestureItem *item2_child1 = new GestureItem("item2_child1"); scene.addItem(item2_child1); item2_child1->setParentItem(item2); item2_child1->setPos(10, 10); @@ -771,9 +864,9 @@ void tst_Gestures::explicitGraphicsObjectTarget() sendCustomGesture(&event, item1, &scene); QCOMPARE(item1->gestureEventsReceived, 0); - QCOMPARE(item1->gestureOverrideEventsReceived, 0); + QCOMPARE(item1->gestureOverrideEventsReceived, 1); QCOMPARE(item2_child1->gestureEventsReceived, TotalGestureEventsCount); - QCOMPARE(item2_child1->gestureOverrideEventsReceived, 0); + QCOMPARE(item2_child1->gestureOverrideEventsReceived, 1); QCOMPARE(item2_child1->events.all.size(), TotalGestureEventsCount); for(int i = 0; i < item2_child1->events.all.size(); ++i) QCOMPARE(item2_child1->events.all.at(i), CustomGesture::GestureType); @@ -782,7 +875,7 @@ void tst_Gestures::explicitGraphicsObjectTarget() QCOMPARE(item2_child1->events.finished.size(), 1); QCOMPARE(item2_child1->events.canceled.size(), 0); QCOMPARE(item2->gestureEventsReceived, 0); - QCOMPARE(item2->gestureOverrideEventsReceived, 0); + QCOMPARE(item2->gestureOverrideEventsReceived, 1); } void tst_Gestures::gestureOverChildGraphicsItem() @@ -790,19 +883,23 @@ void tst_Gestures::gestureOverChildGraphicsItem() QGraphicsScene scene; QGraphicsView view(&scene); - GestureItem *item0 = new GestureItem; + GestureItem *item0 = new GestureItem("item0"); scene.addItem(item0); item0->setPos(0, 0); + item0->grabGesture(CustomGesture::GestureType); + item0->setZValue(1); - GestureItem *item1 = new GestureItem; + GestureItem *item1 = new GestureItem("item1"); scene.addItem(item1); item1->setPos(100, 100); + item1->setZValue(5); - GestureItem *item2 = new GestureItem; + GestureItem *item2 = new GestureItem("item2"); scene.addItem(item2); item2->setPos(100, 100); + item2->setZValue(10); - GestureItem *item2_child1 = new GestureItem; + GestureItem *item2_child1 = new GestureItem("item2_child1"); scene.addItem(item2_child1); item2_child1->setParentItem(item2); item2_child1->setPos(0, 0); @@ -827,12 +924,12 @@ void tst_Gestures::gestureOverChildGraphicsItem() QCOMPARE(item2_child1->gestureOverrideEventsReceived, 0); QCOMPARE(item2->gestureEventsReceived, 0); QCOMPARE(item2->gestureOverrideEventsReceived, 0); - QEXPECT_FAIL("", "need to fix gesture event propagation inside graphicsview", Continue); QCOMPARE(item1->gestureEventsReceived, TotalGestureEventsCount); QCOMPARE(item1->gestureOverrideEventsReceived, 0); item0->reset(); item1->reset(); item2->reset(); item2_child1->reset(); item2->grabGesture(CustomGesture::GestureType); + item2->ignoredGestures << CustomGesture::GestureType; event.hotSpot = mapToGlobal(QPointF(10, 10), item2_child1, &view); event.hasHotSpot = true; @@ -841,13 +938,10 @@ void tst_Gestures::gestureOverChildGraphicsItem() QCOMPARE(item0->customEventsReceived, TotalCustomEventsCount); QCOMPARE(item2_child1->gestureEventsReceived, 0); QCOMPARE(item2_child1->gestureOverrideEventsReceived, 0); - QEXPECT_FAIL("", "need to fix gesture event propagation inside graphicsview", Continue); QCOMPARE(item2->gestureEventsReceived, TotalGestureEventsCount); - QEXPECT_FAIL("", "need to fix gesture event propagation inside graphicsview", Continue); - QCOMPARE(item2->gestureOverrideEventsReceived, TotalGestureEventsCount); - QCOMPARE(item1->gestureEventsReceived, 0); - QEXPECT_FAIL("", "need to fix gesture event propagation inside graphicsview", Continue); - QCOMPARE(item1->gestureOverrideEventsReceived, TotalGestureEventsCount); + QCOMPARE(item2->gestureOverrideEventsReceived, 1); + QCOMPARE(item1->gestureEventsReceived, TotalGestureEventsCount); + QCOMPARE(item1->gestureOverrideEventsReceived, 1); } void tst_Gestures::twoGesturesOnDifferentLevel() -- cgit v0.12