summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/widgets/kernel/qwidget.cpp99
-rw-r--r--tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp94
2 files changed, 149 insertions, 44 deletions
diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp
index 8e6b44c370..be9287d39b 100644
--- a/src/widgets/kernel/qwidget.cpp
+++ b/src/widgets/kernel/qwidget.cpp
@@ -6945,6 +6945,9 @@ bool QWidget::isActiveWindow() const
/*!
Puts the \a second widget after the \a first widget in the focus order.
+ It effectively removes the \a second widget from its focus chain and
+ inserts it after the \a first widget.
+
Note that since the tab order of the \a second widget is changed, you
should order a chain like this:
@@ -6957,11 +6960,19 @@ bool QWidget::isActiveWindow() const
If \a first or \a second has a focus proxy, setTabOrder()
correctly substitutes the proxy.
+ \note Since Qt 5.10: A widget that has a child as focus proxy is understood as
+ a compound widget. When setting a tab order between one or two compound widgets, the
+ local tab order inside each will be preserved. This means that if both widgets are
+ compound widgets, the resulting tab order will be from the last child inside
+ \a first, to the first child inside \a second.
+
\sa setFocusPolicy(), setFocusProxy(), {Keyboard Focus in Widgets}
*/
void QWidget::setTabOrder(QWidget* first, QWidget *second)
{
- if (!first || !second || first->focusPolicy() == Qt::NoFocus || second->focusPolicy() == Qt::NoFocus)
+ if (!first || !second || first == second
+ || first->focusPolicy() == Qt::NoFocus
+ || second->focusPolicy() == Qt::NoFocus)
return;
if (Q_UNLIKELY(first->window() != second->window())) {
@@ -6969,54 +6980,56 @@ void QWidget::setTabOrder(QWidget* first, QWidget *second)
return;
}
- QWidget *fp = first->focusProxy();
- if (fp) {
- // If first is redirected, set first to the last child of first
- // that can take keyboard focus so that second is inserted after
- // that last child, and the focus order within first is (more
- // likely to be) preserved.
- QList<QWidget *> l = first->findChildren<QWidget *>();
- for (int i = l.size()-1; i >= 0; --i) {
- QWidget * next = l.at(i);
- if (next->window() == fp->window()) {
- fp = next;
- if (fp->focusPolicy() != Qt::NoFocus)
- break;
- }
- }
- first = fp;
- }
+ auto determineLastFocusChild = [](QWidget *target, QWidget *&lastFocusChild)
+ {
+ // Since we need to repeat the same logic for both 'first' and 'second', we add a function that
+ // determines the last focus child for a widget, taking proxies and compound widgets into account.
+ // If the target is not a compound widget (it doesn't have a focus proxy that points to a child),
+ // 'lastFocusChild' will be set to the target itself.
+ lastFocusChild = target;
+
+ QWidget *focusProxy = target->d_func()->deepestFocusProxy();
+ if (!focusProxy || !target->isAncestorOf(focusProxy))
+ return;
- if (fp == second)
- return;
+ lastFocusChild = focusProxy;
- if (QWidget *sp = second->focusProxy())
- second = sp;
+ for (QWidget *focusNext = lastFocusChild->d_func()->focus_next;
+ focusNext != focusProxy && target->isAncestorOf(focusNext) && focusNext->window() == focusProxy->window();
+ focusNext = focusNext->d_func()->focus_next) {
+ if (focusNext->focusPolicy() != Qt::NoFocus)
+ lastFocusChild = focusNext;
+ }
+ };
-// QWidget *fp = first->d_func()->focus_prev;
- QWidget *fn = first->d_func()->focus_next;
+ QWidget *lastFocusChildOfFirst, *lastFocusChildOfSecond;
+ determineLastFocusChild(first, lastFocusChildOfFirst);
+ determineLastFocusChild(second, lastFocusChildOfSecond);
- if (fn == second || first == second)
+ // If the tab order is already correct, exit early
+ if (lastFocusChildOfFirst->d_func()->focus_next == second)
return;
- QWidget *sp = second->d_func()->focus_prev;
- QWidget *sn = second->d_func()->focus_next;
-
- fn->d_func()->focus_prev = second;
- first->d_func()->focus_next = second;
-
- second->d_func()->focus_next = fn;
- second->d_func()->focus_prev = first;
-
- sp->d_func()->focus_next = sn;
- sn->d_func()->focus_prev = sp;
-
-
- Q_ASSERT(first->d_func()->focus_next->d_func()->focus_prev == first);
- Q_ASSERT(first->d_func()->focus_prev->d_func()->focus_next == first);
-
- Q_ASSERT(second->d_func()->focus_next->d_func()->focus_prev == second);
- Q_ASSERT(second->d_func()->focus_prev->d_func()->focus_next == second);
+ // Note that we need to handle two different sections in the tab chain; The section
+ // that 'first' belongs to (firstSection), where we are about to insert 'second', and
+ // the section that 'second' used be a part of (secondSection). When we pull 'second'
+ // out of the second section and insert it into the first, we also need to ensure
+ // that we leave the second section in a connected state.
+ QWidget *firstChainOldSecond = lastFocusChildOfFirst->d_func()->focus_next;
+ QWidget *secondChainNewFirst = second->d_func()->focus_prev;
+ QWidget *secondChainNewSecond = lastFocusChildOfSecond->d_func()->focus_next;
+
+ // Insert 'second' after 'first'
+ lastFocusChildOfFirst->d_func()->focus_next = second;
+ second->d_func()->focus_prev = lastFocusChildOfFirst;
+
+ // The widget that used to be 'second' in the first section, should now become 'third'
+ lastFocusChildOfSecond->d_func()->focus_next = firstChainOldSecond;
+ firstChainOldSecond->d_func()->focus_prev = lastFocusChildOfSecond;
+
+ // Repair the second section after we pulled 'second' out of it
+ secondChainNewFirst->d_func()->focus_next = secondChainNewSecond;
+ secondChainNewSecond->d_func()->focus_prev = secondChainNewFirst;
}
/*!\internal
diff --git a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp
index 36258d8196..c328de37ca 100644
--- a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp
+++ b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp
@@ -199,6 +199,7 @@ private slots:
void defaultTabOrder();
void reverseTabOrder();
void tabOrderWithProxy();
+ void tabOrderWithCompoundWidgets();
#ifdef Q_OS_WIN
void activation();
#endif
@@ -1665,22 +1666,26 @@ public:
class Composite : public QFrame
{
public:
- Composite(QWidget* parent = 0, const char* name = 0)
+ Composite(QWidget* parent = 0, const QString &name = 0)
: QFrame(parent)
{
setObjectName(name);
lineEdit1 = new QLineEdit;
lineEdit2 = new QLineEdit;
+ lineEdit3 = new QLineEdit;
+ lineEdit3->setEnabled(false);
QHBoxLayout* hbox = new QHBoxLayout(this);
hbox->addWidget(lineEdit1);
hbox->addWidget(lineEdit2);
+ hbox->addWidget(lineEdit3);
}
public:
QLineEdit *lineEdit1;
QLineEdit *lineEdit2;
+ QLineEdit *lineEdit3;
};
void tst_QWidget::defaultTabOrder()
@@ -1851,6 +1856,93 @@ void tst_QWidget::tabOrderWithProxy()
QVERIFY(firstEdit->hasFocus());
}
+void tst_QWidget::tabOrderWithCompoundWidgets()
+{
+ const int compositeCount = 4;
+ Container container;
+ Composite *composite[compositeCount];
+
+ QLineEdit *firstEdit = new QLineEdit();
+ container.box->addWidget(firstEdit);
+
+ for (int i = 0; i < compositeCount; i++) {
+ composite[i] = new Composite(0, QStringLiteral("Composite: ") + QString::number(i));
+ container.box->addWidget(composite[i]);
+
+ // Let the composite handle focus, and set a child as focus proxy (use the second child, just
+ // to ensure that we don't just tab to the first child by coinsidence). This will make the
+ // composite "compound". Also enable the last line edit to have a bit more data to check when
+ // tabbing forwards.
+ composite[i]->setFocusPolicy(Qt::StrongFocus);
+ composite[i]->setFocusProxy(composite[i]->lineEdit2);
+ composite[i]->lineEdit3->setEnabled(true);
+ }
+
+ QLineEdit *lastEdit = new QLineEdit();
+ container.box->addWidget(lastEdit);
+
+ // Reverse tab order between each composite
+ // (but not inside them), including first and last line edit.
+ // The result should not affect local tab order inside each
+ // composite, only between them.
+ QWidget::setTabOrder(lastEdit, composite[compositeCount - 1]);
+ for (int i = compositeCount - 1; i >= 1; --i)
+ QWidget::setTabOrder(composite[i], composite[i-1]);
+ QWidget::setTabOrder(composite[0], firstEdit);
+
+ container.show();
+ container.activateWindow();
+ qApp->setActiveWindow(&container);
+ QVERIFY(QTest::qWaitForWindowActive(&container));
+
+ lastEdit->setFocus();
+ QTRY_VERIFY(lastEdit->hasFocus());
+
+ // Check that focus moves between the line edits in the normal
+ // order when tabbing inside each compound, but in the reverse
+ // order when tabbing between them. Since the composites have
+ // lineEdit2 as focus proxy, lineEdit2 will be the first with focus
+ // when the compound gets focus, and lineEdit1 will therefore be skipped.
+ for (int i = compositeCount - 1; i >= 0; --i) {
+ container.tab();
+ Composite *c = composite[i];
+ QVERIFY(!c->lineEdit1->hasFocus());
+ QVERIFY(c->lineEdit2->hasFocus());
+ QVERIFY(!c->lineEdit3->hasFocus());
+ container.tab();
+ QVERIFY(!c->lineEdit1->hasFocus());
+ QVERIFY(!c->lineEdit2->hasFocus());
+ QVERIFY(c->lineEdit3->hasFocus());
+ }
+
+ container.tab();
+ QVERIFY(firstEdit->hasFocus());
+
+ // Check that focus moves in reverse order when backTab inside the composites, but
+ // in the 'correct' order when backTab between them (since the composites are in reverse tab
+ // order from before, which cancels it out). Note that when we backtab into a compound, we start
+ // at lineEdit3 rather than the focus proxy, since that is the reverse of what happens when we tab
+ // forward. And this time we will also backtab to lineEdit1, since there is no focus proxy that interferes.
+ for (int i = 0; i < compositeCount; ++i) {
+ container.backTab();
+ Composite *c = composite[i];
+ QVERIFY(!c->lineEdit1->hasFocus());
+ QVERIFY(!c->lineEdit2->hasFocus());
+ QVERIFY(c->lineEdit3->hasFocus());
+ container.backTab();
+ QVERIFY(!c->lineEdit1->hasFocus());
+ QVERIFY(c->lineEdit2->hasFocus());
+ QVERIFY(!c->lineEdit3->hasFocus());
+ container.backTab();
+ QVERIFY(c->lineEdit1->hasFocus());
+ QVERIFY(!c->lineEdit2->hasFocus());
+ QVERIFY(!c->lineEdit3->hasFocus());
+ }
+
+ container.backTab();
+ QVERIFY(lastEdit->hasFocus());
+}
+
#ifdef Q_OS_WIN
void tst_QWidget::activation()
{