Source code
Revision control
Copy as Markdown
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant
#include "qqmlclassicpreviewhandler.h"
#include "qqmlpreviewservice.h"
#include <QtCore/qtimer.h>
#include <QtGui/qwindow.h>
#include <QtGui/qguiapplication.h>
#include <QtQuick/qquickwindow.h>
#include <QtQuick/qquickitem.h>
#include <QtQml/qqmlcomponent.h>
#include <private/qqmlmetatype_p.h>
#include <private/qquickpixmap_p.h>
#include <private/qquickview_p.h>
#include <private/qv4compileddata_p.h>
QT_BEGIN_NAMESPACE
struct QuitLockDisabler
{
const bool quitLockEnabled;
Q_NODISCARD_CTOR QuitLockDisabler()
: quitLockEnabled(QCoreApplication::isQuitLockEnabled())
{
QCoreApplication::setQuitLockEnabled(false);
}
~QuitLockDisabler()
{
QCoreApplication::setQuitLockEnabled(quitLockEnabled);
}
};
static void closeAllWindows()
{
const QWindowList windows = QGuiApplication::allWindows();
for (QWindow *window : windows)
window->close();
}
static Qt::WindowFlags fixFlags(Qt::WindowFlags flags)
{
// If only the type flag is given, some other window flags are automatically assumed. When we
// add a flag, we need to make those explicit.
switch (flags) {
case Qt::Window:
return flags | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint
| Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint;
case Qt::Dialog:
case Qt::Tool:
return flags | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
default:
return flags;
}
}
QQmlClassicPreviewHandler::QQmlClassicPreviewHandler(QObject *parent)
: QQmlPreviewHandler(parent)
{
m_dummyItem.reset(new QQuickItem);
const QString platformName = QGuiApplication::platformName();
m_supportsMultipleWindows = (platformName == QStringLiteral("windows")
|| platformName == QStringLiteral("cocoa")
|| platformName == QStringLiteral("xcb")
|| platformName == QStringLiteral("wayland"));
QCoreApplication::instance()->installEventFilter(this);
}
QQmlClassicPreviewHandler::~QQmlClassicPreviewHandler()
{
clear();
}
bool QQmlClassicPreviewHandler::eventFilter(QObject *obj, QEvent *event)
{
QQuickWindow *window = currentWindow();
if (window && (event->type() == QEvent::Move) &&
qobject_cast<QQuickWindow*>(obj) == window) {
m_lastPosition.takePosition(window);
}
return QObject::eventFilter(obj, event);
}
void QQmlClassicPreviewHandler::removeEngine(QQmlEngine *qmlEngine)
{
QQmlPreviewHandler::removeEngine(qmlEngine);
for (QObject *obj : m_createdObjects)
if (obj && ::qmlEngine(obj) == qmlEngine)
delete obj;
m_createdObjects.removeAll(nullptr);
}
void QQmlClassicPreviewHandler::connectToService(QQmlPreviewServiceImpl *service)
{
QQmlPreviewHandler::connectToService(service);
connect(service, &QQmlPreviewServiceImpl::drop, this, &QQmlClassicPreviewHandler::dropCU);
connect(service, &QQmlPreviewServiceImpl::rerun, this, &QQmlClassicPreviewHandler::rerun);
connect(service, &QQmlPreviewServiceImpl::zoom, this, &QQmlClassicPreviewHandler::zoom);
}
void QQmlClassicPreviewHandler::load(const QUrl &url)
{
QSharedPointer<QuitLockDisabler> disabler(new QuitLockDisabler);
clear();
m_component.reset(nullptr);
QQuickPixmap::purgeCache();
const QList<QQmlEngine *> seenEngines = engines();
const int numEngines = seenEngines.size();
if (numEngines > 1) {
emit error(QString::fromLatin1("%1 QML engines available. We cannot decide which one "
"should load the component.").arg(numEngines));
return;
} else if (numEngines == 0) {
emit error(QLatin1String("No QML engines found."));
return;
}
m_lastPosition.loadWindowPositionSettings(url);
QQmlEngine *engine = seenEngines.front();
engine->clearSingletons();
engine->clearComponentCache();
m_component.reset(new QQmlComponent(engine, url, this));
auto onStatusChanged = [disabler, this](QQmlComponent::Status status) {
switch (status) {
case QQmlComponent::Null:
case QQmlComponent::Loading:
return true; // try again later
case QQmlComponent::Ready:
tryCreateObject();
break;
case QQmlComponent::Error:
emit error(m_component->errorString());
break;
default:
Q_UNREACHABLE();
break;
}
disconnect(m_component.data(), &QQmlComponent::statusChanged, this, nullptr);
return false; // we're done
};
if (onStatusChanged(m_component->status()))
connect(m_component.data(), &QQmlComponent::statusChanged, this, onStatusChanged);
}
void QQmlClassicPreviewHandler::dropCU(const QUrl &url)
{
// Drop any existing compilation units for this URL from the type registry.
// There can be multiple, one for each engine.
while (const auto cu = QQmlMetaType::obtainCompilationUnit(url))
QQmlMetaType::unregisterInternalCompositeType(cu);
}
void QQmlClassicPreviewHandler::rerun()
{
if (m_component.isNull() || !m_component->isReady()) {
emit error(QLatin1String("Component is not ready."));
return;
}
QuitLockDisabler disabler;
Q_UNUSED(disabler);
clear();
tryCreateObject();
}
void QQmlClassicPreviewHandler::clear()
{
qDeleteAll(m_createdObjects);
m_createdObjects.clear();
setCurrentWindow(nullptr);
}
void QQmlClassicPreviewHandler::tryCreateObject()
{
if (!m_supportsMultipleWindows)
closeAllWindows();
QObject *object = m_component->create();
m_createdObjects.append(object);
showObject(object);
}
void QQmlClassicPreviewHandler::showObject(QObject *object)
{
if (QWindow *window = qobject_cast<QWindow *>(object)) {
setCurrentWindow(qobject_cast<QQuickWindow *>(window));
for (QWindow *otherWindow : QGuiApplication::allWindows()) {
if (QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(otherWindow)) {
if (quickWindow == currentWindow())
continue;
quickWindow->setVisible(false);
quickWindow->setFlags(quickWindow->flags() & ~Qt::WindowStaysOnTopHint);
}
}
} else if (QQuickItem *item = qobject_cast<QQuickItem *>(object)) {
setCurrentWindow(nullptr);
for (QWindow *window : QGuiApplication::allWindows()) {
if (QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(window)) {
if (currentWindow() != nullptr) {
emit error(QLatin1String("Multiple QQuickWindows available. We cannot "
"decide which one to use."));
return;
}
setCurrentWindow(quickWindow);
} else {
window->setVisible(false);
window->setFlag(Qt::WindowStaysOnTopHint, false);
}
}
if (currentWindow() == nullptr) {
setCurrentWindow(new QQuickWindow);
m_createdObjects.append(currentWindow());
}
for (QQuickItem *oldItem : currentWindow()->contentItem()->childItems())
oldItem->setParentItem(m_dummyItem.data());
// Special case for QQuickView, as that keeps a "root" pointer around, and uses it to
// automatically resize the window or the item.
if (QQuickView *view = qobject_cast<QQuickView *>(currentWindow()))
QQuickViewPrivate::get(view)->setRootObject(item);
else
item->setParentItem(currentWindow()->contentItem());
currentWindow()->resize(item->size().toSize());
// used by debug translation service to get the states
setCurrentRootItem(item);
} else {
emit error(QLatin1String("Created object is neither a QWindow nor a QQuickItem."));
}
if (QQuickWindow *window = currentWindow()) {
m_lastPosition.initLastSavedWindowPosition(window);
window->setFlags(fixFlags(window->flags()) | Qt::WindowStaysOnTopHint);
window->setVisible(true);
}
}
void QQmlClassicPreviewHandler::zoom(qreal newFactor)
{
m_zoomFactor = newFactor;
QTimer::singleShot(0, this, [this, newFactor]() {
zoomWindow(currentWindow(), newFactor, &m_lastPosition);
});
}
QT_END_NAMESPACE
#include "moc_qqmlclassicpreviewhandler.cpp"