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. For instance,
160 when entering a Qt Quick based window with tab focus, it is quite
161 likely that further tab presses will only cycle inside the QML
162 application. Also, whether QWindow::requestActivate() actually
163 gives the window focus, is platform dependent.
164
165 \li Using many window container instances in a QWidget-based
166 application can greatly hurt the overall performance of the
167 application.
168
169 \li Since 6.7, if \a window belongs to a widget (that is, \a window
170 was received from calling \l windowHandle()), no container will be
171 created. Instead, this function will return the widget itself, after
172 being reparented to \l parent. Since no container will be created,
173 \a flags will be ignored. In other words, if \a window belongs to
174 a widget, consider just reparenting that widget to \a parent instead
175 of using this function.
176
177 \endlist
178 */
179
180QWidget *QWidget::createWindowContainer(QWindow *window, QWidget *parent, Qt::WindowFlags flags)
181{
182 // Embedding a QWidget in a window container doesn't make sense,
183 // and has various issues in practice, so just return the widget
184 // itself.
185 if (auto *widgetWindow = qobject_cast<QWidgetWindow *>(object: window)) {
186 QWidget *widget = widgetWindow->widget();
187 if (flags != Qt::WindowFlags()) {
188 qWarning() << window << "refers to a widget:" << widget
189 << "WindowFlags" << flags << "will be ignored.";
190 }
191 widget->setParent(parent);
192 return widget;
193 }
194 return new QWindowContainer(window, parent, flags);
195}
196
197/*!
198 \internal
199 */
200
201QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt::WindowFlags flags)
202 : QWidget(*new QWindowContainerPrivate, parent, flags)
203{
204 Q_D(QWindowContainer);
205 if (Q_UNLIKELY(!embeddedWindow)) {
206 qWarning(msg: "QWindowContainer: embedded window cannot be null");
207 return;
208 }
209
210 d->window = embeddedWindow;
211 d->window->installEventFilter(filterObj: this);
212
213 QString windowName = d->window->objectName();
214 if (windowName.isEmpty())
215 windowName = QString::fromUtf8(utf8: d->window->metaObject()->className());
216 d->fakeParent.setObjectName(windowName + "ContainerFakeParent"_L1);
217
218 d->window->setParent(&d->fakeParent);
219 d->window->parent()->installEventFilter(filterObj: this);
220 d->window->setFlag(Qt::SubWindow);
221
222 setAcceptDrops(true);
223
224 connect(sender: containedWindow(), signal: &QWindow::minimumHeightChanged, context: this, slot: &QWindowContainer::updateGeometry);
225 connect(sender: containedWindow(), signal: &QWindow::minimumWidthChanged, context: this, slot: &QWindowContainer::updateGeometry);
226}
227
228QWindow *QWindowContainer::containedWindow() const
229{
230 Q_D(const QWindowContainer);
231 return d->window;
232}
233
234/*!
235 \internal
236 */
237
238QWindowContainer::~QWindowContainer()
239{
240 Q_D(QWindowContainer);
241
242 // Call destroy() explicitly first. The dtor would do this too, but
243 // QEvent::PlatformSurface delivery relies on virtuals. Getting
244 // SurfaceAboutToBeDestroyed can be essential for OpenGL, Vulkan, etc.
245 // QWindow subclasses in particular. Keep these working.
246 if (d->window) {
247 d->window->removeEventFilter(obj: this);
248 d->window->destroy();
249 }
250
251 delete d->window;
252}
253
254/*!
255 \internal
256 */
257
258bool QWindowContainer::eventFilter(QObject *o, QEvent *e)
259{
260 Q_D(QWindowContainer);
261 if (!d->window)
262 return false;
263
264 if (e->type() == QEvent::ChildRemoved) {
265 QChildEvent *ce = static_cast<QChildEvent *>(e);
266 if (ce->child() == d->window) {
267 o->removeEventFilter(obj: this);
268 d->window->removeEventFilter(obj: this);
269 d->window = nullptr;
270 }
271 } else if (e->type() == QEvent::FocusIn) {
272 if (o == d->window)
273 setFocus(Qt::ActiveWindowFocusReason);
274 }
275 return false;
276}
277
278/*!
279 \internal
280 */
281
282bool QWindowContainer::event(QEvent *e)
283{
284 Q_D(QWindowContainer);
285 if (!d->window)
286 return QWidget::event(event: e);
287
288 QEvent::Type type = e->type();
289 switch (type) {
290 // The only thing we are interested in is making sure our sizes stay
291 // in sync, so do a catch-all case.
292 case QEvent::Resize:
293 d->updateGeometry();
294 break;
295 case QEvent::Move:
296 d->updateGeometry();
297 break;
298 case QEvent::PolishRequest:
299 d->updateGeometry();
300 break;
301 case QEvent::Show:
302 d->updateUsesNativeWidgets();
303 if (d->isStillAnOrphan()) {
304 d->window->parent()->removeEventFilter(obj: this);
305 d->window->setParent(d->usesNativeWidgets
306 ? windowHandle()
307 : window()->windowHandle());
308 d->fakeParent.destroy();
309 if (d->window->parent())
310 d->window->parent()->installEventFilter(filterObj: this);
311 }
312 if (d->window->parent()) {
313 d->markParentChain();
314 d->window->show();
315 }
316 break;
317 case QEvent::Hide:
318 if (d->window->parent())
319 d->window->hide();
320 break;
321 case QEvent::FocusIn:
322 if (d->window->parent()) {
323 if (QGuiApplication::focusWindow() != d->window) {
324 QFocusEvent *event = static_cast<QFocusEvent *>(e);
325 const auto reason = event->reason();
326 QWindowPrivate::FocusTarget target = QWindowPrivate::FocusTarget::Current;
327 if (reason == Qt::TabFocusReason)
328 target = QWindowPrivate::FocusTarget::First;
329 else if (reason == Qt::BacktabFocusReason)
330 target = QWindowPrivate::FocusTarget::Last;
331 qt_window_private(window: d->window)->setFocusToTarget(target, reason);
332 d->window->requestActivate();
333 }
334 }
335 break;
336#if QT_CONFIG(draganddrop)
337 case QEvent::Drop:
338 case QEvent::DragMove:
339 case QEvent::DragLeave:
340 QCoreApplication::sendEvent(receiver: d->window, event: e);
341 return e->isAccepted();
342 case QEvent::DragEnter:
343 // Don't reject drag events for the entire widget when one
344 // item rejects the drag enter
345 QCoreApplication::sendEvent(receiver: d->window, event: e);
346 e->accept();
347 return true;
348#endif
349
350 case QEvent::Paint:
351 {
352 static bool needsPunch = !QGuiApplicationPrivate::platformIntegration()->hasCapability(
353 cap: QPlatformIntegration::TopStackedNativeChildWindows);
354 if (needsPunch) {
355 QPainter p(this);
356 p.setCompositionMode(QPainter::CompositionMode_Source);
357 p.fillRect(r: rect(), c: Qt::transparent);
358 }
359 break;
360 }
361
362 default:
363 break;
364 }
365
366 return QWidget::event(event: e);
367}
368
369QSize QWindowContainer::minimumSizeHint() const
370{
371 return containedWindow() ? containedWindow()->minimumSize() : QSize(0, 0);
372}
373
374typedef void (*qwindowcontainer_traverse_callback)(QWidget *parent);
375static void qwindowcontainer_traverse(QWidget *parent, qwindowcontainer_traverse_callback callback)
376{
377 const QObjectList &children = parent->children();
378 for (int i=0; i<children.size(); ++i) {
379 QWidget *w = qobject_cast<QWidget *>(o: children.at(i));
380 if (w) {
381 QWidgetPrivate *wd = static_cast<QWidgetPrivate *>(QWidgetPrivate::get(w));
382 if (wd->extra && wd->extra->hasWindowContainer)
383 callback(w);
384 }
385 }
386}
387
388void QWindowContainer::toplevelAboutToBeDestroyed(QWidget *parent)
389{
390 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(w: parent)) {
391 if (d->window->parent())
392 d->window->parent()->removeEventFilter(obj: parent);
393 d->window->setParent(&d->fakeParent);
394 d->window->parent()->installEventFilter(filterObj: parent);
395 }
396 qwindowcontainer_traverse(parent, callback: toplevelAboutToBeDestroyed);
397}
398
399void QWindowContainer::parentWasChanged(QWidget *parent)
400{
401 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(w: parent)) {
402 if (d->window->parent()) {
403 d->updateUsesNativeWidgets();
404 d->markParentChain();
405 QWidget *toplevel = d->usesNativeWidgets ? parent : parent->window();
406 if (!toplevel->windowHandle()) {
407 QWidgetPrivate *tld = static_cast<QWidgetPrivate *>(QWidgetPrivate::get(w: toplevel));
408 tld->createTLExtra();
409 tld->createTLSysExtra();
410 Q_ASSERT(toplevel->windowHandle());
411 }
412 d->window->parent()->removeEventFilter(obj: parent);
413 d->window->setParent(toplevel->windowHandle());
414 toplevel->windowHandle()->installEventFilter(filterObj: parent);
415 d->fakeParent.destroy();
416 d->updateGeometry();
417 }
418 }
419 qwindowcontainer_traverse(parent, callback: parentWasChanged);
420}
421
422void QWindowContainer::parentWasMoved(QWidget *parent)
423{
424 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(w: parent)) {
425 if (d->window->parent())
426 d->updateGeometry();
427 }
428 qwindowcontainer_traverse(parent, callback: parentWasMoved);
429}
430
431void QWindowContainer::parentWasRaised(QWidget *parent)
432{
433 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(w: parent)) {
434 if (d->window->parent())
435 d->window->raise();
436 }
437 qwindowcontainer_traverse(parent, callback: parentWasRaised);
438}
439
440void QWindowContainer::parentWasLowered(QWidget *parent)
441{
442 if (QWindowContainerPrivate *d = QWindowContainerPrivate::get(w: parent)) {
443 if (d->window->parent())
444 d->window->lower();
445 }
446 qwindowcontainer_traverse(parent, callback: parentWasLowered);
447}
448
449QT_END_NAMESPACE
450
451#include "moc_qwindowcontainer_p.cpp"
452

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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