Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlpreviewhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
6
7#include <QtCore/qtimer.h>
8#include <QtCore/qsettings.h>
9#include <QtCore/qlibraryinfo.h>
10
11#include <QtGui/qwindow.h>
12#include <QtGui/qguiapplication.h>
13#include <QtQuick/qquickwindow.h>
14#include <QtQuick/qquickitem.h>
15#include <QtQml/qqmlcomponent.h>
16
17#include <private/qabstractanimation_p.h>
18#include <private/qhighdpiscaling_p.h>
19#include <private/qqmlmetatype_p.h>
20#include <private/qquickpixmap_p.h>
21#include <private/qquickview_p.h>
22#include <private/qv4compileddata_p.h>
23
24QT_BEGIN_NAMESPACE
25
26struct QuitLockDisabler
27{
28 const bool quitLockEnabled;
29
30 Q_NODISCARD_CTOR QuitLockDisabler()
31 : quitLockEnabled(QCoreApplication::isQuitLockEnabled())
32 {
33 QCoreApplication::setQuitLockEnabled(false);
34 }
35
36 ~QuitLockDisabler()
37 {
38 QCoreApplication::setQuitLockEnabled(quitLockEnabled);
39 }
40};
41
43{
44 m_dummyItem.reset(new QQuickItem);
45
46 // TODO: Is there a better way to determine this? We want to keep the window alive when possible
47 // as otherwise it will reappear in a different place when (re)loading a file. However,
48 // the file we load might create another window, in which case the eglfs plugin (and
49 // others?) will do a qFatal as it only supports a single window.
50 const QString platformName = QGuiApplication::platformName();
51 m_supportsMultipleWindows = (platformName == QStringLiteral("windows")
52 || platformName == QStringLiteral("cocoa")
53 || platformName == QStringLiteral("xcb")
54 || platformName == QStringLiteral("wayland"));
55
56 QCoreApplication::instance()->installEventFilter(this);
57
58 m_fpsTimer.setInterval(1000);
59 connect(&m_fpsTimer, &QTimer::timeout, this, &QQmlPreviewHandler::fpsTimerHit);
60}
61
66
67static void closeAllWindows()
68{
69 const QWindowList windows = QGuiApplication::allWindows();
70 for (QWindow *window : windows)
71 window->close();
72}
73
74bool QQmlPreviewHandler::eventFilter(QObject *obj, QEvent *event)
75{
76 if (m_currentWindow && (event->type() == QEvent::Move) &&
77 qobject_cast<QQuickWindow*>(obj) == m_currentWindow) {
78 m_lastPosition.takePosition(m_currentWindow);
79 }
80
81 return QObject::eventFilter(obj, event);
82}
83
85{
86 return m_currentRootItem;
87}
88
89void QQmlPreviewHandler::addEngine(QQmlEngine *qmlEngine)
90{
91 m_engines.append(qmlEngine);
92}
93
94void QQmlPreviewHandler::removeEngine(QQmlEngine *qmlEngine)
95{
96 const bool found = m_engines.removeOne(qmlEngine);
97 Q_ASSERT(found);
98 for (QObject *obj : m_createdObjects)
99 if (obj && ::qmlEngine(obj) == qmlEngine)
100 delete obj;
101 m_createdObjects.removeAll(nullptr);
102}
103
104void QQmlPreviewHandler::load(const QUrl &url)
105{
106 return m_settings.enableInPlaceUpdates ? loadPatch(url) : loadUrl(url);
107}
108
109void QQmlPreviewHandler::loadPatch(const QUrl &url)
110{
111 Q_UNUSED(url);
112 // TODO: Implement patching of the existing object instead of creating a new one.
113 emit hotReloadFailure(QLatin1String("In-place reload is not yet implemented"));
114}
115
116void QQmlPreviewHandler::loadUrl(const QUrl &url)
117{
118 QSharedPointer<QuitLockDisabler> disabler(new QuitLockDisabler);
119
120 clear();
121 m_component.reset(nullptr);
122 QQuickPixmap::purgeCache();
123
124 const int numEngines = m_engines.size();
125 if (numEngines > 1) {
126 emit error(QString::fromLatin1("%1 QML engines available. We cannot decide which one "
127 "should load the component.").arg(numEngines));
128 return;
129 } else if (numEngines == 0) {
130 emit error(QLatin1String("No QML engines found."));
131 return;
132 }
133 m_lastPosition.loadWindowPositionSettings(url);
134
135 QQmlEngine *engine = m_engines.front();
136 engine->clearSingletons();
137 engine->clearComponentCache();
138 m_component.reset(new QQmlComponent(engine, url, this));
139
140 auto onStatusChanged = [disabler, this](QQmlComponent::Status status) {
141 switch (status) {
142 case QQmlComponent::Null:
143 case QQmlComponent::Loading:
144 return true; // try again later
145 case QQmlComponent::Ready:
146 tryCreateObject();
147 break;
148 case QQmlComponent::Error:
149 emit error(m_component->errorString());
150 break;
151 default:
152 Q_UNREACHABLE();
153 break;
154 }
155
156 disconnect(m_component.data(), &QQmlComponent::statusChanged, this, nullptr);
157 return false; // we're done
158 };
159
160 if (onStatusChanged(m_component->status()))
161 connect(m_component.data(), &QQmlComponent::statusChanged, this, onStatusChanged);
162}
163
164void QQmlPreviewHandler::dropCU(const QUrl &url)
165{
166 // Drop any existing compilation units for this URL from the type registry.
167 // There can be multiple, one for each engine.
168 while (const auto cu = QQmlMetaType::obtainCompilationUnit(url))
169 QQmlMetaType::unregisterInternalCompositeType(cu);
170}
171
173{
174 if (m_component.isNull() || !m_component->isReady())
175 emit error(QLatin1String("Component is not ready."));
176
177 QuitLockDisabler disabler;
178 Q_UNUSED(disabler);
179 clear();
180 tryCreateObject();
181}
182
183void QQmlPreviewHandler::zoom(qreal newFactor)
184{
185 m_zoomFactor = newFactor;
186 QTimer::singleShot(0, this, &QQmlPreviewHandler::doZoom);
187}
188
190{
191 QUnifiedTimer::instance()->setSpeedModifier(newFactor);
192}
193
194void QQmlPreviewHandler::configure(const Settings &settings)
195{
196 if (m_settings.enableInPlaceUpdates && !settings.enableInPlaceUpdates) {
197 emit error(QLatin1String("Cannot disable in-place updates once enabled"));
198 return;
199 }
200 m_settings = settings;
201 emit confirmation(settings);
202}
203
204void QQmlPreviewHandler::doZoom()
205{
206 if (!m_currentWindow)
207 return;
208 if (qFuzzyIsNull(m_zoomFactor)) {
209 emit error(QString::fromLatin1("Zooming with factor: %1 will result in nothing "
210 "so it will be ignored.").arg(m_zoomFactor));
211 return;
212 }
213
214 bool resetZoom = false;
215 if (m_zoomFactor < 0) {
216 resetZoom = true;
217 m_zoomFactor = 1.0;
218 }
219
220 m_currentWindow->setGeometry(m_currentWindow->geometry());
221
222 m_lastPosition.takePosition(m_currentWindow, QQmlPreviewPosition::InitializePosition);
223 m_currentWindow->destroy();
224
225 for (QScreen *screen : QGuiApplication::screens())
226 QHighDpiScaling::setScreenFactor(screen, m_zoomFactor);
227 if (resetZoom)
228 QHighDpiScaling::updateHighDpiScaling();
229
230 m_currentWindow->show();
231 m_lastPosition.initLastSavedWindowPosition(m_currentWindow);
232}
233
235{
236 qDeleteAll(m_createdObjects);
237 m_createdObjects.clear();
238 setCurrentWindow(nullptr);
239}
240
241Qt::WindowFlags fixFlags(Qt::WindowFlags flags)
242{
243 // If only the type flag is given, some other window flags are automatically assumed. When we
244 // add a flag, we need to make those explicit.
245 switch (flags) {
246 case Qt::Window:
247 return flags | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint
248 | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint;
249 case Qt::Dialog:
250 case Qt::Tool:
251 return flags | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
252 default:
253 return flags;
254 }
255}
256
257void QQmlPreviewHandler::showObject(QObject *object)
258{
259 if (QWindow *window = qobject_cast<QWindow *>(object)) {
260 setCurrentWindow(qobject_cast<QQuickWindow *>(window));
261 for (QWindow *otherWindow : QGuiApplication::allWindows()) {
262 if (QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(otherWindow)) {
263 if (quickWindow == m_currentWindow)
264 continue;
265 quickWindow->setVisible(false);
266 quickWindow->setFlags(quickWindow->flags() & ~Qt::WindowStaysOnTopHint);
267 }
268 }
269 } else if (QQuickItem *item = qobject_cast<QQuickItem *>(object)) {
270 setCurrentWindow(nullptr);
271 for (QWindow *window : QGuiApplication::allWindows()) {
272 if (QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(window)) {
273 if (m_currentWindow != nullptr) {
274 emit error(QLatin1String("Multiple QQuickWindows available. We cannot "
275 "decide which one to use."));
276 return;
277 }
278 setCurrentWindow(quickWindow);
279 } else {
280 window->setVisible(false);
281 window->setFlag(Qt::WindowStaysOnTopHint, false);
282 }
283 }
284
285 if (m_currentWindow == nullptr) {
286 setCurrentWindow(new QQuickWindow);
287 m_createdObjects.append(m_currentWindow.data());
288 }
289
290 for (QQuickItem *oldItem : m_currentWindow->contentItem()->childItems())
291 oldItem->setParentItem(m_dummyItem.data());
292
293 // Special case for QQuickView, as that keeps a "root" pointer around, and uses it to
294 // automatically resize the window or the item.
295 if (QQuickView *view = qobject_cast<QQuickView *>(m_currentWindow))
296 QQuickViewPrivate::get(view)->setRootObject(item);
297 else
298 item->setParentItem(m_currentWindow->contentItem());
299
300 m_currentWindow->resize(item->size().toSize());
301 // used by debug translation service to get the states
302 m_currentRootItem = item;
303 } else {
304 emit error(QLatin1String("Created object is neither a QWindow nor a QQuickItem."));
305 }
306
307 if (m_currentWindow) {
308 m_lastPosition.initLastSavedWindowPosition(m_currentWindow);
309 m_currentWindow->setFlags(fixFlags(m_currentWindow->flags()) | Qt::WindowStaysOnTopHint);
310 m_currentWindow->setVisible(true);
311 }
312}
313
314void QQmlPreviewHandler::setCurrentWindow(QQuickWindow *window)
315{
316 if (window == m_currentWindow.data())
317 return;
318
319 if (m_currentWindow) {
320 disconnect(m_currentWindow.data(), &QQuickWindow::beforeSynchronizing,
321 this, &QQmlPreviewHandler::beforeSynchronizing);
322 disconnect(m_currentWindow.data(), &QQuickWindow::afterSynchronizing,
323 this, &QQmlPreviewHandler::afterSynchronizing);
324 disconnect(m_currentWindow.data(), &QQuickWindow::beforeRendering,
325 this, &QQmlPreviewHandler::beforeRendering);
326 disconnect(m_currentWindow.data(), &QQuickWindow::frameSwapped,
327 this, &QQmlPreviewHandler::frameSwapped);
328 m_fpsTimer.stop();
329 m_rendering = FrameTime();
330 m_synchronizing = FrameTime();
331 }
332
333 m_currentWindow = window;
334
335 if (m_currentWindow) {
336 connect(m_currentWindow.data(), &QQuickWindow::beforeSynchronizing,
337 this, &QQmlPreviewHandler::beforeSynchronizing, Qt::DirectConnection);
338 connect(m_currentWindow.data(), &QQuickWindow::afterSynchronizing,
339 this, &QQmlPreviewHandler::afterSynchronizing, Qt::DirectConnection);
340 connect(m_currentWindow.data(), &QQuickWindow::beforeRendering,
341 this, &QQmlPreviewHandler::beforeRendering, Qt::DirectConnection);
342 connect(m_currentWindow.data(), &QQuickWindow::frameSwapped,
343 this, &QQmlPreviewHandler::frameSwapped, Qt::DirectConnection);
344 m_fpsTimer.start();
345 }
346}
347
348void QQmlPreviewHandler::beforeSynchronizing()
349{
350 m_synchronizing.beginFrame();
351}
352
353void QQmlPreviewHandler::afterSynchronizing()
354{
355
356 if (m_rendering.elapsed >= 0)
357 m_rendering.endFrame();
358 m_synchronizing.recordFrame();
359 m_synchronizing.endFrame();
360}
361
362void QQmlPreviewHandler::beforeRendering()
363{
364 m_rendering.beginFrame();
365}
366
367void QQmlPreviewHandler::frameSwapped()
368{
369 m_rendering.recordFrame();
370}
371
372void QQmlPreviewHandler::FrameTime::beginFrame()
373{
374 timer.start();
375}
376
377void QQmlPreviewHandler::FrameTime::recordFrame()
378{
379 elapsed = timer.elapsed();
380}
381
382void QQmlPreviewHandler::FrameTime::endFrame()
383{
384 if (elapsed < min)
385 min = static_cast<quint16>(qMax(0ll, elapsed));
386 if (elapsed > max)
387 max = static_cast<quint16>(qMin(qint64(std::numeric_limits<quint16>::max()), elapsed));
388 total = static_cast<quint16>(qBound(0ll, qint64(std::numeric_limits<quint16>::max()),
389 elapsed + total));
390 ++number;
391 elapsed = -1;
392}
393
394void QQmlPreviewHandler::FrameTime::reset()
395{
396 min = std::numeric_limits<quint16>::max();
397 max = 0;
398 total = 0;
399 number = 0;
400}
401
402void QQmlPreviewHandler::fpsTimerHit()
403{
404 const FpsInfo info = {
405 m_synchronizing.number,
406 m_synchronizing.min,
407 m_synchronizing.max,
408 m_synchronizing.total,
409
410 m_rendering.number,
411 m_rendering.min,
412 m_rendering.max,
413 m_rendering.total
414 };
415
416 emit fps(info);
417
418 m_rendering.reset();
419 m_synchronizing.reset();
420}
421
422void QQmlPreviewHandler::tryCreateObject()
423{
424 if (!m_supportsMultipleWindows)
426 QObject *object = m_component->create();
427 m_createdObjects.append(object);
428 showObject(object);
429}
430
431QT_END_NAMESPACE
432
433#include "moc_qqmlpreviewhandler.cpp"
void dropCU(const QUrl &url)
void load(const QUrl &url)
void zoom(qreal newFactor)
QQuickItem * currentRootItem()
bool eventFilter(QObject *obj, QEvent *event) override
Filters events if this object has been installed as an event filter for the watched object.
void setAnimationSpeed(qreal newFactor)
void removeEngine(QQmlEngine *engine)
void configure(const Settings &settings)
void addEngine(QQmlEngine *engine)
void loadPatch(const QUrl &url)
void loadUrl(const QUrl &url)
QQmlPreviewHandler(QObject *parent=nullptr)
static void closeAllWindows()
Qt::WindowFlags fixFlags(Qt::WindowFlags flags)