summaryrefslogtreecommitdiffstats
path: root/src/widgets/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'src/widgets/kernel')
-rw-r--r--src/widgets/kernel/qaction_widgets_p.h2
-rw-r--r--src/widgets/kernel/qapplication.cpp71
-rw-r--r--src/widgets/kernel/qapplication_p.h3
-rw-r--r--src/widgets/kernel/qgesturemanager.cpp51
-rw-r--r--src/widgets/kernel/qgesturemanager_p.h2
-rw-r--r--src/widgets/kernel/qlayout.cpp16
-rw-r--r--src/widgets/kernel/qlayoutitem.cpp2
-rw-r--r--src/widgets/kernel/qrhiwidget.cpp1309
-rw-r--r--src/widgets/kernel/qrhiwidget.h101
-rw-r--r--src/widgets/kernel/qrhiwidget_p.h64
-rw-r--r--src/widgets/kernel/qshortcut_widgets.cpp18
-rw-r--r--src/widgets/kernel/qstackedlayout.cpp2
-rw-r--r--src/widgets/kernel/qtestsupport_widgets.cpp30
-rw-r--r--src/widgets/kernel/qtestsupport_widgets.h1
-rw-r--r--src/widgets/kernel/qtooltip.cpp36
-rw-r--r--src/widgets/kernel/qwhatsthis.cpp6
-rw-r--r--src/widgets/kernel/qwidget.cpp1075
-rw-r--r--src/widgets/kernel/qwidget.h18
-rw-r--r--src/widgets/kernel/qwidget_p.h53
-rw-r--r--src/widgets/kernel/qwidgetaction.cpp18
-rw-r--r--src/widgets/kernel/qwidgetaction.h1
-rw-r--r--src/widgets/kernel/qwidgetaction_p.h4
-rw-r--r--src/widgets/kernel/qwidgetrepaintmanager.cpp25
-rw-r--r--src/widgets/kernel/qwidgetsvariant.cpp3
-rw-r--r--src/widgets/kernel/qwidgetwindow.cpp93
-rw-r--r--src/widgets/kernel/qwidgetwindow_p.h2
-rw-r--r--src/widgets/kernel/qwindowcontainer.cpp73
-rw-r--r--src/widgets/kernel/qwindowcontainer_p.h4
28 files changed, 2589 insertions, 494 deletions
diff --git a/src/widgets/kernel/qaction_widgets_p.h b/src/widgets/kernel/qaction_widgets_p.h
index bc24ce3df1..77c3d8a680 100644
--- a/src/widgets/kernel/qaction_widgets_p.h
+++ b/src/widgets/kernel/qaction_widgets_p.h
@@ -20,6 +20,8 @@
#include <QtWidgets/qmenu.h>
#endif
+#include <QtCore/qpointer.h>
+
QT_REQUIRE_CONFIG(action);
QT_BEGIN_NAMESPACE
diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp
index 8b6adc0176..a1392e10dc 100644
--- a/src/widgets/kernel/qapplication.cpp
+++ b/src/widgets/kernel/qapplication.cpp
@@ -310,7 +310,6 @@ QWidget *QApplication::topLevelAt(const QPoint &pos)
*/
void qt_init_tooltip_palette();
-void qt_cleanup();
QStyle *QApplicationPrivate::app_style = nullptr; // default application style
#ifndef QT_NO_STYLE_STYLESHEET
@@ -496,8 +495,11 @@ void QApplicationPrivate::init()
void qt_init_tooltip_palette()
{
#if QT_CONFIG(tooltip)
- if (const QPalette *toolTipPalette = QGuiApplicationPrivate::platformTheme()->palette(QPlatformTheme::ToolTipPalette))
- QToolTip::setPalette(*toolTipPalette);
+ if (const QPalette *toolTipPalette = QGuiApplicationPrivate::platformTheme()->palette(QPlatformTheme::ToolTipPalette)) {
+ QPalette toolTipPal = *toolTipPalette;
+ toolTipPal.setResolveMask(0);
+ QToolTip::setPalette(toolTipPal);
+ }
#endif
}
@@ -705,7 +707,10 @@ QApplication::~QApplication()
d->cleanupMultitouch();
- qt_cleanup();
+ QPixmapCache::clear();
+ QColormap::cleanup();
+
+ QApplicationPrivate::active_window = nullptr; //### this should not be necessary
if (QApplicationPrivate::widgetCount)
qDebug("Widgets left: %i Max widgets: %i \n", QWidgetPrivate::instanceCounter, QWidgetPrivate::maxInstances);
@@ -716,14 +721,6 @@ QApplication::~QApplication()
QApplicationPrivate::widgetCount = false;
}
-void qt_cleanup()
-{
- QPixmapCache::clear();
- QColormap::cleanup();
-
- QApplicationPrivate::active_window = nullptr; //### this should not be necessary
-}
-
/*!
\fn QWidget *QApplication::widgetAt(const QPoint &point)
@@ -789,6 +786,7 @@ bool QApplication::compressEvent(QEvent *event, QObject *receiver, QPostEventLis
const QEvent::Type type = event->type();
switch (type) {
case QEvent::UpdateRequest:
+ case QEvent::UpdateLater:
case QEvent::LayoutRequest:
case QEvent::Resize:
case QEvent::Move:
@@ -817,6 +815,10 @@ bool QApplication::compressEvent(QEvent *event, QObject *receiver, QPostEventLis
static_cast<QMoveEvent *>(postedEvent.event)->m_pos =
static_cast<const QMoveEvent *>(event)->pos();
break;
+ case QEvent::UpdateLater:
+ static_cast<QUpdateLaterEvent *>(postedEvent.event)->m_region +=
+ static_cast<const QUpdateLaterEvent *>(event)->region();
+ break;
case QEvent::UpdateRequest:
case QEvent::LanguageChange:
case QEvent::LayoutRequest:
@@ -1092,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
@@ -1774,7 +1782,7 @@ void QApplicationPrivate::notifyLayoutDirectionChange()
/*!
\fn void QApplication::setActiveWindow(QWidget* active)
- \deprecated Use QWidget::activateWindow() instead.
+ \deprecated [6.5] Use QWidget::activateWindow() instead.
Sets the active window to the \a active widget in response to a system
event. The function is called from the platform specific event handlers.
@@ -1967,7 +1975,7 @@ QWidget *QApplicationPrivate::focusNextPrevChild_helper(QWidget *toplevel, bool
f = toplevel;
QWidget *w = f;
- QWidget *test = f->d_func()->focus_next;
+ QWidget *test = f->nextInFocusChain();
bool seenWindow = false;
bool focusWidgetAfterWindow = false;
while (test && test != f) {
@@ -1998,7 +2006,7 @@ QWidget *QApplicationPrivate::focusNextPrevChild_helper(QWidget *toplevel, bool
if (next)
break;
}
- test = test->d_func()->focus_next;
+ test = test->nextInFocusChain();
}
if (wrappingOccurred != nullptr)
@@ -2023,19 +2031,6 @@ QWidget *QApplicationPrivate::focusNextPrevChild_helper(QWidget *toplevel, bool
*/
void QApplicationPrivate::dispatchEnterLeave(QWidget* enter, QWidget* leave, const QPointF &globalPosF)
{
-#if 0
- if (leave) {
- QEvent e(QEvent::Leave);
- QCoreApplication::sendEvent(leave, & e);
- }
- if (enter) {
- const QPoint windowPos = enter->window()->mapFromGlobal(globalPos);
- QEnterEvent e(enter->mapFromGlobal(globalPos), windowPos, globalPos);
- QCoreApplication::sendEvent(enter, & e);
- }
- return;
-#endif
-
if ((!enter && !leave) || (enter == leave))
return;
@@ -2546,8 +2541,9 @@ int QApplication::startDragDistance()
exec(), because modal widgets call exec() to start a local event loop.
To make your application perform idle processing, i.e., executing a special
- function whenever there are no pending events, use a QTimer with 0 timeout.
- More advanced idle processing schemes can be achieved using processEvents().
+ function whenever there are no pending events, use a QChronoTimer with 0ns
+ timeout. More advanced idle processing schemes can be achieved using
+ processEvents().
We recommend that you connect clean-up code to the
\l{QCoreApplication::}{aboutToQuit()} signal, instead of putting it in your
@@ -2656,6 +2652,7 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
Q_FALLTHROUGH();
case QEvent::Leave:
d->toolTipWakeUp.stop();
+ break;
default:
break;
}
@@ -2680,6 +2677,7 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
|| key == Qt::Key_Up
|| key == Qt::Key_Right
|| key == Qt::Key_Down);
+ break;
}
default:
break;
@@ -2941,6 +2939,7 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
tablet->tangentialPressure(), tablet->rotation(), tablet->z(),
tablet->modifiers(), tablet->button(), tablet->buttons());
te.m_spont = e->spontaneous();
+ te.setTimestamp(tablet->timestamp());
te.setAccepted(false);
res = d->notify_helper(w, w == receiver ? tablet : &te);
eventAccepted = ((w == receiver) ? tablet : &te)->isAccepted();
@@ -3036,8 +3035,16 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
#endif
w = qobject_cast<QWidget *>(QDragManager::self()->currentTarget());
- if (!w)
- break;
+ if (!w) {
+ // The widget that received DragEnter didn't accept the event, so we have no
+ // current drag target in the QDragManager. But DragLeave still needs to be
+ // dispatched so that enter/leave events are in balance (and so that UnderMouse
+ // gets cleared).
+ if (e->type() == QEvent::DragLeave)
+ w = static_cast<QWidget *>(receiver);
+ else
+ break;
+ }
if (e->type() == QEvent::DragMove || e->type() == QEvent::Drop) {
QDropEvent *dragEvent = static_cast<QDropEvent *>(e);
QWidget *origReceiver = static_cast<QWidget *>(receiver);
diff --git a/src/widgets/kernel/qapplication_p.h b/src/widgets/kernel/qapplication_p.h
index 4be35320d9..21b1605dfc 100644
--- a/src/widgets/kernel/qapplication_p.h
+++ b/src/widgets/kernel/qapplication_p.h
@@ -145,9 +145,7 @@ public:
static QWidget *main_widget;
static QWidget *focus_widget;
static QWidget *hidden_focus_widget;
- static QWidget *active_window;
#if QT_CONFIG(wheelevent)
- static int wheel_scroll_lines;
static QPointer<QWidget> wheel_widget;
#endif
@@ -228,6 +226,7 @@ private:
static void giveFocusAccordingToFocusPolicy(QWidget *w, QEvent *event, QPoint localPos);
static bool shouldSetFocus(QWidget *w, Qt::FocusPolicy policy);
+ static QWidget *active_window;
static bool isAlien(QWidget *);
};
diff --git a/src/widgets/kernel/qgesturemanager.cpp b/src/widgets/kernel/qgesturemanager.cpp
index 76ae157137..edb159bbbf 100644
--- a/src/widgets/kernel/qgesturemanager.cpp
+++ b/src/widgets/kernel/qgesturemanager.cpp
@@ -98,25 +98,23 @@ void QGestureManager::unregisterGestureRecognizer(Qt::GestureType type)
{
QList<QGestureRecognizer *> list = m_recognizers.values(type);
m_recognizers.remove(type);
- foreach (QGesture *g, m_gestureToRecognizer.keys()) {
- QGestureRecognizer *recognizer = m_gestureToRecognizer.value(g);
+ for (const auto &[g, recognizer] : std::as_const(m_gestureToRecognizer).asKeyValueRange()) {
if (list.contains(recognizer)) {
m_deletedRecognizers.insert(g, recognizer);
}
}
- QMap<ObjectGesture, QList<QGesture *> >::const_iterator iter = m_objectGestures.constBegin();
- while (iter != m_objectGestures.constEnd()) {
- ObjectGesture objectGesture = iter.key();
+ for (const auto &[objectGesture, gestures] : std::as_const(m_objectGestures).asKeyValueRange()) {
if (objectGesture.gesture == type) {
- foreach (QGesture *g, iter.value()) {
- if (QGestureRecognizer *recognizer = m_gestureToRecognizer.value(g)) {
- m_gestureToRecognizer.remove(g);
+ for (QGesture *g : gestures) {
+ auto it = m_gestureToRecognizer.constFind(g);
+ if (it != m_gestureToRecognizer.cend() && it.value()) {
+ QGestureRecognizer *recognizer = it.value();
+ m_gestureToRecognizer.erase(it);
m_obsoleteGestures[recognizer].insert(g);
}
}
}
- ++iter;
}
}
@@ -282,18 +280,18 @@ bool QGestureManager::filterEventThroughContexts(const QMultiMap<QObject *,
}
if (!triggeredGestures.isEmpty() || !finishedGestures.isEmpty()
|| !newMaybeGestures.isEmpty() || !notGestures.isEmpty()) {
- QSet<QGesture *> startedGestures = triggeredGestures - m_activeGestures;
+ const QSet<QGesture *> startedGestures = triggeredGestures - m_activeGestures;
triggeredGestures &= m_activeGestures;
// check if a running gesture switched back to maybe state
- QSet<QGesture *> activeToMaybeGestures = m_activeGestures & newMaybeGestures;
+ const QSet<QGesture *> activeToMaybeGestures = m_activeGestures & newMaybeGestures;
// check if a maybe gesture switched to canceled - reset it but don't send an event
QSet<QGesture *> maybeToCanceledGestures = m_maybeGestures & notGestures;
// check if a running gesture switched back to not gesture state,
// i.e. were canceled
- QSet<QGesture *> canceledGestures = m_activeGestures & notGestures;
+ const QSet<QGesture *> canceledGestures = m_activeGestures & notGestures;
// new gestures in maybe state
m_maybeGestures += newMaybeGestures;
@@ -311,11 +309,11 @@ bool QGestureManager::filterEventThroughContexts(const QMultiMap<QObject *,
Q_ASSERT((finishedGestures & canceledGestures).isEmpty());
Q_ASSERT((canceledGestures & newMaybeGestures).isEmpty());
- QSet<QGesture *> notStarted = finishedGestures - m_activeGestures;
+ const QSet<QGesture *> notStarted = finishedGestures - m_activeGestures;
if (!notStarted.isEmpty()) {
// there are some gestures that claim to be finished, but never started.
// probably those are "singleshot" gestures so we'll fake the started state.
- foreach (QGesture *gesture, notStarted)
+ for (QGesture *gesture : notStarted)
gesture->d_func()->state = Qt::GestureStarted;
QSet<QGesture *> undeliveredGestures;
deliverEvents(notStarted, &undeliveredGestures);
@@ -330,15 +328,15 @@ bool QGestureManager::filterEventThroughContexts(const QMultiMap<QObject *,
m_activeGestures -= canceledGestures;
// set the proper gesture state on each gesture
- foreach (QGesture *gesture, startedGestures)
+ for (QGesture *gesture : startedGestures)
gesture->d_func()->state = Qt::GestureStarted;
- foreach (QGesture *gesture, triggeredGestures)
+ for (QGesture *gesture : std::as_const(triggeredGestures))
gesture->d_func()->state = Qt::GestureUpdated;
- foreach (QGesture *gesture, finishedGestures)
+ for (QGesture *gesture : std::as_const(finishedGestures))
gesture->d_func()->state = Qt::GestureFinished;
- foreach (QGesture *gesture, canceledGestures)
+ for (QGesture *gesture : canceledGestures)
gesture->d_func()->state = Qt::GestureCanceled;
- foreach (QGesture *gesture, activeToMaybeGestures)
+ for (QGesture *gesture : activeToMaybeGestures)
gesture->d_func()->state = Qt::GestureFinished;
if (!m_activeGestures.isEmpty() || !m_maybeGestures.isEmpty() ||
@@ -358,7 +356,7 @@ bool QGestureManager::filterEventThroughContexts(const QMultiMap<QObject *,
deliverEvents(startedGestures+triggeredGestures+finishedGestures+canceledGestures,
&undeliveredGestures);
- foreach (QGesture *g, startedGestures) {
+ for (QGesture *g : startedGestures) {
if (undeliveredGestures.contains(g))
continue;
if (g->gestureCancelPolicy() == QGesture::CancelAllInContext) {
@@ -371,9 +369,9 @@ bool QGestureManager::filterEventThroughContexts(const QMultiMap<QObject *,
m_activeGestures -= undeliveredGestures;
// reset gestures that ended
- QSet<QGesture *> endedGestures =
+ const QSet<QGesture *> endedGestures =
finishedGestures + canceledGestures + undeliveredGestures + maybeToCanceledGestures;
- foreach (QGesture *gesture, endedGestures) {
+ for (QGesture *gesture : endedGestures) {
recycle(gesture);
m_gestureTargets.remove(gesture);
}
@@ -550,7 +548,7 @@ void QGestureManager::getGestureTargets(const QSet<QGesture*> &gestures,
GestureByTypes gestureByTypes;
// sort gestures by types
- foreach (QGesture *gesture, gestures) {
+ for (QGesture *gesture : gestures) {
QWidget *receiver = m_gestureTargets.value(gesture, nullptr);
Q_ASSERT(receiver);
if (receiver)
@@ -612,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())
@@ -651,7 +650,7 @@ void QGestureManager::deliverEvents(const QSet<QGesture *> &gestures,
for (GesturesPerWidget::const_iterator it = conflictedGestures.constBegin(),
e = conflictedGestures.constEnd(); it != e; ++it) {
QWidget *receiver = it.key();
- QList<QGesture *> gestures = it.value();
+ const QList<QGesture *> &gestures = it.value();
qCDebug(lcGestureManager) << "QGestureManager::deliverEvents: sending GestureOverride to"
<< receiver
<< "gestures:" << gestures;
@@ -659,7 +658,7 @@ void QGestureManager::deliverEvents(const QSet<QGesture *> &gestures,
event.t = QEvent::GestureOverride;
// mark event and individual gestures as ignored
event.ignore();
- foreach(QGesture *g, gestures)
+ for (QGesture *g : gestures)
event.setAccepted(g, false);
QCoreApplication::sendEvent(receiver, &event);
diff --git a/src/widgets/kernel/qgesturemanager_p.h b/src/widgets/kernel/qgesturemanager_p.h
index f15e4ba233..02beb024f9 100644
--- a/src/widgets/kernel/qgesturemanager_p.h
+++ b/src/widgets/kernel/qgesturemanager_p.h
@@ -21,6 +21,8 @@
#include "private/qwidget_p.h"
#include "qgesturerecognizer.h"
+#include <QtCore/qpointer.h>
+
#ifndef QT_NO_GESTURES
#include <functional>
diff --git a/src/widgets/kernel/qlayout.cpp b/src/widgets/kernel/qlayout.cpp
index 7639803c8b..a826ea75bc 100644
--- a/src/widgets/kernel/qlayout.cpp
+++ b/src/widgets/kernel/qlayout.cpp
@@ -57,14 +57,13 @@ static int menuBarHeightForWidth(QWidget *menubar, int w)
resized to zero size if there is too little space. To support
children whose heights depend on their widths, implement
hasHeightForWidth() and heightForWidth(). See the
- \l{layouts/borderlayout}{Border Layout} and
- \l{layouts/flowlayout}{Flow Layout} examples for
+ \l{layouts/flowlayout}{Flow Layout} example for
more information about implementing custom layout managers.
Geometry management stops when the layout manager is deleted.
\sa QLayoutItem, {Layout Management}, {Basic Layouts Example},
- {Border Layout Example}, {Flow Layout Example}
+ {Flow Layout Example}
*/
@@ -521,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();
@@ -773,7 +773,7 @@ void QLayoutPrivate::reparentChildWidgets(QWidget *mw)
w->metaObject()->className(), qUtf16Printable(w->objectName()));
}
#endif
- bool needShow = mwVisible && !(w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide));
+ bool needShow = mwVisible && !QWidgetPrivate::get(w)->isExplicitlyHidden();
if (pw != mw)
w->setParent(mw);
if (needShow)
@@ -859,7 +859,7 @@ void QLayout::addChildWidget(QWidget *w)
#endif
pw = nullptr;
}
- bool needShow = mw && mw->isVisible() && !(w->isHidden() && w->testAttribute(Qt::WA_WState_ExplicitShowHide));
+ bool needShow = mw && mw->isVisible() && !QWidgetPrivate::get(w)->isExplicitlyHidden();
if (!pw && mw)
w->setParent(mw);
w->setAttribute(Qt::WA_LaidOut);
@@ -1259,7 +1259,7 @@ QLayout::SizeConstraint QLayout::sizeConstraint() const
this layout is set to \a r, provided that this layout supports
setAlignment().
- The result is derived from sizeHint() and expanding(). It is never
+ The result is derived from sizeHint() and expandingDirections(). It is never
larger than \a r.
*/
QRect QLayout::alignmentRect(const QRect &r) const
diff --git a/src/widgets/kernel/qlayoutitem.cpp b/src/widgets/kernel/qlayoutitem.cpp
index eb53a3a120..246d090828 100644
--- a/src/widgets/kernel/qlayoutitem.cpp
+++ b/src/widgets/kernel/qlayoutitem.cpp
@@ -52,7 +52,7 @@ inline static QSize toLayoutItemSize(QWidgetPrivate *priv, const QSize &size)
Pure virtual functions are provided to return information about
the layout, including, sizeHint(), minimumSize(), maximumSize()
- and expanding().
+ and expandingDirections().
The layout's geometry can be set and retrieved with setGeometry()
and geometry(), and its alignment with setAlignment() and
diff --git a/src/widgets/kernel/qrhiwidget.cpp b/src/widgets/kernel/qrhiwidget.cpp
new file mode 100644
index 0000000000..bf56263113
--- /dev/null
+++ b/src/widgets/kernel/qrhiwidget.cpp
@@ -0,0 +1,1309 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qrhiwidget_p.h"
+#include <private/qguiapplication_p.h>
+#include <qpa/qplatformintegration.h>
+#include <private/qwidgetrepaintmanager_p.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QRhiWidget
+ \inmodule QtWidgets
+ \since 6.7
+
+ \brief The QRhiWidget class is a widget for rendering 3D graphics via an
+ accelerated grapics API, such as Vulkan, Metal, or Direct 3D.
+
+ \preliminary
+
+ \note QRhiWidget is in tech preview in Qt 6.7. \b {The API is under
+ development and subject to change.}
+
+ QRhiWidget provides functionality for displaying 3D content rendered
+ through the \l QRhi APIs within a QWidget-based application. In many ways
+ it is the portable equivalent of \l QOpenGLWidget that is not tied to a
+ single 3D graphics API, but rather can function with all the APIs QRhi
+ supports (such as, Direct 3D 11/12, Vulkan, Metal, and OpenGL).
+
+ QRhiWidget is expected to be subclassed. To render into the 2D texture that
+ is implicitly created and managed by the QRhiWidget, subclasses should
+ reimplement the virtual functions initialize() and render().
+
+ The size of the texture will by default adapt to the size of the widget. If
+ a fixed size is preferred, set a fixed size specified in pixels by calling
+ setFixedColorBufferSize().
+
+ In addition to the texture serving as the color buffer, a depth/stencil
+ buffer and a render target binding these together is maintained implicitly
+ as well.
+
+ The QRhi for the widget's top-level window is configured to use a
+ platform-specific backend and graphics API by default: Metal on macOS and
+ iOS, Direct 3D 11 on Windows, OpenGL otherwise. Call setApi() to override
+ this.
+
+ \note A single widget window can only use one QRhi backend, and so one
+ single 3D graphics API. If two QRhiWidget or QQuickWidget widgets in the
+ window's widget hierarchy request different APIs, only one of them will
+ function correctly.
+
+ \note While QRhiWidget is a public Qt API, the QRhi family of classes in
+ the Qt Gui module, including QRhi, QShader and QShaderDescription, offer
+ limited compatibility guarantees. There are no source or binary
+ compatibility guarantees for these classes, meaning the API is only
+ guaranteed to work with the Qt version the application was developed
+ against. Source incompatible changes are however aimed to be kept at a
+ minimum and will only be made in minor releases (6.7, 6.8, and so on).
+ \c{qrhiwidget.h} does not directly include any QRhi-related headers. To use
+ those classes when implementing a QRhiWidget subclass, link to
+ \c{Qt::GuiPrivate} (if using CMake), and include the appropriate headers
+ with the \c rhi prefix, for example \c{#include <rhi/qrhi.h>}.
+
+ An example of a simple QRhiWidget subclass rendering a triangle is the
+ following:
+
+ \snippet qrhiwidget/rhiwidgetintro.cpp 0
+
+ This is a widget that continuously requests updates, throttled by the
+ presentation rate (vsync, depending on the screen refresh rate). If
+ rendering continuously is not desired, the update() call in render() should
+ be removed, and rather issued only when updating the rendered content is
+ necessary. For example, if the rotation of the cube should be tied to the
+ value of a QSlider, then connecting the slider's value change signal to a
+ slot or lambda that forwards the new value and calls update() is
+ sufficient.
+
+ The vertex and fragment shaders are provided as Vulkan-style GLSL and must
+ be processed first by the Qt shader infrastructure first. This is achieved
+ either by running the \c qsb command-line tool manually, or by using the
+ \l{Qt Shader Tools Build System Integration}{qt_add_shaders()} function in
+ CMake. The QRhiWidget implementation loads these pre-processed \c{.qsb}
+ files that are shipped with the application. See \l{Qt Shader Tools} for
+ more information about Qt's shader translation infrastructure.
+
+ The source code for these shaders could be the following:
+
+ \c{color.vert}
+
+ \snippet qrhiwidget/rhiwidgetintro.vert 0
+
+ \c{color.frag}
+
+ \snippet qrhiwidget/rhiwidgetintro.frag 0
+
+ The result is a widget that shows the following:
+
+ \image qrhiwidget-intro.jpg
+
+ For a complete, minimal, introductory example check out the \l{Simple RHI
+ Widget Example}.
+
+ For an example with more functionality and demonstration of further
+ concepts, see the \l{Cube RHI Widget Example}.
+
+ QRhiWidget always involves rendering into a backing texture, not
+ directly to the window (the surface or layer provided by the windowing
+ system for the native window). This allows properly compositing the content
+ with the rest of the widget-based UI, and offering a simple and compact
+ API, making it easy to get started. All this comes at the expense of
+ additional resources and a potential effect on performance. This is often
+ perfectly acceptable in practice, but advanced users should keep in mind
+ the pros and cons of the different approaches. Refer to the \l{RHI Window
+ Example} and compare it with the \l{Simple RHI Widget Example} for details
+ about the two approaches.
+
+ Reparenting a QRhiWidget into a widget hierarchy that belongs to a
+ different window (top-level widget), or making the QRhiWidget itself a
+ top-level (by setting the parent to \nullptr), involves changing the
+ associated QRhi (and potentially destroying the old one) while the
+ QRhiWidget continues to stay alive and well. To support this, robust
+ QRhiWidget implementations are expected to reimplement the
+ releaseResources() virtual function as well, and drop their QRhi resources
+ just as they do in the destructor. The \l{Cube RHI Widget Example}
+ demonstrates this in practice.
+
+ While not a primary use case, QRhiWidget also allows incorporating
+ rendering code that directly uses a 3D graphics API such as Vulkan, Metal,
+ Direct 3D, or OpenGL. See \l QRhiCommandBuffer::beginExternal() for details
+ on recording native commands within a QRhi render pass, as well as
+ \l QRhiTexture::createFrom() for a way to wrap an existing native texture and
+ then use it with QRhi in a subsequent render pass. Note however that the
+ configurability of the underlying graphics API (its device or context
+ features, layers, extensions, etc.) is going to be limited since
+ QRhiWidget's primary goal is to provide an environment suitable for
+ QRhi-based rendering code, not to enable arbitrary, potentially complex,
+ foreign rendering engines.
+
+ \since 6.7
+
+ \sa QRhi, QShader, QOpenGLWidget, {Simple RHI Widget Example}, {Cube RHI Widget Example}
+ */
+
+/*!
+ \enum QRhiWidget::Api
+ Specifies the 3D API and QRhi backend to use
+
+ \value Null
+ \value OpenGL
+ \value Metal
+ \value Vulkan
+ \value Direct3D11
+ \value Direct3D12
+
+ \sa QRhi
+ */
+
+/*!
+ \enum QRhiWidget::TextureFormat
+ Specifies the format of the texture to which the QRhiWidget renders.
+
+ \value RGBA8 See QRhiTexture::RGBA8.
+ \value RGBA16F See QRhiTexture::RGBA16F.
+ \value RGBA32F See QRhiTexture::RGBA32F.
+ \value RGB10A2 See QRhiTexture::RGB10A2.
+
+ \sa QRhiTexture
+ */
+
+/*!
+ Constructs a widget which is a child of \a parent, with widget flags set to \a f.
+ */
+QRhiWidget::QRhiWidget(QWidget *parent, Qt::WindowFlags f)
+ : QWidget(*(new QRhiWidgetPrivate), parent, f)
+{
+ Q_D(QRhiWidget);
+ if (Q_UNLIKELY(!QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RhiBasedRendering)))
+ qWarning("QRhiWidget: QRhi is not supported on this platform.");
+ else
+ d->setRenderToTexture();
+
+ d->config.setEnabled(true);
+#if defined(Q_OS_DARWIN)
+ d->config.setApi(QPlatformBackingStoreRhiConfig::Metal);
+#elif defined(Q_OS_WIN)
+ d->config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
+#else
+ d->config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
+#endif
+}
+
+/*!
+ Destructor.
+ */
+QRhiWidget::~QRhiWidget()
+{
+ Q_D(QRhiWidget);
+
+ if (d->rhi) {
+ d->rhi->removeCleanupCallback(this);
+ // rhi resources must be destroyed here, due to how QWidget teardown works;
+ // it should not be left to the private object's destruction.
+ d->resetRenderTargetObjects();
+ d->resetColorBufferObjects();
+ qDeleteAll(d->pendingDeletes);
+ }
+
+ d->offscreenRenderer.reset();
+}
+
+/*!
+ Handles resize events that are passed in the \a e event parameter. Calls
+ the virtual function initialize().
+
+ \note Avoid overriding this function in derived classes. If that is not
+ feasible, make sure that QRhiWidget's implementation is invoked too.
+ Otherwise the underlying texture object and related resources will not get
+ resized properly and will lead to incorrect rendering.
+ */
+void QRhiWidget::resizeEvent(QResizeEvent *e)
+{
+ Q_D(QRhiWidget);
+
+ if (e->size().isEmpty()) {
+ d->noSize = true;
+ return;
+ }
+ d->noSize = false;
+
+ d->sendPaintEvent(QRect(QPoint(0, 0), size()));
+}
+
+/*!
+ Handles paint events.
+
+ Calling QWidget::update() will lead to sending a paint event \a e, and thus
+ invoking this function. The sending of the event is asynchronous and will
+ happen at some point after returning from update(). This function will
+ then, after some preparation, call the virtual render() to update the
+ contents of the QRhiWidget's associated texture. The widget's top-level
+ window will then composite the texture with the rest of the window.
+ */
+void QRhiWidget::paintEvent(QPaintEvent *)
+{
+ Q_D(QRhiWidget);
+ if (!updatesEnabled() || d->noSize)
+ return;
+
+ d->ensureRhi();
+ if (!d->rhi) {
+ qWarning("QRhiWidget: No QRhi");
+ emit renderFailed();
+ return;
+ }
+
+ QRhiCommandBuffer *cb = nullptr;
+ if (d->rhi->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess)
+ return;
+
+ bool needsInit = false;
+ d->ensureTexture(&needsInit);
+ if (d->colorTexture || d->msaaColorBuffer) {
+ bool canRender = true;
+ if (needsInit)
+ canRender = d->invokeInitialize(cb);
+ if (canRender)
+ render(cb);
+ }
+
+ d->rhi->endOffscreenFrame();
+}
+
+/*!
+ \reimp
+*/
+bool QRhiWidget::event(QEvent *e)
+{
+ Q_D(QRhiWidget);
+ switch (e->type()) {
+ case QEvent::WindowAboutToChangeInternal:
+ // The QRhi will almost certainly change, prevent texture() from
+ // returning the existing QRhiTexture in the meantime.
+ d->textureInvalid = true;
+
+ if (d->rhi && d->rhi != d->offscreenRenderer.rhi()) {
+ // Drop the cleanup callback registered to the toplevel's rhi and
+ // do the early-release, there may not be another chance to do
+ // this, and the QRhi we have currently set may be destroyed by the
+ // time we get to ensureRhi() again.
+ d->rhi->removeCleanupCallback(this);
+ releaseResources(); // notify the user code about the early-release
+ d->releaseResources();
+ // must _not_ null out d->rhi here, for proper interaction with ensureRhi()
+ }
+
+ break;
+
+ case QEvent::Show:
+ if (isVisible())
+ d->sendPaintEvent(QRect(QPoint(0, 0), size()));
+ break;
+ default:
+ break;
+ }
+ return QWidget::event(e);
+}
+
+QWidgetPrivate::TextureData QRhiWidgetPrivate::texture() const
+{
+ // This is the only safe place to clear pendingDeletes, due to the
+ // possibility of the texture returned in the previous invocation of this
+ // function having been added to pendingDeletes, meaning the object then
+ // needs to be valid until the next (this) invocation of this function.
+ // (the exact object lifetime requirements depend on the
+ // QWidget/RepaintManager internal implementation; for now avoid relying on
+ // such details by clearing pendingDeletes only here, not in endCompose())
+ qDeleteAll(pendingDeletes);
+ pendingDeletes.clear();
+
+ TextureData td;
+ if (!textureInvalid)
+ td.textureLeft = resolveTexture ? resolveTexture : colorTexture;
+ return td;
+}
+
+QPlatformTextureList::Flags QRhiWidgetPrivate::textureListFlags()
+{
+ QPlatformTextureList::Flags flags = QWidgetPrivate::textureListFlags();
+ if (mirrorVertically)
+ flags |= QPlatformTextureList::MirrorVertically;
+ return flags;
+}
+
+QPlatformBackingStoreRhiConfig QRhiWidgetPrivate::rhiConfig() const
+{
+ return config;
+}
+
+void QRhiWidgetPrivate::endCompose()
+{
+ // This function is called by QWidgetRepaintManager right after the
+ // backingstore's QRhi-based flush returns. In practice that means after
+ // the begin-endFrame() on the top-level window's swapchain.
+
+ if (rhi) {
+ Q_Q(QRhiWidget);
+ emit q->frameSubmitted();
+ }
+}
+
+// This is reimplemented to enable calling QWidget::grab() on the widget or an
+// ancestor of it. At the same time, QRhiWidget provides its own
+// grabFramebuffer() as well, mirroring QQuickWidget and QOpenGLWidget for
+// consistency. In both types of grabs we end up in here.
+QImage QRhiWidgetPrivate::grabFramebuffer()
+{
+ Q_Q(QRhiWidget);
+ if (noSize)
+ return QImage();
+
+ ensureRhi();
+ if (!rhi) {
+ // The widget (and its parent chain, if any) may not be shown at
+ // all, yet one may still want to use it for grabs. This is
+ // ridiculous of course because the rendering infrastructure is
+ // tied to the top-level widget that initializes upon expose, but
+ // it has to be supported.
+ offscreenRenderer.setConfig(config);
+ // no window passed in, so no swapchain, but we get a functional QRhi which we own
+ offscreenRenderer.create();
+ rhi = offscreenRenderer.rhi();
+ if (!rhi) {
+ qWarning("QRhiWidget: Failed to create dedicated QRhi for grabbing");
+ emit q->renderFailed();
+ return QImage();
+ }
+ }
+
+ QRhiCommandBuffer *cb = nullptr;
+ if (rhi->beginOffscreenFrame(&cb) != QRhi::FrameOpSuccess)
+ return QImage();
+
+ QRhiReadbackResult readResult;
+ bool readCompleted = false;
+ bool needsInit = false;
+ ensureTexture(&needsInit);
+
+ if (colorTexture || msaaColorBuffer) {
+ bool canRender = true;
+ if (needsInit)
+ canRender = invokeInitialize(cb);
+ if (canRender)
+ q->render(cb);
+
+ QRhiResourceUpdateBatch *readbackBatch = rhi->nextResourceUpdateBatch();
+ readResult.completed = [&readCompleted] { readCompleted = true; };
+ readbackBatch->readBackTexture(resolveTexture ? resolveTexture : colorTexture, &readResult);
+ cb->resourceUpdate(readbackBatch);
+ }
+
+ rhi->endOffscreenFrame();
+
+ if (readCompleted) {
+ QImage::Format imageFormat = QImage::Format_RGBA8888;
+ switch (widgetTextureFormat) {
+ case QRhiWidget::TextureFormat::RGBA8:
+ break;
+ case QRhiWidget::TextureFormat::RGBA16F:
+ imageFormat = QImage::Format_RGBA16FPx4;
+ break;
+ case QRhiWidget::TextureFormat::RGBA32F:
+ imageFormat = QImage::Format_RGBA32FPx4;
+ break;
+ case QRhiWidget::TextureFormat::RGB10A2:
+ imageFormat = QImage::Format_BGR30;
+ break;
+ }
+ QImage wrapperImage(reinterpret_cast<const uchar *>(readResult.data.constData()),
+ readResult.pixelSize.width(), readResult.pixelSize.height(),
+ imageFormat);
+ QImage result;
+ if (rhi->isYUpInFramebuffer())
+ result = wrapperImage.mirrored();
+ else
+ result = wrapperImage.copy();
+ result.setDevicePixelRatio(q->devicePixelRatio());
+ return result;
+ } else {
+ Q_UNREACHABLE();
+ }
+
+ return QImage();
+}
+
+void QRhiWidgetPrivate::resetColorBufferObjects()
+{
+ if (colorTexture) {
+ pendingDeletes.append(colorTexture);
+ colorTexture = nullptr;
+ }
+ if (msaaColorBuffer) {
+ pendingDeletes.append(msaaColorBuffer);
+ msaaColorBuffer = nullptr;
+ }
+ if (resolveTexture) {
+ pendingDeletes.append(resolveTexture);
+ resolveTexture = nullptr;
+ }
+}
+
+void QRhiWidgetPrivate::resetRenderTargetObjects()
+{
+ if (renderTarget) {
+ renderTarget->deleteLater();
+ renderTarget = nullptr;
+ }
+ if (renderPassDescriptor) {
+ renderPassDescriptor->deleteLater();
+ renderPassDescriptor = nullptr;
+ }
+ if (depthStencilBuffer) {
+ depthStencilBuffer->deleteLater();
+ depthStencilBuffer = nullptr;
+ }
+}
+
+void QRhiWidgetPrivate::releaseResources()
+{
+ resetRenderTargetObjects();
+ resetColorBufferObjects();
+ qDeleteAll(pendingDeletes);
+ pendingDeletes.clear();
+}
+
+void QRhiWidgetPrivate::ensureRhi()
+{
+ Q_Q(QRhiWidget);
+ QRhi *currentRhi = QWidgetPrivate::rhi();
+ if (currentRhi && currentRhi->backend() != QBackingStoreRhiSupport::apiToRhiBackend(config.api())) {
+ qWarning("The top-level window is already using another graphics API for composition, "
+ "'%s' is not compatible with this widget",
+ currentRhi->backendName());
+ return;
+ }
+
+ // NB the rhi member may be an invalid object, the pointer can be used, but no deref
+ if (currentRhi && rhi != currentRhi) {
+ if (rhi) {
+ // if previously we created our own but now get a QRhi from the
+ // top-level, then drop what we have and start using the top-level's
+ if (rhi == offscreenRenderer.rhi()) {
+ q->releaseResources(); // notify the user code about the early-release
+ releaseResources();
+ offscreenRenderer.reset();
+ } else {
+ // rhi resources created by us all belong to the old rhi, drop them;
+ // due to nulling out colorTexture this is also what ensures that
+ // initialize() is going to be called again eventually
+ resetRenderTargetObjects();
+ resetColorBufferObjects();
+ }
+ }
+
+ // Normally the widget gets destroyed before the QRhi (which is managed by
+ // the top-level's backingstore). When reparenting between top-levels is
+ // involved, that is not always the case. Therefore we use a per-widget rhi
+ // cleanup callback to get notified when the QRhi is about to be destroyed
+ // while the QRhiWidget is still around.
+ currentRhi->addCleanupCallback(q, [q, this](QRhi *regRhi) {
+ if (!QWidgetPrivate::get(q)->data.in_destructor && this->rhi == regRhi) {
+ q->releaseResources(); // notify the user code about the early-release
+ releaseResources();
+ // must null out our ref, the QRhi object is going to be invalid
+ this->rhi = nullptr;
+ }
+ });
+ }
+
+ rhi = currentRhi;
+}
+
+void QRhiWidgetPrivate::ensureTexture(bool *changed)
+{
+ Q_Q(QRhiWidget);
+
+ QSize newSize = fixedSize;
+ if (newSize.isEmpty())
+ newSize = q->size() * q->devicePixelRatio();
+
+ const int minTexSize = rhi->resourceLimit(QRhi::TextureSizeMin);
+ const int maxTexSize = rhi->resourceLimit(QRhi::TextureSizeMax);
+ newSize.setWidth(qMin(maxTexSize, qMax(minTexSize, newSize.width())));
+ newSize.setHeight(qMin(maxTexSize, qMax(minTexSize, newSize.height())));
+
+ if (colorTexture) {
+ if (colorTexture->format() != rhiTextureFormat || colorTexture->sampleCount() != samples) {
+ resetColorBufferObjects();
+ // sample count change needs new depth-stencil, possibly a new
+ // render target; format change needs new renderpassdescriptor;
+ // therefore must drop the rest too
+ resetRenderTargetObjects();
+ }
+ }
+
+ if (msaaColorBuffer) {
+ if (msaaColorBuffer->backingFormat() != rhiTextureFormat || msaaColorBuffer->sampleCount() != samples) {
+ resetColorBufferObjects();
+ // sample count change needs new depth-stencil, possibly a new
+ // render target; format change needs new renderpassdescriptor;
+ // therefore must drop the rest too
+ resetRenderTargetObjects();
+ }
+ }
+
+ if (!colorTexture && samples <= 1) {
+ if (changed)
+ *changed = true;
+ if (!rhi->isTextureFormatSupported(rhiTextureFormat)) {
+ qWarning("QRhiWidget: The requested texture format (%d) is not supported by the "
+ "underlying 3D graphics API implementation", int(rhiTextureFormat));
+ }
+ colorTexture = rhi->newTexture(rhiTextureFormat, newSize, samples, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
+ if (!colorTexture->create()) {
+ qWarning("Failed to create backing texture for QRhiWidget");
+ delete colorTexture;
+ colorTexture = nullptr;
+ return;
+ }
+ }
+
+ if (samples > 1) {
+ if (!msaaColorBuffer) {
+ if (changed)
+ *changed = true;
+ if (!rhi->isFeatureSupported(QRhi::MultisampleRenderBuffer)) {
+ qWarning("QRhiWidget: Multisample renderbuffers are reported as unsupported; "
+ "sample count %d will not work as expected", samples);
+ }
+ if (!rhi->isTextureFormatSupported(rhiTextureFormat)) {
+ qWarning("QRhiWidget: The requested texture format (%d) is not supported by the "
+ "underlying 3D graphics API implementation", int(rhiTextureFormat));
+ }
+ msaaColorBuffer = rhi->newRenderBuffer(QRhiRenderBuffer::Color, newSize, samples, {}, rhiTextureFormat);
+ if (!msaaColorBuffer->create()) {
+ qWarning("Failed to create multisample color buffer for QRhiWidget");
+ delete msaaColorBuffer;
+ msaaColorBuffer = nullptr;
+ return;
+ }
+ }
+ if (!resolveTexture) {
+ if (changed)
+ *changed = true;
+ resolveTexture = rhi->newTexture(rhiTextureFormat, newSize, 1, QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
+ if (!resolveTexture->create()) {
+ qWarning("Failed to create resolve texture for QRhiWidget");
+ delete resolveTexture;
+ resolveTexture = nullptr;
+ return;
+ }
+ }
+ } else if (resolveTexture) {
+ resolveTexture->deleteLater();
+ resolveTexture = nullptr;
+ }
+
+ if (colorTexture && colorTexture->pixelSize() != newSize) {
+ if (changed)
+ *changed = true;
+ colorTexture->setPixelSize(newSize);
+ if (!colorTexture->create())
+ qWarning("Failed to rebuild texture for QRhiWidget after resizing");
+ }
+
+ if (msaaColorBuffer && msaaColorBuffer->pixelSize() != newSize) {
+ if (changed)
+ *changed = true;
+ msaaColorBuffer->setPixelSize(newSize);
+ if (!msaaColorBuffer->create())
+ qWarning("Failed to rebuild multisample color buffer for QRhiWidget after resizing");
+ }
+
+ if (resolveTexture && resolveTexture->pixelSize() != newSize) {
+ if (changed)
+ *changed = true;
+ resolveTexture->setPixelSize(newSize);
+ if (!resolveTexture->create())
+ qWarning("Failed to rebuild resolve texture for QRhiWidget after resizing");
+ }
+
+ textureInvalid = false;
+}
+
+bool QRhiWidgetPrivate::invokeInitialize(QRhiCommandBuffer *cb)
+{
+ Q_Q(QRhiWidget);
+ if (!colorTexture && !msaaColorBuffer)
+ return false;
+
+ if (autoRenderTarget) {
+ const QSize pixelSize = colorTexture ? colorTexture->pixelSize() : msaaColorBuffer->pixelSize();
+ if (!depthStencilBuffer) {
+ depthStencilBuffer = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, samples);
+ if (!depthStencilBuffer->create()) {
+ qWarning("Failed to create depth-stencil buffer for QRhiWidget");
+ resetRenderTargetObjects();
+ return false;
+ }
+ } else if (depthStencilBuffer->pixelSize() != pixelSize) {
+ depthStencilBuffer->setPixelSize(pixelSize);
+ if (!depthStencilBuffer->create()) {
+ qWarning("Failed to rebuild depth-stencil buffer for QRhiWidget with new size");
+ return false;
+ }
+ }
+
+ if (!renderTarget) {
+ QRhiColorAttachment color0;
+ if (colorTexture)
+ color0.setTexture(colorTexture);
+ else
+ color0.setRenderBuffer(msaaColorBuffer);
+ if (samples > 1)
+ color0.setResolveTexture(resolveTexture);
+ QRhiTextureRenderTargetDescription rtDesc(color0, depthStencilBuffer);
+ renderTarget = rhi->newTextureRenderTarget(rtDesc);
+ renderPassDescriptor = renderTarget->newCompatibleRenderPassDescriptor();
+ renderTarget->setRenderPassDescriptor(renderPassDescriptor);
+ if (!renderTarget->create()) {
+ qWarning("Failed to create render target for QRhiWidget");
+ resetRenderTargetObjects();
+ return false;
+ }
+ }
+ } else {
+ resetRenderTargetObjects();
+ }
+
+ q->initialize(cb);
+
+ return true;
+}
+
+/*!
+ \return the currently set graphics API (QRhi backend).
+
+ \sa setApi()
+ */
+QRhiWidget::Api QRhiWidget::api() const
+{
+ Q_D(const QRhiWidget);
+ switch (d->config.api()) {
+ case QPlatformBackingStoreRhiConfig::OpenGL:
+ return Api::OpenGL;
+ case QPlatformBackingStoreRhiConfig::Metal:
+ return Api::Metal;
+ case QPlatformBackingStoreRhiConfig::Vulkan:
+ return Api::Vulkan;
+ case QPlatformBackingStoreRhiConfig::D3D11:
+ return Api::Direct3D11;
+ case QPlatformBackingStoreRhiConfig::D3D12:
+ return Api::Direct3D12;
+ case QPlatformBackingStoreRhiConfig::Null:
+ return Api::Null;
+ }
+ Q_UNREACHABLE_RETURN(Api::Null);
+}
+
+/*!
+ Sets the graphics API and QRhi backend to use to \a api.
+
+ \warning This function must be called early enough, before the widget is
+ added to a widget hierarchy and displayed on screen. For example, aim to
+ call the function for the subclass constructor. If called too late, the
+ function will have no effect.
+
+ The default value depends on the platform: Metal on macOS and iOS, Direct
+ 3D 11 on Windows, OpenGL otherwise.
+
+ The \a api can only be set once for the widget and its top-level window,
+ once it is done and takes effect, the window can only use that API and QRhi
+ backend to render. Attempting to set another value, or to add another
+ QRhiWidget with a different \a api will not function as expected.
+
+ \sa setColorBufferFormat(), setDebugLayerEnabled(), api()
+ */
+void QRhiWidget::setApi(Api api)
+{
+ Q_D(QRhiWidget);
+ switch (api) {
+ case Api::OpenGL:
+ d->config.setApi(QPlatformBackingStoreRhiConfig::OpenGL);
+ break;
+ case Api::Metal:
+ d->config.setApi(QPlatformBackingStoreRhiConfig::Metal);
+ break;
+ case Api::Vulkan:
+ d->config.setApi(QPlatformBackingStoreRhiConfig::Vulkan);
+ break;
+ case Api::Direct3D11:
+ d->config.setApi(QPlatformBackingStoreRhiConfig::D3D11);
+ break;
+ case Api::Direct3D12:
+ d->config.setApi(QPlatformBackingStoreRhiConfig::D3D12);
+ break;
+ case Api::Null:
+ d->config.setApi(QPlatformBackingStoreRhiConfig::Null);
+ break;
+ }
+}
+
+/*!
+ \return true if a debug or validation layer will be requested if applicable
+ to the graphics API in use.
+
+ \sa setDebugLayerEnabled()
+ */
+bool QRhiWidget::isDebugLayerEnabled() const
+{
+ Q_D(const QRhiWidget);
+ return d->config.isDebugLayerEnabled();
+}
+
+/*!
+ Requests the debug or validation layer of the underlying graphics API
+ when \a enable is true.
+
+ \warning This function must be called early enough, before the widget is added
+ to a widget hierarchy and displayed on screen. For example, aim to call the
+ function for the subclass constructor. If called too late, the function
+ will have no effect.
+
+ Applicable for Vulkan and Direct 3D.
+
+ By default this is disabled.
+
+ \sa setApi(), isDebugLayerEnabled()
+ */
+void QRhiWidget::setDebugLayerEnabled(bool enable)
+{
+ Q_D(QRhiWidget);
+ d->config.setDebugLayer(enable);
+}
+
+/*!
+ \property QRhiWidget::colorBufferFormat
+
+ This property controls the texture format of the texture (or renderbuffer)
+ used as the color buffer. The default value is TextureFormat::RGBA8.
+ QRhiWidget supports rendering to a subset of the formats supported by \l
+ QRhiTexture. Only formats that are reported as supported from \l
+ QRhi::isTextureFormatSupported() should be specified, rendering will not be
+ functional otherwise.
+
+ \note Setting a new format when the widget is already initialized and has
+ rendered implies that all QRhiGraphicsPipeline objects created by the
+ renderer may become unusable, if the associated QRhiRenderPassDescriptor is
+ now incompatible due to the different texture format. Similarly to changing
+ \l sampleCount dynamically, this means that initialize() or render()
+ implementations must then take care of releasing the existing pipelines and
+ creating new ones.
+ */
+
+QRhiWidget::TextureFormat QRhiWidget::colorBufferFormat() const
+{
+ Q_D(const QRhiWidget);
+ return d->widgetTextureFormat;
+}
+
+void QRhiWidget::setColorBufferFormat(TextureFormat format)
+{
+ Q_D(QRhiWidget);
+ if (d->widgetTextureFormat != format) {
+ d->widgetTextureFormat = format;
+ switch (format) {
+ case TextureFormat::RGBA8:
+ d->rhiTextureFormat = QRhiTexture::RGBA8;
+ break;
+ case TextureFormat::RGBA16F:
+ d->rhiTextureFormat = QRhiTexture::RGBA16F;
+ break;
+ case TextureFormat::RGBA32F:
+ d->rhiTextureFormat = QRhiTexture::RGBA32F;
+ break;
+ case TextureFormat::RGB10A2:
+ d->rhiTextureFormat = QRhiTexture::RGB10A2;
+ break;
+ }
+ emit colorBufferFormatChanged(format);
+ update();
+ }
+}
+
+/*!
+ \property QRhiWidget::sampleCount
+
+ This property controls for sample count for multisample antialiasing.
+ By default the value is \c 1 which means MSAA is disabled.
+
+ Valid values are 1, 4, 8, and sometimes 16 and 32.
+ \l QRhi::supportedSampleCounts() can be used to query the supported sample
+ counts at run time, but typically applications should request 1 (no MSAA),
+ 4x (normal MSAA) or 8x (high MSAA).
+
+ \note Setting a new value implies that all QRhiGraphicsPipeline objects
+ created by the renderer must use the same sample count from then on.
+ Existing QRhiGraphicsPipeline objects created with a different sample count
+ must not be used anymore. When the value changes, all color and
+ depth-stencil buffers are destroyed and recreated automatically, and
+ initialize() is invoked again. However, when
+ \l autoRenderTarget is \c false, it will be up to the application to
+ manage this with regards to the depth-stencil buffer or additional color
+ buffers.
+
+ Changing the sample count from the default 1 to a higher value implies that
+ colorTexture() becomes \nullptr and msaaColorBuffer() starts returning a
+ valid object. Switching back to 1 (or 0), implies the opposite: in the next
+ call to initialize() msaaColorBuffer() is going to return \nullptr, whereas
+ colorTexture() becomes once again valid. In addition, resolveTexture()
+ returns a valid (non-multisample) QRhiTexture whenever the sample count is
+ greater than 1 (i.e., MSAA is in use).
+
+ \sa msaaColorBuffer(), resolveTexture()
+ */
+
+int QRhiWidget::sampleCount() const
+{
+ Q_D(const QRhiWidget);
+ return d->samples;
+}
+
+void QRhiWidget::setSampleCount(int samples)
+{
+ Q_D(QRhiWidget);
+ if (d->samples != samples) {
+ d->samples = samples;
+ emit sampleCountChanged(samples);
+ update();
+ }
+}
+
+/*!
+ \property QRhiWidget::fixedColorBufferSize
+
+ The fixed size, in pixels, of the QRhiWidget's associated texture. Relevant
+ when a fixed texture size is desired that does not depend on the widget's
+ size. This size has no effect on the geometry of the widget (its size and
+ placement within the top-level window), which means the texture's content
+ will appear stretched (scaled up) or scaled down onto the widget's area.
+
+ For example, setting a size that is exactly twice the widget's (pixel) size
+ effectively performs 2x supersampling (rendering at twice the resolution
+ and then implicitly scaling down when texturing the quad corresponding to
+ the widget in the window).
+
+ By default the value is a null QSize. A null or empty QSize means that the
+ texture's size follows the QRhiWidget's size. (\c{texture size} = \c{widget
+ size} * \c{device pixel ratio}).
+ */
+
+QSize QRhiWidget::fixedColorBufferSize() const
+{
+ Q_D(const QRhiWidget);
+ return d->fixedSize;
+}
+
+void QRhiWidget::setFixedColorBufferSize(QSize pixelSize)
+{
+ Q_D(QRhiWidget);
+ if (d->fixedSize != pixelSize) {
+ d->fixedSize = pixelSize;
+ emit fixedColorBufferSizeChanged(pixelSize);
+ update();
+ }
+}
+
+/*!
+ \property QRhiWidget::mirrorVertically
+
+ When enabled, flips the image around the X axis when compositing the
+ QRhiWidget's backing texture with the rest of the widget content in the
+ top-level window.
+
+ The default value is \c false.
+ */
+
+bool QRhiWidget::isMirrorVerticallyEnabled() const
+{
+ Q_D(const QRhiWidget);
+ return d->mirrorVertically;
+}
+
+void QRhiWidget::setMirrorVertically(bool enabled)
+{
+ Q_D(QRhiWidget);
+ if (d->mirrorVertically != enabled) {
+ d->mirrorVertically = enabled;
+ emit mirrorVerticallyChanged(enabled);
+ update();
+ }
+}
+
+/*!
+ \property QRhiWidget::autoRenderTarget
+
+ The current setting for automatic depth-stencil buffer and render
+ target maintenance.
+
+ By default the value is \c true.
+ */
+bool QRhiWidget::isAutoRenderTargetEnabled() const
+{
+ Q_D(const QRhiWidget);
+ return d->autoRenderTarget;
+}
+
+/*!
+ Controls if a depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget
+ is created and maintained automatically by the widget. The default value is
+ \c true.
+
+ In automatic mode, the size and sample count of the depth-stencil buffer
+ follows the color buffer texture's settings. In non-automatic mode,
+ renderTarget() and depthStencilBuffer() always return \nullptr and it is
+ then up to the application's implementation of initialize() to take care of
+ setting up and managing these objects.
+
+ Call this function with \a enabled set to \c false early on, for example in
+ the derived class' constructor, to disable the automatic mode.
+ */
+void QRhiWidget::setAutoRenderTarget(bool enabled)
+{
+ Q_D(QRhiWidget);
+ if (d->autoRenderTarget != enabled) {
+ d->autoRenderTarget = enabled;
+ update();
+ }
+}
+
+/*!
+ Renders a new frame, reads the contents of the texture back, and returns it
+ as a QImage.
+
+ When an error occurs, a null QImage is returned.
+
+ The returned QImage will have a format of QImage::Format_RGBA8888,
+ QImage::Format_RGBA16FPx4, QImage::Format_RGBA32FPx4, or
+ QImage::Format_BGR30, depending on colorBufferFormat().
+
+ QRhiWidget does not know the renderer's approach to blending and
+ composition, and therefore cannot know if the output has alpha
+ premultiplied in the RGB color values. Thus \c{_Premultiplied} QImage
+ formats are never used for the returned QImage, even when it would be
+ appropriate. It is up to the caller to reinterpret the resulting data as it
+ sees fit.
+
+ \note This function can also be called when the QRhiWidget is not added to
+ a widget hierarchy belonging to an on-screen top-level window. This allows
+ generating an image from a 3D rendering off-screen.
+
+ The function is named grabFramebuffer() for consistency with QOpenGLWidget
+ and QQuickWidget. It is not the only way to get CPU-side image data out of
+ the QRhiWidget's content: calling \l QWidget::grab() on a QRhiWidget, or an
+ ancestor of it, is functional as well (returning a QPixmap). Besides
+ working directly with QImage, another advantage of grabFramebuffer() is
+ that it may be slightly more performant, simply because it does not have to
+ go through the rest of QWidget infrastructure but can right away trigger
+ rendering a new frame and then do the readback.
+
+ \sa setColorBufferFormat()
+ */
+QImage QRhiWidget::grabFramebuffer() const
+{
+ return const_cast<QRhiWidgetPrivate *>(d_func())->grabFramebuffer();
+}
+
+/*!
+ Called when the widget is initialized for the first time, when the
+ associated texture's size, format, or sample count changes, or when the
+ QRhi and texture change for any reason. The function is expected to
+ maintain (create if not yet created, adjust and rebuild if the size has
+ changed) the graphics resources used by the rendering code in render().
+
+ To query the QRhi, QRhiTexture, and other related objects, call rhi(),
+ colorTexture(), depthStencilBuffer(), and renderTarget().
+
+ When the widget size changes, the QRhi object, the color buffer texture,
+ and the depth stencil buffer objects are all the same instances (so the
+ getters return the same pointers) as before, but the color and
+ depth/stencil buffers will likely have been rebuilt, meaning the
+ \l{QRhiTexture::pixelSize()}{size} and the underlying native texture
+ resource may be different than in the last invocation.
+
+ Reimplementations should also be prepared that the QRhi object and the
+ color buffer texture may change between invocations of this function. One
+ special case where the objects will be different is when performing a
+ grabFramebuffer() with a widget that is not yet shown, and then making the
+ widget visible on-screen within a top-level widget. There the grab will
+ happen with a dedicated QRhi that is then replaced with the top-level
+ window's associated QRhi in subsequent initialize() and render()
+ invocations. Another, more common case is when the widget is reparented so
+ that it belongs to a new top-level window. In this case the QRhi and all
+ related resources managed by the QRhiWidget will be different instances
+ than before in the subsequent call to this function. Is is then important
+ that all existing QRhi resources previously created by the subclass are
+ destroyed because they belong to the previous QRhi that should not be used
+ by the widget anymore.
+
+ When \l autoRenderTarget is \c true, which is the default, a
+ depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget associated
+ with colorTexture() (or msaaColorBuffer()) and the depth-stencil buffer are
+ created and managed automatically. Reimplementations of initialize() and
+ render() can query those objects via depthStencilBuffer() and
+ renderTarget(). When \l autoRenderTarget is set to \c false, these
+ objects are no longer created and managed automatically. Rather, it will be
+ up the the initialize() implementation to create buffers and set up the
+ render target as it sees fit. When manually managing additional color or
+ depth-stencil attachments for the render target, their size and sample
+ count must always follow the size and sample count of colorTexture() /
+ msaaColorBuffer(), otherwise rendering or 3D API validation errors may
+ occur.
+
+ The subclass-created graphics resources are expected to be released in the
+ destructor implementation of the subclass.
+
+ \a cb is the QRhiCommandBuffer for the current frame of the widget. The
+ function is called with a frame being recorded, but without an active
+ render pass. The command buffer is provided primarily to allow enqueuing
+ \l{QRhiCommandBuffer::resourceUpdate()}{resource updates} without deferring
+ to render().
+
+ \sa render()
+ */
+void QRhiWidget::initialize(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+/*!
+ Called when the widget contents (i.e. the contents of the texture) need
+ updating.
+
+ There is always at least one call to initialize() before this function is
+ called.
+
+ To request updates, call QWidget::update(). Calling update() from within
+ render() will lead to updating continuously, throttled by vsync.
+
+ \a cb is the QRhiCommandBuffer for the current frame of the widget. The
+ function is called with a frame being recorded, but without an active
+ render pass.
+
+ \sa initialize()
+ */
+void QRhiWidget::render(QRhiCommandBuffer *cb)
+{
+ Q_UNUSED(cb);
+}
+
+/*!
+ Called when the need to early-release the graphics resources arises.
+
+ This normally does not happen for a QRhiWidget that is added to a top-level
+ widget's child hierarchy and it then stays there for the rest of its and
+ the top-level's lifetime. Thus in many cases there is no need to
+ reimplement this function, e.g. because the application only ever has a
+ single top-level widget (native window). However, when reparenting of the
+ widget (or an ancestor of it) is involved, reimplementing this function
+ will become necessary in robust, well-written QRhiWidget subclasses.
+
+ When this function is called, the implementation is expected to destroy all
+ QRhi resources (QRhiBuffer, QRhiTexture, etc. objects), similarly to how it
+ is expected to do this in the destructor. Nulling out, using a smart
+ pointer, or setting a \c{resources-invalid} flag is going to be required as
+ well, because initialize() will eventually get called afterwards. Note
+ however that deferring the releasing of resources to the subsequent
+ initialize() is wrong. If this function is called, the resource must be
+ dropped before returning. Also note that implementing this function does
+ not replace the class destructor (or smart pointers): the graphics
+ resources must still be released in both.
+
+ See the \l{Cube RHI Widget Example} for an example of this in action. There
+ the button that toggles the QRhiWidget between being a child widget (due to
+ having a parent widget) and being a top-level widget (due to having no
+ parent widget), will trigger invoking this function since the associated
+ top-level widget, native window, and QRhi all change during the lifetime of
+ the QRhiWidget, with the previously used QRhi getting destroyed which
+ implies an early-release of the associated resources managed by the
+ still-alive QRhiWidget.
+
+ Another case when this function is called is when grabFramebuffer() is used
+ with a QRhiWidget that is not added to a visible window, i.e. the rendering
+ is performed offscreen. If later on this QRhiWidget is made visible, or
+ added to a visible widget hierarchy, the associated QRhi will change from
+ the temporary one used for offscreen rendering to the window's dedicated
+ one, thus triggering this function as well.
+
+ \sa initialize()
+ */
+void QRhiWidget::releaseResources()
+{
+}
+
+/*!
+ \return the current QRhi object.
+
+ Must only be called from initialize() and render().
+ */
+QRhi *QRhiWidget::rhi() const
+{
+ Q_D(const QRhiWidget);
+ return d->rhi;
+}
+
+/*!
+ \return the texture serving as the color buffer for the widget.
+
+ Must only be called from initialize() and render().
+
+ Unlike the depth-stencil buffer and the QRhiRenderTarget, this texture is
+ always available and is managed by the QRhiWidget, independent of the value
+ of \l autoRenderTarget.
+
+ \note When \l sampleCount is larger than 1, and so multisample antialiasing
+ is enabled, the return value is \nullptr. Instead, query the
+ \l QRhiRenderBuffer by calling msaaColorBuffer().
+
+ \note The backing texture size and sample count can also be queried via the
+ QRhiRenderTarget returned from renderTarget(). This can be more convenient
+ and compact than querying from the QRhiTexture or QRhiRenderBuffer, because
+ it works regardless of multisampling is in use or not.
+
+ \sa msaaColorBuffer(), depthStencilBuffer(), renderTarget(), resolveTexture()
+ */
+QRhiTexture *QRhiWidget::colorTexture() const
+{
+ Q_D(const QRhiWidget);
+ return d->colorTexture;
+}
+
+/*!
+ \return the renderbuffer serving as the multisample color buffer for the widget.
+
+ Must only be called from initialize() and render().
+
+ When \l sampleCount is larger than 1, and so multisample antialising is
+ enabled, the returned QRhiRenderBuffer has a matching sample count and
+ serves as the color buffer. Graphics pipelines used to render into this
+ buffer must be created with the same sample count, and the depth-stencil
+ buffer's sample count must match as well. The multisample content is
+ expected to be resolved into the texture returned from resolveTexture().
+ When \l autoRenderTarget is
+ \c true, renderTarget() is set up automatically to do this, by setting up
+ msaaColorBuffer() as the \l{QRhiColorAttachment::renderBuffer()}{renderbuffer} of
+ color attachment 0 and resolveTexture() as its
+ \l{QRhiColorAttachment::resolveTexture()}{resolveTexture}.
+
+ When MSAA is not in use, the return value is \nullptr. Use colorTexture()
+ instead then.
+
+ Depending on the underlying 3D graphics API, there may be no practical
+ difference between multisample textures and color renderbuffers with a
+ sample count larger than 1 (QRhi may just map both to the same native
+ resource type). Some older APIs however may differentiate between textures
+ and renderbuffers. In order to support OpenGL ES 3.0, where multisample
+ renderbuffers are available, but multisample textures are not, QRhiWidget
+ always performs MSAA by using a multisample QRhiRenderBuffer as the color
+ attachment (and never a multisample QRhiTexture).
+
+ \note The backing texture size and sample count can also be queried via the
+ QRhiRenderTarget returned from renderTarget(). This can be more convenient
+ and compact than querying from the QRhiTexture or QRhiRenderBuffer, because
+ it works regardless of multisampling is in use or not.
+
+ \sa colorTexture(), depthStencilBuffer(), renderTarget(), resolveTexture()
+ */
+QRhiRenderBuffer *QRhiWidget::msaaColorBuffer() const
+{
+ Q_D(const QRhiWidget);
+ return d->msaaColorBuffer;
+}
+
+/*!
+ \return the non-multisample texture to which the multisample content is resolved.
+
+ The result is \nullptr when multisample antialiasing is not enabled.
+
+ Must only be called from initialize() and render().
+
+ With MSAA enabled, this is the texture that gets composited with the rest
+ of the QWidget content on-screen. However, the QRhiWidget's rendering must
+ target the (multisample) QRhiRenderBuffer returned from
+ msaaColorBuffer(). When
+ \l autoRenderTarget is \c true, this is taken care of by the
+ QRhiRenderTarget returned from renderTarget(). Otherwise, it is up to the
+ subclass code to correctly configure a render target object with both the
+ color buffer and resolve textures.
+
+ \sa colorTexture()
+ */
+QRhiTexture *QRhiWidget::resolveTexture() const
+{
+ Q_D(const QRhiWidget);
+ return d->resolveTexture;
+}
+
+/*!
+ \return the depth-stencil buffer used by the widget's rendering.
+
+ Must only be called from initialize() and render().
+
+ Available only when \l autoRenderTarget is \c true. Otherwise the
+ returned value is \nullptr and it is up the reimplementation of
+ initialize() to create and manage a depth-stencil buffer and a
+ QRhiTextureRenderTarget.
+
+ \sa colorTexture(), renderTarget()
+ */
+QRhiRenderBuffer *QRhiWidget::depthStencilBuffer() const
+{
+ Q_D(const QRhiWidget);
+ return d->depthStencilBuffer;
+}
+
+/*!
+ \return the render target object that must be used with
+ \l QRhiCommandBuffer::beginPass() in reimplementations of render().
+
+ Must only be called from initialize() and render().
+
+ Available only when \l autoRenderTarget is \c true. Otherwise the
+ returned value is \nullptr and it is up the reimplementation of
+ initialize() to create and manage a depth-stencil buffer and a
+ QRhiTextureRenderTarget.
+
+ When creating \l{QRhiGraphicsPipeline}{graphics pipelines}, a
+ QRhiRenderPassDescriptor is needed. This can be queried from the returned
+ QRhiTextureRenderTarget by calling
+ \l{QRhiTextureRenderTarget::renderPassDescriptor()}{renderPassDescriptor()}.
+
+ \sa colorTexture(), depthStencilBuffer()
+ */
+QRhiRenderTarget *QRhiWidget::renderTarget() const
+{
+ Q_D(const QRhiWidget);
+ return d->renderTarget;
+}
+
+/*!
+ \fn void QRhiWidget::frameSubmitted()
+
+ This signal is emitted after the widget's top-level window has finished
+ composition and has \l{QRhi::endFrame()}{submitted a frame}.
+*/
+
+/*!
+ \fn void QRhiWidget::renderFailed()
+
+ This signal is emitted whenever the widget is supposed to render to its
+ backing texture (either due to a \l{QWidget::update()}{widget update} or
+ due to a call to grabFramebuffer()), but there is no \l QRhi for the widget to
+ use, likely due to issues related to graphics configuration.
+
+ This signal may be emitted multiple times when a problem arises. Do not
+ assume it is emitted only once. Connect with Qt::SingleShotConnection if
+ the error handling code is to be notified only once.
+*/
+
+QT_END_NAMESPACE
diff --git a/src/widgets/kernel/qrhiwidget.h b/src/widgets/kernel/qrhiwidget.h
new file mode 100644
index 0000000000..45fe7b7482
--- /dev/null
+++ b/src/widgets/kernel/qrhiwidget.h
@@ -0,0 +1,101 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRHIWIDGET_H
+#define QRHIWIDGET_H
+
+#include <QtWidgets/qwidget.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRhiWidgetPrivate;
+class QRhi;
+class QRhiTexture;
+class QRhiRenderBuffer;
+class QRhiRenderTarget;
+class QRhiCommandBuffer;
+
+class Q_WIDGETS_EXPORT QRhiWidget : public QWidget
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QRhiWidget)
+ Q_PROPERTY(int sampleCount READ sampleCount WRITE setSampleCount NOTIFY sampleCountChanged)
+ Q_PROPERTY(TextureFormat colorBufferFormat READ colorBufferFormat WRITE setColorBufferFormat NOTIFY colorBufferFormatChanged)
+ Q_PROPERTY(QSize fixedColorBufferSize READ fixedColorBufferSize WRITE setFixedColorBufferSize NOTIFY fixedColorBufferSizeChanged)
+ Q_PROPERTY(bool mirrorVertically READ isMirrorVerticallyEnabled WRITE setMirrorVertically NOTIFY mirrorVerticallyChanged)
+ QDOC_PROPERTY(bool autoRenderTarget READ isAutoRenderTargetEnabled WRITE setAutoRenderTarget)
+
+public:
+ explicit QRhiWidget(QWidget *parent = nullptr, Qt::WindowFlags f = {});
+ ~QRhiWidget() override;
+
+ enum class Api {
+ Null,
+ OpenGL,
+ Metal,
+ Vulkan,
+ Direct3D11,
+ Direct3D12,
+ };
+ Q_ENUM(Api)
+
+ enum class TextureFormat {
+ RGBA8,
+ RGBA16F,
+ RGBA32F,
+ RGB10A2,
+ };
+ Q_ENUM(TextureFormat)
+
+ Api api() const;
+ void setApi(Api api);
+
+ bool isDebugLayerEnabled() const;
+ void setDebugLayerEnabled(bool enable);
+
+ int sampleCount() const;
+ void setSampleCount(int samples);
+
+ TextureFormat colorBufferFormat() const;
+ void setColorBufferFormat(TextureFormat format);
+
+ QSize fixedColorBufferSize() const;
+ void setFixedColorBufferSize(QSize pixelSize);
+ void setFixedColorBufferSize(int w, int h) { setFixedColorBufferSize(QSize(w, h)); }
+
+ bool isMirrorVerticallyEnabled() const;
+ void setMirrorVertically(bool enabled);
+
+ QImage grabFramebuffer() const;
+
+protected:
+ bool isAutoRenderTargetEnabled() const;
+ void setAutoRenderTarget(bool enabled);
+
+ virtual void initialize(QRhiCommandBuffer *cb);
+ virtual void render(QRhiCommandBuffer *cb);
+ virtual void releaseResources();
+
+ QRhi *rhi() const;
+ QRhiTexture *colorTexture() const;
+ QRhiRenderBuffer *msaaColorBuffer() const;
+ QRhiTexture *resolveTexture() const;
+ QRhiRenderBuffer *depthStencilBuffer() const;
+ QRhiRenderTarget *renderTarget() const;
+
+ void resizeEvent(QResizeEvent *e) override;
+ void paintEvent(QPaintEvent *e) override;
+ bool event(QEvent *e) override;
+
+Q_SIGNALS:
+ void frameSubmitted();
+ void renderFailed();
+ void sampleCountChanged(int samples);
+ void colorBufferFormatChanged(TextureFormat format);
+ void fixedColorBufferSizeChanged(const QSize &pixelSize);
+ void mirrorVerticallyChanged(bool enabled);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/widgets/kernel/qrhiwidget_p.h b/src/widgets/kernel/qrhiwidget_p.h
new file mode 100644
index 0000000000..b594b67ec8
--- /dev/null
+++ b/src/widgets/kernel/qrhiwidget_p.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRHIWIDGET_P_H
+#define QRHIWIDGET_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrhiwidget.h"
+#include <rhi/qrhi.h>
+#include <private/qwidget_p.h>
+#include <private/qbackingstorerhisupport_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRhiWidgetPrivate : public QWidgetPrivate
+{
+ Q_DECLARE_PUBLIC(QRhiWidget)
+public:
+ TextureData texture() const override;
+ QPlatformTextureList::Flags textureListFlags() override;
+ QPlatformBackingStoreRhiConfig rhiConfig() const override;
+ void endCompose() override;
+ QImage grabFramebuffer() override;
+
+ void ensureRhi();
+ void ensureTexture(bool *changed);
+ bool invokeInitialize(QRhiCommandBuffer *cb);
+ void resetColorBufferObjects();
+ void resetRenderTargetObjects();
+ void releaseResources();
+
+ QRhi *rhi = nullptr;
+ bool noSize = false;
+ QPlatformBackingStoreRhiConfig config;
+ QRhiWidget::TextureFormat widgetTextureFormat = QRhiWidget::TextureFormat::RGBA8;
+ QRhiTexture::Format rhiTextureFormat = QRhiTexture::RGBA8;
+ int samples = 1;
+ QSize fixedSize;
+ bool autoRenderTarget = true;
+ bool mirrorVertically = false;
+ QBackingStoreRhiSupport offscreenRenderer;
+ bool textureInvalid = false;
+ QRhiTexture *colorTexture = nullptr;
+ QRhiRenderBuffer *msaaColorBuffer = nullptr;
+ QRhiTexture *resolveTexture = nullptr;
+ QRhiRenderBuffer *depthStencilBuffer = nullptr;
+ QRhiTextureRenderTarget *renderTarget = nullptr;
+ QRhiRenderPassDescriptor *renderPassDescriptor = nullptr;
+ mutable QVector<QRhiResource *> pendingDeletes;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/widgets/kernel/qshortcut_widgets.cpp b/src/widgets/kernel/qshortcut_widgets.cpp
index cb184ef07d..6e25d79bd6 100644
--- a/src/widgets/kernel/qshortcut_widgets.cpp
+++ b/src/widgets/kernel/qshortcut_widgets.cpp
@@ -65,9 +65,6 @@ bool qWidgetShortcutContextMatcher(QObject *object, Qt::ShortcutContext context)
}
}
- if (!active_window)
- return false;
-
#if QT_CONFIG(action)
if (auto a = qobject_cast<QAction *>(object))
return correctActionContext(context, a, active_window);
@@ -95,14 +92,17 @@ bool qWidgetShortcutContextMatcher(QObject *object, Qt::ShortcutContext context)
}
}
- if (!w)
- return false;
+ if (w)
+ return correctWidgetContext(context, w, active_window);
- return correctWidgetContext(context, w, active_window);
+ return QShortcutPrivate::simpleContextMatcher(object, context);
}
static bool correctWidgetContext(Qt::ShortcutContext context, QWidget *w, QWidget *active_window)
{
+ if (!active_window)
+ return false;
+
bool visible = w->isVisible();
#if QT_CONFIG(menubar)
if (auto menuBar = qobject_cast<QMenuBar *>(w)) {
@@ -188,6 +188,9 @@ static bool correctWidgetContext(Qt::ShortcutContext context, QWidget *w, QWidge
#if QT_CONFIG(graphicsview)
static bool correctGraphicsWidgetContext(Qt::ShortcutContext context, QGraphicsWidget *w, QWidget *active_window)
{
+ if (!active_window)
+ return false;
+
bool visible = w->isVisible();
#if defined(Q_OS_DARWIN) && QT_CONFIG(menubar)
if (!QCoreApplication::testAttribute(Qt::AA_DontUseNativeMenuBar) && qobject_cast<QMenuBar *>(w))
@@ -247,6 +250,9 @@ static bool correctGraphicsWidgetContext(Qt::ShortcutContext context, QGraphicsW
#if QT_CONFIG(action)
static bool correctActionContext(Qt::ShortcutContext context, QAction *a, QWidget *active_window)
{
+ if (!active_window)
+ return false;
+
const QObjectList associatedObjects = a->associatedObjects();
#if defined(DEBUG_QSHORTCUTMAP)
if (associatedObjects.isEmpty())
diff --git a/src/widgets/kernel/qstackedlayout.cpp b/src/widgets/kernel/qstackedlayout.cpp
index aca470fe1c..04eacf1d41 100644
--- a/src/widgets/kernel/qstackedlayout.cpp
+++ b/src/widgets/kernel/qstackedlayout.cpp
@@ -8,6 +8,8 @@
#include "private/qwidget_p.h"
#include "private/qlayoutengine_p.h"
+#include <QtCore/qpointer.h>
+
#include <memory>
QT_BEGIN_NAMESPACE
diff --git a/src/widgets/kernel/qtestsupport_widgets.cpp b/src/widgets/kernel/qtestsupport_widgets.cpp
index 3aa13b2416..f7b25b6643 100644
--- a/src/widgets/kernel/qtestsupport_widgets.cpp
+++ b/src/widgets/kernel/qtestsupport_widgets.cpp
@@ -16,8 +16,8 @@
QT_BEGIN_NAMESPACE
-template <typename FunctorWindowGetter, typename FunctorPredicate>
-static bool qWaitForWidgetWindow(FunctorWindowGetter windowGetter, FunctorPredicate predicate, int timeout)
+template <typename FunctorWindowGetter, typename FunctorPredicate, typename Timeout>
+static bool qWaitForWidgetWindow(FunctorWindowGetter windowGetter, FunctorPredicate predicate, Timeout timeout)
{
if (!windowGetter())
return false;
@@ -60,6 +60,32 @@ Q_WIDGETS_EXPORT bool QTest::qWaitForWindowActive(QWidget *widget, int timeout)
timeout);
}
+
+/*!
+ \since 6.7
+
+ Returns \c true, if \a widget is the focus window within \a timeout. Otherwise returns \c false.
+
+ The method is useful in tests that call QWidget::show() and rely on the widget
+ having focus (for receiving keyboard events e.g.) before proceeding.
+
+ \note The method will time out and return \c false if another window prevents \a widget from
+ becoming focused.
+
+ \note Since focus is an exclusive property, \a widget may loose its focus to another window at
+ any time - even after the method has returned \c true.
+
+ \sa qWaitForWindowExposed(), qWaitForWindowActive(), QGuiApplication::focusWindow()
+*/
+Q_WIDGETS_EXPORT bool QTest::qWaitForWindowFocused(QWidget *widget, QDeadlineTimer timeout)
+{
+ return qWaitForWidgetWindow([&]() {
+ return widget->window()->windowHandle();
+ }, [&](QWindow *window) {
+ return qGuiApp->focusWindow() == window;
+ }, timeout);
+}
+
/*!
\since 5.0
diff --git a/src/widgets/kernel/qtestsupport_widgets.h b/src/widgets/kernel/qtestsupport_widgets.h
index 84b529ae31..b49e68db65 100644
--- a/src/widgets/kernel/qtestsupport_widgets.h
+++ b/src/widgets/kernel/qtestsupport_widgets.h
@@ -15,6 +15,7 @@ class QWidget;
namespace QTest {
[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowActive(QWidget *widget, int timeout = 5000);
+[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowFocused(QWidget *widget, QDeadlineTimer timeout = std::chrono::seconds{5});
[[nodiscard]] Q_WIDGETS_EXPORT bool qWaitForWindowExposed(QWidget *widget, int timeout = 5000);
class Q_WIDGETS_EXPORT QTouchEventWidgetSequence : public QTouchEventSequence
diff --git a/src/widgets/kernel/qtooltip.cpp b/src/widgets/kernel/qtooltip.cpp
index 2c68b029b0..35eaa8042a 100644
--- a/src/widgets/kernel/qtooltip.cpp
+++ b/src/widgets/kernel/qtooltip.cpp
@@ -45,15 +45,24 @@ using namespace Qt::StringLiterals;
Rich text displayed in a tool tip is implicitly word-wrapped unless
specified differently with \c{<p style='white-space:pre'>}.
- The simplest and most common way to set a widget's tool tip is by
- calling its QWidget::setToolTip() function.
+ UI elements that are created via \l{QAction} use the tooltip property
+ of the QAction, so for most interactive UI elements, setting that
+ property is the easiest way to provide tool tips.
+
+ \snippet tooltips/main.cpp action_tooltip
+
+ For any other widgets, the simplest and most common way to set
+ a widget's tool tip is by calling its QWidget::setToolTip() function.
+
+ \snippet tooltips/main.cpp static_tooltip
It is also possible to show different tool tips for different
regions of a widget, by using a QHelpEvent of type
QEvent::ToolTip. Intercept the help event in your widget's \l
{QWidget::}{event()} function and call QToolTip::showText() with
- the text you want to display. The \l{widgets/tooltips}{Tooltips}
- example illustrates this technique.
+ the text you want to display.
+
+ \snippet tooltips/main.cpp dynamic_tooltip
If you are calling QToolTip::hideText(), or QToolTip::showText()
with an empty string, as a result of a \l{QEvent::}{ToolTip}-event you
@@ -75,7 +84,7 @@ using namespace Qt::StringLiterals;
\note Tool tips use the inactive color group of QPalette, because tool
tips are not active windows.
- \sa QWidget::toolTip, QAction::toolTip, {Tool Tips Example}
+ \sa QWidget::toolTip, QAction::toolTip
*/
class QTipLabel : public QLabel
@@ -170,8 +179,8 @@ void QTipLabel::reuseTip(const QString &text, int msecDisplayTime, const QPoint
{
#ifndef QT_NO_STYLE_STYLESHEET
if (styleSheetParent){
- disconnect(styleSheetParent, SIGNAL(destroyed()),
- QTipLabel::instance, SLOT(styleSheetParentDestroyed()));
+ disconnect(styleSheetParent, &QWidget::destroyed,
+ this, &QTipLabel::styleSheetParentDestroyed);
styleSheetParent = nullptr;
}
#endif
@@ -320,6 +329,7 @@ bool QTipLabel::eventFilter(QObject *o, QEvent *e)
case QEvent::MouseMove:
if (o == widget && !rect.isNull() && !rect.contains(static_cast<QMouseEvent*>(e)->position().toPoint()))
hideTip();
+ break;
default:
break;
}
@@ -345,12 +355,14 @@ void QTipLabel::placeTip(const QPoint &pos, QWidget *w)
// Set up for cleaning up this later...
QTipLabel::instance->styleSheetParent = w;
if (w) {
- connect(w, SIGNAL(destroyed()),
- QTipLabel::instance, SLOT(styleSheetParentDestroyed()));
- // QTBUG-64550: A font inherited by the style sheet might change the size,
- // particular on Windows, where the tip is not parented on a window.
- QTipLabel::instance->updateSize(pos);
+ connect(w, &QWidget::destroyed,
+ QTipLabel::instance, &QTipLabel::styleSheetParentDestroyed);
}
+ // QTBUG-64550: A font inherited by the style sheet might change the size,
+ // particular on Windows, where the tip is not parented on a window.
+ // The updatesSize() also makes sure that the content size be updated with
+ // correct content margin.
+ QTipLabel::instance->updateSize(pos);
}
#endif //QT_NO_STYLE_STYLESHEET
diff --git a/src/widgets/kernel/qwhatsthis.cpp b/src/widgets/kernel/qwhatsthis.cpp
index c32645dfc4..c80f37267f 100644
--- a/src/widgets/kernel/qwhatsthis.cpp
+++ b/src/widgets/kernel/qwhatsthis.cpp
@@ -97,8 +97,6 @@ QT_BEGIN_NAMESPACE
\sa QToolTip
*/
-Q_CORE_EXPORT void qDeleteInEventHandler(QObject *o);
-
class QWhatsThat : public QWidget
{
Q_OBJECT
@@ -464,7 +462,7 @@ QWhatsThisAction::QWhatsThisAction(QObject *parent) : QAction(tr("What's This?")
setIcon(p);
#endif
setCheckable(true);
- connect(this, SIGNAL(triggered()), this, SLOT(actionTriggered()));
+ connect(this, &QWhatsThisAction::triggered, this, &QWhatsThisAction::actionTriggered);
#ifndef QT_NO_SHORTCUT
setShortcut(Qt::ShiftModifier | Qt::Key_F1);
#endif
@@ -610,7 +608,7 @@ void QWhatsThis::showText(const QPoint &pos, const QString &text, QWidget *w)
*/
void QWhatsThis::hideText()
{
- qDeleteInEventHandler(QWhatsThat::instance);
+ delete QWhatsThat::instance;
}
/*!
diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp
index 1b8e9890b5..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)
@@ -82,6 +83,16 @@ using namespace QNativeInterface::Private;
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 {
+ struct WidgetAttributes { const QWidget *widget; };
+ QDebug operator<<(QDebug debug, const WidgetAttributes &attributes);
+}
+#endif
static inline bool qRectIntersects(const QRect &r1, const QRect &r2)
{
@@ -357,14 +368,14 @@ void QWidget::setAutoFillBackground(bool enabled)
If not, it will be a child of \e parent, and be constrained by
\e parent's geometry (unless you specify Qt::Window as window flag).
\li \c{Qt::WindowFlags f = { }} (where available) sets the window flags;
- the default is suitable for almost all widgets, but to get, for
+ the default is suitable for most widgets, but to get, for
example, a window without a window system frame, you must use
special flags.
\endlist
QWidget has many member functions, but some of them have little direct
functionality; for example, QWidget has a font property, but never uses
- this itself. There are many subclasses which provide real functionality,
+ this itself. There are many subclasses that provide real functionality,
such as QLabel, QPushButton, QListWidget, and QTabWidget.
@@ -372,7 +383,7 @@ void QWidget::setAutoFillBackground(bool enabled)
A widget without a parent widget is always an independent window (top-level
widget). For these widgets, setWindowTitle() and setWindowIcon() set the
- title bar and icon respectively.
+ title bar and icon, respectively.
Non-window widgets are child widgets, displayed within their parent
widgets. Most widgets in Qt are mainly useful as child widgets. For
@@ -385,7 +396,7 @@ void QWidget::setAutoFillBackground(bool enabled)
widgets in a layout provided by QGridLayout. The QLabel child widgets have
been outlined to indicate their full sizes.
- If you want to use a QWidget to hold child widgets you will usually want to
+ If you want to use a QWidget to hold child widgets, you will usually want to
add a layout to the parent QWidget. See \l{Layout Management} for more
information.
@@ -395,8 +406,7 @@ void QWidget::setAutoFillBackground(bool enabled)
When a widget is used as a container to group a number of child widgets, it
is known as a composite widget. These can be created by constructing a
widget with the required visual properties - a QFrame, for example - and
- adding child widgets to it, usually managed by a layout. The above diagram
- shows such a composite widget that was created using Qt Designer.
+ adding child widgets to it, usually managed by a layout.
Composite widgets can also be created by subclassing a standard widget,
such as QWidget or QFrame, and adding the necessary layout and child
@@ -416,7 +426,7 @@ void QWidget::setAutoFillBackground(bool enabled)
Each widget performs all painting operations from within its paintEvent()
function. This is called whenever the widget needs to be redrawn, either
- as a result of some external change or when requested by the application.
+ because of some external change or when requested by the application.
The \l{widgets/analogclock}{Analog Clock example} shows how a simple widget
can handle paint events.
@@ -428,7 +438,7 @@ void QWidget::setAutoFillBackground(bool enabled)
sizeHint() to provide a reasonable default size for the widget and to set
the correct size policy with setSizePolicy().
- By default, composite widgets which do not provide a size hint will be
+ By default, composite widgets that do not provide a size hint will be
sized according to the space requirements of their child widgets.
The size policy lets you supply good default behavior for the layout
@@ -448,9 +458,9 @@ void QWidget::setAutoFillBackground(bool enabled)
delivers events to widgets by calling specific event handler functions with
instances of QEvent subclasses containing information about each event.
- If your widget only contains child widgets, you probably do not need to
+ If your widget only contains child widgets, you probably don't need to
implement any event handlers. If you want to detect a mouse click in a
- child widget call the child's underMouse() function inside the widget's
+ child widget, call the child's underMouse() function inside the widget's
mousePressEvent().
The \l{widgets/scribble}{Scribble example} implements a wider set of
@@ -529,7 +539,7 @@ void QWidget::setAutoFillBackground(bool enabled)
space. (This excludes screen space owned by any of the widget's
children.)
\li leaveEvent() is called when the mouse leaves the widget's screen
- space. If the mouse enters a child widget it will not cause a
+ space. If the mouse enters a child widget, it will not cause a
leaveEvent().
\li moveEvent() is called when the widget has been moved relative to
its parent.
@@ -667,11 +677,11 @@ void QWidget::setAutoFillBackground(bool enabled)
\section1 Transparency and Double Buffering
- Since Qt 4.0, QWidget automatically double-buffers its painting, so there
+ QWidget automatically double-buffers its painting, so there
is no need to write double-buffering code in paintEvent() to avoid
flicker.
- Since Qt 4.1, the contents of parent widgets are propagated by
+ The contents of parent widgets are propagated by
default to each of their children as long as Qt::WA_PaintOnScreen is not
set. Custom widgets can be written to take advantage of this feature by
updating irregular regions (to create non-rectangular child widgets), or
@@ -688,7 +698,7 @@ void QWidget::setAutoFillBackground(bool enabled)
\list
\li The left widget has no additional properties or widget attributes
- set. This default state suits most custom widgets using
+ set. This default state suits most custom widgets that have
transparency, are irregularly-shaped, or do not paint over their
entire area with an opaque brush.
\li The center widget has the \l autoFillBackground property set. This
@@ -699,10 +709,7 @@ void QWidget::setAutoFillBackground(bool enabled)
set. This indicates that the widget will paint over its entire area
with opaque colors. The widget's area will initially be
\e{uninitialized}, represented in the diagram with a red diagonal
- grid pattern that shines through the overpainted area. The
- Qt::WA_OpaquePaintArea attribute is useful for widgets that need to
- paint their own specialized contents quickly and do not need a
- default filled background.
+ grid pattern that shines through the overpainted area.
\endlist
To rapidly update custom widgets with simple background colors, such as
@@ -712,19 +719,18 @@ void QWidget::setAutoFillBackground(bool enabled)
implement the necessary drawing functionality in the widget's paintEvent().
To rapidly update custom widgets that constantly paint over their entire
- areas with opaque content, e.g., video streaming widgets, it is better to
- set the widget's Qt::WA_OpaquePaintEvent, avoiding any unnecessary overhead
- associated with repainting the widget's background.
+ areas with opaque content, for example, video streaming widgets, it is
+ better to set the widget's Qt::WA_OpaquePaintEvent, avoiding any unnecessary
+ overhead associated with repainting the widget's background.
If a widget has both the Qt::WA_OpaquePaintEvent widget attribute \e{and}
the \l autoFillBackground property set, the Qt::WA_OpaquePaintEvent
attribute takes precedence. Depending on your requirements, you should
choose either one of them.
- Since Qt 4.1, the contents of parent widgets are also propagated to
- standard Qt widgets. This can lead to some unexpected results if the
- parent widget is decorated in a non-standard way, as shown in the diagram
- below.
+ The contents of parent widgets are also propagated to standard Qt widgets.
+ This can lead to some unexpected results if the parent widget is decorated
+ in a non-standard way, as shown in the diagram below.
\image propagation-standard.png
@@ -736,8 +742,8 @@ void QWidget::setAutoFillBackground(bool enabled)
\section1 Creating Translucent Windows
- Since Qt 4.5, it has been possible to create windows with translucent regions
- on window systems that support compositing.
+ You can create windows with translucent regions on window systems that
+ support compositing.
To enable this feature in a top-level widget, set its Qt::WA_TranslucentBackground
attribute with setAttribute() and ensure that its background is painted with
@@ -757,19 +763,19 @@ void QWidget::setAutoFillBackground(bool enabled)
\section1 Native Widgets vs Alien Widgets
- Introduced in Qt 4.4, alien widgets are widgets unknown to the windowing
- system. They do not have a native window handle associated with them. This
- feature significantly speeds up widget painting, resizing, and removes flicker.
+ Alien widgets are widgets unknown to the windowing system. They do not have
+ a native window handle associated with them. This feature significantly
+ speeds up widget painting, resizing, and removes flicker.
- Should you require the old behavior with native windows, you can choose
- one of the following options:
+ Should you require the old behavior with native windows, choose one of the
+ following options:
\list 1
\li Use the \c{QT_USE_NATIVE_WINDOWS=1} in your environment.
\li Set the Qt::AA_NativeWindows attribute on your application. All
widgets will be native widgets.
\li Set the Qt::WA_NativeWindow attribute on widgets: The widget itself
- and all of its ancestors will become native (unless
+ and all its ancestors will become native (unless
Qt::WA_DontCreateNativeAncestors is set).
\li Call QWidget::winId to enforce a native window (this implies 3).
\li Set the Qt::WA_PaintOnScreen attribute to enforce a native window
@@ -814,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
}
};
@@ -987,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();
@@ -1026,6 +1027,31 @@ void QWidgetPrivate::createRecursively()
}
}
+QRhi *QWidgetPrivate::rhi() const
+{
+ if (QWidgetRepaintManager *repaintManager = maybeRepaintManager())
+ return repaintManager->rhi();
+ else
+ return nullptr;
+}
+
+/*!
+ \internal
+ Returns the closest parent widget that has a QWindow window handle
+
+ \note This behavior is different from nativeParentWidget(), which
+ returns the closest parent that has a QWindow window handle with
+ a created QPlatformWindow, and hence native window (winId).
+*/
+QWidget *QWidgetPrivate::closestParentWidgetWithWindowHandle() const
+{
+ Q_Q(const QWidget);
+ QWidget *parent = q->parentWidget();
+ while (parent && !parent->windowHandle())
+ parent = parent->parentWidget();
+ return parent;
+}
+
QWindow *QWidgetPrivate::windowHandle(WindowHandleMode mode) const
{
if (mode == WindowHandleMode::Direct || mode == WindowHandleMode::Closest) {
@@ -1035,6 +1061,7 @@ QWindow *QWidgetPrivate::windowHandle(WindowHandleMode mode) const
}
}
if (mode == WindowHandleMode::Closest) {
+ // FIXME: Use closestParentWidgetWithWindowHandle instead
if (auto nativeParent = q_func()->nativeParentWidget()) {
if (auto window = nativeParent->windowHandle())
return window;
@@ -1262,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
@@ -1271,7 +1298,6 @@ void QWidgetPrivate::create()
win->setProperty("_q_showWithoutActivating", QVariant(true));
if (q->testAttribute(Qt::WA_MacAlwaysShowToolWindow))
win->setProperty("_q_macAlwaysShowToolWindow", QVariant(true));
- setNetWmWindowTypes(true); // do nothing if none of WA_X11NetWmWindowType* is set
win->setFlags(flags);
fixPosIncludesFrame();
if (q->testAttribute(Qt::WA_Moved)
@@ -1319,8 +1345,6 @@ void QWidgetPrivate::create()
}
data.window_flags = win->flags();
- if (!win->isTopLevel()) // In a Widget world foreign windows can only be top level
- data.window_flags &= ~Qt::ForeignWindow;
#if QT_CONFIG(xcb)
if (!topData()->role.isNull()) {
@@ -1355,6 +1379,7 @@ void QWidgetPrivate::create()
Q_ASSERT(id != WId(0));
setWinId(id);
}
+ setNetWmWindowTypes(true); // do nothing if none of WA_X11NetWmWindowType* is set
// Check children and create windows for them if necessary
q_createNativeChildrenAndSetParent(q);
@@ -1449,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)
@@ -1659,10 +1676,9 @@ void QWidgetPrivate::deleteExtra()
if (QStyleSheetStyle *proxy = qt_styleSheet(extra->style))
proxy->deref();
#endif
- if (extra->topextra) {
+ if (extra->topextra)
deleteTLSysExtra();
- // extra->topextra->backingStore destroyed in QWidgetPrivate::deleteTLSysExtra()
- }
+
// extra->xic destroyed in QWidget::destroy()
extra.reset();
}
@@ -1672,34 +1688,11 @@ void QWidgetPrivate::deleteSysExtra()
{
}
-static void deleteBackingStore(QWidgetPrivate *d)
-{
- QTLWExtra *topData = d->topData();
-
- delete topData->backingStore;
- topData->backingStore = nullptr;
-}
-
void QWidgetPrivate::deleteTLSysExtra()
{
if (extra && extra->topextra) {
- //the qplatformbackingstore may hold a reference to the window, so the backingstore
- //needs to be deleted first.
-
- extra->topextra->repaintManager.reset(nullptr);
- deleteBackingStore(this);
- extra->topextra->widgetTextures.clear();
-
- //the toplevel might have a context with a "qglcontext associated with it. We need to
- //delete the qglcontext before we delete the qplatformopenglcontext.
- //One unfortunate thing about this is that we potentially create a glContext just to
- //delete it straight afterwards.
- if (extra->topextra->window) {
- extra->topextra->window->destroy();
- }
delete extra->topextra->window;
extra->topextra->window = nullptr;
-
}
}
@@ -2925,7 +2918,7 @@ void QWidget::overrideWindowState(Qt::WindowStates newstate)
\snippet code/src_gui_kernel_qwidget.cpp 0
- In order to restore and activate a minimized window (while
+ To restore and activate a minimized window (while
preserving its maximized and/or full-screen state), use the following:
\snippet code/src_gui_kernel_qwidget.cpp 1
@@ -3001,7 +2994,7 @@ bool QWidget::isFullScreen() const
ICCCM protocol that specifies the communication between X11
clients and the window manager. ICCCM simply does not understand
the concept of non-decorated full-screen windows. Therefore, the
- best we can do is to request a borderless window and place and
+ best you can do is to request a borderless window and place and
resize it to fill the entire screen. Depending on the window
manager, this may or may not work. The borderless window is
requested using MOTIF hints, which are at least partially
@@ -3009,7 +3002,7 @@ bool QWidget::isFullScreen() const
An alternative would be to bypass the window manager entirely and
create a window with the Qt::X11BypassWindowManagerHint flag. This
- has other severe problems though, like totally broken keyboard focus
+ has other severe problems though, like broken keyboard focus
and very strange effects on desktop changes or when the user raises
other windows.
@@ -3109,7 +3102,7 @@ bool QWidget::isEnabledTo(const QWidget *ancestor) const
/*!
Appends the action \a action to this widget's list of actions.
- All QWidgets have a list of \l{QAction}s, however they can be
+ All QWidgets have a list of \l{QAction}s. However, they can be
represented graphically in many different ways. The default use of
the QAction list (as returned by actions()) is to create a context
QMenu.
@@ -3319,10 +3312,10 @@ QAction *QWidget::addAction(const QIcon &icon, const QString &text, const QKeySe
#endif // QT_CONFIG(shortcut)
/*!
- \fn template<typename...Args> QAction *QWidget::addAction(const QString &text, Args&&...args)
- \fn template<typename...Args> QAction *QWidget::addAction(const QString &text, const QKeySequence &shortcut, Args&&...args)
- \fn template<typename...Args> QAction *QWidget::addAction(const QIcon &icon, const QString &text, Args&&...args)
- \fn template<typename...Args> QAction *QWidget::addAction(const QIcon &icon, const QString &text, const QKeySequence &shortcut, Args&&...args)
+ \fn template<typename...Args, typename = compatible_action_slot_args<Args...>> QAction *QWidget::addAction(const QString &text, Args&&...args)
+ \fn template<typename...Args, typename = compatible_action_slot_args<Args...>> QAction *QWidget::addAction(const QString &text, const QKeySequence &shortcut, Args&&...args)
+ \fn template<typename...Args, typename = compatible_action_slot_args<Args...>> QAction *QWidget::addAction(const QIcon &icon, const QString &text, Args&&...args)
+ \fn template<typename...Args, typename = compatible_action_slot_args<Args...>> QAction *QWidget::addAction(const QIcon &icon, const QString &text, const QKeySequence &shortcut, Args&&...args)
\since 6.3
\overload
@@ -3748,7 +3741,7 @@ QRegion QWidget::childrenRegion() const
the current size is smaller.
The minimum size set by this function will override the minimum size
- defined by QLayout. In order to unset the minimum size, use a
+ defined by QLayout. To unset the minimum size, use a
value of \c{QSize(0, 0)}.
By default, this property contains a size with zero width and height.
@@ -4359,7 +4352,7 @@ QWidget *QWidget::nativeParentWidget() const
The background role defines the brush from the widget's \l palette that
is used to render the background.
- If no explicit background role is set, the widget inherts its parent
+ If no explicit background role is set, the widget inherits its parent
widget's background role.
\sa setBackgroundRole(), foregroundRole()
@@ -4503,7 +4496,7 @@ void QWidget::setForegroundRole(QPalette::ColorRole role)
QWidget's palette propagation is similar to its font propagation.
The current style, which is used to render the content of all standard Qt
- widgets, is free to choose colors and brushes from the widget palette, or
+ widgets, is free to choose colors and brushes from the widget palette, or,
in some cases, to ignore the palette (partially, or completely). In
particular, certain styles like GTK style, Mac style, and Windows Vista
style, depend on third party APIs to render the content of widgets,
@@ -5167,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()) {
@@ -5175,6 +5169,7 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset,
} else {
enginePriv->setSystemViewport(oldSystemClip);
}
+ painter->setLayoutDirection(layoutDirection());
d->render(target, targetOffset, toBePainted, renderFlags);
@@ -5182,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);
@@ -6402,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);
}
}
@@ -6644,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) {
@@ -6696,8 +6673,8 @@ void QWidgetPrivate::updateFocusChild()
If the widget has active focus, a \l{focusOutEvent()}{focus out event} is sent to this widget to tell it that it has
lost the focus.
- This widget must enable focus setting in order to get the keyboard
- input focus, i.e. it must call setFocusPolicy().
+ This widget must enable focus setting to get the keyboard
+ input focus; that is, it must call setFocusPolicy().
\sa hasFocus(), setFocus(), focusInEvent(), focusOutEvent(),
setFocusPolicy(), QApplication::focusWidget()
@@ -6853,6 +6830,13 @@ QWidget *QWidget::focusWidget() const
return const_cast<QWidget *>(d_func()->focus_child);
}
+QObject *QWidgetPrivate::focusObject()
+{
+ Q_Q(QWidget);
+ QWidget *proxy = deepestFocusProxy();
+ return proxy ? proxy : q;
+}
+
/*!
Returns the next widget in this widget's focus chain.
@@ -6860,7 +6844,8 @@ QWidget *QWidget::focusWidget() const
*/
QWidget *QWidget::nextInFocusChain() const
{
- return const_cast<QWidget *>(d_func()->focus_next);
+ Q_D(const QWidget);
+ return d->nextPrevElementInFocusChain(QWidgetPrivate::FocusDirection::Next);
}
/*!
@@ -6873,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);
}
/*!
@@ -7028,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)
@@ -7039,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)
@@ -7054,30 +7033,31 @@ 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);
- }
+ // 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);
+}
- // 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);
+void QWidget::setTabOrder(std::initializer_list<QWidget *> widgets)
+{
+ QWidget *prev = nullptr;
+ for (const auto &widget : widgets) {
+ if (!prev) {
+ prev = widget;
+ } else {
+ QWidget::setTabOrder(prev, widget);
+ prev = widget;
+ }
}
}
+
/*!\internal
Moves the relevant subwidgets of this widget from the \a oldtlw's
@@ -7099,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);
}
/*!
@@ -7587,7 +7508,7 @@ bool QWidget::restoreGeometry(const QByteArray &geometry)
}
const int frameHeight = QApplication::style()
- ? QApplication::style()->pixelMetric(QStyle::PM_TitleBarHeight)
+ ? QApplication::style()->pixelMetric(QStyle::PM_TitleBarHeight, nullptr, this)
: 20;
if (!restoredNormalGeometry.isValid())
@@ -7940,21 +7861,29 @@ void QWidget::setUpdatesEnabled(bool enable)
/*!
Shows the widget and its child widgets.
- This is equivalent to calling showFullScreen(), showMaximized(), or setVisible(true),
- depending on the platform's default behavior for the window flags.
+ For child windows, this is equivalent to calling setVisible(true).
+ Otherwise, it is equivalent to calling showFullScreen(), showMaximized(),
+ or setVisible(true), depending on the platform's default behavior for the window flags.
- \sa raise(), showEvent(), hide(), setVisible(), showMinimized(), showMaximized(),
+ \sa raise(), showEvent(), hide(), setVisible(), showMinimized(), showMaximized(),
showNormal(), isVisible(), windowFlags()
*/
void QWidget::show()
{
- Qt::WindowState defaultState = QGuiApplicationPrivate::platformIntegration()->defaultWindowState(data->window_flags);
- if (defaultState == Qt::WindowFullScreen)
- showFullScreen();
- else if (defaultState == Qt::WindowMaximized)
- showMaximized();
- else
- setVisible(true); // Don't call showNormal() as not to clobber Qt::Window(Max/Min)imized
+ // Note: We don't call showNormal() as not to clobber Qt::Window(Max/Min)imized
+
+ if (!isWindow()) {
+ setVisible(true);
+ } else {
+ const auto *platformIntegration = QGuiApplicationPrivate::platformIntegration();
+ Qt::WindowState defaultState = platformIntegration->defaultWindowState(data->window_flags);
+ if (defaultState == Qt::WindowFullScreen)
+ showFullScreen();
+ else if (defaultState == Qt::WindowMaximized)
+ showMaximized();
+ else
+ setVisible(true);
+ }
}
/*! \internal
@@ -8321,13 +8250,17 @@ void QWidgetPrivate::hide_sys()
void QWidget::setVisible(bool visible)
{
+ Q_D(QWidget);
+ qCDebug(lcWidgetShowHide) << "Setting visibility of" << this
+ << "with attributes" << WidgetAttributes{this}
+ << "to" << visible << "via QWidget";
+
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden) == !visible)
return;
// Remember that setVisible was called explicitly
setAttribute(Qt::WA_WState_ExplicitShowHide);
- Q_D(QWidget);
d->setVisible(visible);
}
@@ -8337,6 +8270,10 @@ void QWidget::setVisible(bool visible)
void QWidgetPrivate::setVisible(bool visible)
{
Q_Q(QWidget);
+ qCDebug(lcWidgetShowHide) << "Setting visibility of" << q
+ << "with attributes" << WidgetAttributes{q}
+ << "to" << visible << "via QWidgetPrivate";
+
if (visible) { // show
// Designer uses a trick to make grabWidget work without showing
if (!q->isWindow() && q->parentWidget() && q->parentWidget()->isVisible()
@@ -8416,8 +8353,7 @@ void QWidgetPrivate::setVisible(bool visible)
if (!q->testAttribute(Qt::WA_WState_Hidden)) {
q->setAttribute(Qt::WA_WState_Hidden);
- if (q->testAttribute(Qt::WA_WState_Created))
- hide_helper();
+ hide_helper();
}
// invalidate layout similar to updateGeometry()
@@ -8441,23 +8377,35 @@ void QWidget::setHidden(bool hidden)
setVisible(!hidden);
}
+bool QWidgetPrivate::isExplicitlyHidden() const
+{
+ Q_Q(const QWidget);
+ return q->isHidden() && q->testAttribute(Qt::WA_WState_ExplicitShowHide);
+}
+
void QWidgetPrivate::_q_showIfNotHidden()
{
Q_Q(QWidget);
- if ( !(q->isHidden() && q->testAttribute(Qt::WA_WState_ExplicitShowHide)) )
+ if (!isExplicitlyHidden())
q->setVisible(true);
}
void QWidgetPrivate::showChildren(bool spontaneous)
{
+ Q_Q(QWidget);
+ qCDebug(lcWidgetShowHide) << "Showing children of" << q
+ << "spontaneously" << spontaneous;
+
QList<QObject*> childList = children;
for (int i = 0; i < childList.size(); ++i) {
QWidget *widget = qobject_cast<QWidget*>(childList.at(i));
- if (widget && widget->windowHandle() && !widget->testAttribute(Qt::WA_WState_ExplicitShowHide))
+ if (!widget)
+ continue;
+ qCDebug(lcWidgetShowHide) << "Considering" << widget
+ << "with attributes" << WidgetAttributes{widget};
+ if (widget->windowHandle() && !widget->testAttribute(Qt::WA_WState_ExplicitShowHide))
widget->setAttribute(Qt::WA_WState_Hidden, false);
- if (!widget
- || widget->isWindow()
- || widget->testAttribute(Qt::WA_WState_Hidden))
+ if (widget->isWindow() || widget->testAttribute(Qt::WA_WState_Hidden))
continue;
if (spontaneous) {
widget->setAttribute(Qt::WA_Mapped);
@@ -8468,7 +8416,7 @@ void QWidgetPrivate::showChildren(bool spontaneous)
if (widget->testAttribute(Qt::WA_WState_ExplicitShowHide))
widget->d_func()->show_recursive();
else
- widget->show();
+ widget->d_func()->setVisible(true);
}
}
}
@@ -8476,10 +8424,17 @@ void QWidgetPrivate::showChildren(bool spontaneous)
void QWidgetPrivate::hideChildren(bool spontaneous)
{
Q_Q(QWidget);
+ qCDebug(lcWidgetShowHide) << "Hiding children of" << q
+ << "spontaneously" << spontaneous;
+
QList<QObject*> childList = children;
for (int i = 0; i < childList.size(); ++i) {
QWidget *widget = qobject_cast<QWidget*>(childList.at(i));
- if (!widget || widget->isWindow() || widget->testAttribute(Qt::WA_WState_Hidden))
+ if (!widget)
+ continue;
+ qCDebug(lcWidgetShowHide) << "Considering" << widget
+ << "with attributes" << WidgetAttributes{widget};
+ if (widget->isWindow() || widget->testAttribute(Qt::WA_WState_Hidden))
continue;
if (spontaneous)
@@ -8537,13 +8492,15 @@ void QWidgetPrivate::hideChildren(bool spontaneous)
*/
bool QWidgetPrivate::handleClose(CloseMode mode)
{
+ Q_Q(QWidget);
+ qCDebug(lcWidgetShowHide) << "Handling close event for" << q;
+
if (data.is_closing)
return true;
// We might not have initiated the close, so update the state now that we know
data.is_closing = true;
- Q_Q(QWidget);
QPointer<QWidget> that = q;
if (data.in_destructor)
@@ -8653,7 +8610,7 @@ bool QWidgetPrivate::close()
when the user minimizes the window, and a spontaneous show event
when the window is restored again.
- You almost never have to reimplement the setVisible() function. If
+ You seldom have to reimplement the setVisible() function. If
you need to change some settings before a widget is shown, use
showEvent() instead. If you need to do some delayed initialization
use the Polish event delivered to the event() function.
@@ -9639,7 +9596,7 @@ void QWidget::tabletEvent(QTabletEvent *event)
implementation if you act upon the key.
\sa keyReleaseEvent(), setFocusPolicy(),
- focusInEvent(), focusOutEvent(), event(), QKeyEvent, {Tetrix Example}
+ focusInEvent(), focusOutEvent(), event(), QKeyEvent
*/
void QWidget::keyPressEvent(QKeyEvent *event)
@@ -9690,7 +9647,7 @@ void QWidget::keyReleaseEvent(QKeyEvent *event)
is passed in the \a event parameter
A widget normally must setFocusPolicy() to something other than
- Qt::NoFocus in order to receive focus events. (Note that the
+ Qt::NoFocus to receive focus events. (Note that the
application programmer can call setFocus() on any widget, even
those that do not normally accept focus.)
@@ -9716,7 +9673,7 @@ void QWidget::focusInEvent(QFocusEvent *)
passed in the \a event parameter.
A widget normally must setFocusPolicy() to something other than
- Qt::NoFocus in order to receive focus events. (Note that the
+ Qt::NoFocus to receive focus events. (Note that the
application programmer can call setFocus() on any widget, even
those that do not normally accept focus.)
@@ -9888,13 +9845,8 @@ void QWidget::actionEvent(QActionEvent *)
Main window applications typically use reimplementations of this function to check
whether the user's work has been saved and ask for permission before closing.
- For example, the \l{Qt Widgets - Application Example} uses a helper function to
- determine whether or not to close the window:
-
- \snippet mainwindows/application/mainwindow.cpp 3
- \snippet mainwindows/application/mainwindow.cpp 4
- \sa event(), hide(), close(), QCloseEvent, {Qt Widgets - Application Example}
+ \sa event(), hide(), close(), QCloseEvent
*/
void QWidget::closeEvent(QCloseEvent *event)
@@ -9983,7 +9935,7 @@ QVariant QWidget::inputMethodQuery(Qt::InputMethodQuery query) const
is set, the input method may change its visual components to reflect
that only numbers can be entered.
- \warning Some widgets require certain flags in order to work as
+ \warning Some widgets require certain flags to work as
intended. To set a flag, do \c{w->setInputMethodHints(w->inputMethodHints()|f)}
instead of \c{w->setInputMethodHints(f)}.
@@ -10664,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);
+ }
}
/*!
@@ -10697,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;
@@ -10715,7 +10674,20 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
if (wasCreated) {
if (!testAttribute(Qt::WA_WState_Hidden)) {
+ // Hiding the widget will set WA_WState_Hidden as well, which would
+ // normally require the widget to be explicitly shown again to become
+ // visible, even as a child widget. But we refine this value later in
+ // setParent_sys(), applying WA_WState_Hidden based on whether the
+ // widget is a top level or not.
hide();
+
+ // We reset WA_WState_ExplicitShowHide here, likely as a remnant of
+ // when we only had QWidget::setVisible(), which is treated as an
+ // explicit show/hide. Nowadays we have QWidgetPrivate::setVisible(),
+ // that allows us to hide a widget without affecting ExplicitShowHide.
+ // Though it can be argued that ExplicitShowHide should reflect the
+ // last update of the widget's state, so if we hide the widget as a
+ // side effect of changing parent, perhaps we _should_ reset it?
setAttribute(Qt::WA_WState_ExplicitShowHide, false);
}
if (newParent) {
@@ -10726,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
@@ -10807,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) {
@@ -10842,12 +10814,26 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f)
// do it on newtlw instead, the performance implications of that are
// problematic when it comes to large widget trees.
if (q_evaluateRhiConfig(this, nullptr, &surfaceType)) {
+ const bool wasUsingRhiFlush = newtlw->d_func()->usesRhiFlush;
newtlw->d_func()->usesRhiFlush = true;
+ bool recreate = false;
if (QWindow *w = newtlw->windowHandle()) {
- if (w->surfaceType() != surfaceType) {
- newtlw->destroy();
- newtlw->create();
- }
+ if (w->surfaceType() != surfaceType || !wasUsingRhiFlush)
+ recreate = true;
+ }
+ // QTBUG-115652: Besides the toplevel the nativeParentWidget()'s QWindow must be checked as well.
+ if (QWindow *w = d->windowHandle(QWidgetPrivate::WindowHandleMode::Closest)) {
+ if (w->surfaceType() != surfaceType)
+ recreate = true;
+ }
+ if (recreate) {
+ const auto windowStateBeforeDestroy = newtlw->windowState();
+ const auto visibilityBeforeDestroy = newtlw->isVisible();
+ newtlw->destroy();
+ newtlw->create();
+ Q_ASSERT(newtlw->windowHandle());
+ newtlw->windowHandle()->setWindowStates(windowStateBeforeDestroy);
+ QWidgetPrivate::get(newtlw)->setVisible(visibilityBeforeDestroy);
}
}
}
@@ -10871,57 +10857,61 @@ void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
setWinId(0);
- if (parent != newparent) {
- QObjectPrivate::setParent_helper(newparent); //### why does this have to be done in the _sys function???
- if (q->windowHandle()) {
- q->windowHandle()->setFlags(f);
- QWidget *parentWithWindow =
- newparent ? (newparent->windowHandle() ? newparent : newparent->nativeParentWidget()) : nullptr;
- if (parentWithWindow) {
- QWidget *topLevel = parentWithWindow->window();
- if ((f & Qt::Window) && topLevel && topLevel->windowHandle()) {
- q->windowHandle()->setTransientParent(topLevel->windowHandle());
- q->windowHandle()->setParent(nullptr);
- } else {
- q->windowHandle()->setTransientParent(nullptr);
- q->windowHandle()->setParent(parentWithWindow->windowHandle());
- }
- } else {
- q->windowHandle()->setTransientParent(nullptr);
- q->windowHandle()->setParent(nullptr);
- }
- }
- }
-
if (!newparent) {
f |= Qt::Window;
if (parent)
targetScreen = q->parentWidget()->window()->screen();
}
- bool explicitlyHidden = q->testAttribute(Qt::WA_WState_Hidden) && q->testAttribute(Qt::WA_WState_ExplicitShowHide);
+ const bool destroyWindow = (
+ // Reparenting top level to child
+ (oldFlags & Qt::Window) && !(f & Qt::Window)
+ // And we can dispose of the window
+ && wasCreated && !q->testAttribute(Qt::WA_NativeWindow)
+ );
- // Reparenting toplevel to child
- if (wasCreated && !(f & Qt::Window) && (oldFlags & Qt::Window) && !q->testAttribute(Qt::WA_NativeWindow)) {
- if (extra && extra->hasWindowContainer)
- QWindowContainer::toplevelAboutToBeDestroyed(q);
+ if (parent != newparent) {
+ // Update object parent now, so we can resolve new parent window below
+ QObjectPrivate::setParent_helper(newparent);
- QWindow *newParentWindow = newparent->windowHandle();
- if (!newParentWindow)
- if (QWidget *npw = newparent->nativeParentWidget())
- newParentWindow = npw->windowHandle();
+ if (q->windowHandle())
+ q->windowHandle()->setFlags(f);
- for (QObject *child : q->windowHandle()->children()) {
- QWindow *childWindow = qobject_cast<QWindow *>(child);
- if (!childWindow)
- continue;
+ // If the widget itself or any of its children have been created,
+ // we need to reparent their QWindows as well.
+ QWidget *parentWithWindow = closestParentWidgetWithWindowHandle();
+ // But if the widget is about to be destroyed we must skip the
+ // widget itself, and only reparent children.
+ if (destroyWindow)
+ reparentWidgetWindowChildren(parentWithWindow);
+ else
+ reparentWidgetWindows(parentWithWindow, f);
+ }
+
+ bool explicitlyHidden = isExplicitlyHidden();
+
+ if (destroyWindow) {
+ if (extra && extra->hasWindowContainer)
+ QWindowContainer::toplevelAboutToBeDestroyed(q);
- QWidgetWindow *childWW = qobject_cast<QWidgetWindow *>(childWindow);
- QWidget *childWidget = childWW ? childWW->widget() : nullptr;
- if (!childWW || (childWidget && childWidget->testAttribute(Qt::WA_NativeWindow)))
- childWindow->setParent(newParentWindow);
+ // There shouldn't be any QWindow children left, but if there
+ // are, re-parent them now, before we destroy.
+ if (!q->windowHandle()->children().isEmpty()) {
+ QWidget *parentWithWindow = closestParentWidgetWithWindowHandle();
+ QWindow *newParentWindow = parentWithWindow ? parentWithWindow->windowHandle() : nullptr;
+ for (QObject *child : q->windowHandle()->children()) {
+ if (QWindow *childWindow = qobject_cast<QWindow *>(child)) {
+ qCWarning(lcWidgetWindow) << "Reparenting" << childWindow
+ << "before destroying" << this;
+ childWindow->setParent(newParentWindow);
+ }
+ }
}
- q->destroy();
+
+ // We have reparented any child windows of the widget we are
+ // about to destroy to the new parent window handle, so we can
+ // safely destroy this widget without destroying sub windows.
+ q->destroy(true, false);
}
adjustFlags(f, q);
@@ -10947,6 +10937,53 @@ void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
}
}
+void QWidgetPrivate::reparentWidgetWindows(QWidget *parentWithWindow, Qt::WindowFlags windowFlags)
+{
+ if (QWindow *window = windowHandle()) {
+ // Reparent this QWindow, and all QWindow children will follow
+ if (parentWithWindow) {
+ // The reparented widget has not updated its window flags yet,
+ // so we can't ask the widget directly. And we can't use the
+ // QWindow flags, as unlike QWidgets the QWindow flags always
+ // reflect Qt::Window, even for child windows. And we can't use
+ // QWindow::isTopLevel() either, as that depends on the parent,
+ // which we are in the process of updating. So we propagate the
+ // new flags of the reparented window from setParent_sys().
+ if (windowFlags & Qt::Window) {
+ // Top level windows can only have transient parents,
+ // and the transient parent must be another top level.
+ QWidget *topLevel = parentWithWindow->window();
+ auto *transientParent = topLevel->windowHandle();
+ Q_ASSERT(transientParent);
+ qCDebug(lcWidgetWindow) << "Setting" << window << "transient parent to" << transientParent;
+ window->setTransientParent(transientParent);
+ window->setParent(nullptr);
+ } else {
+ auto *parentWindow = parentWithWindow->windowHandle();
+ qCDebug(lcWidgetWindow) << "Reparenting" << window << "into" << parentWindow;
+ window->setTransientParent(nullptr);
+ window->setParent(parentWindow);
+ }
+ } else {
+ qCDebug(lcWidgetWindow) << "Making" << window << "top level window";
+ window->setTransientParent(nullptr);
+ window->setParent(nullptr);
+ }
+ } else {
+ reparentWidgetWindowChildren(parentWithWindow);
+ }
+}
+
+void QWidgetPrivate::reparentWidgetWindowChildren(QWidget *parentWithWindow)
+{
+ for (auto *child : std::as_const(children)) {
+ if (auto *childWidget = qobject_cast<QWidget*>(child)) {
+ auto *childPrivate = QWidgetPrivate::get(childWidget);
+ childPrivate->reparentWidgetWindows(parentWithWindow);
+ }
+ }
+}
+
/*!
Scrolls the widget including its children \a dx pixels to the
right and \a dy downward. Both \a dx and \a dy may be negative.
@@ -11041,7 +11078,7 @@ void QWidgetPrivate::scroll_sys(int dx, int dy, const QRect &r)
unless updates are disabled or the widget is hidden.
We suggest only using repaint() if you need an immediate repaint,
- for example during animation. In almost all circumstances update()
+ for example during animation. In most circumstances update()
is better, as it permits Qt to optimize for speed and minimize
flicker.
@@ -11537,8 +11574,7 @@ void QWidgetPrivate::setWindowOpacity_sys(qreal level)
its parent because other children of the parent might have been
modified.
- \sa windowTitle, {Qt Widgets - Application Example},
- {MDI Example}
+ \sa windowTitle
*/
bool QWidget::isWindowModified() const
{
@@ -12229,7 +12265,7 @@ void QWidget::setBackingStore(QBackingStore *store)
return;
QBackingStore *oldStore = topData->backingStore;
- deleteBackingStore(d);
+ delete topData->backingStore;
topData->backingStore = store;
QWidgetRepaintManager *repaintManager = d->maybeRepaintManager();
@@ -12427,7 +12463,7 @@ void QWidget::destroy(bool destroyWindow, bool destroySubWindows)
if ((windowType() == Qt::Popup) && qApp)
qApp->d_func()->closePopup(this);
- if (this == QApplicationPrivate::active_window)
+ if (this == qApp->activeWindow())
QApplicationPrivate::setActiveWindow(nullptr);
if (QWidget::mouseGrabber() == this)
releaseMouse();
@@ -12665,7 +12701,7 @@ static void releaseMouseGrabOfWidget(QWidget *widget)
terminal. Use this function with extreme caution, and consider
using the \c -nograb command line option while debugging.
- It is almost never necessary to grab the mouse when using Qt, as
+ It is seldom necessary to grab the mouse when using Qt, as
Qt grabs and releases it sensibly. In particular, Qt grabs the
mouse when a mouse button is pressed and keeps it until the last
button is released.
@@ -13154,22 +13190,47 @@ void QWidgetPrivate::setNetWmWindowTypes(bool skipIfMissing)
#endif
}
+/*!
+ \internal
+ \return \c true, if a child with \param policy exists and isn't a child of \param excludeChildrenOf.
+ Return false otherwise.
+ */
+bool QWidgetPrivate::hasChildWithFocusPolicy(Qt::FocusPolicy policy, const QWidget *excludeChildrenOf) const
+{
+ Q_Q(const QWidget);
+ const QWidgetList &children = q->findChildren<QWidget *>(Qt::FindChildrenRecursively);
+ for (const auto *child : children) {
+ if (child->focusPolicy() == policy && child->isEnabled()
+ && (!excludeChildrenOf || !excludeChildrenOf->isAncestorOf(child))) {
+ return true;
+ }
+ }
+ return false;
+}
+
#ifndef QT_NO_DEBUG_STREAM
-static inline void formatWidgetAttributes(QDebug debug, const QWidget *widget)
+namespace {
+QDebug operator<<(QDebug debug, const WidgetAttributes &attributes)
{
- const QMetaObject *qtMo = qt_getEnumMetaObject(Qt::WA_AttributeCount);
- const QMetaEnum me = qtMo->enumerator(qtMo->indexOfEnumerator("WidgetAttribute"));
- debug << ", attributes=[";
- int count = 0;
- for (int a = 0; a < Qt::WA_AttributeCount; ++a) {
- if (widget->testAttribute(static_cast<Qt::WidgetAttribute>(a))) {
- if (count++)
- debug << ',';
- debug << me.valueToKey(a);
+ const QDebugStateSaver saver(debug);
+ debug.nospace();
+ debug << '[';
+ if (const QWidget *widget = attributes.widget) {
+ const QMetaObject *qtMo = qt_getEnumMetaObject(Qt::WA_AttributeCount);
+ const QMetaEnum me = qtMo->enumerator(qtMo->indexOfEnumerator("WidgetAttribute"));
+ int count = 0;
+ for (int a = 0; a < Qt::WA_AttributeCount; ++a) {
+ if (widget->testAttribute(static_cast<Qt::WidgetAttribute>(a))) {
+ if (count++)
+ debug << ',';
+ debug << me.valueToKey(a);
+ }
}
}
debug << ']';
+ return debug;
+}
}
QDebug operator<<(QDebug debug, const QWidget *widget)
@@ -13189,7 +13250,7 @@ QDebug operator<<(QDebug debug, const QWidget *widget)
debug << ", disabled";
debug << ", states=" << widget->windowState()
<< ", type=" << widget->windowType() << ", flags=" << widget->windowFlags();
- formatWidgetAttributes(debug, widget);
+ debug << ", attributes=" << WidgetAttributes{widget};
if (widget->isWindow())
debug << ", window";
debug << ", " << geometry.width() << 'x' << geometry.height()
@@ -13213,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.h b/src/widgets/kernel/qwidget.h
index a17803cabc..e5e83a43f2 100644
--- a/src/widgets/kernel/qwidget.h
+++ b/src/widgets/kernel/qwidget.h
@@ -432,7 +432,7 @@ public:
void setFocusPolicy(Qt::FocusPolicy policy);
bool hasFocus() const;
static void setTabOrder(QWidget *, QWidget *);
- static inline void setTabOrder(std::initializer_list<QWidget *> widgets);
+ static void setTabOrder(std::initializer_list<QWidget *> widgets);
void setFocusProxy(QWidget *);
QWidget *focusProxy() const;
Qt::ContextMenuPolicy contextMenuPolicy() const;
@@ -769,9 +769,6 @@ private:
#endif // QT_NO_GESTURES
friend class QWidgetEffectSourcePrivate;
-#ifdef Q_OS_MAC
- friend bool qt_mac_is_metal(const QWidget *w);
-#endif
friend Q_WIDGETS_EXPORT QWidgetData *qt_qwidget_data(QWidget *widget);
friend Q_WIDGETS_EXPORT QWidgetPrivate *qt_widget_private(QWidget *widget);
@@ -917,19 +914,6 @@ inline bool QWidget::testAttribute(Qt::WidgetAttribute attribute) const
return testAttribute_helper(attribute);
}
-inline void QWidget::setTabOrder(std::initializer_list<QWidget *> widgets)
-{
- QWidget *prev = nullptr;
- for (const auto &widget : widgets) {
- if (!prev) {
- prev = widget;
- } else {
- QWidget::setTabOrder(prev, widget);
- prev = widget;
- }
- }
-}
-
#define QWIDGETSIZE_MAX ((1<<24)-1)
#ifndef QT_NO_DEBUG_STREAM
diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h
index 2e1c4030f8..9fab9efa62 100644
--- a/src/widgets/kernel/qwidget_p.h
+++ b/src/widgets/kernel/qwidget_p.h
@@ -41,6 +41,9 @@
#include <private/qgesture_p.h>
#include <qpa/qplatformbackingstore.h>
#include <QtGui/private/qbackingstorerhisupport_p.h>
+#include <private/qapplication_p.h>
+
+#include <QtCore/qpointer.h>
#include <vector>
#include <memory>
@@ -48,6 +51,7 @@
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcWidgetPainting);
+Q_DECLARE_LOGGING_CATEGORY(lcWidgetShowHide);
// Extra QWidget data
// - to minimize memory usage for members that are seldom used.
@@ -75,13 +79,10 @@ public:
{
}
- ~QUpdateLaterEvent()
- {
- }
-
inline const QRegion &region() const { return m_region; }
protected:
+ friend class QApplication;
QRegion m_region;
};
@@ -221,6 +222,8 @@ public:
void setSharedPainter(QPainter *painter);
QWidgetRepaintManager *maybeRepaintManager() const;
+ QRhi *rhi() const;
+
enum class WindowHandleMode {
Direct,
Closest,
@@ -368,6 +371,8 @@ public:
void showChildren(bool spontaneous);
void hideChildren(bool spontaneous);
void setParent_sys(QWidget *parent, Qt::WindowFlags);
+ void reparentWidgetWindows(QWidget *parentWithWindow, Qt::WindowFlags windowFlags = {});
+ void reparentWidgetWindowChildren(QWidget *parentWithWindow);
void scroll_sys(int dx, int dy);
void scroll_sys(int dx, int dy, const QRect &r);
void deactivateWidgetCleanup();
@@ -380,6 +385,7 @@ public:
void show_sys();
void hide_sys();
void hide_helper();
+ bool isExplicitlyHidden() const;
void _q_showIfNotHidden();
void setVisible(bool);
@@ -579,7 +585,7 @@ public:
inline QRect mapFromWS(const QRect &r) const
{ return r.translated(data.wrect.topLeft()); }
- virtual QObject *focusObject() { return nullptr; }
+ virtual QObject *focusObject();
virtual QPlatformBackingStoreRhiConfig rhiConfig() const { return {}; }
@@ -634,6 +640,8 @@ public:
std::string flagsForDumping() const override;
+ QWidget *closestParentWidgetWithWindowHandle() const;
+
// Variables.
// Regular pointers (keep them together to avoid gaps on 64 bit architectures).
std::unique_ptr<QWExtra> extra;
@@ -726,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()
@@ -736,6 +778,7 @@ public:
bool stealKeyboardGrab(bool grab);
bool stealMouseGrab(bool grab);
+ bool hasChildWithFocusPolicy(Qt::FocusPolicy policy, const QWidget *excludeChildrenOf = nullptr) const;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QWidgetPrivate::DrawWidgetFlags)
diff --git a/src/widgets/kernel/qwidgetaction.cpp b/src/widgets/kernel/qwidgetaction.cpp
index 4a3fb2be89..13f386d3ec 100644
--- a/src/widgets/kernel/qwidgetaction.cpp
+++ b/src/widgets/kernel/qwidgetaction.cpp
@@ -5,6 +5,8 @@
#include "qwidget.h"
#include "qdebug.h"
+#include <QtWidgets/private/qwidget_p.h>
+
#include "qwidgetaction_p.h"
QT_BEGIN_NAMESPACE
@@ -82,9 +84,9 @@ QWidgetAction::QWidgetAction(QObject *parent)
QWidgetAction::~QWidgetAction()
{
Q_D(QWidgetAction);
- for (int i = 0; i < d->createdWidgets.size(); ++i)
- disconnect(d->createdWidgets.at(i), SIGNAL(destroyed(QObject*)),
- this, SLOT(_q_widgetDestroyed(QObject*)));
+ for (QWidget *w : std::as_const(d->createdWidgets))
+ QObjectPrivate::disconnect(w, &QWidget::destroyed,
+ d, &QWidgetActionPrivate::widgetDestroyed);
QList<QWidget *> widgetsToDelete = d->createdWidgets;
d->createdWidgets.clear();
qDeleteAll(widgetsToDelete);
@@ -108,7 +110,7 @@ void QWidgetAction::setDefaultWidget(QWidget *widget)
if (!widget)
return;
- setVisible(!(widget->isHidden() && widget->testAttribute(Qt::WA_WState_ExplicitShowHide)));
+ setVisible(!QWidgetPrivate::get(widget)->isExplicitlyHidden());
d->defaultWidget->hide();
d->defaultWidget->setParent(nullptr);
d->defaultWidgetInUse = false;
@@ -147,8 +149,8 @@ QWidget *QWidgetAction::requestWidget(QWidget *parent)
return d->defaultWidget;
}
- connect(w, SIGNAL(destroyed(QObject*)),
- this, SLOT(_q_widgetDestroyed(QObject*)));
+ QObjectPrivate::connect(w, &QWidget::destroyed,
+ d, &QWidgetActionPrivate::widgetDestroyed);
d->createdWidgets.append(w);
return w;
}
@@ -175,8 +177,8 @@ void QWidgetAction::releaseWidget(QWidget *widget)
if (!d->createdWidgets.contains(widget))
return;
- disconnect(widget, SIGNAL(destroyed(QObject*)),
- this, SLOT(_q_widgetDestroyed(QObject*)));
+ QObjectPrivate::disconnect(widget, &QWidget::destroyed,
+ d, &QWidgetActionPrivate::widgetDestroyed);
d->createdWidgets.removeAll(widget);
deleteWidget(widget);
}
diff --git a/src/widgets/kernel/qwidgetaction.h b/src/widgets/kernel/qwidgetaction.h
index b8539f275b..296ff52d1e 100644
--- a/src/widgets/kernel/qwidgetaction.h
+++ b/src/widgets/kernel/qwidgetaction.h
@@ -37,7 +37,6 @@ protected:
private:
Q_DISABLE_COPY(QWidgetAction)
- Q_PRIVATE_SLOT(d_func(), void _q_widgetDestroyed(QObject *))
friend class QToolBar;
};
diff --git a/src/widgets/kernel/qwidgetaction_p.h b/src/widgets/kernel/qwidgetaction_p.h
index bd156241c6..6a1cd8c186 100644
--- a/src/widgets/kernel/qwidgetaction_p.h
+++ b/src/widgets/kernel/qwidgetaction_p.h
@@ -18,6 +18,8 @@
#include <QtWidgets/private/qtwidgetsglobal_p.h>
#include "private/qaction_widgets_p.h"
+#include <QtCore/qpointer.h>
+
QT_REQUIRE_CONFIG(action);
QT_BEGIN_NAMESPACE
@@ -32,7 +34,7 @@ public:
uint defaultWidgetInUse : 1;
uint autoCreated : 1; // created by QToolBar::addWidget and the like
- inline void _q_widgetDestroyed(QObject *o) {
+ inline void widgetDestroyed(QObject *o) {
createdWidgets.removeAll(static_cast<QWidget *>(o));
}
};
diff --git a/src/widgets/kernel/qwidgetrepaintmanager.cpp b/src/widgets/kernel/qwidgetrepaintmanager.cpp
index 3cc043ca42..607a767a20 100644
--- a/src/widgets/kernel/qwidgetrepaintmanager.cpp
+++ b/src/widgets/kernel/qwidgetrepaintmanager.cpp
@@ -1,7 +1,6 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
#include "qplatformdefs.h"
#include "qwidgetrepaintmanager_p.h"
@@ -49,7 +48,7 @@ public:
}
bool isLocked() const {
- foreach (bool v, m_locked) {
+ for (const auto &[_, v] : m_locked.asKeyValueRange()) {
if (v)
return true;
}
@@ -340,9 +339,9 @@ void QWidgetRepaintManager::sendUpdateRequest(QWidget *widget, UpdateTime update
// compositing and waiting for vsync each and every time. Change to
// UpdateLater, except for approx. once per frame to prevent starvation in
// case the control does not get back to the event loop.
- QWidget *w = widget->window();
- if (updateTime == UpdateNow && w && w->windowHandle() && QWindowPrivate::get(w->windowHandle())->compositing) {
+ if (updateTime == UpdateNow && QWidgetPrivate::get(widget)->textureChildSeen) {
int refresh = 60;
+ QWidget *w = widget->window();
QScreen *ws = w->windowHandle()->screen();
if (ws)
refresh = ws->refreshRate();
@@ -351,12 +350,16 @@ void QWidgetRepaintManager::sendUpdateRequest(QWidget *widget, UpdateTime update
const qint64 elapsed = wd->lastComposeTime.elapsed();
if (elapsed <= qint64(1000.0f / refresh))
updateTime = UpdateLater;
- }
+ }
}
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: {
@@ -707,7 +710,9 @@ void QWidgetRepaintManager::paintAndFlush()
const QRect tlwRect = tlw->data->crect;
if (!updatesDisabled && store->size() != tlwRect.size()) {
- if (hasStaticContents() && !store->size().isEmpty() ) {
+ QPlatformIntegration *integration = QGuiApplicationPrivate::platformIntegration();
+ if (hasStaticContents() && !store->size().isEmpty()
+ && integration->hasCapability(QPlatformIntegration::BackingStoreStaticContents)) {
// Repaint existing dirty area and newly visible area.
const QRect clipRect(QPoint(0, 0), store->size());
const QRegion staticRegion(staticContents(nullptr, clipRect));
@@ -800,7 +805,6 @@ void QWidgetRepaintManager::paintAndFlush()
QTLWExtra *tlwExtra = tlw->d_func()->topData();
tlwExtra->widgetTextures.clear();
findAllTextureWidgetsRecursively(tlw, tlw);
- qt_window_private(tlw->windowHandle())->compositing = false; // will get updated in flush()
if (toClean.isEmpty()) {
// Nothing to repaint. However renderToTexture widgets are handled
@@ -1070,7 +1074,6 @@ void QWidgetRepaintManager::flush(QWidget *widget, const QRegion &region, QPlatf
if (!widgetTextures)
widgetTextures = qt_dummy_platformTextureList;
- qt_window_private(tlw->windowHandle())->compositing = true;
QWidgetPrivate *widgetWindowPrivate = widget->window()->d_func();
widgetWindowPrivate->sendComposeStatus(widget->window(), false);
// A window may have alpha even when the app did not request
@@ -1141,11 +1144,7 @@ void QWidgetRepaintManager::removeStaticWidget(QWidget *widget)
bool QWidgetRepaintManager::hasStaticContents() const
{
-#if defined(Q_OS_WIN)
return !staticWidgets.isEmpty();
-#else
- return !staticWidgets.isEmpty() && false;
-#endif
}
/*!
diff --git a/src/widgets/kernel/qwidgetsvariant.cpp b/src/widgets/kernel/qwidgetsvariant.cpp
index f5d511abd2..17a19aa780 100644
--- a/src/widgets/kernel/qwidgetsvariant.cpp
+++ b/src/widgets/kernel/qwidgetsvariant.cpp
@@ -12,7 +12,8 @@ QT_BEGIN_NAMESPACE
namespace {
-static const struct : QMetaTypeModuleHelper
+// NOLINTNEXTLINE(cppcoreguidelines-virtual-class-destructor): this is not a base class
+static constexpr struct : QMetaTypeModuleHelper
{
const QtPrivate::QMetaTypeInterface *interfaceForType(int type) const override {
switch (type) {
diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp
index 7756893369..e7f0a84004 100644
--- a/src/widgets/kernel/qwidgetwindow.cpp
+++ b/src/widgets/kernel/qwidgetwindow.cpp
@@ -39,21 +39,25 @@ public:
void setVisible(bool visible) override
{
Q_Q(QWidgetWindow);
+ qCDebug(lcWidgetShowHide) << "Setting visibility of" << q->widget()
+ << "to" << visible << "via QWidgetWindowPrivate";
+
if (QWidget *widget = q->widget()) {
- // Check if the widget was already hidden, as this indicates it was done
- // explicitly and not because the parent window in this case made it hidden.
- // In which case do not automatically show the widget when the parent
- // window is shown.
- const bool wasExplicitShowHide = widget->testAttribute(Qt::WA_WState_ExplicitShowHide);
- const bool wasHidden = widget->testAttribute(Qt::WA_WState_Hidden);
- QWidgetPrivate::get(widget)->setVisible(visible);
- if (wasExplicitShowHide) {
- widget->setAttribute(Qt::WA_WState_ExplicitShowHide, wasExplicitShowHide);
- widget->setAttribute(Qt::WA_WState_Hidden, wasHidden);
- }
- } else {
- QWindowPrivate::setVisible(visible);
+ // If the widget's visible state is already matching the new QWindow
+ // visible state we assume the widget has already synced up.
+ if (visible != widget->isVisible())
+ QWidgetPrivate::get(widget)->setVisible(visible);
}
+
+ // If we end up calling QWidgetPrivate::setVisible() above, we will
+ // in most cases recurse back into setNativeWindowVisibility() to
+ // update the QWindow state. But during QWidget::destroy() this is
+ // not the case, as Qt::WA_WState_Created has been unset by the time
+ // we check if we should call hide_helper(). We handle this case, as
+ // well as the cases where we don't call QWidgetPrivate::setVisible(),
+ // by syncing up the QWindow state here if needed.
+ if (q->isVisible() != visible)
+ QWindowPrivate::setVisible(visible);
}
QWindow *eventReceiver() override {
@@ -73,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
@@ -134,6 +171,21 @@ QWidgetWindow::QWidgetWindow(QWidget *widget)
QWidgetWindow::~QWidgetWindow()
{
+ if (!m_widget)
+ return;
+
+ QTLWExtra *topData = QWidgetPrivate::get(m_widget)->topData();
+ Q_ASSERT(topData);
+
+ // The QPlaformBackingStore may hold a reference to the window,
+ // so the backingstore needs to be deleted first.
+ topData->repaintManager.reset(nullptr);
+ delete topData->backingStore;
+ topData->backingStore = nullptr;
+ topData->widgetTextures.clear();
+
+ // Too late to do anything beyond this point
+ topData->window = nullptr;
}
#if QT_CONFIG(accessibility)
@@ -170,6 +222,9 @@ QObject *QWidgetWindow::focusObject() const
void QWidgetWindow::setNativeWindowVisibility(bool visible)
{
Q_D(QWidgetWindow);
+ qCDebug(lcWidgetShowHide) << "Setting visibility of" << this
+ << "to" << visible << "via QWidgetWindow::setNativeWindowVisibility";
+
// Call base class setVisible() implementation to run the QWindow
// visibility logic. Don't call QWidgetWindowPrivate::setVisible()
// since that will recurse back into QWidget code.
@@ -455,11 +510,11 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
QEvent::MouseButtonRelease : QEvent::MouseButtonPress;
if (QApplicationPrivate::inPopupMode()) {
QPointer<QWidget> activePopupWidget = QApplication::activePopupWidget();
- QPoint mapped = event->position().toPoint();
+ QPointF mapped = event->position();
if (activePopupWidget != m_widget)
- mapped = activePopupWidget->mapFromGlobal(event->globalPosition().toPoint());
+ mapped = activePopupWidget->mapFromGlobal(event->globalPosition());
bool releaseAfter = false;
- QWidget *popupChild = activePopupWidget->childAt(mapped);
+ QWidget *popupChild = activePopupWidget->childAt(mapped.toPoint());
if (activePopupWidget != qt_popup_down) {
qt_button_down = nullptr;
@@ -486,15 +541,15 @@ void QWidgetWindow::handleMouseEvent(QMouseEvent *event)
// deliver event
qt_replay_popup_mouse_event = false;
QPointer<QWidget> receiver = activePopupWidget;
- QPoint widgetPos = mapped;
+ QPointF widgetPos = mapped;
if (qt_button_down)
receiver = qt_button_down;
else if (popupChild)
receiver = popupChild;
if (receiver != activePopupWidget)
- widgetPos = receiver->mapFromGlobal(event->globalPosition().toPoint());
+ widgetPos = receiver->mapFromGlobal(event->globalPosition());
- const bool reallyUnderMouse = activePopupWidget->rect().contains(mapped);
+ const bool reallyUnderMouse = activePopupWidget->rect().contains(mapped.toPoint());
const bool underMouse = activePopupWidget->underMouse();
if (underMouse != reallyUnderMouse) {
if (reallyUnderMouse) {
diff --git a/src/widgets/kernel/qwidgetwindow_p.h b/src/widgets/kernel/qwidgetwindow_p.h
index 0eee3cd900..0ee6b71a79 100644
--- a/src/widgets/kernel/qwidgetwindow_p.h
+++ b/src/widgets/kernel/qwidgetwindow_p.h
@@ -22,6 +22,8 @@
#include <QtGui/private/qevent_p.h>
#include <QtWidgets/qwidget.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
diff --git a/src/widgets/kernel/qwindowcontainer.cpp b/src/widgets/kernel/qwindowcontainer.cpp
index 072a0bc5fa..1aaf04af43 100644
--- a/src/widgets/kernel/qwindowcontainer.cpp
+++ b/src/widgets/kernel/qwindowcontainer.cpp
@@ -3,7 +3,9 @@
#include "qwindowcontainer_p.h"
#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>
@@ -14,6 +16,8 @@
#include <QAbstractScrollArea>
#include <QPainter>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
@@ -25,7 +29,6 @@ public:
QWindowContainerPrivate()
: window(nullptr)
- , oldFocusWindow(nullptr)
, usesNativeWidgets(false)
{
}
@@ -100,7 +103,6 @@ public:
}
QPointer<QWindow> window;
- QWindow *oldFocusWindow;
QWindow fakeParent;
uint usesNativeWidgets : 1;
@@ -162,11 +164,31 @@ public:
application can greatly hurt the overall performance of the
application.
+ \li Since 6.7, if \a window belongs to a widget (that is, \a window
+ was received from calling \l windowHandle()), no container will be
+ created. Instead, this function will return the widget itself, after
+ being reparented to \l parent. Since no container will be created,
+ \a flags will be ignored. In other words, if \a window belongs to
+ a widget, consider just reparenting that widget to \a parent instead
+ of using this function.
+
\endlist
*/
QWidget *QWidget::createWindowContainer(QWindow *window, QWidget *parent, Qt::WindowFlags flags)
{
+ // Embedding a QWidget in a window container doesn't make sense,
+ // and has various issues in practice, so just return the widget
+ // itself.
+ if (auto *widgetWindow = qobject_cast<QWidgetWindow *>(window)) {
+ QWidget *widget = widgetWindow->widget();
+ if (flags != Qt::WindowFlags()) {
+ qWarning() << window << "refers to a widget:" << widget
+ << "WindowFlags" << flags << "will be ignored.";
+ }
+ widget->setParent(parent);
+ return widget;
+ }
return new QWindowContainer(window, parent, flags);
}
@@ -184,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())
@@ -196,7 +219,8 @@ QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt:
setAcceptDrops(true);
- connect(QGuiApplication::instance(), SIGNAL(focusWindowChanged(QWindow*)), this, SLOT(focusWindowChanged(QWindow*)));
+ connect(containedWindow(), &QWindow::minimumHeightChanged, this, &QWindowContainer::updateGeometry);
+ connect(containedWindow(), &QWindow::minimumWidthChanged, this, &QWindowContainer::updateGeometry);
}
QWindow *QWindowContainer::containedWindow() const
@@ -217,29 +241,14 @@ 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;
}
-
-
-/*!
- \internal
- */
-
-void QWindowContainer::focusWindowChanged(QWindow *focusWindow)
-{
- Q_D(QWindowContainer);
- d->oldFocusWindow = focusWindow;
- if (focusWindow == d->window) {
- QWidget *widget = QApplication::focusWidget();
- if (widget)
- widget->clearFocus();
- }
-}
-
/*!
\internal
*/
@@ -254,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;
}
@@ -305,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;
@@ -346,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