1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qquickwindow.h" |
41 | #include "qquickwindow_p.h" |
42 | |
43 | #include "qquickitem.h" |
44 | #include "qquickitem_p.h" |
45 | #include "qquickevents_p_p.h" |
46 | |
47 | #if QT_CONFIG(quick_draganddrop) |
48 | #include <private/qquickdrag_p.h> |
49 | #endif |
50 | #include <private/qquickhoverhandler_p.h> |
51 | #include <private/qquickpointerhandler_p.h> |
52 | |
53 | #include <QtQuick/private/qsgrenderer_p.h> |
54 | #include <QtQuick/private/qsgplaintexture_p.h> |
55 | #include <private/qsgrenderloop_p.h> |
56 | #include <private/qsgrhisupport_p.h> |
57 | #include <private/qquickrendercontrol_p.h> |
58 | #include <private/qquickanimatorcontroller_p.h> |
59 | #include <private/qquickprofiler_p.h> |
60 | |
61 | #include <private/qguiapplication_p.h> |
62 | #include <QtGui/QInputMethod> |
63 | |
64 | #include <private/qabstractanimation_p.h> |
65 | |
66 | #include <QtGui/qpainter.h> |
67 | #include <QtGui/qevent.h> |
68 | #include <QtGui/qmatrix4x4.h> |
69 | #include <QtGui/qpa/qplatformtheme.h> |
70 | #include <QtCore/qvarlengtharray.h> |
71 | #include <QtCore/qabstractanimation.h> |
72 | #include <QtCore/QLibraryInfo> |
73 | #include <QtCore/QRunnable> |
74 | #include <QtQml/qqmlincubator.h> |
75 | #include <QtQml/qqmlinfo.h> |
76 | #include <QtQml/private/qqmlmetatype_p.h> |
77 | |
78 | #include <QtQuick/private/qquickpixmapcache_p.h> |
79 | |
80 | #include <private/qqmldebugserviceinterfaces_p.h> |
81 | #include <private/qqmldebugconnector_p.h> |
82 | #if QT_CONFIG(opengl) |
83 | # include <private/qopenglvertexarrayobject_p.h> |
84 | # include <private/qsgdefaultrendercontext_p.h> |
85 | #endif |
86 | #ifndef QT_NO_DEBUG_STREAM |
87 | #include <private/qdebug_p.h> |
88 | #endif |
89 | |
90 | #include <QtGui/private/qrhi_p.h> |
91 | |
92 | QT_BEGIN_NAMESPACE |
93 | |
94 | Q_LOGGING_CATEGORY(DBG_TOUCH, "qt.quick.touch" ) |
95 | Q_LOGGING_CATEGORY(DBG_TOUCH_TARGET, "qt.quick.touch.target" ) |
96 | Q_LOGGING_CATEGORY(DBG_MOUSE, "qt.quick.mouse" ) |
97 | Q_LOGGING_CATEGORY(DBG_MOUSE_TARGET, "qt.quick.mouse.target" ) |
98 | Q_LOGGING_CATEGORY(lcTablet, "qt.quick.tablet" ) |
99 | Q_LOGGING_CATEGORY(lcWheelTarget, "qt.quick.wheel.target" ) |
100 | Q_LOGGING_CATEGORY(lcGestureTarget, "qt.quick.gesture.target" ) |
101 | Q_LOGGING_CATEGORY(DBG_HOVER_TRACE, "qt.quick.hover.trace" ) |
102 | Q_LOGGING_CATEGORY(DBG_FOCUS, "qt.quick.focus" ) |
103 | Q_LOGGING_CATEGORY(DBG_DIRTY, "qt.quick.dirty" ) |
104 | Q_LOGGING_CATEGORY(lcTransient, "qt.quick.window.transient" ) |
105 | |
106 | extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); |
107 | extern Q_GUI_EXPORT bool qt_sendShortcutOverrideEvent(QObject *o, ulong timestamp, int k, Qt::KeyboardModifiers mods, const QString &text = QString(), bool autorep = false, ushort count = 1); |
108 | |
109 | bool QQuickWindowPrivate::defaultAlphaBuffer = false; |
110 | |
111 | #if defined(QT_QUICK_DEFAULT_TEXT_RENDER_TYPE) |
112 | QQuickWindow::TextRenderType QQuickWindowPrivate::textRenderType = QQuickWindow::QT_QUICK_DEFAULT_TEXT_RENDER_TYPE; |
113 | #else |
114 | QQuickWindow::TextRenderType QQuickWindowPrivate::textRenderType = QQuickWindow::QtTextRendering; |
115 | #endif |
116 | |
117 | void QQuickWindowPrivate::updateFocusItemTransform() |
118 | { |
119 | #if QT_CONFIG(im) |
120 | Q_Q(QQuickWindow); |
121 | QQuickItem *focus = q->activeFocusItem(); |
122 | if (focus && QGuiApplication::focusObject() == focus) { |
123 | QQuickItemPrivate *focusPrivate = QQuickItemPrivate::get(item: focus); |
124 | QGuiApplication::inputMethod()->setInputItemTransform(focusPrivate->itemToWindowTransform()); |
125 | QGuiApplication::inputMethod()->setInputItemRectangle(QRectF(0, 0, focusPrivate->width, focusPrivate->height)); |
126 | focus->updateInputMethod(queries: Qt::ImInputItemClipRectangle); |
127 | } |
128 | #endif |
129 | } |
130 | |
131 | class QQuickWindowIncubationController : public QObject, public QQmlIncubationController |
132 | { |
133 | Q_OBJECT |
134 | |
135 | public: |
136 | QQuickWindowIncubationController(QSGRenderLoop *loop) |
137 | : m_renderLoop(loop), m_timer(0) |
138 | { |
139 | // Allow incubation for 1/3 of a frame. |
140 | m_incubation_time = qMax(a: 1, b: int(1000 / QGuiApplication::primaryScreen()->refreshRate()) / 3); |
141 | |
142 | QAnimationDriver *animationDriver = m_renderLoop->animationDriver(); |
143 | if (animationDriver) { |
144 | connect(sender: animationDriver, SIGNAL(stopped()), receiver: this, SLOT(animationStopped())); |
145 | connect(sender: m_renderLoop, SIGNAL(timeToIncubate()), receiver: this, SLOT(incubate())); |
146 | } |
147 | } |
148 | |
149 | protected: |
150 | void timerEvent(QTimerEvent *) override |
151 | { |
152 | killTimer(id: m_timer); |
153 | m_timer = 0; |
154 | incubate(); |
155 | } |
156 | |
157 | void incubateAgain() { |
158 | if (m_timer == 0) { |
159 | // Wait for a while before processing the next batch. Using a |
160 | // timer to avoid starvation of system events. |
161 | m_timer = startTimer(interval: m_incubation_time); |
162 | } |
163 | } |
164 | |
165 | public slots: |
166 | void incubate() { |
167 | if (m_renderLoop && incubatingObjectCount()) { |
168 | if (m_renderLoop->interleaveIncubation()) { |
169 | incubateFor(msecs: m_incubation_time); |
170 | } else { |
171 | incubateFor(msecs: m_incubation_time * 2); |
172 | if (incubatingObjectCount()) |
173 | incubateAgain(); |
174 | } |
175 | } |
176 | } |
177 | |
178 | void animationStopped() { incubate(); } |
179 | |
180 | protected: |
181 | void incubatingObjectCountChanged(int count) override |
182 | { |
183 | if (count && m_renderLoop && !m_renderLoop->interleaveIncubation()) |
184 | incubateAgain(); |
185 | } |
186 | |
187 | private: |
188 | QPointer<QSGRenderLoop> m_renderLoop; |
189 | int m_incubation_time; |
190 | int m_timer; |
191 | }; |
192 | |
193 | #include "qquickwindow.moc" |
194 | #include "moc_qquickwindow_p.cpp" |
195 | |
196 | |
197 | #if QT_CONFIG(accessibility) |
198 | /*! |
199 | Returns an accessibility interface for this window, or 0 if such an |
200 | interface cannot be created. |
201 | */ |
202 | QAccessibleInterface *QQuickWindow::accessibleRoot() const |
203 | { |
204 | return QAccessible::queryAccessibleInterface(const_cast<QQuickWindow*>(this)); |
205 | } |
206 | #endif |
207 | |
208 | |
209 | /* |
210 | Focus behavior |
211 | ============== |
212 | |
213 | Prior to being added to a valid window items can set and clear focus with no |
214 | effect. Only once items are added to a window (by way of having a parent set that |
215 | already belongs to a window) do the focus rules apply. Focus goes back to |
216 | having no effect if an item is removed from a window. |
217 | |
218 | When an item is moved into a new focus scope (either being added to a window |
219 | for the first time, or having its parent changed), if the focus scope already has |
220 | a scope focused item that takes precedence over the item being added. Otherwise, |
221 | the focus of the added tree is used. In the case of a tree of items being |
222 | added to a window for the first time, which may have a conflicted focus state (two |
223 | or more items in one scope having focus set), the same rule is applied item by item - |
224 | thus the first item that has focus will get it (assuming the scope doesn't already |
225 | have a scope focused item), and the other items will have their focus cleared. |
226 | */ |
227 | |
228 | QQuickRootItem::QQuickRootItem() |
229 | { |
230 | } |
231 | |
232 | /*! \reimp */ |
233 | void QQuickWindow::exposeEvent(QExposeEvent *) |
234 | { |
235 | Q_D(QQuickWindow); |
236 | if (d->windowManager) |
237 | d->windowManager->exposureChanged(window: this); |
238 | } |
239 | |
240 | /*! \reimp */ |
241 | void QQuickWindow::resizeEvent(QResizeEvent *ev) |
242 | { |
243 | Q_D(QQuickWindow); |
244 | if (d->contentItem) |
245 | d->contentItem->setSize(ev->size()); |
246 | if (d->windowManager) |
247 | d->windowManager->resize(this); |
248 | } |
249 | |
250 | /*! \reimp */ |
251 | void QQuickWindow::showEvent(QShowEvent *) |
252 | { |
253 | Q_D(QQuickWindow); |
254 | if (d->windowManager) |
255 | d->windowManager->show(window: this); |
256 | } |
257 | |
258 | /*! \reimp */ |
259 | void QQuickWindow::hideEvent(QHideEvent *) |
260 | { |
261 | Q_D(QQuickWindow); |
262 | if (d->windowManager) |
263 | d->windowManager->hide(window: this); |
264 | } |
265 | |
266 | /*! \reimp */ |
267 | void QQuickWindow::focusOutEvent(QFocusEvent *ev) |
268 | { |
269 | Q_D(QQuickWindow); |
270 | if (d->contentItem) |
271 | d->contentItem->setFocus(focus: false, reason: ev->reason()); |
272 | } |
273 | |
274 | /*! \reimp */ |
275 | void QQuickWindow::focusInEvent(QFocusEvent *ev) |
276 | { |
277 | Q_D(QQuickWindow); |
278 | if (d->contentItem) |
279 | d->contentItem->setFocus(focus: true, reason: ev->reason()); |
280 | d->updateFocusItemTransform(); |
281 | } |
282 | |
283 | #if QT_CONFIG(im) |
284 | static bool transformDirtyOnItemOrAncestor(const QQuickItem *item) |
285 | { |
286 | while (item) { |
287 | if (QQuickItemPrivate::get(item)->dirtyAttributes & ( |
288 | QQuickItemPrivate::TransformOrigin | |
289 | QQuickItemPrivate::Transform | |
290 | QQuickItemPrivate::BasicTransform | |
291 | QQuickItemPrivate::Position | |
292 | QQuickItemPrivate::Size | |
293 | QQuickItemPrivate::ParentChanged | |
294 | QQuickItemPrivate::Clip)) { |
295 | return true; |
296 | } |
297 | item = item->parentItem(); |
298 | } |
299 | return false; |
300 | } |
301 | #endif |
302 | |
303 | /*! |
304 | * \internal |
305 | |
306 | A "polish loop" can occur inside QQuickWindowPrivate::polishItems(). It is when an item calls |
307 | polish() on an(other?) item from updatePolish(). If this anomaly happens repeatedly and without |
308 | interruption (of a well-behaved updatePolish() that doesn't call polish()), it is a strong |
309 | indication that we are heading towards an infinite polish loop. A polish loop is not a bug in |
310 | Qt Quick - it is a bug caused by ill-behaved items put in the scene. |
311 | |
312 | We can detect this sequence of polish loops easily, since the |
313 | QQuickWindowPrivate::itemsToPolish is basically a stack: polish() will push to it, and |
314 | polishItems() will pop from it. |
315 | Therefore if updatePolish() calls polish(), the immediate next item polishItems() processes is |
316 | the item that was polished by the previous call to updatePolish(). |
317 | We therefore just need to count the number of polish loops we detected in _sequence_. |
318 | */ |
319 | struct PolishLoopDetector |
320 | { |
321 | PolishLoopDetector(const QVector<QQuickItem*> &itemsToPolish) |
322 | : itemsToPolish(itemsToPolish) |
323 | { |
324 | } |
325 | |
326 | /* |
327 | * returns true when it detected a likely infinite loop |
328 | * (suggests it should abort the polish loop) |
329 | **/ |
330 | bool check(QQuickItem *item, int itemsRemainingBeforeUpdatePolish) |
331 | { |
332 | if (itemsToPolish.count() > itemsRemainingBeforeUpdatePolish) { |
333 | // Detected potential polish loop. |
334 | ++numPolishLoopsInSequence; |
335 | if (numPolishLoopsInSequence >= 1000) { |
336 | // Start to warn about polish loop after 1000 consecutive polish loops |
337 | if (numPolishLoopsInSequence == 100000) { |
338 | // We have looped 100,000 times without actually reducing the list of items to |
339 | // polish, give up for now. |
340 | // This is not a fix, just a remedy so that the application can be somewhat |
341 | // responsive. |
342 | numPolishLoopsInSequence = 0; |
343 | return true; |
344 | } else if (numPolishLoopsInSequence < 1005) { |
345 | // Show the 5 next items involved in the polish loop. |
346 | // (most likely they will be the same 5 items...) |
347 | QQuickItem *guiltyItem = itemsToPolish.last(); |
348 | qmlWarning(me: item) << "possible QQuickItem::polish() loop" ; |
349 | |
350 | auto typeAndObjectName = [](QQuickItem *item) { |
351 | QString typeName = QQmlMetaType::prettyTypeName(object: item); |
352 | QString objName = item->objectName(); |
353 | if (!objName.isNull()) |
354 | return QLatin1String("%1(%2)" ).arg(args&: typeName, args&: objName); |
355 | return typeName; |
356 | }; |
357 | |
358 | qmlWarning(me: guiltyItem) << typeAndObjectName(guiltyItem) |
359 | << " called polish() inside updatePolish() of " << typeAndObjectName(item); |
360 | |
361 | if (numPolishLoopsInSequence == 1004) |
362 | // Enough warnings. Reset counter in order to speed things up and re-detect |
363 | // more loops |
364 | numPolishLoopsInSequence = 0; |
365 | } |
366 | } |
367 | } else { |
368 | numPolishLoopsInSequence = 0; |
369 | } |
370 | return false; |
371 | } |
372 | const QVector<QQuickItem*> &itemsToPolish; // Just a ref to the one in polishItems() |
373 | int numPolishLoopsInSequence = 0; |
374 | }; |
375 | |
376 | void QQuickWindowPrivate::polishItems() |
377 | { |
378 | // An item can trigger polish on another item, or itself for that matter, |
379 | // during its updatePolish() call. Because of this, we cannot simply |
380 | // iterate through the set, we must continue pulling items out until it |
381 | // is empty. |
382 | // In the case where polish is called from updatePolish() either directly |
383 | // or indirectly, we use a PolishLoopDetector to determine if a warning should |
384 | // be printed to the user. |
385 | |
386 | PolishLoopDetector polishLoopDetector(itemsToPolish); |
387 | while (!itemsToPolish.isEmpty()) { |
388 | QQuickItem *item = itemsToPolish.takeLast(); |
389 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
390 | itemPrivate->polishScheduled = false; |
391 | const int itemsRemaining = itemsToPolish.count(); |
392 | itemPrivate->updatePolish(); |
393 | item->updatePolish(); |
394 | if (polishLoopDetector.check(item, itemsRemainingBeforeUpdatePolish: itemsRemaining) == true) |
395 | break; |
396 | } |
397 | |
398 | #if QT_CONFIG(im) |
399 | if (QQuickItem *focusItem = q_func()->activeFocusItem()) { |
400 | // If the current focus item, or any of its anchestors, has changed location |
401 | // inside the window, we need inform IM about it. This to ensure that overlays |
402 | // such as selection handles will be updated. |
403 | const bool isActiveFocusItem = (focusItem == QGuiApplication::focusObject()); |
404 | const bool hasImEnabled = focusItem->inputMethodQuery(query: Qt::ImEnabled).toBool(); |
405 | if (isActiveFocusItem && hasImEnabled && transformDirtyOnItemOrAncestor(item: focusItem)) |
406 | updateFocusItemTransform(); |
407 | } |
408 | #endif |
409 | } |
410 | |
411 | /*! |
412 | * Schedules the window to render another frame. |
413 | * |
414 | * Calling QQuickWindow::update() differs from QQuickItem::update() in that |
415 | * it always triggers a repaint, regardless of changes in the underlying |
416 | * scene graph or not. |
417 | */ |
418 | void QQuickWindow::update() |
419 | { |
420 | Q_D(QQuickWindow); |
421 | if (d->windowManager) |
422 | d->windowManager->update(window: this); |
423 | else if (d->renderControl) |
424 | QQuickRenderControlPrivate::get(renderControl: d->renderControl)->update(); |
425 | } |
426 | |
427 | static void updatePixelRatioHelper(QQuickItem *item, float pixelRatio) |
428 | { |
429 | if (item->flags() & QQuickItem::ItemHasContents) { |
430 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
431 | itemPrivate->itemChange(QQuickItem::ItemDevicePixelRatioHasChanged, pixelRatio); |
432 | } |
433 | |
434 | QList <QQuickItem *> items = item->childItems(); |
435 | for (int i = 0; i < items.size(); ++i) |
436 | updatePixelRatioHelper(item: items.at(i), pixelRatio); |
437 | } |
438 | |
439 | void QQuickWindow::physicalDpiChanged() |
440 | { |
441 | Q_D(QQuickWindow); |
442 | const qreal newPixelRatio = screen()->devicePixelRatio(); |
443 | if (qFuzzyCompare(p1: newPixelRatio, p2: d->devicePixelRatio)) |
444 | return; |
445 | d->devicePixelRatio = newPixelRatio; |
446 | if (d->contentItem) |
447 | updatePixelRatioHelper(item: d->contentItem, pixelRatio: newPixelRatio); |
448 | } |
449 | |
450 | void QQuickWindow::handleScreenChanged(QScreen *screen) |
451 | { |
452 | Q_D(QQuickWindow); |
453 | // we connected to the initial screen in QQuickWindowPrivate::init, but the screen changed |
454 | disconnect(d->physicalDpiChangedConnection); |
455 | if (screen) { |
456 | physicalDpiChanged(); |
457 | // When physical DPI changes on the same screen, either the resolution or the device pixel |
458 | // ratio changed. We must check what it is. Device pixel ratio does not have its own |
459 | // ...Changed() signal. Reconnect, same as in QQuickWindowPrivate::init. |
460 | d->physicalDpiChangedConnection = connect(sender: screen, signal: &QScreen::physicalDotsPerInchChanged, |
461 | receiver: this, slot: &QQuickWindow::physicalDpiChanged); |
462 | } |
463 | |
464 | d->forcePolish(); |
465 | } |
466 | |
467 | void forcePolishHelper(QQuickItem *item) |
468 | { |
469 | if (item->flags() & QQuickItem::ItemHasContents) { |
470 | item->polish(); |
471 | } |
472 | |
473 | QList <QQuickItem *> items = item->childItems(); |
474 | for (int i=0; i<items.size(); ++i) |
475 | forcePolishHelper(item: items.at(i)); |
476 | } |
477 | |
478 | /*! |
479 | Schedules polish events on all items in the scene. |
480 | */ |
481 | void QQuickWindowPrivate::forcePolish() |
482 | { |
483 | Q_Q(QQuickWindow); |
484 | if (!q->screen()) |
485 | return; |
486 | forcePolishHelper(item: contentItem); |
487 | } |
488 | |
489 | void forceUpdate(QQuickItem *item) |
490 | { |
491 | if (item->flags() & QQuickItem::ItemHasContents) |
492 | item->update(); |
493 | QQuickItemPrivate::get(item)->dirty(QQuickItemPrivate::ChildrenUpdateMask); |
494 | |
495 | QList <QQuickItem *> items = item->childItems(); |
496 | for (int i=0; i<items.size(); ++i) |
497 | forceUpdate(item: items.at(i)); |
498 | } |
499 | |
500 | void QQuickWindowPrivate::syncSceneGraph() |
501 | { |
502 | Q_Q(QQuickWindow); |
503 | |
504 | // Calculate the dpr the same way renderSceneGraph() will. |
505 | qreal devicePixelRatio = q->effectiveDevicePixelRatio(); |
506 | if (renderTargetId && !QQuickRenderControl::renderWindowFor(win: q)) |
507 | devicePixelRatio = 1; |
508 | |
509 | context->prepareSync(devicePixelRatio, cb: rhi ? swapchain->currentFrameCommandBuffer() : nullptr); |
510 | |
511 | animationController->beforeNodeSync(); |
512 | |
513 | emit q->beforeSynchronizing(); |
514 | runAndClearJobs(jobs: &beforeSynchronizingJobs); |
515 | if (!renderer) { |
516 | forceUpdate(item: contentItem); |
517 | |
518 | QSGRootNode *rootNode = new QSGRootNode; |
519 | rootNode->appendChildNode(node: QQuickItemPrivate::get(item: contentItem)->itemNode()); |
520 | renderer = context->createRenderer(); |
521 | renderer->setRootNode(rootNode); |
522 | } |
523 | |
524 | updateDirtyNodes(); |
525 | |
526 | animationController->afterNodeSync(); |
527 | |
528 | // Copy the current state of clearing from window into renderer. |
529 | renderer->setClearColor(clearColor); |
530 | QSGAbstractRenderer::ClearMode mode = QSGAbstractRenderer::ClearStencilBuffer | QSGAbstractRenderer::ClearDepthBuffer; |
531 | if (clearBeforeRendering) |
532 | mode |= QSGAbstractRenderer::ClearColorBuffer; |
533 | renderer->setClearMode(mode); |
534 | |
535 | renderer->setCustomRenderMode(customRenderMode); |
536 | |
537 | emit q->afterSynchronizing(); |
538 | runAndClearJobs(jobs: &afterSynchronizingJobs); |
539 | } |
540 | |
541 | void QQuickWindowPrivate::emitBeforeRenderPassRecording(void *ud) |
542 | { |
543 | QQuickWindow *w = reinterpret_cast<QQuickWindow *>(ud); |
544 | emit w->beforeRenderPassRecording(); |
545 | } |
546 | |
547 | void QQuickWindowPrivate::emitAfterRenderPassRecording(void *ud) |
548 | { |
549 | QQuickWindow *w = reinterpret_cast<QQuickWindow *>(ud); |
550 | emit w->afterRenderPassRecording(); |
551 | } |
552 | |
553 | void QQuickWindowPrivate::renderSceneGraph(const QSize &size, const QSize &surfaceSize) |
554 | { |
555 | Q_Q(QQuickWindow); |
556 | if (!renderer) |
557 | return; |
558 | |
559 | if (rhi) { |
560 | // ### no offscreen ("renderTargetId") support yet |
561 | context->beginNextRhiFrame(renderer, |
562 | rt: swapchain->currentFrameRenderTarget(), |
563 | rp: rpDescForSwapchain, |
564 | cb: swapchain->currentFrameCommandBuffer(), |
565 | mainPassRecordingStart: emitBeforeRenderPassRecording, |
566 | mainPassRecordingEnd: emitAfterRenderPassRecording, |
567 | callbackUserData: q); |
568 | } else { |
569 | context->beginNextFrame(renderer, |
570 | mainPassRecordingStart: emitBeforeRenderPassRecording, |
571 | mainPassRecordingEnd: emitAfterRenderPassRecording, |
572 | callbackUserData: q); |
573 | } |
574 | |
575 | animationController->advance(); |
576 | emit q->beforeRendering(); |
577 | runAndClearJobs(jobs: &beforeRenderingJobs); |
578 | if (!customRenderStage || !customRenderStage->render()) { |
579 | int fboId = 0; |
580 | const qreal devicePixelRatio = q->effectiveDevicePixelRatio(); |
581 | if (renderTargetId) { |
582 | QRect rect(QPoint(0, 0), renderTargetSize); |
583 | fboId = renderTargetId; |
584 | renderer->setDeviceRect(rect); |
585 | renderer->setViewportRect(rect); |
586 | if (QQuickRenderControl::renderWindowFor(win: q)) { |
587 | renderer->setProjectionMatrixToRect(QRect(QPoint(0, 0), size)); |
588 | renderer->setDevicePixelRatio(devicePixelRatio); |
589 | } else { |
590 | renderer->setProjectionMatrixToRect(QRect(QPoint(0, 0), rect.size())); |
591 | renderer->setDevicePixelRatio(1); |
592 | } |
593 | } else { |
594 | QSize pixelSize; |
595 | QSizeF logicalSize; |
596 | if (surfaceSize.isEmpty()) { |
597 | pixelSize = size * devicePixelRatio; |
598 | logicalSize = size; |
599 | } else { |
600 | pixelSize = surfaceSize; |
601 | logicalSize = QSizeF(surfaceSize) / devicePixelRatio; |
602 | } |
603 | QRect rect(QPoint(0, 0), pixelSize); |
604 | renderer->setDeviceRect(rect); |
605 | renderer->setViewportRect(rect); |
606 | const bool flipY = rhi ? !rhi->isYUpInNDC() : false; |
607 | QSGAbstractRenderer::MatrixTransformFlags matrixFlags; |
608 | if (flipY) |
609 | matrixFlags |= QSGAbstractRenderer::MatrixTransformFlipY; |
610 | renderer->setProjectionMatrixToRect(rect: QRectF(QPoint(0, 0), logicalSize), flags: matrixFlags); |
611 | renderer->setDevicePixelRatio(devicePixelRatio); |
612 | } |
613 | |
614 | if (rhi) |
615 | context->renderNextRhiFrame(renderer); |
616 | else |
617 | context->renderNextFrame(renderer, fboId); |
618 | } |
619 | emit q->afterRendering(); |
620 | runAndClearJobs(jobs: &afterRenderingJobs); |
621 | |
622 | if (rhi) |
623 | context->endNextRhiFrame(renderer); |
624 | else |
625 | context->endNextFrame(renderer); |
626 | |
627 | if (renderer && renderer->hasCustomRenderModeWithContinuousUpdate()) { |
628 | // For the overdraw visualizer. This update is not urgent so avoid a |
629 | // direct update() call, this is only here to keep the overdraw |
630 | // visualization box rotating even when the scene is static. |
631 | QCoreApplication::postEvent(receiver: q, event: new QEvent(QEvent::Type(FullUpdateRequest))); |
632 | } |
633 | } |
634 | |
635 | QQuickWindowPrivate::QQuickWindowPrivate() |
636 | : contentItem(nullptr) |
637 | , activeFocusItem(nullptr) |
638 | #if QT_CONFIG(cursor) |
639 | , cursorItem(nullptr) |
640 | , cursorHandler(nullptr) |
641 | #endif |
642 | #if QT_CONFIG(quick_draganddrop) |
643 | , dragGrabber(nullptr) |
644 | #endif |
645 | , touchMouseId(-1) |
646 | , touchMouseDevice(nullptr) |
647 | , touchMousePressTimestamp(0) |
648 | , dirtyItemList(nullptr) |
649 | , devicePixelRatio(0) |
650 | , context(nullptr) |
651 | , renderer(nullptr) |
652 | , windowManager(nullptr) |
653 | , renderControl(nullptr) |
654 | , pointerEventRecursionGuard(0) |
655 | , customRenderStage(nullptr) |
656 | , clearColor(Qt::white) |
657 | , clearBeforeRendering(true) |
658 | , persistentGLContext(true) |
659 | , persistentSceneGraph(true) |
660 | , lastWheelEventAccepted(false) |
661 | , componentCompleted(true) |
662 | , allowChildEventFiltering(true) |
663 | , allowDoubleClick(true) |
664 | , lastFocusReason(Qt::OtherFocusReason) |
665 | , renderTarget(nullptr) |
666 | , renderTargetId(0) |
667 | , vaoHelper(nullptr) |
668 | , incubationController(nullptr) |
669 | , hasActiveSwapchain(false) |
670 | , hasRenderableSwapchain(false) |
671 | , swapchainJustBecameRenderable(false) |
672 | { |
673 | #if QT_CONFIG(quick_draganddrop) |
674 | dragGrabber = new QQuickDragGrabber; |
675 | #endif |
676 | } |
677 | |
678 | QQuickWindowPrivate::~QQuickWindowPrivate() |
679 | { |
680 | delete customRenderStage; |
681 | if (QQmlInspectorService *service = QQmlDebugConnector::service<QQmlInspectorService>()) |
682 | service->removeWindow(q_func()); |
683 | } |
684 | |
685 | void QQuickWindowPrivate::init(QQuickWindow *c, QQuickRenderControl *control) |
686 | { |
687 | q_ptr = c; |
688 | |
689 | |
690 | Q_Q(QQuickWindow); |
691 | |
692 | contentItem = new QQuickRootItem; |
693 | QQml_setParent_noEvent(object: contentItem, parent: c); |
694 | QQmlEngine::setObjectOwnership(contentItem, QQmlEngine::CppOwnership); |
695 | QQuickItemPrivate *contentItemPrivate = QQuickItemPrivate::get(item: contentItem); |
696 | contentItemPrivate->window = q; |
697 | contentItemPrivate->windowRefCount = 1; |
698 | contentItemPrivate->flags |= QQuickItem::ItemIsFocusScope; |
699 | contentItem->setSize(q->size()); |
700 | |
701 | customRenderMode = qgetenv(varName: "QSG_VISUALIZE" ); |
702 | renderControl = control; |
703 | if (renderControl) |
704 | QQuickRenderControlPrivate::get(renderControl)->window = q; |
705 | |
706 | if (!renderControl) |
707 | windowManager = QSGRenderLoop::instance(); |
708 | |
709 | Q_ASSERT(windowManager || renderControl); |
710 | |
711 | if (QScreen *screen = q->screen()) { |
712 | devicePixelRatio = screen->devicePixelRatio(); |
713 | // if the screen changes, then QQuickWindow::handleScreenChanged disconnects |
714 | // and connects to the new screen |
715 | physicalDpiChangedConnection = QObject::connect(sender: screen, signal: &QScreen::physicalDotsPerInchChanged, |
716 | receiver: q, slot: &QQuickWindow::physicalDpiChanged); |
717 | } |
718 | |
719 | QSGContext *sg; |
720 | if (renderControl) { |
721 | QQuickRenderControlPrivate *renderControlPriv = QQuickRenderControlPrivate::get(renderControl); |
722 | sg = renderControlPriv->sg; |
723 | context = renderControlPriv->rc; |
724 | } else { |
725 | windowManager->addWindow(win: q); |
726 | sg = windowManager->sceneGraphContext(); |
727 | context = windowManager->createRenderContext(sg); |
728 | } |
729 | |
730 | q->setSurfaceType(windowManager ? windowManager->windowSurfaceType() : QSurface::OpenGLSurface); |
731 | q->setFormat(sg->defaultSurfaceFormat()); |
732 | #if QT_CONFIG(vulkan) |
733 | if (q->surfaceType() == QSurface::VulkanSurface) |
734 | q->setVulkanInstance(QSGRhiSupport::vulkanInstance()); |
735 | #endif |
736 | |
737 | animationController.reset(other: new QQuickAnimatorController(q)); |
738 | |
739 | QObject::connect(sender: context, SIGNAL(initialized()), receiver: q, SIGNAL(sceneGraphInitialized()), Qt::DirectConnection); |
740 | QObject::connect(sender: context, SIGNAL(invalidated()), receiver: q, SIGNAL(sceneGraphInvalidated()), Qt::DirectConnection); |
741 | QObject::connect(sender: context, SIGNAL(invalidated()), receiver: q, SLOT(cleanupSceneGraph()), Qt::DirectConnection); |
742 | |
743 | QObject::connect(sender: q, SIGNAL(focusObjectChanged(QObject*)), receiver: q, SIGNAL(activeFocusItemChanged())); |
744 | QObject::connect(sender: q, SIGNAL(screenChanged(QScreen*)), receiver: q, SLOT(handleScreenChanged(QScreen*))); |
745 | QObject::connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), |
746 | receiver: q, SLOT(handleApplicationStateChanged(Qt::ApplicationState))); |
747 | QObject::connect(sender: q, SIGNAL(frameSwapped()), receiver: q, SLOT(runJobsAfterSwap()), Qt::DirectConnection); |
748 | |
749 | if (QQmlInspectorService *service = QQmlDebugConnector::service<QQmlInspectorService>()) |
750 | service->addWindow(q); |
751 | } |
752 | |
753 | void QQuickWindow::handleApplicationStateChanged(Qt::ApplicationState state) |
754 | { |
755 | Q_D(QQuickWindow); |
756 | if (state != Qt::ApplicationActive && d->contentItem) |
757 | d->contentItem->windowDeactivateEvent(); |
758 | } |
759 | |
760 | /*! |
761 | \property QQuickWindow::data |
762 | \internal |
763 | */ |
764 | |
765 | QQmlListProperty<QObject> QQuickWindowPrivate::data() |
766 | { |
767 | return QQmlListProperty<QObject>(q_func(), nullptr, |
768 | QQuickWindowPrivate::data_append, |
769 | QQuickWindowPrivate::data_count, |
770 | QQuickWindowPrivate::data_at, |
771 | QQuickWindowPrivate::data_clear, |
772 | QQuickWindowPrivate::data_replace, |
773 | QQuickWindowPrivate::data_removeLast); |
774 | } |
775 | |
776 | static QMouseEvent *touchToMouseEvent(QEvent::Type type, const QTouchEvent::TouchPoint &p, QTouchEvent *event, QQuickItem *item, bool transformNeeded = true) |
777 | { |
778 | Q_ASSERT(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)); |
779 | // The touch point local position and velocity are not yet transformed. |
780 | QMouseEvent *me = new QMouseEvent(type, transformNeeded ? item->mapFromScene(point: p.scenePos()) : p.pos(), p.scenePos(), p.screenPos(), |
781 | Qt::LeftButton, (type == QEvent::MouseButtonRelease ? Qt::NoButton : Qt::LeftButton), event->modifiers()); |
782 | me->setAccepted(true); |
783 | me->setTimestamp(event->timestamp()); |
784 | QVector2D transformedVelocity = p.velocity(); |
785 | if (transformNeeded) { |
786 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
787 | QMatrix4x4 transformMatrix(itemPrivate->windowToItemTransform()); |
788 | transformedVelocity = transformMatrix.mapVector(vector: p.velocity()).toVector2D(); |
789 | } |
790 | QGuiApplicationPrivate::setMouseEventCapsAndVelocity(event: me, caps: event->device()->capabilities(), velocity: transformedVelocity); |
791 | QGuiApplicationPrivate::setMouseEventSource(event: me, source: Qt::MouseEventSynthesizedByQt); |
792 | return me; |
793 | } |
794 | |
795 | bool QQuickWindowPrivate::checkIfDoubleTapped(ulong newPressEventTimestamp, QPoint newPressPos) |
796 | { |
797 | bool doubleClicked = false; |
798 | |
799 | if (touchMousePressTimestamp > 0) { |
800 | QPoint distanceBetweenPresses = newPressPos - touchMousePressPos; |
801 | const int doubleTapDistance = QGuiApplication::styleHints()->touchDoubleTapDistance(); |
802 | doubleClicked = (qAbs(t: distanceBetweenPresses.x()) <= doubleTapDistance) && (qAbs(t: distanceBetweenPresses.y()) <= doubleTapDistance); |
803 | |
804 | if (doubleClicked) { |
805 | ulong timeBetweenPresses = newPressEventTimestamp - touchMousePressTimestamp; |
806 | ulong doubleClickInterval = static_cast<ulong>(QGuiApplication::styleHints()-> |
807 | mouseDoubleClickInterval()); |
808 | doubleClicked = timeBetweenPresses < doubleClickInterval; |
809 | } |
810 | } |
811 | if (doubleClicked) { |
812 | touchMousePressTimestamp = 0; |
813 | } else { |
814 | touchMousePressTimestamp = newPressEventTimestamp; |
815 | touchMousePressPos = newPressPos; |
816 | } |
817 | |
818 | return doubleClicked; |
819 | } |
820 | |
821 | void QQuickWindowPrivate::cancelTouchMouseSynthesis() |
822 | { |
823 | qCDebug(DBG_TOUCH_TARGET); |
824 | touchMouseId = -1; |
825 | touchMouseDevice = nullptr; |
826 | } |
827 | |
828 | bool QQuickWindowPrivate::deliverTouchAsMouse(QQuickItem *item, QQuickPointerEvent *pointerEvent) |
829 | { |
830 | Q_ASSERT(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)); |
831 | Q_Q(QQuickWindow); |
832 | auto device = pointerEvent->device(); |
833 | |
834 | // A touch event from a trackpad is likely to be followed by a mouse or gesture event, so mouse event synth is redundant |
835 | if (device->type() == QQuickPointerDevice::TouchPad && device->capabilities().testFlag(flag: QQuickPointerDevice::MouseEmulation)) { |
836 | qCDebug(DBG_TOUCH_TARGET) << "skipping delivery of synth-mouse event from" << device; |
837 | return false; |
838 | } |
839 | |
840 | // FIXME: make this work for mouse events too and get rid of the asTouchEvent in here. |
841 | Q_ASSERT(pointerEvent->asPointerTouchEvent()); |
842 | QScopedPointer<QTouchEvent> event(pointerEvent->asPointerTouchEvent()->touchEventForItem(item)); |
843 | if (event.isNull()) |
844 | return false; |
845 | |
846 | // For each point, check if it is accepted, if not, try the next point. |
847 | // Any of the fingers can become the mouse one. |
848 | // This can happen because a mouse area might not accept an event at some point but another. |
849 | for (int i = 0; i < event->touchPoints().count(); ++i) { |
850 | const QTouchEvent::TouchPoint &p = event->touchPoints().at(i); |
851 | // A new touch point |
852 | if (touchMouseId == -1 && p.state() & Qt::TouchPointPressed) { |
853 | QPointF pos = item->mapFromScene(point: p.scenePos()); |
854 | |
855 | // probably redundant, we check bounds in the calling function (matchingNewPoints) |
856 | if (!item->contains(point: pos)) |
857 | break; |
858 | |
859 | qCDebug(DBG_TOUCH_TARGET) << "TP (mouse)" << Qt::hex << p.id() << "->" << item; |
860 | QScopedPointer<QMouseEvent> mousePress(touchToMouseEvent(type: QEvent::MouseButtonPress, p, event: event.data(), item, transformNeeded: false)); |
861 | |
862 | // Send a single press and see if that's accepted |
863 | QCoreApplication::sendEvent(receiver: item, event: mousePress.data()); |
864 | event->setAccepted(mousePress->isAccepted()); |
865 | if (mousePress->isAccepted()) { |
866 | touchMouseDevice = device; |
867 | touchMouseId = p.id(); |
868 | if (!q->mouseGrabberItem()) |
869 | item->grabMouse(); |
870 | if (auto pointerEventPoint = pointerEvent->pointById(pointId: p.id())) |
871 | pointerEventPoint->setGrabberItem(item); |
872 | |
873 | if (checkIfDoubleTapped(newPressEventTimestamp: event->timestamp(), newPressPos: p.screenPos().toPoint())) { |
874 | // since we synth the mouse event from from touch, we respect the |
875 | // QPlatformTheme::TouchDoubleTapDistance instead of QPlatformTheme::MouseDoubleClickDistance |
876 | QScopedPointer<QMouseEvent> mouseDoubleClick(touchToMouseEvent(type: QEvent::MouseButtonDblClick, p, event: event.data(), item, transformNeeded: false)); |
877 | QCoreApplication::sendEvent(receiver: item, event: mouseDoubleClick.data()); |
878 | event->setAccepted(mouseDoubleClick->isAccepted()); |
879 | if (!mouseDoubleClick->isAccepted()) |
880 | cancelTouchMouseSynthesis(); |
881 | } |
882 | |
883 | return true; |
884 | } |
885 | // try the next point |
886 | |
887 | // Touch point was there before and moved |
888 | } else if (touchMouseDevice == device && p.id() == touchMouseId) { |
889 | if (p.state() & Qt::TouchPointMoved) { |
890 | if (touchMousePressTimestamp != 0) { |
891 | const int doubleTapDistance = QGuiApplicationPrivate::platformTheme()->themeHint(hint: QPlatformTheme::TouchDoubleTapDistance).toInt(); |
892 | const QPoint moveDelta = p.screenPos().toPoint() - touchMousePressPos; |
893 | if (moveDelta.x() >= doubleTapDistance || moveDelta.y() >= doubleTapDistance) |
894 | touchMousePressTimestamp = 0; // Got dragged too far, dismiss the double tap |
895 | } |
896 | if (QQuickItem *mouseGrabberItem = q->mouseGrabberItem()) { |
897 | QScopedPointer<QMouseEvent> me(touchToMouseEvent(type: QEvent::MouseMove, p, event: event.data(), item: mouseGrabberItem, transformNeeded: false)); |
898 | QCoreApplication::sendEvent(receiver: item, event: me.data()); |
899 | event->setAccepted(me->isAccepted()); |
900 | if (me->isAccepted()) { |
901 | qCDebug(DBG_TOUCH_TARGET) << "TP (mouse)" << Qt::hex << p.id() << "->" << mouseGrabberItem; |
902 | } |
903 | return event->isAccepted(); |
904 | } else { |
905 | // no grabber, check if we care about mouse hover |
906 | // FIXME: this should only happen once, not recursively... I'll ignore it just ignore hover now. |
907 | // hover for touch??? |
908 | QScopedPointer<QMouseEvent> me(touchToMouseEvent(type: QEvent::MouseMove, p, event: event.data(), item, transformNeeded: false)); |
909 | if (lastMousePosition.isNull()) |
910 | lastMousePosition = me->windowPos(); |
911 | QPointF last = lastMousePosition; |
912 | lastMousePosition = me->windowPos(); |
913 | |
914 | bool accepted = me->isAccepted(); |
915 | bool delivered = deliverHoverEvent(contentItem, scenePos: me->windowPos(), lastScenePos: last, modifiers: me->modifiers(), timestamp: me->timestamp(), accepted); |
916 | if (!delivered) { |
917 | //take care of any exits |
918 | accepted = clearHover(timestamp: me->timestamp()); |
919 | } |
920 | me->setAccepted(accepted); |
921 | break; |
922 | } |
923 | } else if (p.state() & Qt::TouchPointReleased) { |
924 | // currently handled point was released |
925 | if (QQuickItem *mouseGrabberItem = q->mouseGrabberItem()) { |
926 | QScopedPointer<QMouseEvent> me(touchToMouseEvent(type: QEvent::MouseButtonRelease, p, event: event.data(), item: mouseGrabberItem, transformNeeded: false)); |
927 | QCoreApplication::sendEvent(receiver: item, event: me.data()); |
928 | |
929 | if (item->acceptHoverEvents() && p.screenPos() != QGuiApplicationPrivate::lastCursorPosition) { |
930 | QPointF localMousePos(qInf(), qInf()); |
931 | if (QWindow *w = item->window()) |
932 | localMousePos = item->mapFromScene(point: w->mapFromGlobal(pos: QGuiApplicationPrivate::lastCursorPosition.toPoint())); |
933 | QMouseEvent mm(QEvent::MouseMove, localMousePos, QGuiApplicationPrivate::lastCursorPosition, |
934 | Qt::NoButton, Qt::NoButton, event->modifiers()); |
935 | QCoreApplication::sendEvent(receiver: item, event: &mm); |
936 | } |
937 | if (q->mouseGrabberItem()) // might have ungrabbed due to event |
938 | q->mouseGrabberItem()->ungrabMouse(); |
939 | |
940 | cancelTouchMouseSynthesis(); |
941 | return me->isAccepted(); |
942 | } |
943 | } |
944 | break; |
945 | } |
946 | } |
947 | return false; |
948 | } |
949 | |
950 | void QQuickWindowPrivate::grabTouchPoints(QObject *grabber, const QVector<int> &ids) |
951 | { |
952 | QQuickPointerEvent *ev = nullptr; |
953 | for (int i = 0; i < ids.count(); ++i) { |
954 | int id = ids.at(i); |
955 | if (Q_UNLIKELY(id < 0)) { |
956 | qWarning(msg: "ignoring grab of touchpoint %d" , id); |
957 | continue; |
958 | } |
959 | if (id == touchMouseId) { |
960 | auto point = pointerEventInstance(device: touchMouseDevice)->pointById(pointId: id); |
961 | auto touchMouseGrabber = point->grabberItem(); |
962 | if (touchMouseGrabber) { |
963 | point->setExclusiveGrabber(nullptr); |
964 | touchMouseGrabber->mouseUngrabEvent(); |
965 | touchMouseGrabber->touchUngrabEvent(); |
966 | cancelTouchMouseSynthesis(); |
967 | } |
968 | qCDebug(DBG_MOUSE_TARGET) << "grabTouchPoints: mouse grabber changed due to grabTouchPoints:" << touchMouseGrabber << "-> null" ; |
969 | } |
970 | |
971 | // optimization to avoid the loop over devices below: |
972 | // all ids are probably from the same event, so we don't have to search |
973 | if (ev) { |
974 | auto point = ev->pointById(pointId: id); |
975 | if (point && point->exclusiveGrabber() != grabber) { |
976 | point->setExclusiveGrabber(grabber); |
977 | continue; // next id in the ids loop |
978 | } |
979 | } |
980 | // search all devices for a QQuickPointerEvent instance that is delivering the point with id |
981 | const auto touchDevices = QQuickPointerDevice::touchDevices(); |
982 | for (auto device : touchDevices) { |
983 | QQuickPointerEvent *pev = pointerEventInstance(device); |
984 | auto point = pev->pointById(pointId: id); |
985 | if (point) { |
986 | ev = pev; |
987 | if (point->exclusiveGrabber() != grabber) |
988 | point->setExclusiveGrabber(grabber); |
989 | break; // out of touchDevices loop |
990 | } |
991 | } |
992 | } |
993 | } |
994 | |
995 | /*! |
996 | Ungrabs all touchpoint grabs and/or the mouse grab from the given item \a grabber. |
997 | This should not be called when processing a release event - that's redundant. |
998 | It is called in other cases, when the points may not be released, but the item |
999 | nevertheless must lose its grab due to becoming disabled, invisible, etc. |
1000 | QQuickEventPoint::setGrabberItem() calls touchUngrabEvent() when all points are released, |
1001 | but if not all points are released, it cannot be sure whether to call touchUngrabEvent() |
1002 | or not; so we have to do it here. |
1003 | */ |
1004 | void QQuickWindowPrivate::removeGrabber(QQuickItem *grabber, bool mouse, bool touch) |
1005 | { |
1006 | Q_Q(QQuickWindow); |
1007 | if (Q_LIKELY(mouse) && q->mouseGrabberItem() == grabber) { |
1008 | bool fromTouch = isDeliveringTouchAsMouse(); |
1009 | auto point = fromTouch ? |
1010 | pointerEventInstance(device: touchMouseDevice)->pointById(pointId: touchMouseId) : |
1011 | pointerEventInstance(device: QQuickPointerDevice::genericMouseDevice())->point(i: 0); |
1012 | QQuickItem *oldGrabber = point->grabberItem(); |
1013 | qCDebug(DBG_MOUSE_TARGET) << "removeGrabber" << oldGrabber << "-> null" ; |
1014 | point->setGrabberItem(nullptr); |
1015 | sendUngrabEvent(grabber: oldGrabber, touch: fromTouch); |
1016 | } |
1017 | if (Q_LIKELY(touch)) { |
1018 | bool ungrab = false; |
1019 | const auto touchDevices = QQuickPointerDevice::touchDevices(); |
1020 | for (auto device : touchDevices) { |
1021 | if (auto pointerEvent = queryPointerEventInstance(device)) { |
1022 | for (int i = 0; i < pointerEvent->pointCount(); ++i) { |
1023 | if (pointerEvent->point(i)->exclusiveGrabber() == grabber) { |
1024 | pointerEvent->point(i)->setGrabberItem(nullptr); |
1025 | ungrab = true; |
1026 | } |
1027 | } |
1028 | } |
1029 | } |
1030 | if (ungrab) |
1031 | grabber->touchUngrabEvent(); |
1032 | } |
1033 | } |
1034 | |
1035 | void QQuickWindowPrivate::sendUngrabEvent(QQuickItem *grabber, bool touch) |
1036 | { |
1037 | if (!grabber) |
1038 | return; |
1039 | QEvent e(QEvent::UngrabMouse); |
1040 | hasFiltered.clear(); |
1041 | if (!sendFilteredMouseEvent(event: &e, receiver: grabber, filteringParent: grabber->parentItem())) { |
1042 | grabber->mouseUngrabEvent(); |
1043 | if (touch) |
1044 | grabber->touchUngrabEvent(); |
1045 | } |
1046 | } |
1047 | |
1048 | /*! |
1049 | Translates the data in \a touchEvent to this window. This method leaves the item local positions in |
1050 | \a touchEvent untouched (these are filled in later). |
1051 | */ |
1052 | void QQuickWindowPrivate::translateTouchEvent(QTouchEvent *touchEvent) |
1053 | { |
1054 | QList<QTouchEvent::TouchPoint> touchPoints = touchEvent->touchPoints(); |
1055 | for (int i = 0; i < touchPoints.count(); ++i) { |
1056 | QTouchEvent::TouchPoint &touchPoint = touchPoints[i]; |
1057 | touchPoint.setScenePos(touchPoint.pos()); |
1058 | touchPoint.setStartScenePos(touchPoint.startPos()); |
1059 | touchPoint.setLastScenePos(touchPoint.lastPos()); |
1060 | } |
1061 | touchEvent->setTouchPoints(touchPoints); |
1062 | } |
1063 | |
1064 | |
1065 | static inline bool windowHasFocus(QQuickWindow *win) |
1066 | { |
1067 | const QWindow *focusWindow = QGuiApplication::focusWindow(); |
1068 | return win == focusWindow || QQuickRenderControl::renderWindowFor(win) == focusWindow; |
1069 | } |
1070 | |
1071 | #ifdef Q_OS_WEBOS |
1072 | // Temporary fix for webOS until multi-seat is implemented see QTBUG-85272 |
1073 | static inline bool singleWindowOnScreen(QQuickWindow *win) |
1074 | { |
1075 | const QWindowList windowList = QGuiApplication::allWindows(); |
1076 | for (int i = 0; i < windowList.count(); i++) { |
1077 | QWindow *ii = windowList.at(i); |
1078 | if (ii == win) |
1079 | continue; |
1080 | if (ii->screen() == win->screen()) |
1081 | return false; |
1082 | } |
1083 | |
1084 | return true; |
1085 | } |
1086 | #endif |
1087 | |
1088 | /*! |
1089 | Set the focus inside \a scope to be \a item. |
1090 | If the scope contains the active focus item, it will be changed to \a item. |
1091 | Calls notifyFocusChangesRecur for all changed items. |
1092 | */ |
1093 | void QQuickWindowPrivate::setFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions options) |
1094 | { |
1095 | Q_Q(QQuickWindow); |
1096 | |
1097 | Q_ASSERT(item); |
1098 | Q_ASSERT(scope || item == contentItem); |
1099 | |
1100 | qCDebug(DBG_FOCUS) << "QQuickWindowPrivate::setFocusInScope():" ; |
1101 | qCDebug(DBG_FOCUS) << " scope:" << (QObject *)scope; |
1102 | if (scope) |
1103 | qCDebug(DBG_FOCUS) << " scopeSubFocusItem:" << (QObject *)QQuickItemPrivate::get(item: scope)->subFocusItem; |
1104 | qCDebug(DBG_FOCUS) << " item:" << (QObject *)item; |
1105 | qCDebug(DBG_FOCUS) << " activeFocusItem:" << (QObject *)activeFocusItem; |
1106 | |
1107 | QQuickItemPrivate *scopePrivate = scope ? QQuickItemPrivate::get(item: scope) : nullptr; |
1108 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
1109 | |
1110 | QQuickItem *oldActiveFocusItem = nullptr; |
1111 | QQuickItem *currentActiveFocusItem = activeFocusItem; |
1112 | QQuickItem *newActiveFocusItem = nullptr; |
1113 | bool sendFocusIn = false; |
1114 | |
1115 | lastFocusReason = reason; |
1116 | |
1117 | QVarLengthArray<QQuickItem *, 20> changed; |
1118 | |
1119 | // Does this change the active focus? |
1120 | if (item == contentItem || scopePrivate->activeFocus) { |
1121 | oldActiveFocusItem = activeFocusItem; |
1122 | if (item->isEnabled()) { |
1123 | newActiveFocusItem = item; |
1124 | while (newActiveFocusItem->isFocusScope() |
1125 | && newActiveFocusItem->scopedFocusItem() |
1126 | && newActiveFocusItem->scopedFocusItem()->isEnabled()) { |
1127 | newActiveFocusItem = newActiveFocusItem->scopedFocusItem(); |
1128 | } |
1129 | } else { |
1130 | newActiveFocusItem = scope; |
1131 | } |
1132 | |
1133 | if (oldActiveFocusItem) { |
1134 | #if QT_CONFIG(im) |
1135 | QGuiApplication::inputMethod()->commit(); |
1136 | #endif |
1137 | |
1138 | activeFocusItem = nullptr; |
1139 | |
1140 | QQuickItem *afi = oldActiveFocusItem; |
1141 | while (afi && afi != scope) { |
1142 | if (QQuickItemPrivate::get(item: afi)->activeFocus) { |
1143 | QQuickItemPrivate::get(item: afi)->activeFocus = false; |
1144 | changed << afi; |
1145 | } |
1146 | afi = afi->parentItem(); |
1147 | } |
1148 | } |
1149 | } |
1150 | |
1151 | if (item != contentItem && !(options & DontChangeSubFocusItem)) { |
1152 | QQuickItem *oldSubFocusItem = scopePrivate->subFocusItem; |
1153 | if (oldSubFocusItem) { |
1154 | QQuickItemPrivate::get(item: oldSubFocusItem)->focus = false; |
1155 | changed << oldSubFocusItem; |
1156 | } |
1157 | |
1158 | QQuickItemPrivate::get(item)->updateSubFocusItem(scope, focus: true); |
1159 | } |
1160 | |
1161 | if (!(options & DontChangeFocusProperty)) { |
1162 | if (item != contentItem |
1163 | || windowHasFocus(win: q) |
1164 | #ifdef Q_OS_WEBOS |
1165 | // Allow focused if there is only one window in the screen where it belongs. |
1166 | // Temporary fix for webOS until multi-seat is implemented see QTBUG-85272 |
1167 | || singleWindowOnScreen(q) |
1168 | #endif |
1169 | ) { |
1170 | itemPrivate->focus = true; |
1171 | changed << item; |
1172 | } |
1173 | } |
1174 | |
1175 | if (newActiveFocusItem && contentItem->hasFocus()) { |
1176 | activeFocusItem = newActiveFocusItem; |
1177 | |
1178 | QQuickItemPrivate::get(item: newActiveFocusItem)->activeFocus = true; |
1179 | changed << newActiveFocusItem; |
1180 | |
1181 | QQuickItem *afi = newActiveFocusItem->parentItem(); |
1182 | while (afi && afi != scope) { |
1183 | if (afi->isFocusScope()) { |
1184 | QQuickItemPrivate::get(item: afi)->activeFocus = true; |
1185 | changed << afi; |
1186 | } |
1187 | afi = afi->parentItem(); |
1188 | } |
1189 | updateFocusItemTransform(); |
1190 | sendFocusIn = true; |
1191 | } |
1192 | |
1193 | // Now that all the state is changed, emit signals & events |
1194 | // We must do this last, as this process may result in further changes to focus. |
1195 | if (oldActiveFocusItem) { |
1196 | QFocusEvent event(QEvent::FocusOut, reason); |
1197 | QCoreApplication::sendEvent(receiver: oldActiveFocusItem, event: &event); |
1198 | } |
1199 | |
1200 | // Make sure that the FocusOut didn't result in another focus change. |
1201 | if (sendFocusIn && activeFocusItem == newActiveFocusItem) { |
1202 | QFocusEvent event(QEvent::FocusIn, reason); |
1203 | QCoreApplication::sendEvent(receiver: newActiveFocusItem, event: &event); |
1204 | } |
1205 | |
1206 | if (activeFocusItem != currentActiveFocusItem) |
1207 | emit q->focusObjectChanged(object: activeFocusItem); |
1208 | |
1209 | if (!changed.isEmpty()) |
1210 | notifyFocusChangesRecur(item: changed.data(), remaining: changed.count() - 1); |
1211 | } |
1212 | |
1213 | void QQuickWindowPrivate::clearFocusInScope(QQuickItem *scope, QQuickItem *item, Qt::FocusReason reason, FocusOptions options) |
1214 | { |
1215 | Q_Q(QQuickWindow); |
1216 | |
1217 | Q_ASSERT(item); |
1218 | Q_ASSERT(scope || item == contentItem); |
1219 | |
1220 | qCDebug(DBG_FOCUS) << "QQuickWindowPrivate::clearFocusInScope():" ; |
1221 | qCDebug(DBG_FOCUS) << " scope:" << (QObject *)scope; |
1222 | qCDebug(DBG_FOCUS) << " item:" << (QObject *)item; |
1223 | qCDebug(DBG_FOCUS) << " activeFocusItem:" << (QObject *)activeFocusItem; |
1224 | |
1225 | QQuickItemPrivate *scopePrivate = nullptr; |
1226 | if (scope) { |
1227 | scopePrivate = QQuickItemPrivate::get(item: scope); |
1228 | if ( !scopePrivate->subFocusItem ) |
1229 | return;//No focus, nothing to do. |
1230 | } |
1231 | |
1232 | QQuickItem *currentActiveFocusItem = activeFocusItem; |
1233 | QQuickItem *oldActiveFocusItem = nullptr; |
1234 | QQuickItem *newActiveFocusItem = nullptr; |
1235 | |
1236 | lastFocusReason = reason; |
1237 | |
1238 | QVarLengthArray<QQuickItem *, 20> changed; |
1239 | |
1240 | Q_ASSERT(item == contentItem || item == scopePrivate->subFocusItem); |
1241 | |
1242 | // Does this change the active focus? |
1243 | if (item == contentItem || scopePrivate->activeFocus) { |
1244 | oldActiveFocusItem = activeFocusItem; |
1245 | newActiveFocusItem = scope; |
1246 | |
1247 | #if QT_CONFIG(im) |
1248 | QGuiApplication::inputMethod()->commit(); |
1249 | #endif |
1250 | |
1251 | activeFocusItem = nullptr; |
1252 | |
1253 | if (oldActiveFocusItem) { |
1254 | QQuickItem *afi = oldActiveFocusItem; |
1255 | while (afi && afi != scope) { |
1256 | if (QQuickItemPrivate::get(item: afi)->activeFocus) { |
1257 | QQuickItemPrivate::get(item: afi)->activeFocus = false; |
1258 | changed << afi; |
1259 | } |
1260 | afi = afi->parentItem(); |
1261 | } |
1262 | } |
1263 | } |
1264 | |
1265 | if (item != contentItem && !(options & DontChangeSubFocusItem)) { |
1266 | QQuickItem *oldSubFocusItem = scopePrivate->subFocusItem; |
1267 | if (oldSubFocusItem && !(options & DontChangeFocusProperty)) { |
1268 | QQuickItemPrivate::get(item: oldSubFocusItem)->focus = false; |
1269 | changed << oldSubFocusItem; |
1270 | } |
1271 | |
1272 | QQuickItemPrivate::get(item)->updateSubFocusItem(scope, focus: false); |
1273 | |
1274 | } else if (!(options & DontChangeFocusProperty)) { |
1275 | QQuickItemPrivate::get(item)->focus = false; |
1276 | changed << item; |
1277 | } |
1278 | |
1279 | if (newActiveFocusItem) { |
1280 | Q_ASSERT(newActiveFocusItem == scope); |
1281 | activeFocusItem = scope; |
1282 | updateFocusItemTransform(); |
1283 | } |
1284 | |
1285 | // Now that all the state is changed, emit signals & events |
1286 | // We must do this last, as this process may result in further changes to |
1287 | // focus. |
1288 | if (oldActiveFocusItem) { |
1289 | QFocusEvent event(QEvent::FocusOut, reason); |
1290 | QCoreApplication::sendEvent(receiver: oldActiveFocusItem, event: &event); |
1291 | } |
1292 | |
1293 | // Make sure that the FocusOut didn't result in another focus change. |
1294 | if (newActiveFocusItem && activeFocusItem == newActiveFocusItem) { |
1295 | QFocusEvent event(QEvent::FocusIn, reason); |
1296 | QCoreApplication::sendEvent(receiver: newActiveFocusItem, event: &event); |
1297 | } |
1298 | |
1299 | if (activeFocusItem != currentActiveFocusItem) |
1300 | emit q->focusObjectChanged(object: activeFocusItem); |
1301 | |
1302 | if (!changed.isEmpty()) |
1303 | notifyFocusChangesRecur(item: changed.data(), remaining: changed.count() - 1); |
1304 | } |
1305 | |
1306 | void QQuickWindowPrivate::clearFocusObject() |
1307 | { |
1308 | if (activeFocusItem == contentItem) |
1309 | return; |
1310 | |
1311 | clearFocusInScope(scope: contentItem, item: QQuickItemPrivate::get(item: contentItem)->subFocusItem, reason: Qt::OtherFocusReason); |
1312 | } |
1313 | |
1314 | void QQuickWindowPrivate::notifyFocusChangesRecur(QQuickItem **items, int remaining) |
1315 | { |
1316 | QPointer<QQuickItem> item(*items); |
1317 | |
1318 | if (remaining) |
1319 | notifyFocusChangesRecur(items: items + 1, remaining: remaining - 1); |
1320 | |
1321 | if (item) { |
1322 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
1323 | |
1324 | if (itemPrivate->notifiedFocus != itemPrivate->focus) { |
1325 | itemPrivate->notifiedFocus = itemPrivate->focus; |
1326 | emit item->focusChanged(itemPrivate->focus); |
1327 | } |
1328 | |
1329 | if (item && itemPrivate->notifiedActiveFocus != itemPrivate->activeFocus) { |
1330 | itemPrivate->notifiedActiveFocus = itemPrivate->activeFocus; |
1331 | itemPrivate->itemChange(QQuickItem::ItemActiveFocusHasChanged, itemPrivate->activeFocus); |
1332 | emit item->activeFocusChanged(itemPrivate->activeFocus); |
1333 | } |
1334 | } |
1335 | } |
1336 | |
1337 | void QQuickWindowPrivate::dirtyItem(QQuickItem *) |
1338 | { |
1339 | Q_Q(QQuickWindow); |
1340 | q->maybeUpdate(); |
1341 | } |
1342 | |
1343 | void QQuickWindowPrivate::cleanup(QSGNode *n) |
1344 | { |
1345 | Q_Q(QQuickWindow); |
1346 | |
1347 | Q_ASSERT(!cleanupNodeList.contains(n)); |
1348 | cleanupNodeList.append(t: n); |
1349 | q->maybeUpdate(); |
1350 | } |
1351 | |
1352 | /*! |
1353 | \qmltype Window |
1354 | \instantiates QQuickWindow |
1355 | \inqmlmodule QtQuick.Window |
1356 | \ingroup qtquick-visual |
1357 | \brief Creates a new top-level window. |
1358 | |
1359 | The Window object creates a new top-level window for a Qt Quick scene. It automatically sets up the |
1360 | window for use with \c {QtQuick 2.x} graphical types. |
1361 | |
1362 | To use this type, you will need to import the module with the following line: |
1363 | \code |
1364 | import QtQuick.Window 2.2 |
1365 | \endcode |
1366 | |
1367 | Omitting this import will allow you to have a QML environment without |
1368 | access to window system features. |
1369 | |
1370 | A Window can be declared inside an Item or inside another Window; in that |
1371 | case the inner Window will automatically become "transient for" the outer |
1372 | Window: that is, most platforms will show it centered upon the outer window |
1373 | by default, and there may be other platform-dependent behaviors, depending |
1374 | also on the \l flags. If the nested window is intended to be a dialog in |
1375 | your application, you should also set \l flags to Qt.Dialog, because some |
1376 | window managers will not provide the centering behavior without that flag. |
1377 | You can also declare multiple windows inside a top-level \l QtObject, in which |
1378 | case the windows will have no transient relationship. |
1379 | |
1380 | Alternatively you can set or bind \l x and \l y to position the Window |
1381 | explicitly on the screen. |
1382 | |
1383 | When the user attempts to close a window, the \l closing signal will be |
1384 | emitted. You can force the window to stay open (for example to prompt the |
1385 | user to save changes) by writing an \c onClosing handler and setting |
1386 | \c {close.accepted = false}. |
1387 | */ |
1388 | /*! |
1389 | \class QQuickWindow |
1390 | \since 5.0 |
1391 | |
1392 | \inmodule QtQuick |
1393 | |
1394 | \brief The QQuickWindow class provides the window for displaying a graphical QML scene. |
1395 | |
1396 | QQuickWindow provides the graphical scene management needed to interact with and display |
1397 | a scene of QQuickItems. |
1398 | |
1399 | A QQuickWindow always has a single invisible root item. To add items to this window, |
1400 | reparent the items to the root item or to an existing item in the scene. |
1401 | |
1402 | For easily displaying a scene from a QML file, see \l{QQuickView}. |
1403 | |
1404 | \section1 Rendering |
1405 | |
1406 | QQuickWindow uses a scene graph to represent what needs to be rendered. |
1407 | This scene graph is disconnected from the QML scene and |
1408 | potentially lives in another thread, depending on the platform |
1409 | implementation. Since the rendering scene graph lives |
1410 | independently from the QML scene, it can also be completely |
1411 | released without affecting the state of the QML scene. |
1412 | |
1413 | The sceneGraphInitialized() signal is emitted on the rendering |
1414 | thread before the QML scene is rendered to the screen for the |
1415 | first time. If the rendering scene graph has been released, the |
1416 | signal will be emitted again before the next frame is rendered. |
1417 | |
1418 | |
1419 | \section2 Integration with OpenGL |
1420 | |
1421 | When using the default OpenGL adaptation, it is possible to integrate |
1422 | OpenGL calls directly into the QQuickWindow using the same OpenGL |
1423 | context as the Qt Quick Scene Graph. This is done by connecting to the |
1424 | QQuickWindow::beforeRendering() or QQuickWindow::afterRendering() |
1425 | signal. |
1426 | |
1427 | \note When using QQuickWindow::beforeRendering(), make sure to |
1428 | disable clearing before rendering with |
1429 | QQuickWindow::setClearBeforeRendering(). |
1430 | |
1431 | |
1432 | \section2 Exposure and Visibility |
1433 | |
1434 | When a QQuickWindow instance is deliberately hidden with hide() or |
1435 | setVisible(false), it will stop rendering and its scene graph and |
1436 | graphics context might be released. The sceneGraphInvalidated() |
1437 | signal will be emitted when this happens. |
1438 | |
1439 | \warning It is crucial that graphics operations and interaction with |
1440 | the scene graph happens exclusively on the rendering thread, |
1441 | primarily during the updatePaintNode() phase. |
1442 | |
1443 | \warning As signals related to rendering might be emitted from the |
1444 | rendering thread, connections should be made using |
1445 | Qt::DirectConnection. |
1446 | |
1447 | |
1448 | \section2 Resource Management |
1449 | |
1450 | QML will try to cache images and scene graph nodes to |
1451 | improve performance, but in some low-memory scenarios it might be |
1452 | required to aggressively release these resources. The |
1453 | releaseResources() can be used to force the clean up of certain |
1454 | resources. Calling releaseResources() may result in the entire |
1455 | scene graph and in the case of the OpenGL adaptation the associated |
1456 | context will be deleted. The sceneGraphInvalidated() signal will be |
1457 | emitted when this happens. |
1458 | |
1459 | \note All classes with QSG prefix should be used solely on the scene graph's |
1460 | rendering thread. See \l {Scene Graph and Rendering} for more information. |
1461 | |
1462 | \section2 Context and Surface Formats |
1463 | |
1464 | While it is possible to specify a QSurfaceFormat for every QQuickWindow by |
1465 | calling the member function setFormat(), windows may also be created from |
1466 | QML by using the Window and ApplicationWindow elements. In this case there |
1467 | is no C++ code involved in the creation of the window instance, yet |
1468 | applications may still wish to set certain surface format values, for |
1469 | example to request a given OpenGL version or profile. Such applications can |
1470 | call the static function QSurfaceFormat::setDefaultFormat() at startup. The |
1471 | specified format will be used for all Quick windows created afterwards. |
1472 | |
1473 | \sa {Scene Graph - OpenGL Under QML} |
1474 | */ |
1475 | |
1476 | /*! |
1477 | Constructs a window for displaying a QML scene with parent window \a parent. |
1478 | */ |
1479 | QQuickWindow::QQuickWindow(QWindow *parent) |
1480 | : QQuickWindow(*new QQuickWindowPrivate, parent) |
1481 | { |
1482 | } |
1483 | |
1484 | |
1485 | |
1486 | /*! |
1487 | \internal |
1488 | */ |
1489 | QQuickWindow::QQuickWindow(QQuickWindowPrivate &dd, QWindow *parent) |
1490 | : QWindow(dd, parent) |
1491 | { |
1492 | Q_D(QQuickWindow); |
1493 | d->init(c: this); |
1494 | } |
1495 | |
1496 | /*! |
1497 | \internal |
1498 | */ |
1499 | QQuickWindow::QQuickWindow(QQuickRenderControl *control) |
1500 | : QWindow(*(new QQuickWindowPrivate), nullptr) |
1501 | { |
1502 | Q_D(QQuickWindow); |
1503 | d->init(c: this, control); |
1504 | } |
1505 | |
1506 | /*! |
1507 | \internal |
1508 | */ |
1509 | QQuickWindow::QQuickWindow(QQuickWindowPrivate &dd, QQuickRenderControl *control) |
1510 | : QWindow(dd, nullptr) |
1511 | { |
1512 | Q_D(QQuickWindow); |
1513 | d->init(c: this, control); |
1514 | } |
1515 | |
1516 | /*! |
1517 | Destroys the window. |
1518 | */ |
1519 | QQuickWindow::~QQuickWindow() |
1520 | { |
1521 | Q_D(QQuickWindow); |
1522 | |
1523 | if (d->renderControl) { |
1524 | QQuickRenderControlPrivate::get(renderControl: d->renderControl)->windowDestroyed(); |
1525 | } else if (d->windowManager) { |
1526 | d->windowManager->removeWindow(win: this); |
1527 | d->windowManager->windowDestroyed(window: this); |
1528 | } |
1529 | |
1530 | delete d->incubationController; d->incubationController = nullptr; |
1531 | #if QT_CONFIG(quick_draganddrop) |
1532 | delete d->dragGrabber; d->dragGrabber = nullptr; |
1533 | #endif |
1534 | QQuickRootItem *root = d->contentItem; |
1535 | d->contentItem = nullptr; |
1536 | delete root; |
1537 | qDeleteAll(c: d->pointerEventInstances); |
1538 | d->pointerEventInstances.clear(); |
1539 | |
1540 | d->renderJobMutex.lock(); |
1541 | qDeleteAll(c: d->beforeSynchronizingJobs); |
1542 | d->beforeSynchronizingJobs.clear(); |
1543 | qDeleteAll(c: d->afterSynchronizingJobs); |
1544 | d->afterSynchronizingJobs.clear(); |
1545 | qDeleteAll(c: d->beforeRenderingJobs); |
1546 | d->beforeRenderingJobs.clear(); |
1547 | qDeleteAll(c: d->afterRenderingJobs); |
1548 | d->afterRenderingJobs.clear(); |
1549 | qDeleteAll(c: d->afterSwapJobs); |
1550 | d->afterSwapJobs.clear(); |
1551 | d->renderJobMutex.unlock(); |
1552 | |
1553 | // It is important that the pixmap cache is cleaned up during shutdown. |
1554 | // Besides playing nice, this also solves a practical problem that |
1555 | // QQuickTextureFactory implementations in other libraries need |
1556 | // have their destructors loaded while they the library is still |
1557 | // loaded into memory. |
1558 | QQuickPixmap::purgeCache(); |
1559 | } |
1560 | |
1561 | /*! |
1562 | This function tries to release redundant resources currently held by the QML scene. |
1563 | |
1564 | Calling this function might result in the scene graph and the OpenGL context used |
1565 | for rendering being released to release graphics memory. If this happens, the |
1566 | sceneGraphInvalidated() signal will be called, allowing users to clean up their |
1567 | own graphics resources. The setPersistentOpenGLContext() and setPersistentSceneGraph() |
1568 | functions can be used to prevent this from happening, if handling the cleanup is |
1569 | not feasible in the application, at the cost of higher memory usage. |
1570 | |
1571 | \sa sceneGraphInvalidated(), setPersistentOpenGLContext(), setPersistentSceneGraph() |
1572 | */ |
1573 | |
1574 | void QQuickWindow::releaseResources() |
1575 | { |
1576 | Q_D(QQuickWindow); |
1577 | if (d->windowManager) |
1578 | d->windowManager->releaseResources(window: this); |
1579 | QQuickPixmap::purgeCache(); |
1580 | } |
1581 | |
1582 | |
1583 | |
1584 | /*! |
1585 | Sets whether the OpenGL context should be preserved, and cannot be |
1586 | released until the last window is deleted, to \a persistent. The |
1587 | default value is true. |
1588 | |
1589 | The OpenGL context can be released to free up graphics resources |
1590 | when the window is obscured, hidden or not rendering. When this |
1591 | happens is implementation specific. |
1592 | |
1593 | The QOpenGLContext::aboutToBeDestroyed() signal is emitted from |
1594 | the QQuickWindow::openglContext() when the OpenGL context is about |
1595 | to be released. The QQuickWindow::sceneGraphInitialized() signal |
1596 | is emitted when a new OpenGL context is created for this |
1597 | window. Make a Qt::DirectConnection to these signals to be |
1598 | notified. |
1599 | |
1600 | The OpenGL context is still released when the last QQuickWindow is |
1601 | deleted. |
1602 | |
1603 | \note This only has an effect when using the default OpenGL scene |
1604 | graph adaptation. |
1605 | |
1606 | \sa setPersistentSceneGraph(), |
1607 | QOpenGLContext::aboutToBeDestroyed(), sceneGraphInitialized() |
1608 | */ |
1609 | |
1610 | void QQuickWindow::setPersistentOpenGLContext(bool persistent) |
1611 | { |
1612 | Q_D(QQuickWindow); |
1613 | d->persistentGLContext = persistent; |
1614 | } |
1615 | |
1616 | |
1617 | |
1618 | /*! |
1619 | Returns whether the OpenGL context can be released during the |
1620 | lifetime of the QQuickWindow. |
1621 | |
1622 | \note This is a hint. When and how this happens is implementation |
1623 | specific. It also only has an effect when using the default OpenGL |
1624 | scene graph adaptation |
1625 | */ |
1626 | |
1627 | bool QQuickWindow::isPersistentOpenGLContext() const |
1628 | { |
1629 | Q_D(const QQuickWindow); |
1630 | return d->persistentGLContext; |
1631 | } |
1632 | |
1633 | |
1634 | |
1635 | /*! |
1636 | Sets whether the scene graph nodes and resources are \a persistent. |
1637 | Persistent means the nodes and resources cannot be released. |
1638 | The default value is \c true. |
1639 | |
1640 | The scene graph nodes and resources can be released to free up |
1641 | graphics resources when the window is obscured, hidden or not |
1642 | rendering. When this happens is implementation specific. |
1643 | |
1644 | The QQuickWindow::sceneGraphInvalidated() signal is emitted when |
1645 | cleanup occurs. The QQuickWindow::sceneGraphInitialized() signal |
1646 | is emitted when a new scene graph is recreated for this |
1647 | window. Make a Qt::DirectConnection to these signals to be |
1648 | notified. |
1649 | |
1650 | The scene graph nodes and resources are still released when the |
1651 | last QQuickWindow is deleted. |
1652 | |
1653 | \sa setPersistentOpenGLContext(), |
1654 | sceneGraphInvalidated(), sceneGraphInitialized() |
1655 | */ |
1656 | |
1657 | void QQuickWindow::setPersistentSceneGraph(bool persistent) |
1658 | { |
1659 | Q_D(QQuickWindow); |
1660 | d->persistentSceneGraph = persistent; |
1661 | } |
1662 | |
1663 | |
1664 | |
1665 | /*! |
1666 | Returns whether the scene graph nodes and resources can be |
1667 | released during the lifetime of this QQuickWindow. |
1668 | |
1669 | \note This is a hint. When and how this happens is implementation |
1670 | specific. |
1671 | */ |
1672 | |
1673 | bool QQuickWindow::isPersistentSceneGraph() const |
1674 | { |
1675 | Q_D(const QQuickWindow); |
1676 | return d->persistentSceneGraph; |
1677 | } |
1678 | |
1679 | |
1680 | |
1681 | |
1682 | /*! |
1683 | \qmlattachedproperty Item Window::contentItem |
1684 | \since 5.4 |
1685 | |
1686 | This attached property holds the invisible root item of the scene or |
1687 | \c null if the item is not in a window. The Window attached property |
1688 | can be attached to any Item. |
1689 | */ |
1690 | |
1691 | /*! |
1692 | \property QQuickWindow::contentItem |
1693 | \brief The invisible root item of the scene. |
1694 | |
1695 | A QQuickWindow always has a single invisible root item containing all of its content. |
1696 | To add items to this window, reparent the items to the contentItem or to an existing |
1697 | item in the scene. |
1698 | */ |
1699 | QQuickItem *QQuickWindow::contentItem() const |
1700 | { |
1701 | Q_D(const QQuickWindow); |
1702 | |
1703 | return d->contentItem; |
1704 | } |
1705 | |
1706 | /*! |
1707 | \property QQuickWindow::activeFocusItem |
1708 | |
1709 | \brief The item which currently has active focus or \c null if there is |
1710 | no item with active focus. |
1711 | |
1712 | \sa QQuickItem::forceActiveFocus(), {Keyboard Focus in Qt Quick} |
1713 | */ |
1714 | QQuickItem *QQuickWindow::activeFocusItem() const |
1715 | { |
1716 | Q_D(const QQuickWindow); |
1717 | |
1718 | return d->activeFocusItem; |
1719 | } |
1720 | |
1721 | /*! |
1722 | \internal |
1723 | \reimp |
1724 | */ |
1725 | QObject *QQuickWindow::focusObject() const |
1726 | { |
1727 | Q_D(const QQuickWindow); |
1728 | |
1729 | if (d->activeFocusItem) |
1730 | return d->activeFocusItem; |
1731 | return const_cast<QQuickWindow*>(this); |
1732 | } |
1733 | |
1734 | |
1735 | /*! |
1736 | Returns the item which currently has the mouse grab. |
1737 | */ |
1738 | QQuickItem *QQuickWindow::mouseGrabberItem() const |
1739 | { |
1740 | Q_D(const QQuickWindow); |
1741 | |
1742 | if (d->isDeliveringTouchAsMouse()) { |
1743 | if (QQuickPointerEvent *event = d->queryPointerEventInstance(device: d->touchMouseDevice)) { |
1744 | auto point = event->pointById(pointId: d->touchMouseId); |
1745 | return point ? point->grabberItem() : nullptr; |
1746 | } |
1747 | } else if (QQuickPointerEvent *event = d->queryPointerEventInstance(device: QQuickPointerDevice::genericMouseDevice())) { |
1748 | Q_ASSERT(event->pointCount()); |
1749 | return event->point(i: 0)->grabberItem(); |
1750 | } |
1751 | return nullptr; |
1752 | } |
1753 | |
1754 | |
1755 | bool QQuickWindowPrivate::clearHover(ulong timestamp) |
1756 | { |
1757 | Q_Q(QQuickWindow); |
1758 | if (hoverItems.isEmpty()) |
1759 | return false; |
1760 | |
1761 | QPointF pos = q->mapFromGlobal(pos: QGuiApplicationPrivate::lastCursorPosition.toPoint()); |
1762 | |
1763 | bool accepted = false; |
1764 | for (QQuickItem* item : qAsConst(t&: hoverItems)) { |
1765 | accepted = sendHoverEvent(QEvent::HoverLeave, item, scenePos: pos, lastScenePos: pos, modifiers: QGuiApplication::keyboardModifiers(), timestamp, accepted: true) || accepted; |
1766 | #if QT_CONFIG(cursor) |
1767 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
1768 | if (itemPrivate->hasPointerHandlers()) { |
1769 | pos = q->mapFromGlobal(pos: QCursor::pos()); |
1770 | QQuickPointerEvent *pointerEvent = pointerEventInstance(device: QQuickPointerDevice::genericMouseDevice(), eventType: QEvent::MouseMove); |
1771 | pointerEvent->point(i: 0)->reset(state: Qt::TouchPointMoved, scenePosition: pos, pointId: quint64(1) << 24 /* mouse has device ID 1 */, timestamp, velocity: QVector2D()); |
1772 | pointerEvent->point(i: 0)->setAccepted(true); |
1773 | pointerEvent->localize(target: item); |
1774 | for (QQuickPointerHandler *h : itemPrivate->extra->pointerHandlers) |
1775 | if (QQuickHoverHandler *hh = qmlobject_cast<QQuickHoverHandler *>(object: h)) |
1776 | hh->handlePointerEvent(event: pointerEvent); |
1777 | } |
1778 | #endif |
1779 | } |
1780 | hoverItems.clear(); |
1781 | return accepted; |
1782 | } |
1783 | |
1784 | /*! \reimp */ |
1785 | bool QQuickWindow::event(QEvent *e) |
1786 | { |
1787 | Q_D(QQuickWindow); |
1788 | |
1789 | switch (e->type()) { |
1790 | |
1791 | case QEvent::TouchBegin: |
1792 | case QEvent::TouchUpdate: |
1793 | case QEvent::TouchEnd: { |
1794 | QTouchEvent *touch = static_cast<QTouchEvent*>(e); |
1795 | d->handleTouchEvent(touch); |
1796 | if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents))) { |
1797 | // we consume all touch events ourselves to avoid duplicate |
1798 | // mouse delivery by QtGui mouse synthesis |
1799 | e->accept(); |
1800 | } |
1801 | return true; |
1802 | } |
1803 | break; |
1804 | case QEvent::TouchCancel: |
1805 | // return in order to avoid the QWindow::event below |
1806 | return d->deliverTouchCancelEvent(static_cast<QTouchEvent*>(e)); |
1807 | break; |
1808 | case QEvent::Enter: { |
1809 | if (!d->contentItem) |
1810 | return false; |
1811 | QEnterEvent *enter = static_cast<QEnterEvent*>(e); |
1812 | bool accepted = enter->isAccepted(); |
1813 | bool delivered = d->deliverHoverEvent(d->contentItem, scenePos: enter->windowPos(), lastScenePos: d->lastMousePosition, |
1814 | modifiers: QGuiApplication::keyboardModifiers(), timestamp: 0L, accepted); |
1815 | d->lastMousePosition = enter->windowPos(); |
1816 | enter->setAccepted(accepted); |
1817 | #if QT_CONFIG(cursor) |
1818 | d->updateCursor(scenePos: mapFromGlobal(pos: QCursor::pos())); |
1819 | #endif |
1820 | return delivered; |
1821 | } |
1822 | break; |
1823 | case QEvent::Leave: |
1824 | d->clearHover(); |
1825 | d->lastMousePosition = QPointF(); |
1826 | break; |
1827 | #if QT_CONFIG(quick_draganddrop) |
1828 | case QEvent::DragEnter: |
1829 | case QEvent::DragLeave: |
1830 | case QEvent::DragMove: |
1831 | case QEvent::Drop: |
1832 | d->deliverDragEvent(d->dragGrabber, e); |
1833 | break; |
1834 | #endif |
1835 | case QEvent::WindowDeactivate: |
1836 | if (d->contentItem) |
1837 | d->contentItem->windowDeactivateEvent(); |
1838 | break; |
1839 | case QEvent::Close: { |
1840 | // TOOD Qt 6 (binary incompatible) |
1841 | // closeEvent(static_cast<QCloseEvent *>(e)); |
1842 | QQuickCloseEvent qev; |
1843 | qev.setAccepted(e->isAccepted()); |
1844 | emit closing(close: &qev); |
1845 | e->setAccepted(qev.isAccepted()); |
1846 | } break; |
1847 | case QEvent::PlatformSurface: |
1848 | if ((static_cast<QPlatformSurfaceEvent *>(e))->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) { |
1849 | // Ensure that the rendering thread is notified before |
1850 | // the QPlatformWindow is destroyed. |
1851 | if (d->windowManager) |
1852 | d->windowManager->hide(window: this); |
1853 | } |
1854 | break; |
1855 | case QEvent::FocusAboutToChange: |
1856 | #if QT_CONFIG(im) |
1857 | if (d->activeFocusItem) |
1858 | qGuiApp->inputMethod()->commit(); |
1859 | #endif |
1860 | if (mouseGrabberItem()) |
1861 | mouseGrabberItem()->ungrabMouse(); |
1862 | break; |
1863 | case QEvent::UpdateRequest: { |
1864 | if (d->windowManager) |
1865 | d->windowManager->handleUpdateRequest(this); |
1866 | break; |
1867 | } |
1868 | #if QT_CONFIG(gestures) |
1869 | case QEvent::NativeGesture: |
1870 | d->deliverSinglePointEventUntilAccepted(d->pointerEventInstance(ev: e)); |
1871 | break; |
1872 | #endif |
1873 | case QEvent::ShortcutOverride: |
1874 | if (d->activeFocusItem) |
1875 | QCoreApplication::sendEvent(receiver: d->activeFocusItem, event: e); |
1876 | return true; |
1877 | case QEvent::LanguageChange: |
1878 | if (d->contentItem) |
1879 | QCoreApplication::sendEvent(receiver: d->contentItem, event: e); |
1880 | break; |
1881 | case QEvent::InputMethod: |
1882 | case QEvent::InputMethodQuery: |
1883 | { |
1884 | QQuickItem *target = d->activeFocusItem; |
1885 | // while an input method delivers the event, this window might still be inactive |
1886 | if (!target) { |
1887 | target = d->contentItem; |
1888 | if (!target || !target->isEnabled()) |
1889 | break; |
1890 | // see setFocusInScope for a similar loop |
1891 | while (target->isFocusScope() && target->scopedFocusItem() && target->scopedFocusItem()->isEnabled()) |
1892 | target = target->scopedFocusItem(); |
1893 | } |
1894 | if (target) { |
1895 | QCoreApplication::sendEvent(receiver: target, event: e); |
1896 | return true; |
1897 | } |
1898 | } |
1899 | break; |
1900 | default: |
1901 | break; |
1902 | } |
1903 | |
1904 | if (e->type() == QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)) |
1905 | update(); |
1906 | else if (e->type() == QEvent::Type(QQuickWindowPrivate::TriggerContextCreationFailure)) |
1907 | d->windowManager->handleContextCreationFailure(window: this); |
1908 | |
1909 | return QWindow::event(e); |
1910 | } |
1911 | |
1912 | /*! \reimp */ |
1913 | void QQuickWindow::keyPressEvent(QKeyEvent *e) |
1914 | { |
1915 | Q_D(QQuickWindow); |
1916 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyPress, e->key(), |
1917 | e->modifiers()); |
1918 | d->deliverKeyEvent(e); |
1919 | } |
1920 | |
1921 | /*! \reimp */ |
1922 | void QQuickWindow::keyReleaseEvent(QKeyEvent *e) |
1923 | { |
1924 | Q_D(QQuickWindow); |
1925 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Key, QQuickProfiler::InputKeyRelease, e->key(), |
1926 | e->modifiers()); |
1927 | d->deliverKeyEvent(e); |
1928 | } |
1929 | |
1930 | void QQuickWindowPrivate::deliverKeyEvent(QKeyEvent *e) |
1931 | { |
1932 | if (activeFocusItem) { |
1933 | QQuickItem *item = activeFocusItem; |
1934 | |
1935 | // In case of generated event, trigger ShortcutOverride event |
1936 | if (e->type() == QEvent::KeyPress && e->spontaneous() == false) |
1937 | qt_sendShortcutOverrideEvent(o: item, timestamp: e->timestamp(), |
1938 | k: e->key(), mods: e->modifiers(), text: e->text(), |
1939 | autorep: e->isAutoRepeat(), count: e->count()); |
1940 | |
1941 | e->accept(); |
1942 | QCoreApplication::sendEvent(receiver: item, event: e); |
1943 | while (!e->isAccepted() && (item = item->parentItem())) { |
1944 | e->accept(); |
1945 | QCoreApplication::sendEvent(receiver: item, event: e); |
1946 | } |
1947 | } |
1948 | } |
1949 | |
1950 | QMouseEvent *QQuickWindowPrivate::cloneMouseEvent(QMouseEvent *event, QPointF *transformedLocalPos) |
1951 | { |
1952 | int caps = QGuiApplicationPrivate::mouseEventCaps(event); |
1953 | QVector2D velocity = QGuiApplicationPrivate::mouseEventVelocity(event); |
1954 | QMouseEvent *me = new QMouseEvent(event->type(), |
1955 | transformedLocalPos ? *transformedLocalPos : event->localPos(), |
1956 | event->windowPos(), event->screenPos(), |
1957 | event->button(), event->buttons(), event->modifiers()); |
1958 | QGuiApplicationPrivate::setMouseEventCapsAndVelocity(event: me, caps, velocity); |
1959 | QGuiApplicationPrivate::setMouseEventSource(event: me, source: QGuiApplicationPrivate::mouseEventSource(event)); |
1960 | me->setTimestamp(event->timestamp()); |
1961 | return me; |
1962 | } |
1963 | |
1964 | void QQuickWindowPrivate::deliverToPassiveGrabbers(const QVector<QPointer <QQuickPointerHandler> > &passiveGrabbers, |
1965 | QQuickPointerEvent *pointerEvent) |
1966 | { |
1967 | const QVector<QQuickPointerHandler *> &eventDeliveryTargets = pointerEvent->device()->eventDeliveryTargets(); |
1968 | QVarLengthArray<QPair<QQuickItem *, bool>, 4> sendFilteredPointerEventResult; |
1969 | hasFiltered.clear(); |
1970 | for (auto handler : passiveGrabbers) { |
1971 | // a null pointer in passiveGrabbers is unlikely, unless the grabbing handler was deleted dynamically |
1972 | if (Q_LIKELY(handler) && !eventDeliveryTargets.contains(t: handler)) { |
1973 | bool alreadyFiltered = false; |
1974 | QQuickItem *par = handler->parentItem(); |
1975 | |
1976 | // see if we already have sent a filter event to the parent |
1977 | auto it = std::find_if(first: sendFilteredPointerEventResult.begin(), last: sendFilteredPointerEventResult.end(), |
1978 | pred: [par](const QPair<QQuickItem *, bool> &pair) { return pair.first == par; }); |
1979 | if (it != sendFilteredPointerEventResult.end()) { |
1980 | // Yes, the event was already filtered to that parent, do not call it again but use |
1981 | // the result of the previous call to determine if we should call the handler. |
1982 | alreadyFiltered = it->second; |
1983 | } else { |
1984 | alreadyFiltered = sendFilteredPointerEvent(event: pointerEvent, receiver: par); |
1985 | sendFilteredPointerEventResult << qMakePair<QQuickItem*, bool>(x: par, y: alreadyFiltered); |
1986 | } |
1987 | if (!alreadyFiltered) { |
1988 | pointerEvent->localize(target: handler->parentItem()); |
1989 | handler->handlePointerEvent(event: pointerEvent); |
1990 | } |
1991 | } |
1992 | } |
1993 | } |
1994 | |
1995 | |
1996 | |
1997 | void QQuickWindowPrivate::deliverMouseEvent(QQuickPointerMouseEvent *pointerEvent) |
1998 | { |
1999 | Q_Q(QQuickWindow); |
2000 | auto point = pointerEvent->point(i: 0); |
2001 | lastMousePosition = point->scenePosition(); |
2002 | const bool mouseIsReleased = (point->state() == QQuickEventPoint::Released && pointerEvent->buttons() == Qt::NoButton); |
2003 | QQuickItem *grabberItem = point->grabberItem(); |
2004 | if (!grabberItem && isDeliveringTouchAsMouse()) |
2005 | grabberItem = q->mouseGrabberItem(); |
2006 | |
2007 | if (grabberItem) { |
2008 | bool handled = false; |
2009 | hasFiltered.clear(); |
2010 | if (sendFilteredPointerEvent(event: pointerEvent, receiver: grabberItem)) |
2011 | handled = true; |
2012 | // if the grabber is an Item: |
2013 | // if the update consists of changing button state, don't accept it unless |
2014 | // the button is one in which the grabber is interested |
2015 | Qt::MouseButtons acceptedButtons = grabberItem->acceptedMouseButtons(); |
2016 | if (!handled && pointerEvent->button() != Qt::NoButton && acceptedButtons |
2017 | && !(acceptedButtons & pointerEvent->button())) { |
2018 | pointerEvent->setAccepted(false); |
2019 | handled = true; |
2020 | } |
2021 | |
2022 | // send update |
2023 | if (!handled) { |
2024 | QPointF localPos = grabberItem->mapFromScene(point: lastMousePosition); |
2025 | auto me = pointerEvent->asMouseEvent(localPos); |
2026 | me->accept(); |
2027 | QCoreApplication::sendEvent(receiver: grabberItem, event: me); |
2028 | point->setAccepted(me->isAccepted()); |
2029 | } |
2030 | |
2031 | // release event: ungrab if no buttons are pressed anymore |
2032 | if (mouseIsReleased) |
2033 | removeGrabber(grabber: grabberItem, mouse: true, touch: isDeliveringTouchAsMouse()); |
2034 | deliverToPassiveGrabbers(passiveGrabbers: point->passiveGrabbers(), pointerEvent); |
2035 | } else if (auto handler = point->grabberPointerHandler()) { |
2036 | pointerEvent->localize(target: handler->parentItem()); |
2037 | hasFiltered.clear(); |
2038 | if (!sendFilteredPointerEvent(event: pointerEvent, receiver: handler->parentItem())) |
2039 | handler->handlePointerEvent(event: pointerEvent); |
2040 | if (mouseIsReleased) |
2041 | point->setGrabberPointerHandler(exclusiveGrabber: nullptr, exclusive: true); |
2042 | deliverToPassiveGrabbers(passiveGrabbers: point->passiveGrabbers(), pointerEvent); |
2043 | } else { |
2044 | bool delivered = false; |
2045 | if (pointerEvent->isPressEvent()) { |
2046 | // send initial press |
2047 | delivered = deliverPressOrReleaseEvent(pointerEvent); |
2048 | } else if (pointerEvent->device()->type() == QQuickPointerDevice::Mouse) { |
2049 | // if this is an update or release from an actual mouse, |
2050 | // and the point wasn't grabbed, deliver only to PointerHandlers: |
2051 | // passive grabbers first, then the rest |
2052 | deliverToPassiveGrabbers(passiveGrabbers: point->passiveGrabbers(), pointerEvent); |
2053 | |
2054 | // If some points weren't grabbed, deliver to non-grabber PointerHandlers in reverse paint order |
2055 | if (!pointerEvent->allPointsGrabbed() && pointerEvent->buttons()) { |
2056 | QVector<QQuickItem *> targetItems = pointerTargets(contentItem, point, checkMouseButtons: false, checkAcceptsTouch: false); |
2057 | for (QQuickItem *item : targetItems) { |
2058 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
2059 | if (!itemPrivate->extra.isAllocated() || itemPrivate->extra->pointerHandlers.isEmpty()) |
2060 | continue; |
2061 | pointerEvent->localize(target: item); |
2062 | hasFiltered.clear(); |
2063 | if (!sendFilteredPointerEvent(event: pointerEvent, receiver: item)) { |
2064 | if (itemPrivate->handlePointerEvent(pointerEvent, avoidExclusiveGrabber: true)) // avoid re-delivering to grabbers |
2065 | delivered = true; |
2066 | } |
2067 | if (point->exclusiveGrabber()) |
2068 | break; |
2069 | } |
2070 | } |
2071 | } |
2072 | |
2073 | if (!delivered) |
2074 | // make sure not to accept unhandled events |
2075 | pointerEvent->setAccepted(false); |
2076 | } |
2077 | } |
2078 | |
2079 | bool QQuickWindowPrivate::sendHoverEvent(QEvent::Type type, QQuickItem *item, |
2080 | const QPointF &scenePos, const QPointF &lastScenePos, |
2081 | Qt::KeyboardModifiers modifiers, ulong timestamp, |
2082 | bool accepted) |
2083 | { |
2084 | const QTransform transform = QQuickItemPrivate::get(item)->windowToItemTransform(); |
2085 | |
2086 | //create copy of event |
2087 | QHoverEvent hoverEvent(type, transform.map(p: scenePos), transform.map(p: lastScenePos), modifiers); |
2088 | hoverEvent.setTimestamp(timestamp); |
2089 | hoverEvent.setAccepted(accepted); |
2090 | |
2091 | hasFiltered.clear(); |
2092 | if (sendFilteredMouseEvent(event: &hoverEvent, receiver: item, filteringParent: item->parentItem())) |
2093 | return true; |
2094 | |
2095 | QCoreApplication::sendEvent(receiver: item, event: &hoverEvent); |
2096 | |
2097 | return hoverEvent.isAccepted(); |
2098 | } |
2099 | |
2100 | bool QQuickWindowPrivate::deliverHoverEvent(QQuickItem *item, const QPointF &scenePos, const QPointF &lastScenePos, |
2101 | Qt::KeyboardModifiers modifiers, ulong timestamp, bool &accepted) |
2102 | { |
2103 | Q_Q(QQuickWindow); |
2104 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
2105 | |
2106 | if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { |
2107 | QPointF p = item->mapFromScene(point: scenePos); |
2108 | if (!item->contains(point: p)) |
2109 | return false; |
2110 | } |
2111 | |
2112 | qCDebug(DBG_HOVER_TRACE) << q << item << scenePos << lastScenePos << "subtreeHoverEnabled" << itemPrivate->subtreeHoverEnabled; |
2113 | if (itemPrivate->subtreeHoverEnabled) { |
2114 | QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); |
2115 | for (int ii = children.count() - 1; ii >= 0; --ii) { |
2116 | QQuickItem *child = children.at(i: ii); |
2117 | if (!child->isVisible() || !child->isEnabled() || QQuickItemPrivate::get(item: child)->culled) |
2118 | continue; |
2119 | if (deliverHoverEvent(item: child, scenePos, lastScenePos, modifiers, timestamp, accepted)) |
2120 | return true; |
2121 | } |
2122 | } |
2123 | |
2124 | if (itemPrivate->hasPointerHandlers()) { |
2125 | QQuickPointerEvent *pointerEvent = pointerEventInstance(device: QQuickPointerDevice::genericMouseDevice(), eventType: QEvent::MouseMove); |
2126 | pointerEvent->point(i: 0)->reset(state: Qt::TouchPointMoved, scenePosition: scenePos, pointId: quint64(1) << 24 /* mouse has device ID 1 */, timestamp, velocity: QVector2D()); |
2127 | pointerEvent->point(i: 0)->setAccepted(true); |
2128 | pointerEvent->localize(target: item); |
2129 | for (QQuickPointerHandler *h : itemPrivate->extra->pointerHandlers) |
2130 | if (QQuickHoverHandler *hh = qmlobject_cast<QQuickHoverHandler *>(object: h)) |
2131 | hh->handlePointerEvent(event: pointerEvent); |
2132 | } |
2133 | |
2134 | if (itemPrivate->hoverEnabled) { |
2135 | QPointF p = item->mapFromScene(point: scenePos); |
2136 | if (item->contains(point: p)) { |
2137 | if (!hoverItems.isEmpty() && hoverItems.at(i: 0) == item) { |
2138 | //move |
2139 | accepted = sendHoverEvent(type: QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, timestamp, accepted); |
2140 | } else { |
2141 | QList<QQuickItem *> itemsToHover; |
2142 | QQuickItem* parent = item; |
2143 | itemsToHover << item; |
2144 | while ((parent = parent->parentItem())) |
2145 | itemsToHover << parent; |
2146 | |
2147 | // Leaving from previous hovered items until we reach the item or one of its ancestors. |
2148 | while (!hoverItems.isEmpty() && !itemsToHover.contains(t: hoverItems.at(i: 0))) { |
2149 | QQuickItem *hoverLeaveItem = hoverItems.takeFirst(); |
2150 | sendHoverEvent(type: QEvent::HoverLeave, item: hoverLeaveItem, scenePos, lastScenePos, modifiers, timestamp, accepted); |
2151 | QQuickItemPrivate *hoverLeaveItemPrivate = QQuickItemPrivate::get(item: hoverLeaveItem); |
2152 | if (hoverLeaveItemPrivate->hasPointerHandlers()) { |
2153 | for (QQuickPointerHandler *handler : hoverLeaveItemPrivate->extra->pointerHandlers) { |
2154 | if (auto *hh = qmlobject_cast<QQuickHoverHandler *>(object: handler)) { |
2155 | QQuickPointerEvent *pointerEvent = pointerEventInstance(device: QQuickPointerDevice::genericMouseDevice(), eventType: QEvent::MouseMove); |
2156 | pointerEvent->point(i: 0)->cancelPassiveGrab(handler: hh); |
2157 | } |
2158 | } |
2159 | } |
2160 | } |
2161 | |
2162 | if (!hoverItems.isEmpty() && hoverItems.at(i: 0) == item) {//Not entering a new Item |
2163 | // ### Shouldn't we send moves for the parent items as well? |
2164 | accepted = sendHoverEvent(type: QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, timestamp, accepted); |
2165 | } else { |
2166 | // Enter items that are not entered yet. |
2167 | int startIdx = -1; |
2168 | if (!hoverItems.isEmpty()) |
2169 | startIdx = itemsToHover.indexOf(t: hoverItems.at(i: 0)) - 1; |
2170 | if (startIdx == -1) |
2171 | startIdx = itemsToHover.count() - 1; |
2172 | |
2173 | for (int i = startIdx; i >= 0; i--) { |
2174 | QQuickItem *itemToHover = itemsToHover.at(i); |
2175 | QQuickItemPrivate *itemToHoverPrivate = QQuickItemPrivate::get(item: itemToHover); |
2176 | // The item may be about to be deleted or reparented to another window |
2177 | // due to another hover event delivered in this function. If that is the |
2178 | // case, sending a hover event here will cause a crash or other bad |
2179 | // behavior when the leave event is generated. Checking |
2180 | // itemToHoverPrivate->window here prevents that case. |
2181 | if (itemToHoverPrivate->window == q && itemToHoverPrivate->hoverEnabled) { |
2182 | hoverItems.prepend(t: itemToHover); |
2183 | sendHoverEvent(type: QEvent::HoverEnter, item: itemToHover, scenePos, lastScenePos, modifiers, timestamp, accepted); |
2184 | } |
2185 | } |
2186 | } |
2187 | } |
2188 | return true; |
2189 | } |
2190 | } |
2191 | |
2192 | return false; |
2193 | } |
2194 | |
2195 | // Simple delivery of non-mouse, non-touch Pointer Events: visit the items and handlers |
2196 | // in the usual reverse-paint-order until propagation is stopped |
2197 | bool QQuickWindowPrivate::deliverSinglePointEventUntilAccepted(QQuickPointerEvent *event) |
2198 | { |
2199 | Q_ASSERT(event->pointCount() == 1); |
2200 | QQuickEventPoint *point = event->point(i: 0); |
2201 | QVector<QQuickItem *> targetItems = pointerTargets(contentItem, point, checkMouseButtons: false, checkAcceptsTouch: false); |
2202 | |
2203 | for (QQuickItem *item : targetItems) { |
2204 | if (!item->window()) |
2205 | continue; |
2206 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
2207 | event->localize(target: item); |
2208 | // Let Pointer Handlers have the first shot |
2209 | itemPrivate->handlePointerEvent(event); |
2210 | if (point->isAccepted()) |
2211 | return true; |
2212 | QPointF g = item->window()->mapToGlobal(pos: point->scenePosition().toPoint()); |
2213 | #if QT_CONFIG(wheelevent) |
2214 | // Let the Item have a chance to handle it |
2215 | if (QQuickPointerScrollEvent *pse = event->asPointerScrollEvent()) { |
2216 | QWheelEvent wheel(point->position(), g, pse->pixelDelta().toPoint(), pse->angleDelta().toPoint(), |
2217 | pse->buttons(), pse->modifiers(), pse->phase(), |
2218 | pse->isInverted(), pse->synthSource()); |
2219 | wheel.setTimestamp(pse->timestamp()); |
2220 | wheel.accept(); |
2221 | QCoreApplication::sendEvent(receiver: item, event: &wheel); |
2222 | if (wheel.isAccepted()) { |
2223 | qCDebug(lcWheelTarget) << &wheel << "->" << item; |
2224 | event->setAccepted(true); |
2225 | return true; |
2226 | } |
2227 | } |
2228 | #endif |
2229 | #if QT_CONFIG(gestures) |
2230 | if (QQuickPointerNativeGestureEvent *pnge = event->asPointerNativeGestureEvent()) { |
2231 | QNativeGestureEvent nge(pnge->type(), pnge->device()->qTouchDevice(), point->position(), point->scenePosition(), g, |
2232 | pnge->value(), 0L, 0L); // TODO can't copy things I can't access |
2233 | nge.accept(); |
2234 | QCoreApplication::sendEvent(receiver: item, event: &nge); |
2235 | if (nge.isAccepted()) { |
2236 | qCDebug(lcGestureTarget) << &nge << "->" << item; |
2237 | event->setAccepted(true); |
2238 | return true; |
2239 | } |
2240 | } |
2241 | #endif // gestures |
2242 | } |
2243 | |
2244 | return false; // it wasn't handled |
2245 | } |
2246 | |
2247 | #if QT_CONFIG(wheelevent) |
2248 | /*! \reimp */ |
2249 | void QQuickWindow::wheelEvent(QWheelEvent *event) |
2250 | { |
2251 | Q_D(QQuickWindow); |
2252 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseWheel, |
2253 | event->angleDelta().x(), event->angleDelta().y()); |
2254 | |
2255 | qCDebug(DBG_MOUSE) << "QQuickWindow::wheelEvent()" << event->pixelDelta() << event->angleDelta() << event->phase(); |
2256 | |
2257 | //if the actual wheel event was accepted, accept the compatibility wheel event and return early |
2258 | if (d->lastWheelEventAccepted && event->angleDelta().isNull() && event->phase() == Qt::ScrollUpdate) |
2259 | return; |
2260 | |
2261 | event->ignore(); |
2262 | d->deliverPointerEvent(d->pointerEventInstance(ev: event)); |
2263 | d->lastWheelEventAccepted = event->isAccepted(); |
2264 | } |
2265 | #endif // wheelevent |
2266 | |
2267 | #if QT_CONFIG(tabletevent) |
2268 | /*! \reimp */ |
2269 | void QQuickWindow::tabletEvent(QTabletEvent *event) |
2270 | { |
2271 | Q_D(QQuickWindow); |
2272 | qCDebug(lcTablet) << event; |
2273 | // TODO Qt 6: make sure TabletEnterProximity and TabletLeaveProximity are delivered here |
2274 | d->deliverPointerEvent(d->pointerEventInstance(ev: event)); |
2275 | } |
2276 | #endif // tabletevent |
2277 | |
2278 | bool QQuickWindowPrivate::deliverTouchCancelEvent(QTouchEvent *event) |
2279 | { |
2280 | qCDebug(DBG_TOUCH) << event; |
2281 | Q_Q(QQuickWindow); |
2282 | |
2283 | if (QQuickItem *grabber = q->mouseGrabberItem()) |
2284 | sendUngrabEvent(grabber, touch: true); |
2285 | cancelTouchMouseSynthesis(); |
2286 | |
2287 | // A TouchCancel event will typically not contain any points. |
2288 | // Deliver it to all items and handlers that have active touches. |
2289 | QQuickPointerEvent *pointerEvent = pointerEventInstance(device: QQuickPointerDevice::touchDevice(d: event->device())); |
2290 | for (int i = 0; i < pointerEvent->pointCount(); ++i) |
2291 | pointerEvent->point(i)->cancelExclusiveGrabImpl(cancelEvent: event); |
2292 | |
2293 | // The next touch event can only be a TouchBegin, so clean up. |
2294 | pointerEvent->clearGrabbers(); |
2295 | return true; |
2296 | } |
2297 | |
2298 | void QQuickWindowPrivate::deliverDelayedTouchEvent() |
2299 | { |
2300 | // Deliver and delete delayedTouch. |
2301 | // Set delayedTouch to 0 before delivery to avoid redelivery in case of |
2302 | // event loop recursions (e.g if it the touch starts a dnd session). |
2303 | QScopedPointer<QTouchEvent> e(delayedTouch.take()); |
2304 | deliverPointerEvent(pointerEventInstance(ev: e.data())); |
2305 | } |
2306 | |
2307 | bool QQuickWindowPrivate::compressTouchEvent(QTouchEvent *event) |
2308 | { |
2309 | Q_Q(QQuickWindow); |
2310 | Qt::TouchPointStates states = event->touchPointStates(); |
2311 | if (((states & (Qt::TouchPointMoved | Qt::TouchPointStationary)) == 0) |
2312 | || ((states & (Qt::TouchPointPressed | Qt::TouchPointReleased)) != 0)) { |
2313 | // we can only compress something that isn't a press or release |
2314 | return false; |
2315 | } |
2316 | |
2317 | if (!delayedTouch) { |
2318 | delayedTouch.reset(other: new QTouchEvent(event->type(), event->device(), event->modifiers(), event->touchPointStates(), event->touchPoints())); |
2319 | delayedTouch->setTimestamp(event->timestamp()); |
2320 | if (renderControl) |
2321 | QQuickRenderControlPrivate::get(renderControl)->maybeUpdate(); |
2322 | else if (windowManager) |
2323 | windowManager->maybeUpdate(window: q); |
2324 | return true; |
2325 | } |
2326 | |
2327 | // check if this looks like the last touch event |
2328 | if (delayedTouch->type() == event->type() && |
2329 | delayedTouch->device() == event->device() && |
2330 | delayedTouch->modifiers() == event->modifiers() && |
2331 | delayedTouch->touchPoints().count() == event->touchPoints().count()) |
2332 | { |
2333 | // possible match.. is it really the same? |
2334 | bool mismatch = false; |
2335 | |
2336 | QList<QTouchEvent::TouchPoint> tpts = event->touchPoints(); |
2337 | Qt::TouchPointStates states; |
2338 | for (int i = 0; i < event->touchPoints().count(); ++i) { |
2339 | const QTouchEvent::TouchPoint &tp = tpts.at(i); |
2340 | const QTouchEvent::TouchPoint &tpDelayed = delayedTouch->touchPoints().at(i); |
2341 | if (tp.id() != tpDelayed.id()) { |
2342 | mismatch = true; |
2343 | break; |
2344 | } |
2345 | |
2346 | if (tpDelayed.state() == Qt::TouchPointMoved && tp.state() == Qt::TouchPointStationary) |
2347 | tpts[i].setState(Qt::TouchPointMoved); |
2348 | tpts[i].setLastPos(tpDelayed.lastPos()); |
2349 | tpts[i].setLastScenePos(tpDelayed.lastScenePos()); |
2350 | tpts[i].setLastScreenPos(tpDelayed.lastScreenPos()); |
2351 | tpts[i].setLastNormalizedPos(tpDelayed.lastNormalizedPos()); |
2352 | |
2353 | states |= tpts.at(i).state(); |
2354 | } |
2355 | |
2356 | // matching touch event? then merge the new event into the old one |
2357 | if (!mismatch) { |
2358 | delayedTouch->setTouchPoints(tpts); |
2359 | delayedTouch->setTimestamp(event->timestamp()); |
2360 | return true; |
2361 | } |
2362 | } |
2363 | |
2364 | // merging wasn't possible, so deliver the delayed event first, and then delay this one |
2365 | deliverDelayedTouchEvent(); |
2366 | delayedTouch.reset(other: new QTouchEvent(event->type(), event->device(), event->modifiers(), event->touchPointStates(), event->touchPoints())); |
2367 | delayedTouch->setTimestamp(event->timestamp()); |
2368 | return true; |
2369 | } |
2370 | |
2371 | // entry point for touch event delivery: |
2372 | // - translate the event to window coordinates |
2373 | // - compress the event instead of delivering it if applicable |
2374 | // - call deliverTouchPoints to actually dispatch the points |
2375 | void QQuickWindowPrivate::handleTouchEvent(QTouchEvent *event) |
2376 | { |
2377 | translateTouchEvent(touchEvent: event); |
2378 | if (event->touchPoints().size()) { |
2379 | auto point = event->touchPoints().at(i: 0); |
2380 | if (point.state() == Qt::TouchPointReleased) { |
2381 | lastMousePosition = QPointF(); |
2382 | } else { |
2383 | lastMousePosition = point.pos(); |
2384 | } |
2385 | } |
2386 | |
2387 | qCDebug(DBG_TOUCH) << event; |
2388 | |
2389 | static bool qquickwindow_no_touch_compression = qEnvironmentVariableIsSet(varName: "QML_NO_TOUCH_COMPRESSION" ); |
2390 | |
2391 | if (qquickwindow_no_touch_compression || pointerEventRecursionGuard) { |
2392 | deliverPointerEvent(pointerEventInstance(ev: event)); |
2393 | return; |
2394 | } |
2395 | |
2396 | if (!compressTouchEvent(event)) { |
2397 | if (delayedTouch) |
2398 | deliverDelayedTouchEvent(); |
2399 | deliverPointerEvent(pointerEventInstance(ev: event)); |
2400 | } |
2401 | } |
2402 | |
2403 | /*! \reimp */ |
2404 | void QQuickWindow::mousePressEvent(QMouseEvent *event) |
2405 | { |
2406 | Q_D(QQuickWindow); |
2407 | d->handleMouseEvent(event); |
2408 | } |
2409 | /*! \reimp */ |
2410 | void QQuickWindow::mouseMoveEvent(QMouseEvent *event) |
2411 | { |
2412 | Q_D(QQuickWindow); |
2413 | d->handleMouseEvent(event); |
2414 | } |
2415 | /*! \reimp */ |
2416 | void QQuickWindow::mouseDoubleClickEvent(QMouseEvent *event) |
2417 | { |
2418 | Q_D(QQuickWindow); |
2419 | d->handleMouseEvent(event); |
2420 | } |
2421 | /*! \reimp */ |
2422 | void QQuickWindow::mouseReleaseEvent(QMouseEvent *event) |
2423 | { |
2424 | Q_D(QQuickWindow); |
2425 | d->handleMouseEvent(event); |
2426 | } |
2427 | |
2428 | void QQuickWindowPrivate::handleMouseEvent(QMouseEvent *event) |
2429 | { |
2430 | if (event->source() == Qt::MouseEventSynthesizedBySystem) { |
2431 | event->accept(); |
2432 | return; |
2433 | } |
2434 | qCDebug(DBG_MOUSE) << "QQuickWindow::handleMouseEvent()" << event->type() << event->localPos() << event->button() << event->buttons(); |
2435 | |
2436 | switch (event->type()) { |
2437 | case QEvent::MouseButtonPress: |
2438 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMousePress, event->button(), |
2439 | event->buttons()); |
2440 | deliverPointerEvent(pointerEventInstance(ev: event)); |
2441 | break; |
2442 | case QEvent::MouseButtonRelease: |
2443 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseRelease, event->button(), |
2444 | event->buttons()); |
2445 | deliverPointerEvent(pointerEventInstance(ev: event)); |
2446 | #if QT_CONFIG(cursor) |
2447 | updateCursor(scenePos: event->windowPos()); |
2448 | #endif |
2449 | break; |
2450 | case QEvent::MouseButtonDblClick: |
2451 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseDoubleClick, |
2452 | event->button(), event->buttons()); |
2453 | if (allowDoubleClick) |
2454 | deliverPointerEvent(pointerEventInstance(ev: event)); |
2455 | break; |
2456 | case QEvent::MouseMove: |
2457 | Q_QUICK_INPUT_PROFILE(QQuickProfiler::Mouse, QQuickProfiler::InputMouseMove, |
2458 | event->localPos().x(), event->localPos().y()); |
2459 | |
2460 | qCDebug(DBG_HOVER_TRACE) << this; |
2461 | |
2462 | #if QT_CONFIG(cursor) |
2463 | updateCursor(scenePos: event->windowPos()); |
2464 | #endif |
2465 | |
2466 | if (!pointerEventInstance(device: QQuickPointerDevice::genericMouseDevice())->point(i: 0)->exclusiveGrabber()) { |
2467 | QPointF last = lastMousePosition.isNull() ? event->windowPos() : lastMousePosition; |
2468 | lastMousePosition = event->windowPos(); |
2469 | |
2470 | bool accepted = event->isAccepted(); |
2471 | bool delivered = deliverHoverEvent(item: contentItem, scenePos: event->windowPos(), lastScenePos: last, modifiers: event->modifiers(), timestamp: event->timestamp(), accepted); |
2472 | if (!delivered) { |
2473 | //take care of any exits |
2474 | accepted = clearHover(timestamp: event->timestamp()); |
2475 | } |
2476 | event->setAccepted(accepted); |
2477 | } |
2478 | deliverPointerEvent(pointerEventInstance(ev: event)); |
2479 | break; |
2480 | default: |
2481 | Q_ASSERT(false); |
2482 | break; |
2483 | } |
2484 | } |
2485 | |
2486 | void QQuickWindowPrivate::flushFrameSynchronousEvents() |
2487 | { |
2488 | Q_Q(QQuickWindow); |
2489 | |
2490 | if (delayedTouch) { |
2491 | deliverDelayedTouchEvent(); |
2492 | |
2493 | // Touch events which constantly start animations (such as a behavior tracking |
2494 | // the mouse point) need animations to start. |
2495 | QQmlAnimationTimer *ut = QQmlAnimationTimer::instance(); |
2496 | if (ut && ut->hasStartAnimationPending()) |
2497 | ut->startAnimations(); |
2498 | } |
2499 | |
2500 | // In webOS we already have the alternative to the issue that this |
2501 | // wanted to address and thus skipping this part won't break anything. |
2502 | #if !defined(Q_OS_WEBOS) |
2503 | // Once per frame, if any items are dirty, send a synthetic hover, |
2504 | // in case items have changed position, visibility, etc. |
2505 | // For instance, during animation (including the case of a ListView |
2506 | // whose delegates contain MouseAreas), a MouseArea needs to know |
2507 | // whether it has moved into a position where it is now under the cursor. |
2508 | if (!q->mouseGrabberItem() && !lastMousePosition.isNull() && dirtyItemList) { |
2509 | bool accepted = false; |
2510 | bool delivered = deliverHoverEvent(item: contentItem, scenePos: lastMousePosition, lastScenePos: lastMousePosition, modifiers: QGuiApplication::keyboardModifiers(), timestamp: 0, accepted); |
2511 | if (!delivered) |
2512 | clearHover(); // take care of any exits |
2513 | } |
2514 | #endif |
2515 | } |
2516 | |
2517 | QQuickPointerEvent *QQuickWindowPrivate::queryPointerEventInstance(QQuickPointerDevice *device, QEvent::Type eventType) const |
2518 | { |
2519 | // Search for a matching reusable event object. |
2520 | for (QQuickPointerEvent *e : pointerEventInstances) { |
2521 | // If device can generate native gestures (e.g. a trackpad), there might be multiple QQuickPointerEvents: |
2522 | // QQuickPointerNativeGestureEvent, QQuickPointerScrollEvent, and QQuickPointerTouchEvent. |
2523 | // Use eventType to disambiguate. |
2524 | #if QT_CONFIG(gestures) |
2525 | if ((eventType == QEvent::NativeGesture) != bool(e->asPointerNativeGestureEvent())) |
2526 | continue; |
2527 | #endif |
2528 | if ((eventType == QEvent::Wheel) != bool(e->asPointerScrollEvent())) |
2529 | continue; |
2530 | // Otherwise we assume there's only one event type per device. |
2531 | // More disambiguation tests might need to be added above if that changes later. |
2532 | if (e->device() == device) |
2533 | return e; |
2534 | } |
2535 | return nullptr; |
2536 | } |
2537 | |
2538 | QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QQuickPointerDevice *device, QEvent::Type eventType) const |
2539 | { |
2540 | QQuickPointerEvent *ev = queryPointerEventInstance(device, eventType); |
2541 | if (ev) |
2542 | return ev; |
2543 | QQuickWindow *q = const_cast<QQuickWindow*>(q_func()); |
2544 | switch (device->type()) { |
2545 | case QQuickPointerDevice::Mouse: |
2546 | // QWindowSystemInterface::handleMouseEvent() does not take a device parameter: |
2547 | // we assume all mouse events come from one mouse (the "core pointer"). |
2548 | // So when the event is a mouse event, device == QQuickPointerDevice::genericMouseDevice() |
2549 | if (eventType == QEvent::Wheel) |
2550 | ev = new QQuickPointerScrollEvent(q, device); |
2551 | else |
2552 | ev = new QQuickPointerMouseEvent(q, device); |
2553 | break; |
2554 | case QQuickPointerDevice::TouchPad: |
2555 | case QQuickPointerDevice::TouchScreen: |
2556 | #if QT_CONFIG(gestures) |
2557 | if (eventType == QEvent::NativeGesture) |
2558 | ev = new QQuickPointerNativeGestureEvent(q, device); |
2559 | else // assume QEvent::Type is one of TouchBegin/Update/End |
2560 | #endif |
2561 | ev = new QQuickPointerTouchEvent(q, device); |
2562 | break; |
2563 | #if QT_CONFIG(tabletevent) |
2564 | case QQuickPointerDevice::Stylus: |
2565 | case QQuickPointerDevice::Airbrush: |
2566 | case QQuickPointerDevice::Puck: |
2567 | ev = new QQuickPointerTabletEvent(q, device); |
2568 | break; |
2569 | #endif |
2570 | default: |
2571 | break; |
2572 | } |
2573 | pointerEventInstances << ev; |
2574 | return ev; |
2575 | } |
2576 | |
2577 | /*! |
2578 | \internal |
2579 | Returns a QQuickPointerEvent instance suitable for wrapping and delivering \a event. |
2580 | |
2581 | There is a unique instance per QQuickPointerDevice, which is determined |
2582 | from \a event's device. |
2583 | */ |
2584 | QQuickPointerEvent *QQuickWindowPrivate::pointerEventInstance(QEvent *event) const |
2585 | { |
2586 | QQuickPointerDevice *dev = nullptr; |
2587 | switch (event->type()) { |
2588 | case QEvent::MouseButtonPress: |
2589 | case QEvent::MouseButtonRelease: |
2590 | case QEvent::MouseButtonDblClick: |
2591 | case QEvent::MouseMove: |
2592 | case QEvent::Wheel: |
2593 | dev = QQuickPointerDevice::genericMouseDevice(); |
2594 | break; |
2595 | case QEvent::TouchBegin: |
2596 | case QEvent::TouchUpdate: |
2597 | case QEvent::TouchEnd: |
2598 | case QEvent::TouchCancel: |
2599 | dev = QQuickPointerDevice::touchDevice(d: static_cast<QTouchEvent *>(event)->device()); |
2600 | break; |
2601 | #if QT_CONFIG(tabletevent) |
2602 | case QEvent::TabletPress: |
2603 | case QEvent::TabletMove: |
2604 | case QEvent::TabletRelease: |
2605 | case QEvent::TabletEnterProximity: |
2606 | case QEvent::TabletLeaveProximity: |
2607 | dev = QQuickPointerDevice::tabletDevice(event: static_cast<QTabletEvent *>(event)); |
2608 | break; |
2609 | #endif |
2610 | #if QT_CONFIG(gestures) |
2611 | case QEvent::NativeGesture: |
2612 | dev = QQuickPointerDevice::touchDevice(d: static_cast<QNativeGestureEvent *>(event)->device()); |
2613 | break; |
2614 | #endif |
2615 | default: |
2616 | break; |
2617 | } |
2618 | |
2619 | Q_ASSERT(dev); |
2620 | auto pev = pointerEventInstance(device: dev, eventType: event->type()); |
2621 | Q_ASSERT(pev); |
2622 | return pev->reset(ev: event); |
2623 | } |
2624 | |
2625 | void QQuickWindowPrivate::deliverPointerEvent(QQuickPointerEvent *event) |
2626 | { |
2627 | Q_Q(QQuickWindow); |
2628 | // If users spin the eventloop as a result of event delivery, we disable |
2629 | // event compression and send events directly. This is because we consider |
2630 | // the usecase a bit evil, but we at least don't want to lose events. |
2631 | ++pointerEventRecursionGuard; |
2632 | |
2633 | skipDelivery.clear(); |
2634 | if (event->asPointerMouseEvent()) { |
2635 | deliverMouseEvent(pointerEvent: event->asPointerMouseEvent()); |
2636 | // failsafe: never allow any kind of grab to persist after release |
2637 | if (event->isReleaseEvent() && event->buttons() == Qt::NoButton) { |
2638 | QQuickItem *oldGrabber = q->mouseGrabberItem(); |
2639 | event->clearGrabbers(); |
2640 | sendUngrabEvent(grabber: oldGrabber, touch: false); |
2641 | } |
2642 | } else if (event->asPointerTouchEvent()) { |
2643 | deliverTouchEvent(event->asPointerTouchEvent()); |
2644 | } else { |
2645 | deliverSinglePointEventUntilAccepted(event); |
2646 | // If any handler got interested in the tablet event, we don't want to receive a synth-mouse event from QtGui |
2647 | // TODO Qt 6: QTabletEvent will be accepted by default, like other events |
2648 | if (event->asPointerTabletEvent() && |
2649 | (!event->point(i: 0)->passiveGrabbers().isEmpty() || event->point(i: 0)->exclusiveGrabber())) |
2650 | event->setAccepted(true); |
2651 | } |
2652 | |
2653 | event->reset(ev: nullptr); |
2654 | |
2655 | --pointerEventRecursionGuard; |
2656 | } |
2657 | |
2658 | // check if item or any of its child items contain the point, or if any pointer handler "wants" the point |
2659 | // FIXME: should this be iterative instead of recursive? |
2660 | // If checkMouseButtons is true, it means we are finding targets for a mouse event, so no item for which acceptedMouseButtons() is NoButton will be added. |
2661 | // If checkAcceptsTouch is true, it means we are finding targets for a touch event, so either acceptTouchEvents() must return true OR |
2662 | // it must accept a synth. mouse event, thus if acceptTouchEvents() returns false but acceptedMouseButtons() is true, gets added; if not, it doesn't. |
2663 | QVector<QQuickItem *> QQuickWindowPrivate::pointerTargets(QQuickItem *item, QQuickEventPoint *point, bool checkMouseButtons, bool checkAcceptsTouch) const |
2664 | { |
2665 | QVector<QQuickItem *> targets; |
2666 | auto itemPrivate = QQuickItemPrivate::get(item); |
2667 | QPointF itemPos = item->mapFromScene(point: point->scenePosition()); |
2668 | // if the item clips, we can potentially return early |
2669 | if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { |
2670 | if (!item->contains(point: itemPos)) |
2671 | return targets; |
2672 | } |
2673 | |
2674 | // recurse for children |
2675 | QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); |
2676 | for (int ii = children.count() - 1; ii >= 0; --ii) { |
2677 | QQuickItem *child = children.at(i: ii); |
2678 | auto childPrivate = QQuickItemPrivate::get(item: child); |
2679 | if (!child->isVisible() || !child->isEnabled() || childPrivate->culled) |
2680 | continue; |
2681 | targets << pointerTargets(item: child, point, checkMouseButtons, checkAcceptsTouch); |
2682 | } |
2683 | |
2684 | bool relevant = item->contains(point: itemPos); |
2685 | if (itemPrivate->hasPointerHandlers()) { |
2686 | if (!relevant) |
2687 | if (itemPrivate->anyPointerHandlerWants(point)) |
2688 | relevant = true; |
2689 | } else { |
2690 | if (relevant && checkMouseButtons && item->acceptedMouseButtons() == Qt::NoButton) |
2691 | relevant = false; |
2692 | if (relevant && checkAcceptsTouch && !(item->acceptTouchEvents() || item->acceptedMouseButtons())) |
2693 | relevant = false; |
2694 | } |
2695 | if (relevant) |
2696 | targets << item; // add this item last: children take precedence |
2697 | return targets; |
2698 | } |
2699 | |
2700 | // return the joined lists |
2701 | // list1 has priority, common items come last |
2702 | QVector<QQuickItem *> QQuickWindowPrivate::mergePointerTargets(const QVector<QQuickItem *> &list1, const QVector<QQuickItem *> &list2) const |
2703 | { |
2704 | QVector<QQuickItem *> targets = list1; |
2705 | // start at the end of list2 |
2706 | // if item not in list, append it |
2707 | // if item found, move to next one, inserting before the last found one |
2708 | int insertPosition = targets.length(); |
2709 | for (int i = list2.length() - 1; i >= 0; --i) { |
2710 | int newInsertPosition = targets.lastIndexOf(t: list2.at(i), from: insertPosition); |
2711 | if (newInsertPosition >= 0) { |
2712 | Q_ASSERT(newInsertPosition <= insertPosition); |
2713 | insertPosition = newInsertPosition; |
2714 | } |
2715 | // check for duplicates, only insert if the item isn't there already |
2716 | if (insertPosition == targets.size() || list2.at(i) != targets.at(i: insertPosition)) |
2717 | targets.insert(i: insertPosition, t: list2.at(i)); |
2718 | } |
2719 | return targets; |
2720 | } |
2721 | |
2722 | void QQuickWindowPrivate::deliverTouchEvent(QQuickPointerTouchEvent *event) |
2723 | { |
2724 | qCDebug(DBG_TOUCH) << " - delivering" << event->asTouchEvent(); |
2725 | |
2726 | if (event->isPressEvent()) |
2727 | deliverPressOrReleaseEvent(event); |
2728 | if (!event->allUpdatedPointsAccepted()) |
2729 | deliverUpdatedTouchPoints(event); |
2730 | if (event->isReleaseEvent()) |
2731 | deliverPressOrReleaseEvent(event, handlersOnly: true); |
2732 | |
2733 | // Remove released points from itemForTouchPointId |
2734 | bool allReleased = true; |
2735 | int pointCount = event->pointCount(); |
2736 | for (int i = 0; i < pointCount; ++i) { |
2737 | QQuickEventPoint *point = event->point(i); |
2738 | if (point->state() == QQuickEventPoint::Released) { |
2739 | int id = point->pointId(); |
2740 | qCDebug(DBG_TOUCH_TARGET) << "TP" << Qt::hex << id << "released" ; |
2741 | point->setGrabberItem(nullptr); |
2742 | if (id == touchMouseId) |
2743 | cancelTouchMouseSynthesis(); |
2744 | } else { |
2745 | allReleased = false; |
2746 | } |
2747 | } |
2748 | |
2749 | if (allReleased) { |
2750 | if (Q_UNLIKELY(!event->exclusiveGrabbers().isEmpty())) |
2751 | qWarning() << "No release received for some grabbers" << event->exclusiveGrabbers(); |
2752 | event->clearGrabbers(); |
2753 | } |
2754 | } |
2755 | |
2756 | // Deliver touch points to existing grabbers |
2757 | void QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *event) |
2758 | { |
2759 | bool done = false; |
2760 | const auto grabbers = event->exclusiveGrabbers(); |
2761 | for (auto grabber : grabbers) { |
2762 | // The grabber is guaranteed to be either an item or a handler. |
2763 | QQuickItem *receiver = qmlobject_cast<QQuickItem *>(object: grabber); |
2764 | if (!receiver) { |
2765 | // The grabber is not an item? It's a handler then. Let it have the event first. |
2766 | QQuickPointerHandler *handler = static_cast<QQuickPointerHandler *>(grabber); |
2767 | receiver = static_cast<QQuickPointerHandler *>(grabber)->parentItem(); |
2768 | hasFiltered.clear(); |
2769 | if (sendFilteredPointerEvent(event, receiver)) |
2770 | done = true; |
2771 | event->localize(target: receiver); |
2772 | handler->handlePointerEvent(event); |
2773 | } |
2774 | if (done) |
2775 | break; |
2776 | // If the grabber is an item or the grabbing handler didn't handle it, |
2777 | // then deliver the event to the item (which may have multiple handlers). |
2778 | deliverMatchingPointsToItem(item: receiver, pointerEvent: event); |
2779 | } |
2780 | |
2781 | // Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once) |
2782 | int pointCount = event->pointCount(); |
2783 | for (int i = 0; i < pointCount; ++i) { |
2784 | QQuickEventPoint *point = event->point(i); |
2785 | deliverToPassiveGrabbers(passiveGrabbers: point->passiveGrabbers(), pointerEvent: event); |
2786 | } |
2787 | |
2788 | if (done) |
2789 | return; |
2790 | |
2791 | // If some points weren't grabbed, deliver only to non-grabber PointerHandlers in reverse paint order |
2792 | if (!event->allPointsGrabbed()) { |
2793 | QVector<QQuickItem *> targetItems; |
2794 | for (int i = 0; i < pointCount; ++i) { |
2795 | QQuickEventPoint *point = event->point(i); |
2796 | if (point->state() == QQuickEventPoint::Pressed) |
2797 | continue; // presses were delivered earlier; not the responsibility of deliverUpdatedTouchPoints |
2798 | QVector<QQuickItem *> targetItemsForPoint = pointerTargets(item: contentItem, point, checkMouseButtons: false, checkAcceptsTouch: false); |
2799 | if (targetItems.count()) { |
2800 | targetItems = mergePointerTargets(list1: targetItems, list2: targetItemsForPoint); |
2801 | } else { |
2802 | targetItems = targetItemsForPoint; |
2803 | } |
2804 | } |
2805 | for (QQuickItem *item : targetItems) { |
2806 | if (grabbers.contains(t: item)) |
2807 | continue; |
2808 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
2809 | event->localize(target: item); |
2810 | itemPrivate->handlePointerEvent(event, avoidExclusiveGrabber: true); // avoid re-delivering to grabbers |
2811 | if (event->allPointsGrabbed()) |
2812 | break; |
2813 | } |
2814 | } |
2815 | } |
2816 | |
2817 | // Deliver an event containing newly pressed or released touch points |
2818 | bool QQuickWindowPrivate::deliverPressOrReleaseEvent(QQuickPointerEvent *event, bool handlersOnly) |
2819 | { |
2820 | int pointCount = event->pointCount(); |
2821 | QVector<QQuickItem *> targetItems; |
2822 | bool isTouchEvent = (event->asPointerTouchEvent() != nullptr); |
2823 | if (isTouchEvent && event->isPressEvent() && isDeliveringTouchAsMouse()) { |
2824 | if (const QQuickEventPoint *point = pointerEventInstance(device: touchMouseDevice)->pointById(pointId: touchMouseId)) { |
2825 | // When a second point is pressed, if the first point's existing |
2826 | // grabber was a pointer handler while a filtering parent is filtering |
2827 | // the same first point _as mouse_: we're starting over with delivery, |
2828 | // so we need to allow the second point to now be sent as a synth-mouse |
2829 | // instead of the first one, so that filtering parents (maybe even the |
2830 | // same one) can get a chance to see the second touchpoint as a |
2831 | // synth-mouse and perhaps grab it. Ideally we would always do this |
2832 | // when a new touchpoint is pressed, but this compromise fixes |
2833 | // QTBUG-70998 and avoids breaking tst_FlickableInterop::touchDragSliderAndFlickable |
2834 | if (point->grabberPointerHandler()) |
2835 | cancelTouchMouseSynthesis(); |
2836 | } else { |
2837 | qCWarning(DBG_TOUCH_TARGET) << "during delivery of touch press, synth-mouse ID" << Qt::hex << touchMouseId << "is missing from" << event; |
2838 | } |
2839 | } |
2840 | for (int i = 0; i < pointCount; ++i) { |
2841 | auto point = event->point(i); |
2842 | if (point->state() == QQuickEventPoint::Pressed && !event->isDoubleClickEvent()) |
2843 | point->clearPassiveGrabbers(); |
2844 | point->setAccepted(false); // because otherwise touchEventForItem will ignore it |
2845 | if (point->grabberPointerHandler() && point->state() == QQuickEventPoint::Released) |
2846 | point->setGrabberPointerHandler(exclusiveGrabber: nullptr, exclusive: true); |
2847 | QVector<QQuickItem *> targetItemsForPoint = pointerTargets(item: contentItem, point, checkMouseButtons: !isTouchEvent, checkAcceptsTouch: isTouchEvent); |
2848 | if (targetItems.count()) { |
2849 | targetItems = mergePointerTargets(list1: targetItems, list2: targetItemsForPoint); |
2850 | } else { |
2851 | targetItems = targetItemsForPoint; |
2852 | } |
2853 | } |
2854 | |
2855 | for (QQuickItem *item : targetItems) { |
2856 | if (!event->m_event) { |
2857 | qWarning(msg: "event went missing during delivery! (nested sendEvent() is not allowed)" ); |
2858 | break; |
2859 | } |
2860 | hasFiltered.clear(); |
2861 | if (!handlersOnly && sendFilteredPointerEvent(event, receiver: item)) { |
2862 | if (event->isAccepted()) { |
2863 | for (int i = 0; i < event->pointCount(); ++i) |
2864 | event->point(i)->setAccepted(); |
2865 | return true; |
2866 | } |
2867 | skipDelivery.append(t: item); |
2868 | } |
2869 | |
2870 | // Do not deliverMatchingPointsTo any item for which the filtering parent already intercepted the event, |
2871 | // nor to any item which already had a chance to filter. |
2872 | if (skipDelivery.contains(t: item)) |
2873 | continue; |
2874 | if (!event->m_event) { |
2875 | qWarning(msg: "event went missing during delivery! (nested sendEvent() is not allowed)" ); |
2876 | break; |
2877 | } |
2878 | deliverMatchingPointsToItem(item, pointerEvent: event, handlersOnly); |
2879 | if (event->allPointsAccepted()) |
2880 | handlersOnly = true; |
2881 | } |
2882 | |
2883 | return event->allPointsAccepted(); |
2884 | } |
2885 | |
2886 | void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPointerEvent *pointerEvent, bool handlersOnly) |
2887 | { |
2888 | Q_Q(QQuickWindow); |
2889 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
2890 | #if defined(Q_OS_ANDROID) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
2891 | // QTBUG-85379 |
2892 | // In QT_VERSION below 6.0.0 touchEnabled for QtQuickItems is set by default to true |
2893 | // It causes delivering touch events to Items which are not interested |
2894 | // In some cases (like using Material Style in Android) it may cause a crash |
2895 | if (itemPrivate->wasDeleted) |
2896 | return; |
2897 | #endif |
2898 | pointerEvent->localize(target: item); |
2899 | |
2900 | // Let the Item's handlers (if any) have the event first. |
2901 | // However, double click should never be delivered to handlers. |
2902 | if (!pointerEvent->isDoubleClickEvent()) { |
2903 | bool wasAccepted = pointerEvent->allPointsAccepted(); |
2904 | itemPrivate->handlePointerEvent(pointerEvent); |
2905 | allowDoubleClick = wasAccepted || !(pointerEvent->asPointerMouseEvent() && pointerEvent->isPressEvent() && pointerEvent->allPointsAccepted()); |
2906 | } |
2907 | if (handlersOnly) |
2908 | return; |
2909 | |
2910 | // If all points are released and the item is not the grabber, it doesn't get the event. |
2911 | // But if at least one point is still pressed, we might be in a potential gesture-takeover scenario. |
2912 | if (pointerEvent->isReleaseEvent() && !pointerEvent->isUpdateEvent() |
2913 | && !pointerEvent->exclusiveGrabbers().contains(t: item)) |
2914 | return; |
2915 | |
2916 | // TODO: unite this mouse point delivery with the synthetic mouse event below |
2917 | auto event = pointerEvent->asPointerMouseEvent(); |
2918 | if (event && item->acceptedMouseButtons() & event->button()) { |
2919 | auto point = event->point(i: 0); |
2920 | // The only reason to already have a mouse grabber here is |
2921 | // synthetic events - flickable sends one when setPressDelay is used. |
2922 | auto oldMouseGrabber = q->mouseGrabberItem(); |
2923 | QPointF localPos = item->mapFromScene(point: point->scenePosition()); |
2924 | QMouseEvent *me = event->asMouseEvent(localPos); |
2925 | me->accept(); |
2926 | QCoreApplication::sendEvent(receiver: item, event: me); |
2927 | if (me->isAccepted()) { |
2928 | auto mouseGrabber = q->mouseGrabberItem(); |
2929 | if (mouseGrabber && mouseGrabber != item && mouseGrabber != oldMouseGrabber) { |
2930 | item->mouseUngrabEvent(); |
2931 | } else if (item->isEnabled() && item->isVisible()) { |
2932 | item->grabMouse(); |
2933 | } |
2934 | point->setAccepted(true); |
2935 | } |
2936 | return; |
2937 | } |
2938 | |
2939 | QQuickPointerTouchEvent *ptEvent = pointerEvent->asPointerTouchEvent(); |
2940 | if (!ptEvent) |
2941 | return; |
2942 | |
2943 | QScopedPointer<QTouchEvent> touchEvent(ptEvent->touchEventForItem(item)); |
2944 | if (!touchEvent) |
2945 | return; |
2946 | |
2947 | qCDebug(DBG_TOUCH) << "considering delivering " << touchEvent.data() << " to " << item; |
2948 | bool eventAccepted = false; |
2949 | |
2950 | // If any parent filters the event, we're done. |
2951 | hasFiltered.clear(); |
2952 | if (sendFilteredPointerEvent(event: pointerEvent, receiver: item)) |
2953 | return; |
2954 | |
2955 | // Deliver the touch event to the given item |
2956 | qCDebug(DBG_TOUCH) << " - actually delivering " << touchEvent.data() << " to " << item; |
2957 | QCoreApplication::sendEvent(receiver: item, event: touchEvent.data()); |
2958 | eventAccepted = touchEvent->isAccepted(); |
2959 | |
2960 | // If the touch event wasn't accepted, synthesize a mouse event and see if the item wants it. |
2961 | if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents))) { |
2962 | if (!eventAccepted && (itemPrivate->acceptedMouseButtons() & Qt::LeftButton)) { |
2963 | // send mouse event |
2964 | if (deliverTouchAsMouse(item, pointerEvent: ptEvent)) |
2965 | eventAccepted = true; |
2966 | } |
2967 | } |
2968 | |
2969 | if (eventAccepted) { |
2970 | // If the touch was accepted (regardless by whom or in what form), |
2971 | // update accepted new points. |
2972 | bool isPressOrRelease = pointerEvent->isPressEvent() || pointerEvent->isReleaseEvent(); |
2973 | for (const auto &point: qAsConst(t: touchEvent->touchPoints())) { |
2974 | if (auto pointerEventPoint = ptEvent->pointById(pointId: point.id())) { |
2975 | pointerEventPoint->setAccepted(); |
2976 | if (isPressOrRelease) |
2977 | pointerEventPoint->setGrabberItem(item); |
2978 | } |
2979 | } |
2980 | } else { |
2981 | // But if the event was not accepted then we know this item |
2982 | // will not be interested in further updates for those touchpoint IDs either. |
2983 | for (const auto &point: qAsConst(t: touchEvent->touchPoints())) { |
2984 | if (point.state() == Qt::TouchPointPressed) { |
2985 | if (auto *tp = ptEvent->pointById(pointId: point.id())) { |
2986 | if (tp->exclusiveGrabber() == item) { |
2987 | qCDebug(DBG_TOUCH_TARGET) << "TP" << Qt::hex << point.id() << "disassociated" ; |
2988 | tp->setGrabberItem(nullptr); |
2989 | } |
2990 | } |
2991 | } |
2992 | } |
2993 | } |
2994 | } |
2995 | |
2996 | #if QT_CONFIG(quick_draganddrop) |
2997 | void QQuickWindowPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QEvent *event) |
2998 | { |
2999 | grabber->resetTarget(); |
3000 | QQuickDragGrabber::iterator grabItem = grabber->begin(); |
3001 | if (grabItem != grabber->end()) { |
3002 | Q_ASSERT(event->type() != QEvent::DragEnter); |
3003 | if (event->type() == QEvent::Drop) { |
3004 | QDropEvent *e = static_cast<QDropEvent *>(event); |
3005 | for (e->setAccepted(false); !e->isAccepted() && grabItem != grabber->end(); grabItem = grabber->release(at: grabItem)) { |
3006 | QPointF p = (**grabItem)->mapFromScene(point: e->pos()); |
3007 | QDropEvent translatedEvent( |
3008 | p.toPoint(), |
3009 | e->possibleActions(), |
3010 | e->mimeData(), |
3011 | e->mouseButtons(), |
3012 | e->keyboardModifiers()); |
3013 | QQuickDropEventEx::copyActions(to: &translatedEvent, from: *e); |
3014 | QCoreApplication::sendEvent(receiver: **grabItem, event: &translatedEvent); |
3015 | e->setAccepted(translatedEvent.isAccepted()); |
3016 | e->setDropAction(translatedEvent.dropAction()); |
3017 | grabber->setTarget(**grabItem); |
3018 | } |
3019 | } |
3020 | if (event->type() != QEvent::DragMove) { // Either an accepted drop or a leave. |
3021 | QDragLeaveEvent leaveEvent; |
3022 | for (; grabItem != grabber->end(); grabItem = grabber->release(at: grabItem)) |
3023 | QCoreApplication::sendEvent(receiver: **grabItem, event: &leaveEvent); |
3024 | return; |
3025 | } else { |
3026 | QDragMoveEvent *moveEvent = static_cast<QDragMoveEvent *>(event); |
3027 | |
3028 | // Used to ensure we don't send DragEnterEvents to current drop targets, |
3029 | // and to detect which current drop targets we have left |
3030 | QVarLengthArray<QQuickItem*, 64> currentGrabItems; |
3031 | for (; grabItem != grabber->end(); grabItem = grabber->release(at: grabItem)) |
3032 | currentGrabItems.append(t: **grabItem); |
3033 | |
3034 | // Look for any other potential drop targets that are higher than the current ones |
3035 | QDragEnterEvent enterEvent( |
3036 | moveEvent->pos(), |
3037 | moveEvent->possibleActions(), |
3038 | moveEvent->mimeData(), |
3039 | moveEvent->mouseButtons(), |
3040 | moveEvent->keyboardModifiers()); |
3041 | QQuickDropEventEx::copyActions(to: &enterEvent, from: *moveEvent); |
3042 | event->setAccepted(deliverDragEvent(grabber, contentItem, &enterEvent, currentGrabItems: ¤tGrabItems)); |
3043 | |
3044 | for (grabItem = grabber->begin(); grabItem != grabber->end(); ++grabItem) { |
3045 | int i = currentGrabItems.indexOf(t: **grabItem); |
3046 | if (i >= 0) { |
3047 | currentGrabItems.remove(i); |
3048 | // Still grabbed: send move event |
3049 | QDragMoveEvent translatedEvent( |
3050 | (**grabItem)->mapFromScene(point: moveEvent->pos()).toPoint(), |
3051 | moveEvent->possibleActions(), |
3052 | moveEvent->mimeData(), |
3053 | moveEvent->mouseButtons(), |
3054 | moveEvent->keyboardModifiers()); |
3055 | QQuickDropEventEx::copyActions(to: &translatedEvent, from: *moveEvent); |
3056 | QCoreApplication::sendEvent(receiver: **grabItem, event: &translatedEvent); |
3057 | event->setAccepted(translatedEvent.isAccepted()); |
3058 | QQuickDropEventEx::copyActions(to: moveEvent, from: translatedEvent); |
3059 | } |
3060 | } |
3061 | |
3062 | // Anything left in currentGrabItems is no longer a drop target and should be sent a DragLeaveEvent |
3063 | QDragLeaveEvent leaveEvent; |
3064 | for (QQuickItem *i : currentGrabItems) |
3065 | QCoreApplication::sendEvent(receiver: i, event: &leaveEvent); |
3066 | |
3067 | return; |
3068 | } |
3069 | } |
3070 | if (event->type() == QEvent::DragEnter || event->type() == QEvent::DragMove) { |
3071 | QDragMoveEvent *e = static_cast<QDragMoveEvent *>(event); |
3072 | QDragEnterEvent enterEvent( |
3073 | e->pos(), |
3074 | e->possibleActions(), |
3075 | e->mimeData(), |
3076 | e->mouseButtons(), |
3077 | e->keyboardModifiers()); |
3078 | QQuickDropEventEx::copyActions(to: &enterEvent, from: *e); |
3079 | event->setAccepted(deliverDragEvent(grabber, contentItem, &enterEvent)); |
3080 | } |
3081 | } |
3082 | |
3083 | bool QQuickWindowPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QQuickItem *item, QDragMoveEvent *event, QVarLengthArray<QQuickItem*, 64> *currentGrabItems) |
3084 | { |
3085 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
3086 | if (!item->isVisible() || !item->isEnabled() || QQuickItemPrivate::get(item)->culled) |
3087 | return false; |
3088 | QPointF p = item->mapFromScene(point: event->pos()); |
3089 | bool itemContained = item->contains(point: p); |
3090 | |
3091 | if (!itemContained && itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { |
3092 | return false; |
3093 | } |
3094 | |
3095 | QDragEnterEvent enterEvent( |
3096 | event->pos(), |
3097 | event->possibleActions(), |
3098 | event->mimeData(), |
3099 | event->mouseButtons(), |
3100 | event->keyboardModifiers()); |
3101 | QQuickDropEventEx::copyActions(to: &enterEvent, from: *event); |
3102 | QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); |
3103 | |
3104 | // Check children in front of this item first |
3105 | for (int ii = children.count() - 1; ii >= 0; --ii) { |
3106 | if (children.at(i: ii)->z() < 0) |
3107 | continue; |
3108 | if (deliverDragEvent(grabber, item: children.at(i: ii), event: &enterEvent, currentGrabItems)) |
3109 | return true; |
3110 | } |
3111 | |
3112 | if (itemContained) { |
3113 | // If this item is currently grabbed, don't send it another DragEnter, |
3114 | // just grab it again if it's still contained. |
3115 | if (currentGrabItems && currentGrabItems->contains(t: item)) { |
3116 | grabber->grab(item); |
3117 | grabber->setTarget(item); |
3118 | return true; |
3119 | } |
3120 | |
3121 | if (event->type() == QEvent::DragMove || itemPrivate->flags & QQuickItem::ItemAcceptsDrops) { |
3122 | QDragMoveEvent translatedEvent( |
3123 | p.toPoint(), |
3124 | event->possibleActions(), |
3125 | event->mimeData(), |
3126 | event->mouseButtons(), |
3127 | event->keyboardModifiers(), |
3128 | event->type()); |
3129 | QQuickDropEventEx::copyActions(to: &translatedEvent, from: *event); |
3130 | translatedEvent.setAccepted(event->isAccepted()); |
3131 | QCoreApplication::sendEvent(receiver: item, event: &translatedEvent); |
3132 | event->setAccepted(translatedEvent.isAccepted()); |
3133 | event->setDropAction(translatedEvent.dropAction()); |
3134 | if (event->type() == QEvent::DragEnter) { |
3135 | if (translatedEvent.isAccepted()) { |
3136 | grabber->grab(item); |
3137 | grabber->setTarget(item); |
3138 | return true; |
3139 | } |
3140 | } else { |
3141 | return true; |
3142 | } |
3143 | } |
3144 | } |
3145 | |
3146 | // Check children behind this item if this item or any higher children have not accepted |
3147 | for (int ii = children.count() - 1; ii >= 0; --ii) { |
3148 | if (children.at(i: ii)->z() >= 0) |
3149 | continue; |
3150 | if (deliverDragEvent(grabber, item: children.at(i: ii), event: &enterEvent, currentGrabItems)) |
3151 | return true; |
3152 | } |
3153 | |
3154 | return false; |
3155 | } |
3156 | #endif // quick_draganddrop |
3157 | |
3158 | #if QT_CONFIG(cursor) |
3159 | void QQuickWindowPrivate::updateCursor(const QPointF &scenePos) |
3160 | { |
3161 | Q_Q(QQuickWindow); |
3162 | |
3163 | auto cursorItemAndHandler = findCursorItemAndHandler(item: contentItem, scenePos); |
3164 | |
3165 | if (cursorItem != cursorItemAndHandler.first || cursorHandler != cursorItemAndHandler.second) { |
3166 | QWindow *renderWindow = QQuickRenderControl::renderWindowFor(win: q); |
3167 | QWindow *window = renderWindow ? renderWindow : q; |
3168 | cursorItem = cursorItemAndHandler.first; |
3169 | cursorHandler = cursorItemAndHandler.second; |
3170 | if (cursorItem) |
3171 | window->setCursor(QQuickItemPrivate::get(item: cursorItem)->effectiveCursor(handler: cursorHandler)); |
3172 | else |
3173 | window->unsetCursor(); |
3174 | } |
3175 | } |
3176 | |
3177 | QPair<QQuickItem*, QQuickPointerHandler*> QQuickWindowPrivate::findCursorItemAndHandler(QQuickItem *item, const QPointF &scenePos) const |
3178 | { |
3179 | QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); |
3180 | if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { |
3181 | QPointF p = item->mapFromScene(point: scenePos); |
3182 | if (!item->contains(point: p)) |
3183 | return {nullptr, nullptr}; |
3184 | } |
3185 | |
3186 | if (itemPrivate->subtreeCursorEnabled) { |
3187 | QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); |
3188 | for (int ii = children.count() - 1; ii >= 0; --ii) { |
3189 | QQuickItem *child = children.at(i: ii); |
3190 | if (!child->isVisible() || !child->isEnabled() || QQuickItemPrivate::get(item: child)->culled) |
3191 | continue; |
3192 | auto ret = findCursorItemAndHandler(item: child, scenePos); |
3193 | if (ret.first) |
3194 | return ret; |
3195 | } |
3196 | if (itemPrivate->hasCursorHandler) { |
3197 | if (auto handler = itemPrivate->effectiveCursorHandler()) { |
3198 | QQuickPointerEvent *pointerEvent = pointerEventInstance(device: QQuickPointerDevice::genericMouseDevice(), eventType: QEvent::MouseMove); |
3199 | pointerEvent->point(i: 0)->reset(state: Qt::TouchPointMoved, scenePosition: scenePos, pointId: quint64(1) << 24 /* mouse has device ID 1 */, timestamp: 0); |
3200 | pointerEvent->point(i: 0)->setAccepted(true); |
3201 | pointerEvent->localize(target: item); |
3202 | if (handler->parentContains(point: pointerEvent->point(i: 0))) |
3203 | return {item, handler}; |
3204 | } |
3205 | } |
3206 | if (itemPrivate->hasCursor) { |
3207 | QPointF p = item->mapFromScene(point: scenePos); |
3208 | if (item->contains(point: p)) |
3209 | return {item, nullptr}; |
3210 | } |
3211 | } |
3212 | |
3213 | return {nullptr, nullptr}; |
3214 | } |
3215 | #endif |
3216 | |
3217 | bool QQuickWindowPrivate::sendFilteredPointerEvent(QQuickPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) |
3218 | { |
3219 | return sendFilteredPointerEventImpl(event, receiver, filteringParent: filteringParent ? filteringParent : receiver->parentItem()); |
3220 | } |
3221 | |
3222 | bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QQuickPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) |
3223 | { |
3224 | if (!allowChildEventFiltering) |
3225 | return false; |
3226 | if (!filteringParent) |
3227 | return false; |
3228 | bool filtered = false; |
3229 | if (filteringParent->filtersChildMouseEvents() && !hasFiltered.contains(t: filteringParent)) { |
3230 | hasFiltered.append(t: filteringParent); |
3231 | if (QQuickPointerMouseEvent *pme = event->asPointerMouseEvent()) { |
3232 | if (receiver->acceptedMouseButtons()) { |
3233 | QPointF localPos = receiver->mapFromScene(point: pme->point(i: 0)->scenePosition()); |
3234 | QMouseEvent *me = pme->asMouseEvent(localPos); |
3235 | const bool wasAccepted = me->isAccepted(); |
3236 | me->setAccepted(true); |
3237 | auto oldMouseGrabber = pme->point(i: 0)->grabberItem(); |
3238 | if (filteringParent->childMouseEventFilter(receiver, me)) { |
3239 | qCDebug(DBG_MOUSE) << "mouse event intercepted by childMouseEventFilter of " << filteringParent; |
3240 | skipDelivery.append(t: filteringParent); |
3241 | filtered = true; |
3242 | if (me->isAccepted() && pme->isPressEvent()) { |
3243 | auto mouseGrabber = pme->point(i: 0)->grabberItem(); |
3244 | if (mouseGrabber && mouseGrabber != receiver && mouseGrabber != oldMouseGrabber) { |
3245 | receiver->mouseUngrabEvent(); |
3246 | } else { |
3247 | pme->point(i: 0)->setGrabberItem(receiver); |
3248 | } |
3249 | } |
3250 | } else { |
3251 | // Restore accepted state if the event was not filtered. |
3252 | me->setAccepted(wasAccepted); |
3253 | } |
3254 | } |
3255 | } else if (QQuickPointerTouchEvent *pte = event->asPointerTouchEvent()) { |
3256 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) |
3257 | bool acceptsTouchEvents = receiver->acceptTouchEvents(); |
3258 | #else |
3259 | // In versions prior to Qt 6, we can't trust item->acceptTouchEvents() here, because it defaults to true. |
3260 | bool acceptsTouchEvents = false; |
3261 | #endif |
3262 | auto device = pte->device(); |
3263 | if (device->type() == QQuickPointerDevice::TouchPad && |
3264 | device->capabilities().testFlag(flag: QQuickPointerDevice::MouseEmulation)) { |
3265 | qCDebug(DBG_TOUCH_TARGET) << "skipping filtering of synth-mouse event from" << device; |
3266 | } else if (acceptsTouchEvents || receiver->acceptedMouseButtons()) { |
3267 | // get a touch event customized for delivery to filteringParent |
3268 | QScopedPointer<QTouchEvent> filteringParentTouchEvent(pte->touchEventForItem(item: receiver, isFiltering: true)); |
3269 | if (filteringParentTouchEvent) { |
3270 | if (filteringParent->childMouseEventFilter(receiver, filteringParentTouchEvent.data())) { |
3271 | qCDebug(DBG_TOUCH) << "touch event intercepted by childMouseEventFilter of " << filteringParent; |
3272 | skipDelivery.append(t: filteringParent); |
3273 | for (const auto &point: qAsConst(t: filteringParentTouchEvent->touchPoints())) { |
3274 | QQuickEventPoint *pt = event->pointById(pointId: point.id()); |
3275 | pt->setAccepted(); |
3276 | pt->setGrabberItem(filteringParent); |
3277 | } |
3278 | return true; |
3279 | } else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents))) { |
3280 | // filteringParent didn't filter the touch event. Give it a chance to filter a synthetic mouse event. |
3281 | for (int i = 0; i < filteringParentTouchEvent->touchPoints().size(); ++i) { |
3282 | const QTouchEvent::TouchPoint &tp = filteringParentTouchEvent->touchPoints().at(i); |
3283 | |
3284 | QEvent::Type t; |
3285 | switch (tp.state()) { |
3286 | case Qt::TouchPointPressed: |
3287 | t = QEvent::MouseButtonPress; |
3288 | break; |
3289 | case Qt::TouchPointReleased: |
3290 | t = QEvent::MouseButtonRelease; |
3291 | break; |
3292 | case Qt::TouchPointStationary: |
3293 | continue; |
3294 | default: |
3295 | t = QEvent::MouseMove; |
3296 | break; |
3297 | } |
3298 | |
3299 | bool touchMouseUnset = (touchMouseId == -1); |
3300 | // Only deliver mouse event if it is the touchMouseId or it could become the touchMouseId |
3301 | if (touchMouseUnset || touchMouseId == tp.id()) { |
3302 | // convert filteringParentTouchEvent (which is already transformed wrt local position, velocity, etc.) |
3303 | // into a synthetic mouse event, and let childMouseEventFilter() have another chance with that |
3304 | QScopedPointer<QMouseEvent> mouseEvent(touchToMouseEvent(type: t, p: tp, event: filteringParentTouchEvent.data(), item: receiver, transformNeeded: false)); |
3305 | // If a filtering item calls QQuickWindow::mouseGrabberItem(), it should |
3306 | // report the touchpoint's grabber. Whenever we send a synthetic mouse event, |
3307 | // touchMouseId and touchMouseDevice must be set, even if it's only temporarily and isn't grabbed. |
3308 | touchMouseId = tp.id(); |
3309 | touchMouseDevice = event->device(); |
3310 | QQuickPointerDevice *dev = touchMouseDevice; |
3311 | if (filteringParent->childMouseEventFilter(receiver, mouseEvent.data())) { |
3312 | qCDebug(DBG_TOUCH) << "touch event intercepted as synth mouse event by childMouseEventFilter of " << filteringParent; |
3313 | skipDelivery.append(t: filteringParent); |
3314 | if (t != QEvent::MouseButtonRelease) { |
3315 | qCDebug(DBG_TOUCH_TARGET) << "TP (mouse)" << Qt::hex << tp.id() << "->" << filteringParent; |
3316 | pointerEventInstance(device: dev)->pointById(pointId: tp.id())->setGrabberItem(filteringParent); |
3317 | touchMouseUnset = false; // We want to leave touchMouseId and touchMouseDevice set |
3318 | if (mouseEvent->isAccepted()) |
3319 | filteringParent->grabMouse(); |
3320 | } |
3321 | filtered = true; |
3322 | } |
3323 | if (touchMouseUnset) { |
3324 | // Now that we're done sending a synth mouse event, and it wasn't grabbed, |
3325 | // the touchpoint is no longer acting as a synthetic mouse. Restore previous state. |
3326 | cancelTouchMouseSynthesis(); |
3327 | } |
3328 | // Only one touchpoint can be treated as a synthetic mouse, so after childMouseEventFilter |
3329 | // has been called once, we're done with this loop over the touchpoints. |
3330 | break; |
3331 | } |
3332 | } |
3333 | } |
3334 | } |
3335 | } |
3336 | } |
3337 | } |
3338 | return sendFilteredPointerEventImpl(event, receiver, filteringParent: filteringParent->parentItem()) || filtered; |
3339 | } |
3340 | |
3341 | bool QQuickWindowPrivate::sendFilteredMouseEvent(QEvent *event, QQuickItem *receiver, QQuickItem *filteringParent) |
3342 | { |
3343 | if (!filteringParent) |
3344 | return false; |
3345 | |
3346 | QQuickItemPrivate *filteringParentPrivate = QQuickItemPrivate::get(item: filteringParent); |
3347 | if (filteringParentPrivate->replayingPressEvent) |
3348 | return false; |
3349 | |
3350 | bool filtered = false; |
3351 | if (filteringParentPrivate->filtersChildMouseEvents && !hasFiltered.contains(t: filteringParent)) { |
3352 | hasFiltered.append(t: filteringParent); |
3353 | if (filteringParent->childMouseEventFilter(receiver, event)) { |
3354 | filtered = true; |
3355 | skipDelivery.append(t: filteringParent); |
3356 | } |
3357 | qCDebug(DBG_MOUSE_TARGET) << "for" << receiver << filteringParent << "childMouseEventFilter ->" << filtered; |
3358 | } |
3359 | |
3360 | return sendFilteredMouseEvent(event, receiver, filteringParent: filteringParent->parentItem()) || filtered; |
3361 | } |
3362 | |
3363 | bool QQuickWindowPrivate::dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold) |
3364 | { |
3365 | QStyleHints *styleHints = QGuiApplication::styleHints(); |
3366 | int caps = QGuiApplicationPrivate::mouseEventCaps(event); |
3367 | bool dragVelocityLimitAvailable = (caps & QTouchDevice::Velocity) |
3368 | && styleHints->startDragVelocity(); |
3369 | bool overThreshold = qAbs(t: d) > (startDragThreshold >= 0 ? startDragThreshold : styleHints->startDragDistance()); |
3370 | if (dragVelocityLimitAvailable) { |
3371 | QVector2D velocityVec = QGuiApplicationPrivate::mouseEventVelocity(event); |
3372 | qreal velocity = axis == Qt::XAxis ? velocityVec.x() : velocityVec.y(); |
3373 | overThreshold |= qAbs(t: velocity) > styleHints->startDragVelocity(); |
3374 | } |
3375 | return overThreshold; |
3376 | } |
3377 | |
3378 | bool QQuickWindowPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const QTouchEvent::TouchPoint *tp, int startDragThreshold) |
3379 | { |
3380 | QStyleHints *styleHints = qApp->styleHints(); |
3381 | bool overThreshold = qAbs(t: d) > (startDragThreshold >= 0 ? startDragThreshold : styleHints->startDragDistance()); |
3382 | const bool dragVelocityLimitAvailable = (styleHints->startDragVelocity() > 0); |
3383 | if (!overThreshold && dragVelocityLimitAvailable) { |
3384 | qreal velocity = axis == Qt::XAxis ? tp->velocity().x() : tp->velocity().y(); |
3385 | overThreshold |= qAbs(t: velocity) > styleHints->startDragVelocity(); |
3386 | } |
3387 | return overThreshold; |
3388 | } |
3389 | |
3390 | bool QQuickWindowPrivate::dragOverThreshold(QVector2D delta) |
3391 | { |
3392 | int threshold = qApp->styleHints()->startDragDistance(); |
3393 | return qAbs(t: delta.x()) > threshold || qAbs(t: delta.y()) > threshold; |
3394 | } |
3395 | |
3396 | /*! |
3397 | \qmlproperty list<Object> Window::data |
3398 | \default |
3399 | |
3400 | The data property allows you to freely mix visual children, resources |
3401 | and other Windows in a Window. |
3402 | |
3403 | If you assign another Window to the data list, the nested window will |
3404 | become "transient for" the outer Window. |
3405 | |
3406 | If you assign an \l Item to the data list, it becomes a child of the |
3407 | Window's \l contentItem, so that it appears inside the window. The item's |
3408 | parent will be the window's contentItem, which is the root of the Item |
3409 | ownership tree within that Window. |
3410 | |
3411 | If you assign any other object type, it is added as a resource. |
3412 | |
3413 | It should not generally be necessary to refer to the \c data property, |
3414 | as it is the default property for Window and thus all child items are |
3415 | automatically assigned to this property. |
3416 | |
3417 | \sa QWindow::transientParent() |
3418 | */ |
3419 | |
3420 | void QQuickWindowPrivate::data_append(QQmlListProperty<QObject> *property, QObject *o) |
3421 | { |
3422 | if (!o) |
3423 | return; |
3424 | QQuickWindow *that = static_cast<QQuickWindow *>(property->object); |
3425 | if (QQuickWindow *window = qmlobject_cast<QQuickWindow *>(object: o)) { |
3426 | qCDebug(lcTransient) << window << "is transient for" << that; |
3427 | window->setTransientParent(that); |
3428 | } |
3429 | QQmlListProperty<QObject> itemProperty = QQuickItemPrivate::get(item: that->contentItem())->data(); |
3430 | itemProperty.append(&itemProperty, o); |
3431 | } |
3432 | |
3433 | int QQuickWindowPrivate::data_count(QQmlListProperty<QObject> *property) |
3434 | { |
3435 | QQuickWindow *win = static_cast<QQuickWindow*>(property->object); |
3436 | if (!win || !win->contentItem() || !QQuickItemPrivate::get(item: win->contentItem())->data().count) |
3437 | return 0; |
3438 | QQmlListProperty<QObject> itemProperty = QQuickItemPrivate::get(item: win->contentItem())->data(); |
3439 | return itemProperty.count(&itemProperty); |
3440 | } |
3441 | |
3442 | QObject *QQuickWindowPrivate::data_at(QQmlListProperty<QObject> *property, int i) |
3443 | { |
3444 | QQuickWindow *win = static_cast<QQuickWindow*>(property->object); |
3445 | QQmlListProperty<QObject> itemProperty = QQuickItemPrivate::get(item: win->contentItem())->data(); |
3446 | return itemProperty.at(&itemProperty, i); |
3447 | } |
3448 | |
3449 | void QQuickWindowPrivate::data_clear(QQmlListProperty<QObject> *property) |
3450 | { |
3451 | QQuickWindow *win = static_cast<QQuickWindow*>(property->object); |
3452 | QQmlListProperty<QObject> itemProperty = QQuickItemPrivate::get(item: win->contentItem())->data(); |
3453 | itemProperty.clear(&itemProperty); |
3454 | } |
3455 | |
3456 | void QQuickWindowPrivate::data_replace(QQmlListProperty<QObject> *property, int i, QObject *o) |
3457 | { |
3458 | QQuickWindow *win = static_cast<QQuickWindow*>(property->object); |
3459 | QQmlListProperty<QObject> itemProperty = QQuickItemPrivate::get(item: win->contentItem())->data(); |
3460 | itemProperty.replace(&itemProperty, i, o); |
3461 | } |
3462 | |
3463 | void QQuickWindowPrivate::data_removeLast(QQmlListProperty<QObject> *property) |
3464 | { |
3465 | QQuickWindow *win = static_cast<QQuickWindow*>(property->object); |
3466 | QQmlListProperty<QObject> itemProperty = QQuickItemPrivate::get(item: win->contentItem())->data(); |
3467 | itemProperty.removeLast(&itemProperty); |
3468 | } |
3469 | |
3470 | bool QQuickWindowPrivate::isRenderable() const |
3471 | { |
3472 | Q_Q(const QQuickWindow); |
3473 | return ((q->isExposed() && q->isVisible())) && q->geometry().isValid(); |
3474 | } |
3475 | |
3476 | void QQuickWindowPrivate::contextCreationFailureMessage(const QSurfaceFormat &format, |
3477 | QString *translatedMessage, |
3478 | QString *untranslatedMessage) |
3479 | { |
3480 | const QString contextType = QLatin1String("OpenGL" ); |
3481 | QString formatStr; |
3482 | QDebug(&formatStr) << format; |
3483 | #if defined(Q_OS_WIN32) |
3484 | const bool isDebug = QLibraryInfo::isDebugBuild(); |
3485 | const QString eglLibName = QLatin1String(isDebug ? "libEGLd.dll" : "libEGL.dll" ); |
3486 | const QString glesLibName = QLatin1String(isDebug ? "libGLESv2d.dll" : "libGLESv2.dll" ); |
3487 | //: %1 Context type (Open GL, EGL), %2 format, ANGLE %3, %4 library names |
3488 | const char msg[] = QT_TRANSLATE_NOOP("QQuickWindow" , |
3489 | "Failed to create %1 context for format %2.\n" |
3490 | "This is most likely caused by not having the necessary graphics drivers installed.\n\n" |
3491 | "Install a driver providing OpenGL 2.0 or higher, or, if this is not possible, " |
3492 | "make sure the ANGLE Open GL ES 2.0 emulation libraries (%3, %4 and d3dcompiler_*.dll) " |
3493 | "are available in the application executable's directory or in a location listed in PATH." ); |
3494 | *translatedMessage = QQuickWindow::tr(msg).arg(contextType, formatStr, eglLibName, glesLibName); |
3495 | *untranslatedMessage = QString::fromLatin1(msg).arg(contextType, formatStr, eglLibName, glesLibName); |
3496 | #else // Q_OS_WIN32 |
3497 | //: %1 Context type (Open GL, EGL), %2 format specification |
3498 | const char msg[] = QT_TRANSLATE_NOOP("QQuickWindow" , |
3499 | "Failed to create %1 context for format %2" ); |
3500 | *translatedMessage = QQuickWindow::tr(s: msg).arg(args: contextType, args&: formatStr); |
3501 | *untranslatedMessage = QString::fromLatin1(str: msg).arg(args: contextType, args&: formatStr); |
3502 | #endif // !Q_OS_WIN32 |
3503 | } |
3504 | |
3505 | void QQuickWindowPrivate::rhiCreationFailureMessage(const QString &backendName, |
3506 | QString *translatedMessage, |
3507 | QString *untranslatedMessage) |
3508 | { |
3509 | const char msg[] = QT_TRANSLATE_NOOP("QQuickWindow" , |
3510 | "Failed to initialize graphics backend for %1." ); |
3511 | *translatedMessage = QQuickWindow::tr(s: msg).arg(a: backendName); |
3512 | *untranslatedMessage = QString::fromLatin1(str: msg).arg(a: backendName); |
3513 | } |
3514 | |
3515 | #if QT_DEPRECATED_SINCE(5, 8) |
3516 | |
3517 | // ### Qt6: remove |
3518 | /*! |
3519 | Propagates an event \a e to a QQuickItem \a item on the window. |
3520 | |
3521 | Use \l QCoreApplication::sendEvent() directly instead. |
3522 | |
3523 | The return value is currently not used. |
3524 | |
3525 | \deprecated |
3526 | */ |
3527 | bool QQuickWindow::sendEvent(QQuickItem *item, QEvent *e) |
3528 | { |
3529 | Q_D(QQuickWindow); |
3530 | |
3531 | if (!item) { |
3532 | qWarning(msg: "QQuickWindow::sendEvent: Cannot send event to a null item" ); |
3533 | return false; |
3534 | } |
3535 | |
3536 | Q_ASSERT(e); |
3537 | |
3538 | switch (e->type()) { |
3539 | case QEvent::KeyPress: |
3540 | case QEvent::KeyRelease: |
3541 | e->accept(); |
3542 | QCoreApplication::sendEvent(receiver: item, event: e); |
3543 | while (!e->isAccepted() && (item = item->parentItem())) { |
3544 | e->accept(); |
3545 | QCoreApplication::sendEvent(receiver: item, event: e); |
3546 | } |
3547 | break; |
3548 | case QEvent::MouseButtonPress: |
3549 | case QEvent::MouseButtonRelease: |
3550 | case QEvent::MouseButtonDblClick: |
3551 | case QEvent::MouseMove: { |
3552 | // XXX todo - should sendEvent be doing this? how does it relate to forwarded events? |
3553 | d->hasFiltered.clear(); |
3554 | if (!d->sendFilteredMouseEvent(event: e, receiver: item, filteringParent: item->parentItem())) { |
3555 | // accept because qml items by default accept and have to explicitly opt out of accepting |
3556 | e->accept(); |
3557 | QCoreApplication::sendEvent(receiver: item, event: e); |
3558 | } |
3559 | } |
3560 | break; |
3561 | default: |
3562 | QCoreApplication::sendEvent(receiver: item, event: e); |
3563 | break; |
3564 | } |
3565 | |
3566 | return false; |
3567 | } |
3568 | |
3569 | #endif |
3570 | |
3571 | void QQuickWindowPrivate::cleanupNodes() |
3572 | { |
3573 | for (int ii = 0; ii < cleanupNodeList.count(); ++ii) |
3574 | delete cleanupNodeList.at(i: ii); |
3575 | cleanupNodeList.clear(); |
3576 | } |
3577 | |
3578 | void QQuickWindowPrivate::cleanupNodesOnShutdown(QQuickItem *item) |
3579 | { |
3580 | QQuickItemPrivate *p = QQuickItemPrivate::get(item); |
3581 | if (p->itemNodeInstance) { |
3582 | delete p->itemNodeInstance; |
3583 | p->itemNodeInstance = nullptr; |
3584 | |
3585 | if (p->extra.isAllocated()) { |
3586 | p->extra->opacityNode = nullptr; |
3587 | p->extra->clipNode = nullptr; |
3588 | p->extra->rootNode = nullptr; |
3589 | } |
3590 | |
3591 | p->paintNode = nullptr; |
3592 | |
3593 | p->dirty(QQuickItemPrivate::Window); |
3594 | } |
3595 | |
3596 | // Qt 6: Make invalidateSceneGraph a virtual member of QQuickItem |
3597 | if (p->flags & QQuickItem::ItemHasContents) { |
3598 | const QMetaObject *mo = item->metaObject(); |
3599 | int index = mo->indexOfSlot(slot: "invalidateSceneGraph()" ); |
3600 | if (index >= 0) { |
3601 | const QMetaMethod &method = mo->method(index); |
3602 | // Skip functions named invalidateSceneGraph() in QML items. |
3603 | if (strstr(haystack: method.enclosingMetaObject()->className(), needle: "_QML_" ) == nullptr) |
3604 | method.invoke(object: item, connectionType: Qt::DirectConnection); |
3605 | } |
3606 | } |
3607 | |
3608 | for (int ii = 0; ii < p->childItems.count(); ++ii) |
3609 | cleanupNodesOnShutdown(item: p->childItems.at(i: ii)); |
3610 | } |
3611 | |
3612 | // This must be called from the render thread, with the main thread frozen |
3613 | void QQuickWindowPrivate::cleanupNodesOnShutdown() |
3614 | { |
3615 | Q_Q(QQuickWindow); |
3616 | cleanupNodes(); |
3617 | cleanupNodesOnShutdown(item: contentItem); |
3618 | for (QSet<QQuickItem *>::const_iterator it = parentlessItems.begin(), cend = parentlessItems.end(); it != cend; ++it) |
3619 | cleanupNodesOnShutdown(item: *it); |
3620 | animationController->windowNodesDestroyed(); |
3621 | q->cleanupSceneGraph(); |
3622 | } |
3623 | |
3624 | void QQuickWindowPrivate::updateDirtyNodes() |
3625 | { |
3626 | qCDebug(DBG_DIRTY) << "QQuickWindowPrivate::updateDirtyNodes():" ; |
3627 | |
3628 | cleanupNodes(); |
3629 | |
3630 | QQuickItem *updateList = dirtyItemList; |
3631 | dirtyItemList = nullptr; |
3632 | if (updateList) QQuickItemPrivate::get(item: updateList)->prevDirtyItem = &updateList; |
3633 | |
3634 | while (updateList) { |
3635 | QQuickItem *item = updateList; |
3636 | QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(item); |
3637 | itemPriv->removeFromDirtyList(); |
3638 | |
3639 | qCDebug(DBG_DIRTY) << " QSGNode:" << item << qPrintable(itemPriv->dirtyToString()); |
3640 | updateDirtyNode(item); |
3641 | } |
3642 | } |
3643 | |
3644 | static inline QSGNode *qquickitem_before_paintNode(QQuickItemPrivate *d) |
3645 | { |
3646 | const QList<QQuickItem *> childItems = d->paintOrderChildItems(); |
3647 | QQuickItem *before = nullptr; |
3648 | for (int i=0; i<childItems.size(); ++i) { |
3649 | QQuickItemPrivate *dd = QQuickItemPrivate::get(item: childItems.at(i)); |
3650 | // Perform the same check as the in fetchNextNode below. |
3651 | if (dd->z() < 0 && (dd->explicitVisible || (dd->extra.isAllocated() && dd->extra->effectRefCount))) |
3652 | before = childItems.at(i); |
3653 | else |
3654 | break; |
3655 | } |
3656 | return Q_UNLIKELY(before) ? QQuickItemPrivate::get(item: before)->itemNode() : nullptr; |
3657 | } |
3658 | |
3659 | static QSGNode *fetchNextNode(QQuickItemPrivate *itemPriv, int &ii, bool &returnedPaintNode) |
3660 | { |
3661 | QList<QQuickItem *> orderedChildren = itemPriv->paintOrderChildItems(); |
3662 | |
3663 | for (; ii < orderedChildren.count() && orderedChildren.at(i: ii)->z() < 0; ++ii) { |
3664 | QQuickItemPrivate *childPrivate = QQuickItemPrivate::get(item: orderedChildren.at(i: ii)); |
3665 | if (!childPrivate->explicitVisible && |
3666 | (!childPrivate->extra.isAllocated() || !childPrivate->extra->effectRefCount)) |
3667 | continue; |
3668 | |
3669 | ii++; |
3670 | return childPrivate->itemNode(); |
3671 | } |
3672 | |
3673 | if (itemPriv->paintNode && !returnedPaintNode) { |
3674 | returnedPaintNode = true; |
3675 | return itemPriv->paintNode; |
3676 | } |
3677 | |
3678 | for (; ii < orderedChildren.count(); ++ii) { |
3679 | QQuickItemPrivate *childPrivate = QQuickItemPrivate::get(item: orderedChildren.at(i: ii)); |
3680 | if (!childPrivate->explicitVisible && |
3681 | (!childPrivate->extra.isAllocated() || !childPrivate->extra->effectRefCount)) |
3682 | continue; |
3683 | |
3684 | ii++; |
3685 | return childPrivate->itemNode(); |
3686 | } |
3687 | |
3688 | return nullptr; |
3689 | } |
3690 | |
3691 | void QQuickWindowPrivate::updateDirtyNode(QQuickItem *item) |
3692 | { |
3693 | QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(item); |
3694 | quint32 dirty = itemPriv->dirtyAttributes; |
3695 | itemPriv->dirtyAttributes = 0; |
3696 | |
3697 | if ((dirty & QQuickItemPrivate::TransformUpdateMask) || |
3698 | (dirty & QQuickItemPrivate::Size && itemPriv->origin() != QQuickItem::TopLeft && |
3699 | (itemPriv->scale() != 1. || itemPriv->rotation() != 0.))) { |
3700 | |
3701 | QMatrix4x4 matrix; |
3702 | |
3703 | if (itemPriv->x != 0. || itemPriv->y != 0.) |
3704 | matrix.translate(x: itemPriv->x, y: itemPriv->y); |
3705 | |
3706 | for (int ii = itemPriv->transforms.count() - 1; ii >= 0; --ii) |
3707 | itemPriv->transforms.at(i: ii)->applyTo(matrix: &matrix); |
3708 | |
3709 | if (itemPriv->scale() != 1. || itemPriv->rotation() != 0.) { |
3710 | QPointF origin = item->transformOriginPoint(); |
3711 | matrix.translate(x: origin.x(), y: origin.y()); |
3712 | if (itemPriv->scale() != 1.) |
3713 | matrix.scale(x: itemPriv->scale(), y: itemPriv->scale()); |
3714 | if (itemPriv->rotation() != 0.) |
3715 | matrix.rotate(angle: itemPriv->rotation(), x: 0, y: 0, z: 1); |
3716 | matrix.translate(x: -origin.x(), y: -origin.y()); |
3717 | } |
3718 | |
3719 | itemPriv->itemNode()->setMatrix(matrix); |
3720 | } |
3721 | |
3722 | bool clipEffectivelyChanged = (dirty & (QQuickItemPrivate::Clip | QQuickItemPrivate::Window)) && |
3723 | ((item->clip() == false) != (itemPriv->clipNode() == nullptr)); |
3724 | int effectRefCount = itemPriv->extra.isAllocated()?itemPriv->extra->effectRefCount:0; |
3725 | bool effectRefEffectivelyChanged = (dirty & (QQuickItemPrivate::EffectReference | QQuickItemPrivate::Window)) && |
3726 | ((effectRefCount == 0) != (itemPriv->rootNode() == nullptr)); |
3727 | |
3728 | if (clipEffectivelyChanged) { |
3729 | QSGNode *parent = itemPriv->opacityNode() ? (QSGNode *) itemPriv->opacityNode() : |
3730 | (QSGNode *) itemPriv->itemNode(); |
3731 | QSGNode *child = itemPriv->rootNode(); |
3732 | |
3733 | if (item->clip()) { |
3734 | Q_ASSERT(itemPriv->clipNode() == nullptr); |
3735 | QQuickDefaultClipNode *clip = new QQuickDefaultClipNode(item->clipRect()); |
3736 | itemPriv->extra.value().clipNode = clip; |
3737 | clip->update(); |
3738 | |
3739 | if (!child) { |
3740 | parent->reparentChildNodesTo(newParent: clip); |
3741 | parent->appendChildNode(node: clip); |
3742 | } else { |
3743 | parent->removeChildNode(node: child); |
3744 | clip->appendChildNode(node: child); |
3745 | parent->appendChildNode(node: clip); |
3746 | } |
3747 | |
3748 | } else { |
3749 | QQuickDefaultClipNode *clip = itemPriv->clipNode(); |
3750 | Q_ASSERT(clip); |
3751 | parent->removeChildNode(node: clip); |
3752 | if (child) { |
3753 | clip->removeChildNode(node: child); |
3754 | parent->appendChildNode(node: child); |
3755 | } else { |
3756 | clip->reparentChildNodesTo(newParent: parent); |
3757 | } |
3758 | |
3759 | delete itemPriv->clipNode(); |
3760 | itemPriv->extra->clipNode = nullptr; |
3761 | } |
3762 | } |
3763 | |
3764 | if (effectRefEffectivelyChanged) { |
3765 | if (dirty & QQuickItemPrivate::ChildrenUpdateMask) |
3766 | itemPriv->childContainerNode()->removeAllChildNodes(); |
3767 | |
3768 | QSGNode *parent = itemPriv->clipNode(); |
3769 | if (!parent) |
3770 | parent = itemPriv->opacityNode(); |
3771 | if (!parent) |
3772 | parent = itemPriv->itemNode(); |
3773 | |
3774 | if (itemPriv->extra.isAllocated() && itemPriv->extra->effectRefCount) { |
3775 | Q_ASSERT(itemPriv->rootNode() == nullptr); |
3776 | QSGRootNode *root = new QSGRootNode(); |
3777 | itemPriv->extra->rootNode = root; |
3778 | parent->reparentChildNodesTo(newParent: root); |
3779 | parent->appendChildNode(node: root); |
3780 | } else { |
3781 | Q_ASSERT(itemPriv->rootNode() != nullptr); |
3782 | QSGRootNode *root = itemPriv->rootNode(); |
3783 | parent->removeChildNode(node: root); |
3784 | root->reparentChildNodesTo(newParent: parent); |
3785 | delete itemPriv->rootNode(); |
3786 | itemPriv->extra->rootNode = nullptr; |
3787 | } |
3788 | } |
3789 | |
3790 | if (dirty & QQuickItemPrivate::ChildrenUpdateMask) { |
3791 | int ii = 0; |
3792 | bool fetchedPaintNode = false; |
3793 | QList<QQuickItem *> orderedChildren = itemPriv->paintOrderChildItems(); |
3794 | int desiredNodesSize = orderedChildren.size() + (itemPriv->paintNode ? 1 : 0); |
3795 | |
3796 | // now start making current state match the promised land of |
3797 | // desiredNodes. in the case of our current state matching desiredNodes |
3798 | // (though why would we get ChildrenUpdateMask with no changes?) then we |
3799 | // should make no changes at all. |
3800 | |
3801 | // how many nodes did we process, when examining changes |
3802 | int desiredNodesProcessed = 0; |
3803 | |
3804 | // currentNode is how far, in our present tree, we have processed. we |
3805 | // make use of this later on to trim the current child list if the |
3806 | // desired list is shorter. |
3807 | QSGNode *groupNode = itemPriv->childContainerNode(); |
3808 | QSGNode *currentNode = groupNode->firstChild(); |
3809 | int added = 0; |
3810 | int removed = 0; |
3811 | int replaced = 0; |
3812 | QSGNode *desiredNode = nullptr; |
3813 | |
3814 | while (currentNode && (desiredNode = fetchNextNode(itemPriv, ii, returnedPaintNode&: fetchedPaintNode))) { |
3815 | // uh oh... reality and our utopic paradise are diverging! |
3816 | // we need to reconcile this... |
3817 | if (currentNode != desiredNode) { |
3818 | // for now, we're just removing the node from the children - |
3819 | // and replacing it with the new node. |
3820 | if (desiredNode->parent()) |
3821 | desiredNode->parent()->removeChildNode(node: desiredNode); |
3822 | groupNode->insertChildNodeAfter(node: desiredNode, after: currentNode); |
3823 | groupNode->removeChildNode(node: currentNode); |
3824 | replaced++; |
3825 | |
3826 | // since we just replaced currentNode, we also need to reset |
3827 | // the pointer. |
3828 | currentNode = desiredNode; |
3829 | } |
3830 | |
3831 | currentNode = currentNode->nextSibling(); |
3832 | desiredNodesProcessed++; |
3833 | } |
3834 | |
3835 | // if we didn't process as many nodes as in the new list, then we have |
3836 | // more nodes at the end of desiredNodes to append to our list. |
3837 | // this will be the case when adding new nodes, for instance. |
3838 | if (desiredNodesProcessed < desiredNodesSize) { |
3839 | while ((desiredNode = fetchNextNode(itemPriv, ii, returnedPaintNode&: fetchedPaintNode))) { |
3840 | if (desiredNode->parent()) |
3841 | desiredNode->parent()->removeChildNode(node: desiredNode); |
3842 | groupNode->appendChildNode(node: desiredNode); |
3843 | added++; |
3844 | } |
3845 | } else if (currentNode) { |
3846 | // on the other hand, if we processed less than our current node |
3847 | // tree, then nodes have been _removed_ from the scene, and we need |
3848 | // to take care of that here. |
3849 | while (currentNode) { |
3850 | QSGNode *node = currentNode->nextSibling(); |
3851 | groupNode->removeChildNode(node: currentNode); |
3852 | currentNode = node; |
3853 | removed++; |
3854 | } |
3855 | } |
3856 | } |
3857 | |
3858 | if ((dirty & QQuickItemPrivate::Size) && itemPriv->clipNode()) { |
3859 | itemPriv->clipNode()->setRect(item->clipRect()); |
3860 | itemPriv->clipNode()->update(); |
3861 | } |
3862 | |
3863 | if (dirty & (QQuickItemPrivate::OpacityValue | QQuickItemPrivate::Visible |
3864 | | QQuickItemPrivate::HideReference | QQuickItemPrivate::Window)) |
3865 | { |
3866 | qreal opacity = itemPriv->explicitVisible && (!itemPriv->extra.isAllocated() || itemPriv->extra->hideRefCount == 0) |
3867 | ? itemPriv->opacity() : qreal(0); |
3868 | |
3869 | if (opacity != 1 && !itemPriv->opacityNode()) { |
3870 | QSGOpacityNode *node = new QSGOpacityNode; |
3871 | itemPriv->extra.value().opacityNode = node; |
3872 | |
3873 | QSGNode *parent = itemPriv->itemNode(); |
3874 | QSGNode *child = itemPriv->clipNode(); |
3875 | if (!child) |
3876 | child = itemPriv->rootNode(); |
3877 | |
3878 | if (child) { |
3879 | parent->removeChildNode(node: child); |
3880 | node->appendChildNode(node: child); |
3881 | parent->appendChildNode(node); |
3882 | } else { |
3883 | parent->reparentChildNodesTo(newParent: node); |
3884 | parent->appendChildNode(node); |
3885 | } |
3886 | } |
3887 | if (itemPriv->opacityNode()) |
3888 | itemPriv->opacityNode()->setOpacity(opacity); |
3889 | } |
3890 | |
3891 | if (dirty & QQuickItemPrivate::ContentUpdateMask) { |
3892 | |
3893 | if (itemPriv->flags & QQuickItem::ItemHasContents) { |
3894 | updatePaintNodeData.transformNode = itemPriv->itemNode(); |
3895 | itemPriv->paintNode = item->updatePaintNode(itemPriv->paintNode, &updatePaintNodeData); |
3896 | |
3897 | Q_ASSERT(itemPriv->paintNode == nullptr || |
3898 | itemPriv->paintNode->parent() == nullptr || |
3899 | itemPriv->paintNode->parent() == itemPriv->childContainerNode()); |
3900 | |
3901 | if (itemPriv->paintNode && itemPriv->paintNode->parent() == nullptr) { |
3902 | QSGNode *before = qquickitem_before_paintNode(d: itemPriv); |
3903 | if (before && before->parent()) { |
3904 | Q_ASSERT(before->parent() == itemPriv->childContainerNode()); |
3905 | itemPriv->childContainerNode()->insertChildNodeAfter(node: itemPriv->paintNode, after: before); |
3906 | } else { |
3907 | itemPriv->childContainerNode()->prependChildNode(node: itemPriv->paintNode); |
3908 | } |
3909 | } |
3910 | } else if (itemPriv->paintNode) { |
3911 | delete itemPriv->paintNode; |
3912 | itemPriv->paintNode = nullptr; |
3913 | } |
3914 | } |
3915 | |
3916 | #ifndef QT_NO_DEBUG |
3917 | // Check consistency. |
3918 | |
3919 | QList<QSGNode *> nodes; |
3920 | nodes << itemPriv->itemNodeInstance |
3921 | << itemPriv->opacityNode() |
3922 | << itemPriv->clipNode() |
3923 | << itemPriv->rootNode() |
3924 | << itemPriv->paintNode; |
3925 | nodes.removeAll(t: 0); |
3926 | |
3927 | Q_ASSERT(nodes.constFirst() == itemPriv->itemNodeInstance); |
3928 | for (int i=1; i<nodes.size(); ++i) { |
3929 | QSGNode *n = nodes.at(i); |
3930 | // Failing this means we messed up reparenting |
3931 | Q_ASSERT(n->parent() == nodes.at(i-1)); |
3932 | // Only the paintNode and the one who is childContainer may have more than one child. |
3933 | Q_ASSERT(n == itemPriv->paintNode || n == itemPriv->childContainerNode() || n->childCount() == 1); |
3934 | } |
3935 | #endif |
3936 | |
3937 | } |
3938 | |
3939 | bool QQuickWindowPrivate::emitError(QQuickWindow::SceneGraphError error, const QString &msg) |
3940 | { |
3941 | Q_Q(QQuickWindow); |
3942 | static const QMetaMethod errorSignal = QMetaMethod::fromSignal(signal: &QQuickWindow::sceneGraphError); |
3943 | if (q->isSignalConnected(signal: errorSignal)) { |
3944 | emit q->sceneGraphError(error, message: msg); |
3945 | return true; |
3946 | } |
3947 | return false; |
3948 | } |
3949 | |
3950 | void QQuickWindow::maybeUpdate() |
3951 | { |
3952 | Q_D(QQuickWindow); |
3953 | if (d->renderControl) |
3954 | QQuickRenderControlPrivate::get(renderControl: d->renderControl)->maybeUpdate(); |
3955 | else if (d->windowManager) |
3956 | d->windowManager->maybeUpdate(window: this); |
3957 | } |
3958 | |
3959 | void QQuickWindow::cleanupSceneGraph() |
3960 | { |
3961 | Q_D(QQuickWindow); |
3962 | #if QT_CONFIG(opengl) |
3963 | delete d->vaoHelper; |
3964 | d->vaoHelper = nullptr; |
3965 | #endif |
3966 | if (!d->renderer) |
3967 | return; |
3968 | |
3969 | delete d->renderer->rootNode(); |
3970 | delete d->renderer; |
3971 | d->renderer = nullptr; |
3972 | |
3973 | d->runAndClearJobs(jobs: &d->beforeSynchronizingJobs); |
3974 | d->runAndClearJobs(jobs: &d->afterSynchronizingJobs); |
3975 | d->runAndClearJobs(jobs: &d->beforeRenderingJobs); |
3976 | d->runAndClearJobs(jobs: &d->afterRenderingJobs); |
3977 | d->runAndClearJobs(jobs: &d->afterSwapJobs); |
3978 | } |
3979 | |
3980 | void QQuickWindow::setTransientParent_helper(QQuickWindow *window) |
3981 | { |
3982 | qCDebug(lcTransient) << this << "is transient for" << window; |
3983 | setTransientParent(window); |
3984 | disconnect(sender: sender(), SIGNAL(windowChanged(QQuickWindow*)), |
3985 | receiver: this, SLOT(setTransientParent_helper(QQuickWindow*))); |
3986 | } |
3987 | |
3988 | /*! |
3989 | Returns the OpenGL context used for rendering. |
3990 | |
3991 | \note If the scene graph is not ready, or the scene graph is not using |
3992 | OpenGL (or RHI over OpenGL), this function will return null. |
3993 | |
3994 | \sa sceneGraphInitialized(), sceneGraphInvalidated() |
3995 | */ |
3996 | QOpenGLContext *QQuickWindow::openglContext() const |
3997 | { |
3998 | #if QT_CONFIG(opengl) |
3999 | Q_D(const QQuickWindow); |
4000 | if (d->context && d->context->isValid()) { |
4001 | QSGRendererInterface *rif = d->context->sceneGraphContext()->rendererInterface(renderContext: d->context); |
4002 | if (rif) { |
4003 | return reinterpret_cast<QOpenGLContext *>(rif->getResource(window: const_cast<QQuickWindow *>(this), |
4004 | resource: QSGRendererInterface::OpenGLContextResource)); |
4005 | } |
4006 | } |
4007 | #endif |
4008 | return nullptr; |
4009 | } |
4010 | |
4011 | /*! |
4012 | Returns true if the scene graph has been initialized; otherwise returns false. |
4013 | */ |
4014 | bool QQuickWindow::isSceneGraphInitialized() const |
4015 | { |
4016 | Q_D(const QQuickWindow); |
4017 | return d->context != nullptr && d->context->isValid(); |
4018 | } |
4019 | |
4020 | /*! |
4021 | \fn void QQuickWindow::frameSwapped() |
4022 | |
4023 | This signal is emitted when a frame has been queued for presenting. With |
4024 | vertical synchronization enabled the signal is emitted at most once per |
4025 | vsync interval in a continuously animating scene. |
4026 | |
4027 | This signal will be emitted from the scene graph rendering thread. |
4028 | */ |
4029 | |
4030 | /*! |
4031 | \qmlsignal QtQuick.Window::Window::frameSwapped() |
4032 | |
4033 | This signal is emitted when a frame has been queued for presenting. With |
4034 | vertical synchronization enabled the signal is emitted at most once per |
4035 | vsync interval in a continuously animating scene. |
4036 | */ |
4037 | |
4038 | /*! |
4039 | \fn void QQuickWindow::sceneGraphInitialized() |
4040 | |
4041 | This signal is emitted when the scene graph has been initialized. |
4042 | |
4043 | This signal will be emitted from the scene graph rendering thread. |
4044 | */ |
4045 | |
4046 | /*! |
4047 | \qmlsignal QtQuick.Window::Window::sceneGraphInitialized() |
4048 | \internal |
4049 | */ |
4050 | |
4051 | /*! |
4052 | \fn void QQuickWindow::sceneGraphInvalidated() |
4053 | |
4054 | This signal is emitted when the scene graph has been invalidated. |
4055 | |
4056 | This signal implies that the graphics rendering context used |
4057 | has been invalidated and all user resources tied to that context |
4058 | should be released. |
4059 | |
4060 | In the case of the default OpenGL adaptation the context of this |
4061 | window will be bound when this function is called. The only exception |
4062 | is if the native OpenGL has been destroyed outside Qt's control, |
4063 | for instance through EGL_CONTEXT_LOST. |
4064 | |
4065 | This signal will be emitted from the scene graph rendering thread. |
4066 | */ |
4067 | |
4068 | /*! |
4069 | \qmlsignal QtQuick.Window::Window::sceneGraphInvalidated() |
4070 | \internal |
4071 | */ |
4072 | |
4073 | /*! |
4074 | \fn void QQuickWindow::sceneGraphError(SceneGraphError error, const QString &message) |
4075 | |
4076 | This signal is emitted when an \a error occurred during scene graph initialization. |
4077 | |
4078 | Applications should connect to this signal if they wish to handle errors, |
4079 | like graphics context creation failures, in a custom way. When no slot is |
4080 | connected to the signal, the behavior will be different: Quick will print |
4081 | the \a message, or show a message box, and terminate the application. |
4082 | |
4083 | This signal will be emitted from the GUI thread. |
4084 | |
4085 | \since 5.3 |
4086 | */ |
4087 | |
4088 | /*! |
4089 | \qmlsignal QtQuick.Window::Window::sceneGraphError(SceneGraphError error, QString message) |
4090 | |
4091 | This signal is emitted when an \a error occurred during scene graph initialization. |
4092 | |
4093 | You can implement onSceneGraphError(error, message) to handle errors, |
4094 | such as graphics context creation failures, in a custom way. |
4095 | If no handler is connected to this signal, Quick will print the \a message, |
4096 | or show a message box, and terminate the application. |
4097 | |
4098 | \since 5.3 |
4099 | */ |
4100 | |
4101 | /*! |
4102 | \class QQuickCloseEvent |
4103 | \internal |
4104 | \since 5.1 |
4105 | |
4106 | \inmodule QtQuick |
4107 | |
4108 | \brief Notification that a \l QQuickWindow is about to be closed |
4109 | */ |
4110 | /*! |
4111 | \qmltype CloseEvent |
4112 | \instantiates QQuickCloseEvent |
4113 | \inqmlmodule QtQuick.Window |
4114 | \ingroup qtquick-visual |
4115 | \brief Notification that a \l Window is about to be closed. |
4116 | \since 5.1 |
4117 | |
4118 | Notification that a window is about to be closed by the windowing system |
4119 | (e.g. the user clicked the title bar close button). The CloseEvent contains |
4120 | an accepted property which can be set to false to abort closing the window. |
4121 | |
4122 | \sa QQuickWindow::closing() |
4123 | */ |
4124 | |
4125 | /*! |
4126 | \qmlproperty bool CloseEvent::accepted |
4127 | |
4128 | This property indicates whether the application will allow the user to |
4129 | close the window. It is true by default. |
4130 | */ |
4131 | |
4132 | /*! |
4133 | \fn void QQuickWindow::closing(QQuickCloseEvent *close) |
4134 | \since 5.1 |
4135 | |
4136 | This signal is emitted when the window receives the event \a close from |
4137 | the windowing system. |
4138 | |
4139 | On \macOs, Qt will create a menu item \c Quit if there is no menu item |
4140 | whose text is "quit" or "exit". This menu item calls the \c QCoreApplication::quit |
4141 | signal, not the \c QQuickWindow::closing() signal. |
4142 | |
4143 | \sa {QMenuBar as a Global Menu Bar} |
4144 | */ |
4145 | |
4146 | /*! |
4147 | \qmlsignal QtQuick.Window::Window::closing(CloseEvent close) |
4148 | \since 5.1 |
4149 | |
4150 | This signal is emitted when the user tries to close the window. |
4151 | |
4152 | This signal includes a \a close parameter. The \c {close.accepted} |
4153 | property is true by default so that the window is allowed to close; but you |
4154 | can implement an \c onClosing handler and set \c {close.accepted = false} if |
4155 | you need to do something else before the window can be closed. |
4156 | */ |
4157 | |
4158 | #if QT_CONFIG(opengl) |
4159 | /*! |
4160 | Sets the render target for this window to be \a fbo. |
4161 | |
4162 | The specified fbo must be created in the context of the window |
4163 | or one that shares with it. |
4164 | |
4165 | \note |
4166 | This function only has an effect when using the default OpenGL scene |
4167 | graph adaptation. |
4168 | |
4169 | \note This function has no effect when running on the RHI graphics abstraction. |
4170 | |
4171 | \warning |
4172 | This function can only be called from the thread doing |
4173 | the rendering. |
4174 | */ |
4175 | |
4176 | void QQuickWindow::setRenderTarget(QOpenGLFramebufferObject *fbo) |
4177 | { |
4178 | Q_D(QQuickWindow); |
4179 | if (d->rhi) |
4180 | return; |
4181 | |
4182 | if (d->context && QThread::currentThread() != d->context->thread()) { |
4183 | qWarning(msg: "QQuickWindow::setRenderTarget: Cannot set render target from outside the rendering thread" ); |
4184 | return; |
4185 | } |
4186 | |
4187 | d->renderTarget = fbo; |
4188 | if (fbo) { |
4189 | d->renderTargetId = fbo->handle(); |
4190 | d->renderTargetSize = fbo->size(); |
4191 | } else { |
4192 | d->renderTargetId = 0; |
4193 | d->renderTargetSize = QSize(); |
4194 | } |
4195 | } |
4196 | #endif |
4197 | /*! |
4198 | \overload |
4199 | |
4200 | Sets the render target for this window to be an FBO with |
4201 | \a fboId and \a size. |
4202 | |
4203 | The specified FBO must be created in the context of the window |
4204 | or one that shares with it. |
4205 | |
4206 | \note \a fboId can also be set to 0. In this case rendering will target the |
4207 | default framebuffer of whichever surface is current when the scenegraph |
4208 | renders. \a size must still be valid, specifying the dimensions of the |
4209 | surface. |
4210 | |
4211 | \note |
4212 | This function only has an effect when using the default OpenGL scene |
4213 | graph adaptation. |
4214 | |
4215 | \warning |
4216 | This function can only be called from the thread doing |
4217 | the rendering. |
4218 | */ |
4219 | |
4220 | void QQuickWindow::setRenderTarget(uint fboId, const QSize &size) |
4221 | { |
4222 | Q_D(QQuickWindow); |
4223 | if (d->context && QThread::currentThread() != d->context->thread()) { |
4224 | qWarning(msg: "QQuickWindow::setRenderThread: Cannot set render target from outside the rendering thread" ); |
4225 | return; |
4226 | } |
4227 | |
4228 | d->renderTargetId = fboId; |
4229 | d->renderTargetSize = size; |
4230 | |
4231 | // Unset any previously set instance... |
4232 | d->renderTarget = nullptr; |
4233 | } |
4234 | |
4235 | |
4236 | /*! |
4237 | Returns the FBO id of the render target when set; otherwise returns 0. |
4238 | */ |
4239 | uint QQuickWindow::renderTargetId() const |
4240 | { |
4241 | Q_D(const QQuickWindow); |
4242 | return d->renderTargetId; |
4243 | } |
4244 | |
4245 | /*! |
4246 | Returns the size of the currently set render target; otherwise returns an empty size. |
4247 | */ |
4248 | QSize QQuickWindow::renderTargetSize() const |
4249 | { |
4250 | Q_D(const QQuickWindow); |
4251 | return d->renderTargetSize; |
4252 | } |
4253 | |
4254 | |
4255 | |
4256 | #if QT_CONFIG(opengl) |
4257 | /*! |
4258 | Returns the render target for this window. |
4259 | |
4260 | The default is to render to the surface of the window, in which |
4261 | case the render target is 0. |
4262 | |
4263 | \note This function will return nullptr when not using the OpenGL scene |
4264 | graph adaptation. |
4265 | |
4266 | \note This function has no effect and returns nullptr when running on the |
4267 | RHI graphics abstraction. |
4268 | */ |
4269 | QOpenGLFramebufferObject *QQuickWindow::renderTarget() const |
4270 | { |
4271 | Q_D(const QQuickWindow); |
4272 | return d->renderTarget; |
4273 | } |
4274 | #endif |
4275 | |
4276 | /*! |
4277 | Grabs the contents of the window and returns it as an image. |
4278 | |
4279 | It is possible to call the grabWindow() function when the window is not |
4280 | visible. This requires that the window is \l{QWindow::create()} {created} |
4281 | and has a valid size and that no other QQuickWindow instances are rendering |
4282 | in the same process. |
4283 | |
4284 | \warning Calling this function will cause performance problems. |
4285 | |
4286 | \warning This function can only be called from the GUI thread. |
4287 | */ |
4288 | QImage QQuickWindow::grabWindow() |
4289 | { |
4290 | Q_D(QQuickWindow); |
4291 | |
4292 | if (!isVisible() && !d->renderControl) { |
4293 | // backends like software and d3d12 can grab regardless of the window state |
4294 | if (d->windowManager && (d->windowManager->flags() & QSGRenderLoop::SupportsGrabWithoutExpose)) |
4295 | return d->windowManager->grab(window: this); |
4296 | } |
4297 | |
4298 | if (!isVisible() && !d->renderControl) { |
4299 | if (d->rhi) { |
4300 | // ### we may need a full offscreen round when non-exposed... |
4301 | |
4302 | if (d->renderControl) |
4303 | return d->renderControl->grab(); |
4304 | else if (d->windowManager) |
4305 | return d->windowManager->grab(window: this); |
4306 | return QImage(); |
4307 | } |
4308 | |
4309 | #if QT_CONFIG(opengl) |
4310 | auto openglRenderContext = static_cast<QSGDefaultRenderContext *>(d->context); |
4311 | if (!openglRenderContext->openglContext()) { |
4312 | if (!handle() || !size().isValid()) { |
4313 | qWarning(msg: "QQuickWindow::grabWindow: window must be created and have a valid size" ); |
4314 | return QImage(); |
4315 | } |
4316 | |
4317 | QOpenGLContext context; |
4318 | context.setFormat(requestedFormat()); |
4319 | context.setShareContext(qt_gl_global_share_context()); |
4320 | context.create(); |
4321 | context.makeCurrent(surface: this); |
4322 | QSGDefaultRenderContext::InitParams rcParams; |
4323 | rcParams.openGLContext = &context; |
4324 | d->context->initialize(params: &rcParams); |
4325 | |
4326 | d->polishItems(); |
4327 | d->syncSceneGraph(); |
4328 | d->renderSceneGraph(size: size()); |
4329 | |
4330 | bool alpha = format().alphaBufferSize() > 0 && color().alpha() < 255; |
4331 | QImage image = qt_gl_read_framebuffer(size: size() * effectiveDevicePixelRatio(), alpha_format: alpha, include_alpha: alpha); |
4332 | image.setDevicePixelRatio(effectiveDevicePixelRatio()); |
4333 | d->cleanupNodesOnShutdown(); |
4334 | d->context->invalidate(); |
4335 | context.doneCurrent(); |
4336 | |
4337 | return image; |
4338 | } |
4339 | #endif |
4340 | } |
4341 | |
4342 | if (d->renderControl) |
4343 | return d->renderControl->grab(); |
4344 | else if (d->windowManager) |
4345 | return d->windowManager->grab(window: this); |
4346 | return QImage(); |
4347 | } |
4348 | |
4349 | /*! |
4350 | Returns an incubation controller that splices incubation between frames |
4351 | for this window. QQuickView automatically installs this controller for you, |
4352 | otherwise you will need to install it yourself using \l{QQmlEngine::setIncubationController()}. |
4353 | |
4354 | The controller is owned by the window and will be destroyed when the window |
4355 | is deleted. |
4356 | */ |
4357 | QQmlIncubationController *QQuickWindow::incubationController() const |
4358 | { |
4359 | Q_D(const QQuickWindow); |
4360 | |
4361 | if (!d->windowManager) |
4362 | return nullptr; // TODO: make sure that this is safe |
4363 | |
4364 | if (!d->incubationController) |
4365 | d->incubationController = new QQuickWindowIncubationController(d->windowManager); |
4366 | return d->incubationController; |
4367 | } |
4368 | |
4369 | |
4370 | |
4371 | /*! |
4372 | \enum QQuickWindow::CreateTextureOption |
4373 | |
4374 | The CreateTextureOption enums are used to customize a texture is wrapped. |
4375 | |
4376 | \value TextureHasAlphaChannel The texture has an alpha channel and should |
4377 | be drawn using blending. |
4378 | |
4379 | \value TextureHasMipmaps The texture has mipmaps and can be drawn with |
4380 | mipmapping enabled. |
4381 | |
4382 | \value TextureOwnsGLTexture The texture object owns the texture id and |
4383 | will delete the OpenGL texture when the texture object is deleted. |
4384 | |
4385 | \value TextureCanUseAtlas The image can be uploaded into a texture atlas. |
4386 | |
4387 | \value TextureIsOpaque The texture will return false for |
4388 | QSGTexture::hasAlphaChannel() and will not be blended. This flag was added |
4389 | in Qt 5.6. |
4390 | |
4391 | */ |
4392 | |
4393 | /*! |
4394 | \enum QQuickWindow::SceneGraphError |
4395 | |
4396 | This enum describes the error in a sceneGraphError() signal. |
4397 | |
4398 | \value ContextNotAvailable graphics context creation failed. This typically means that |
4399 | no suitable OpenGL implementation was found, for example because no graphics drivers |
4400 | are installed and so no OpenGL 2 support is present. On mobile and embedded boards |
4401 | that use OpenGL ES such an error is likely to indicate issues in the windowing system |
4402 | integration and possibly an incorrect configuration of Qt. |
4403 | |
4404 | \since 5.3 |
4405 | */ |
4406 | |
4407 | /*! |
4408 | \enum QQuickWindow::TextRenderType |
4409 | \since 5.10 |
4410 | |
4411 | This enum describes the default render type of text-like elements in Qt |
4412 | Quick (\l Text, \l TextInput, etc.). |
4413 | |
4414 | Select NativeTextRendering if you prefer text to look native on the target |
4415 | platform and do not require advanced features such as transformation of the |
4416 | text. Using such features in combination with the NativeTextRendering |
4417 | render type will lend poor and sometimes pixelated results. |
4418 | |
4419 | \value QtTextRendering Use Qt's own rasterization algorithm. |
4420 | |
4421 | \value NativeTextRendering Use the operating system's native rasterizer for text. |
4422 | */ |
4423 | |
4424 | /*! |
4425 | \fn void QQuickWindow::beforeSynchronizing() |
4426 | |
4427 | This signal is emitted before the scene graph is synchronized with the QML state. |
4428 | |
4429 | This signal can be used to do any preparation required before calls to |
4430 | QQuickItem::updatePaintNode(). |
4431 | |
4432 | The OpenGL context used for rendering the scene graph will be bound at this point. |
4433 | |
4434 | \warning This signal is emitted from the scene graph rendering thread. If your |
4435 | slot function needs to finish before execution continues, you must make sure that |
4436 | the connection is direct (see Qt::ConnectionType). |
4437 | |
4438 | \warning Make very sure that a signal handler for beforeSynchronizing leaves the GL |
4439 | context in the same state as it was when the signal handler was entered. Failing to |
4440 | do so can result in the scene not rendering properly. |
4441 | |
4442 | \sa resetOpenGLState() |
4443 | */ |
4444 | |
4445 | /*! |
4446 | \qmlsignal QtQuick.Window::Window::beforeSynchronizing() |
4447 | \internal |
4448 | */ |
4449 | |
4450 | /*! |
4451 | \fn void QQuickWindow::afterSynchronizing() |
4452 | |
4453 | This signal is emitted after the scene graph is synchronized with the QML state. |
4454 | |
4455 | This signal can be used to do preparation required after calls to |
4456 | QQuickItem::updatePaintNode(), while the GUI thread is still locked. |
4457 | |
4458 | The graphics context used for rendering the scene graph will be bound at this point. |
4459 | |
4460 | \warning This signal is emitted from the scene graph rendering thread. If your |
4461 | slot function needs to finish before execution continues, you must make sure that |
4462 | the connection is direct (see Qt::ConnectionType). |
4463 | |
4464 | \warning When using the OpenGL adaptation, make sure that a signal handler for |
4465 | afterSynchronizing leaves the OpenGL context in the same state as it was when the |
4466 | signal handler was entered. Failing to do so can result in the scene not rendering |
4467 | properly. |
4468 | |
4469 | \since 5.3 |
4470 | \sa resetOpenGLState() |
4471 | */ |
4472 | |
4473 | /*! |
4474 | \qmlsignal QtQuick.Window::Window::afterSynchronizing() |
4475 | \internal |
4476 | \since 5.3 |
4477 | */ |
4478 | |
4479 | /*! |
4480 | \fn void QQuickWindow::beforeRendering() |
4481 | |
4482 | This signal is emitted before the scene starts rendering. |
4483 | |
4484 | Combined with the modes for clearing the background, this option |
4485 | can be used to paint using raw OpenGL under QML content. |
4486 | |
4487 | The OpenGL context used for rendering the scene graph will be bound |
4488 | at this point. |
4489 | |
4490 | When using the RHI, the signal is emitted after the preparations for the |
4491 | frame have been done, meaning there is a command buffer in recording mode, |
4492 | where applicable. If desired, the slot function connected to this signal |
4493 | can query native resources like the command before via |
4494 | QSGRendererInterface. Note however that the recording of the main render |
4495 | pass is not yet started at this point and it is not possible to add |
4496 | commands within that pass. Starting a pass means clearing the color, depth, |
4497 | and stencil buffers so it is not possible to achieve an underlay type of |
4498 | rendering by just connecting to this signal. Rather, connect to |
4499 | beforeRenderPassRecording(). However, connecting to this signal is still |
4500 | important if the recording of copy type of commands is desired since those |
4501 | cannot be enqueued within a render pass. |
4502 | |
4503 | \warning This signal is emitted from the scene graph rendering thread. If your |
4504 | slot function needs to finish before execution continues, you must make sure that |
4505 | the connection is direct (see Qt::ConnectionType). |
4506 | |
4507 | \warning Make very sure that a signal handler for beforeRendering leaves the OpenGL |
4508 | context in the same state as it was when the signal handler was entered. Failing to |
4509 | do so can result in the scene not rendering properly. |
4510 | |
4511 | \sa resetOpenGLState() |
4512 | */ |
4513 | |
4514 | /*! |
4515 | \qmlsignal QtQuick.Window::Window::beforeRendering() |
4516 | \internal |
4517 | */ |
4518 | |
4519 | /*! |
4520 | \fn void QQuickWindow::afterRendering() |
4521 | |
4522 | This signal is emitted after the scene has completed rendering, before swapbuffers is called. |
4523 | |
4524 | This signal can be used to paint using raw OpenGL on top of QML content, |
4525 | or to do screen scraping of the current frame buffer. |
4526 | |
4527 | The OpenGL context used for rendering the scene graph will be bound at this point. |
4528 | |
4529 | When using the RHI, the signal is emitted after scene graph has added its |
4530 | commands to the command buffer, which is not yet submitted to the graphics |
4531 | queue. If desired, the slot function connected to this signal can query |
4532 | native resources, like the command buffer, before via QSGRendererInterface. |
4533 | Note however that the render pass (or passes) are already recorded at this |
4534 | point and it is not possible to add more commands within the scenegraph's |
4535 | pass. Instead, use afterRenderPassRecording() for that. This signal has |
4536 | therefore limited use and is rarely needed in an RHI-based setup. Rather, |
4537 | it is the combination of beforeRendering() + beforeRenderPassRecording() or |
4538 | beforeRendering() + afterRenderPassRecording() that is typically used to |
4539 | achieve under- or overlaying of the custom rendering. |
4540 | |
4541 | \warning This signal is emitted from the scene graph rendering thread. If your |
4542 | slot function needs to finish before execution continues, you must make sure that |
4543 | the connection is direct (see Qt::ConnectionType). |
4544 | |
4545 | \warning Make very sure that a signal handler for afterRendering() leaves the OpenGL |
4546 | context in the same state as it was when the signal handler was entered. Failing to |
4547 | do so can result in the scene not rendering properly. |
4548 | |
4549 | \sa resetOpenGLState() |
4550 | */ |
4551 | |
4552 | /*! |
4553 | \qmlsignal QtQuick.Window::Window::afterRendering() |
4554 | \internal |
4555 | */ |
4556 | |
4557 | /*! |
4558 | \fn void QQuickWindow::beforeRenderPassRecording() |
4559 | |
4560 | This signal is emitted before the scenegraph starts recording commands for |
4561 | the main render pass. (Layers have their own passes and are fully recorded |
4562 | by the time this signal is emitted.) The render pass is already active on |
4563 | the command buffer when the signal is emitted. |
4564 | |
4565 | This signal is applicable when using the RHI graphics abstraction with the |
4566 | scenegraph. It is emitted later than beforeRendering() and it guarantees |
4567 | that not just the frame, but also the recording of the scenegraph's main |
4568 | render pass is active. This allows inserting commands without having to |
4569 | generate an entire, separate render pass (which would typically clear the |
4570 | attached images). The native graphics objects can be queried via |
4571 | QSGRendererInterface. |
4572 | |
4573 | When not running with the RHI (and using OpenGL directly), the signal is |
4574 | emitted after the renderer has cleared the render target. This makes it |
4575 | possible to create applications that function identically both with and |
4576 | without the RHI. |
4577 | |
4578 | \note Resource updates (uploads, copies) typically cannot be enqueued from |
4579 | within a render pass. Therefore, more complex user rendering will need to |
4580 | connect to both beforeRendering() and this signal. |
4581 | |
4582 | \warning This signal is emitted from the scene graph rendering thread. If your |
4583 | slot function needs to finish before execution continues, you must make sure that |
4584 | the connection is direct (see Qt::ConnectionType). |
4585 | |
4586 | \since 5.14 |
4587 | */ |
4588 | |
4589 | /*! |
4590 | \qmlsignal QtQuick.Window::Window::beforeRenderPassRecording() |
4591 | \internal |
4592 | \since 5.14 |
4593 | */ |
4594 | |
4595 | /*! |
4596 | \fn void QQuickWindow::afterRenderPassRecording() |
4597 | |
4598 | This signal is emitted after the scenegraph has recorded the commands for |
4599 | its main render pass, but the pass is not yet finalized on the command |
4600 | buffer. |
4601 | |
4602 | This signal is applicable when using the RHI graphics abstraction with the |
4603 | scenegraph. It is emitted earlier than afterRendering() and it guarantees |
4604 | that not just the frame, but also the recording of the scenegraph's main |
4605 | render pass is still active. This allows inserting commands without having |
4606 | to generate an entire, separate render pass (which would typically clear |
4607 | the attached images). The native graphics objects can be queried via |
4608 | QSGRendererInterface. |
4609 | |
4610 | When not running with the RHI (and using OpenGL directly), the signal is |
4611 | emitted after the renderer has finished its rendering, but before |
4612 | afterRendering(). This makes it possible to create applications that |
4613 | function identically both with and without the RHI. |
4614 | |
4615 | \note Resource updates (uploads, copies) typically cannot be enqueued from |
4616 | within a render pass. Therefore, more complex user rendering will need to |
4617 | connect to both beforeRendering() and this signal. |
4618 | |
4619 | \warning This signal is emitted from the scene graph rendering thread. If your |
4620 | slot function needs to finish before execution continues, you must make sure that |
4621 | the connection is direct (see Qt::ConnectionType). |
4622 | |
4623 | \since 5.14 |
4624 | */ |
4625 | |
4626 | /*! |
4627 | \qmlsignal QtQuick.Window::Window::afterRenderPassRecording() |
4628 | \internal |
4629 | \since 5.14 |
4630 | */ |
4631 | |
4632 | /*! |
4633 | \fn void QQuickWindow::afterAnimating() |
4634 | |
4635 | This signal is emitted on the GUI thread before requesting the render thread to |
4636 | perform the synchronization of the scene graph. |
4637 | |
4638 | Unlike the other similar signals, this one is emitted on the GUI thread |
4639 | instead of the render thread. It can be used to synchronize external |
4640 | animation systems with the QML content. At the same time this means that |
4641 | this signal is not suitable for triggering graphics operations. |
4642 | |
4643 | \since 5.3 |
4644 | */ |
4645 | |
4646 | /*! |
4647 | \qmlsignal QtQuick.Window::Window::afterAnimating() |
4648 | |
4649 | This signal is emitted on the GUI thread before requesting the render thread to |
4650 | perform the synchronization of the scene graph. |
4651 | |
4652 | You can implement onAfterAnimating to do additional processing after each animation step. |
4653 | |
4654 | \since 5.3 |
4655 | */ |
4656 | |
4657 | /*! |
4658 | \fn void QQuickWindow::openglContextCreated(QOpenGLContext *context) |
4659 | |
4660 | This signal is emitted on the GUI thread when the OpenGL \a context |
4661 | for this window is created, before it is made current. |
4662 | |
4663 | Some implementations will share the same OpenGL context between |
4664 | multiple QQuickWindow instances. The openglContextCreated() signal |
4665 | will in this case only be emitted for the first window, when the |
4666 | OpenGL context is actually created. |
4667 | |
4668 | QQuickWindow::openglContext() will still return 0 for this window |
4669 | until after the QQuickWindow::sceneGraphInitialized() has been |
4670 | emitted. |
4671 | |
4672 | \note |
4673 | This signal will only be emmited when using the default OpenGL scene |
4674 | graph adaptation. |
4675 | |
4676 | \since 5.3 |
4677 | */ |
4678 | |
4679 | /*! |
4680 | \qmlsignal QtQuick.Window::Window::openglContextCreated() |
4681 | \internal |
4682 | \since 5.3 |
4683 | */ |
4684 | |
4685 | /*! |
4686 | \fn void QQuickWindow::sceneGraphAboutToStop() |
4687 | |
4688 | This signal is emitted on the render thread when the scene graph is |
4689 | about to stop rendering. This happens usually because the window |
4690 | has been hidden. |
4691 | |
4692 | Applications may use this signal to release resources, but should be |
4693 | prepared to reinstantiated them again fast. The scene graph and the |
4694 | graphics context are not released at this time. |
4695 | |
4696 | \warning This signal is emitted from the scene graph rendering thread. If your |
4697 | slot function needs to finish before execution continues, you must make sure that |
4698 | the connection is direct (see Qt::ConnectionType). |
4699 | |
4700 | \warning Make very sure that a signal handler for sceneGraphAboutToStop() leaves the |
4701 | graphics context in the same state as it was when the signal handler was entered. |
4702 | Failing to do so can result in the scene not rendering properly. |
4703 | |
4704 | \sa sceneGraphInvalidated(), resetOpenGLState() |
4705 | \since 5.3 |
4706 | */ |
4707 | |
4708 | /*! |
4709 | \qmlsignal QtQuick.Window::Window::sceneGraphAboutToStop() |
4710 | \internal |
4711 | \since 5.3 |
4712 | */ |
4713 | |
4714 | /*! |
4715 | Sets whether the scene graph rendering of QML should clear the color buffer |
4716 | before it starts rendering to \a enabled. |
4717 | |
4718 | By disabling clearing of the color buffer, it is possible to render OpengGL content |
4719 | under the scene graph. |
4720 | |
4721 | The color buffer is cleared by default. |
4722 | |
4723 | \warning This flag is ignored completely when running with the RHI graphics |
4724 | abstraction instead of using OpenGL directly. As explicit clear commands |
4725 | simply do not exist in some modern APIs, the scene graph cannot offer this |
4726 | flexibility anymore. The images associated with a render target will always |
4727 | get cleared when a render pass starts. As a solution, an alternative to |
4728 | disabling scene graph issued clears is provided in form of the |
4729 | beforeRenderPassRecording() signal. |
4730 | |
4731 | \sa beforeRendering(), beforeRenderPassRecording() |
4732 | */ |
4733 | |
4734 | void QQuickWindow::setClearBeforeRendering(bool enabled) |
4735 | { |
4736 | Q_D(QQuickWindow); |
4737 | d->clearBeforeRendering = enabled; |
4738 | } |
4739 | |
4740 | |
4741 | |
4742 | /*! |
4743 | Returns whether clearing of the color buffer is done before rendering or not. |
4744 | */ |
4745 | |
4746 | bool QQuickWindow::clearBeforeRendering() const |
4747 | { |
4748 | Q_D(const QQuickWindow); |
4749 | return d->clearBeforeRendering; |
4750 | } |
4751 | |
4752 | /*! |
4753 | \overload |
4754 | */ |
4755 | |
4756 | QSGTexture *QQuickWindow::createTextureFromImage(const QImage &image) const |
4757 | { |
4758 | return createTextureFromImage(image, options: {}); |
4759 | } |
4760 | |
4761 | |
4762 | /*! |
4763 | Creates a new QSGTexture from the supplied \a image. If the image has an |
4764 | alpha channel, the corresponding texture will have an alpha channel. |
4765 | |
4766 | The caller of the function is responsible for deleting the returned texture. |
4767 | For example whe using the OpenGL adaptation the actual OpenGL texture will |
4768 | be deleted when the texture object is deleted. |
4769 | |
4770 | When \a options contains TextureCanUseAtlas, the engine may put the image |
4771 | into a texture atlas. Textures in an atlas need to rely on |
4772 | QSGTexture::normalizedTextureSubRect() for their geometry and will not |
4773 | support QSGTexture::Repeat. Other values from CreateTextureOption are |
4774 | ignored. |
4775 | |
4776 | When \a options contains TextureIsOpaque, the engine will create an RGB |
4777 | texture which returns false for QSGTexture::hasAlphaChannel(). Opaque |
4778 | textures will in most cases be faster to render. When this flag is not set, |
4779 | the texture will have an alpha channel based on the image's format. |
4780 | |
4781 | When \a options contains TextureHasMipmaps, the engine will create a |
4782 | texture which can use mipmap filtering. Mipmapped textures can not be in |
4783 | an atlas. |
4784 | |
4785 | When using the OpenGL adaptation, the returned texture will be using |
4786 | \c GL_TEXTURE_2D as texture target and \c GL_RGBA as internal format. |
4787 | Reimplement QSGTexture to create textures with different parameters. |
4788 | |
4789 | \warning This function will return 0 if the scene graph has not yet been |
4790 | initialized. |
4791 | |
4792 | \warning The returned texture is not memory managed by the scene graph and |
4793 | must be explicitly deleted by the caller on the rendering thread. |
4794 | This is achieved by deleting the texture from a QSGNode destructor |
4795 | or by using deleteLater() in the case where the texture already has affinity |
4796 | to the rendering thread. |
4797 | |
4798 | This function can be called from any thread. |
4799 | |
4800 | \sa sceneGraphInitialized(), QSGTexture |
4801 | */ |
4802 | |
4803 | QSGTexture *QQuickWindow::createTextureFromImage(const QImage &image, CreateTextureOptions options) const |
4804 | { |
4805 | Q_D(const QQuickWindow); |
4806 | if (!isSceneGraphInitialized()) // check both for d->context and d->context->isValid() |
4807 | return nullptr; |
4808 | uint flags = 0; |
4809 | if (options & TextureCanUseAtlas) flags |= QSGRenderContext::CreateTexture_Atlas; |
4810 | if (options & TextureHasMipmaps) flags |= QSGRenderContext::CreateTexture_Mipmap; |
4811 | if (!(options & TextureIsOpaque)) flags |= QSGRenderContext::CreateTexture_Alpha; |
4812 | return d->context->createTexture(image, flags); |
4813 | } |
4814 | |
4815 | |
4816 | #if QT_DEPRECATED_SINCE(5, 15) |
4817 | /*! |
4818 | Creates a new QSGTexture object from an existing OpenGL texture \a id and \a size. |
4819 | |
4820 | The caller of the function is responsible for deleting the returned texture. |
4821 | |
4822 | The returned texture will be using \c GL_TEXTURE_2D as texture target and |
4823 | assumes that internal format is \c {GL_RGBA}. Reimplement QSGTexture to |
4824 | create textures with different parameters. |
4825 | |
4826 | Use \a options to customize the texture attributes. The TextureUsesAtlas |
4827 | option is ignored. |
4828 | |
4829 | \warning This function will return null if the scenegraph has not yet been |
4830 | initialized or OpenGL is not in use. |
4831 | |
4832 | \note This function only has an effect when using the default OpenGL scene graph |
4833 | adaptation. |
4834 | |
4835 | \note This function has no effect when running on the RHI graphics |
4836 | abstraction. Use createTextureFromNativeObject() instead. |
4837 | |
4838 | \obsolete |
4839 | |
4840 | \sa sceneGraphInitialized(), QSGTexture |
4841 | */ |
4842 | QSGTexture *QQuickWindow::createTextureFromId(uint id, const QSize &size, CreateTextureOptions options) const |
4843 | { |
4844 | #if QT_CONFIG(opengl) |
4845 | Q_D(const QQuickWindow); |
4846 | if (!d->rhi) { |
4847 | if (openglContext()) { |
4848 | QSGPlainTexture *texture = new QSGPlainTexture(); |
4849 | texture->setTextureId(id); |
4850 | texture->setHasAlphaChannel(options & TextureHasAlphaChannel); |
4851 | texture->setOwnsTexture(options & TextureOwnsGLTexture); |
4852 | texture->setTextureSize(size); |
4853 | return texture; |
4854 | } |
4855 | } else { |
4856 | qWarning(msg: "createTextureFromId() must not be called when running on the RHI. " |
4857 | "Use createTextureFromNativeObject() instead." ); |
4858 | } |
4859 | #else |
4860 | Q_UNUSED(id) |
4861 | Q_UNUSED(size) |
4862 | Q_UNUSED(options) |
4863 | #endif |
4864 | return nullptr; |
4865 | } |
4866 | #endif |
4867 | |
4868 | /*! |
4869 | \enum QQuickWindow::NativeObjectType |
4870 | \since 5.14 |
4871 | |
4872 | Specifies the type of the native object passed to functions such as |
4873 | createTextureFromNativeObject(). |
4874 | |
4875 | \value NativeObjectTexture The native object is a 2D texture (OpenGL, |
4876 | Direct3D 11, Metal) or image (Vulkan). |
4877 | */ |
4878 | |
4879 | /*! |
4880 | Creates a new QSGTexture object from an existing native object. |
4881 | |
4882 | The native object is wrapped, but not owned, by the resulting QSGTexture. |
4883 | The caller of the function is responsible for deleting the returned |
4884 | QSGTexture, but that will not destroy the underlying native object. |
4885 | |
4886 | \a type specifies the type of the object. In practice the type is |
4887 | NativeObjectTexture, indicating that the native object is a texture or |
4888 | image of the underlying graphics API. Other types may be introduced in the |
4889 | future. |
4890 | |
4891 | This function is currently suitable for 2D RGBA textures only. |
4892 | |
4893 | Unlike createTextureFromId(), this function supports both direct OpenGL |
4894 | usage and the RHI abstracted rendering path. |
4895 | |
4896 | \warning This function will return null if the scenegraph has not yet been |
4897 | initialized. |
4898 | |
4899 | Use \a options to customize the texture attributes. Only the |
4900 | TextureHasAlphaChannel and TextureHasMipmaps are taken into account here. |
4901 | |
4902 | \warning Unlike createTextureFromId(), this function never takes ownership |
4903 | of the native object, and the TextureOwnsGLTexture flag is ignored. |
4904 | |
4905 | \a size specifies the size in pixels. |
4906 | |
4907 | \a nativeObjectPtr is a pointer to the native object handle. With OpenGL, |
4908 | the native handle is a GLuint value, so \a nativeObjectPtr is then a |
4909 | pointer to a GLuint. With Vulkan, the native handle is a VkImage, so \a |
4910 | nativeObjectPtr is a pointer to a VkImage. With Direct3D 11 and Metal \a |
4911 | nativeObjectPtr is a pointer to a ID3D11Texture2D or MTLTexture pointer. |
4912 | |
4913 | \note Pay attention to the fact that \a nativeObjectPtr is always a pointer |
4914 | to the native texture handle type, even if the native type itself is a |
4915 | pointer. |
4916 | |
4917 | \a nativeLayout is only used for APIs like Vulkan. When applicable, it must |
4918 | specify the current image layout, such as, a VkImageLayout value. |
4919 | |
4920 | \sa sceneGraphInitialized(), QSGTexture, QSGTexture::nativeTexture() |
4921 | |
4922 | \since 5.14 |
4923 | */ |
4924 | QSGTexture *QQuickWindow::createTextureFromNativeObject(NativeObjectType type, |
4925 | const void *nativeObjectPtr, |
4926 | int nativeLayout, |
4927 | const QSize &size, |
4928 | CreateTextureOptions options) const |
4929 | { |
4930 | if (type != NativeObjectTexture) { |
4931 | qWarning(msg: "createTextureFromNativeObject: only textures are supported" ); |
4932 | return nullptr; |
4933 | } |
4934 | |
4935 | #if QT_CONFIG(opengl) /* || QT_CONFIG(vulkan) || defined(Q_OS_WIN) || defined(Q_OS_DARWIN) */ |
4936 | Q_D(const QQuickWindow); |
4937 | if (d->rhi) { |
4938 | QSGPlainTexture *texture = new QSGPlainTexture; |
4939 | texture->setTextureFromNativeObject(rhi: d->rhi, type, nativeObjectPtr, nativeLayout, |
4940 | size, mipmap: options.testFlag(flag: TextureHasMipmaps)); |
4941 | texture->setHasAlphaChannel(options & TextureHasAlphaChannel); |
4942 | // note that the QRhiTexture does not (and cannot) own the native object |
4943 | texture->setOwnsTexture(true); // texture meaning the QRhiTexture here, not the native object |
4944 | texture->setTextureSize(size); |
4945 | return texture; |
4946 | } else if (openglContext()) { |
4947 | QSGPlainTexture *texture = new QSGPlainTexture; |
4948 | texture->setTextureId(*reinterpret_cast<const uint *>(nativeObjectPtr)); |
4949 | texture->setHasAlphaChannel(options & TextureHasAlphaChannel); |
4950 | texture->setOwnsTexture(options & TextureOwnsGLTexture); |
4951 | texture->setTextureSize(size); |
4952 | return texture; |
4953 | } |
4954 | #else |
4955 | Q_UNUSED(nativeObjectPtr); |
4956 | Q_UNUSED(nativeLayout); |
4957 | Q_UNUSED(size); |
4958 | Q_UNUSED(options); |
4959 | #endif |
4960 | |
4961 | return nullptr; |
4962 | } |
4963 | |
4964 | /*! |
4965 | \qmlproperty color Window::color |
4966 | |
4967 | The background color for the window. |
4968 | |
4969 | Setting this property is more efficient than using a separate Rectangle. |
4970 | */ |
4971 | |
4972 | /*! |
4973 | \property QQuickWindow::color |
4974 | \brief The color used to clear the OpenGL context. |
4975 | |
4976 | Setting the clear color has no effect when clearing is disabled. |
4977 | By default, the clear color is white. |
4978 | |
4979 | \sa setClearBeforeRendering(), setDefaultAlphaBuffer() |
4980 | */ |
4981 | |
4982 | void QQuickWindow::setColor(const QColor &color) |
4983 | { |
4984 | Q_D(QQuickWindow); |
4985 | if (color == d->clearColor) |
4986 | return; |
4987 | |
4988 | if (color.alpha() != d->clearColor.alpha()) { |
4989 | QSurfaceFormat fmt = requestedFormat(); |
4990 | if (color.alpha() < 255) |
4991 | fmt.setAlphaBufferSize(8); |
4992 | else |
4993 | fmt.setAlphaBufferSize(-1); |
4994 | setFormat(fmt); |
4995 | } |
4996 | d->clearColor = color; |
4997 | emit colorChanged(color); |
4998 | update(); |
4999 | } |
5000 | |
5001 | QColor QQuickWindow::color() const |
5002 | { |
5003 | return d_func()->clearColor; |
5004 | } |
5005 | |
5006 | /*! |
5007 | \brief Returns whether to use alpha transparency on newly created windows. |
5008 | |
5009 | \since 5.1 |
5010 | \sa setDefaultAlphaBuffer() |
5011 | */ |
5012 | bool QQuickWindow::hasDefaultAlphaBuffer() |
5013 | { |
5014 | return QQuickWindowPrivate::defaultAlphaBuffer; |
5015 | } |
5016 | |
5017 | /*! |
5018 | \brief \a useAlpha specifies whether to use alpha transparency on newly created windows. |
5019 | \since 5.1 |
5020 | |
5021 | In any application which expects to create translucent windows, it's necessary to set |
5022 | this to true before creating the first QQuickWindow. The default value is false. |
5023 | |
5024 | \sa hasDefaultAlphaBuffer() |
5025 | */ |
5026 | void QQuickWindow::setDefaultAlphaBuffer(bool useAlpha) |
5027 | { |
5028 | QQuickWindowPrivate::defaultAlphaBuffer = useAlpha; |
5029 | } |
5030 | #if QT_CONFIG(opengl) |
5031 | /*! |
5032 | \since 5.2 |
5033 | |
5034 | Call this function to reset the OpenGL context its default state. |
5035 | |
5036 | The scene graph uses the OpenGL context and will both rely on and |
5037 | clobber its state. When mixing raw OpenGL commands with scene |
5038 | graph rendering, this function provides a convenient way of |
5039 | resetting the OpenGL context state back to its default values. |
5040 | |
5041 | This function does not touch state in the fixed-function pipeline. |
5042 | |
5043 | This function does not clear the color, depth and stencil buffers. Use |
5044 | QQuickWindow::setClearBeforeRendering to control clearing of the color |
5045 | buffer. The depth and stencil buffer might be clobbered by the scene |
5046 | graph renderer. Clear these manually on demand. |
5047 | |
5048 | \note This function only has an effect when using the default OpenGL scene graph |
5049 | adaptation. |
5050 | |
5051 | \note This function will only reset the OpenGL context in relation to what has been changed |
5052 | internally as part of the OpenGL scene graph. It does not reset anything that has been changed |
5053 | externally such as direct OpenGL calls done inside the application code if those same calls are |
5054 | not used internally. |
5055 | |
5056 | \note This function has no effect when running on the RHI graphics |
5057 | abstraction and the underlying RHI backend is not OpenGL. |
5058 | |
5059 | \sa QQuickWindow::beforeRendering(), beginExternalCommands(), endExternalCommands() |
5060 | */ |
5061 | void QQuickWindow::resetOpenGLState() |
5062 | { |
5063 | Q_D(QQuickWindow); |
5064 | |
5065 | if (!openglContext()) |
5066 | return; |
5067 | |
5068 | QOpenGLContext *ctx = openglContext(); |
5069 | QOpenGLFunctions *gl = ctx->functions(); |
5070 | |
5071 | gl->glBindBuffer(GL_ARRAY_BUFFER, buffer: 0); |
5072 | gl->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer: 0); |
5073 | |
5074 | if (!d->vaoHelper) |
5075 | d->vaoHelper = new QOpenGLVertexArrayObjectHelper(ctx); |
5076 | if (d->vaoHelper->isValid()) |
5077 | d->vaoHelper->glBindVertexArray(array: 0); |
5078 | |
5079 | if (ctx->isOpenGLES() || (gl->openGLFeatures() & QOpenGLFunctions::FixedFunctionPipeline)) { |
5080 | int maxAttribs; |
5081 | gl->glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, params: &maxAttribs); |
5082 | for (int i=0; i<maxAttribs; ++i) { |
5083 | gl->glVertexAttribPointer(indx: i, size: 4, GL_FLOAT, GL_FALSE, stride: 0, ptr: nullptr); |
5084 | gl->glDisableVertexAttribArray(index: i); |
5085 | } |
5086 | } |
5087 | |
5088 | gl->glActiveTexture(GL_TEXTURE0); |
5089 | gl->glBindTexture(GL_TEXTURE_2D, texture: 0); |
5090 | |
5091 | gl->glDisable(GL_DEPTH_TEST); |
5092 | gl->glDisable(GL_STENCIL_TEST); |
5093 | gl->glDisable(GL_SCISSOR_TEST); |
5094 | |
5095 | gl->glColorMask(red: true, green: true, blue: true, alpha: true); |
5096 | gl->glClearColor(red: 0, green: 0, blue: 0, alpha: 0); |
5097 | |
5098 | gl->glDepthMask(flag: true); |
5099 | gl->glDepthFunc(GL_LESS); |
5100 | gl->glClearDepthf(depth: 1); |
5101 | |
5102 | gl->glStencilMask(mask: 0xff); |
5103 | gl->glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); |
5104 | gl->glStencilFunc(GL_ALWAYS, ref: 0, mask: 0xff); |
5105 | |
5106 | gl->glDisable(GL_BLEND); |
5107 | gl->glBlendFunc(GL_ONE, GL_ZERO); |
5108 | |
5109 | gl->glUseProgram(program: 0); |
5110 | |
5111 | QOpenGLFramebufferObject::bindDefault(); |
5112 | } |
5113 | #endif |
5114 | |
5115 | /*! |
5116 | \struct QQuickWindow::GraphicsStateInfo |
5117 | \inmodule QtQuick |
5118 | \since 5.14 |
5119 | |
5120 | \brief Describes some of the RHI's graphics state at the point of a |
5121 | \l{QQuickWindow::beginExternalCommands()}{beginExternalCommands()} call. |
5122 | */ |
5123 | |
5124 | /*! |
5125 | \return a reference to a GraphicsStateInfo struct describing some of the |
5126 | RHI's internal state, in particular, the double or tripple buffering status |
5127 | of the backend (such as, the Vulkan or Metal integrations). This is |
5128 | relevant when the underlying graphics APIs is Vulkan or Metal, and the |
5129 | external rendering code wishes to perform double or tripple buffering of |
5130 | its own often-changing resources, such as, uniform buffers, in order to |
5131 | avoid stalling the pipeline. |
5132 | */ |
5133 | const QQuickWindow::GraphicsStateInfo &QQuickWindow::graphicsStateInfo() |
5134 | { |
5135 | Q_D(QQuickWindow); |
5136 | if (d->rhi) { |
5137 | d->rhiStateInfo.currentFrameSlot = d->rhi->currentFrameSlot(); |
5138 | d->rhiStateInfo.framesInFlight = d->rhi->resourceLimit(limit: QRhi::FramesInFlight); |
5139 | } |
5140 | return d->rhiStateInfo; |
5141 | } |
5142 | |
5143 | /*! |
5144 | When mixing raw graphics (OpenGL, Vulkan, Metal, etc.) commands with scene |
5145 | graph rendering, it is necessary to call this function before recording |
5146 | commands to the command buffer used by the scene graph to render its main |
5147 | render pass. This is to avoid clobbering state. |
5148 | |
5149 | In practice this function is often called from a slot connected to the |
5150 | beforeRenderPassRecording() or afterRenderPassRecording() signals. |
5151 | |
5152 | The function does not need to be called when recording commands to the |
5153 | application's own command buffer (such as, a VkCommandBuffer or |
5154 | MTLCommandBuffer + MTLRenderCommandEncoder created and managed by the |
5155 | application, not retrieved from the scene graph). With graphics APIs where |
5156 | no native command buffer concept is exposed (OpenGL, Direct 3D 11), |
5157 | beginExternalCommands() and endExternalCommands() together provide a |
5158 | replacement for resetOpenGLState(). |
5159 | |
5160 | Calling this function and endExternalCommands() is not necessary within the |
5161 | \l{QSGRenderNode::render()}{render()} implementation of a QSGRenderNode |
5162 | because the scene graph performs the necessary steps implicitly for render |
5163 | nodes. |
5164 | |
5165 | Native graphics objects (such as, graphics device, command buffer or |
5166 | encoder) are accessible via QSGRendererInterface::getResource(). |
5167 | |
5168 | \warning Watch out for the fact that |
5169 | QSGRendererInterface::CommandListResource may return a different object |
5170 | between beginExternalCommands() - endExternalCommands(). This can happen |
5171 | when the underlying implementation provides a dedicated secondary command |
5172 | buffer for recording external graphics commands within a render pass. |
5173 | Therefore, always query CommandListResource after calling this function. Do |
5174 | not attempt to reuse an object from an earlier query. |
5175 | |
5176 | \note This function has no effect when the scene graph is using OpenGL |
5177 | directly and the RHI graphics abstraction layer is not in use. Refer to |
5178 | resetOpenGLState() in that case. |
5179 | |
5180 | \note When the scenegraph is using the RHI graphics abstraction layer with |
5181 | the OpenGL backend underneath, pay attention to the fact that the OpenGL |
5182 | state in the context can have arbitrary settings, and this function does not |
5183 | perform any resetting of the state back to defaults. Call |
5184 | resetOpenGLState() if that is seen necessary. |
5185 | |
5186 | \sa endExternalCommands(), resetOpenGLState() |
5187 | |
5188 | \since 5.14 |
5189 | */ |
5190 | void QQuickWindow::beginExternalCommands() |
5191 | { |
5192 | #if QT_CONFIG(opengl) /* || QT_CONFIG(vulkan) || defined(Q_OS_WIN) || defined(Q_OS_DARWIN) */ |
5193 | Q_D(QQuickWindow); |
5194 | if (d->rhi && d->context && d->context->isValid()) { |
5195 | QSGDefaultRenderContext *rc = static_cast<QSGDefaultRenderContext *>(d->context); |
5196 | QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer(); |
5197 | if (cb) |
5198 | cb->beginExternal(); |
5199 | } |
5200 | #endif |
5201 | } |
5202 | |
5203 | /*! |
5204 | When mixing raw graphics (OpenGL, Vulkan, Metal, etc.) commands with scene |
5205 | graph rendering, it is necessary to call this function after recording |
5206 | commands to the command buffer used by the scene graph to render its main |
5207 | render pass. This is to avoid clobbering state. |
5208 | |
5209 | In practice this function is often called from a slot connected to the |
5210 | beforeRenderPassRecording() or afterRenderPassRecording() signals. |
5211 | |
5212 | The function does not need to be called when recording commands to the |
5213 | application's own command buffer (such as, a VkCommandBuffer or |
5214 | MTLCommandBuffer + MTLRenderCommandEncoder created and managed by the |
5215 | application, not retrieved from the scene graph). With graphics APIs where |
5216 | no native command buffer concept is exposed (OpenGL, Direct 3D 11), |
5217 | beginExternalCommands() and endExternalCommands() together provide a |
5218 | replacement for resetOpenGLState(). |
5219 | |
5220 | Calling this function and beginExternalCommands() is not necessary within the |
5221 | \l{QSGRenderNode::render()}{render()} implementation of a QSGRenderNode |
5222 | because the scene graph performs the necessary steps implicitly for render |
5223 | nodes. |
5224 | |
5225 | \note This function has no effect when the scene graph is using OpenGL |
5226 | directly and the RHI graphics abstraction layer is not in use. Refer to |
5227 | resetOpenGLState() in that case. |
5228 | |
5229 | \sa beginExternalCommands() |
5230 | |
5231 | \since 5.14 |
5232 | */ |
5233 | void QQuickWindow::endExternalCommands() |
5234 | { |
5235 | #if QT_CONFIG(opengl) /* || QT_CONFIG(vulkan) || defined(Q_OS_WIN) || defined(Q_OS_DARWIN) */ |
5236 | Q_D(QQuickWindow); |
5237 | if (d->rhi && d->context && d->context->isValid()) { |
5238 | QSGDefaultRenderContext *rc = static_cast<QSGDefaultRenderContext *>(d->context); |
5239 | QRhiCommandBuffer *cb = rc->currentFrameCommandBuffer(); |
5240 | if (cb) |
5241 | cb->endExternal(); |
5242 | } |
5243 | #endif |
5244 | } |
5245 | |
5246 | /*! |
5247 | \qmlproperty string Window::title |
5248 | |
5249 | The window's title in the windowing system. |
5250 | |
5251 | The window title might appear in the title area of the window decorations, |
5252 | depending on the windowing system and the window flags. It might also |
5253 | be used by the windowing system to identify the window in other contexts, |
5254 | such as in the task switcher. |
5255 | */ |
5256 | |
5257 | /*! |
5258 | \qmlproperty Qt::WindowModality Window::modality |
5259 | |
5260 | The modality of the window. |
5261 | |
5262 | A modal window prevents other windows from receiving input events. |
5263 | Possible values are Qt.NonModal (the default), Qt.WindowModal, |
5264 | and Qt.ApplicationModal. |
5265 | */ |
5266 | |
5267 | /*! |
5268 | \qmlproperty Qt::WindowFlags Window::flags |
5269 | |
5270 | The window flags of the window. |
5271 | |
5272 | The window flags control the window's appearance in the windowing system, |
5273 | whether it's a dialog, popup, or a regular window, and whether it should |
5274 | have a title bar, etc. |
5275 | |
5276 | The flags which you read from this property might differ from the ones |
5277 | that you set if the requested flags could not be fulfilled. |
5278 | |
5279 | \sa Qt::WindowFlags |
5280 | */ |
5281 | |
5282 | /*! |
5283 | \qmlattachedproperty Window Window::window |
5284 | \since 5.7 |
5285 | |
5286 | This attached property holds the item's window. |
5287 | The Window attached property can be attached to any Item. |
5288 | */ |
5289 | |
5290 | /*! |
5291 | \qmlattachedproperty int Window::width |
5292 | \qmlattachedproperty int Window::height |
5293 | \since 5.5 |
5294 | |
5295 | These attached properties hold the size of the item's window. |
5296 | The Window attached property can be attached to any Item. |
5297 | */ |
5298 | |
5299 | /*! |
5300 | \qmlproperty int Window::x |
5301 | \qmlproperty int Window::y |
5302 | \qmlproperty int Window::width |
5303 | \qmlproperty int Window::height |
5304 | |
5305 | Defines the window's position and size. |
5306 | |
5307 | The (x,y) position is relative to the \l Screen if there is only one, |
5308 | or to the virtual desktop (arrangement of multiple screens). |
5309 | |
5310 | \qml |
5311 | Window { x: 100; y: 100; width: 100; height: 100 } |
5312 | \endqml |
5313 | |
5314 | \image screen-and-window-dimensions.jpg |
5315 | */ |
5316 | |
5317 | /*! |
5318 | \qmlproperty int Window::minimumWidth |
5319 | \qmlproperty int Window::minimumHeight |
5320 | \since 5.1 |
5321 | |
5322 | Defines the window's minimum size. |
5323 | |
5324 | This is a hint to the window manager to prevent resizing below the specified |
5325 | width and height. |
5326 | */ |
5327 | |
5328 | /*! |
5329 | \qmlproperty int Window::maximumWidth |
5330 | \qmlproperty int Window::maximumHeight |
5331 | \since 5.1 |
5332 | |
5333 | Defines the window's maximum size. |
5334 | |
5335 | This is a hint to the window manager to prevent resizing above the specified |
5336 | width and height. |
5337 | */ |
5338 | |
5339 | /*! |
5340 | \qmlproperty bool Window::visible |
5341 | |
5342 | Whether the window is visible on the screen. |
5343 | |
5344 | Setting visible to false is the same as setting \l visibility to \l {QWindow::}{Hidden}. |
5345 | |
5346 | \sa visibility |
5347 | */ |
5348 | |
5349 | /*! |
5350 | \qmlproperty QWindow::Visibility Window::visibility |
5351 | |
5352 | The screen-occupation state of the window. |
5353 | |
5354 | Visibility is whether the window should appear in the windowing system as |
5355 | normal, minimized, maximized, fullscreen or hidden. |
5356 | |
5357 | To set the visibility to \l {QWindow::}{AutomaticVisibility} means to give the |
5358 | window a default visible state, which might be \l {QWindow::}{FullScreen} or |
5359 | \l {QWindow::}{Windowed} depending on the platform. However when reading the |
5360 | visibility property you will always get the actual state, never |
5361 | \c AutomaticVisibility. |
5362 | |
5363 | When a window is not visible its visibility is Hidden, and setting |
5364 | visibility to \l {QWindow::}{Hidden} is the same as setting \l visible to \c false. |
5365 | |
5366 | \sa visible |
5367 | \since 5.1 |
5368 | */ |
5369 | |
5370 | /*! |
5371 | \qmlattachedproperty QWindow::Visibility Window::visibility |
5372 | \since 5.4 |
5373 | |
5374 | This attached property holds whether the window is currently shown |
5375 | in the windowing system as normal, minimized, maximized, fullscreen or |
5376 | hidden. The \c Window attached property can be attached to any Item. If the |
5377 | item is not shown in any window, the value will be \l {QWindow::}{Hidden}. |
5378 | |
5379 | \sa visible, visibility |
5380 | */ |
5381 | |
5382 | /*! |
5383 | \qmlproperty Item Window::contentItem |
5384 | \readonly |
5385 | \brief The invisible root item of the scene. |
5386 | */ |
5387 | |
5388 | /*! |
5389 | \qmlproperty Qt::ScreenOrientation Window::contentOrientation |
5390 | |
5391 | This is a hint to the window manager in case it needs to display |
5392 | additional content like popups, dialogs, status bars, or similar |
5393 | in relation to the window. |
5394 | |
5395 | The recommended orientation is \l {Screen::orientation}{Screen.orientation}, but |
5396 | an application doesn't have to support all possible orientations, |
5397 | and thus can opt to ignore the current screen orientation. |
5398 | |
5399 | The difference between the window and the content orientation |
5400 | determines how much to rotate the content by. |
5401 | |
5402 | The default value is Qt::PrimaryOrientation. |
5403 | |
5404 | \sa Screen |
5405 | |
5406 | \since 5.1 |
5407 | */ |
5408 | |
5409 | /*! |
5410 | \qmlproperty real Window::opacity |
5411 | |
5412 | The opacity of the window. |
5413 | |
5414 | If the windowing system supports window opacity, this can be used to fade the |
5415 | window in and out, or to make it semitransparent. |
5416 | |
5417 | A value of 1.0 or above is treated as fully opaque, whereas a value of 0.0 or below |
5418 | is treated as fully transparent. Values inbetween represent varying levels of |
5419 | translucency between the two extremes. |
5420 | |
5421 | The default value is 1.0. |
5422 | |
5423 | \since 5.1 |
5424 | */ |
5425 | |
5426 | /*! |
5427 | \qmlproperty variant Window::screen |
5428 | |
5429 | The screen with which the window is associated. |
5430 | |
5431 | If specified before showing a window, will result in the window being shown |
5432 | on that screen, unless an explicit window position has been set. The value |
5433 | must be an element from the Qt.application.screens array. |
5434 | |
5435 | \note To ensure that the window is associated with the desired screen when |
5436 | the underlying native window is created, make sure this property is set as |
5437 | early as possible and that the setting of its value is not deferred. This |
5438 | can be particularly important on embedded platforms without a windowing system, |
5439 | where only one window per screen is allowed at a time. Setting the screen after |
5440 | a window has been created does not move the window if the new screen is part of |
5441 | the same virtual desktop as the old screen. |
5442 | |
5443 | \since 5.9 |
5444 | |
5445 | \sa QWindow::setScreen(), QWindow::screen(), QScreen, {QtQml::Qt::application}{Qt.application} |
5446 | */ |
5447 | |
5448 | /*! |
5449 | \qmlproperty QWindow Window::transientParent |
5450 | \since 5.13 |
5451 | |
5452 | The window for which this window is a transient pop-up. |
5453 | |
5454 | This is a hint to the window manager that this window is a dialog or pop-up |
5455 | on behalf of the transient parent. It usually means that the transient |
5456 | window will be centered over its transient parent when it is initially |
5457 | shown, that minimizing the parent window will also minimize the transient |
5458 | window, and so on; however results vary somewhat from platform to platform. |
5459 | |
5460 | Normally if you declare a Window inside an Item or inside another Window, |
5461 | this relationship is deduced automatically. In that case, if you declare |
5462 | this window's \l visible property \c true, it will not actually be shown |
5463 | until the \c transientParent window is shown. |
5464 | |
5465 | However if you set this property, then Qt Quick will no longer wait until |
5466 | the \c transientParent window is shown before showing this window. If you |
5467 | want to to be able to show a transient window independently of the "parent" |
5468 | Item or Window within which it was declared, you can remove that |
5469 | relationship by setting \c transientParent to \c null: |
5470 | |
5471 | \qml |
5472 | import QtQuick.Window 2.13 |
5473 | |
5474 | Window { |
5475 | // visible is false by default |
5476 | Window { |
5477 | transientParent: null |
5478 | visible: true |
5479 | } |
5480 | } |
5481 | \endqml |
5482 | |
5483 | In order to cause the window to be centered above its transient parent by |
5484 | default, depending on the window manager, it may also be necessary to set |
5485 | the \l Window::flags property with a suitable \l Qt::WindowType (such as |
5486 | \c Qt::Dialog). |
5487 | */ |
5488 | |
5489 | /*! |
5490 | \property QQuickWindow::transientParent |
5491 | \brief The window for which this window is a transient pop-up. |
5492 | \since 5.13 |
5493 | |
5494 | This is a hint to the window manager that this window is a dialog or pop-up |
5495 | on behalf of the transient parent, which may be any kind of \l QWindow. |
5496 | |
5497 | In order to cause the window to be centered above its transient parent by |
5498 | default, depending on the window manager, it may also be necessary to set |
5499 | the \l flags property with a suitable \l Qt::WindowType (such as \c Qt::Dialog). |
5500 | |
5501 | \sa parent() |
5502 | */ |
5503 | |
5504 | /*! |
5505 | \qmlproperty Item Window::activeFocusItem |
5506 | \since 5.1 |
5507 | |
5508 | The item which currently has active focus or \c null if there is |
5509 | no item with active focus. |
5510 | */ |
5511 | |
5512 | /*! |
5513 | \qmlattachedproperty Item Window::activeFocusItem |
5514 | \since 5.4 |
5515 | |
5516 | This attached property holds the item which currently has active focus or |
5517 | \c null if there is no item with active focus. The Window attached property |
5518 | can be attached to any Item. |
5519 | */ |
5520 | |
5521 | /*! |
5522 | \qmlproperty bool Window::active |
5523 | \since 5.1 |
5524 | |
5525 | The active status of the window. |
5526 | |
5527 | \sa requestActivate() |
5528 | */ |
5529 | |
5530 | /*! |
5531 | \qmlattachedproperty bool Window::active |
5532 | \since 5.4 |
5533 | |
5534 | This attached property tells whether the window is active. The Window |
5535 | attached property can be attached to any Item. |
5536 | |
5537 | Here is an example which changes a label to show the active state of the |
5538 | window in which it is shown: |
5539 | |
5540 | \qml |
5541 | import QtQuick 2.4 |
5542 | import QtQuick.Window 2.2 |
5543 | |
5544 | Text { |
5545 | text: Window.active ? "active" : "inactive" |
5546 | } |
5547 | \endqml |
5548 | */ |
5549 | |
5550 | /*! |
5551 | \qmlmethod QtQuick::Window::requestActivate() |
5552 | \since 5.1 |
5553 | |
5554 | Requests the window to be activated, i.e. receive keyboard focus. |
5555 | */ |
5556 | |
5557 | /*! |
5558 | \qmlmethod QtQuick::Window::alert(int msec) |
5559 | \since 5.1 |
5560 | |
5561 | Causes an alert to be shown for \a msec milliseconds. If \a msec is \c 0 |
5562 | (the default), then the alert is shown indefinitely until the window |
5563 | becomes active again. |
5564 | |
5565 | In alert state, the window indicates that it demands attention, for example |
5566 | by flashing or bouncing the taskbar entry. |
5567 | */ |
5568 | |
5569 | /*! |
5570 | \qmlmethod QtQuick::Window::close() |
5571 | |
5572 | Closes the window. |
5573 | |
5574 | When this method is called, or when the user tries to close the window by |
5575 | its title bar button, the \l closing signal will be emitted. If there is no |
5576 | handler, or the handler does not revoke permission to close, the window |
5577 | will subsequently close. If the QGuiApplication::quitOnLastWindowClosed |
5578 | property is \c true, and there are no other windows open, the application |
5579 | will quit. |
5580 | */ |
5581 | |
5582 | /*! |
5583 | \qmlmethod QtQuick::Window::raise() |
5584 | |
5585 | Raises the window in the windowing system. |
5586 | |
5587 | Requests that the window be raised to appear above other windows. |
5588 | */ |
5589 | |
5590 | /*! |
5591 | \qmlmethod QtQuick::Window::lower() |
5592 | |
5593 | Lowers the window in the windowing system. |
5594 | |
5595 | Requests that the window be lowered to appear below other windows. |
5596 | */ |
5597 | |
5598 | /*! |
5599 | \qmlmethod QtQuick::Window::show() |
5600 | |
5601 | Shows the window. |
5602 | |
5603 | This is equivalent to calling showFullScreen(), showMaximized(), or showNormal(), |
5604 | depending on the platform's default behavior for the window type and flags. |
5605 | |
5606 | \sa showFullScreen(), showMaximized(), showNormal(), hide(), QQuickItem::flags() |
5607 | */ |
5608 | |
5609 | /*! |
5610 | \qmlmethod QtQuick::Window::hide() |
5611 | |
5612 | Hides the window. |
5613 | |
5614 | Equivalent to setting \l visible to \c false or \l visibility to \l {QWindow::}{Hidden}. |
5615 | |
5616 | \sa show() |
5617 | */ |
5618 | |
5619 | /*! |
5620 | \qmlmethod QtQuick::Window::showMinimized() |
5621 | |
5622 | Shows the window as minimized. |
5623 | |
5624 | Equivalent to setting \l visibility to \l {QWindow::}{Minimized}. |
5625 | */ |
5626 | |
5627 | /*! |
5628 | \qmlmethod QtQuick::Window::showMaximized() |
5629 | |
5630 | Shows the window as maximized. |
5631 | |
5632 | Equivalent to setting \l visibility to \l {QWindow::}{Maximized}. |
5633 | */ |
5634 | |
5635 | /*! |
5636 | \qmlmethod QtQuick::Window::showFullScreen() |
5637 | |
5638 | Shows the window as fullscreen. |
5639 | |
5640 | Equivalent to setting \l visibility to \l {QWindow::}{FullScreen}. |
5641 | */ |
5642 | |
5643 | /*! |
5644 | \qmlmethod QtQuick::Window::showNormal() |
5645 | |
5646 | Shows the window as normal, i.e. neither maximized, minimized, nor fullscreen. |
5647 | |
5648 | Equivalent to setting \l visibility to \l {QWindow::}{Windowed}. |
5649 | */ |
5650 | |
5651 | /*! |
5652 | \enum QQuickWindow::RenderStage |
5653 | \since 5.4 |
5654 | |
5655 | \value BeforeSynchronizingStage Before synchronization. |
5656 | \value AfterSynchronizingStage After synchronization. |
5657 | \value BeforeRenderingStage Before rendering. |
5658 | \value AfterRenderingStage After rendering. |
5659 | \value AfterSwapStage After the frame is swapped. |
5660 | \value NoStage As soon as possible. This value was added in Qt 5.6. |
5661 | |
5662 | \sa {Scene Graph and Rendering} |
5663 | */ |
5664 | |
5665 | /*! |
5666 | \since 5.4 |
5667 | |
5668 | Schedules \a job to run when the rendering of this window reaches |
5669 | the given \a stage. |
5670 | |
5671 | This is a convenience to the equivalent signals in QQuickWindow for |
5672 | "one shot" tasks. |
5673 | |
5674 | The window takes ownership over \a job and will delete it when the |
5675 | job is completed. |
5676 | |
5677 | If rendering is shut down before \a job has a chance to run, the |
5678 | job will be run and then deleted as part of the scene graph cleanup. |
5679 | If the window is never shown and no rendering happens before the QQuickWindow |
5680 | is destroyed, all pending jobs will be destroyed without their run() |
5681 | method being called. |
5682 | |
5683 | If the rendering is happening on a different thread, then the job |
5684 | will happen on the rendering thread. |
5685 | |
5686 | If \a stage is \l NoStage, \a job will be run at the earliest opportunity |
5687 | whenever the render thread is not busy rendering a frame. If there is no |
5688 | OpenGL context available or the window is not exposed at the time the job is |
5689 | either posted or handled, it is deleted without executing the run() method. |
5690 | If a non-threaded renderer is in use, the run() method of the job is executed |
5691 | synchronously. |
5692 | The OpenGL context is changed to the renderer context before executing a |
5693 | \l NoStage job. |
5694 | |
5695 | \note This function does not trigger rendering; the jobs targeting any other |
5696 | stage than NoStage will be stored run until rendering is triggered elsewhere. |
5697 | To force the job to run earlier, call QQuickWindow::update(); |
5698 | |
5699 | \sa beforeRendering(), afterRendering(), beforeSynchronizing(), |
5700 | afterSynchronizing(), frameSwapped(), sceneGraphInvalidated() |
5701 | */ |
5702 | |
5703 | void QQuickWindow::scheduleRenderJob(QRunnable *job, RenderStage stage) |
5704 | { |
5705 | Q_D(QQuickWindow); |
5706 | |
5707 | d->renderJobMutex.lock(); |
5708 | if (stage == BeforeSynchronizingStage) { |
5709 | d->beforeSynchronizingJobs << job; |
5710 | } else if (stage == AfterSynchronizingStage) { |
5711 | d->afterSynchronizingJobs << job; |
5712 | } else if (stage == BeforeRenderingStage) { |
5713 | d->beforeRenderingJobs << job; |
5714 | } else if (stage == AfterRenderingStage) { |
5715 | d->afterRenderingJobs << job; |
5716 | } else if (stage == AfterSwapStage) { |
5717 | d->afterSwapJobs << job; |
5718 | } else if (stage == NoStage) { |
5719 | if (d->renderControl && openglContext() |
5720 | #if QT_CONFIG(opengl) |
5721 | && openglContext()->thread() == QThread::currentThread() |
5722 | #endif |
5723 | ) { |
5724 | job->run(); |
5725 | delete job; |
5726 | } else if (isExposed()) { |
5727 | d->windowManager->postJob(window: this, job); |
5728 | } else { |
5729 | delete job; |
5730 | } |
5731 | } |
5732 | d->renderJobMutex.unlock(); |
5733 | } |
5734 | |
5735 | void QQuickWindowPrivate::runAndClearJobs(QList<QRunnable *> *jobs) |
5736 | { |
5737 | renderJobMutex.lock(); |
5738 | QList<QRunnable *> jobList = *jobs; |
5739 | jobs->clear(); |
5740 | renderJobMutex.unlock(); |
5741 | |
5742 | for (QRunnable *r : qAsConst(t&: jobList)) { |
5743 | r->run(); |
5744 | delete r; |
5745 | } |
5746 | } |
5747 | |
5748 | void QQuickWindow::runJobsAfterSwap() |
5749 | { |
5750 | Q_D(QQuickWindow); |
5751 | d->runAndClearJobs(jobs: &d->afterSwapJobs); |
5752 | } |
5753 | |
5754 | /*! |
5755 | * Returns the device pixel ratio for this window. |
5756 | * |
5757 | * This is different from QWindow::devicePixelRatio() in that it supports |
5758 | * redirected rendering via QQuickRenderControl. When using a |
5759 | * QQuickRenderControl, the QQuickWindow is often not created, meaning it is |
5760 | * never shown and there is no underlying native window created in the |
5761 | * windowing system. As a result, querying properties like the device pixel |
5762 | * ratio cannot give correct results. Use this function instead. |
5763 | * |
5764 | * \sa QWindow::devicePixelRatio() |
5765 | */ |
5766 | qreal QQuickWindow::effectiveDevicePixelRatio() const |
5767 | { |
5768 | QWindow *w = QQuickRenderControl::renderWindowFor(win: const_cast<QQuickWindow *>(this)); |
5769 | return w ? w->devicePixelRatio() : devicePixelRatio(); |
5770 | } |
5771 | |
5772 | /*! |
5773 | \return the current renderer interface. The value is always valid and is never null. |
5774 | |
5775 | \note This function can be called at any time after constructing the |
5776 | QQuickWindow, even while isSceneGraphInitialized() is still false. However, |
5777 | some renderer interface functions, in particular |
5778 | QSGRendererInterface::getResource() will not be functional until the |
5779 | scenegraph is up and running. Backend queries, like |
5780 | QSGRendererInterface::graphicsApi() or QSGRendererInterface::shaderType(), |
5781 | will always be functional on the other hand. |
5782 | |
5783 | \note The ownership of the returned pointer stays with Qt. The returned |
5784 | instance may or may not be shared between different QQuickWindow instances, |
5785 | depending on the scenegraph backend in use. Therefore applications are |
5786 | expected to query the interface object for each QQuickWindow instead of |
5787 | reusing the already queried pointer. |
5788 | |
5789 | \sa QSGRenderNode, QSGRendererInterface |
5790 | |
5791 | \since 5.8 |
5792 | */ |
5793 | QSGRendererInterface *QQuickWindow::rendererInterface() const |
5794 | { |
5795 | Q_D(const QQuickWindow); |
5796 | |
5797 | // no context validity check - it is essential to be able to return a |
5798 | // renderer interface instance before scenegraphInitialized() is emitted |
5799 | // (depending on the backend, that can happen way too late for some of the |
5800 | // rif use cases, like examining the graphics api or shading language in |
5801 | // use) |
5802 | |
5803 | return d->context->sceneGraphContext()->rendererInterface(renderContext: d->context); |
5804 | } |
5805 | |
5806 | /*! |
5807 | Requests a Qt Quick scenegraph backend for the specified graphics \a api. |
5808 | Backends can either be built-in or be installed in form of dynamically |
5809 | loaded plugins. |
5810 | |
5811 | \note The call to the function must happen before constructing the first |
5812 | QQuickWindow in the application. It cannot be changed afterwards. |
5813 | |
5814 | If the selected backend is invalid or an error occurs, the default backend |
5815 | (OpenGL or software, depending on the Qt configuration) is used. |
5816 | |
5817 | \since 5.8 |
5818 | */ |
5819 | void QQuickWindow::setSceneGraphBackend(QSGRendererInterface::GraphicsApi api) |
5820 | { |
5821 | switch (api) { |
5822 | case QSGRendererInterface::Software: |
5823 | setSceneGraphBackend(QStringLiteral("software" )); |
5824 | break; |
5825 | case QSGRendererInterface::Direct3D12: |
5826 | setSceneGraphBackend(QStringLiteral("d3d12" )); |
5827 | break; |
5828 | default: |
5829 | break; |
5830 | } |
5831 | #if QT_CONFIG(opengl) /* || QT_CONFIG(vulkan) || defined(Q_OS_WIN) || defined(Q_OS_DARWIN) */ |
5832 | if (QSGRendererInterface::isApiRhiBased(api)) |
5833 | QSGRhiSupport::configure(api); |
5834 | #endif |
5835 | } |
5836 | |
5837 | /*! |
5838 | Requests the specified Qt Quick scenegraph \a backend. Backends can either |
5839 | be built-in or be installed in form of dynamically loaded plugins. |
5840 | |
5841 | \overload |
5842 | |
5843 | \note The call to the function must happen before constructing the first |
5844 | QQuickWindow in the application. It cannot be changed afterwards. |
5845 | |
5846 | If \a backend is invalid or an error occurs, the default backend (OpenGL or |
5847 | software, depending on the Qt configuration) is used. |
5848 | |
5849 | \note Calling this function is equivalent to setting the |
5850 | \c QT_QUICK_BACKEND or \c QMLSCENE_DEVICE environment variables. However, this |
5851 | API is safer to use in applications that spawn other processes as there is |
5852 | no need to worry about environment inheritance. |
5853 | |
5854 | \since 5.8 |
5855 | */ |
5856 | void QQuickWindow::setSceneGraphBackend(const QString &backend) |
5857 | { |
5858 | QSGContext::setBackend(backend); |
5859 | } |
5860 | |
5861 | /*! |
5862 | Returns the requested Qt Quick scenegraph backend. |
5863 | |
5864 | \note The return value of this function may still be outdated by |
5865 | subsequent calls to setSceneGraphBackend() until the first QQuickWindow in the |
5866 | application has been constructed. |
5867 | |
5868 | \since 5.9 |
5869 | */ |
5870 | QString QQuickWindow::sceneGraphBackend() |
5871 | { |
5872 | return QSGContext::backend(); |
5873 | } |
5874 | |
5875 | /*! |
5876 | Creates a simple rectangle node. When the scenegraph is not initialized, the return value is null. |
5877 | |
5878 | This is cross-backend alternative to constructing a QSGSimpleRectNode directly. |
5879 | |
5880 | \since 5.8 |
5881 | \sa QSGRectangleNode |
5882 | */ |
5883 | QSGRectangleNode *QQuickWindow::createRectangleNode() const |
5884 | { |
5885 | Q_D(const QQuickWindow); |
5886 | return isSceneGraphInitialized() ? d->context->sceneGraphContext()->createRectangleNode() : nullptr; |
5887 | } |
5888 | |
5889 | /*! |
5890 | Creates a simple image node. When the scenegraph is not initialized, the return value is null. |
5891 | |
5892 | This is cross-backend alternative to constructing a QSGSimpleTextureNode directly. |
5893 | |
5894 | \since 5.8 |
5895 | \sa QSGImageNode |
5896 | */ |
5897 | QSGImageNode *QQuickWindow::createImageNode() const |
5898 | { |
5899 | Q_D(const QQuickWindow); |
5900 | return isSceneGraphInitialized() ? d->context->sceneGraphContext()->createImageNode() : nullptr; |
5901 | } |
5902 | |
5903 | /*! |
5904 | Creates a nine patch node. When the scenegraph is not initialized, the return value is null. |
5905 | |
5906 | \since 5.8 |
5907 | */ |
5908 | QSGNinePatchNode *QQuickWindow::createNinePatchNode() const |
5909 | { |
5910 | Q_D(const QQuickWindow); |
5911 | return isSceneGraphInitialized() ? d->context->sceneGraphContext()->createNinePatchNode() : nullptr; |
5912 | } |
5913 | |
5914 | /*! |
5915 | \since 5.10 |
5916 | |
5917 | Returns the render type of text-like elements in Qt Quick. |
5918 | The default is QQuickWindow::QtTextRendering. |
5919 | |
5920 | \sa setTextRenderType() |
5921 | */ |
5922 | QQuickWindow::TextRenderType QQuickWindow::textRenderType() |
5923 | { |
5924 | return QQuickWindowPrivate::textRenderType; |
5925 | } |
5926 | |
5927 | /*! |
5928 | \since 5.10 |
5929 | |
5930 | Sets the default render type of text-like elements in Qt Quick to \a renderType. |
5931 | |
5932 | \note setting the render type will only affect elements created afterwards; |
5933 | the render type of existing elements will not be modified. |
5934 | |
5935 | \sa textRenderType() |
5936 | */ |
5937 | void QQuickWindow::setTextRenderType(QQuickWindow::TextRenderType renderType) |
5938 | { |
5939 | QQuickWindowPrivate::textRenderType = renderType; |
5940 | } |
5941 | |
5942 | #ifndef QT_NO_DEBUG_STREAM |
5943 | QDebug operator<<(QDebug debug, const QQuickWindow *win) |
5944 | { |
5945 | QDebugStateSaver saver(debug); |
5946 | debug.nospace(); |
5947 | if (!win) { |
5948 | debug << "QQuickWindow(0)" ; |
5949 | return debug; |
5950 | } |
5951 | |
5952 | debug << win->metaObject()->className() << '(' << static_cast<const void *>(win); |
5953 | if (win->isActive()) |
5954 | debug << " active" ; |
5955 | if (win->isExposed()) |
5956 | debug << " exposed" ; |
5957 | debug << ", visibility=" << win->visibility() << ", flags=" << win->flags(); |
5958 | if (!win->title().isEmpty()) |
5959 | debug << ", title=" << win->title(); |
5960 | if (!win->objectName().isEmpty()) |
5961 | debug << ", name=" << win->objectName(); |
5962 | if (win->parent()) |
5963 | debug << ", parent=" << static_cast<const void *>(win->parent()); |
5964 | if (win->transientParent()) |
5965 | debug << ", transientParent=" << static_cast<const void *>(win->transientParent()); |
5966 | debug << ", geometry=" ; |
5967 | QtDebugUtils::formatQRect(debug, rect: win->geometry()); |
5968 | debug << ')'; |
5969 | return debug; |
5970 | } |
5971 | #endif |
5972 | |
5973 | #include "moc_qquickwindow.cpp" |
5974 | |
5975 | QT_END_NAMESPACE |
5976 | |