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