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
qquickwindowcontainer.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 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
7#include <QtQuick/qquickrendercontrol.h>
8
9#include <QtGui/private/qwindow_p.h>
10
11#include <QtQuick/private/qquickitem_p.h>
12#include <QtQuick/private/qquickrectangle_p.h>
13#include <QtQuick/private/qquickwindowmodule_p.h>
14#include <QtQuick/private/qquickimplicitsizeitem_p_p.h>
15
17
18Q_STATIC_LOGGING_CATEGORY(lcWindowContainer, "qt.quick.window.container")
19
20using namespace Qt::StringLiterals;
21
22/*!
23 \qmltype WindowContainer
24 \inqmlmodule QtQuick
25 \ingroup qtquick-visual
26 \inherits Item
27 \since 6.8
28
29 \brief Allows embedding arbitrary QWindows into a Qt Quick scene.
30
31 The window will become a child of the item's window,
32 with its position, size, z-order, etc. managed by the item.
33
34 Sibling items with a higher z-order than the window container
35 will not automatically overlap the embedded window, as the
36 window lives on top of the Qt Quick scene. To work around this,
37 place the sibling items inside their own dedicated child window:
38
39 \code
40 Item {
41 id: someItem
42 WindowContainer {
43 window: foreignWindow
44 }
45 WindowContainer {
46 window: Window {
47 Item {
48 id: siblingItem
49 }
50 }
51 }
52 }
53 \endcode
54
55 Similarly, child Items of the window container will not automatically
56 overlap the embedded window. To work around this, place the child
57 item inside a dedicated child window.
58
59 \code
60 Item {
61 id: someItem
62 WindowContainer {
63 id: windowContainer
64 window: foreignWindow
65 WindowContainer {
66 window: Window {
67 Item {
68 id: childItem
69 }
70 }
71 }
72 }
73 }
74 \endcode
75
76 \note The window container does not interoperate with QQuickWidget,
77 QQuickWindow::setRenderTarget(), QQuickRenderControl, or similar
78 functionality.
79
80 \sa {QQuickWindow::}{parent()}
81*/
82
83/*!
84 \qmlproperty QWindow QtQuick::WindowContainer::window
85
86 This property holds the window to embed.
87*/
88
90{
91 Q_DECLARE_PUBLIC(QQuickWindowContainer)
92protected:
94
95public:
96 QWindow *window = nullptr;
98};
99
100/*!
101 \internal
102
103 Creates a new window container.
104
105 The container mode determines who has the last word in what the state
106 of the contained window should be. If the window container is explicitly
107 requested by the user via WindowContainer, the properties are set on the
108 item, and the embedded window should match that. If the window container
109 is implicitly created by setting a visual parent on a Window, the properties
110 are set on the Window, and the window container should respect that.
111*/
112QQuickWindowContainer::QQuickWindowContainer(QQuickItem *parent, ContainerMode containerMode)
113 : QQuickImplicitSizeItem(*(new QQuickWindowContainerPrivate), parent)
114{
115 Q_D(QQuickWindowContainer);
116
117 qCDebug(lcWindowContainer).verbosity(1) << "Creating window container"
118 << this << "with parent" << parent << "and" << containerMode;
119
120 d->containerMode = containerMode;
121
122 setFlag(QQuickItem::ItemObservesViewport); // For clipping
123 setFocusPolicy(Qt::TabFocus);
124
125#if QT_CONFIG(accessibility)
126 // The accessibility hierarchy of the contained window hangs off of the
127 // container, so the container needs to have accessibility enabled to
128 // be able to reflect the contained window as a child.
129 d->setAccessible();
130#endif
131
132 connect(this, &QQuickItem::windowChanged,
133 this, &QQuickWindowContainer::parentWindowChanged);
134
135 if (lcWindowContainer().isDebugEnabled()) {
136 auto *debugRectangle = new QQuickRectangle(this);
137 debugRectangle->setColor(QColor(255, 0, 255, 20));
138 auto *border = debugRectangle->border();
139 border->setColor(Qt::magenta);
140 border->setWidth(1.0);
141 QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(debugRectangle);
142 rectPrivate->anchors()->setFill(this);
143 }
144}
145
146QQuickWindowContainer::~QQuickWindowContainer()
147{
148 Q_D(QQuickWindowContainer);
149 qCDebug(lcWindowContainer) << "Destructing window container" << this;
150
151 disconnect(this, &QQuickItem::windowChanged,
152 this, &QQuickWindowContainer::parentWindowChanged);
153
154 if (d->window) {
155 d->window->disconnect(this);
156 d->window->removeEventFilter(this);
157
158 auto ownership = QJSEngine::objectOwnership(d->window);
159 qCDebug(lcWindowContainer) << "Contained window" << d->window
160 << "has" << (ownership == QQmlEngine::JavaScriptOwnership ?
161 "JavaScript" : "C++") << "ownership";
162 if (ownership == QQmlEngine::JavaScriptOwnership) {
163 delete d->window;
164 } else {
165 d->window->destroy();
166 d->window->setParent(nullptr);
167#if QT_CONFIG(accessibility)
168 auto *windowPrivate = QWindowPrivate::get(d->window);
169 if (windowPrivate->accessibleParent == this)
170 windowPrivate->setAccessibleParent(nullptr);
171#endif
172 }
173
174 d->window = nullptr;
175 }
176}
177
178void QQuickWindowContainer::releaseResources()
179{
180 Q_D(const QQuickWindowContainer);
181 qCDebug(lcWindowContainer) << "Destroying" << d->window
182 << "with platform window" << (d->window ? d->window->handle() : nullptr);
183 if (d->window)
184 d->window->destroy();
185}
186
187void QQuickWindowContainer::classBegin()
188{
189 qCDebug(lcWindowContainer) << "Class begin for" << this;
190
191 QQuickImplicitSizeItem::classBegin();
192}
193
194void QQuickWindowContainer::componentComplete()
195{
196 Q_D(const QQuickWindowContainer);
197
198 qCDebug(lcWindowContainer) << "Component completed for" << this;
199 QQuickImplicitSizeItem::componentComplete();
200
201 if (d->window)
202 initializeContainedWindow();
203}
204
205QWindow *QQuickWindowContainer::containedWindow() const
206{
207 Q_D(const QQuickWindowContainer);
208 return d->window;
209}
210
211void QQuickWindowContainer::setContainedWindow(QWindow *window)
212{
213 qCDebug(lcWindowContainer) << "Setting contained window for" << this << "to" << window;
214
215 Q_D(QQuickWindowContainer);
216
217 if (window == d->window)
218 return;
219
220 if (auto *previousWindow = d->window) {
221 qCDebug(lcWindowContainer) << "Decoupling container from" << d->window;
222 previousWindow->disconnect(this);
223 previousWindow->removeEventFilter(this);
224 previousWindow->setParent(nullptr);
225#if QT_CONFIG(accessibility)
226 auto *previousWindowPrivate = QWindowPrivate::get(previousWindow);
227 if (previousWindowPrivate->accessibleParent == this)
228 previousWindowPrivate->setAccessibleParent(nullptr);
229#endif
230 }
231
232 d->window = window;
233
234 if (d->window) {
235 if (d->containerMode == ItemControlsWindow) {
236 if (auto *quickWindow = qobject_cast<QQuickWindowQmlImpl*>(d->window)) {
237 // Make sure the Window reflects the window container as its visual parent
238 quickWindow->setVisualParent(this);
239 }
240 }
241
242 // When the window controls the container, we need to reflect any changes
243 // in the window back to the container, so they stay in sync. And when the
244 // container controls the window, we still want to reflect width/height as
245 // new implicit size, and override any other changes with the item state.
246 connect(d->window, &QWindow::xChanged, this, &QQuickWindowContainer::windowUpdated);
247 connect(d->window, &QWindow::yChanged, this, &QQuickWindowContainer::windowUpdated);
248 connect(d->window, &QWindow::widthChanged, this, &QQuickWindowContainer::windowUpdated);
249 connect(d->window, &QWindow::heightChanged, this, &QQuickWindowContainer::windowUpdated);
250 connect(d->window, &QWindow::visibleChanged, this, &QQuickWindowContainer::windowUpdated);
251
252 connect(d->window, &QObject::destroyed, this, &QQuickWindowContainer::windowDestroyed);
253
254 d->window->installEventFilter(this);
255
256#if QT_CONFIG(accessibility)
257 QWindowPrivate::get(d->window)->setAccessibleParent(this);
258#endif
259
260 if (d->componentComplete)
261 initializeContainedWindow();
262 } else {
263 // Reset state based on not having a window
264 syncWindowToItem();
265 }
266
267 emit containedWindowChanged(d->window);
268}
269
270void QQuickWindowContainer::initializeContainedWindow()
271{
272 Q_D(const QQuickWindowContainer);
273 Q_ASSERT(d->componentComplete);
274 Q_ASSERT(d->window);
275
276 qCDebug(lcWindowContainer) << "Doing initial sync between" << d->window << "and" << this;
277
278 syncWindowToItem();
279 polish();
280}
281
282static QTransform sanitizeTransform(const QTransform &transform)
283{
284 if (transform.isRotating()) {
285 // FIXME: Can we keep more here?
286 return QTransform::fromTranslate(transform.dx(), transform.dy());
287 }
288
289 return transform;
290}
291
292void QQuickWindowContainer::syncWindowToItem()
293{
294 Q_D(const QQuickWindowContainer);
295
296 const auto windowGeometry = d->window ? d->window->geometry() : QRect();
297
298 qCDebug(lcWindowContainer) << "Syncing window state from" << d->window
299 << "with geometry" << windowGeometry << "to" << this
300 << "with mode" << d->containerMode;
301
302 const auto transform = sanitizeTransform(d->windowToItemTransform());
303
304 // The window might have a larger size than the item's natural
305 // size, if there's a scale applied somewhere in the hierarchy.
306 auto itemSize = d->window ? transform.mapRect(windowGeometry).size()
307 : QSize();
308
309 if (d->containerMode == WindowControlsItem) {
310 // When the Window controls the window container the position is
311 // set up front, when creating the window container, and from that
312 // point on set exclusively via the window container, so we skip
313 // setting the position here, and only set the size.
314 setSize(itemSize);
315 setVisible(d->window ? d->window->isVisible() : false);
316 } else {
317 // Position defined by item, so don't sync from window
318 // Visible defined by item, so don't sync from window
319 setImplicitWidth(itemSize.width());
320 setImplicitHeight(itemSize.height());
321 }
322}
323
324/*!
325 \internal
326
327 updatePolish() should perform any layout as required for this item.
328
329 For us, that means propagating the item's state to the window.
330*/
331void QQuickWindowContainer::updatePolish()
332{
333 Q_D(QQuickWindowContainer);
334
335 qCDebug(lcWindowContainer) << "Propagating" << this << "state"
336 << "to" << d->window;
337
338 auto *parentWindow = window();
339
340 // FIXME: If we are part of a QQuickWidget, we have a QQuickRenderControl,
341 // and should look up the parent window via that, and apply the offset we
342 // get to the item transform below. But at the moment it's not possible
343 // to observe changes to the offset, which is critical to support this
344 // for child windows.
345
346 if (!d->window || !parentWindow)
347 return;
348
349 if (d->window->parent() != parentWindow) {
350 qCDebug(lcWindowContainer) << "Updating window parent to" << parentWindow;
351 d->window->setParent(parentWindow);
352 }
353
354 auto transform = sanitizeTransform(d->itemToWindowTransform());
355
356 // Find the window's geometry, based on the item's bounding rect,
357 // mapped to the scene. The mapping includes any x/y position set
358 // on the item itself, as well as any transforms applied to the item
359 // or its ancestor (scale, translation).
360 const QRectF itemSceneRect = transform.mapRect(boundingRect());
361 // FIXME: Rounding to a QRect here means we'll have some jitter or off
362 // placement when the underlying item is not on a integer coordinate.
363 QRect windowGeometry = itemSceneRect.toRect();
364 if (windowGeometry != d->window->geometry()) {
365 QRectF itemRect(position(), size());
366 qCDebug(lcWindowContainer) << "Updating window geometry to" << windowGeometry
367 << "based on item rect" << itemRect << "and scene rect" << itemSceneRect;
368 d->window->setGeometry(windowGeometry);
369 }
370
371 // Clip the container to its own and ancestor clip rects, by setting
372 // a mask on the window. This does not necessarily clip native windows,
373 // as QWindow::setMask() is not guaranteed to visually clip the window,
374 // only to mask input, but in most cases we should be good. For the
375 // cases where this fails, we can potentially use an intermediate window
376 // as parent of the contained window, if the platform allows clipping
377 // child windows to parent window geometry. We do not want to resize the
378 // contained window, as that will just fill the content into a smaller
379 // area.
380 const auto clipMask = [&]{
381 if (clipRect() == boundingRect())
382 return QRect();
383
384 // The clip rect has all the relevant transforms applied to it,
385 // except for the item's own scale. As the mask is in window
386 // local coordinates in the possibly scaled window, we need
387 // to apply the scale manually.
388 auto scaleTransform = QTransform::fromScale(transform.m11(), transform.m22());
389 auto rect = scaleTransform.mapRect(clipRect()).toRect();
390
391 // An empty clip rect means clip away everything, while for a
392 // window, an empty mask means mask nothing. Fake the former
393 // by setting a mask outside of the window's bounds. We have
394 // to do this check after rounding the clip rect to a QRect.
395 // FIXME: Verify this works on all platforms
396 if (rect.isEmpty())
397 return QRect(-1, -1, 1, 1);
398
399 return rect;
400 }();
401
402 if (clipMask != d->window->mask().boundingRect()) {
403 qCDebug(lcWindowContainer) << "Updating window clip mask to" << clipMask
404 << "based on clip rect" << clipRect();
405 d->window->setMask(clipMask);
406 }
407
408 // FIXME: Opacity support. Need to calculate effective opacity ourselves,
409 // and there doesn't seem to be any existing observer for opacity changes.
410 // Not all platforms implement opacity for child windows yet.
411
412 // FIXME: If a scale is applied to the item or its parents, we end up
413 // with a bigger item, and window, but we don't translate the scale to
414 // an increase device-pixel-ratio of the window. As a result, the window
415 // will likely just render more content, instead of the same content at
416 // a potentially higher density.
417
418 if (d->window->isVisible() != isVisible()) {
419 qCDebug(lcWindowContainer) << "Updating window visibility"
420 << "based on item visible" << isVisible();
421 d->window->setVisible(isVisible());
422 }
423}
424
425/*!
426 \internal
427
428 QQuickItem::clipRect() doesn't take ItemClipsChildrenToShape into
429 account, so a parent item that has clip:false, but ItemIsViewport
430 will still result in affecting the clip.
431
432 We want to stay consistent with the clipping in the scene graph,
433 which is based on QQuickItem::clip(), so we override the clipRect
434 to take ItemClipsChildrenToShape into account.
435*/
436QRectF QQuickWindowContainer::clipRect() const
437{
438 QRectF rect = boundingRect();
439
440 for (auto *viewport = viewportItem(); viewport; viewport = viewport->viewportItem()) {
441 if (viewport == this)
442 break;
443
444 if (viewport->flags().testFlag(QQuickItem::ItemClipsChildrenToShape)) {
445 // FIXME: This fails to take into account viewports that override clipRect()
446 const auto mappedViewportRect = mapRectFromItem(viewport, viewport->boundingRect());
447 rect = mappedViewportRect.intersected(rect);
448 }
449
450 if (viewport->viewportItem() == viewport)
451 break; // Content item returns itself as viewport
452 }
453
454 return rect;
455}
456
457// ----------------------- Window updates -----------------------
458
459/*!
460 \internal
461
462 Called when the contained QWindow is changed.
463
464 Depending on the sync mode we need to reflect these changes
465 to the item, or override them by applying the item state.
466*/
467void QQuickWindowContainer::windowUpdated()
468{
469 Q_D(const QQuickWindowContainer);
470
471 if (lcWindowContainer().isDebugEnabled()) {
472 auto metaMethod = sender()->metaObject()->method(senderSignalIndex());
473 auto signalName = QString::fromUtf8(metaMethod.name());
474 qCDebug(lcWindowContainer).noquote() << d->window << signalName;
475 }
476
477 syncWindowToItem();
478
479 if (d->containerMode == ItemControlsWindow) {
480 qCDebug(lcWindowContainer) << "Overriding window state by polishing";
481 // Ideally we'd always call ensurePolished() here, to synchronously
482 // override the window state ASAP, rather than wait for polish to
483 // trigger it asynchronously, but due to QWindowPrivate::setVisible
484 // emitting visibleChanged before updating the platform window, we
485 // end up applying our override temporarily, only to have QWindowPrivate
486 // follow up with the original change to the platform window.
487 if (d->window->isVisible() != isVisible())
488 polish();
489 else
490 ensurePolished();
491 }
492}
493
494bool QQuickWindowContainer::eventFilter(QObject *object, QEvent *event)
495{
496 Q_D(const QQuickWindowContainer);
497 Q_ASSERT(object == d->window);
498
499 if (event->type() == QEvent::PlatformSurface) {
500 auto type = static_cast<QPlatformSurfaceEvent*>(event)->surfaceEventType();
501 if (type == QPlatformSurfaceEvent::SurfaceCreated) {
502 qCDebug(lcWindowContainer) << "Surface created for" << object;
503 syncWindowToItem();
504 // The surface creation has already resulted in the native window
505 // being added to its parent, on top of all other windows. We need
506 // to do a synchronous re-stacking of the windows here, to avoid
507 // leaving the window in the wrong position while waiting for the
508 // asynchronous callback to QQuickWindow::polishItems().
509 if (auto *quickWindow = qobject_cast<QQuickWindow*>(window()))
510 QQuickWindowPrivate::get(quickWindow)->updateChildWindowStackingOrder();
511 }
512 }
513
514 return QQuickImplicitSizeItem::eventFilter(object, event);
515}
516
517void QQuickWindowContainer::focusInEvent(QFocusEvent *event)
518{
519 Q_D(QQuickWindowContainer);
520 if (d->window) {
521 const auto reason = event->reason();
522 QWindowPrivate::FocusTarget target = QWindowPrivate::FocusTarget::Current;
523 if (reason == Qt::TabFocusReason)
524 target = QWindowPrivate::FocusTarget::First;
525 else if (reason == Qt::BacktabFocusReason)
526 target = QWindowPrivate::FocusTarget::Last;
527 QWindowPrivate::get(d->window)->setFocusToTarget(target, reason);
528 d->window->requestActivate();
529 }
530}
531
532void QQuickWindowContainer::windowDestroyed()
533{
534 Q_D(QQuickWindowContainer);
535 qCDebug(lcWindowContainer) << "Window" << (void*)d->window << "destroyed";
536
537 d->window->removeEventFilter(this);
538 d->window = nullptr;
539
540 syncWindowToItem(); // Reset state based on not having a window
541 emit containedWindowChanged(d->window);
542}
543
544// ----------------------- Item updates -----------------------
545
546/*!
547 \internal
548
549 Called when the item's geometry has changed
550*/
551void QQuickWindowContainer::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
552{
553 qCDebug(lcWindowContainer) << this << "geometry changed from"
554 << oldGeometry << "to" << newGeometry;
555
556 QQuickImplicitSizeItem::geometryChange(newGeometry, oldGeometry);
557 if (newGeometry.isValid())
558 polish();
559}
560
561/*!
562 \internal
563
564 Called when the item's (effective) state has changed
565*/
566void QQuickWindowContainer::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
567{
568 switch (change) {
569 case ItemVisibleHasChanged:
570 qCDebug(lcWindowContainer) << "Visible changed for" << this << "to" << isVisible();
571 polish();
572 break;
573 default:
574 break;
575 }
576
577 QQuickImplicitSizeItem::itemChange(change, data);
578}
579
580/*!
581 \internal
582
583 Called when the window container item is moved to another window
584*/
585void QQuickWindowContainer::parentWindowChanged(QQuickWindow *parentWindow)
586{
587 qCDebug(lcWindowContainer) << this << "parent window changed to" << parentWindow;
588
589 Q_D(QQuickWindowContainer);
590
591 if (!parentWindow) {
592 // We have been removed from the window we were part of,
593 // possibly because the window is going away. We need to
594 // make sure the contained window is no longer a child of
595 // former window, as otherwise it will be wiped out along
596 // with it. We can't wait for updatePolish() to do that
597 // as polish has no effect when an item is not part of a
598 // window.
599 if (d->window) {
600 // The window should already be destroyed from the
601 // call to releaseResources(), which is part of the
602 // removal of an item from a scene, but just in case
603 // we do it here as well.
604 d->window->destroy();
605
606 d->window->setParent(nullptr);
607 }
608 } else {
609 polish();
610 }
611}
612
613bool QQuickWindowContainerPrivate::transformChanged(QQuickItem *transformedItem)
614{
615 Q_Q(QQuickWindowContainer);
616
617 if (this->componentComplete && this->window) {
618 auto *transformedItemPrivate = QQuickItemPrivate::get(transformedItem);
619 qCDebug(lcWindowContainer) << "Transform changed for" << transformedItem
620 << "with dirty state" << transformedItemPrivate->dirtyToString();
621
622 if (transformedItemPrivate->dirtyAttributes
623 & QQuickItemPrivate::BasicTransform) {
624 // For some reason scale transforms, which result in the window
625 // being resized, end up with the window lagging a frame or two
626 // behind the item. Polish synchronously instead, to mitigate
627 // this, even if it may result in the opposite situation.
628 q->ensurePolished();
629 } else {
630 q->polish();
631 }
632 }
633
634 return QQuickItemPrivate::transformChanged(transformedItem);
635}
636
637QT_END_NAMESPACE
638
639#include "moc_qquickwindowcontainer_p.cpp"
\qmltype WindowContainer \inqmlmodule QtQuick\inherits Item
Combined button and popup list for selecting options.
static QTransform sanitizeTransform(const QTransform &transform)