Repair QGraphicsWidget focus chain when used with ItemIsPanel.

Add handling of the focus chain to QGraphicsItem::setFlags(), so that
the focus chain is repaired (panels pop out of the chain and non-panels
merge back in) when the ItemIsPanel flag is toggled. Add handling focus
chain to QGraphicsWidgetPrivate::fixFocusChainBeforeReparenting for
panels.

Before this fix, you must enable the ItemIsPanel flag before adding
the item as a child to a parent panel, and you lose focus when using
the tab key to focus around a panel after it has been reparented into
another panel.

Task-number: QTBUG-28187
Change-Id: I1d0d81a90697eaf715a8a337c8bf6c2159329e68
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
This commit is contained in:
Andreas Aardal Hanssen 2012-11-23 21:52:41 +01:00 committed by The Qt Project
parent 9c02a28552
commit 4a9c3b2433
4 changed files with 196 additions and 77 deletions

View File

@ -1884,15 +1884,46 @@ void QGraphicsItem::setFlags(GraphicsItemFlags flags)
d_ptr->scene->d_func()->updateInputMethodSensitivityInViews();
}
if ((flags & ItemIsPanel) != (oldFlags & ItemIsPanel)) {
bool becomesPanel = (flags & ItemIsPanel);
if ((d_ptr->panelModality != NonModal) && d_ptr->scene) {
// update the panel's modal state
if (becomesPanel)
d_ptr->scene->d_func()->enterModal(this);
else
d_ptr->scene->d_func()->leaveModal(this);
}
if (d_ptr->isWidget && (becomesPanel || parentWidget())) {
QGraphicsWidget *w = static_cast<QGraphicsWidget *>(this);
QGraphicsWidget *focusFirst = w;
QGraphicsWidget *focusLast = w;
for (;;) {
QGraphicsWidget *test = focusLast->d_func()->focusNext;
if (!w->isAncestorOf(test) || test == w)
break;
focusLast = test;
}
if ((d_ptr->panelModality != NonModal)
&& d_ptr->scene
&& (flags & ItemIsPanel) != (oldFlags & ItemIsPanel)) {
// update the panel's modal state
if (flags & ItemIsPanel)
d_ptr->scene->d_func()->enterModal(this);
else
d_ptr->scene->d_func()->leaveModal(this);
if (becomesPanel) {
// unlink own widgets from focus chain
QGraphicsWidget *beforeMe = w->d_func()->focusPrev;
QGraphicsWidget *afterMe = focusLast->d_func()->focusNext;
beforeMe->d_func()->focusNext = afterMe;
afterMe->d_func()->focusPrev = beforeMe;
focusFirst->d_func()->focusPrev = focusLast;
focusLast->d_func()->focusNext = focusFirst;
if (!isAncestorOf(focusFirst->d_func()->focusNext))
focusFirst->d_func()->focusNext = w;
} else if (QGraphicsWidget *pw = parentWidget()) {
// link up own widgets to focus chain
QGraphicsWidget *beforeMe = pw;
QGraphicsWidget *afterMe = pw->d_func()->focusNext;
beforeMe->d_func()->focusNext = w;
afterMe->d_func()->focusPrev = focusLast;
w->d_func()->focusPrev = beforeMe;
focusLast->d_func()->focusNext = afterMe;
}
}
}
if (d_ptr->scene) {
@ -2274,7 +2305,7 @@ void QGraphicsItemPrivate::setVisibleHelper(bool newVisible, bool explicitly,
scene->d_func()->leaveModal(q_ptr);
}
if (hasFocus && scene) {
// Hiding the closest non-panel ancestor of the focus item
// Hiding the focus item or the closest non-panel ancestor of the focus item
QGraphicsItem *focusItem = scene->focusItem();
bool clear = true;
if (isWidget && !focusItem->isPanel()) {

View File

@ -607,7 +607,7 @@ void QGraphicsScenePrivate::removeItemHelper(QGraphicsItem *item)
q->removeItem(item->d_ptr->children.at(i));
}
if (!item->d_ptr->inDestructor && item == tabFocusFirst) {
if (!item->d_ptr->inDestructor && !item->parentItem() && item->isWidget()) {
QGraphicsWidget *widget = static_cast<QGraphicsWidget *>(item);
widget->d_func()->fixFocusChainBeforeReparenting(0, oldScene, 0);
}
@ -2528,14 +2528,13 @@ void QGraphicsScene::addItem(QGraphicsItem *item)
// No first tab focus widget - make this the first tab focus
// widget.
d->tabFocusFirst = widget;
} else if (!widget->parentWidget()) {
} else if (!widget->parentWidget() && !widget->isPanel()) {
// Adding a widget that is not part of a tab focus chain.
QGraphicsWidget *last = d->tabFocusFirst->d_func()->focusPrev;
QGraphicsWidget *lastNew = widget->d_func()->focusPrev;
last->d_func()->focusNext = widget;
widget->d_func()->focusPrev = last;
d->tabFocusFirst->d_func()->focusPrev = lastNew;
lastNew->d_func()->focusNext = d->tabFocusFirst;
QGraphicsWidget *myNewPrev = d->tabFocusFirst->d_func()->focusPrev;
myNewPrev->d_func()->focusNext = widget;
widget->d_func()->focusPrev->d_func()->focusNext = d->tabFocusFirst;
d->tabFocusFirst->d_func()->focusPrev = widget->d_func()->focusPrev;
widget->d_func()->focusPrev = myNewPrev;
}
}
@ -5330,7 +5329,7 @@ bool QGraphicsScene::focusNextPrevChild(bool next)
return true;
}
}
if (!d->tabFocusFirst) {
if (!item && !d->tabFocusFirst) {
// No widgets...
return false;
}
@ -5342,8 +5341,10 @@ bool QGraphicsScene::focusNextPrevChild(bool next)
} else {
QGraphicsWidget *test = static_cast<QGraphicsWidget *>(item);
widget = next ? test->d_func()->focusNext : test->d_func()->focusPrev;
if ((next && widget == d->tabFocusFirst) || (!next && widget == d->tabFocusFirst->d_func()->focusPrev))
if (!widget->panel() && ((next && widget == d->tabFocusFirst) || (!next && widget == d->tabFocusFirst->d_func()->focusPrev))) {
// Tab out of the scene.
return false;
}
}
QGraphicsWidget *widgetThatHadFocus = widget;

View File

@ -764,73 +764,59 @@ bool QGraphicsWidgetPrivate::hasDecoration() const
void QGraphicsWidgetPrivate::fixFocusChainBeforeReparenting(QGraphicsWidget *newParent, QGraphicsScene *oldScene, QGraphicsScene *newScene)
{
Q_Q(QGraphicsWidget);
Q_ASSERT(focusNext && focusPrev);
QGraphicsWidget *n = q; //last one in 'new' list
QGraphicsWidget *o = 0; //last one in 'old' list
QGraphicsWidget *w = focusNext;
QGraphicsWidget *firstOld = 0;
bool wasPreviousNew = true;
while (w != q) {
bool isCurrentNew = q->isAncestorOf(w);
if (isCurrentNew) {
if (!wasPreviousNew) {
n->d_func()->focusNext = w;
w->d_func()->focusPrev = n;
}
n = w;
} else /*if (!isCurrentNew)*/ {
if (wasPreviousNew) {
if (o) {
o->d_func()->focusNext = w;
w->d_func()->focusPrev = o;
} else {
firstOld = w;
}
}
o = w;
}
w = w->d_func()->focusNext;
wasPreviousNew = isCurrentNew;
if (q_ptr->isPanel()) {
// panels are never a part of their parent's or ancestors' focus
// chains. so reparenting a panel is easy; there's nothing to
// do.
return;
}
// repair the 'old' chain
if (firstOld) {
o->d_func()->focusNext = firstOld;
firstOld->d_func()->focusPrev = o;
// we're not a panel, so find the first widget in the focus chain
// (this), and the last (this, or the last widget that is still
// a descendent of this). also find the widgets that currently /
// before reparenting point to this widgets' focus chain.
QGraphicsWidget *focusFirst = q;
QGraphicsWidget *focusBefore = focusPrev;
QGraphicsWidget *focusLast = focusFirst;
QGraphicsWidget *focusAfter = focusNext;
do {
if (!q->isAncestorOf(focusAfter))
break;
focusLast = focusAfter;
} while ((focusAfter = focusAfter->d_func()->focusNext));
if (!parent && oldScene && oldScene != newScene && oldScene->d_func()->tabFocusFirst == q) {
// detach from old scene's top level focus chain.
oldScene->d_func()->tabFocusFirst = (focusAfter != q) ? focusAfter : 0;
}
// update tabFocusFirst for oldScene if the item is going to be removed from oldScene
if (newParent)
newScene = newParent->scene();
// detach from current focus chain; skip this widget subtree.
focusBefore->d_func()->focusNext = focusAfter;
focusAfter->d_func()->focusPrev = focusBefore;
if (oldScene && newScene != oldScene)
oldScene->d_func()->tabFocusFirst = (firstOld && firstOld->scene() == oldScene) ? firstOld : 0;
if (newParent) {
// attach to new parent's focus chain as the last element
// in its chain.
QGraphicsWidget *newFocusFirst = newParent;
QGraphicsWidget *newFocusLast = newFocusFirst;
QGraphicsWidget *newFocusAfter = newFocusFirst->d_func()->focusNext;
do {
if (!newParent->isAncestorOf(newFocusAfter))
break;
newFocusLast = newFocusAfter;
} while ((newFocusAfter = newFocusAfter->d_func()->focusNext));
QGraphicsItem *topLevelItem = newParent ? newParent->topLevelItem() : 0;
QGraphicsWidget *topLevel = 0;
if (topLevelItem && topLevelItem->isWidget())
topLevel = static_cast<QGraphicsWidget *>(topLevelItem);
if (topLevel && newParent) {
QGraphicsWidget *last = topLevel->d_func()->focusPrev;
// link last with new chain
last->d_func()->focusNext = q;
focusPrev = last;
// link last in chain with
topLevel->d_func()->focusPrev = n;
n->d_func()->focusNext = topLevel;
newFocusLast->d_func()->focusNext = q;
focusLast->d_func()->focusNext = newFocusAfter;
newFocusAfter->d_func()->focusPrev = focusLast;
focusPrev = newFocusLast;
} else {
// q is the start of the focus chain
n->d_func()->focusNext = q;
focusPrev = n;
// no new parent, so just link up our own prev->last widgets.
focusPrev = focusLast;
focusLast->d_func()->focusNext = q;
}
}
void QGraphicsWidgetPrivate::setLayout_helper(QGraphicsLayout *l)

View File

@ -172,12 +172,12 @@ private slots:
void initialShow2();
void itemChangeEvents();
void itemSendGeometryPosChangesDeactivated();
void fontPropagatesResolveToChildren();
void fontPropagatesResolveToGrandChildren();
void fontPropagatesResolveInParentChange();
void fontPropagatesResolveViaNonWidget();
void fontPropagatesResolveFromScene();
void tabFocus();
// Task fixes
void task236127_bspTreeIndexFails();
@ -3303,6 +3303,107 @@ void tst_QGraphicsWidget::itemSendGeometryPosChangesDeactivated()
QCOMPARE(item->geometry(), QRectF(10, 10, 60, 60));
}
class TabFocusWidget : public QGraphicsWidget
{
Q_OBJECT
public:
TabFocusWidget(const QString &name, QGraphicsItem *parent = 0)
: QGraphicsWidget(parent)
{ setFocusPolicy(Qt::TabFocus); setData(0, name); }
};
void verifyTabFocus(QGraphicsScene *scene, const QList<QGraphicsWidget *> &chain, bool wrapsAround)
{
QKeyEvent tabEvent(QEvent::KeyPress, Qt::Key_Tab, 0);
QKeyEvent backtabEvent(QEvent::KeyPress, Qt::Key_Backtab, 0);
for (int i = 0; i < chain.size(); ++i)
chain.at(i)->clearFocus();
int n = chain.size() * (wrapsAround ? 3 : 1);
for (int i = 0; i < n; ++i)
{
if (i == 0)
chain.at(0)->setFocus();
else
qApp->sendEvent(scene, &tabEvent);
QVERIFY(chain.at(i % chain.size())->hasFocus());
QCOMPARE(scene->focusItem(), chain.at(i % chain.size()));
}
for (int i = n - 2; i >= 0; --i)
{
qApp->sendEvent(scene, &backtabEvent);
QVERIFY(chain.at(i % chain.size())->hasFocus());
QCOMPARE(scene->focusItem(), chain.at(i % chain.size()));
}
}
void tst_QGraphicsWidget::tabFocus()
{
QGraphicsScene scene;
scene.setFocus();
QEvent activate(QEvent::WindowActivate);
qApp->sendEvent(&scene, &activate);
TabFocusWidget *widget = new TabFocusWidget("1");
scene.addItem(widget);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget, false);
TabFocusWidget *widget2 = new TabFocusWidget("2");
scene.addItem(widget2);
scene.setFocusItem(0);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget << widget2, false);
TabFocusWidget *widget3 = new TabFocusWidget("3");
widget3->setFlag(QGraphicsItem::ItemIsPanel);
scene.addItem(widget3);
QCOMPARE(scene.activePanel(), (QGraphicsItem *)widget3);
scene.setActivePanel(0);
scene.setFocusItem(0);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget << widget2, false);
scene.setActivePanel(widget3);
widget3->setFocus();
QCOMPARE(scene.focusItem(), (QGraphicsItem *)widget3);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget3, true);
TabFocusWidget *widget4 = new TabFocusWidget("4");
widget4->setParentItem(widget3);
QVERIFY(widget3->hasFocus());
widget3->clearFocus();
QVERIFY(!widget3->focusItem());
QCOMPARE(scene.activePanel(), (QGraphicsItem *)widget3);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget3 << widget4, true);
QGraphicsWidget *widget5 = new QGraphicsWidget; widget5->setData(0, QLatin1String("5"));
widget5->setParentItem(widget3);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget3 << widget4, true);
widget5->setFocusPolicy(Qt::TabFocus);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget3 << widget4 << widget5, true);
TabFocusWidget *widget6 = new TabFocusWidget("6");
widget6->setParentItem(widget4);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget3 << widget4 << widget6 << widget5, true);
TabFocusWidget *widget7 = new TabFocusWidget("7", widget6);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget3 << widget4 << widget6 << widget7 << widget5, true);
TabFocusWidget *widget8 = new TabFocusWidget("8", widget6);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget3 << widget4 << widget6 << widget7 << widget8 << widget5, true);
widget6->setFlag(QGraphicsItem::ItemIsPanel);
widget6->setActive(true);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget6 << widget7 << widget8, true);
widget3->setActive(true);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget3 << widget4 << widget5, true);
widget6->setFlag(QGraphicsItem::ItemIsPanel, false);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget3 << widget4 << widget6 << widget7 << widget8 << widget5, true);
scene.removeItem(widget6);
verifyTabFocus(&scene, QList<QGraphicsWidget *>() << widget3 << widget4 << widget5, true);
delete widget6;
}
void tst_QGraphicsWidget::QT_BUG_6544_tabFocusFirstUnsetWhenRemovingItems()
{
QGraphicsScene scene;