From 3f630677d415d4a67938fa6cf64754c80aa30ba0 Mon Sep 17 00:00:00 2001 From: Andreas Aardal Hanssen Date: Wed, 22 Jul 2009 09:19:12 +0200 Subject: Add QGraphicsItem::focusProxy(), focus proxy support. Following QWidget's behavior, you can not assign any item in the same scene as a focus proxy for another item. Also supports nested focus proxies. You can only assign items in the same scene as focus proxies. Autotests are included. Reviewed-By: mbm --- src/gui/graphicsview/qgraphicsitem.cpp | 76 ++++++++++++++-- src/gui/graphicsview/qgraphicsitem.h | 3 + src/gui/graphicsview/qgraphicsitem_p.h | 2 + src/gui/graphicsview/qgraphicsscene.cpp | 106 +++++++++++++--------- src/gui/graphicsview/qgraphicsscene_p.h | 2 + tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp | 120 +++++++++++++++++++++++++ 6 files changed, 261 insertions(+), 48 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsitem.cpp b/src/gui/graphicsview/qgraphicsitem.cpp index ae2a2c3..4e3677a 100644 --- a/src/gui/graphicsview/qgraphicsitem.cpp +++ b/src/gui/graphicsview/qgraphicsitem.cpp @@ -2455,6 +2455,8 @@ void QGraphicsItem::setHandlesChildEvents(bool enabled) */ bool QGraphicsItem::hasFocus() const { + if (d_ptr->focusProxy) + return d_ptr->focusProxy->hasFocus(); return (d_ptr->scene && d_ptr->scene->focusItem() == this); } @@ -2480,12 +2482,16 @@ void QGraphicsItem::setFocus(Qt::FocusReason focusReason) { if (!d_ptr->scene || !isEnabled() || hasFocus() || !(d_ptr->flags & ItemIsFocusable)) return; - if (isVisible()) { + QGraphicsItem *item = this; + QGraphicsItem *f; + while ((f = item->d_ptr->focusProxy)) + item = f; + if (item->isVisible()) { // Visible items immediately gain focus from scene. - d_ptr->scene->setFocusItem(this, focusReason); - } else if (d_ptr->isWidget) { + d_ptr->scene->d_func()->setFocusItemHelper(item, focusReason); + } else if (item->d_ptr->isWidget) { // Just set up subfocus. - static_cast(this)->d_func()->setFocusWidget(); + static_cast(item)->d_func()->setFocusWidget(); } } @@ -2508,13 +2514,73 @@ void QGraphicsItem::clearFocus() // Invisible widget items with focus must explicitly clear subfocus. static_cast(this)->d_func()->clearFocusWidget(); } - if (d_ptr->scene->focusItem() == this) { + if (hasFocus()) { // If this item has the scene's input focus, clear it. d_ptr->scene->setFocusItem(0); } } /*! + \since 4.6 + + Returns this item's focus proxy, or 0 if the item + does not have any focus proxy. + + \sa setFocusProxy() +*/ +QGraphicsItem *QGraphicsItem::focusProxy() const +{ + return d_ptr->focusProxy; +} + +/*! + \since 4.6 + + Sets the item's focus proxy to \a item. + + If an item has a focus proxy, the focus proxy will receive + input focus when the item gains input focus. The item itself + will still have focus (i.e., hasFocus() will return true), + but only the focus proxy will receive the keyboard input. + + A focus proxy can itself have a focus proxy, and so on. In + such case, keyboard input will be handled by the outermost + focus proxy. + + \sa focusProxy() +*/ +void QGraphicsItem::setFocusProxy(QGraphicsItem *item) +{ + if (item == d_ptr->focusProxy) + return; + if (item == this) { + qWarning("QGraphicsItem::setFocusProxy: cannot assign self as focus proxy"); + return; + } + if (item) { + if (item->d_ptr->scene != d_ptr->scene) { + qWarning("QGraphicsItem::setFocusProxy: focus proxy must be in same scene"); + return; + } + for (QGraphicsItem *f = item->focusProxy(); f != 0; f = f->focusProxy()) { + if (f == this) { + qWarning("QGraphicsItem::setFocusProxy: %p is already in the focus proxy chain", item); + return; + } + } + } + + QGraphicsItem *lastFocusProxy = d_ptr->focusProxy; + d_ptr->focusProxy = item; + if (d_ptr->scene) { + if (lastFocusProxy) + d_ptr->scene->d_func()->focusProxyReverseMap.remove(lastFocusProxy, this); + if (item) + d_ptr->scene->d_func()->focusProxyReverseMap.insert(item, this); + } +} + +/*! \since 4.4 Grabs the mouse input. diff --git a/src/gui/graphicsview/qgraphicsitem.h b/src/gui/graphicsview/qgraphicsitem.h index a307622..5c9297f 100644 --- a/src/gui/graphicsview/qgraphicsitem.h +++ b/src/gui/graphicsview/qgraphicsitem.h @@ -226,6 +226,9 @@ public: void setFocus(Qt::FocusReason focusReason = Qt::OtherFocusReason); void clearFocus(); + QGraphicsItem *focusProxy() const; + void setFocusProxy(QGraphicsItem *item); + void grabMouse(); void ungrabMouse(); void grabKeyboard(); diff --git a/src/gui/graphicsview/qgraphicsitem_p.h b/src/gui/graphicsview/qgraphicsitem_p.h index 4729634..fee7083 100644 --- a/src/gui/graphicsview/qgraphicsitem_p.h +++ b/src/gui/graphicsview/qgraphicsitem_p.h @@ -122,6 +122,7 @@ public: index(-1), siblingIndex(-1), depth(0), + focusProxy(0), acceptedMouseButtons(0x1f), visible(1), explicitlyHidden(0), @@ -409,6 +410,7 @@ public: int index; int siblingIndex; int depth; + QGraphicsItem *focusProxy; // Packed 32 bytes quint32 acceptedMouseButtons : 5; diff --git a/src/gui/graphicsview/qgraphicsscene.cpp b/src/gui/graphicsview/qgraphicsscene.cpp index b017022..4796436 100644 --- a/src/gui/graphicsview/qgraphicsscene.cpp +++ b/src/gui/graphicsview/qgraphicsscene.cpp @@ -491,6 +491,13 @@ void QGraphicsScenePrivate::removeItemHelper(QGraphicsItem *item) item->d_func()->scene = 0; + // Unregister focus proxy. + QMultiHash::iterator it = focusProxyReverseMap.find(item); + while (it != focusProxyReverseMap.end() && it.key() == item) { + it.value()->d_ptr->focusProxy = 0; + it = focusProxyReverseMap.erase(it); + } + // Remove from parent, or unregister from toplevels. if (QGraphicsItem *parentItem = item->parentItem()) { if (parentItem->scene()) { @@ -559,6 +566,58 @@ void QGraphicsScenePrivate::removeItemHelper(QGraphicsItem *item) /*! \internal */ +void QGraphicsScenePrivate::setFocusItemHelper(QGraphicsItem *item, + Qt::FocusReason focusReason) +{ + Q_Q(QGraphicsScene); + if (item == focusItem) + return; + if (item && (!(item->flags() & QGraphicsItem::ItemIsFocusable) + || !item->isVisible() || !item->isEnabled())) { + item = 0; + } + + if (item) { + q->setFocus(focusReason); + if (item == focusItem) + return; + } + + if (focusItem) { + QFocusEvent event(QEvent::FocusOut, focusReason); + lastFocusItem = focusItem; + focusItem = 0; + sendEvent(lastFocusItem, &event); + + if (lastFocusItem + && (lastFocusItem->flags() & QGraphicsItem::ItemAcceptsInputMethod)) { + // Reset any visible preedit text + QInputMethodEvent imEvent; + sendEvent(lastFocusItem, &imEvent); + + // Close any external input method panel + for (int i = 0; i < views.size(); ++i) + views.at(i)->inputContext()->reset(); + } + } + + if (item) { + if (item->isWidget()) { + // Update focus child chain. + static_cast(item)->d_func()->setFocusWidget(); + } + + focusItem = item; + QFocusEvent event(QEvent::FocusIn, focusReason); + sendEvent(item, &event); + } + + updateInputMethodSensitivityInViews(); +} + +/*! + \internal +*/ void QGraphicsScenePrivate::addPopup(QGraphicsWidget *widget) { Q_ASSERT(widget); @@ -2597,49 +2656,10 @@ QGraphicsItem *QGraphicsScene::focusItem() const void QGraphicsScene::setFocusItem(QGraphicsItem *item, Qt::FocusReason focusReason) { Q_D(QGraphicsScene); - if (item == d->focusItem) - return; - if (item && (!(item->flags() & QGraphicsItem::ItemIsFocusable) - || !item->isVisible() || !item->isEnabled())) { - item = 0; - } - - if (item) { - setFocus(focusReason); - if (item == d->focusItem) - return; - } - - if (d->focusItem) { - QFocusEvent event(QEvent::FocusOut, focusReason); - d->lastFocusItem = d->focusItem; - d->focusItem = 0; - d->sendEvent(d->lastFocusItem, &event); - - if (d->lastFocusItem - && (d->lastFocusItem->flags() & QGraphicsItem::ItemAcceptsInputMethod)) { - // Reset any visible preedit text - QInputMethodEvent imEvent; - d->sendEvent(d->lastFocusItem, &imEvent); - - // Close any external input method panel - for (int i = 0; i < d->views.size(); ++i) - d->views.at(i)->inputContext()->reset(); - } - } - - if (item) { - if (item->isWidget()) { - // Update focus child chain. - static_cast(item)->d_func()->setFocusWidget(); - } - - d->focusItem = item; - QFocusEvent event(QEvent::FocusIn, focusReason); - d->sendEvent(item, &event); - } - - d->updateInputMethodSensitivityInViews(); + if (item) + item->setFocus(focusReason); + else + d->setFocusItemHelper(item, focusReason); } /*! diff --git a/src/gui/graphicsview/qgraphicsscene_p.h b/src/gui/graphicsview/qgraphicsscene_p.h index 9a91acc..a4bbdd2 100644 --- a/src/gui/graphicsview/qgraphicsscene_p.h +++ b/src/gui/graphicsview/qgraphicsscene_p.h @@ -130,6 +130,8 @@ public: QGraphicsWidget *tabFocusFirst; QGraphicsWidget *activeWindow; int activationRefCount; + void setFocusItemHelper(QGraphicsItem *item, Qt::FocusReason focusReason); + QMultiHash focusProxyReverseMap; QList popupWidgets; void addPopup(QGraphicsWidget *widget); diff --git a/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp b/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp index f58cad2..011e480 100644 --- a/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp +++ b/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp @@ -76,6 +76,46 @@ Q_DECLARE_METATYPE(QRectF) #define Q_CHECK_PAINTEVENTS #endif +class EventSpy : public QGraphicsWidget +{ + Q_OBJECT +public: + EventSpy(QObject *watched, QEvent::Type type) + : _count(0), spied(type) + { + watched->installEventFilter(this); + } + + EventSpy(QGraphicsScene *scene, QGraphicsItem *watched, QEvent::Type type) + : _count(0), spied(type) + { + scene->addItem(this); + watched->installSceneEventFilter(this); + } + + int count() const { return _count; } + +protected: + bool eventFilter(QObject *watched, QEvent *event) + { + Q_UNUSED(watched); + if (event->type() == spied) + ++_count; + return false; + } + + bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) + { + Q_UNUSED(watched); + if (event->type() == spied) + ++_count; + return false; + } + + int _count; + QEvent::Type spied; +}; + class EventTester : public QGraphicsItem { public: @@ -234,6 +274,7 @@ private slots: void sorting(); void itemHasNoContents(); void hitTestUntransformableItem(); + void focusProxy(); // task specific tests below me void task141694_textItemEnsureVisible(); @@ -7344,5 +7385,84 @@ void tst_QGraphicsItem::hitTestUntransformableItem() QCOMPARE(items.at(0), static_cast(item3)); } +void tst_QGraphicsItem::focusProxy() +{ + QGraphicsScene scene; + QGraphicsItem *item = scene.addRect(0, 0, 10, 10); + item->setFlag(QGraphicsItem::ItemIsFocusable); + QVERIFY(!item->focusProxy()); + + QGraphicsItem *item2 = scene.addRect(0, 0, 10, 10); + item2->setFlag(QGraphicsItem::ItemIsFocusable); + item->setFocusProxy(item2); + QCOMPARE(item->focusProxy(), item2); + + item->setFocus(); + QVERIFY(item->hasFocus()); + QVERIFY(item2->hasFocus()); + + // Try to make a focus chain loop + QString err; + QTextStream stream(&err); + stream << "QGraphicsItem::setFocusProxy: " + << (void*)item << " is already in the focus proxy chain" << flush; + QTest::ignoreMessage(QtWarningMsg, err.toLatin1().constData()); + item2->setFocusProxy(item); // fails + QCOMPARE(item->focusProxy(), (QGraphicsItem *)item2); + QCOMPARE(item2->focusProxy(), (QGraphicsItem *)0); + + // Try to assign self as focus proxy + QTest::ignoreMessage(QtWarningMsg, "QGraphicsItem::setFocusProxy: cannot assign self as focus proxy"); + item->setFocusProxy(item); // fails + QCOMPARE(item->focusProxy(), (QGraphicsItem *)item2); + QCOMPARE(item2->focusProxy(), (QGraphicsItem *)0); + + // Reset the focus proxy + item->setFocusProxy(0); + QCOMPARE(item->focusProxy(), (QGraphicsItem *)0); + QVERIFY(!item->hasFocus()); + QVERIFY(item2->hasFocus()); + + // Test deletion + item->setFocusProxy(item2); + QCOMPARE(item->focusProxy(), (QGraphicsItem *)item2); + delete item2; + QCOMPARE(item->focusProxy(), (QGraphicsItem *)0); + + // Test event delivery + item2 = scene.addRect(0, 0, 10, 10); + item2->setFlag(QGraphicsItem::ItemIsFocusable); + item->setFocusProxy(item2); + item->clearFocus(); + + EventSpy focusInSpy(&scene, item, QEvent::FocusIn); + EventSpy focusOutSpy(&scene, item, QEvent::FocusOut); + EventSpy focusInSpy2(&scene, item2, QEvent::FocusIn); + EventSpy focusOutSpy2(&scene, item2, QEvent::FocusOut); + QCOMPARE(focusInSpy.count(), 0); + QCOMPARE(focusOutSpy.count(), 0); + QCOMPARE(focusInSpy2.count(), 0); + QCOMPARE(focusOutSpy2.count(), 0); + + item->setFocus(); + QCOMPARE(focusInSpy.count(), 0); + QCOMPARE(focusInSpy2.count(), 1); + item->clearFocus(); + QCOMPARE(focusOutSpy.count(), 0); + QCOMPARE(focusOutSpy2.count(), 1); + + // Test two items proxying one item. + QGraphicsItem *item3 = scene.addRect(0, 0, 10, 10); + item3->setFlag(QGraphicsItem::ItemIsFocusable); + item3->setFocusProxy(item2); // item and item3 use item2 as proxy + + QCOMPARE(item->focusProxy(), item2); + QCOMPARE(item2->focusProxy(), (QGraphicsItem *)0); + QCOMPARE(item3->focusProxy(), item2); + delete item2; + QCOMPARE(item->focusProxy(), (QGraphicsItem *)0); + QCOMPARE(item3->focusProxy(), (QGraphicsItem *)0); +} + QTEST_MAIN(tst_QGraphicsItem) #include "tst_qgraphicsitem.moc" -- cgit v0.12