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

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