diff options
Diffstat (limited to 'src/widgets/kernel')
-rw-r--r-- | src/widgets/kernel/qapplication.cpp | 6 | ||||
-rw-r--r-- | src/widgets/kernel/qgesturemanager.cpp | 3 | ||||
-rw-r--r-- | src/widgets/kernel/qlayout.cpp | 5 | ||||
-rw-r--r-- | src/widgets/kernel/qwidget.cpp | 583 | ||||
-rw-r--r-- | src/widgets/kernel/qwidget_p.h | 35 | ||||
-rw-r--r-- | src/widgets/kernel/qwidgetrepaintmanager.cpp | 6 | ||||
-rw-r--r-- | src/widgets/kernel/qwidgetwindow.cpp | 33 | ||||
-rw-r--r-- | src/widgets/kernel/qwindowcontainer.cpp | 54 | ||||
-rw-r--r-- | src/widgets/kernel/qwindowcontainer_p.h | 4 |
9 files changed, 552 insertions, 177 deletions
diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp index de7d93ce20..a1392e10dc 100644 --- a/src/widgets/kernel/qapplication.cpp +++ b/src/widgets/kernel/qapplication.cpp @@ -1094,6 +1094,12 @@ QPalette QApplicationPrivate::basePalette() const if (const QPalette *themePalette = platformTheme() ? platformTheme()->palette() : nullptr) palette = themePalette->resolve(palette); + // This palette now is Qt-generated, so reset the resolve mask. This allows + // QStyle::polish implementations to respect palettes that are user provided, + // by checking if the palette has a brush set for a color that the style might + // otherwise overwrite. + palette.setResolveMask(0); + // Finish off by letting the application style polish the palette. This will // not result in the polished palette becoming a user-set palette, as the // resulting base palette is only used as a fallback, with the resolve mask diff --git a/src/widgets/kernel/qgesturemanager.cpp b/src/widgets/kernel/qgesturemanager.cpp index c93876c500..edb159bbbf 100644 --- a/src/widgets/kernel/qgesturemanager.cpp +++ b/src/widgets/kernel/qgesturemanager.cpp @@ -610,7 +610,8 @@ void QGestureManager::deliverEvents(const QSet<QGesture *> &gestures, QWidget *child = topLevel->childAt(topLevel->mapFromGlobal(pt)); target = child ? child : topLevel; } - } else { + } + if (!target) { // or use the context of the gesture QObject *context = m_gestureOwners.value(gesture, 0); if (context->isWidgetType()) diff --git a/src/widgets/kernel/qlayout.cpp b/src/widgets/kernel/qlayout.cpp index 0251ecd7fd..a826ea75bc 100644 --- a/src/widgets/kernel/qlayout.cpp +++ b/src/widgets/kernel/qlayout.cpp @@ -520,10 +520,11 @@ void QLayoutPrivate::doResize() void QLayout::widgetEvent(QEvent *e) { Q_D(QLayout); - if (!d->enabled) + const QEvent::Type type = e->type(); + if (!d->enabled && type != QEvent::ChildRemoved) return; - switch (e->type()) { + switch (type) { case QEvent::Resize: if (d->activated) d->doResize(); diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index 30daee1b79..6eff4abfb0 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(lcWidgetFocus, "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(); @@ -1292,7 +1289,7 @@ void QWidgetPrivate::create() Qt::WindowFlags &flags = data.window_flags; -#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +#if defined(QT_PLATFORM_UIKIT) if (q->testAttribute(Qt::WA_ContentsMarginsRespectsSafeArea)) flags |= Qt::MaximizeUsingFullscreenGeometryHint; #endif @@ -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) @@ -5171,6 +5160,7 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, const QRegion oldSystemClip = enginePriv->systemClip; const QRegion oldBaseClip = enginePriv->baseSystemClip; const QRegion oldSystemViewport = enginePriv->systemViewport; + const Qt::LayoutDirection oldLayoutDirection = painter->layoutDirection(); // This ensures that all painting triggered by render() is clipped to the current engine clip. if (painter->hasClipping()) { @@ -5179,6 +5169,7 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, } else { enginePriv->setSystemViewport(oldSystemClip); } + painter->setLayoutDirection(layoutDirection()); d->render(target, targetOffset, toBePainted, renderFlags); @@ -5186,6 +5177,7 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, enginePriv->baseSystemClip = oldBaseClip; enginePriv->setSystemTransformAndViewport(oldTransform, oldSystemViewport); enginePriv->systemStateChanged(); + painter->setLayoutDirection(oldLayoutDirection); // Restore shared painter. d->setSharedPainter(oldPainter); @@ -6406,39 +6398,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); } } @@ -6648,7 +6619,9 @@ void QWidgetPrivate::setFocus_sys() { Q_Q(QWidget); // Embedded native widget may have taken the focus; get it back to toplevel - // if that is the case (QTBUG-25852) + // if that is the case (QTBUG-25852), unless widget is a window container. + if (extra && extra->hasWindowContainer) + return; // Do not activate in case the popup menu opens another application (QTBUG-70810) // unless the application is embedded (QTBUG-71991). if (QWindow *nativeWindow = q->testAttribute(Qt::WA_WState_Created) ? q->window()->windowHandle() : nullptr) { @@ -6871,7 +6844,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 +6858,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 +7014,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 +7025,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 +7033,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 +7079,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); } /*! @@ -10720,9 +10616,15 @@ void qSendWindowChangeToTextureChildrenRecursively(QWidget *widget, QEvent::Type for (int i = 0; i < d->children.size(); ++i) { QWidget *w = qobject_cast<QWidget *>(d->children.at(i)); - if (w && !w->isWindow() && QWidgetPrivate::get(w)->textureChildSeen) + if (w && !w->isWindow()) qSendWindowChangeToTextureChildrenRecursively(w, eventType); } + + // Notify QWidgetWindow after we've notified all child QWidgets + if (auto *window = d->windowHandle(QWidgetPrivate::WindowHandleMode::Direct)) { + QEvent e(eventType); + QCoreApplication::sendEvent(window, &e); + } } /*! @@ -10753,6 +10655,7 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) const bool resized = testAttribute(Qt::WA_Resized); const bool wasCreated = testAttribute(Qt::WA_WState_Created); QWidget *oldtlw = window(); + Q_ASSERT(oldtlw); if (f & Qt::Window) // Frame geometry likely changes, refresh. d->data.fstrut_dirty = true; @@ -10795,7 +10698,7 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) // texture-based widgets need a pre-notification when their associated top-level window changes // This is not under the wasCreated/newParent conditions above in order to also play nice with QDockWidget. - if (d->textureChildSeen && ((!parent && parentWidget()) || (parent && parent->window() != oldtlw))) + if (oldtlw->d_func()->usesRhiFlush && ((!parent && parentWidget()) || (parent && parent->window() != oldtlw))) qSendWindowChangeToTextureChildrenRecursively(this, QEvent::WindowAboutToChangeInternal); // If we get parented into another window, children will be folded @@ -10876,7 +10779,7 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) // texture-based widgets need another event when their top-level window // changes (more precisely, has already changed at this point) - if (d->textureChildSeen && oldtlw != window()) + if (oldtlw->d_func()->usesRhiFlush && oldtlw != window()) qSendWindowChangeToTextureChildrenRecursively(this, QEvent::WindowChangeInternal); if (!wasCreated) { @@ -13371,6 +13274,404 @@ 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(lcWidgetFocus) << q << "wasn't removed, because of inconsistent focus chain."; + return false; + } + + if (!isInFocusChain()) { + qCDebug(lcWidgetFocus) << 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(lcWidgetFocus) << 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(lcWidgetFocus) << "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(lcWidgetFocus) << "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(lcWidgetFocus) << q << "inserted after" << position; + break; + + case FocusDirection::Previous: + if (next == position) { + qCDebug(lcWidgetFocus) << "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(lcWidgetFocus) << 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(lcWidgetFocus) << "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(lcWidgetFocus) << "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(lcWidgetFocus) << toBeInserted << "inserted before" << position; + break; + case FocusDirection::Next: + if (FOCUS_PREV(position) == last) { + qCDebug(lcWidgetFocus) << "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(lcWidgetFocus) << 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(lcWidgetFocus) << "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(lcWidgetFocus) << 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. + + The method always returns \c true, when the logging category "qt.widgets.focus" is disabled. + When it is enabled, the 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); + const bool skip = !QLoggingCategory("qt.widgets.focus").isDebugEnabled(); + if (skip) + return true; + + 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(lcWidgetFocus) << "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(lcWidgetFocus) << "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(lcWidgetFocus) << "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() diff --git a/src/widgets/kernel/qwidgetrepaintmanager.cpp b/src/widgets/kernel/qwidgetrepaintmanager.cpp index abe5e8016e..607a767a20 100644 --- a/src/widgets/kernel/qwidgetrepaintmanager.cpp +++ b/src/widgets/kernel/qwidgetrepaintmanager.cpp @@ -355,7 +355,11 @@ void QWidgetRepaintManager::sendUpdateRequest(QWidget *widget, UpdateTime update switch (updateTime) { case UpdateLater: - updateRequestSent = true; + // Prevent redundant update request events, unless it's a + // paint on screen widget, as these don't go through the + // normal backingstore sync machinery. + if (!widget->d_func()->shouldPaintOnScreen()) + updateRequestSent = true; QCoreApplication::postEvent(widget, new QEvent(QEvent::UpdateRequest), Qt::LowEventPriority); break; case UpdateNow: { diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp index f51baba88b..e7f0a84004 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -77,6 +77,39 @@ public: widget->focusWidget()->clearFocus(); } + void setFocusToTarget(FocusTarget target, Qt::FocusReason reason) override + { + Q_Q(QWidgetWindow); + QWidget *widget = q->widget(); + if (!widget) + return; + QWidget *newFocusWidget = nullptr; + + switch (target) { + case FocusTarget::First: + newFocusWidget = q->getFocusWidget(QWidgetWindow::FirstFocusWidget); + break; + case FocusTarget::Last: + newFocusWidget = q->getFocusWidget(QWidgetWindow::LastFocusWidget); + break; + case FocusTarget::Next: { + QWidget *focusWidget = widget->focusWidget() ? widget->focusWidget() : widget; + newFocusWidget = focusWidget->nextInFocusChain() ? focusWidget->nextInFocusChain() : focusWidget; + break; + } + case FocusTarget::Prev: { + QWidget *focusWidget = widget->focusWidget() ? widget->focusWidget() : widget; + newFocusWidget = focusWidget->previousInFocusChain() ? focusWidget->previousInFocusChain() : focusWidget; + break; + } + default: + break; + } + + if (newFocusWidget) + newFocusWidget->setFocus(reason); + } + QRectF closestAcceptableGeometry(const QRectF &rect) const override; void processSafeAreaMarginsChanged() override diff --git a/src/widgets/kernel/qwindowcontainer.cpp b/src/widgets/kernel/qwindowcontainer.cpp index cfd785a82c..1aaf04af43 100644 --- a/src/widgets/kernel/qwindowcontainer.cpp +++ b/src/widgets/kernel/qwindowcontainer.cpp @@ -5,6 +5,7 @@ #include "qwidget_p.h" #include "qwidgetwindow_p.h" #include <QtGui/qwindow.h> +#include <QtGui/private/qwindow_p.h> #include <QtGui/private/qguiapplication_p.h> #include <qpa/qplatformintegration.h> #include <QDebug> @@ -28,7 +29,6 @@ public: QWindowContainerPrivate() : window(nullptr) - , oldFocusWindow(nullptr) , usesNativeWidgets(false) { } @@ -103,7 +103,6 @@ public: } QPointer<QWindow> window; - QWindow *oldFocusWindow; QWindow fakeParent; uint usesNativeWidgets : 1; @@ -207,6 +206,7 @@ QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt: } d->window = embeddedWindow; + d->window->installEventFilter(this); QString windowName = d->window->objectName(); if (windowName.isEmpty()) @@ -219,8 +219,8 @@ QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt: setAcceptDrops(true); - connect(qGuiApp, &QGuiApplication::focusWindowChanged, - this, &QWindowContainer::focusWindowChanged); + connect(containedWindow(), &QWindow::minimumHeightChanged, this, &QWindowContainer::updateGeometry); + connect(containedWindow(), &QWindow::minimumWidthChanged, this, &QWindowContainer::updateGeometry); } QWindow *QWindowContainer::containedWindow() const @@ -241,30 +241,12 @@ QWindowContainer::~QWindowContainer() // QEvent::PlatformSurface delivery relies on virtuals. Getting // SurfaceAboutToBeDestroyed can be essential for OpenGL, Vulkan, etc. // QWindow subclasses in particular. Keep these working. - if (d->window) + if (d->window) { + d->window->removeEventFilter(this); d->window->destroy(); + } delete d->window; - - disconnect(qGuiApp, &QGuiApplication::focusWindowChanged, - this, &QWindowContainer::focusWindowChanged); -} - - - -/*! - \internal - */ - -void QWindowContainer::focusWindowChanged(QWindow *focusWindow) -{ - Q_D(QWindowContainer); - d->oldFocusWindow = focusWindow; - if (focusWindow == d->window) { - QWidget *widget = QApplication::focusWidget(); - if (widget) - widget->clearFocus(); - } } /*! @@ -281,8 +263,12 @@ bool QWindowContainer::eventFilter(QObject *o, QEvent *e) QChildEvent *ce = static_cast<QChildEvent *>(e); if (ce->child() == d->window) { o->removeEventFilter(this); + d->window->removeEventFilter(this); d->window = nullptr; } + } else if (e->type() == QEvent::FocusIn) { + if (o == d->window) + setFocus(Qt::ActiveWindowFocusReason); } return false; } @@ -332,11 +318,16 @@ bool QWindowContainer::event(QEvent *e) break; case QEvent::FocusIn: if (d->window->parent()) { - if (d->oldFocusWindow != d->window) { + if (QGuiApplication::focusWindow() != d->window) { + QFocusEvent *event = static_cast<QFocusEvent *>(e); + const auto reason = event->reason(); + QWindowPrivate::FocusTarget target = QWindowPrivate::FocusTarget::Current; + if (reason == Qt::TabFocusReason) + target = QWindowPrivate::FocusTarget::First; + else if (reason == Qt::BacktabFocusReason) + target = QWindowPrivate::FocusTarget::Last; + qt_window_private(d->window)->setFocusToTarget(target, reason); d->window->requestActivate(); - } else { - QWidget *next = nextInFocusChain(); - next->setFocus(); } } break; @@ -373,6 +364,11 @@ bool QWindowContainer::event(QEvent *e) return QWidget::event(e); } +QSize QWindowContainer::minimumSizeHint() const +{ + return containedWindow() ? containedWindow()->minimumSize() : QSize(0, 0); +} + typedef void (*qwindowcontainer_traverse_callback)(QWidget *parent); static void qwindowcontainer_traverse(QWidget *parent, qwindowcontainer_traverse_callback callback) { diff --git a/src/widgets/kernel/qwindowcontainer_p.h b/src/widgets/kernel/qwindowcontainer_p.h index 8dc5c64af4..0cbcc5321d 100644 --- a/src/widgets/kernel/qwindowcontainer_p.h +++ b/src/widgets/kernel/qwindowcontainer_p.h @@ -31,6 +31,7 @@ public: explicit QWindowContainer(QWindow *embeddedWindow, QWidget *parent = nullptr, Qt::WindowFlags f = { }); ~QWindowContainer(); QWindow *containedWindow() const; + QSize minimumSizeHint() const override; static void toplevelAboutToBeDestroyed(QWidget *parent); static void parentWasChanged(QWidget *parent); @@ -41,9 +42,6 @@ public: protected: bool event(QEvent *ev) override; bool eventFilter(QObject *, QEvent *ev) override; - -private slots: - void focusWindowChanged(QWindow *focusWindow); }; QT_END_NAMESPACE |