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