From f68fed388dcdba6ab6dad3af4933bcd3aa123cf8 Mon Sep 17 00:00:00 2001 From: Andreas Aardal Hanssen Date: Fri, 24 Jul 2009 03:40:51 +0200 Subject: Add QGraphicsItem::ItemAutoDetectsFocusProxy and improve subfocus support. If you set this flag on an item, and descendant item that gains input focus will become this item's focus proxy. This simplifies how focus proxy items are assigned from QML; instead of binding the possible focusProxy property to a named child widget, this assignment happens automatically as you set the focus property of a descendant to true. As part of this change, QGraphicsWidget::focusWidget behavior has been improved and moved into QGraphicsItem. For example, if you set focus on an item that it's part of a scene, it can gain focus once the parent has been assigned (which is how object trees are built in QML). Autotests are included. Reviewed-by: Michael Brasser --- src/gui/graphicsview/qgraphicsitem.cpp | 120 ++++++++++++++++++++----- src/gui/graphicsview/qgraphicsitem.h | 5 +- src/gui/graphicsview/qgraphicsitem_p.h | 9 +- src/gui/graphicsview/qgraphicsscene.cpp | 29 ++++-- src/gui/graphicsview/qgraphicswidget.cpp | 8 +- src/gui/graphicsview/qgraphicswidget_p.cpp | 34 ------- src/gui/graphicsview/qgraphicswidget_p.h | 4 - tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp | 107 ++++++++++++++++++++++ 8 files changed, 243 insertions(+), 73 deletions(-) diff --git a/src/gui/graphicsview/qgraphicsitem.cpp b/src/gui/graphicsview/qgraphicsitem.cpp index 0ff3685..2425354 100644 --- a/src/gui/graphicsview/qgraphicsitem.cpp +++ b/src/gui/graphicsview/qgraphicsitem.cpp @@ -325,6 +325,10 @@ \value ItemAcceptsInputMethod The item supports input methods typically used for Asian languages. This flag was introduced in Qt 4.6. + + \value ItemAutoDetectsFocusProxy The item will assign any child that + gains input focus as its focus proxy. See also focusProxy(). + This flag was introduced in Qt 4.6. */ /*! @@ -907,12 +911,12 @@ void QGraphicsItemPrivate::setParentItemHelper(QGraphicsItem *newParent) scene->d_func()->index->itemChange(q, QGraphicsItem::ItemParentChange, newParentVariant); } - if (QGraphicsWidget *w = isWidget ? static_cast(q) : q->parentWidget()) { - // Update the child focus chain; when reparenting a widget that has a + QGraphicsItem *lastSubFocusItem = subFocusItem; + if (subFocusItem) { + // Update the child focus chain; when reparenting an item that has a // focus child, ensure that that focus child clears its focus child // chain from our parents before it's reparented. - if (QGraphicsWidget *focusChild = w->focusWidget()) - focusChild->clearFocus(); + subFocusItem->clearFocus(); } // We anticipate geometry changes. If the item is deleted, it will be @@ -1000,6 +1004,21 @@ void QGraphicsItemPrivate::setParentItemHelper(QGraphicsItem *newParent) resolveDepth(parent ? parent->d_ptr->depth : -1); dirtySceneTransform = 1; + // Restore the sub focus chain. + if (lastSubFocusItem) + lastSubFocusItem->d_ptr->setSubFocus(); + + // Auto-update focus proxy. The closest parent that detects + // focus proxies is updated as the proxy gains or loses focus. + QGraphicsItem *p = newParent; + while (p) { + if (p->d_ptr->flags & QGraphicsItem::ItemAutoDetectsFocusProxy) { + p->setFocusProxy(q); + break; + } + p = p->d_ptr->parent; + } + // Deliver post-change notification q->itemChange(QGraphicsItem::ItemParentHasChanged, newParentVariant); @@ -2463,33 +2482,45 @@ bool QGraphicsItem::hasFocus() const be passed into any focus event generated by this function; it is used to give an explanation of what caused the item to get focus. - Only items that set the ItemIsFocusable flag can accept keyboard focus. + Only enabled items that set the ItemIsFocusable flag can accept keyboard + focus. - If this item is not visible (i.e., isVisible() returns false), not - enabled, not associated with a scene, or if it already has input focus, - this function will do nothing. + If this item is not visible, or not associated with a scene, it will not + gain immediate input focus. However, it will be registered as 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 focus in event with \a focusReason. If another item already has focus, that item will first receive a focus out event indicating that it has lost input focus. - \sa clearFocus(), hasFocus() + \sa clearFocus(), hasFocus(), focusItem() */ void QGraphicsItem::setFocus(Qt::FocusReason focusReason) { - if (!d_ptr->scene || !isEnabled() || hasFocus() || !(d_ptr->flags & ItemIsFocusable)) + // Disabled / unfocusable items cannot accept focus. + if (!isEnabled() || !(d_ptr->flags & QGraphicsItem::ItemIsFocusable)) + return; + + // Find focus proxy. + QGraphicsItem *f = this; + while (f->d_ptr->focusProxy) + f = f->d_ptr->focusProxy; + + // Return if it already has focus. + if (d_ptr->scene && d_ptr->scene->focusItem() == f) return; - 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->d_func()->setFocusItemHelper(item, focusReason); - } else if (item->d_ptr->isWidget) { - // Just set up subfocus. - static_cast(item)->d_func()->setFocusWidget(); + + // Update the child focus chain. + d_ptr->setSubFocus(); + + // Update the scene's focus item. + if (d_ptr->scene) { + QGraphicsWidget *w = window(); + if (!w || w->isActiveWindow()) { + // Visible items immediately gain focus from scene. + d_ptr->scene->d_func()->setFocusItemHelper(f, focusReason); + } } } @@ -2508,10 +2539,8 @@ void QGraphicsItem::clearFocus() { if (!d_ptr->scene) return; - if (d_ptr->isWidget) { - // Invisible widget items with focus must explicitly clear subfocus. - static_cast(this)->d_func()->clearFocusWidget(); - } + // Invisible items with focus must explicitly clear subfocus. + d_ptr->clearSubFocus(); if (hasFocus()) { // If this item has the scene's input focus, clear it. d_ptr->scene->setFocusItem(0); @@ -2579,6 +2608,18 @@ void QGraphicsItem::setFocusProxy(QGraphicsItem *item) } /*! + If this item, a child or descendant of this item currently has input + focus, this function will return a pointer to that item. If + no descendant has input focus, 0 is returned. + + \sa QWidget::focusWidget() +*/ +QGraphicsItem *QGraphicsItem::focusItem() const +{ + return d_ptr->subFocusItem; +} + +/*! \since 4.4 Grabs the mouse input. @@ -4668,6 +4709,34 @@ void QGraphicsItemPrivate::ensureSceneTransform() /*! \internal +*/ +void QGraphicsItemPrivate::setSubFocus() +{ + // Update focus child chain. + QGraphicsItem *item = q_ptr; + QGraphicsItem *parent = item; + bool hidden = !visible; + do { + parent->d_func()->subFocusItem = item; + } while (!parent->isWindow() && (parent = parent->d_ptr->parent) && (!hidden || !parent->d_func()->visible)); +} + +/*! + \internal +*/ +void QGraphicsItemPrivate::clearSubFocus() +{ + // Reset focus child chain. + QGraphicsItem *parent = q_ptr; + do { + if (parent->d_ptr->subFocusItem != q_ptr) + break; + parent->d_ptr->subFocusItem = 0; + } while (!parent->isWindow() && (parent = parent->d_ptr->parent)); +} + +/*! + \internal Tells us if it is a proxy widget */ @@ -10038,6 +10107,9 @@ QDebug operator<<(QDebug debug, QGraphicsItem::GraphicsItemFlag flag) case QGraphicsItem::ItemAcceptsInputMethod: str = "ItemAcceptsInputMethod"; break; + case QGraphicsItem::ItemAutoDetectsFocusProxy: + str = "ItemAutoDetectsFocusProxy"; + break; } debug << str; return debug; diff --git a/src/gui/graphicsview/qgraphicsitem.h b/src/gui/graphicsview/qgraphicsitem.h index 5c9297f..0e21a47 100644 --- a/src/gui/graphicsview/qgraphicsitem.h +++ b/src/gui/graphicsview/qgraphicsitem.h @@ -99,7 +99,8 @@ public: ItemUsesExtendedStyleOption = 0x200, ItemHasNoContents = 0x400, ItemSendsGeometryChanges = 0x800, - ItemAcceptsInputMethod = 0x1000 + ItemAcceptsInputMethod = 0x1000, + ItemAutoDetectsFocusProxy = 0x2000 // NB! Don't forget to increase the d_ptr->flags bit field by 1 when adding a new flag. }; Q_DECLARE_FLAGS(GraphicsItemFlags, GraphicsItemFlag) @@ -229,6 +230,8 @@ public: QGraphicsItem *focusProxy() const; void setFocusProxy(QGraphicsItem *item); + QGraphicsItem *focusItem() const; + void grabMouse(); void ungrabMouse(); void grabKeyboard(); diff --git a/src/gui/graphicsview/qgraphicsitem_p.h b/src/gui/graphicsview/qgraphicsitem_p.h index 1cf188e..c208b99 100644 --- a/src/gui/graphicsview/qgraphicsitem_p.h +++ b/src/gui/graphicsview/qgraphicsitem_p.h @@ -123,6 +123,7 @@ public: siblingIndex(-1), depth(0), focusProxy(0), + subFocusItem(0), acceptedMouseButtons(0x1f), visible(1), explicitlyHidden(0), @@ -391,6 +392,9 @@ public: || (childrenCombineOpacity() && isFullyTransparent()); } + void setSubFocus(); + void clearSubFocus(); + inline QTransform transformToParent() const; inline void ensureSortedChildren(); @@ -411,6 +415,7 @@ public: int siblingIndex; int depth; QGraphicsItem *focusProxy; + QGraphicsItem *subFocusItem; // Packed 32 bytes quint32 acceptedMouseButtons : 5; @@ -440,7 +445,7 @@ public: // New 32 bits quint32 fullUpdatePending : 1; - quint32 flags : 13; + quint32 flags : 14; quint32 dirtyChildrenBoundingRect : 1; quint32 paintedViewBoundingRectsNeedRepaint : 1; quint32 dirtySceneTransform : 1; @@ -453,7 +458,7 @@ public: quint32 acceptedTouchBeginEvent : 1; quint32 filtersDescendantEvents : 1; quint32 sceneTransformTranslateOnly : 1; - quint32 unused : 6; // feel free to use + quint32 unused : 5; // feel free to use // Optional stacking order int globalStackingOrder; diff --git a/src/gui/graphicsview/qgraphicsscene.cpp b/src/gui/graphicsview/qgraphicsscene.cpp index 0dce26b..f223cbe 100644 --- a/src/gui/graphicsview/qgraphicsscene.cpp +++ b/src/gui/graphicsview/qgraphicsscene.cpp @@ -484,6 +484,8 @@ void QGraphicsScenePrivate::removeItemHelper(QGraphicsItem *item) index->removeItem(item); } + item->d_ptr->clearSubFocus(); + if (!item->d_ptr->inDestructor && item == tabFocusFirst) { QGraphicsWidget *widget = static_cast(item); widget->d_func()->fixFocusChainBeforeReparenting(0, 0); @@ -572,17 +574,34 @@ void QGraphicsScenePrivate::setFocusItemHelper(QGraphicsItem *item, Q_Q(QGraphicsScene); if (item == focusItem) return; + + // Clear focus if asked to set focus on something that can't + // accept input focus. if (item && (!(item->flags() & QGraphicsItem::ItemIsFocusable) || !item->isVisible() || !item->isEnabled())) { item = 0; } + // Set focus on the scene if an item requests focus. if (item) { q->setFocus(focusReason); if (item == focusItem) return; } + // Auto-update focus proxy. The closest parent that detects + // focus proxies is updated as the proxy gains or loses focus. + if (item) { + QGraphicsItem *p = item->d_ptr->parent; + while (p) { + if (p->d_ptr->flags & QGraphicsItem::ItemAutoDetectsFocusProxy) { + p->setFocusProxy(item); + break; + } + p = p->d_ptr->parent; + } + } + if (focusItem) { QFocusEvent event(QEvent::FocusOut, focusReason); lastFocusItem = focusItem; @@ -602,11 +621,6 @@ void QGraphicsScenePrivate::setFocusItemHelper(QGraphicsItem *item, } 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); @@ -2342,6 +2356,11 @@ void QGraphicsScene::addItem(QGraphicsItem *item) // Deliver post-change notification item->itemChange(QGraphicsItem::ItemSceneHasChanged, newSceneVariant); + // Ensure that newly added items that have subfocus set, gain + // focus automatically if there isn't a focus item already. + if (!d->focusItem && item->focusItem()) + item->focusItem()->setFocus(); + d->updateInputMethodSensitivityInViews(); } diff --git a/src/gui/graphicsview/qgraphicswidget.cpp b/src/gui/graphicsview/qgraphicswidget.cpp index 86c0b48..6937584 100644 --- a/src/gui/graphicsview/qgraphicswidget.cpp +++ b/src/gui/graphicsview/qgraphicswidget.cpp @@ -1716,14 +1716,16 @@ void QGraphicsWidget::setFocusPolicy(Qt::FocusPolicy policy) /*! If this widget, a child or descendant of this widget currently has input focus, this function will return a pointer to that widget. If - no descendant has input focus, 0 is returned. + no descendant widget has input focus, 0 is returned. - \sa QWidget::focusWidget() + \sa QGraphicsItem::focusItem(), QWidget::focusWidget() */ QGraphicsWidget *QGraphicsWidget::focusWidget() const { Q_D(const QGraphicsWidget); - return d->focusChild; + if (d->subFocusItem && d->subFocusItem->d_ptr->isWidget) + return static_cast(d->subFocusItem); + return 0; } /*! \property QGraphicsWidget::horizontalShear diff --git a/src/gui/graphicsview/qgraphicswidget_p.cpp b/src/gui/graphicsview/qgraphicswidget_p.cpp index c9212f7..8eac063 100644 --- a/src/gui/graphicsview/qgraphicswidget_p.cpp +++ b/src/gui/graphicsview/qgraphicswidget_p.cpp @@ -626,34 +626,6 @@ bool QGraphicsWidgetPrivate::hasDecoration() const return (windowFlags & Qt::Window) && (windowFlags & Qt::WindowTitleHint); } -/*! - \internal -*/ -void QGraphicsWidgetPrivate::setFocusWidget() -{ - // Update focus child chain. - QGraphicsWidget *widget = static_cast(q_ptr); - QGraphicsWidget *parent = widget; - bool hidden = !visible; - do { - parent->d_func()->focusChild = widget; - } while (!parent->isWindow() && (parent = parent->parentWidget()) && (!hidden || !parent->d_func()->visible)); -} - -/*! - \internal -*/ -void QGraphicsWidgetPrivate::clearFocusWidget() -{ - // Reset focus child chain. - QGraphicsWidget *parent = static_cast(q_ptr); - do { - if (parent->d_func()->focusChild != q_ptr) - break; - parent->d_func()->focusChild = 0; - } while (!parent->isWindow() && (parent = parent->parentWidget())); -} - /** * is called after a reparent has taken place to fix up the focus chain(s) */ @@ -670,12 +642,6 @@ void QGraphicsWidgetPrivate::fixFocusChainBeforeReparenting(QGraphicsWidget *new QGraphicsWidget *firstOld = 0; bool wasPreviousNew = true; - - if (focusChild) { - // Ensure that the current focus child doesn't leave pointers around - // before reparenting. - focusChild->clearFocus(); - } while (w != q) { bool isCurrentNew = q->isAncestorOf(w); diff --git a/src/gui/graphicsview/qgraphicswidget_p.h b/src/gui/graphicsview/qgraphicswidget_p.h index 0c34baa..92e520a 100644 --- a/src/gui/graphicsview/qgraphicswidget_p.h +++ b/src/gui/graphicsview/qgraphicswidget_p.h @@ -83,7 +83,6 @@ public: focusPolicy(Qt::NoFocus), focusNext(0), focusPrev(0), - focusChild(0), windowFlags(0), windowData(0), setWindowFrameMargins(false), @@ -178,9 +177,6 @@ public: Qt::FocusPolicy focusPolicy; QGraphicsWidget *focusNext; QGraphicsWidget *focusPrev; - QGraphicsWidget *focusChild; - void setFocusWidget(); - void clearFocusWidget(); // Windows Qt::WindowFlags windowFlags; diff --git a/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp b/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp index 011e480..9f1693d 100644 --- a/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp +++ b/tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp @@ -275,6 +275,9 @@ private slots: void itemHasNoContents(); void hitTestUntransformableItem(); void focusProxy(); + void autoDetectFocusProxy(); + void subFocus(); + void reverseCreateAutoFocusProxy(); // task specific tests below me void task141694_textItemEnsureVisible(); @@ -7464,5 +7467,109 @@ void tst_QGraphicsItem::focusProxy() QCOMPARE(item3->focusProxy(), (QGraphicsItem *)0); } +void tst_QGraphicsItem::autoDetectFocusProxy() +{ + QGraphicsScene scene; + QGraphicsItem *item = scene.addRect(0, 0, 10, 10); + item->setFlag(QGraphicsItem::ItemIsFocusable); + + QGraphicsItem *item2 = scene.addRect(0, 0, 10, 10); + item2->setParentItem(item); + item2->setFlag(QGraphicsItem::ItemIsFocusable); + + QGraphicsItem *item3 = scene.addRect(0, 0, 10, 10); + item3->setParentItem(item2); + item3->setFlag(QGraphicsItem::ItemIsFocusable); + + item->setFlag(QGraphicsItem::ItemAutoDetectsFocusProxy); + QCOMPARE(item->focusProxy(), (QGraphicsItem *)0); + + item2->setFocus(); + QCOMPARE(item->focusProxy(), item2); + item3->setFocus(); + QCOMPARE(item->focusProxy(), item3); + item3->clearFocus(); + QCOMPARE(item->focusProxy(), item3); +} + +void tst_QGraphicsItem::subFocus() +{ + // Construct a text item that's not part of a scene (yet) + // and has no parent. Setting focus on it will not make + // the item gain input focus; that requires a scene. But + // it does set subfocus, indicating that the item wishes + // to gain focus later. + QGraphicsTextItem *text = new QGraphicsTextItem("Hello"); + text->setTextInteractionFlags(Qt::TextEditorInteraction); + QVERIFY(!text->hasFocus()); + text->setFocus(); + QVERIFY(!text->hasFocus()); + QCOMPARE(text->focusItem(), (QGraphicsItem *)text); + + // Add a sibling. + QGraphicsTextItem *text2 = new QGraphicsTextItem("Hi"); + text2->setTextInteractionFlags(Qt::TextEditorInteraction); + text2->setPos(30, 30); + + // Add both items to a scene and check that it's text that + // got input focus. + QGraphicsScene scene; + scene.addItem(text); + scene.addItem(text2); + QVERIFY(text->hasFocus()); + + text->setData(0, "text"); + text2->setData(0, "text2"); + + // Remove text2 and set subfocus on it. Then readd. Reparent it onto the + // other item and see that although it becomes text's focus + // item, it does not immediately gain input focus. + scene.removeItem(text2); + text2->setFocus(); + scene.addItem(text2); + QCOMPARE(text2->focusItem(), (QGraphicsItem *)text2); + text2->setParentItem(text); + qDebug() << text->data(0).toString() << (void*)(QGraphicsItem *)text; + qDebug() << text2->data(0).toString() << (void*)(QGraphicsItem *)text2; + QCOMPARE(text->focusItem(), (QGraphicsItem *)text2); + QVERIFY(text->hasFocus()); + QVERIFY(!text2->hasFocus()); + + // Remove both items from the scene, restore subfocus and + // readd them. Now the subfocus should kick in and give + // text2 focus. + scene.removeItem(text); + QCOMPARE(text->focusItem(), (QGraphicsItem *)0); + QCOMPARE(text2->focusItem(), (QGraphicsItem *)0); + text2->setFocus(); + QCOMPARE(text->focusItem(), (QGraphicsItem *)text2); + QCOMPARE(text2->focusItem(), (QGraphicsItem *)text2); + scene.addItem(text); + + // Hiding and showing text should pass focus to text2. + QCOMPARE(text->focusItem(), (QGraphicsItem *)text2); + QVERIFY(text2->hasFocus()); +} + +void tst_QGraphicsItem::reverseCreateAutoFocusProxy() +{ + QGraphicsTextItem *text = new QGraphicsTextItem; + text->setTextInteractionFlags(Qt::TextEditorInteraction); + text->setFlag(QGraphicsItem::ItemAutoDetectsFocusProxy); + + QGraphicsTextItem *text2 = new QGraphicsTextItem; + text2->setTextInteractionFlags(Qt::TextEditorInteraction); + text2->setFocus(); + QVERIFY(!text2->hasFocus()); + QCOMPARE(text->focusProxy(), (QGraphicsItem *)0); + text2->setParentItem(text); + QCOMPARE(text->focusProxy(), (QGraphicsItem *)text2); + QCOMPARE(text->focusItem(), (QGraphicsItem *)text2); + + QGraphicsScene scene; + scene.addItem(text); + QVERIFY(text2->hasFocus()); +} + QTEST_MAIN(tst_QGraphicsItem) #include "tst_qgraphicsitem.moc" -- cgit v0.12