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