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