From af8a6cdd87c27b4ed18d5e6238757ba8885f630d Mon Sep 17 00:00:00 2001 From: Andreas Aardal Hanssen Date: Thu, 29 Nov 2012 22:33:43 +0100 Subject: [PATCH] Add new signal: QGraphicsScene::focusItemChanged(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This signal is emitted by QGraphicsScene whenever focus changes in the scene (i.e., when an item gains or loses input focus, or when focus passes from one item to another). You can connect to this signal if you need to keep track of when other items gain input focus. It is particularily useful for implementing virtual keyboards, input methods, and cursor items. Task-number: QTBUG-10570 Change-Id: I9cbbd9a2d15d6f568e1597c2c33ec049eb70f793 Reviewed-by: Jan Arve Sæther Reviewed-by: Yoann Lopes --- src/widgets/graphicsview/qgraphicsscene.cpp | 50 +++++++- src/widgets/graphicsview/qgraphicsscene.h | 1 + src/widgets/graphicsview/qgraphicsscene_p.h | 3 +- .../qgraphicsscene/tst_qgraphicsscene.cpp | 115 ++++++++++++++++++ 4 files changed, 162 insertions(+), 7 deletions(-) diff --git a/src/widgets/graphicsview/qgraphicsscene.cpp b/src/widgets/graphicsview/qgraphicsscene.cpp index 139fad0163..e14a98f332 100644 --- a/src/widgets/graphicsview/qgraphicsscene.cpp +++ b/src/widgets/graphicsview/qgraphicsscene.cpp @@ -742,12 +742,14 @@ void QGraphicsScenePrivate::setActivePanelHelper(QGraphicsItem *item, bool durin if (panel == activePanel || (!q->isActive() && !duringActivationEvent)) return; + QGraphicsItem *oldFocusItem = focusItem; + // 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); + setFocusItemHelper(0, Qt::ActiveWindowFocusReason, /* emitFocusChanged = */ false); } QEvent event(QEvent::WindowDeactivate); @@ -775,14 +777,14 @@ void QGraphicsScenePrivate::setActivePanelHelper(QGraphicsItem *item, bool durin // focusable, or on the first focusable item in the panel's // focus chain as a last resort. if (QGraphicsItem *focusItem = panel->focusItem()) { - focusItem->setFocus(Qt::ActiveWindowFocusReason); + setFocusItemHelper(focusItem, Qt::ActiveWindowFocusReason, /* emitFocusChanged = */ false); } else if (panel->flags() & QGraphicsItem::ItemIsFocusable) { - panel->setFocus(Qt::ActiveWindowFocusReason); + setFocusItemHelper(panel, Qt::ActiveWindowFocusReason, /* emitFocusChanged = */ false); } else if (panel->isWidget()) { QGraphicsWidget *fw = static_cast(panel)->d_func()->focusNext; do { if (fw->focusPolicy() & Qt::TabFocus) { - fw->setFocus(Qt::ActiveWindowFocusReason); + setFocusItemHelper(fw, Qt::ActiveWindowFocusReason, /* emitFocusChanged = */ false); break; } fw = fw->d_func()->focusNext; @@ -796,13 +798,23 @@ void QGraphicsScenePrivate::setActivePanelHelper(QGraphicsItem *item, bool durin q->sendEvent(item, &event); } } + + emit q->focusItemChanged(focusItem, oldFocusItem, Qt::ActiveWindowFocusReason); } /*! \internal + + \a emitFocusChanged needs to be false when focus passes from one + item to another through setActivePanel(); i.e. when activation + passes from one panel to another, to avoid getting two focusChanged() + emissions; one focusChanged(0, lastFocus), then one + focusChanged(newFocus, 0). Instead setActivePanel() emits the signal + once itself: focusChanged(newFocus, oldFocus). */ void QGraphicsScenePrivate::setFocusItemHelper(QGraphicsItem *item, - Qt::FocusReason focusReason) + Qt::FocusReason focusReason, + bool emitFocusChanged) { Q_Q(QGraphicsScene); if (item == focusItem) @@ -818,10 +830,14 @@ void QGraphicsScenePrivate::setFocusItemHelper(QGraphicsItem *item, // Set focus on the scene if an item requests focus. if (item) { q->setFocus(focusReason); - if (item == focusItem) + if (item == focusItem) { + if (emitFocusChanged) + emit q->focusItemChanged(focusItem, (QGraphicsItem *)0, focusReason); return; + } } + QGraphicsItem *oldFocusItem = focusItem; if (focusItem) { lastFocusItem = focusItem; @@ -862,6 +878,9 @@ void QGraphicsScenePrivate::setFocusItemHelper(QGraphicsItem *item, QFocusEvent event(QEvent::FocusIn, focusReason); sendEvent(item, &event); } + + if (emitFocusChanged) + emit q->focusItemChanged(focusItem, oldFocusItem, focusReason); } /*! @@ -5436,6 +5455,25 @@ bool QGraphicsScene::focusNextPrevChild(bool next) \sa setSelectionArea(), selectedItems(), QGraphicsItem::setSelected() */ +/*! + \fn QGraphicsScene::focusChanged(QGraphicsItem *newFocusItem, QGraphicsItem *oldFocusItem, Qt::FocusReason reason) + + This signal is emitted by QGraphicsScene whenever focus changes in the + scene (i.e., when an item gains or loses input focus, or when focus + passes from one item to another). You can connect to this signal if you + need to keep track of when other items gain input focus. It is + particularily useful for implementing virtual keyboards, input methods, + and cursor items. + + \a oldFocusItem is a pointer to the item that previously had focus, or + 0 if no item had focus before the signal was emitted. \a newFocusItem + is a pointer to the item that gained input focus, or 0 if focus was lost. + \a reason is the reason for the focus change (e.g., if the scene was + deactivated while an input field had focus, \a oldFocusItem would point + to the input field item, \a newFocusItem would be 0, and \a reason would be + Qt::ActiveWindowFocusReason. +*/ + /*! \since 4.4 diff --git a/src/widgets/graphicsview/qgraphicsscene.h b/src/widgets/graphicsview/qgraphicsscene.h index 27337d3a2a..3f00b74f53 100644 --- a/src/widgets/graphicsview/qgraphicsscene.h +++ b/src/widgets/graphicsview/qgraphicsscene.h @@ -292,6 +292,7 @@ Q_SIGNALS: void changed(const QList ®ion); void sceneRectChanged(const QRectF &rect); void selectionChanged(); + void focusItemChanged(QGraphicsItem *newFocus, QGraphicsItem *oldFocus, Qt::FocusReason reason); private: Q_DECLARE_PRIVATE(QGraphicsScene) diff --git a/src/widgets/graphicsview/qgraphicsscene_p.h b/src/widgets/graphicsview/qgraphicsscene_p.h index 6799a835ac..bcb95c694a 100644 --- a/src/widgets/graphicsview/qgraphicsscene_p.h +++ b/src/widgets/graphicsview/qgraphicsscene_p.h @@ -157,7 +157,8 @@ public: int activationRefCount; int childExplicitActivation; void setActivePanelHelper(QGraphicsItem *item, bool duringActivationEvent); - void setFocusItemHelper(QGraphicsItem *item, Qt::FocusReason focusReason); + void setFocusItemHelper(QGraphicsItem *item, Qt::FocusReason focusReason, + bool emitFocusChanged = true); QList popupWidgets; void addPopup(QGraphicsWidget *widget); diff --git a/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp b/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp index 69e687d633..3c253bf9cc 100644 --- a/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp +++ b/tests/auto/widgets/graphicsview/qgraphicsscene/tst_qgraphicsscene.cpp @@ -63,6 +63,7 @@ #define Q_CHECK_PAINTEVENTS #endif +Q_DECLARE_METATYPE(Qt::FocusReason) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QMatrix) @@ -270,6 +271,7 @@ private slots: void siblingIndexAlwaysValid(); void removeFullyTransparentItem(); void zeroScale(); + void focusItemChangedSignal(); // task specific tests below me void task139710_bspTreeCrash(); @@ -4565,6 +4567,119 @@ void tst_QGraphicsScene::zeroScale() QTRY_COMPARE(cl.changes.count(), 2); } +void tst_QGraphicsScene::focusItemChangedSignal() +{ + qRegisterMetaType("QGraphicsItem *"); + qRegisterMetaType("Qt::FocusReason"); + + QGraphicsScene scene; + QSignalSpy spy(&scene, SIGNAL(focusItemChanged(QGraphicsItem *, QGraphicsItem *, Qt::FocusReason))); + QVERIFY(spy.isValid()); + QCOMPARE(spy.count(), 0); + scene.setFocus(); + QCOMPARE(spy.count(), 0); + QEvent activateEvent(QEvent::WindowActivate); + qApp->sendEvent(&scene, &activateEvent); + QCOMPARE(spy.count(), 0); + + QGraphicsRectItem *topLevelItem1 = new QGraphicsRectItem; + topLevelItem1->setFlag(QGraphicsItem::ItemIsFocusable); + scene.addItem(topLevelItem1); + QCOMPARE(spy.count(), 0); + QVERIFY(!topLevelItem1->hasFocus()); + + QGraphicsRectItem *topLevelItem2 = new QGraphicsRectItem; + topLevelItem2->setFlag(QGraphicsItem::ItemIsFocusable); + topLevelItem2->setFocus(); + QVERIFY(!topLevelItem2->hasFocus()); + scene.addItem(topLevelItem2); + QCOMPARE(spy.count(), 1); + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 3); + QCOMPARE(qVariantValue(arguments.at(0)), (QGraphicsItem *)topLevelItem2); + QCOMPARE(qVariantValue(arguments.at(1)), (QGraphicsItem *)0); + QCOMPARE(qVariantValue(arguments.at(2)), Qt::OtherFocusReason); + QVERIFY(topLevelItem2->hasFocus()); + + scene.clearFocus(); + QCOMPARE(spy.count(), 1); + arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 3); + QCOMPARE(qVariantValue(arguments.at(0)), (QGraphicsItem *)0); + QCOMPARE(qVariantValue(arguments.at(1)), (QGraphicsItem *)topLevelItem2); + QCOMPARE(qVariantValue(arguments.at(2)), Qt::OtherFocusReason); + + scene.setFocus(Qt::MenuBarFocusReason); + QCOMPARE(spy.count(), 1); + arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 3); + QCOMPARE(qVariantValue(arguments.at(0)), (QGraphicsItem *)topLevelItem2); + QCOMPARE(qVariantValue(arguments.at(1)), (QGraphicsItem *)0); + QCOMPARE(qVariantValue(arguments.at(2)), Qt::MenuBarFocusReason); + + for (int i = 0; i < 3; ++i) { + topLevelItem1->setFocus(Qt::TabFocusReason); + arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 3); + QCOMPARE(qVariantValue(arguments.at(0)), (QGraphicsItem *)topLevelItem1); + QCOMPARE(qVariantValue(arguments.at(1)), (QGraphicsItem *)topLevelItem2); + QCOMPARE(qVariantValue(arguments.at(2)), Qt::TabFocusReason); + + topLevelItem2->setFocus(Qt::TabFocusReason); + arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 3); + QCOMPARE(qVariantValue(arguments.at(0)), (QGraphicsItem *)topLevelItem2); + QCOMPARE(qVariantValue(arguments.at(1)), (QGraphicsItem *)topLevelItem1); + QCOMPARE(qVariantValue(arguments.at(2)), Qt::TabFocusReason); + } + + // The following two are unexpected, but fixing this (i.e., losing and gaining focus + // when the scene activation changes) breaks quite a few tests so leave this fix + // for some future release. See QTBUG-28346. + QEvent deactivateEvent(QEvent::WindowDeactivate); + qApp->sendEvent(&scene, &deactivateEvent); + QEXPECT_FAIL("", "QTBUG-28346", Continue); + QCOMPARE(spy.count(), 1); + qApp->sendEvent(&scene, &activateEvent); + QEXPECT_FAIL("", "QTBUG-28346", Continue); + QCOMPARE(spy.count(), 1); + + QGraphicsRectItem *panel1 = new QGraphicsRectItem; + panel1->setFlags(QGraphicsItem::ItemIsPanel | QGraphicsItem::ItemIsFocusable); + panel1->setFocus(); + scene.addItem(panel1); + QCOMPARE(spy.count(), 1); + arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 3); + QCOMPARE(qVariantValue(arguments.at(0)), (QGraphicsItem *)panel1); + QCOMPARE(qVariantValue(arguments.at(1)), (QGraphicsItem *)topLevelItem2); + QCOMPARE(qVariantValue(arguments.at(2)), Qt::ActiveWindowFocusReason); + + QGraphicsRectItem *panel2 = new QGraphicsRectItem; + panel2->setFlags(QGraphicsItem::ItemIsPanel | QGraphicsItem::ItemIsFocusable); + scene.addItem(panel2); + QCOMPARE(spy.count(), 0); + + for (int i = 0; i < 3; ++i) { + scene.setActivePanel(panel2); + QCOMPARE(spy.count(), 1); + arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 3); + QCOMPARE(qVariantValue(arguments.at(0)), (QGraphicsItem *)panel2); + QCOMPARE(qVariantValue(arguments.at(1)), (QGraphicsItem *)panel1); + QCOMPARE(qVariantValue(arguments.at(2)), Qt::ActiveWindowFocusReason); + + scene.setActivePanel(panel1); + QCOMPARE(spy.count(), 1); + arguments = spy.takeFirst(); + QCOMPARE(arguments.size(), 3); + QCOMPARE(qVariantValue(arguments.at(0)), (QGraphicsItem *)panel1); + QCOMPARE(qVariantValue(arguments.at(1)), (QGraphicsItem *)panel2); + QCOMPARE(qVariantValue(arguments.at(2)), Qt::ActiveWindowFocusReason); + } + +} + void tst_QGraphicsScene::taskQTBUG_15977_renderWithDeviceCoordinateCache() { QGraphicsScene scene;