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