summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Aardal Hanssen <andreas.aardal.hanssen@nokia.com>2009-07-22 07:19:12 (GMT)
committerAndreas Aardal Hanssen <andreas.aardal.hanssen@nokia.com>2009-07-23 06:59:47 (GMT)
commit3f630677d415d4a67938fa6cf64754c80aa30ba0 (patch)
tree9d521157a0022ac3eae3e05a8888b952c8421962
parent708150e3a88fdc5f7c3d1998f2b5132cea264a71 (diff)
downloadQt-3f630677d415d4a67938fa6cf64754c80aa30ba0.zip
Qt-3f630677d415d4a67938fa6cf64754c80aa30ba0.tar.gz
Qt-3f630677d415d4a67938fa6cf64754c80aa30ba0.tar.bz2
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
-rw-r--r--src/gui/graphicsview/qgraphicsitem.cpp76
-rw-r--r--src/gui/graphicsview/qgraphicsitem.h3
-rw-r--r--src/gui/graphicsview/qgraphicsitem_p.h2
-rw-r--r--src/gui/graphicsview/qgraphicsscene.cpp106
-rw-r--r--src/gui/graphicsview/qgraphicsscene_p.h2
-rw-r--r--tests/auto/qgraphicsitem/tst_qgraphicsitem.cpp120
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<QGraphicsWidget *>(this)->d_func()->setFocusWidget();
+ static_cast<QGraphicsWidget *>(item)->d_func()->setFocusWidget();
}
}
@@ -2508,13 +2514,73 @@ void QGraphicsItem::clearFocus()
// Invisible widget items with focus must explicitly clear subfocus.
static_cast<QGraphicsWidget *>(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<QGraphicsItem *, QGraphicsItem *>::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<QGraphicsWidget *>(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<QGraphicsWidget *>(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<QGraphicsItem *, QGraphicsItem *> focusProxyReverseMap;
QList<QGraphicsWidget *> 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<QGraphicsItem*>(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"