From 790583dc16a89ed77954c096aae52bf9f91aec09 Mon Sep 17 00:00:00 2001 From: "Bradley T. Hughes" Date: Thu, 1 Oct 2009 09:21:02 +0200 Subject: Add support for modality to QGraphicsItem panels. This add QGraphicsItem::setPanelModality(), panelModality(), isBlockedByModalPanel() and the QGraphicsItem::PanelModality enum (enumerators are either SceneModal, PanelModal, or NonModal, which mirror Qt::ApplicationModal, Qt::WindowModal, and Qt::NonModal). Reviewed-by: ahanssen Squashed commit of the following: commit a980a1b9c2972c676f3a70e8577d4eace54a25b3 Author: Bradley T. Hughes Date: Wed Sep 30 14:42:01 2009 +0200 Fix tst_QGraphicsItem::modality_hover() test failures As pointed out by Andreas, QGraphicsScenePrivate::dispatchHoverEvent() already has all the logic needed to correctly dispatch the hover events when changing modality. All we need to do is store the last known mouse position, and call dispatchHoverEvent() when entering, changing, and leaving the modal state. commit ba41633c96ece8da3a8bbf9c7491c15b14f83f76 Author: Bradley T. Hughes Date: Wed Sep 30 14:40:59 2009 +0200 Fix tst_QGraphicsItem::mixedModality() failure When changing modality from SceneModal to PanelModal, we may need to send WindowUnblocked events in addition to the WindowBlocked events. commit d1076e315de10b1b2fb7617ebaee552c14e49c8b Author: Bradley T. Hughes Date: Wed Sep 30 14:33:21 2009 +0200 Update the expected events counts in tst_QGraphicsItem::modality_hover() HoverEnter and HoverMove always come in pairs, and should do so when entering or leaving modality as well. This means though that changing modality can cause spurious HoverMove events (as seen in this test). commit a29b098e4c391651ef61dd4714a66b22654e4628 Author: Bradley T. Hughes Date: Wed Sep 30 14:20:25 2009 +0200 Update tst_QGraphicsItem::mixedModality() to do more detailed checking The test looks for unwanted WindowBlocked/WindowUnblocked events now, making it easier to spot where the failure comes from. commit c1ae96126ed01d0e662bddf38ff161e50a804b1c Author: Bradley T. Hughes Date: Tue Sep 29 12:17:17 2009 +0200 Documentation for QGraphicsItem::PanelModality Document the behavior of the QGraphicsItem::NonModal, PanelModal, and SceneModal enumerators. Corrected a qdoc error in the isBlockedByModalPanel() documentation commit 02fec999e660180ff65bbbf79c8085e582879ed1 Author: Bradley T. Hughes Date: Thu Sep 17 10:31:44 2009 +0200 Add bool QGraphicsItem::isBlockedByModalPanel() This function can be used to figure out 1) if an item is modally shadowed and 2) which panel is blocking the item in question. This will make it possible to implement window-manager like behavior of activating/highlighting/animating modal panels when the user tries to interact with a blocked item (this will most likely need to be done by reimplementing QGraphicsScene::event()). commit eab9a975dcd71b68135325d479374108bd7f3b2a Author: Bradley T. Hughes Date: Wed Sep 16 14:38:55 2009 +0200 Block key events to the focus item if it is blocked by a modal panel. We don't want the event to propagate either, just stop propagating once we reach an item that is blocked. commit 038b61a10bb837b353f988cb0d1665dd53656cdb Author: Bradley T. Hughes Date: Tue Sep 15 12:40:27 2009 +0200 Add a test for click-to-focus behavior in the presence of modality Clicking on a widget should neither give it focus nor set it as the sub-focus item. commit 3bb3662556efe8d76af5a56e65b1df7a9f4b476a Author: Bradley T. Hughes Date: Tue Sep 8 09:08:42 2009 +0200 Newly blocked QGraphicsItem panels should lose implicit grabs when modality is enabled. If an item has an implicit grab when blocked by a modal panel, this grab is lost and no mouse events are sent until the mouse is released and re- commit 3be51be3da36e782a5a1f282c552064d5d490a71 Author: Bradley T. Hughes Date: Thu Sep 3 13:39:14 2009 +0200 Support changing modality from PanelModal to SceneModal or vice-versa Don't leave modality first, and then re-enter... this sends unnecessary events. commit bb0aea559ba01a8bbb03c0370a247ab902f561f5 Author: Bradley T. Hughes Date: Wed Sep 2 16:58:23 2009 +0200 Fix hover event delivery in the presence of modal panels Panels that are modally shadowed should not get hover events. When entering and leaving the modal state, GraphicsSceneHoverEnter and GraphicsSceneHoverLeave event should be send to the widgets that become or are no longer blocked. Auto-test included. commit cad00b1d9da19565e2d7ea2d30d37eb45005b5ae Author: Bradley T. Hughes Date: Wed Sep 2 10:45:20 2009 +0200 Fix tst_QGraphicsItem::modality_hover() test Don't send hover events to items that are modally shadowed. commit ae15df331901110e19eb2037f37ff7f84cd7cd16 Author: Bradley T. Hughes Date: Wed Sep 2 10:44:14 2009 +0200 Enable the ItemIsPanel flag in the modality_hover() test Otherwise the modality settings are ignored. commit 1580f43c8feabc3a2bf9c1450e1a8916e8940a4c Author: Andreas Aardal Hanssen Date: Fri Aug 28 15:20:37 2009 +0200 More work on QGraphicsItem::PanelModality. Added many tests. commit ed2064ad2ec8bc06d62cf1e931b973d5d92c0563 Author: Bradley T. Hughes Date: Thu Aug 27 13:45:11 2009 +0200 Add support for modality to QGraphicsItem panels. This add QGraphicsItem::setPanelModality() and the QGraphicsItem::PanelModality enum (enumerators are either SceneModal, PanelModal, or NonModal, which mirror Qt::ApplicationModal, Qt::WindowModal, and Qt::NonModal). Reviewed-by: ahanssen --- src/gui/graphicsview/qgraphicsitem.cpp | 139 +++- src/gui/graphicsview/qgraphicsitem.h | 11 + src/gui/graphicsview/qgraphicsitem_p.h | 2 + src/gui/graphicsview/qgraphicsscene.cpp | 164 +++- src/gui/graphicsview/qgraphicsscene_p.h | 6 + tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp | 1017 +++++++++++++++++++++++- 6 files changed, 1314 insertions(+), 25 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsitem.cpp b/src/gui/graphicsview/qgraphicsitem.cpp index d7a7bd2..bc9c73f 100644 --- a/src/gui/graphicsview/qgraphicsitem.cpp +++ b/src/gui/graphicsview/qgraphicsitem.cpp @@ -564,6 +564,21 @@ supportsExtension() and setExtension(). */ +/*! + \enum QGraphicsItem::PanelModality + + This enum specifies the behavior of a modal panel. A modal panel + is one that blocks input to other panels. Note that items that + are children of a modal panel are not blocked. + + The values are: + \value NonModal The panel is not modal and does not block input to other panels. + \value PanelModal The panel is modal to a single item hierarchy and blocks input to its parent pane, all grandparent panels, and all siblings of its parent and grandparent panels. + \value SceneModal The window is modal to the entire scene and blocks input to all panels. + + \sa QGraphicsItem::setPanelModality(), QGraphicsItem::panelModality(), QGraphicsItem::ItemIsPanel +*/ + #include "qgraphicsitem.h" #ifndef QT_NO_GRAPHICSVIEW @@ -1649,6 +1664,16 @@ void QGraphicsItem::setFlags(GraphicsItemFlags flags) setFlag(ItemStacksBehindParent, d_ptr->z < qreal(0.0)); } + if ((d_ptr->panelModality != NonModal) + && d_ptr->scene + && (flags & ItemIsPanel) != (oldFlags & ItemIsPanel)) { + // update the panel's modal state + if (flags & ItemIsPanel) + d_ptr->scene->d_func()->enterModal(this); + else + d_ptr->scene->d_func()->leaveModal(this); + } + if (d_ptr->scene) { d_ptr->scene->d_func()->markDirty(this, QRectF(), /*invalidateChildren=*/true, @@ -1725,6 +1750,87 @@ void QGraphicsItem::setCacheMode(CacheMode mode, const QSize &logicalCacheSize) update(); } +/*! + \since 4.6 + + Returns the modality for this item. +*/ +QGraphicsItem::PanelModality QGraphicsItem::panelModality() const +{ + return d_ptr->panelModality; +} + +/*! + \since 4.6 + + Sets the modality for this item to \a panelModality. + + Changing the modality of a visible item takes effect immediately. +*/ +void QGraphicsItem::setPanelModality(PanelModality panelModality) +{ + if (d_ptr->panelModality == panelModality) + return; + + PanelModality previousModality = d_ptr->panelModality; + bool enterLeaveModal = (isPanel() && d_ptr->scene && isVisible()); + if (enterLeaveModal && panelModality == NonModal) + d_ptr->scene->d_func()->leaveModal(this); + d_ptr->panelModality = panelModality; + if (enterLeaveModal && d_ptr->panelModality != NonModal) + d_ptr->scene->d_func()->enterModal(this, previousModality); +} + +/*! + \since 4.6 + + Returns true if this item is blocked by a modal panel, false otherwise. If \a blockingPanel is + non-zero, \a blockingPanel will be set to the modal panel that is blocking this item. If this + item is not blocked, \a blockingPanel will not be set by this function. + + This function always returns false for items not in a scene. + + \sa panelModality() setPanelModality() PanelModality +*/ +bool QGraphicsItem::isBlockedByModalPanel(QGraphicsItem **blockingPanel) const +{ + if (!d_ptr->scene) + return false; + + + QGraphicsItem *dummy = 0; + if (!blockingPanel) + blockingPanel = &dummy; + + QGraphicsScenePrivate *scene_d = d_ptr->scene->d_func(); + if (scene_d->modalPanels.isEmpty()) + return false; + + // ### + if (!scene_d->popupWidgets.isEmpty() && scene_d->popupWidgets.first() == this) + return false; + + for (int i = 0; i < scene_d->modalPanels.count(); ++i) { + QGraphicsItem *modalPanel = scene_d->modalPanels.at(i); + if (modalPanel->panelModality() == QGraphicsItem::SceneModal) { + // Scene modal panels block all non-descendents. + if (modalPanel != this && !modalPanel->isAncestorOf(this)) { + *blockingPanel = modalPanel; + return true; + } + } else { + // Window modal panels block ancestors and siblings/cousins. + if (modalPanel != this + && !modalPanel->isAncestorOf(this) + && commonAncestorItem(modalPanel)) { + *blockingPanel = modalPanel; + return true; + } + } + } + return false; +} + #ifndef QT_NO_TOOLTIP /*! Returns the item's tool tip, or an empty QString if no tool tip has been @@ -1934,6 +2040,8 @@ void QGraphicsItemPrivate::setVisibleHelper(bool newVisible, bool explicitly, bo q->ungrabMouse(); if (scene->d_func()->keyboardGrabberItems.contains(q)) q->ungrabKeyboard(); + if (q->isPanel() && panelModality != QGraphicsItem::NonModal) + scene->d_func()->leaveModal(q_ptr); } if (q_ptr->hasFocus() && scene) { // Hiding the closest non-panel ancestor of the focus item @@ -1955,10 +2063,15 @@ void QGraphicsItemPrivate::setVisibleHelper(bool newVisible, bool explicitly, bo } else { geometryChanged = 1; paintedViewBoundingRectsNeedRepaint = 1; - if (isWidget && scene) { - QGraphicsWidget *widget = static_cast(q_ptr); - if (widget->windowType() == Qt::Popup) - scene->d_func()->addPopup(widget); + if (scene) { + if (isWidget) { + QGraphicsWidget *widget = static_cast(q_ptr); + if (widget->windowType() == Qt::Popup) + scene->d_func()->addPopup(widget); + } + if (q->isPanel() && panelModality != QGraphicsItem::NonModal) { + scene->d_func()->enterModal(q_ptr); + } } } @@ -2781,7 +2894,7 @@ bool QGraphicsItem::hasFocus() const the preferred focus item for its subtree of items, should it later become visible. - As a result of calling this function, this item will receive a + As a result of calling this function, this item will receive a \l{focusInEvent()}{focus in event} with \a focusReason. If another item already has focus, that item will first receive a \l{focusOutEvent()} {focus out event} indicating that it has lost input focus. @@ -3299,7 +3412,7 @@ QMatrix QGraphicsItem::matrix() const The transformation matrix is combined with the item's rotation(), scale() and transformations() into a combined transformations for the item. - + The default transformation matrix is an identity matrix. \sa setTransform(), sceneTransform() @@ -3780,7 +3893,7 @@ void QGraphicsItem::setMatrix(const QMatrix &matrix, bool combine) // Update and set the new transformation. d_ptr->setTransformHelper(newTransform); - + // Send post-notification. itemChange(ItemTransformHasChanged, qVariantFromValue(newTransform)); } @@ -3914,7 +4027,7 @@ void QGraphicsItem::scale(qreal sx, qreal sy) /*! \obsolete - Use + Use \code setTransform(QTransform().shear(sh, sv), true); @@ -3936,7 +4049,7 @@ void QGraphicsItem::shear(qreal sh, qreal sv) Use setPos() or setTransformOriginPoint() instead. For identical behavior, use - + \code setTransform(QTransform::fromTranslate(dx, dy), true); \endcode @@ -4446,7 +4559,7 @@ bool QGraphicsItem::collidesWithItem(const QGraphicsItem *other, Qt::ItemSelecti Note that this function checks whether the item's shape or bounding rectangle (depending on \a mode) is contained within \a path, and not whether \a path is contained within the items shape - or bounding rectangle. + or bounding rectangle. \sa collidesWithItem(), contains(), shape() */ @@ -6595,7 +6708,7 @@ void QGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event) if (d_ptr->isWidget) { // Qt::Popup closes when you click outside. QGraphicsWidget *w = static_cast(this); - if (w->windowFlags() & Qt::Popup) { + if ((w->windowFlags() & Qt::Popup) == Qt::Popup) { event->accept(); if (!w->rect().contains(event->pos())) w->close(); @@ -7020,7 +7133,7 @@ void QGraphicsItem::prepareGeometryChange() // if someone is connected to the changed signal or the scene has no views. // Note that this has to be done *after* markDirty to ensure that // _q_processDirtyItems is called before _q_emitUpdated. - if (scenePrivate->isSignalConnected(scenePrivate->changedSignalIndex) + if (scenePrivate->isSignalConnected(scenePrivate->changedSignalIndex) || scenePrivate->views.isEmpty()) { if (d_ptr->hasTranslateOnlySceneTransform()) { d_ptr->scene->update(boundingRect().translated(d_ptr->sceneTransform.dx(), @@ -10524,7 +10637,7 @@ QPixmap QGraphicsItemEffectSourcePrivate::pixmap(Qt::CoordinateSystem system, QP effectRect.setRight(deviceWidth - 1); if (bottom + 1 > deviceHeight) effectRect.setBottom(deviceHeight -1); - + } if (effectRect.isEmpty()) diff --git a/src/gui/graphicsview/qgraphicsitem.h b/src/gui/graphicsview/qgraphicsitem.h index 99d2e12..e6e324a 100644 --- a/src/gui/graphicsview/qgraphicsitem.h +++ b/src/gui/graphicsview/qgraphicsitem.h @@ -146,6 +146,13 @@ public: DeviceCoordinateCache }; + enum PanelModality + { + NonModal, + PanelModal, + SceneModal + }; + QGraphicsItem(QGraphicsItem *parent = 0 #ifndef Q_QDOC // ### obsolete argument @@ -183,6 +190,10 @@ public: CacheMode cacheMode() const; void setCacheMode(CacheMode mode, const QSize &cacheSize = QSize()); + PanelModality panelModality() const; + void setPanelModality(PanelModality panelModality); + bool isBlockedByModalPanel(QGraphicsItem **blockingPanel = 0) const; + #ifndef QT_NO_TOOLTIP QString toolTip() const; void setToolTip(const QString &toolTip); diff --git a/src/gui/graphicsview/qgraphicsitem_p.h b/src/gui/graphicsview/qgraphicsitem_p.h index 3feccdc..51bfea1 100644 --- a/src/gui/graphicsview/qgraphicsitem_p.h +++ b/src/gui/graphicsview/qgraphicsitem_p.h @@ -131,6 +131,7 @@ public: subFocusItem(0), focusScopeItem(0), imHints(Qt::ImhNone), + panelModality(QGraphicsItem::NonModal), acceptedMouseButtons(0x1f), visible(1), explicitlyHidden(0), @@ -448,6 +449,7 @@ public: QGraphicsItem *subFocusItem; QGraphicsItem *focusScopeItem; Qt::InputMethodHints imHints; + QGraphicsItem::PanelModality panelModality; // Packed 32 bits quint32 acceptedMouseButtons : 5; diff --git a/src/gui/graphicsview/qgraphicsscene.cpp b/src/gui/graphicsview/qgraphicsscene.cpp index 4b74b67..0acef0b 100644 --- a/src/gui/graphicsview/qgraphicsscene.cpp +++ b/src/gui/graphicsview/qgraphicsscene.cpp @@ -565,6 +565,9 @@ void QGraphicsScenePrivate::removeItemHelper(QGraphicsItem *item) q->removeItem(item->d_ptr->children.at(i)); } + if (item->isPanel() && item->isVisible() && item->panelModality() != QGraphicsItem::NonModal) + leaveModal(item); + // Reset the mouse grabber and focus item data. if (mouseGrabberItems.contains(item)) ungrabMouse(item, /* item is dying */ item->d_ptr->inDestructor); @@ -1111,6 +1114,9 @@ void QGraphicsScenePrivate::sendMouseEvent(QGraphicsSceneMouseEvent *mouseEvent) } QGraphicsItem *item = mouseGrabberItems.last(); + if (item->isBlockedByModalPanel()) + return; + for (int i = 0x1; i <= 0x10; i <<= 1) { Qt::MouseButton button = Qt::MouseButton(i); mouseEvent->setButtonDownPos(button, mouseGrabberButtonDownPos.value(button, item->d_ptr->genericMapFromScene(mouseEvent->scenePos(), mouseEvent->widget()))); @@ -1134,6 +1140,8 @@ void QGraphicsScenePrivate::mousePressEventHandler(QGraphicsSceneMouseEvent *mou // Deliver to any existing mouse grabber. if (!mouseGrabberItems.isEmpty()) { + if (mouseGrabberItems.last()->isBlockedByModalPanel()) + return; // The event is ignored by default, but we disregard the event's // accepted state after delivery; the mouse is grabbed, after all. sendMouseEvent(mouseEvent); @@ -1151,12 +1159,22 @@ void QGraphicsScenePrivate::mousePressEventHandler(QGraphicsSceneMouseEvent *mou // Update window activation. QGraphicsItem *topItem = cachedItemsUnderMouse.value(0); QGraphicsWidget *newActiveWindow = topItem ? topItem->window() : 0; + if (newActiveWindow && newActiveWindow->isBlockedByModalPanel(&topItem)) { + // pass activation to the blocking modal window + newActiveWindow = topItem ? topItem->window() : 0; + } + if (newActiveWindow != q->activeWindow()) q->setActiveWindow(newActiveWindow); // Set focus on the topmost enabled item that can take focus. bool setFocus = false; foreach (QGraphicsItem *item, cachedItemsUnderMouse) { + if (item->isBlockedByModalPanel()) { + // Make sure we don't clear focus. + setFocus = true; + break; + } if (item->isEnabled() && ((item->flags() & QGraphicsItem::ItemIsFocusable) && item->d_ptr->mouseSetsFocus)) { if (!item->isWidget() || ((QGraphicsWidget *)item)->focusPolicy() & Qt::ClickFocus) { setFocus = true; @@ -1165,12 +1183,27 @@ void QGraphicsScenePrivate::mousePressEventHandler(QGraphicsSceneMouseEvent *mou break; } } + if (item->isPanel()) + break; + } + + // Check for scene modality. + bool sceneModality = false; + for (int i = 0; i < modalPanels.size(); ++i) { + if (modalPanels.at(i)->panelModality() == QGraphicsItem::SceneModal) { + sceneModality = true; + break; + } } // If nobody could take focus, clear it. - if (!stickyFocus && !setFocus) + if (!stickyFocus && !setFocus && !sceneModality) q->setFocusItem(0, Qt::MouseFocusReason); + // Any item will do. + if (sceneModality && cachedItemsUnderMouse.isEmpty()) + cachedItemsUnderMouse << modalPanels.first(); + // Find a mouse grabber by sending mouse press events to all mouse grabber // candidates one at a time, until the event is accepted. It's accepted by // default, so the receiver has to explicitly ignore it for it to pass @@ -1181,6 +1214,10 @@ void QGraphicsScenePrivate::mousePressEventHandler(QGraphicsSceneMouseEvent *mou continue; } + // Check if this item is blocked by a modal panel and deliver the mouse event to the + // blocking panel instead of this item if blocked. + (void) item->isBlockedByModalPanel(&item); + grabMouse(item, /* implicit = */ true); mouseEvent->accept(); @@ -2403,6 +2440,8 @@ void QGraphicsScene::addItem(QGraphicsItem *item) d->selectedItems << item; if (item->isWidget() && item->isVisible() && static_cast(item)->windowType() == Qt::Popup) d->addPopup(static_cast(item)); + if (item->isPanel() && item->isVisible() && item->panelModality() != QGraphicsItem::NonModal) + d->enterModal(item); // Update creation order focus chain. Make sure to leave the widget's // internal tab order intact. @@ -3205,8 +3244,12 @@ bool QGraphicsScene::event(QEvent *event) } return false; case QEvent::GraphicsSceneMouseMove: - mouseMoveEvent(static_cast(event)); + { + QGraphicsSceneMouseEvent *mouseEvent = static_cast(event); + d->lastSceneMousePos = mouseEvent->scenePos(); + mouseMoveEvent(mouseEvent); break; + } case QEvent::GraphicsSceneMousePress: mousePressEvent(static_cast(event)); break; @@ -3228,8 +3271,12 @@ bool QGraphicsScene::event(QEvent *event) case QEvent::GraphicsSceneHoverEnter: case QEvent::GraphicsSceneHoverLeave: case QEvent::GraphicsSceneHoverMove: - d->dispatchHoverEvent(static_cast(event)); + { + QGraphicsSceneHoverEvent *hoverEvent = static_cast(event); + d->lastSceneMousePos = hoverEvent->scenePos(); + d->dispatchHoverEvent(hoverEvent); break; + } case QEvent::Leave: d->leaveScene(); break; @@ -3599,8 +3646,10 @@ void QGraphicsScene::helpEvent(QGraphicsSceneHelpEvent *helpEvent) bool QGraphicsScenePrivate::itemAcceptsHoverEvents_helper(const QGraphicsItem *item) const { - return item->acceptHoverEvents() - || (item->isWidget() && static_cast(item)->d_func()->hasDecoration()); + return (!item->isBlockedByModalPanel() && + (item->acceptHoverEvents() + || (item->isWidget() + && static_cast(item)->d_func()->hasDecoration()))); } /*! @@ -3674,7 +3723,9 @@ bool QGraphicsScenePrivate::dispatchHoverEvent(QGraphicsSceneHoverEvent *hoverEv } // Generate a move event for the item itself - if (item && !hoverItems.isEmpty() && item == hoverItems.last()) { + if (item + && !hoverItems.isEmpty() + && item == hoverItems.last()) { sendHoverEvent(QEvent::GraphicsSceneHoverMove, item, hoverEvent); return true; } @@ -3708,8 +3759,7 @@ void QGraphicsScenePrivate::leaveScene() while (!hoverItems.isEmpty()) { QGraphicsItem *lastItem = hoverItems.takeLast(); - if (lastItem->acceptHoverEvents() - || (lastItem->isWidget() && static_cast(lastItem)->d_func()->hasDecoration())) + if (itemAcceptsHoverEvents_helper(lastItem)) sendHoverEvent(QEvent::GraphicsSceneHoverLeave, lastItem, &hoverEvent); } } @@ -3736,6 +3786,8 @@ void QGraphicsScene::keyPressEvent(QKeyEvent *keyEvent) keyEvent->accept(); // Send it; QGraphicsItem::keyPressEvent ignores it. If the event // is filtered out, stop propagating it. + if (p->isBlockedByModalPanel()) + break; if (!d->sendEvent(p, keyEvent)) break; } while (!keyEvent->isAccepted() && !p->isPanel() && (p = p->parentItem())); @@ -3766,6 +3818,8 @@ void QGraphicsScene::keyReleaseEvent(QKeyEvent *keyEvent) keyEvent->accept(); // Send it; QGraphicsItem::keyPressEvent ignores it. If the event // is filtered out, stop propagating it. + if (p->isBlockedByModalPanel()) + break; if (!d->sendEvent(p, keyEvent)) break; } while (!keyEvent->isAccepted() && !p->isPanel() && (p = p->parentItem())); @@ -5420,6 +5474,8 @@ void QGraphicsScenePrivate::touchEventHandler(QTouchEvent *sceneTouchEvent) for (; it != end; ++it) { QGraphicsItem *item = it.key(); + (void) item->isBlockedByModalPanel(&item); + // determine event type from the state mask QEvent::Type eventType; switch (it.value().first) { @@ -5493,6 +5549,8 @@ bool QGraphicsScenePrivate::sendTouchBeginEvent(QGraphicsItem *origin, QTouchEve break; } } + if (item->isPanel()) + break; } // If nobody could take focus, clear it. @@ -5518,6 +5576,8 @@ bool QGraphicsScenePrivate::sendTouchBeginEvent(QGraphicsItem *origin, QTouchEve } break; } + if (item->isPanel()) + break; } touchEvent->setAccepted(eventAccepted); @@ -5536,6 +5596,94 @@ void QGraphicsScenePrivate::updateInputMethodSensitivityInViews() views.at(i)->d_func()->updateInputMethodSensitivity(); } +void QGraphicsScenePrivate::enterModal(QGraphicsItem *panel, QGraphicsItem::PanelModality previousModality) +{ + Q_Q(QGraphicsScene); + Q_ASSERT(panel && panel->isPanel()); + + QGraphicsItem::PanelModality panelModality = panel->d_ptr->panelModality; + if (previousModality != QGraphicsItem::NonModal) { + // the panel is changing from one modality type to another... temporarily set it back so + // that blockedPanels is populated correctly + panel->d_ptr->panelModality = previousModality; + } + + QSet blockedPanels; + QList items = q->items(); // ### store panels separately + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && item->isBlockedByModalPanel()) + blockedPanels.insert(item); + } + // blockedPanels contains all currently blocked panels + + if (previousModality != QGraphicsItem::NonModal) { + // reset the modality to the proper value, since we changed it above + panel->d_ptr->panelModality = panelModality; + // remove this panel so that it will be reinserted at the front of the stack + modalPanels.removeAll(panel); + } + + modalPanels.prepend(panel); + + if (!hoverItems.isEmpty()) { + // send GraphicsSceneHoverLeave events to newly blocked hoverItems + QGraphicsSceneHoverEvent hoverEvent; + hoverEvent.setScenePos(lastSceneMousePos); + dispatchHoverEvent(&hoverEvent); + } + + if (!mouseGrabberItems.isEmpty() && lastMouseGrabberItemHasImplicitMouseGrab) { + QGraphicsItem *item = mouseGrabberItems.last(); + if (item->isBlockedByModalPanel()) + ungrabMouse(item, /*itemIsDying =*/ false); + } + + QEvent windowBlockedEvent(QEvent::WindowBlocked); + QEvent windowUnblockedEvent(QEvent::WindowUnblocked); + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel()) { + if (!blockedPanels.contains(item) && item->isBlockedByModalPanel()) { + // send QEvent::WindowBlocked to newly blocked panels + sendEvent(item, &windowBlockedEvent); + } else if (blockedPanels.contains(item) && !item->isBlockedByModalPanel()) { + // send QEvent::WindowUnblocked to unblocked panels when downgrading + // a panel from SceneModal to PanelModal + sendEvent(item, &windowUnblockedEvent); + } + } + } +} + +void QGraphicsScenePrivate::leaveModal(QGraphicsItem *panel) +{ + Q_Q(QGraphicsScene); + Q_ASSERT(panel && panel->isPanel()); + + QSet blockedPanels; + QList items = q->items(); // ### same as above + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && item->isBlockedByModalPanel()) + blockedPanels.insert(item); + } + + modalPanels.removeAll(panel); + + QEvent e(QEvent::WindowUnblocked); + for (int i = 0; i < items.count(); ++i) { + QGraphicsItem *item = items.at(i); + if (item->isPanel() && blockedPanels.contains(item) && !item->isBlockedByModalPanel()) + sendEvent(item, &e); + } + + // send GraphicsSceneHoverEnter events to newly unblocked items + QGraphicsSceneHoverEvent hoverEvent; + hoverEvent.setScenePos(lastSceneMousePos); + dispatchHoverEvent(&hoverEvent); +} + QT_END_NAMESPACE #include "moc_qgraphicsscene.cpp" diff --git a/src/gui/graphicsview/qgraphicsscene_p.h b/src/gui/graphicsview/qgraphicsscene_p.h index 46917ce..5000860 100644 --- a/src/gui/graphicsview/qgraphicsscene_p.h +++ b/src/gui/graphicsview/qgraphicsscene_p.h @@ -160,6 +160,7 @@ public: Qt::DropAction lastDropAction; QList cachedItemsUnderMouse; QList hoverItems; + QPointF lastSceneMousePos; bool allItemsIgnoreHoverEvents; bool allItemsUseDefaultCursor; void enableMouseTrackingOnViews(); @@ -282,6 +283,11 @@ public: void enableTouchEventsOnViews(); void updateInputMethodSensitivityInViews(); + + QList modalPanels; + void enterModal(QGraphicsItem *item, + QGraphicsItem::PanelModality panelModality = QGraphicsItem::NonModal); + void leaveModal(QGraphicsItem *item); }; // QRectF::intersects() returns false always if either the source or target diff --git a/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp b/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp index bbf19f2..20dc0a4 100644 --- a/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp +++ b/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp @@ -80,6 +80,57 @@ Q_DECLARE_METATYPE(QRectF) #define Q_CHECK_PAINTEVENTS #endif +static void sendMousePress(QGraphicsScene *scene, const QPointF &point, Qt::MouseButton button = Qt::LeftButton) +{ + QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMousePress); + event.setScenePos(point); + event.setButton(button); + event.setButtons(button); + QApplication::sendEvent(scene, &event); +} + +static void sendMouseMove(QGraphicsScene *scene, const QPointF &point, + Qt::MouseButton button = Qt::NoButton, Qt::MouseButtons buttons = 0) +{ + QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseMove); + event.setScenePos(point); + event.setButton(button); + event.setButtons(button); + QApplication::sendEvent(scene, &event); +} + +static void sendMouseRelease(QGraphicsScene *scene, const QPointF &point, Qt::MouseButton button = Qt::LeftButton) +{ + QGraphicsSceneMouseEvent event(QEvent::GraphicsSceneMouseRelease); + event.setScenePos(point); + event.setButton(button); + QApplication::sendEvent(scene, &event); +} + +static void sendMouseClick(QGraphicsScene *scene, const QPointF &point, Qt::MouseButton button = Qt::LeftButton) +{ + sendMousePress(scene, point, button); + sendMouseRelease(scene, point, button); +} + +static void sendKeyPress(QGraphicsScene *scene, Qt::Key key) +{ + QKeyEvent keyEvent(QEvent::KeyPress, key, Qt::NoModifier); + QApplication::sendEvent(scene, &keyEvent); +} + +static void sendKeyRelease(QGraphicsScene *scene, Qt::Key key) +{ + QKeyEvent keyEvent(QEvent::KeyRelease, key, Qt::NoModifier); + QApplication::sendEvent(scene, &keyEvent); +} + +static void sendKeyClick(QGraphicsScene *scene, Qt::Key key) +{ + sendKeyPress(scene, key); + sendKeyRelease(scene, key); +} + class EventSpy : public QGraphicsWidget { Q_OBJECT @@ -120,6 +171,39 @@ protected: QEvent::Type spied; }; +class EventSpy2 : public QGraphicsWidget +{ + Q_OBJECT +public: + EventSpy2(QObject *watched) + { + watched->installEventFilter(this); + } + + EventSpy2(QGraphicsScene *scene, QGraphicsItem *watched) + { + scene->addItem(this); + watched->installSceneEventFilter(this); + } + + QMap counts; + +protected: + bool eventFilter(QObject *watched, QEvent *event) + { + Q_UNUSED(watched); + ++counts[event->type()]; + return false; + } + + bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) + { + Q_UNUSED(watched); + ++counts[event->type()]; + return false; + } +}; + class EventTester : public QGraphicsItem { public: @@ -296,6 +380,13 @@ private slots: void ensureDirtySceneTransform(); void focusScope(); void stackBefore(); + void sceneModality(); + void panelModality(); + void mixedModality(); + void modality_hover(); + void modality_mouseGrabber(); + void modality_clickFocus(); + void modality_keyEvents(); // task specific tests below me void task141694_textItemEnsureVisible(); @@ -4668,11 +4759,11 @@ public: { QGraphicsRectItem::paint(painter, option, widget); if (harakiri == 0) { - // delete unsupported since 4.5 - /* + // delete unsupported since 4.5 + /* dead = 1; - delete this; - */ + delete this; + */ } } @@ -8483,5 +8574,923 @@ void tst_QGraphicsItem::QTBUG_4233_updateCachedWithSceneRect() QCOMPARE(tester->repaints, 2); } +void tst_QGraphicsItem::sceneModality() +{ + // 1) Test mouse events (delivery/propagation/redirection) + // 2) Test hover events (incl. leave on block, enter on unblock) + // 3) Test cursor stuff (incl. unset on block, set on unblock) + // 4) Test clickfocus + // 5) Test grab/ungrab events (possibly ungrab on block, regrab on unblock) + // 6) ### modality for non-panels is unsupported for now + QGraphicsScene scene; + + QGraphicsRectItem *bottomItem = scene.addRect(-150, -100, 300, 200); + bottomItem->setFlag(QGraphicsItem::ItemIsFocusable); + bottomItem->setBrush(Qt::yellow); + + QGraphicsRectItem *leftParent = scene.addRect(-50, -50, 100, 100); + leftParent->setFlag(QGraphicsItem::ItemIsPanel); + leftParent->setBrush(Qt::blue); + + QGraphicsRectItem *leftChild = scene.addRect(-25, -25, 50, 50); + leftChild->setFlag(QGraphicsItem::ItemIsPanel); + leftChild->setBrush(Qt::green); + leftChild->setParentItem(leftParent); + + QGraphicsRectItem *rightParent = scene.addRect(-50, -50, 100, 100); + rightParent->setFlag(QGraphicsItem::ItemIsPanel); + rightParent->setBrush(Qt::red); + QGraphicsRectItem *rightChild = scene.addRect(-25, -25, 50, 50); + rightChild->setFlag(QGraphicsItem::ItemIsPanel); + rightChild->setBrush(Qt::gray); + rightChild->setParentItem(rightParent); + + leftParent->setPos(-75, 0); + rightParent->setPos(75, 0); + + bottomItem->setData(0, "bottomItem"); + leftParent->setData(0, "leftParent"); + leftChild->setData(0, "leftChild"); + rightParent->setData(0, "rightParent"); + rightChild->setData(0, "rightChild"); + + scene.setSceneRect(scene.itemsBoundingRect().adjusted(-50, -50, 50, 50)); + + EventSpy2 leftParentSpy(&scene, leftParent); + EventSpy2 leftChildSpy(&scene, leftChild); + EventSpy2 rightParentSpy(&scene, rightParent); + EventSpy2 rightChildSpy(&scene, rightChild); + EventSpy2 bottomItemSpy(&scene, bottomItem); + + // Scene modality, also test multiple scene modal items + leftChild->setPanelModality(QGraphicsItem::SceneModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0); // not a panel + + // Click inside left child + sendMouseClick(&scene, leftChild->scenePos(), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + + // Click on left parent, event goes to modal child + sendMouseClick(&scene, leftParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 2); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + + // Click on all other items and outside the items + sendMouseClick(&scene, rightParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 3); + sendMouseClick(&scene, rightChild->scenePos(), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 4); + sendMouseClick(&scene, bottomItem->scenePos(), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 5); + sendMouseClick(&scene, QPointF(10000, 10000), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 6); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + + leftChildSpy.counts.clear(); + rightChildSpy.counts.clear(); + leftParentSpy.counts.clear(); + rightParentSpy.counts.clear(); + bottomItemSpy.counts.clear(); + + leftChild->setPanelModality(QGraphicsItem::NonModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 1); + QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 1); + QCOMPARE(bottomItemSpy.counts[QEvent::WindowUnblocked], 0); + + // Left parent enters scene modality. + leftParent->setPanelModality(QGraphicsItem::SceneModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0); + + // Click inside left child. + sendMouseClick(&scene, leftChild->scenePos(), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // panel stops propagation + QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); + QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + + // Click on left parent. + sendMouseClick(&scene, leftParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); + QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); + QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); + + // Click on all other items and outside the items + sendMouseClick(&scene, rightParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton); + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 2); + sendMouseClick(&scene, rightChild->scenePos(), Qt::LeftButton); + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 3); + sendMouseClick(&scene, bottomItem->scenePos(), Qt::LeftButton); + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 4); + sendMouseClick(&scene, QPointF(10000, 10000), Qt::LeftButton); + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 5); + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); + QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); + QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); + + leftChildSpy.counts.clear(); + rightChildSpy.counts.clear(); + leftParentSpy.counts.clear(); + rightParentSpy.counts.clear(); + bottomItemSpy.counts.clear(); + + // Now both left parent and child are scene modal. Left parent is blocked. + leftChild->setPanelModality(QGraphicsItem::SceneModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0); + + // Click inside left child + sendMouseClick(&scene, leftChild->scenePos(), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + + // Click on left parent, event goes to modal child + sendMouseClick(&scene, leftParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 2); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + + // Click on all other items and outside the items + sendMouseClick(&scene, rightParent->sceneBoundingRect().topLeft() + QPointF(5, 5), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 3); + sendMouseClick(&scene, rightChild->scenePos(), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 4); + sendMouseClick(&scene, bottomItem->scenePos(), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 5); + sendMouseClick(&scene, QPointF(10000, 10000), Qt::LeftButton); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMousePress], 6); + QCOMPARE(leftChildSpy.counts[QEvent::GraphicsSceneMouseRelease], 0); // no grab + QCOMPARE(leftParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightParentSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(rightChildSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + QCOMPARE(bottomItemSpy.counts[QEvent::GraphicsSceneMousePress], 0); // blocked + + leftChildSpy.counts.clear(); + rightChildSpy.counts.clear(); + leftParentSpy.counts.clear(); + rightParentSpy.counts.clear(); + bottomItemSpy.counts.clear(); + + // Right child enters scene modality, only left child is blocked. + rightChild->setPanelModality(QGraphicsItem::SceneModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0); +} + +void tst_QGraphicsItem::panelModality() +{ + // 1) Test mouse events (delivery/propagation/redirection) + // 2) Test hover events (incl. leave on block, enter on unblock) + // 3) Test cursor stuff (incl. unset on block, set on unblock) + // 4) Test clickfocus + // 5) Test grab/ungrab events (possibly ungrab on block, regrab on unblock) + // 6) ### modality for non-panels is unsupported for now + QGraphicsScene scene; + + QGraphicsRectItem *bottomItem = scene.addRect(-150, -100, 300, 200); + bottomItem->setFlag(QGraphicsItem::ItemIsFocusable); + bottomItem->setBrush(Qt::yellow); + + QGraphicsRectItem *leftParent = scene.addRect(-50, -50, 100, 100); + leftParent->setFlag(QGraphicsItem::ItemIsPanel); + leftParent->setBrush(Qt::blue); + + QGraphicsRectItem *leftChild = scene.addRect(-25, -25, 50, 50); + leftChild->setFlag(QGraphicsItem::ItemIsPanel); + leftChild->setBrush(Qt::green); + leftChild->setParentItem(leftParent); + + QGraphicsRectItem *rightParent = scene.addRect(-50, -50, 100, 100); + rightParent->setFlag(QGraphicsItem::ItemIsPanel); + rightParent->setBrush(Qt::red); + QGraphicsRectItem *rightChild = scene.addRect(-25, -25, 50, 50); + rightChild->setFlag(QGraphicsItem::ItemIsPanel); + rightChild->setBrush(Qt::gray); + rightChild->setParentItem(rightParent); + + leftParent->setPos(-75, 0); + rightParent->setPos(75, 0); + + bottomItem->setData(0, "bottomItem"); + leftParent->setData(0, "leftParent"); + leftChild->setData(0, "leftChild"); + rightParent->setData(0, "rightParent"); + rightChild->setData(0, "rightChild"); + + scene.setSceneRect(scene.itemsBoundingRect().adjusted(-50, -50, 50, 50)); + + EventSpy2 leftParentSpy(&scene, leftParent); + EventSpy2 leftChildSpy(&scene, leftChild); + EventSpy2 rightParentSpy(&scene, rightParent); + EventSpy2 rightChildSpy(&scene, rightChild); + EventSpy2 bottomItemSpy(&scene, bottomItem); + + // Left Child enters panel modality, only left parent is blocked. + leftChild->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0); + + leftChild->setPanelModality(QGraphicsItem::NonModal); + leftChildSpy.counts.clear(); + rightChildSpy.counts.clear(); + leftParentSpy.counts.clear(); + rightParentSpy.counts.clear(); + bottomItemSpy.counts.clear(); + + // Left parent enter panel modality, nothing is blocked. + leftParent->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0); + + // Left child enters panel modality, left parent is blocked again. + leftChild->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(bottomItemSpy.counts[QEvent::WindowBlocked], 0); + + leftChildSpy.counts.clear(); + rightChildSpy.counts.clear(); + leftParentSpy.counts.clear(); + rightParentSpy.counts.clear(); + bottomItemSpy.counts.clear(); + + leftChild->setPanelModality(QGraphicsItem::NonModal); + QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 1); + leftParent->setPanelModality(QGraphicsItem::NonModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(bottomItemSpy.counts[QEvent::WindowUnblocked], 0); + + leftChildSpy.counts.clear(); + rightChildSpy.counts.clear(); + leftParentSpy.counts.clear(); + rightParentSpy.counts.clear(); + bottomItemSpy.counts.clear(); + + // Left and right child enter panel modality, both parents are blocked. + rightChild->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1); + leftChild->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1); +} + +void tst_QGraphicsItem::mixedModality() +{ + // 1) Test mouse events (delivery/propagation/redirection) + // 2) Test hover events (incl. leave on block, enter on unblock) + // 3) Test cursor stuff (incl. unset on block, set on unblock) + // 4) Test clickfocus + // 5) Test grab/ungrab events (possibly ungrab on block, regrab on unblock) + // 6) ### modality for non-panels is unsupported for now + QGraphicsScene scene; + + QGraphicsRectItem *bottomItem = scene.addRect(-150, -100, 300, 200); + bottomItem->setFlag(QGraphicsItem::ItemIsFocusable); + bottomItem->setBrush(Qt::yellow); + + QGraphicsRectItem *leftParent = scene.addRect(-50, -50, 100, 100); + leftParent->setFlag(QGraphicsItem::ItemIsPanel); + leftParent->setBrush(Qt::blue); + + QGraphicsRectItem *leftChild = scene.addRect(-25, -25, 50, 50); + leftChild->setFlag(QGraphicsItem::ItemIsPanel); + leftChild->setBrush(Qt::green); + leftChild->setParentItem(leftParent); + + QGraphicsRectItem *rightParent = scene.addRect(-50, -50, 100, 100); + rightParent->setFlag(QGraphicsItem::ItemIsPanel); + rightParent->setBrush(Qt::red); + QGraphicsRectItem *rightChild = scene.addRect(-25, -25, 50, 50); + rightChild->setFlag(QGraphicsItem::ItemIsPanel); + rightChild->setBrush(Qt::gray); + rightChild->setParentItem(rightParent); + + leftParent->setPos(-75, 0); + rightParent->setPos(75, 0); + + bottomItem->setData(0, "bottomItem"); + leftParent->setData(0, "leftParent"); + leftChild->setData(0, "leftChild"); + rightParent->setData(0, "rightParent"); + rightChild->setData(0, "rightChild"); + + scene.setSceneRect(scene.itemsBoundingRect().adjusted(-50, -50, 50, 50)); + + EventSpy2 leftParentSpy(&scene, leftParent); + EventSpy2 leftChildSpy(&scene, leftChild); + EventSpy2 rightParentSpy(&scene, rightParent); + EventSpy2 rightChildSpy(&scene, rightChild); + EventSpy2 bottomItemSpy(&scene, bottomItem); + + // Left Child enters panel modality, only left parent is blocked. + leftChild->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 0); + + // Left parent enters scene modality, which blocks everything except the child. + leftParent->setPanelModality(QGraphicsItem::SceneModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0); + + // Right child enters panel modality (changes nothing). + rightChild->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0); + + // Left parent leaves modality. Right child is unblocked. + leftParent->setPanelModality(QGraphicsItem::NonModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 0); + QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 1); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0); + + // Right child "upgrades" its modality to scene modal. Left child is blocked. + // Right parent is unaffected. + rightChild->setPanelModality(QGraphicsItem::SceneModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 1); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0); + + // "downgrade" right child back to panel modal, left child is unblocked + rightChild->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(leftChildSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(leftChildSpy.counts[QEvent::WindowUnblocked], 1); + QCOMPARE(rightChildSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightChildSpy.counts[QEvent::WindowUnblocked], 1); + QCOMPARE(leftParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(leftParentSpy.counts[QEvent::WindowUnblocked], 0); + QCOMPARE(rightParentSpy.counts[QEvent::WindowBlocked], 1); + QCOMPARE(rightParentSpy.counts[QEvent::WindowUnblocked], 0); +} + +void tst_QGraphicsItem::modality_hover() +{ + QGraphicsScene scene; + QGraphicsRectItem *rect1 = scene.addRect(-50, -50, 100, 100); + rect1->setFlag(QGraphicsItem::ItemIsPanel); + rect1->setAcceptHoverEvents(true); + rect1->setData(0, "rect1"); + + QGraphicsRectItem *rect2 = scene.addRect(-50, -50, 100, 100); + rect2->setParentItem(rect1); + rect2->setFlag(QGraphicsItem::ItemIsPanel); + rect2->setAcceptHoverEvents(true); + rect2->setPos(50, 50); + rect2->setPanelModality(QGraphicsItem::SceneModal); + rect2->setData(0, "rect2"); + + EventSpy2 rect1Spy(&scene, rect1); + EventSpy2 rect2Spy(&scene, rect2); + + sendMouseMove(&scene, QPointF(-25, -25)); + + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 0); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 0); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 0); + + sendMouseMove(&scene, QPointF(75, 75)); + + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 0); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 0); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 0); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverEnter], 1); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverMove], 1); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverLeave], 0); + + sendMouseMove(&scene, QPointF(-25, -25)); + + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverLeave], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 0); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 0); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 0); + + rect2->setPanelModality(QGraphicsItem::NonModal); + + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 1); + + sendMouseMove(&scene, QPointF(75, 75)); + + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverEnter], 2); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverMove], 2); + + rect2->setPanelModality(QGraphicsItem::SceneModal); + + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 1); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverEnter], 2); + // changing modality causes a spurious GraphicsSceneHoveMove, even though the mouse didn't + // actually move + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverMove], 3); + + sendMouseMove(&scene, QPointF(-25, -25)); + + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverLeave], 2); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 1); + + rect2->setPanelModality(QGraphicsItem::PanelModal); + + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverLeave], 1); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverEnter], 2); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverMove], 3); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverLeave], 2); + + rect2->setPanelModality(QGraphicsItem::NonModal); + + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverEnter], 2); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneHoverMove], 2); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverEnter], 2); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverMove], 3); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneHoverLeave], 2); +} + +void tst_QGraphicsItem::modality_mouseGrabber() +{ + QGraphicsScene scene; + QGraphicsRectItem *rect1 = scene.addRect(-50, -50, 100, 100); + rect1->setFlag(QGraphicsItem::ItemIsPanel); + rect1->setFlag(QGraphicsItem::ItemIsMovable); + rect1->setData(0, "rect1"); + + QGraphicsRectItem *rect2 = scene.addRect(-50, -50, 100, 100); + rect2->setParentItem(rect1); + rect2->setFlag(QGraphicsItem::ItemIsPanel); + rect2->setFlag(QGraphicsItem::ItemIsMovable); + rect2->setPos(50, 50); + rect2->setData(0, "rect2"); + + EventSpy2 rect1Spy(&scene, rect1); + EventSpy2 rect2Spy(&scene, rect2); + + { + // pressing mouse on rect1 starts implicit grab + sendMousePress(&scene, QPoint(-25, -25)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) rect1); + + // grab lost when rect1 is modally shadowed + rect2->setPanelModality(QGraphicsItem::SceneModal); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + + // releasing goes nowhere + sendMouseRelease(&scene, QPoint(-25, -25)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMouseRelease], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + + // pressing mouse on rect1 starts implicit grab on rect2 (since it is modal) + sendMouseClick(&scene, QPoint(-25, -25)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMouseRelease], 1); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1); + + rect2->setPanelModality(QGraphicsItem::NonModal); + + // pressing mouse on rect1 starts implicit grab + sendMousePress(&scene, QPoint(-25, -25)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 2); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) rect1); + + // grab lost to rect2 when rect1 is modally shadowed + rect2->setPanelModality(QGraphicsItem::SceneModal); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + + // rect1 does *not* re-grab when rect2 is no longer modal + rect2->setPanelModality(QGraphicsItem::NonModal); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + + // release goes nowhere + sendMouseRelease(&scene, QPoint(-25, -25)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + } + { + // repeat the test using PanelModal + rect2->setPanelModality(QGraphicsItem::NonModal); + rect1Spy.counts.clear(); + rect2Spy.counts.clear(); + + // pressing mouse on rect1 starts implicit grab + sendMousePress(&scene, QPoint(-25, -25)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) rect1); + + // grab lost when rect1 is modally shadowed + rect2->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + + // releasing goes nowhere + sendMouseRelease(&scene, QPoint(-25, -25)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMouseRelease], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + + // pressing mouse on rect1 starts implicit grab on rect2 (since it is modal) + sendMouseClick(&scene, QPoint(-25, -25)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(rect2Spy.counts[QEvent::GraphicsSceneMouseRelease], 1); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1); + + rect2->setPanelModality(QGraphicsItem::NonModal); + + // pressing mouse on rect1 starts implicit grab + sendMousePress(&scene, QPoint(-25, -25)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMousePress], 2); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) rect1); + + // grab lost to rect2 when rect1 is modally shadowed + rect2->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + + // rect1 does *not* re-grab when rect2 is no longer modal + rect2->setPanelModality(QGraphicsItem::NonModal); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + + // release goes nowhere + sendMouseRelease(&scene, QPoint(-25, -25)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect1Spy.counts[QEvent::GraphicsSceneMouseRelease], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 2); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + } + + { + // repeat the PanelModal tests, but this time the mouse events will be on a non-modal item, + // meaning normal grabbing should work + rect2->setPanelModality(QGraphicsItem::NonModal); + rect1Spy.counts.clear(); + rect2Spy.counts.clear(); + + QGraphicsRectItem *rect3 = scene.addRect(-50, -50, 100, 100); + rect3->setFlag(QGraphicsItem::ItemIsPanel); + rect3->setFlag(QGraphicsItem::ItemIsMovable); + rect3->setPos(150, 50); + rect3->setData(0, "rect3"); + + EventSpy2 rect3Spy(&scene, rect3); + + // pressing mouse on rect3 starts implicit grab + sendMousePress(&scene, QPoint(150, 50)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect3Spy.counts[QEvent::GraphicsSceneMousePress], 1); + QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) rect3); + + // grab is *not* lost when rect1 is modally shadowed by rect2 + rect2->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) rect3); + + // releasing goes to rect3 + sendMouseRelease(&scene, QPoint(150, 50)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 1); + QCOMPARE(rect3Spy.counts[QEvent::GraphicsSceneMouseRelease], 1); + QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + + rect2->setPanelModality(QGraphicsItem::NonModal); + + // pressing mouse on rect3 starts implicit grab + sendMousePress(&scene, QPoint(150, 50)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) rect3); + + // grab is not lost + rect2->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) rect3); + + // grab stays on rect3 + rect2->setPanelModality(QGraphicsItem::NonModal); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 1); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) rect3); + + // release goes to rect3 + sendMouseRelease(&scene, QPoint(150, 50)); + QCOMPARE(rect1Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect1Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::GrabMouse], 0); + QCOMPARE(rect2Spy.counts[QEvent::UngrabMouse], 0); + QCOMPARE(rect3Spy.counts[QEvent::GrabMouse], 2); + QCOMPARE(rect3Spy.counts[QEvent::UngrabMouse], 2); + QCOMPARE(scene.mouseGrabberItem(), (QGraphicsItem *) 0); + } +} + +void tst_QGraphicsItem::modality_clickFocus() +{ + QGraphicsScene scene; + QGraphicsRectItem *rect1 = scene.addRect(-50, -50, 100, 100); + rect1->setFlag(QGraphicsItem::ItemIsPanel); + rect1->setFlag(QGraphicsItem::ItemIsFocusable); + rect1->setData(0, "rect1"); + + QGraphicsRectItem *rect2 = scene.addRect(-50, -50, 100, 100); + rect2->setParentItem(rect1); + rect2->setFlag(QGraphicsItem::ItemIsPanel); + rect2->setFlag(QGraphicsItem::ItemIsFocusable); + rect2->setPos(50, 50); + rect2->setData(0, "rect2"); + + QEvent windowActivateEvent(QEvent::WindowActivate); + QApplication::sendEvent(&scene, &windowActivateEvent); + + EventSpy2 rect1Spy(&scene, rect1); + EventSpy2 rect2Spy(&scene, rect2); + + // activate rect1, it should not get focus + rect1->setActive(true); + QCOMPARE(scene.focusItem(), (QGraphicsItem *) 0); + + // focus stays unset when rect2 becomes modal + rect2->setPanelModality(QGraphicsItem::SceneModal); + QCOMPARE(scene.focusItem(), (QGraphicsItem *) 0); + QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 0); + QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 0); + QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 0); + QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 0); + + // clicking on rect1 should not set it's focus item + sendMouseClick(&scene, QPointF(-25, -25)); + QCOMPARE(rect1->focusItem(), (QGraphicsItem *) 0); + QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 0); + QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 0); + QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 0); + QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 0); + + // clicking on rect2 gives it focus + rect2->setActive(true); + sendMouseClick(&scene, QPointF(75, 75)); + QCOMPARE(scene.focusItem(), (QGraphicsItem *) rect2); + QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 0); + QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 0); + QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 1); + QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 0); + + // clicking on rect1 does *not* give it focus + rect1->setActive(true); + sendMouseClick(&scene, QPointF(-25, -25)); + QCOMPARE(scene.focusItem(), (QGraphicsItem *) 0); + QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 0); + QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 0); + QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 1); + QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 1); + + // focus doesn't change when leaving modality either + rect2->setPanelModality(QGraphicsItem::NonModal); + QCOMPARE(scene.focusItem(), (QGraphicsItem *) 0); + QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 0); + QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 0); + QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 1); + QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 1); + + // click on rect1, it should get focus now + sendMouseClick(&scene, QPointF(-25, -25)); + QCOMPARE(scene.focusItem(), (QGraphicsItem *) rect1); + QCOMPARE(rect1Spy.counts[QEvent::FocusIn], 1); + QCOMPARE(rect1Spy.counts[QEvent::FocusOut], 0); + QCOMPARE(rect2Spy.counts[QEvent::FocusIn], 1); + QCOMPARE(rect2Spy.counts[QEvent::FocusOut], 1); +} + +void tst_QGraphicsItem::modality_keyEvents() +{ + QGraphicsScene scene; + QGraphicsRectItem *rect1 = scene.addRect(-50, -50, 100, 100); + rect1->setFlag(QGraphicsItem::ItemIsPanel); + rect1->setFlag(QGraphicsItem::ItemIsFocusable); + rect1->setData(0, "rect1"); + + QGraphicsRectItem *rect1child = scene.addRect(-10, -10, 20, 20); + rect1child->setParentItem(rect1); + rect1child->setFlag(QGraphicsItem::ItemIsFocusable); + rect1child->setData(0, "rect1child1"); + + QGraphicsRectItem *rect2 = scene.addRect(-50, -50, 100, 100); + rect2->setParentItem(rect1); + rect2->setFlag(QGraphicsItem::ItemIsPanel); + rect2->setFlag(QGraphicsItem::ItemIsFocusable); + rect2->setPos(50, 50); + rect2->setData(0, "rect2"); + + QGraphicsRectItem *rect2child = scene.addRect(-10, -10, 20, 20); + rect2child->setParentItem(rect2); + rect2child->setFlag(QGraphicsItem::ItemIsFocusable); + rect2child->setData(0, "rect2child1"); + + QEvent windowActivateEvent(QEvent::WindowActivate); + QApplication::sendEvent(&scene, &windowActivateEvent); + + EventSpy2 rect1Spy(&scene, rect1); + EventSpy2 rect1childSpy(&scene, rect1child); + EventSpy2 rect2Spy(&scene, rect2); + EventSpy2 rect2childSpy(&scene, rect2child); + + // activate rect1 and give it rect1child focus + rect1->setActive(true); + rect1child->setFocus(); + QCOMPARE(scene.focusItem(), (QGraphicsItem *) rect1child); + + // focus stays on rect1child when rect2 becomes modal + rect2->setPanelModality(QGraphicsItem::SceneModal); + QCOMPARE(scene.focusItem(), (QGraphicsItem *) rect1child); + + // but key events to rect1child should be neither delivered nor propagated + sendKeyClick(&scene, Qt::Key_A); + sendKeyClick(&scene, Qt::Key_S); + sendKeyClick(&scene, Qt::Key_D); + sendKeyClick(&scene, Qt::Key_F); + QCOMPARE(rect1childSpy.counts[QEvent::KeyPress], 0); + QCOMPARE(rect1childSpy.counts[QEvent::KeyRelease], 0); + QCOMPARE(rect1Spy.counts[QEvent::KeyPress], 0); + QCOMPARE(rect1Spy.counts[QEvent::KeyRelease], 0); + + // change to panel modality, rect1child1 keeps focus + rect2->setPanelModality(QGraphicsItem::PanelModal); + QCOMPARE(scene.focusItem(), (QGraphicsItem *) rect1child); + + // still no key events + sendKeyClick(&scene, Qt::Key_J); + sendKeyClick(&scene, Qt::Key_K); + sendKeyClick(&scene, Qt::Key_L); + sendKeyClick(&scene, Qt::Key_Semicolon); + QCOMPARE(rect1childSpy.counts[QEvent::KeyPress], 0); + QCOMPARE(rect1childSpy.counts[QEvent::KeyRelease], 0); + QCOMPARE(rect1Spy.counts[QEvent::KeyPress], 0); + QCOMPARE(rect1Spy.counts[QEvent::KeyRelease], 0); +} + QTEST_MAIN(tst_QGraphicsItem) #include "tst_qgraphicsitem.moc" -- cgit v0.12