From 62ca28a96d200fe55ed5bc2d0d1df327ab44c97e Mon Sep 17 00:00:00 2001 From: Andreas Aardal Hanssen Date: Mon, 31 Aug 2009 14:06:47 +0200 Subject: Fix activation behavior for panels, and add QGraphicsItem::setActive(). Allow delayed activation for more fine grained control over which panels are activated or left inactive when the scene is created. Autotests included. Reviewed-by: Brad --- src/gui/graphicsview/qgraphicsitem.cpp | 37 +++++- src/gui/graphicsview/qgraphicsitem.h | 2 + src/gui/graphicsview/qgraphicsitem_p.h | 8 +- src/gui/graphicsview/qgraphicsscene.cpp | 149 +++++++++++++++---------- src/gui/graphicsview/qgraphicsscene_p.h | 2 + tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp | 140 +++++++++++++++++++---- 6 files changed, 255 insertions(+), 83 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsitem.cpp b/src/gui/graphicsview/qgraphicsitem.cpp index 86c589d..31fd53a 100644 --- a/src/gui/graphicsview/qgraphicsitem.cpp +++ b/src/gui/graphicsview/qgraphicsitem.cpp @@ -2669,6 +2669,37 @@ bool QGraphicsItem::isActive() const } /*! + \since 4.6 + + If \a active is true, and the scene is active, this item's panel will be + activated. Otherwise, the panel is deactivated. + + If the item is not part of an active scene, \a active will decide what + happens to the panel when the scene becomes active or the item is added to + the scene. If true, the item's panel will be activated when the item is + either added to the scene or the scene is activated. Otherwise, the item + will stay inactive independent of the scene's activated state. + + \sa isPanel(), QGraphicsScene::setActivePanel(), QGraphicsScene::isActive() +*/ +void QGraphicsItem::setActive(bool active) +{ + d_ptr->explicitActivate = 1; + d_ptr->wantsActive = active; + if (d_ptr->scene) { + if (active) { + // Activate this item. + d_ptr->scene->setActivePanel(this); + } else { + // Deactivate this item, and reactivate the last active item + // (if any). + QGraphicsItem *lastActive = d_ptr->scene->d_func()->lastActivePanel; + d_ptr->scene->setActivePanel(lastActive != this ? lastActive : 0); + } + } +} + +/*! Returns true if this item is active, and it or its \l{focusProxy()}{focus proxy} has keyboard input focus; otherwise, returns false. @@ -6099,8 +6130,10 @@ bool QGraphicsItem::sceneEvent(QEvent *event) if (d_ptr->scene) { for (int i = 0; i < d_ptr->children.size(); ++i) { QGraphicsItem *child = d_ptr->children.at(i); - if (!(child->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorHandlesChildEvents)) - d_ptr->scene->sendEvent(child, event); + if (child->isVisible() && !child->isPanel()) { + if (!(child->d_ptr->ancestorFlags & QGraphicsItemPrivate::AncestorHandlesChildEvents)) + d_ptr->scene->sendEvent(child, event); + } } } break; diff --git a/src/gui/graphicsview/qgraphicsitem.h b/src/gui/graphicsview/qgraphicsitem.h index 04fe0cb..df25e6a 100644 --- a/src/gui/graphicsview/qgraphicsitem.h +++ b/src/gui/graphicsview/qgraphicsitem.h @@ -235,6 +235,8 @@ public: void setHandlesChildEvents(bool enabled); bool isActive() const; + void setActive(bool active); + bool hasFocus() const; void setFocus(Qt::FocusReason focusReason = Qt::OtherFocusReason); void clearFocus(); diff --git a/src/gui/graphicsview/qgraphicsitem_p.h b/src/gui/graphicsview/qgraphicsitem_p.h index 2bc876c..1090620 100644 --- a/src/gui/graphicsview/qgraphicsitem_p.h +++ b/src/gui/graphicsview/qgraphicsitem_p.h @@ -171,6 +171,8 @@ public: notifyBoundingRectChanged(0), notifyInvalidated(0), mouseSetsFocus(1), + explicitActivate(0), + wantsActive(0), globalStackingOrder(-1), q_ptr(0) { @@ -461,7 +463,7 @@ public: quint32 needSortChildren : 1; quint32 allChildrenDirty : 1; - // New 32 bits + // Packed 32 bits quint32 fullUpdatePending : 1; quint32 flags : 16; quint32 dirtyChildrenBoundingRect : 1; @@ -480,6 +482,10 @@ public: quint32 notifyInvalidated : 1; quint32 mouseSetsFocus : 1; + // New 32 bits + quint32 explicitActivate : 1; + quint32 wantsActive : 1; + // Optional stacking order int globalStackingOrder; QGraphicsItem *q_ptr; diff --git a/src/gui/graphicsview/qgraphicsscene.cpp b/src/gui/graphicsview/qgraphicsscene.cpp index ac30668..2ac1dca 100644 --- a/src/gui/graphicsview/qgraphicsscene.cpp +++ b/src/gui/graphicsview/qgraphicsscene.cpp @@ -291,6 +291,7 @@ QGraphicsScenePrivate::QGraphicsScenePrivate() activePanel(0), lastActivePanel(0), activationRefCount(0), + childExplicitActivation(0), lastMouseGrabberItem(0), lastMouseGrabberItemHasImplicitMouseGrab(false), dragDropItem(0), @@ -571,6 +572,66 @@ void QGraphicsScenePrivate::removeItemHelper(QGraphicsItem *item) /*! \internal */ +void QGraphicsScenePrivate::setActivePanelHelper(QGraphicsItem *item, bool duringActivationEvent) +{ + Q_Q(QGraphicsScene); + if (item && item->scene() != q) { + qWarning("QGraphicsScene::setActivePanel: item %p must be part of this scene", + item); + return; + } + + // Find the item's panel. + QGraphicsItem *panel = item ? item->panel() : 0; + lastActivePanel = panel ? activePanel : 0; + if (panel == activePanel || (!q->isActive() && !duringActivationEvent)) + return; + + // Deactivate the last active panel. + if (activePanel) { + if (QGraphicsItem *fi = activePanel->focusItem()) { + // Remove focus from the current focus item. + if (fi == q->focusItem()) + q->setFocusItem(0, Qt::ActiveWindowFocusReason); + } + + QEvent event(QEvent::WindowDeactivate); + q->sendEvent(activePanel, &event); + } else if (panel && !duringActivationEvent) { + // Deactivate the scene if changing activation to a panel. + QEvent event(QEvent::WindowDeactivate); + foreach (QGraphicsItem *item, q->items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + q->sendEvent(item, &event); + } + } + + // Update activate state. + activePanel = panel; + QEvent event(QEvent::ActivationChange); + QApplication::sendEvent(q, &event); + + // Activate + if (panel) { + QEvent event(QEvent::WindowActivate); + q->sendEvent(panel, &event); + + // Set focus on the panel's focus item. + if (QGraphicsItem *focusItem = panel->focusItem()) + focusItem->setFocus(Qt::ActiveWindowFocusReason); + } else if (q->isActive()) { + // Activate the scene + QEvent event(QEvent::WindowActivate); + foreach (QGraphicsItem *item, q->items()) { + if (item->isVisible() && !item->isPanel() && !item->parentItem()) + q->sendEvent(item, &event); + } + } +} + +/*! + \internal +*/ void QGraphicsScenePrivate::setFocusItemHelper(QGraphicsItem *item, Qt::FocusReason focusReason) { @@ -2351,9 +2412,29 @@ void QGraphicsScene::addItem(QGraphicsItem *item) // Deliver post-change notification item->itemChange(QGraphicsItem::ItemSceneHasChanged, newSceneVariant); - // Auto-activate the first inactive panel if the scene is active. - if (isActive() && !d->activePanel && item->isPanel()) - setActivePanel(item); + // Update explicit activation + bool autoActivate = true; + if (!d->childExplicitActivation && item->d_ptr->explicitActivate) + d->childExplicitActivation = item->d_ptr->wantsActive ? 1 : 2; + if (d->childExplicitActivation && item->isPanel()) { + if (d->childExplicitActivation == 1) + setActivePanel(item); + else + autoActivate = false; + d->childExplicitActivation = 0; + } else if (!item->d_ptr->parent) { + d->childExplicitActivation = 0; + } + + // Auto-activate this item's panel if nothing else has been activated + if (autoActivate) { + if (!d->lastActivePanel && !d->activePanel && item->isPanel()) { + if (isActive()) + setActivePanel(item); + else + d->lastActivePanel = item; + } + } // Ensure that newly added items that have subfocus set, gain // focus automatically if there isn't a focus item already. @@ -3129,11 +3210,11 @@ bool QGraphicsScene::event(QEvent *event) if (!d->activationRefCount++) { if (d->lastActivePanel) { // Activate the last panel. - setActivePanel(d->lastActivePanel); + d->setActivePanelHelper(d->lastActivePanel, true); } else if (d->tabFocusFirst && d->tabFocusFirst->isPanel()) { // Activate the panel of the first item in the tab focus // chain. - setActivePanel(d->tabFocusFirst); + d->setActivePanelHelper(d->tabFocusFirst, true); } else { // Activate all toplevel items. QEvent event(QEvent::WindowActivate); @@ -3150,7 +3231,7 @@ bool QGraphicsScene::event(QEvent *event) // Deactivate the active panel (but keep it so we can // reactivate it later). QGraphicsItem *lastActivePanel = d->activePanel; - setActivePanel(0); + d->setActivePanelHelper(0, true); d->lastActivePanel = lastActivePanel; } else { // Activate all toplevel items. @@ -5095,63 +5176,15 @@ QGraphicsItem *QGraphicsScene::activePanel() const can also pass 0 for \a item, in which case QGraphicsScene will deactivate any currently active panel. + If the scene is currently inactive, \a item remains inactive until the + scene becomes active (or, ir \a item is 0, no item will be activated). + \sa activePanel(), isActive(), QGraphicsItem::isActive() */ void QGraphicsScene::setActivePanel(QGraphicsItem *item) { Q_D(QGraphicsScene); - if (item && item->scene() != this) { - qWarning("QGraphicsScene::setActivePanel: item %p must be part of this scene", - item); - return; - } - - // Find the item's panel. - QGraphicsItem *panel = item ? item->panel() : 0; - d->lastActivePanel = panel ? d->activePanel : 0; - if (panel == d->activePanel) - return; - - // Deactivate the last active panel. - if (d->activePanel) { - if (QGraphicsItem *fi = d->activePanel->focusItem()) { - // Remove focus from the current focus item. - if (fi == focusItem()) - setFocusItem(0, Qt::ActiveWindowFocusReason); - } - - QEvent event(QEvent::WindowDeactivate); - sendEvent(d->activePanel, &event); - } else if (panel) { - // Deactivate the scene if changing activation to a panel. - QEvent event(QEvent::WindowDeactivate); - foreach (QGraphicsItem *item, items()) { - if (item->isVisible() && !item->isPanel() && !item->parentItem()) - sendEvent(item, &event); - } - } - - // Update activate state. - d->activePanel = panel; - QEvent event(QEvent::ActivationChange); - QApplication::sendEvent(this, &event); - - // Activate - if (panel) { - QEvent event(QEvent::WindowActivate); - sendEvent(panel, &event); - - // Set focus on the panel's focus item. - if (QGraphicsItem *focusItem = panel->focusItem()) - focusItem->setFocus(Qt::ActiveWindowFocusReason); - } else if (isActive()) { - // Activate the scene - QEvent event(QEvent::WindowActivate); - foreach (QGraphicsItem *item, items()) { - if (item->isVisible() && !item->isPanel() && !item->parentItem()) - sendEvent(item, &event); - } - } + d->setActivePanelHelper(item, false); } /*! diff --git a/src/gui/graphicsview/qgraphicsscene_p.h b/src/gui/graphicsview/qgraphicsscene_p.h index c1b78ff..1f66eb4 100644 --- a/src/gui/graphicsview/qgraphicsscene_p.h +++ b/src/gui/graphicsview/qgraphicsscene_p.h @@ -132,6 +132,8 @@ public: QGraphicsItem *activePanel; QGraphicsItem *lastActivePanel; int activationRefCount; + int childExplicitActivation; + void setActivePanelHelper(QGraphicsItem *item, bool duringActivationEvent); void setFocusItemHelper(QGraphicsItem *item, Qt::FocusReason focusReason); QList popupWidgets; diff --git a/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp b/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp index 541b5ba..517380e 100644 --- a/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp +++ b/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp @@ -289,6 +289,8 @@ private slots: void setGraphicsEffect(); void panel(); void addPanelToActiveScene(); + void activate(); + void setActivePanelOnInactiveScene(); // task specific tests below me void task141694_textItemEnsureVisible(); @@ -7823,26 +7825,15 @@ void tst_QGraphicsItem::panel() // No previous activation, so the scene is active. QVERIFY(scene.isActive()); - QVERIFY(!scene.activePanel()); - QVERIFY(!panel1->isActive()); - QVERIFY(!panel2->isActive()); - QVERIFY(!panel3->isActive()); - QVERIFY(!panel4->isActive()); - QVERIFY(notPanel1->isActive()); - QVERIFY(notPanel2->isActive()); - QCOMPARE(spy_activate_notPanel1.count(), 1); - QCOMPARE(spy_activate_notPanel2.count(), 1); - - // Switch to panel1. - scene.setActivePanel(panel1); + QCOMPARE(scene.activePanel(), (QGraphicsItem *)panel1); QVERIFY(panel1->isActive()); QVERIFY(!panel2->isActive()); QVERIFY(!panel3->isActive()); QVERIFY(!panel4->isActive()); QVERIFY(!notPanel1->isActive()); QVERIFY(!notPanel2->isActive()); - QCOMPARE(spy_deactivate_notPanel1.count(), 1); - QCOMPARE(spy_deactivate_notPanel2.count(), 1); + QCOMPARE(spy_deactivate_notPanel1.count(), 0); + QCOMPARE(spy_deactivate_notPanel2.count(), 0); QCOMPARE(spy_activate_panel1.count(), 1); QCOMPARE(spy_activate_panel2.count(), 0); QCOMPARE(spy_activate_panel3.count(), 0); @@ -7857,8 +7848,8 @@ void tst_QGraphicsItem::panel() QVERIFY(!panel4->isActive()); QVERIFY(notPanel1->isActive()); QVERIFY(notPanel2->isActive()); - QCOMPARE(spy_activate_notPanel1.count(), 2); - QCOMPARE(spy_activate_notPanel2.count(), 2); + QCOMPARE(spy_activate_notPanel1.count(), 1); + QCOMPARE(spy_activate_notPanel2.count(), 1); // Deactivate the scene QApplication::sendEvent(&scene, &deactivate); @@ -7869,8 +7860,8 @@ void tst_QGraphicsItem::panel() QVERIFY(!panel4->isActive()); QVERIFY(!notPanel1->isActive()); QVERIFY(!notPanel2->isActive()); - QCOMPARE(spy_deactivate_notPanel1.count(), 2); - QCOMPARE(spy_deactivate_notPanel2.count(), 2); + QCOMPARE(spy_deactivate_notPanel1.count(), 1); + QCOMPARE(spy_deactivate_notPanel2.count(), 1); // Reactivate the scene QApplication::sendEvent(&scene, &activate); @@ -7881,14 +7872,14 @@ void tst_QGraphicsItem::panel() QVERIFY(!panel4->isActive()); QVERIFY(notPanel1->isActive()); QVERIFY(notPanel2->isActive()); - QCOMPARE(spy_activate_notPanel1.count(), 3); - QCOMPARE(spy_activate_notPanel2.count(), 3); + QCOMPARE(spy_activate_notPanel1.count(), 2); + QCOMPARE(spy_activate_notPanel2.count(), 2); // Switch to panel1 scene.setActivePanel(panel1); QVERIFY(panel1->isActive()); - QCOMPARE(spy_deactivate_notPanel1.count(), 3); - QCOMPARE(spy_deactivate_notPanel2.count(), 3); + QCOMPARE(spy_deactivate_notPanel1.count(), 2); + QCOMPARE(spy_deactivate_notPanel2.count(), 2); QCOMPARE(spy_activate_panel1.count(), 2); // Deactivate the scene @@ -7942,5 +7933,110 @@ void tst_QGraphicsItem::addPanelToActiveScene() QCOMPARE(scene.activePanel(), (QGraphicsItem *)rect); } +void tst_QGraphicsItem::activate() +{ + QGraphicsScene scene; + QGraphicsRectItem *rect = scene.addRect(-10, -10, 20, 20); + QVERIFY(!rect->isActive()); + + QEvent activate(QEvent::WindowActivate); + QEvent deactivate(QEvent::WindowDeactivate); + + QApplication::sendEvent(&scene, &activate); + + // Non-panel item (active when scene is active). + QVERIFY(rect->isActive()); + + QGraphicsRectItem *rect2 = new QGraphicsRectItem; + rect2->setFlag(QGraphicsItem::ItemIsPanel); + QGraphicsRectItem *rect3 = new QGraphicsRectItem; + rect3->setFlag(QGraphicsItem::ItemIsPanel); + + // Test normal activation. + QVERIFY(!rect2->isActive()); + scene.addItem(rect2); + QVERIFY(rect2->isActive()); // first panel item is activated + scene.addItem(rect3); + QVERIFY(!rect3->isActive()); // second panel item is _not_ activated + rect3->setActive(true); + QVERIFY(rect3->isActive()); + scene.removeItem(rect3); + QVERIFY(!rect3->isActive()); // no panel is active anymore + QCOMPARE(scene.activePanel(), (QGraphicsItem *)0); + scene.addItem(rect3); + QVERIFY(rect3->isActive()); // second panel item is activated + + // Test pending activation. + scene.removeItem(rect3); + rect2->setActive(true); + QVERIFY(rect2->isActive()); // first panel item is activated + rect3->setActive(true); + QVERIFY(!rect3->isActive()); // not active (yet) + scene.addItem(rect3); + QVERIFY(rect3->isActive()); // now becomes active + + // Test pending deactivation. + scene.removeItem(rect3); + rect3->setActive(false); + scene.addItem(rect3); + QVERIFY(!rect3->isActive()); // doesn't become active + + // Child of panel activation. + rect3->setActive(true); + QGraphicsRectItem *rect4 = new QGraphicsRectItem; + rect4->setFlag(QGraphicsItem::ItemIsPanel); + QGraphicsRectItem *rect5 = new QGraphicsRectItem(rect4); + QGraphicsRectItem *rect6 = new QGraphicsRectItem(rect5); + scene.addItem(rect4); + QCOMPARE(scene.activePanel(), (QGraphicsItem *)rect3); + scene.removeItem(rect4); + rect6->setActive(true); + scene.addItem(rect4); + QVERIFY(rect4->isActive()); + QVERIFY(rect5->isActive()); + QVERIFY(rect6->isActive()); + QCOMPARE(scene.activePanel(), (QGraphicsItem *)rect4); + scene.removeItem(rect4); // no active panel + rect6->setActive(false); + scene.addItem(rect4); + QVERIFY(!rect4->isActive()); + QVERIFY(!rect5->isActive()); + QVERIFY(!rect6->isActive()); + QCOMPARE(scene.activePanel(), (QGraphicsItem *)0); + + // Controlling auto-activation when the scene changes activation. + rect4->setActive(true); + QApplication::sendEvent(&scene, &deactivate); + QVERIFY(!scene.isActive()); + QVERIFY(!rect4->isActive()); + rect4->setActive(false); + QApplication::sendEvent(&scene, &activate); + QVERIFY(scene.isActive()); + QVERIFY(!scene.activePanel()); + QVERIFY(!rect4->isActive()); +} + +void tst_QGraphicsItem::setActivePanelOnInactiveScene() +{ + QGraphicsScene scene; + QGraphicsRectItem *item = scene.addRect(QRectF()); + QGraphicsRectItem *panel = scene.addRect(QRectF()); + panel->setFlag(QGraphicsItem::ItemIsPanel); + + EventSpy itemActivateSpy(&scene, item, QEvent::WindowActivate); + EventSpy itemDeactivateSpy(&scene, item, QEvent::WindowDeactivate); + EventSpy panelActivateSpy(&scene, panel, QEvent::WindowActivate); + EventSpy panelDeactivateSpy(&scene, panel, QEvent::WindowDeactivate); + EventSpy sceneActivationChangeSpy(&scene, QEvent::ActivationChange); + + scene.setActivePanel(panel); + QCOMPARE(scene.activePanel(), (QGraphicsItem *)0); + QCOMPARE(itemActivateSpy.count(), 0); + QCOMPARE(itemDeactivateSpy.count(), 0); + QCOMPARE(panelActivateSpy.count(), 0); + QCOMPARE(panelDeactivateSpy.count(), 0); + QCOMPARE(sceneActivationChangeSpy.count(), 0); +} + QTEST_MAIN(tst_QGraphicsItem) #include "tst_qgraphicsitem.moc" -- cgit v0.12