1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qwindowcontainer_p.h"
5#include "qwidget_p.h"
6#include "qwidgetwindow_p.h"
7#include <QtGui/qwindow.h>
8#include <QtGui/private/qwindow_p.h>
9#include <QtGui/private/qguiapplication_p.h>
10#include <qpa/qplatformintegration.h>
11#include <QDebug>
12
13#if QT_CONFIG(mdiarea)
14#include <QMdiSubWindow>
15#endif
16#include <QAbstractScrollArea>
17#include <QPainter>
18
19#include <QtCore/qpointer.h>
20
21QT_BEGIN_NAMESPACE
22
23using namespace Qt::StringLiterals;
24
25class QWindowContainerPrivate : public QWidgetPrivate
26{
27public:
28 Q_DECLARE_PUBLIC(QWindowContainer)
29
30 QWindowContainerPrivate()
31 : window(nullptr)
32 , usesNativeWidgets(false)
33 {
34 }
35
36 ~QWindowContainerPrivate() { }
37
38 static QWindowContainerPrivate *get(QWidget *w) {
39 QWindowContainer *wc = qobject_cast<QWindowContainer *>(object: w);
40 if (wc)
41 return wc->d_func();
42 return nullptr;
43 }
44
45 void updateGeometry() {
46 Q_Q(QWindowContainer);
47 if (!q->isWindow() && (q->geometry().bottom() <= 0 || q->geometry().right() <= 0))
48 /* Qt (e.g. QSplitter) sometimes prefer to hide a widget by *not* calling
49 setVisible(false). This is often done by setting its coordinates to a sufficiently
50 negative value so that its clipped outside the parent. Since a QWindow is not clipped
51 to widgets in general, it needs to be dealt with as a special case.
52 */
53 window->setGeometry(q->geometry());
54 else if (usesNativeWidgets)
55 window->setGeometry(q->rect());
56 else
57 window->setGeometry(QRect(q->mapTo(q->window(), QPoint()), q->size()));
58 }
59
60 void updateUsesNativeWidgets()
61 {
62 if (window->parent() == nullptr)
63 return;
64 Q_Q(QWindowContainer);
65 if (q->testAttribute(attribute: Qt::WA_DontCreateNativeAncestors))
66 return;
67 if (q->internalWinId()) {
68 // Allow use native widgets if the window container is already a native widget
69 usesNativeWidgets = true;
70 return;
71 }
72 bool nativeWidgetSet = false;
73 QWidget *p = q->parentWidget();
74 while (p) {
75 if (false
76#if QT_CONFIG(mdiarea)
77 || qobject_cast<QMdiSubWindow *>(object: p) != 0
78#endif
79#if QT_CONFIG(scrollarea)
80 || qobject_cast<QAbstractScrollArea *>(object: p) != 0
81#endif
82 ) {
83 q->winId();
84 nativeWidgetSet = true;
85 break;
86 }
87 p = p->parentWidget();
88 }
89 usesNativeWidgets = nativeWidgetSet;
90 }
91
92 void markParentChain() {
93 Q_Q(QWindowContainer);
94 QWidget *p = q;
95 while (p) {
96 QWidgetPrivate *d = static_cast<QWidgetPrivate *>(QWidgetPrivate::get(w: p));
97 d->createExtra();
98 d->extra->hasWindowContainer = true;
99 p = p->parentWidget();
100 }
101 }
102
103 bool isStillAnOrphan() const {
104 return window->parent() == &fakeParent;
105 }
106
107 QPointer<QWindow> window;
108 QWindow fakeParent;
109
110 uint usesNativeWidgets : 1;
111};
112
113
114
115/*!
116 \fn QWidget *QWidget::createWindowContainer(QWindow *window, QWidget *parent, Qt::WindowFlags flags);
117
118 Creates a QWidget that makes it possible to embed \a window into
119 a QWidget-based application.
120
121 The window container is created as a child of \a parent and with
122 window flags \a flags.
123
124 Once the window has been embedded into the container, the
125 container will control the window's geometry and
126 visibility. Explicit calls to QWindow::setGeometry(),
127 QWindow::show() or QWindow::hide() on an embedded window is not
128 recommended.
129
130 The container takes over ownership of \a window. The window can
131 be removed from the window container with a call to
132 QWindow::setParent().
133
134 The window container is attached as a native child window to the
135 toplevel window it is a child of. When a window container is used
136 as a child of a QAbstractScrollArea or QMdiArea, it will
137 create a \l {Native Widgets vs Alien Widgets} {native window} for
138 every widget in its parent chain to allow for proper stacking and
139 clipping in this use case. Creating a native window for the window
140 container also allows for proper stacking and clipping. This must
141 be done before showing the window container. Applications with
142 many native child windows may suffer from performance issues.
143
144 The window container has a number of known limitations:
145
146 \list
147
148 \li Stacking order; The embedded window will stack on top of the
149 widget hierarchy as an opaque box. The stacking order of multiple
150 overlapping window container instances is undefined.
151
152 \li Rendering Integration; The window container does not interoperate
153 with QGraphicsProxyWidget, QWidget::render() or similar functionality.
154
155 \li Focus Handling; It is possible to let the window container
156 instance have any focus policy and it will delegate focus to the
157 window via a call to QWindow::requestActivate(). However,
158 returning to the normal focus chain from the QWindow instance will
159 be up to the QWindow instance implementation itself. Also, whether
160 QWindow::requestActivate() actually gives the window focus, is
161 platform dependent.
162
163 Since 6.8, if embedding a Qt Quick based window, tab presses will
164 transition in and out of the embedded QML window, allowing focus to move
165 to the next or previous focusable object in the window container chain.
166
167 \li Using many window container instances in a QWidget-based
168 application can greatly hurt the overall performance of the
169 application.
170
171 \li Since 6.7, if \a window belongs to a widget (that is, \a window
172 was received from calling \l windowHandle()), no container will be
173 created. Instead, this function will return the widget itself, after
174 being reparented to \l parent. Since no container will be created,
175 \a flags will be ignored. In other words, if \a window belongs to
176 a widget, consider just reparenting that widget to \a parent instead
177 of using this function.
178
179 \endlist
180 */
181
182QWidget *QWidget::createWindowContainer(QWindow *window, QWidget *parent, Qt::WindowFlags flags)
183{
184 // Embedding a QWidget in a window container doesn't make sense,
185 // and has various issues in practice, so just return the widget
186 // itself.
187 if (auto *widgetWindow = qobject_cast<QWidgetWindow *>(object: window)) {
188 QWidget *widget = widgetWindow->widget();
189 if (flags != Qt::WindowFlags()) {
190 qWarning() << window << "refers to a widget:" << widget
191 << "WindowFlags" << flags << "will be ignored.";
192 }
193 widget->setParent(parent);
194 return widget;
195 }
196 return new QWindowContainer(window, parent, flags);
197}
198
199/*!
200 \internal
201 */
202
203QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt::WindowFlags flags)
204 : QWidget(*new QWindowContainerPrivate, parent, flags)
205{
206 Q_D(QWindowContainer);
207 if (Q_UNLIKELY(!embeddedWindow)) {
208 qWarning(msg: "QWindowContainer: embedded window cannot be null");
209 return;
210 }
211
212 d->window = embeddedWindow;
213 d->window->installEventFilter(filterObj: this);
214
215 QString windowName = d->window->objectName();
216 if (windowName.isEmpty())
217 windowName = QString::fromUtf8(utf8: d->window->metaObject()->className());
218 d->fakeParent.setObjectName(windowName + "ContainerFakeParent"_L1);
219
220 d->window->setParent(&d->fakeParent);
221 d->window->parent()->installEventFilter(filterObj: this);
222 d->window->setFlag(Qt::SubWindow);
223
224 setAcceptDrops(true);
225
226 connect(sender: containedWindow(), signal: &QWindow::minimumHeightChanged, context: this, slot: &QWindowContainer::updateGeometry);
227 connect(sender: containedWindow(), signal: &QWindow::minimumWidthChanged, context: this, slot: &QWindowContainer::updateGeometry);
228}
229
230QWindow *QWindowContainer::containedWindow() const
231{
232 Q_D(const QWindowContainer);
233 return d->window;
234}
235
236/*!
237 \internal
238 */
239
240QWindowContainer::~QWindowContainer()
241{
242 Q_D(QWindowContainer);
243
244 // Call destroy() explicitly first. The dtor would do this too, but
245 // QEvent::PlatformSurface delivery relies on virtuals. Getting
246 // SurfaceAboutToBeDestroyed can be essential for OpenGL, Vulkan, etc.
247 // QWindow subclasses in particular. Keep these working.
248 if (d->window) {
249 d->window->removeEventFilter(obj: this);
250 d->window->destroy();
251 }
252
253 delete d->window;
254}
255
256/*!
257 \internal
258 */
259
260bool QWindowContainer::eventFilter(QObject *o, QEvent *e)
261{
262 Q_D(QWindowContainer);
263 if (!d->window)
264 return false;
265
266 if (e->type() == QEvent::ChildRemoved) {
267 QChildEvent *ce = static_cast<QChildEvent *>(e);
268 if (ce->child() == d->window) {
269 o->removeEventFilter(obj: this);
270 d->window->removeEventFilter(obj: this);
271 d->window = nullptr;
272 }
273 } else if (e->type() == QEvent::FocusIn) {
274 if (o == d->window)
275 setFocus(Qt::ActiveWindowFocusReason);
276 }
277 return false;
278}
279
280/*!
281 \internal
282 */
283
284bool QWindowContainer::event(QEvent *e)
285{
286 Q_D(QWindowContainer);
287 if (!d->window)
288 return QWidget::event(event: e);
289
290 QEvent::Type type = e->type();
291 switch (type) {
292 // The only thing we are interested in is making sure our sizes stay
293 // in sync, so do a catch-all case.
294 case QEvent::Resize:
295 d->updateGeometry();
296 break;
297 case QEvent::Move:
298 d->updateGeometry();
299 break;
300 case QEvent::PolishRequest:
301 d->updateGeometry();
302 break;
303 case QEvent::Show:
304 d->updateUsesNativeWidgets();
305 if (d->isStillAnOrphan()) {
306 d->window->parent()->removeEventFilter(obj: this);
307 d->window->setParent(d->usesNativeWidgets
308 ? windowHandle()
309 : window()->windowHandle());
310 d->fakeParent.destroy();
311 if (d->window->parent())
312 d->window->parent()->installEventFilter(filterObj: this);
313 }
314 if (d->window->parent()) {
315 d->markParentChain();
316 d->window->show();
317 }
318 break;
319 case QEvent::Hide:
320 if (d->window->parent())
321 d->window->hide();
322 break;
323 case QEvent::FocusIn:
324 if (d->window->parent()) {
325 if (QGuiApplication::focusWindow() != d->window) {
326 QFocusEvent *event = static_cast<QFocusEvent *>(e);
327 const auto reason = event->reason();
328 QWindowPrivate::FocusTarget target = QWindowPrivate::FocusTarget::Current;
329 if (reason == Qt::TabFocusReason)
330 target = QWindowPrivate::FocusTarget::First;
331 else if (reason == Qt::BacktabFocusReason)
332 target = QWindowPrivate::FocusTarget::Last;
333 qt_window_private(window: d->window)->setFocusToTarget(target, reason);
334 d->window->requestActivate();
335 }
336 }
337 break;
338#if QT_CONFIG(draganddrop)
339 case QEvent::Drop:
340 case QEvent::DragMove:
341 case QEvent::DragLeave:
342 QCoreApplication::sendEvent(receiver: d->window, event: e);
343 return e->isAccepted();
344 case QEvent::DragEnter:
345 // Don't reject drag events for the entire widget when one
346 // item rejects the drag enter
347 QCoreApplication::sendEvent(receiver: d->window, event: e);
348 e->accept();
349 return true;
350#endif
351
352 case QEvent::Paint:
353 {
354 static bool needsPunch = !QGuiApplicationPrivate::platformIntegration()->hasCapability(
355 cap: QPlatformIntegration::TopStackedNativeChildWindows);
356 if (needsPunch) {
357 QPainter p(this);
358 p.setCompositionMode(QPainter::CompositionMode_Source);
359 p.fillRect(r: rect(), c: Qt::transparent);
360 }
361 break;
362 }
363
364 default:
365 break;
366 }
367
368 return QWidget::event(event: e);
369}
370
371QSize QWindowContainer::minimumSizeHint() const
372{
373 return containedWindow() ? containedWindow()->minimumSize() : QSize(0, 0);
374}
375
376typedef void (*qwindowcontainer_traverse_callback)(QWidget *parent);
377static void qwindowcontainer_traverse(QWidget *parent, qwindowcontainer_traverse_callback callback)
378{
379 const QObjectList &children = parent->children();
380 for (int i=0; i<children.size(); ++i) {
381 QWidget *w = qobject_cast<QWidget *>(o: children.at(i));
382 if (w) {
383 QWidgetPrivate *wd = static_cast<QWidgetPrivate *>(QWidgetPrivate::get(w));
384 if (wd->extra && wd->extra->hasWindowContainer)
385 callback(w);
386 }
387 }
388}
389
390void QWindowContainer::toplevelAboutToBeDestroyed(QWidget *parent)
391{
392 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(w: parent)) {
393 if (d->window->parent())
394 d->window->parent()->removeEventFilter(obj: parent);
395 d->window->setParent(&d->fakeParent);
396 d->window->parent()->installEventFilter(filterObj: parent);
397 }
398 qwindowcontainer_traverse(parent, callback: toplevelAboutToBeDestroyed);
399}
400
401void QWindowContainer::parentWasChanged(QWidget *parent)
402{
403 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(w: parent)) {
404 if (d->window->parent()) {
405 d->updateUsesNativeWidgets();
406 d->markParentChain();
407 QWidget *toplevel = d->usesNativeWidgets ? parent : parent->window();
408 if (!toplevel->windowHandle()) {
409 QWidgetPrivate *tld = static_cast<QWidgetPrivate *>(QWidgetPrivate::get(w: toplevel));
410 tld->createTLExtra();
411 tld->createTLSysExtra();
412 Q_ASSERT(toplevel->windowHandle());
413 }
414 d->window->parent()->removeEventFilter(obj: parent);
415 d->window->setParent(toplevel->windowHandle());
416 toplevel->windowHandle()->installEventFilter(filterObj: parent);
417 d->fakeParent.destroy();
418 d->updateGeometry();
419 }
420 }
421 qwindowcontainer_traverse(parent, callback: parentWasChanged);
422}
423
424void QWindowContainer::parentWasMoved(QWidget *parent)
425{
426 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(w: parent)) {
427 if (!d->window)
428 return;
429 else if (d->window->parent())
430 d->updateGeometry();
431 }
432 qwindowcontainer_traverse(parent, callback: parentWasMoved);
433}
434
435void QWindowContainer::parentWasRaised(QWidget *parent)
436{
437 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(w: parent)) {
438 if (!d->window)
439 return;
440 else if (d->window->parent())
441 d->window->raise();
442 }
443 qwindowcontainer_traverse(parent, callback: parentWasRaised);
444}
445
446void QWindowContainer::parentWasLowered(QWidget *parent)
447{
448 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(w: parent)) {
449 if (!d->window)
450 return;
451 else if (d->window->parent())
452 d->window->lower();
453 }
454 qwindowcontainer_traverse(parent, callback: parentWasLowered);
455}
456
457QT_END_NAMESPACE
458
459#include "moc_qwindowcontainer_p.cpp"
460

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/widgets/kernel/qwindowcontainer.cpp