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 "qquickwindowmodule_p.h"
5#include "qquickwindowattached_p.h"
6#include "qquickrendercontrol.h"
7#include "qquickscreen_p.h"
8#include "qquickview_p.h"
9#include "qquickwindowmodule_p_p.h"
10#include "qquickitem_p.h"
11#include <QtQuick/QQuickWindow>
12#include <QtCore/QCoreApplication>
13#include <QtQml/QQmlEngine>
14
15#include <private/qguiapplication_p.h>
16#include <private/qqmlengine_p.h>
17#include <private/qv4qobjectwrapper_p.h>
18#include <private/qqmlglobal_p.h>
19#include <qpa/qplatformintegration.h>
20
21QT_BEGIN_NAMESPACE
22
23using namespace Qt::StringLiterals;
24
25Q_DECLARE_LOGGING_CATEGORY(lcTransient)
26
27QQuickWindowQmlImplPrivate::QQuickWindowQmlImplPrivate() = default;
28
29QQuickWindowQmlImpl::QQuickWindowQmlImpl(QWindow *parent)
30 : QQuickWindowQmlImpl(*(new QQuickWindowQmlImplPrivate), parent)
31{
32}
33
34QQuickWindowQmlImpl::QQuickWindowQmlImpl(QQuickWindowQmlImplPrivate &dd, QWindow *parent)
35 : QQuickWindow(dd, parent)
36{
37 connect(sender: this, signal: &QWindow::visibleChanged, context: this, slot: [&] {
38 Q_D(QQuickWindowQmlImpl);
39 d->visible = QWindow::isVisible();
40 emit QQuickWindowQmlImpl::visibleChanged(arg: d->visible);
41 });
42 connect(sender: this, signal: &QWindow::visibilityChanged, context: this, slot: [&]{
43 Q_D(QQuickWindowQmlImpl);
44 // Update the window's actual visibility and turn off visibilityExplicitlySet,
45 // so that future applyWindowVisibility() calls do not apply both window state
46 // and visible state, unless setVisibility() is called again by the user.
47 d->visibility = QWindow::visibility();
48 d->visibilityExplicitlySet = false;
49 emit QQuickWindowQmlImpl::visibilityChanged(visibility: d->visibility);
50 });
51 connect(sender: this, signal: &QWindow::screenChanged, context: this, slot: &QQuickWindowQmlImpl::screenChanged);
52
53 // We shadow the x and y properties, so that we can re-map them in case
54 // we have an Item as our visual parent, which will result in creating an
55 // implicit window container that we control. Ensure that signals still work,
56 // and that they reflect the mapped values if a window container is used.
57 QObject::connect(sender: this, signal: &QWindow::xChanged, context: this, slot: [this] { emit xChanged(arg: x()); });
58 QObject::connect(sender: this, signal: &QWindow::yChanged, context: this, slot: [this] { emit yChanged(arg: y()); });
59}
60
61QQuickWindowQmlImpl::~QQuickWindowQmlImpl()
62{
63 // Destroy the window while we are still alive, so that any signals
64 // emitted by the destruction can be delivered properly.
65 destroy();
66}
67
68void QQuickWindowQmlImpl::classBegin()
69{
70 Q_D(QQuickWindowQmlImpl);
71 qCDebug(lcQuickWindow) << "Class begin for" << this;
72 d->componentComplete = false;
73
74 QQmlEngine* e = qmlEngine(this);
75
76 QQmlEngine::setContextForObject(contentItem(), e->rootContext());
77
78 //Give QQuickView behavior when created from QML with QQmlApplicationEngine
79 if (QCoreApplication::instance()->property(name: "__qml_using_qqmlapplicationengine") == QVariant(true)) {
80 if (e && !e->incubationController())
81 e->setIncubationController(incubationController());
82 }
83 {
84 // The content item has CppOwnership policy (set in QQuickWindow). Ensure the presence of a JS
85 // wrapper so that the garbage collector can see the policy.
86 QV4::ExecutionEngine *v4 = e->handle();
87 QV4::QObjectWrapper::ensureWrapper(engine: v4, object: d->contentItem);
88 }
89}
90
91void QQuickWindowQmlImpl::componentComplete()
92{
93 Q_D(QQuickWindowQmlImpl);
94 qCDebug(lcQuickWindow) << "Component completed for" << this;
95 d->componentComplete = true;
96
97 applyVisualParent();
98
99 // Apply automatic transient parent if needed, and opt in to future
100 // parent change events, so we can keep the transient parent in sync.
101 updateTransientParent();
102 d->receiveParentEvents = true;
103
104 applyWindowVisibility();
105
106 // If the transient parent changes, and we've deferred making
107 // the window visible, we need to re-evaluate our decision.
108 connect(sender: this, signal: &QWindow::transientParentChanged,
109 context: this, slot: &QQuickWindowQmlImpl::applyWindowVisibility);
110}
111
112void QQuickWindowQmlImpl::setVisible(bool visible)
113{
114 Q_D(QQuickWindowQmlImpl);
115 d->visible = visible;
116 d->visibleExplicitlySet = true;
117 if (d->componentComplete)
118 applyWindowVisibility();
119}
120
121void QQuickWindowQmlImpl::setVisibility(Visibility visibility)
122{
123 Q_D(QQuickWindowQmlImpl);
124 d->visibility = visibility;
125 d->visibilityExplicitlySet = true;
126 if (d->componentComplete)
127 applyWindowVisibility();
128}
129
130bool QQuickWindowQmlImpl::event(QEvent *event)
131{
132 Q_D(QQuickWindowQmlImpl);
133
134 if (event->type() == QEvent::ParentWindowChange) {
135 qCDebug(lcQuickWindow) << "Parent of" << this << "changed to" << parent();
136 if (d->visualParent) {
137 // If the window parent changes, and we've deferred making
138 // the window visible, we need to re-evaluate our decision.
139 applyWindowVisibility();
140 } else {
141 QObject::disconnect(d->itemParentWindowChangeListener);
142 updateTransientParent();
143 }
144 }
145 return QQuickWindow::event(event);
146}
147
148/*
149 Update the transient parent of the window based on its
150 QObject parent (Item or Window), unless the user has
151 set an explicit transient parent.
152*/
153void QQuickWindowQmlImpl::updateTransientParent()
154{
155 Q_D(QQuickWindowQmlImpl);
156
157 // We defer updating the transient parent until the component
158 // has been fully completed, and we know whether an explicit
159 // transient parent has been set.
160 if (!d->componentComplete)
161 return;
162
163 // If an explicit transient parent has been set,
164 // we don't want to apply our magic.
165 if (d->transientParentPropertySet)
166 return;
167
168 // Nor if we have a visual parent that makes this a true child window
169 if (d->visualParent)
170 return;
171
172 auto *objectParent = QObject::parent();
173 qCDebug(lcTransient) << "Applying transient parent magic to"
174 << this << "based on object parent" << objectParent << "🪄";
175
176 QWindow *transientParent = nullptr;
177 if (auto *windowParent = qmlobject_cast<QWindow *>(object: objectParent)) {
178 transientParent = windowParent;
179 } else if (auto *itemParent = qmlobject_cast<QQuickItem *>(object: objectParent)) {
180 if (!d->itemParentWindowChangeListener) {
181 d->itemParentWindowChangeListener = connect(
182 sender: itemParent, signal: &QQuickItem::windowChanged,
183 context: this, slot: &QQuickWindowQmlImpl::updateTransientParent);
184 }
185 transientParent = itemParent->window();
186 }
187
188 if (!transientParent) {
189 qCDebug(lcTransient) << "No transient parent resolved from object parent";
190 return;
191 }
192
193 qCDebug(lcTransient) << "Setting" << transientParent << "as transient parent of" << this;
194 setTransientParent(transientParent);
195
196 // We want to keep applying the automatic transient parent
197 d->transientParentPropertySet = false;
198}
199
200void QQuickWindowQmlImpl::applyWindowVisibility()
201{
202 Q_D(QQuickWindowQmlImpl);
203
204 Q_ASSERT(d->componentComplete);
205
206 const bool visible = d->visibilityExplicitlySet
207 ? d->visibility != Hidden : d->visible;
208
209 qCDebug(lcQuickWindow) << "Applying visible" << visible << "for" << this;
210
211 if (visible) {
212 if (d->visualParent) {
213 // Even though we're complete, and have a visual parent set,
214 // we may not be part of a window yet, or we may have been
215 // removed from a window that's going away. Showing this window
216 // now would make it a top level, which is not what we want.
217 if (!QWindow::parent()) {
218 qCDebug(lcQuickWindow) << "Waiting for visual parent to reparent us into a window";
219 // We apply the visibility again on ParentWindowChange
220 return;
221 }
222 } else {
223 // Handle deferred visibility due to possible transient parent
224 auto *itemParent = qmlobject_cast<QQuickItem *>(object: QObject::parent());
225 if (!d->transientParentPropertySet && itemParent && !itemParent->window()) {
226 qCDebug(lcTransient) << "Waiting for parent" << itemParent << "to resolve"
227 << "its window. Deferring visibility";
228 return;
229 }
230
231 const QWindow *transientParent = QWindow::transientParent();
232 if (transientParent && !transientParentVisible()) {
233 // Defer visibility of this window until the transient parent has
234 // been made visible, or we've get a new transient parent.
235 qCDebug(lcTransient) << "Transient parent" << transientParent
236 << "not visible yet. Deferring visibility";
237
238 // QWindowPrivate::setVisible emits visibleChanged _before_ actually
239 // propagating the visibility to the platform window, so we can't use
240 // a direct connection here, as that would result in showing this
241 // window before the transient parent.
242 connect(sender: transientParent, signal: &QQuickWindow::visibleChanged, context: this,
243 slot: &QQuickWindowQmlImpl::applyWindowVisibility,
244 type: Qt::ConnectionType(Qt::QueuedConnection | Qt::SingleShotConnection));
245 return;
246 }
247 }
248 }
249
250 if (d->visibleExplicitlySet && d->visibilityExplicitlySet &&
251 ((d->visibility == Hidden && d->visible) ||
252 (d->visibility > AutomaticVisibility && !d->visible))) {
253 // FIXME: Should we bail out in this case?
254 qmlWarning(me: this) << "Conflicting properties 'visible' and 'visibility'";
255 }
256
257 if (d->visibility == AutomaticVisibility) {
258 // We're either showing for the first time, with the default
259 // visibility of AutomaticVisibility, or the user has called
260 // setVisibility with AutomaticVisibility at some point, so
261 // apply both window state and visible.
262 if (QWindow::parent() || visualParent())
263 setWindowState(Qt::WindowNoState);
264 else
265 setWindowState(QGuiApplicationPrivate::platformIntegration()->defaultWindowState(flags()));
266 QQuickWindow::setVisible(d->visible);
267 } else if (d->visibilityExplicitlySet) {
268 // We're not AutomaticVisibility, but the user has requested
269 // an explicit visibility, so apply both window state and visible.
270 QQuickWindow::setVisibility(d->visibility);
271 } else {
272 // Our window state should be up to date, so only apply visible
273 QQuickWindow::setVisible(d->visible);
274 }
275}
276
277bool QQuickWindowQmlImpl::transientParentVisible()
278{
279 Q_ASSERT(transientParent());
280 if (!transientParent()->isVisible()) {
281 // handle case where transient parent is offscreen window
282 QWindow *rw = QQuickRenderControl::renderWindowFor(win: qobject_cast<QQuickWindow*>(object: transientParent()));
283 return rw && rw->isVisible();
284 }
285 return true;
286}
287
288// -------------------------- Visual Parent ---------------------------
289
290/*!
291 \qmlproperty var QtQuick::Window::parent
292 \since 6.7
293 \internal
294
295 This property holds the visual parent of the window.
296
297 The visual parent can be either another Window, or an Item.
298
299 A window with a visual parent will result in the window becoming a child
300 window of its visual parent, either directly if the visual parent is another
301 Window, or indirectly via the visual parent Item's window.
302
303 Just like QtQuick::Item::parent, the window will be positioned relative to
304 its visual parent.
305
306 The stacking order between sibling Windows follows the document order,
307 just like Items, but can be customized via the Window's \l{QtQuick::Window::z}
308 {z-order} property.
309
310 Setting a visual parent on a Window will take precedence over the
311 \l{QtQuick::Window::transientParent}{transient parent}.
312
313 \sa{Concepts - Visual Parent in Qt Quick}, transientParent
314*/
315
316void QQuickWindowQmlImpl::setVisualParent(QObject *visualParent)
317{
318 Q_D(QQuickWindowQmlImpl);
319 if (visualParent == d->visualParent)
320 return;
321
322 qCDebug(lcQuickWindow) << "Setting visual parent of" << this << "to" << visualParent;
323
324 if (d->visualParent) {
325 // Disconnect from deferred window listener
326 d->visualParent->disconnect(receiver: this);
327 }
328
329 d->visualParent = visualParent;
330
331 if (d->componentComplete)
332 applyVisualParent();
333
334 emit visualParentChanged(d->visualParent);
335}
336
337void QQuickWindowQmlImpl::applyVisualParent()
338{
339 Q_D(QQuickWindowQmlImpl);
340 Q_ASSERT(d->componentComplete);
341
342 qCDebug(lcQuickWindow) << "Applying" << this << "visual parent" << d->visualParent;
343
344 if (!d->visualParent) {
345 if (d->windowContainer) {
346 d->windowContainer->setContainedWindow(nullptr);
347 delete std::exchange(obj&: d->windowContainer, new_val: nullptr);
348 }
349 QQuickWindow::setParent(nullptr);
350 return;
351 }
352
353 QQuickItem *parentItem = nullptr;
354 if ((parentItem = qobject_cast<QQuickItem*>(o: d->visualParent)))
355 ; // All good, can use directly
356 else if (auto *parentWindow = qobject_cast<QWindow*>(o: d->visualParent)) {
357 if (auto *parentQuickWindow = qobject_cast<QQuickWindow*>(object: parentWindow)) {
358 parentItem = parentQuickWindow->contentItem();
359 } else {
360 qmlWarning(me: this) << "Parenting into non-Quick window. "
361 << "Stacking, position, and destruction must be handled manually";
362 QQuickWindow::setParent(parentWindow); // Try our best
363 return;
364 }
365 }
366
367 if (!parentItem) {
368 qmlWarning(me: this) << "Unsupported visual parent type"
369 << d->visualParent->metaObject()->className();
370 return;
371 }
372
373 if (!parentItem->window()) {
374 qCDebug(lcQuickWindow) << "No window yet. Deferring.";
375 connect(sender: parentItem, signal: &QQuickItem::windowChanged, context: this, slot: [this]{
376 qCDebug(lcQuickWindow) << "Got window. Applying deferred visual parent item.";
377 applyVisualParent();
378 }, type: Qt::SingleShotConnection);
379 return;
380 }
381
382 if (qobject_cast<QQuickWindowContainer*>(object: d->visualParent)) {
383 qCDebug(lcQuickWindow) << "Visual parent is window container, everything is in order";
384 return;
385 }
386
387 if (!d->windowContainer) {
388 d->windowContainer = new QQuickWindowContainer(parentItem,
389 QQuickWindowContainer::WindowControlsItem);
390 d->windowContainer->setObjectName(objectName() + "Container"_L1);
391
392 auto *objectParent = this->QObject::parent();
393 if (objectParent == parentItem) {
394 // We want to reflect the QML document order of sibling windows in the
395 // resulting stacking order of the windows. We can do so by carefully
396 // using the the information we have about the child object order.
397
398 // We know that the window's object child index is correct in relation
399 // to the other child windows of the parent. Since the window container
400 // is going to represent the window from now on, make the window container
401 // take the window's place in the parent's child object list.
402 auto &objectChildren = QObjectPrivate::get(o: objectParent)->children;
403 auto windowIndex = objectChildren.indexOf(t: this);
404 auto containerIndex = objectChildren.indexOf(t: d->windowContainer);
405 objectChildren.move(from: containerIndex, to: windowIndex);
406 containerIndex = windowIndex;
407
408 // The parent's item children are unfortunately managed separately from
409 // the object children. But thanks to the logic above we can use the now
410 // correct object order of the window container in the object children list
411 // to also ensure a correct stacking order between the sibling child items.
412 for (int i = containerIndex + 1; i < objectChildren.size(); ++i) {
413 if (auto *childItem = qobject_cast<QQuickItem*>(o: objectChildren.at(i))) {
414 qCDebug(lcQuickWindow) << "Stacking" << d->windowContainer
415 << "below" << childItem;
416 d->windowContainer->stackBefore(childItem);
417 break;
418 }
419 }
420 } else {
421 // Having another visual parent than the direct object parent will
422 // mess up the stacking order. This is also the case for normal items.
423 qCDebug(lcQuickWindow) << "Visual parent is not object parent."
424 << "Can not reflect document order as stacking order.";
425 }
426
427 QQmlEngine::setContextForObject(d->windowContainer, qmlContext(this));
428
429 d->windowContainer->classBegin();
430 d->windowContainer->setContainedWindow(this);
431 // Once the window has a window container, all x/y/z changes of
432 // the window will go through the container, and ensure the
433 // correct mapping. But any changes that happened prior to
434 // this have not been mapped yet, so do that now.
435 d->windowContainer->setPosition(position());
436 d->windowContainer->setZ(d->z);
437 d->windowContainer->componentComplete();
438
439 QObject::connect(sender: d->windowContainer, signal: &QQuickItem::zChanged,
440 context: this, slot: &QQuickWindowQmlImpl::zChanged);
441 } else {
442 d->windowContainer->setParentItem(parentItem);
443 }
444}
445
446QObject *QQuickWindowQmlImpl::visualParent() const
447{
448 Q_D(const QQuickWindowQmlImpl);
449 return d->visualParent;
450}
451
452// We shadow the x and y properties of the Window, so that in case
453// the window has an Item as its visual parent we can re-map the
454// coordinates via the corresponding window container. We need to
455// do this also for the signal emissions, as otherwise the Window's
456// change signals will reflect different values than what we report
457// via the accessors. It would be nicer if this logic was contained
458// in the window container, for example via meta object property
459// interception, but that does not allow intercepting signal emissions.
460
461void QQuickWindowQmlImpl::setX(int x)
462{
463 Q_D(QQuickWindowQmlImpl);
464 if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
465 d->windowContainer->setX(x);
466 else
467 QQuickWindow::setX(x);
468}
469
470int QQuickWindowQmlImpl::x() const
471{
472 Q_D(const QQuickWindowQmlImpl);
473 if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
474 return d->windowContainer->x();
475 else
476 return QQuickWindow::x();
477}
478
479void QQuickWindowQmlImpl::setY(int y)
480{
481 Q_D(QQuickWindowQmlImpl);
482 if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
483 d->windowContainer->setY(y);
484 else
485 QQuickWindow::setY(y);
486}
487
488int QQuickWindowQmlImpl::y() const
489{
490 Q_D(const QQuickWindowQmlImpl);
491 if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
492 return d->windowContainer->y();
493 else
494 return QQuickWindow::y();
495}
496
497/*!
498 \qmlproperty real QtQuick::Window::z
499 \internal
500
501 Sets the stacking order of sibling windows.
502
503 By default the stacking order is 0.
504
505 Windows with a higher stacking value are drawn on top of windows with a
506 lower stacking order. Windows with the same stacking value are drawn
507 bottom up in the order they appear in the QML document.
508
509 \note This property only has an effect for child windows.
510
511 \sa QtQuick::Item::z
512*/
513
514void QQuickWindowQmlImpl::setZ(qreal z)
515{
516 Q_D(QQuickWindowQmlImpl);
517 if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
518 d->windowContainer->setZ(z);
519 else
520 d->z = z;
521}
522
523qreal QQuickWindowQmlImpl::z() const
524{
525 Q_D(const QQuickWindowQmlImpl);
526 if (Q_UNLIKELY(d->windowContainer && d->windowContainer->window()))
527 return d->windowContainer->z();
528 else
529 return d->z;
530}
531
532// --------------------------------------------------------------------
533
534QObject *QQuickWindowQmlImpl::screen() const
535{
536 return new QQuickScreenInfo(const_cast<QQuickWindowQmlImpl *>(this), QWindow::screen());
537}
538
539void QQuickWindowQmlImpl::setScreen(QObject *screen)
540{
541 QQuickScreenInfo *screenWrapper = qobject_cast<QQuickScreenInfo *>(object: screen);
542 QWindow::setScreen(screenWrapper ? screenWrapper->wrappedScreen() : nullptr);
543}
544
545QQuickWindowAttached *QQuickWindowQmlImpl::qmlAttachedProperties(QObject *object)
546{
547 return new QQuickWindowAttached(object);
548}
549
550QT_END_NAMESPACE
551
552#include "moc_qquickwindowmodule_p.cpp"
553

source code of qtdeclarative/src/quick/items/qquickwindowmodule.cpp