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