diff options
-rw-r--r-- | src/gui/graphicsview/qgraphicsitem.cpp | 120 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsitem.h | 5 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsitem_p.h | 9 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicsscene.cpp | 29 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicswidget.cpp | 8 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicswidget_p.cpp | 34 | ||||
-rw-r--r-- | src/gui/graphicsview/qgraphicswidget_p.h | 4 | ||||
-rw-r--r-- | 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<QGraphicsWidget *>(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<QGraphicsWidget *>(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<QGraphicsWidget *>(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<QGraphicsWidget *>(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<QGraphicsWidget *>(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<QGraphicsWidget *>(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<QGraphicsWidget *>(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<QGraphicsWidget *>(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" |