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

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