summaryrefslogtreecommitdiffstats
path: root/src/widgets
diff options
context:
space:
mode:
authorAxel Spoerl <axel.spoerl@qt.io>2024-01-26 11:42:17 +0100
committerAxel Spoerl <axel.spoerl@qt.io>2024-04-04 16:13:29 +0200
commit58d5d4b7c2faaeaa2c2ccdabb3da6d37f9db880a (patch)
treef17f2a1f9a8542c4e4e8931d9df9f842b2712d6d /src/widgets
parentf7a809ab73182ef25578eb8fd200c223fa3eb0f5 (diff)
Abstract QWidget focus chain management
The focus chain in widgets is implemented as a double-linked list, using QWidgetPrivate::focus_next and focus_prev as pointers to the next/previous widget in the focus chain. These pointers are manipulated directly at many places, which is error prone and difficult to read. Build an abstraction layer and remove direct usage of focus_next and focus_prev. Provide functions to insert and remove widgets to/from the focus chain. Add a test function to tst_QWidget. Task-number: QTBUG-121478 Change-Id: Ide6379c0197137e420352a2976912f2de8a8b338 Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io>
Diffstat (limited to 'src/widgets')
-rw-r--r--src/widgets/kernel/qwidget.cpp556
-rw-r--r--src/widgets/kernel/qwidget_p.h35
2 files changed, 455 insertions, 136 deletions
diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp
index 30daee1b79..a7a0b0362f 100644
--- a/src/widgets/kernel/qwidget.cpp
+++ b/src/widgets/kernel/qwidget.cpp
@@ -6,6 +6,7 @@
#include "qapplication_p.h"
#include "qbrush.h"
#include "qcursor.h"
+#include "private/qduplicatetracker_p.h"
#include "qevent.h"
#include "qlayout.h"
#if QT_CONFIG(menu)
@@ -84,6 +85,7 @@ using namespace Qt::StringLiterals;
Q_LOGGING_CATEGORY(lcWidgetPainting, "qt.widgets.painting", QtWarningMsg);
Q_LOGGING_CATEGORY(lcWidgetShowHide, "qt.widgets.showhide", QtWarningMsg);
Q_LOGGING_CATEGORY(lcWidgetWindow, "qt.widgets.window", QtWarningMsg);
+Q_LOGGING_CATEGORY(lcFocus, "qt.widgets.focus")
#ifndef QT_NO_DEBUG_STREAM
namespace {
@@ -818,12 +820,7 @@ struct QWidgetExceptionCleaner
Q_UNUSED(d);
#else
QWidgetPrivate::allWidgets->remove(that);
- if (d->focus_next != that) {
- if (d->focus_next)
- d->focus_next->d_func()->focus_prev = d->focus_prev;
- if (d->focus_prev)
- d->focus_prev->d_func()->focus_next = d->focus_next;
- }
+ d->removeFromFocusChain();
#endif
}
};
@@ -991,7 +988,7 @@ void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
//give potential windows a bigger "pre-initial" size; create() will give them a new size later
data.crect = parentWidget ? QRect(0,0,100,30) : QRect(0,0,640,480);
- focus_next = focus_prev = q;
+ initFocusChain();
if ((f & Qt::WindowType_Mask) == Qt::Desktop)
q->create();
@@ -1477,17 +1474,9 @@ QWidget::~QWidget()
// delete layout while we still are a valid widget
delete d->layout;
d->layout = nullptr;
- // Remove myself from focus list
-
- Q_ASSERT(d->focus_next->d_func()->focus_prev == this);
- Q_ASSERT(d->focus_prev->d_func()->focus_next == this);
-
- if (d->focus_next != this) {
- d->focus_next->d_func()->focus_prev = d->focus_prev;
- d->focus_prev->d_func()->focus_next = d->focus_next;
- d->focus_next = d->focus_prev = nullptr;
- }
+ // Remove this from focus list
+ d->removeFromFocusChain(QWidgetPrivate::FocusChainRemovalRule::AssertConsistency);
QT_TRY {
#if QT_CONFIG(graphicsview)
@@ -6406,39 +6395,18 @@ void QWidget::setFocusProxy(QWidget * w)
break;
}
Q_ASSERT(firstChild); // can't be nullptr since w is a child
- QWidget *oldNext = d->focus_next;
- QWidget *oldPrev = d->focus_prev;
- oldNext->d_func()->focus_prev = oldPrev;
- oldPrev->d_func()->focus_next = oldNext;
-
- oldPrev = firstChild->d_func()->focus_prev;
- d->focus_next = firstChild;
- d->focus_prev = oldPrev;
- oldPrev->d_func()->focus_next = this;
- firstChild->d_func()->focus_prev = this;
+ d->insertIntoFocusChainBefore(firstChild);
} else if (w && w->isAncestorOf(this)) {
// If the focus proxy is a parent, 'this' has to be inserted directly after its parent in the focus chain
// remove it from the chain and insert this into the focus chain after its parent
// is this the case already?
- QWidget *parentsNext = w->d_func()->focus_next;
+ QWidget *parentsNext = w->nextInFocusChain();
if (parentsNext == this) {
// nothing to do.
- Q_ASSERT(d->focus_prev == w);
+ Q_ASSERT(previousInFocusChain() == w);
} else {
- // Remove 'this' from the focus chain by making prev and next point directly to each other
- QWidget *myOldNext = d->focus_next;
- QWidget *myOldPrev = d->focus_prev;
- if (myOldNext && myOldPrev) {
- myOldNext->d_func()->focus_prev = myOldPrev;
- myOldPrev->d_func()->focus_next = myOldNext;
- }
-
- // Insert 'this' behind the parent
- w->d_func()->focus_next = this;
- d->focus_prev = w;
- d->focus_next = parentsNext;
- parentsNext->d_func()->focus_prev = this;
+ d->QWidgetPrivate::insertIntoFocusChainAfter(w);
}
}
@@ -6871,7 +6839,8 @@ QObject *QWidgetPrivate::focusObject()
*/
QWidget *QWidget::nextInFocusChain() const
{
- return const_cast<QWidget *>(d_func()->focus_next);
+ Q_D(const QWidget);
+ return d->nextPrevElementInFocusChain(QWidgetPrivate::FocusDirection::Next);
}
/*!
@@ -6884,7 +6853,8 @@ QWidget *QWidget::nextInFocusChain() const
*/
QWidget *QWidget::previousInFocusChain() const
{
- return const_cast<QWidget *>(d_func()->focus_prev);
+ Q_D(const QWidget);
+ return d->nextPrevElementInFocusChain(QWidgetPrivate::FocusDirection::Previous);
}
/*!
@@ -7039,9 +7009,9 @@ void QWidget::setTabOrder(QWidget* first, QWidget *second)
}
} else if (target->isAncestorOf(focusProxy)) {
lastFocusChild = focusProxy;
- for (QWidget *focusNext = lastFocusChild->d_func()->focus_next;
+ for (QWidget *focusNext = lastFocusChild->nextInFocusChain();
focusNext != focusProxy && target->isAncestorOf(focusNext) && focusNext->window() == focusProxy->window();
- focusNext = focusNext->d_func()->focus_next) {
+ focusNext = focusNext->nextInFocusChain()) {
if (focusNext == noFurtherThan)
break;
if (focusNext->focusPolicy() != Qt::NoFocus)
@@ -7050,13 +7020,6 @@ void QWidget::setTabOrder(QWidget* first, QWidget *second)
}
return lastFocusChild;
};
- auto setPrev = [](QWidget *w, QWidget *prev) {
- w->d_func()->focus_prev = prev;
- };
- auto setNext = [](QWidget *w, QWidget *next) {
- w->d_func()->focus_next = next;
- };
-
// detect inflection in case we have compound widgets
QWidget *lastFocusChildOfFirst = determineLastFocusChild(first, second);
if (lastFocusChildOfFirst == second)
@@ -7065,28 +7028,15 @@ void QWidget::setTabOrder(QWidget* first, QWidget *second)
if (lastFocusChildOfSecond == first)
lastFocusChildOfSecond = second;
- // remove the second widget from the chain
- {
- QWidget *oldPrev = second->d_func()->focus_prev;
- QWidget *prevWithFocus = oldPrev;
- while (prevWithFocus->focusPolicy() == Qt::NoFocus)
- prevWithFocus = prevWithFocus->d_func()->focus_prev;
- // only widgets between first and second -> all is fine
- if (prevWithFocus == first)
- return;
- QWidget *oldNext = lastFocusChildOfSecond->d_func()->focus_next;
- setPrev(oldNext, oldPrev);
- setNext(oldPrev, oldNext);
- }
-
- // insert the second widget into the chain
- {
- QWidget *oldNext = lastFocusChildOfFirst->d_func()->focus_next;
- setPrev(second, lastFocusChildOfFirst);
- setNext(lastFocusChildOfFirst, second);
- setPrev(oldNext, lastFocusChildOfSecond);
- setNext(lastFocusChildOfSecond, oldNext);
- }
+ // Return if only NoFocus widgets are between first and second
+ QWidget *oldPrev = second->previousInFocusChain();
+ QWidget *prevWithFocus = oldPrev;
+ while (prevWithFocus->focusPolicy() == Qt::NoFocus)
+ prevWithFocus = prevWithFocus->previousInFocusChain();
+ if (prevWithFocus == first)
+ return;
+ const QWidgetList chain = QWidgetPrivate::takeFromFocusChain(second, lastFocusChildOfSecond);
+ QWidgetPrivate::insertIntoFocusChain(chain, QWidgetPrivate::FocusDirection::Next, lastFocusChildOfFirst);
}
void QWidget::setTabOrder(std::initializer_list<QWidget *> widgets)
@@ -7124,67 +7074,8 @@ void QWidgetPrivate::reparentFocusWidgets(QWidget * oldtlw)
if (focus_child)
focus_child->clearFocus();
- // separate the focus chain into new (children of myself) and old (the rest)
- QWidget *firstOld = nullptr;
- //QWidget *firstNew = q; //invariant
- QWidget *o = nullptr; // last in the old list
- QWidget *n = q; // last in the new list
-
- bool prevWasNew = true;
- QWidget *w = focus_next;
-
- //Note: for efficiency, we do not maintain the list invariant inside the loop
- //we append items to the relevant list, and we optimize by not changing pointers
- //when subsequent items are going into the same list.
- while (w != q) {
- bool currentIsNew = q->isAncestorOf(w);
- if (currentIsNew) {
- if (!prevWasNew) {
- //prev was old -- append to new list
- n->d_func()->focus_next = w;
- w->d_func()->focus_prev = n;
- }
- n = w;
- } else {
- if (prevWasNew) {
- //prev was new -- append to old list, if there is one
- if (o) {
- o->d_func()->focus_next = w;
- w->d_func()->focus_prev = o;
- } else {
- // "create" the old list
- firstOld = w;
- }
- }
- o = w;
- }
- w = w->d_func()->focus_next;
- prevWasNew = currentIsNew;
- }
-
- //repair the old list:
- if (firstOld) {
- o->d_func()->focus_next = firstOld;
- firstOld->d_func()->focus_prev = o;
- }
-
- if (!q->isWindow()) {
- QWidget *topLevel = q->window();
- //insert new chain into toplevel's chain
-
- QWidget *prev = topLevel->d_func()->focus_prev;
-
- topLevel->d_func()->focus_prev = n;
- prev->d_func()->focus_next = q;
-
- focus_prev = prev;
- n->d_func()->focus_next = topLevel;
- } else {
- //repair the new list
- n->d_func()->focus_next = q;
- focus_prev = n;
- }
-
+ insertIntoFocusChain(QWidgetPrivate::FocusDirection::Previous, q->window());
+ reparentFocusChildren(QWidgetPrivate::FocusDirection::Next);
}
/*!
@@ -13371,6 +13262,399 @@ QDebug operator<<(QDebug debug, const QWidget *widget)
}
#endif // !QT_NO_DEBUG_STREAM
+
+// *************************** Focus abstraction ************************************
+
+#define FOCUS_NEXT(w) w->d_func()->focus_next
+#define FOCUS_PREV(w) w->d_func()->focus_prev
+
+/*!
+ \internal
+ \return next or previous element in the focus chain, depending on
+ \param direction, irrespective of focus proxies or widgets with Qt::NoFocus.
+ */
+QWidget *QWidgetPrivate::nextPrevElementInFocusChain(FocusDirection direction) const
+{
+ Q_Q(const QWidget);
+ return direction == FocusDirection::Next ? FOCUS_NEXT(q) : FOCUS_PREV(q);
+}
+
+/*!
+ \internal
+ Removes a widget from the focus chain, respecting the flags set in \param rules.
+ \list
+ \li EnsureFocusOut: If the widget has input focus, transfer focus to the next or previous widget
+ in the focus chain, depending on \param direction.
+ \li RemoveInconsistent: Remove the widget, even if its focus chain is inconsistent.
+ \li AssertConsistency: qFatal, if the focus chain is inconsistent. This is used in the QWidget destructor.
+ \endlist
+ \return \c true if the widget has been removed, otherwise \c false.
+ */
+bool QWidgetPrivate::removeFromFocusChain(FocusChainRemovalRules rules, FocusDirection direction)
+{
+ Q_Q(QWidget);
+ if (!isFocusChainConsistent()) {
+#ifdef QT_DEBUG
+ if (rules.testFlag(FocusChainRemovalRule::AssertConsistency))
+ qFatal() << q << "has inconsistent focus chain.";
+#endif
+ qCDebug(lcFocus) << q << "wasn't removed, because of inconsistent focus chain.";
+ return false;
+ }
+
+ if (!isInFocusChain()) {
+ qCDebug(lcFocus) << q << "wasn't removed, because it is not part of a focus chain.";
+ return false;
+ }
+
+ if (rules.testFlag(FocusChainRemovalRule::EnsureFocusOut))
+ q->focusNextPrevChild(direction == FocusDirection::Next);
+
+ FOCUS_NEXT(FOCUS_PREV(q)) = FOCUS_NEXT(q);
+ FOCUS_PREV(FOCUS_NEXT(q)) = FOCUS_PREV(q);
+ initFocusChain();
+ qCDebug(lcFocus) << q << "removed from focus chain.";
+ return true;
+}
+
+/*!
+ \internal
+ Initialises the focus chain by making the widget point to itself.
+ */
+void QWidgetPrivate::initFocusChain()
+{
+ Q_Q(QWidget);
+ qCDebug(lcFocus) << "Initializing focus chain of" << q;
+ FOCUS_PREV(q) = q;
+ FOCUS_NEXT(q) = q;
+}
+
+/*!
+ \internal
+ Reads QWidget children, which are not part of a focus chain yet.
+ Inserts them into the focus chain before or after the widget,
+ depending on \param direction and in the order of their creation.
+ This is used, when QWidget::setParent() causes a widget to change toplevel windows.
+ */
+void QWidgetPrivate::reparentFocusChildren(FocusDirection direction)
+{
+ Q_Q(QWidget);
+ QWidgetList focusChildrenInsideChain;
+ QDuplicateTracker<QWidget *> seen;
+ QWidget *widget = q->nextInFocusChain();
+ while (q->isAncestorOf(widget)
+ && !seen.hasSeen(widget)
+ && widget != q->window()) {
+ if (widget->focusPolicy() != Qt::NoFocus)
+ focusChildrenInsideChain << widget;
+
+ widget = direction == FocusDirection::Next ? widget->nextInFocusChain()
+ : widget->previousInFocusChain();
+ }
+
+ const QWidgetList children = q->findChildren<QWidget *>(Qt::FindDirectChildrenOnly);
+ QWidgetList focusChildrenOutsideChain;
+ for (auto *child : children) {
+ if (!focusChildrenInsideChain.contains(child))
+ focusChildrenOutsideChain << child;
+ }
+ if (focusChildrenOutsideChain.isEmpty())
+ return;
+
+ QWidget *previous = q;
+ for (auto *child : focusChildrenOutsideChain) {
+ child->d_func()->insertIntoFocusChain(direction, previous);
+ previous = child;
+ }
+}
+
+/*!
+ \internal
+ Inserts a widget into the focus chain before or after \param position, depending on
+ \param direction.
+ \return \c true, if the insertion has changed the focus chain, otherwise \c false.
+ */
+bool QWidgetPrivate::insertIntoFocusChain(FocusDirection direction, QWidget *position)
+{
+ Q_Q(QWidget);
+ Q_ASSERT(position);
+ QWidget *next = FOCUS_NEXT(q);
+ QWidget *previous = FOCUS_PREV(q);
+
+ switch (direction) {
+ case FocusDirection::Next:
+ if (previous == position) {
+ qCDebug(lcFocus) << "No-op insertion." << q << "is already before" << position;
+ return false;
+ }
+
+ removeFromFocusChain(FocusChainRemovalRule::AssertConsistency);
+
+ FOCUS_NEXT(q) = FOCUS_NEXT(position);
+ FOCUS_PREV(FOCUS_NEXT(position)) = q;
+ FOCUS_NEXT(position) = q;
+ FOCUS_PREV(q) = position;
+ qCDebug(lcFocus) << q << "inserted after" << position;
+ break;
+
+ case FocusDirection::Previous:
+ if (next == position) {
+ qCDebug(lcFocus) << "No-op insertion." << q << "is already after" << position;
+ return false;
+ }
+
+ removeFromFocusChain(FocusChainRemovalRule::AssertConsistency);
+
+ FOCUS_PREV(q) = FOCUS_PREV(position);
+ FOCUS_NEXT(FOCUS_PREV(position)) = q;
+ FOCUS_PREV(position) = q;
+ FOCUS_NEXT(q) = position;
+ qCDebug(lcFocus) << q << "inserted before" << position;
+ break;
+ }
+
+ Q_ASSERT(isFocusChainConsistent());
+ return true;
+}
+
+/*!
+ \internal
+ Convenience override to insert a QWidgetList \param toBeInserted into the focus chain
+ before or after \param position, depending on \param direction.
+ \return \c true, if the insertion has changed the focus chain, otherwise \c false.
+ \note
+ \param toBeInserted must be a consistent focus chain.
+ */
+bool QWidgetPrivate::insertIntoFocusChain(const QWidgetList &toBeInserted,
+ FocusDirection direction, QWidget *position)
+{
+ if (toBeInserted.isEmpty()) {
+ qCDebug(lcFocus) << "No-op insertion of an empty list";
+ return false;
+ }
+
+ Q_ASSERT_X(!toBeInserted.contains(position),
+ Q_FUNC_INFO,
+ "Coding error: toBeInserted contains position");
+
+ QWidget *first = toBeInserted.constFirst();
+ QWidget *last = toBeInserted.constLast();
+
+ // Call QWidget override to log accordingly
+ if (toBeInserted.count() == 1)
+ return first->d_func()->insertIntoFocusChain(direction, position);
+
+ Q_ASSERT(first != last);
+ switch (direction) {
+ case FocusDirection::Previous:
+ if (FOCUS_PREV(position) == last) {
+ qCDebug(lcFocus) << "No-op insertion." << toBeInserted << "is already before"
+ << position;
+ return false;
+ }
+ FOCUS_NEXT(FOCUS_PREV(position)) = first;
+ FOCUS_PREV(first) = FOCUS_PREV(position);
+ FOCUS_NEXT(last) = position;
+ FOCUS_PREV(position) = last;
+ qCDebug(lcFocus) << toBeInserted << "inserted before" << position;
+ break;
+ case FocusDirection::Next:
+ if (FOCUS_PREV(position) == last) {
+ qCDebug(lcFocus) << "No-op insertion." << toBeInserted << "is already after"
+ << position;
+ return false;
+ }
+ FOCUS_PREV(FOCUS_NEXT(position)) = last;
+ FOCUS_NEXT(last) = FOCUS_NEXT(position);
+ FOCUS_PREV(first) = position;
+ FOCUS_NEXT(position) = first;
+ qCDebug(lcFocus) << toBeInserted << "inserted after" << position;
+ break;
+ }
+
+ Q_ASSERT(position->d_func()->isFocusChainConsistent());
+ return true;
+}
+
+/*!
+ \internal
+ \return a QWidgetList, representing the part of the focus chain,
+ starting with \param from and ending with \param to, in \param direction.
+ */
+QWidgetList focusPath(QWidget *from, QWidget *to, QWidgetPrivate::FocusDirection direction)
+{
+ QWidgetList path({from});
+ if (from == to)
+ return path;
+
+ QWidget *current = from;
+ do {
+ switch (direction) {
+ case QWidgetPrivate::FocusDirection::Previous:
+ current = current->previousInFocusChain();
+ break;
+ case QWidgetPrivate::FocusDirection::Next:
+ current = current->nextInFocusChain();
+ break;
+ }
+ if (path.contains(current))
+ return QWidgetList();
+ path << current;
+ } while (current != to);
+
+ return path;
+}
+
+/*!
+ \internal
+ Removes the part from the focus chain starting with \param from and ending with \param to,
+ in \param direction.
+ \return removed part as a QWidgetList.
+ */
+QWidgetList QWidgetPrivate::takeFromFocusChain(QWidget *from,
+ QWidget *to,
+ FocusDirection direction)
+{
+ // Check if there is a path from->to in direction
+ const QWidgetList path = focusPath(from, to , direction);
+ if (path.isEmpty()) {
+ qCDebug(lcFocus) << "No-op removal. Focus chain from" << from << "doesn't lead to " << to;
+ return QWidgetList();
+ }
+
+ QWidget *first = path.constFirst();
+ QWidget *last = path.constLast();
+ if (first == last) {
+ first->d_func()->removeFromFocusChain();
+ return QWidgetList({first});
+ }
+
+ FOCUS_NEXT(FOCUS_PREV(first)) = FOCUS_NEXT(last);
+ FOCUS_PREV(FOCUS_NEXT(last)) = FOCUS_PREV(first);
+ FOCUS_PREV(first) = last;
+ FOCUS_NEXT(last) = first;
+ qCDebug(lcFocus) << path << "removed from focus chain";
+ return path;
+}
+
+/*!
+ \internal
+ \return The last focus child of the widget, traversing the focus chain no further than
+ \param noFurtherThan.
+ */
+QWidget *QWidgetPrivate::determineLastFocusChild(QWidget *noFurtherThan)
+{
+ Q_Q(QWidget);
+ // 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.
+ QWidget *lastFocusChild = q;
+
+ QWidget *focusProxy = deepestFocusProxy();
+ if (!focusProxy) {
+ // QTBUG-81097: Another case is possible here. We can have a child
+ // widget, that sets its focusProxy() to the parent (target).
+ // An example of such widget is a QLineEdit, nested into
+ // a QAbstractSpinBox. In this case such widget should be considered
+ // the last focus child.
+ for (auto *object : std::as_const(q->children())) {
+ QWidget *w = qobject_cast<QWidget *>(object);
+ if (w && w->focusProxy() == q) {
+ lastFocusChild = w;
+ break;
+ }
+ }
+ } else if (q->isAncestorOf(focusProxy)) {
+ lastFocusChild = focusProxy;
+ for (QWidget *focusNext = lastFocusChild->nextInFocusChain();
+ focusNext != focusProxy && q->isAncestorOf(focusNext)
+ && focusNext->window() == focusProxy->window();
+ focusNext = focusNext->nextInFocusChain()) {
+ if (focusNext == noFurtherThan)
+ break;
+ if (focusNext->focusPolicy() != Qt::NoFocus)
+ lastFocusChild = focusNext;
+ }
+ }
+ return lastFocusChild;
+};
+
+/*!
+ \internal
+ \return \c true, if the widget is part of a focus chain and \c false otherwise.
+ A widget is considered to be part of a focus chain, neither FOCUS_NEXT, nor FOCUS_PREV
+ are pointing to the widget itself.
+
+ \note
+ This method doesn't check the consistency of the focus chain.
+ If multiple widgets have been removed from the focus chain by takeFromFocusChain(),
+ isInFocusChain() will return \c true for all of those widgets, even if they represent
+ an inconsistent focus chain.
+ */
+bool QWidgetPrivate::isInFocusChain() const
+{
+ Q_Q(const QWidget);
+ return !(FOCUS_NEXT(q) == q && FOCUS_PREV(q) == q);
+}
+
+/*!
+ \internal
+ A focus chain is consistent, when it is circular: Following the chain in either direction
+ has to return to the beginning. This is why a newly constructed widget points to itself,
+ when the focus chain has been initialized. A newly constructed widget is considered to have
+ a consistent focus chain, while not being part of a focus chain.
+
+ This method returns \c true early, if a widget is pointing to itself.
+ It returns \c false, if one of the following is detected:
+ \list
+ \li nullptr found in a previous/next pointer.
+ \li broken chain: widget A is B's previous, but B isn't A's next.
+ \li chain isn't closed: starting at A doesn't lead back to A.
+ \endlist
+ It return \c true, if none of the above is observed.
+
+ \note
+ The focus chain is checked only in forward direction.
+ This is sufficient, because the check for a broken chain asserts consistent paths
+ in both directions.
+ */
+bool QWidgetPrivate::isFocusChainConsistent() const
+{
+ Q_Q(const QWidget);
+ if (!isInFocusChain())
+ return true;
+
+ const QWidget *position = q;
+
+ for (int i = 0; i < QApplication::allWidgets().count(); ++i) {
+ if (!FOCUS_PREV(position) || !FOCUS_NEXT(position)) {
+ qCDebug(lcFocus) << "Nullptr found at:" << position
+ << "Previous pointing to" << FOCUS_PREV(position)
+ << "Next pointing to" << FOCUS_NEXT(position);
+ return false;
+ }
+ if (!(FOCUS_PREV(FOCUS_NEXT(position)) == position
+ && FOCUS_NEXT(FOCUS_PREV(position)) == position)) {
+ qCDebug(lcFocus) << "Inconsistent focus chain at:" << position
+ << "Previous pointing to" << FOCUS_PREV(FOCUS_NEXT(position))
+ << "Next pointing to" << FOCUS_NEXT(FOCUS_PREV(position));
+ return false;
+ }
+ position = FOCUS_NEXT(position);
+ if (position == q)
+ return true;
+
+ }
+
+ qCDebug(lcFocus) << "Focus chain leading from" << q << "to" << position << "is not closed.";
+ return false;
+}
+
+#undef FOCUS_NEXT
+#undef FOCUS_PREV
+
+
QT_END_NAMESPACE
#include "moc_qwidget.cpp"
diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h
index d16d0f438c..9fab9efa62 100644
--- a/src/widgets/kernel/qwidget_p.h
+++ b/src/widgets/kernel/qwidget_p.h
@@ -41,6 +41,7 @@
#include <private/qgesture_p.h>
#include <qpa/qplatformbackingstore.h>
#include <QtGui/private/qbackingstorerhisupport_p.h>
+#include <private/qapplication_p.h>
#include <QtCore/qpointer.h>
@@ -733,6 +734,40 @@ public:
uint childrenHiddenByWState : 1;
uint childrenShownByExpose : 1;
+ // *************************** Focus abstraction ************************************
+ enum class FocusDirection {
+ Previous,
+ Next,
+ };
+
+ enum class FocusChainRemovalRule {
+ EnsureFocusOut = 0x01,
+ AssertConsistency = 0x02,
+ };
+ Q_DECLARE_FLAGS(FocusChainRemovalRules, FocusChainRemovalRule)
+
+ // Getters
+ QWidget *nextPrevElementInFocusChain(FocusDirection direction) const;
+
+ // manipulators
+ bool removeFromFocusChain(FocusChainRemovalRules rules = FocusChainRemovalRules(),
+ FocusDirection direction = FocusDirection::Next);
+ bool insertIntoFocusChain(FocusDirection direction, QWidget *position);
+ static bool insertIntoFocusChain(const QWidgetList &toBeInserted, FocusDirection direction, QWidget *position);
+ bool insertIntoFocusChainBefore(QWidget *position)
+ { return insertIntoFocusChain(FocusDirection::Previous, position); }
+ bool insertIntoFocusChainAfter(QWidget *position)
+ { return insertIntoFocusChain(FocusDirection::Next, position); }
+ static QWidgetList takeFromFocusChain(QWidget *from, QWidget *to,
+ FocusDirection direction = FocusDirection::Next);
+ void reparentFocusChildren(FocusDirection direction);
+ QWidget *determineLastFocusChild(QWidget *noFurtherThan);
+
+ // Initialization and tests
+ void initFocusChain();
+ bool isInFocusChain() const;
+ bool isFocusChainConsistent() const;
+
// *************************** Platform specific ************************************
#if defined(Q_OS_WIN)
uint noPaintOnScreen : 1; // see qwidget.cpp ::paintEngine()