aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qmldbg_preview.pro29
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp202
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h89
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp432
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.h125
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp181
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h105
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.cpp313
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.h92
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp102
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.h76
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp150
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h97
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.json3
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.cpp39
-rw-r--r--src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.h58
-rw-r--r--src/plugins/qmltooling/qmltooling.pro5
-rw-r--r--src/qmldebug/qmldebug.pro3
-rw-r--r--src/qmldebug/qqmlpreviewclient.cpp120
-rw-r--r--src/qmldebug/qqmlpreviewclient_p.h95
-rw-r--r--src/qmldebug/qqmlpreviewclient_p_p.h70
-rw-r--r--tests/auto/qml/debugger/debugger.pro3
-rw-r--r--tests/auto/qml/debugger/qqmlpreview/data/broken.qml31
-rw-r--r--tests/auto/qml/debugger/qqmlpreview/data/qtquick2.qml71
-rw-r--r--tests/auto/qml/debugger/qqmlpreview/data/window.qml44
-rw-r--r--tests/auto/qml/debugger/qqmlpreview/data/window1.qml43
-rw-r--r--tests/auto/qml/debugger/qqmlpreview/data/window2.qml43
-rw-r--r--tests/auto/qml/debugger/qqmlpreview/data/zoom.qml51
-rw-r--r--tests/auto/qml/debugger/qqmlpreview/qqmlpreview.pro24
-rw-r--r--tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp307
-rw-r--r--tests/auto/qml/qmlmin/tst_qmlmin.cpp1
31 files changed, 3002 insertions, 2 deletions
diff --git a/src/plugins/qmltooling/qmldbg_preview/qmldbg_preview.pro b/src/plugins/qmltooling/qmldbg_preview/qmldbg_preview.pro
new file mode 100644
index 0000000000..08686a43e3
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qmldbg_preview.pro
@@ -0,0 +1,29 @@
+QT += core-private qml-private packetprotocol-private network quick-private gui-private
+
+TARGET = qmldbg_preview
+
+SOURCES += \
+ $$PWD/qqmlpreviewblacklist.cpp \
+ $$PWD/qqmlpreviewfileengine.cpp \
+ $$PWD/qqmlpreviewfileloader.cpp \
+ $$PWD/qqmlpreviewhandler.cpp \
+ $$PWD/qqmlpreviewposition.cpp \
+ $$PWD/qqmlpreviewservice.cpp \
+ $$PWD/qqmlpreviewservicefactory.cpp
+
+HEADERS += \
+ $$PWD/qqmlpreviewblacklist.h \
+ $$PWD/qqmlpreviewfileengine.h \
+ $$PWD/qqmlpreviewfileloader.h \
+ $$PWD/qqmlpreviewhandler.h \
+ $$PWD/qqmlpreviewposition.h \
+ $$PWD/qqmlpreviewservice.h \
+ $$PWD/qqmlpreviewservicefactory.h
+
+OTHER_FILES += \
+ $$PWD/qqmlpreviewservice.json
+
+PLUGIN_TYPE = qmltooling
+PLUGIN_CLASS_NAME = QQmlPreviewServiceFactory
+
+load(qt_plugin)
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp
new file mode 100644
index 0000000000..d942740db3
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp
@@ -0,0 +1,202 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlpreviewblacklist.h"
+
+QT_BEGIN_NAMESPACE
+
+void QQmlPreviewBlacklist::blacklist(const QString &path)
+{
+ if (!path.isEmpty())
+ m_root.insert(path, 0);
+}
+
+void QQmlPreviewBlacklist::whitelist(const QString &path)
+{
+ if (!path.isEmpty())
+ m_root.remove(path, 0);
+}
+
+bool QQmlPreviewBlacklist::isBlacklisted(const QString &path) const
+{
+ return path.isEmpty() ? true : m_root.containedPrefixLeaf(path, 0) > 0;
+}
+
+void QQmlPreviewBlacklist::clear()
+{
+ m_root = Node();
+}
+
+QQmlPreviewBlacklist::Node::Node()
+{
+}
+
+QQmlPreviewBlacklist::Node::Node(const QQmlPreviewBlacklist::Node &other) :
+ m_mine(other.m_mine), m_isLeaf(other.m_isLeaf)
+{
+ for (auto it = other.m_next.begin(), end = other.m_next.end(); it != end; ++it)
+ m_next.insert(it.key(), new Node(**it));
+}
+
+QQmlPreviewBlacklist::Node::Node(QQmlPreviewBlacklist::Node &&other) Q_DECL_NOEXCEPT
+{
+ m_mine.swap(other.m_mine);
+ m_next.swap(other.m_next);
+ m_isLeaf = other.m_isLeaf;
+}
+
+QQmlPreviewBlacklist::Node::~Node()
+{
+ qDeleteAll(m_next);
+}
+
+QQmlPreviewBlacklist::Node &QQmlPreviewBlacklist::Node::operator=(
+ const QQmlPreviewBlacklist::Node &other)
+{
+ if (&other != this) {
+ m_mine = other.m_mine;
+ for (auto it = other.m_next.begin(), end = other.m_next.end(); it != end; ++it)
+ m_next.insert(it.key(), new Node(**it));
+ m_isLeaf = other.m_isLeaf;
+ }
+ return *this;
+}
+
+QQmlPreviewBlacklist::Node &QQmlPreviewBlacklist::Node::operator=(
+ QQmlPreviewBlacklist::Node &&other) Q_DECL_NOEXCEPT
+{
+ if (&other != this) {
+ m_mine.swap(other.m_mine);
+ m_next.swap(other.m_next);
+ m_isLeaf = other.m_isLeaf;
+ }
+ return *this;
+}
+
+void QQmlPreviewBlacklist::Node::split(QString::iterator it, QString::iterator end)
+{
+ QString existing;
+ existing.resize(end - it - 1);
+ std::copy(it + 1, end, existing.begin());
+
+ Node *node = new Node(existing, m_next, m_isLeaf);
+ m_next.clear();
+ m_next.insert(*it, node);
+ m_mine.resize(it - m_mine.begin());
+ m_isLeaf = false;
+}
+
+void QQmlPreviewBlacklist::Node::insert(const QString &path, int offset)
+{
+ for (auto it = m_mine.begin(), end = m_mine.end(); it != end; ++it) {
+ if (offset == path.size()) {
+ split(it, end);
+ m_isLeaf = true;
+ return;
+ }
+
+ if (path.at(offset) != *it) {
+ split(it, end);
+
+ QString inserted;
+ inserted.resize(path.size() - offset - 1);
+ std::copy(path.begin() + offset + 1, path.end(), inserted.begin());
+ m_next.insert(path.at(offset), new Node(inserted));
+ return;
+ }
+
+ ++offset;
+ }
+
+ if (offset == path.size()) {
+ m_isLeaf = true;
+ return;
+ }
+
+ Node *&node = m_next[path.at(offset++)];
+ if (node == nullptr) {
+ QString inserted;
+ inserted.resize(path.size() - offset);
+ std::copy(path.begin() + offset, path.end(), inserted.begin());
+ node = new Node(inserted);
+ } else {
+ node->insert(path, offset);
+ }
+}
+
+void QQmlPreviewBlacklist::Node::remove(const QString &path, int offset)
+{
+ for (auto it = m_mine.begin(), end = m_mine.end(); it != end; ++it) {
+ if (offset == path.size() || path.at(offset) != *it) {
+ split(it, end);
+ return;
+ }
+ ++offset;
+ }
+
+ m_isLeaf = false;
+ if (offset == path.size())
+ return;
+
+ auto it = m_next.find(path.at(offset));
+ if (it != m_next.end())
+ (*it)->remove(path, ++offset);
+}
+
+int QQmlPreviewBlacklist::Node::containedPrefixLeaf(const QString &path, int offset) const
+{
+ if (offset == path.size())
+ return (m_mine.isEmpty() && m_isLeaf) ? offset : -1;
+
+ for (auto it = m_mine.begin(), end = m_mine.end(); it != end; ++it) {
+ if (path.at(offset) != *it)
+ return -1;
+
+ if (++offset == path.size())
+ return (++it == end && m_isLeaf) ? offset : -1;
+ }
+
+ const QChar c = path.at(offset);
+ if (m_isLeaf && c == '/')
+ return offset;
+
+ auto it = m_next.find(c);
+ if (it == m_next.end())
+ return -1;
+
+ return (*it)->containedPrefixLeaf(path, ++offset);
+}
+
+QQmlPreviewBlacklist::Node::Node(const QString &mine,
+ const QHash<QChar, QQmlPreviewBlacklist::Node *> &next,
+ bool isLeaf)
+ : m_mine(mine), m_next(next), m_isLeaf(isLeaf)
+{
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h
new file mode 100644
index 0000000000..ab9c3a3d8a
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLPREVIEWBLACKLIST_H
+#define QQMLPREVIEWBLACKLIST_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qhash.h>
+#include <QtCore/qchar.h>
+#include <QtCore/qstring.h>
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlPreviewBlacklist
+{
+public:
+ void blacklist(const QString &path);
+ void whitelist(const QString &path);
+ bool isBlacklisted(const QString &path) const;
+ void clear();
+
+private:
+ class Node {
+ public:
+ Node();
+ Node(const Node &other);
+ Node(Node &&other) Q_DECL_NOEXCEPT;
+
+ ~Node();
+
+ Node &operator=(const Node &other);
+ Node &operator=(Node &&other) Q_DECL_NOEXCEPT;
+
+ void split(QString::iterator it, QString::iterator end);
+ void insert(const QString &path, int offset);
+ void remove(const QString &path, int offset);
+ int containedPrefixLeaf(const QString &path, int offset) const;
+
+ private:
+ Node(const QString &mine, const QHash<QChar, Node *> &next = QHash<QChar, Node *>(),
+ bool isLeaf = true);
+
+ QString m_mine;
+ QHash<QChar, Node *> m_next;
+ bool m_isLeaf = false;
+ };
+
+ Node m_root;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLPREVIEWBLACKLIST_H
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp
new file mode 100644
index 0000000000..f88cebf806
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp
@@ -0,0 +1,432 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlpreviewfileengine.h"
+#include "qqmlpreviewservice.h"
+
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qthread.h>
+#include <QtCore/qwaitcondition.h>
+
+#include <cstring>
+
+QT_BEGIN_NAMESPACE
+
+static bool isRelative(const QString &path)
+{
+ if (path.isEmpty())
+ return true;
+ if (path.at(0) == '/')
+ return false;
+ if (path.at(0) == ':' && path.length() >= 2 && path.at(1) == '/')
+ return false;
+#ifdef Q_OS_WIN
+ if (path.length() >= 2 && path.at(1) == ':')
+ return false;
+#endif
+ return true;
+}
+
+static QString absolutePath(const QString &path)
+{
+ return QDir::cleanPath(isRelative(path) ? (QDir::currentPath() + '/' + path) : path);
+}
+
+bool isRootPath(const QString &path)
+{
+ return QFileSystemEntry::isRootPath(path);
+}
+
+class QQmlPreviewFileEngineIterator : public QAbstractFileEngineIterator
+{
+public:
+ QQmlPreviewFileEngineIterator(QDir::Filters filters, const QStringList &filterNames,
+ const QStringList &m_entries);
+ ~QQmlPreviewFileEngineIterator();
+
+ QString next() override;
+ bool hasNext() const override;
+ QString currentFileName() const override;
+
+private:
+ const QStringList m_entries;
+ int m_index;
+};
+
+QQmlPreviewFileEngineIterator::QQmlPreviewFileEngineIterator(QDir::Filters filters,
+ const QStringList &filterNames,
+ const QStringList &entries)
+ : QAbstractFileEngineIterator(filters, filterNames), m_entries(entries), m_index(0)
+{
+}
+
+QQmlPreviewFileEngineIterator::~QQmlPreviewFileEngineIterator()
+{
+}
+
+QString QQmlPreviewFileEngineIterator::next()
+{
+ if (!hasNext())
+ return QString();
+ ++m_index;
+ return currentFilePath();
+}
+
+bool QQmlPreviewFileEngineIterator::hasNext() const
+{
+ return m_index < m_entries.size();
+}
+
+QString QQmlPreviewFileEngineIterator::currentFileName() const
+{
+ if (m_index == 0 || m_index > m_entries.size())
+ return QString();
+ return m_entries.at(m_index - 1);
+}
+
+QQmlPreviewFileEngine::QQmlPreviewFileEngine(const QString &file, const QString &absolute,
+ QQmlPreviewFileLoader *loader) :
+ m_name(file), m_absolute(absolute), m_loader(loader)
+{
+ load();
+}
+
+void QQmlPreviewFileEngine::setFileName(const QString &file)
+{
+ m_name = file;
+ m_absolute = absolutePath(file);
+ m_fallback.reset();
+ m_contents.close();
+ m_contents.setData(QByteArray());
+ m_entries.clear();
+ load();
+}
+
+bool QQmlPreviewFileEngine::open(QIODevice::OpenMode flags)
+{
+ switch (m_result) {
+ case QQmlPreviewFileLoader::File:
+ return m_contents.open(flags);
+ case QQmlPreviewFileLoader::Directory:
+ return false;
+ case QQmlPreviewFileLoader::Fallback:
+ return m_fallback->open(flags);
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+bool QQmlPreviewFileEngine::close()
+{
+ switch (m_result) {
+ case QQmlPreviewFileLoader::Fallback:
+ return m_fallback->close();
+ case QQmlPreviewFileLoader::File:
+ m_contents.close();
+ return true;
+ case QQmlPreviewFileLoader::Directory:
+ return false;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+qint64 QQmlPreviewFileEngine::size() const
+{
+ return m_fallback ? m_fallback->size() : m_contents.size();
+}
+
+qint64 QQmlPreviewFileEngine::pos() const
+{
+ return m_fallback ? m_fallback->pos() : m_contents.pos();
+}
+
+bool QQmlPreviewFileEngine::seek(qint64 newPos)
+{
+ return m_fallback? m_fallback->seek(newPos) : m_contents.seek(newPos);
+}
+
+qint64 QQmlPreviewFileEngine::read(char *data, qint64 maxlen)
+{
+ return m_fallback ? m_fallback->read(data, maxlen) : m_contents.read(data, maxlen);
+}
+
+QAbstractFileEngine::FileFlags QQmlPreviewFileEngine::fileFlags(
+ QAbstractFileEngine::FileFlags type) const
+{
+ if (m_fallback)
+ return m_fallback->fileFlags(type);
+
+ QAbstractFileEngine::FileFlags ret = 0;
+
+ if (type & PermsMask) {
+ ret |= QAbstractFileEngine::FileFlags(
+ ReadOwnerPerm | ReadUserPerm | ReadGroupPerm | ReadOtherPerm);
+ }
+
+ if (type & TypesMask) {
+ if (m_result == QQmlPreviewFileLoader::Directory)
+ ret |= DirectoryType;
+ else
+ ret |= FileType;
+ }
+
+ if (type & FlagsMask) {
+ ret |= ExistsFlag;
+ if (isRootPath(m_name))
+ ret |= RootFlag;
+ }
+
+ return ret;
+}
+
+QString QQmlPreviewFileEngine::fileName(QAbstractFileEngine::FileName file) const
+{
+ if (m_fallback)
+ return m_fallback->fileName(file);
+
+ if (file == BaseName) {
+ int slashPos = m_name.lastIndexOf('/');
+ if (slashPos == -1)
+ return m_name;
+ return m_name.mid(slashPos + 1);
+ } else if (file == PathName || file == AbsolutePathName) {
+ const QString path = (file == AbsolutePathName) ? m_absolute : m_name;
+ const int slashPos = path.lastIndexOf('/');
+ if (slashPos == -1)
+ return QString();
+ else if (slashPos == 0)
+ return "/";
+ return path.left(slashPos);
+ } else if (file == CanonicalName || file == CanonicalPathName) {
+ if (file == CanonicalPathName) {
+ const int slashPos = m_absolute.lastIndexOf('/');
+ if (slashPos != -1)
+ return m_absolute.left(slashPos);
+ }
+ return m_absolute;
+ }
+ return m_name;
+}
+
+uint QQmlPreviewFileEngine::ownerId(QAbstractFileEngine::FileOwner owner) const
+{
+ return m_fallback ? m_fallback->ownerId(owner) : static_cast<uint>(-2);
+}
+
+QAbstractFileEngine::Iterator *QQmlPreviewFileEngine::beginEntryList(QDir::Filters filters,
+ const QStringList &filterNames)
+{
+ return m_fallback ? m_fallback->beginEntryList(filters, filterNames)
+ : new QQmlPreviewFileEngineIterator(filters, filterNames, m_entries);
+}
+
+QAbstractFileEngine::Iterator *QQmlPreviewFileEngine::endEntryList()
+{
+ return m_fallback ? m_fallback->endEntryList() : nullptr;
+}
+
+bool QQmlPreviewFileEngine::flush()
+{
+ return m_fallback ? m_fallback->flush() : true;
+}
+
+bool QQmlPreviewFileEngine::syncToDisk()
+{
+ return m_fallback ? m_fallback->syncToDisk() : false;
+}
+
+bool QQmlPreviewFileEngine::isSequential() const
+{
+ return m_fallback ? m_fallback->isSequential() : m_contents.isSequential();
+}
+
+bool QQmlPreviewFileEngine::remove()
+{
+ return m_fallback ? m_fallback->remove() : false;
+}
+
+bool QQmlPreviewFileEngine::copy(const QString &newName)
+{
+ return m_fallback ? m_fallback->copy(newName) : false;
+}
+
+bool QQmlPreviewFileEngine::rename(const QString &newName)
+{
+ return m_fallback ? m_fallback->rename(newName) : false;
+}
+
+bool QQmlPreviewFileEngine::renameOverwrite(const QString &newName)
+{
+ return m_fallback ? m_fallback->renameOverwrite(newName) : false;
+}
+
+bool QQmlPreviewFileEngine::link(const QString &newName)
+{
+ return m_fallback ? m_fallback->link(newName) : false;
+}
+
+bool QQmlPreviewFileEngine::mkdir(const QString &dirName, bool createParentDirectories) const
+{
+ return m_fallback ? m_fallback->mkdir(dirName, createParentDirectories) : false;
+}
+
+bool QQmlPreviewFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const
+{
+ return m_fallback ? m_fallback->rmdir(dirName, recurseParentDirectories) : false;
+}
+
+bool QQmlPreviewFileEngine::setSize(qint64 size)
+{
+ switch (m_result) {
+ case QQmlPreviewFileLoader::Fallback:
+ return m_fallback->setSize(size);
+ case QQmlPreviewFileLoader::File:
+ if (size < 0 || size > std::numeric_limits<int>::max())
+ return false;
+ m_contents.buffer().resize(static_cast<int>(size));
+ return true;
+ case QQmlPreviewFileLoader::Directory:
+ return false;
+ default:
+ Q_UNREACHABLE();
+ return false;
+ }
+}
+
+bool QQmlPreviewFileEngine::caseSensitive() const
+{
+ return m_fallback ? m_fallback->caseSensitive() : true;
+}
+
+bool QQmlPreviewFileEngine::isRelativePath() const
+{
+ return m_fallback ? m_fallback->isRelativePath() : isRelative(m_name);
+}
+
+QStringList QQmlPreviewFileEngine::entryList(QDir::Filters filters,
+ const QStringList &filterNames) const
+{
+ return m_fallback ? m_fallback->entryList(filters, filterNames)
+ : QAbstractFileEngine::entryList(filters, filterNames);
+}
+
+bool QQmlPreviewFileEngine::setPermissions(uint perms)
+{
+ return m_fallback ? m_fallback->setPermissions(perms) : false;
+}
+
+QByteArray QQmlPreviewFileEngine::id() const
+{
+ return m_fallback ? m_fallback->id() : QByteArray();
+}
+
+QString QQmlPreviewFileEngine::owner(FileOwner owner) const
+{
+ return m_fallback ? m_fallback->owner(owner) : QString();
+}
+
+QDateTime QQmlPreviewFileEngine::fileTime(FileTime time) const
+{
+ // Files we replace are always newer than the ones we had before. This makes the QML engine
+ // actually recompile them, rather than pick them from the cache.
+ return m_fallback ? m_fallback->fileTime(time) : QDateTime::currentDateTime();
+}
+
+int QQmlPreviewFileEngine::handle() const
+{
+ return m_fallback ? m_fallback->handle() : -1;
+}
+
+qint64 QQmlPreviewFileEngine::readLine(char *data, qint64 maxlen)
+{
+ return m_fallback ? m_fallback->readLine(data, maxlen) : m_contents.readLine(data, maxlen);
+}
+
+qint64 QQmlPreviewFileEngine::write(const char *data, qint64 len)
+{
+ return m_fallback ? m_fallback->write(data, len) : m_contents.write(data, len);
+}
+
+bool QQmlPreviewFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
+{
+ return m_fallback ? m_fallback->extension(extension, option, output) : false;
+}
+
+bool QQmlPreviewFileEngine::supportsExtension(Extension extension) const
+{
+ return m_fallback ? m_fallback->supportsExtension(extension) : false;
+}
+
+void QQmlPreviewFileEngine::load() const
+{
+ m_result = m_loader->load(m_absolute);
+ switch (m_result) {
+ case QQmlPreviewFileLoader::File:
+ m_contents.setData(m_loader->contents());
+ break;
+ case QQmlPreviewFileLoader::Directory:
+ m_entries = m_loader->entries();
+ break;
+ case QQmlPreviewFileLoader::Fallback:
+ m_fallback.reset(QAbstractFileEngine::create(m_name));
+ break;
+ case QQmlPreviewFileLoader::Unknown:
+ Q_UNREACHABLE();
+ break;
+ }
+}
+
+QQmlPreviewFileEngineHandler::QQmlPreviewFileEngineHandler(QQmlPreviewFileLoader *loader)
+ : m_loader(loader)
+{
+}
+
+QAbstractFileEngine *QQmlPreviewFileEngineHandler::create(const QString &fileName) const
+{
+ // Don't load compiled QML/JS over the network
+ if (fileName.endsWith(".qmlc") || fileName.endsWith(".jsc") || isRootPath(fileName)) {
+ return nullptr;
+ }
+
+ QString relative = fileName;
+ while (relative.endsWith('/'))
+ relative.chop(1);
+
+ if (relative.isEmpty() || relative == ":")
+ return nullptr;
+
+ const QString absolute = relative.startsWith(':') ? relative : absolutePath(relative);
+
+ return m_loader->isBlacklisted(absolute)
+ ? nullptr : new QQmlPreviewFileEngine(relative, absolute, m_loader.data());
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.h
new file mode 100644
index 0000000000..60af76c334
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.h
@@ -0,0 +1,125 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLPREVIEWFILEENGINE_H
+#define QQMLPREVIEWFILEENGINE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmlpreviewfileloader.h"
+
+#include <private/qabstractfileengine_p.h>
+#include <private/qfsfileengine_p.h>
+#include <QtCore/qbuffer.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlPreviewFileEngine : public QAbstractFileEngine
+{
+public:
+ QQmlPreviewFileEngine(const QString &file, const QString &absolute,
+ QQmlPreviewFileLoader *loader);
+
+ void setFileName(const QString &file) override;
+
+ bool open(QIODevice::OpenMode flags) override ;
+ bool close() override;
+ qint64 size() const override;
+ qint64 pos() const override;
+ bool seek(qint64) override;
+ qint64 read(char *data, qint64 maxlen) override;
+
+ FileFlags fileFlags(FileFlags type) const override;
+ QString fileName(QAbstractFileEngine::FileName file) const override;
+ uint ownerId(FileOwner) const override;
+
+ Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override;
+ Iterator *endEntryList() override;
+
+ // Forwarding to fallback if exists
+ bool flush() override;
+ bool syncToDisk() override;
+ bool isSequential() const override;
+ bool remove() override;
+ bool copy(const QString &newName) override;
+ bool rename(const QString &newName) override;
+ bool renameOverwrite(const QString &newName) override;
+ bool link(const QString &newName) override;
+ bool mkdir(const QString &dirName, bool createParentDirectories) const override;
+ bool rmdir(const QString &dirName, bool recurseParentDirectories) const override;
+ bool setSize(qint64 size) override;
+ bool caseSensitive() const override;
+ bool isRelativePath() const override;
+ QStringList entryList(QDir::Filters filters, const QStringList &filterNames) const override;
+ bool setPermissions(uint perms) override;
+ QByteArray id() const override;
+ QString owner(FileOwner) const override;
+ QDateTime fileTime(FileTime time) const override;
+ int handle() const override;
+ qint64 readLine(char *data, qint64 maxlen) override;
+ qint64 write(const char *data, qint64 len) override;
+ bool extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) override;
+ bool supportsExtension(Extension extension) const override;
+
+private:
+ void load() const;
+
+ QString m_name;
+ QString m_absolute;
+ QPointer<QQmlPreviewFileLoader> m_loader;
+
+ mutable QBuffer m_contents;
+ mutable QStringList m_entries;
+ mutable QScopedPointer<QAbstractFileEngine> m_fallback;
+ mutable QQmlPreviewFileLoader::Result m_result = QQmlPreviewFileLoader::Unknown;
+};
+
+class QQmlPreviewFileEngineHandler : public QAbstractFileEngineHandler
+{
+public:
+ QQmlPreviewFileEngineHandler(QQmlPreviewFileLoader *loader);
+ QAbstractFileEngine *create(const QString &fileName) const override;
+
+private:
+ QPointer<QQmlPreviewFileLoader> m_loader;
+};
+
+
+
+QT_END_NAMESPACE
+
+#endif // QQMLPREVIEWFILEENGINE_H
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp
new file mode 100644
index 0000000000..6026d58746
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp
@@ -0,0 +1,181 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlpreviewfileloader.h"
+#include "qqmlpreviewservice.h"
+
+#include <QtCore/qlibraryinfo.h>
+#include <QtCore/qstandardpaths.h>
+
+QT_BEGIN_NAMESPACE
+
+QQmlPreviewFileLoader::QQmlPreviewFileLoader(QQmlPreviewServiceImpl *service) : m_service(service)
+{
+ // Exclude some resource paths used by Qt itself. There is no point in loading those from the
+ // client as the client will not have the files (or even worse, it may have different ones).
+ m_blacklist.blacklist(":/qt-project.org");
+ m_blacklist.blacklist(":/QtQuick/Controls/Styles");
+ m_blacklist.blacklist(":/ExtrasImports/QtQuick/Controls/Styles");
+
+ for (int loc = QLibraryInfo::PrefixPath; loc <= QLibraryInfo::TestsPath; ++loc) {
+ m_blacklist.blacklist(QLibraryInfo::location(
+ static_cast<QLibraryInfo::LibraryLocation>(loc)));
+ }
+ m_blacklist.blacklist(QLibraryInfo::location(QLibraryInfo::SettingsPath));
+
+ static const QStandardPaths::StandardLocation blackListLocations[] = {
+ QStandardPaths::DataLocation,
+ QStandardPaths::CacheLocation,
+ QStandardPaths::GenericDataLocation,
+ QStandardPaths::RuntimeLocation,
+ QStandardPaths::ConfigLocation,
+ QStandardPaths::GenericCacheLocation,
+ QStandardPaths::GenericConfigLocation,
+ QStandardPaths::AppDataLocation,
+ QStandardPaths::AppConfigLocation
+ };
+
+ for (auto locationType : blackListLocations) {
+ const QStringList locations = QStandardPaths::standardLocations(locationType);
+ for (const QString &location : locations)
+ m_blacklist.blacklist(location);
+ }
+
+ connect(this, &QQmlPreviewFileLoader::request, service, &QQmlPreviewServiceImpl::forwardRequest,
+ Qt::DirectConnection);
+ connect(service, &QQmlPreviewServiceImpl::directory, this, &QQmlPreviewFileLoader::directory);
+ connect(service, &QQmlPreviewServiceImpl::file, this, &QQmlPreviewFileLoader::file);
+ connect(service, &QQmlPreviewServiceImpl::error, this, &QQmlPreviewFileLoader::error);
+ connect(service, &QQmlPreviewServiceImpl::clearCache, this, &QQmlPreviewFileLoader::clearCache);
+ moveToThread(&m_thread);
+ m_thread.start();
+}
+
+QQmlPreviewFileLoader::~QQmlPreviewFileLoader() {
+ m_thread.quit();
+ m_thread.wait();
+}
+
+QQmlPreviewFileLoader::Result QQmlPreviewFileLoader::load(const QString &path)
+{
+ QMutexLocker locker(&m_mutex);
+ m_path = path;
+
+ auto fileIterator = m_fileCache.constFind(path);
+ if (fileIterator != m_fileCache.constEnd()) {
+ m_result = File;
+ m_contents = *fileIterator;
+ m_entries.clear();
+ return m_result;
+ }
+
+ auto dirIterator = m_directoryCache.constFind(path);
+ if (dirIterator != m_directoryCache.constEnd()) {
+ m_result = Directory;
+ m_contents.clear();
+ m_entries = *dirIterator;
+ return m_result;
+ }
+
+ m_result = Unknown;
+ m_entries.clear();
+ m_contents.clear();
+ emit request(path);
+ m_waitCondition.wait(&m_mutex);
+ return m_result;
+}
+
+QByteArray QQmlPreviewFileLoader::contents()
+{
+ QMutexLocker locker(&m_mutex);
+ return m_contents;
+}
+
+QStringList QQmlPreviewFileLoader::entries()
+{
+ QMutexLocker locker(&m_mutex);
+ return m_entries;
+}
+
+void QQmlPreviewFileLoader::whitelist(const QUrl &url)
+{
+ const QString path = QQmlFile::urlToLocalFileOrQrc(url);
+ if (!path.isEmpty()) {
+ QMutexLocker locker(&m_mutex);
+ m_blacklist.whitelist(path);
+ }
+}
+
+bool QQmlPreviewFileLoader::isBlacklisted(const QString &path)
+{
+ QMutexLocker locker(&m_mutex);
+ return m_blacklist.isBlacklisted(path);
+}
+
+void QQmlPreviewFileLoader::file(const QString &path, const QByteArray &contents)
+{
+ QMutexLocker locker(&m_mutex);
+ m_blacklist.whitelist(path);
+ m_fileCache[path] = contents;
+ if (path == m_path) {
+ m_contents = contents;
+ m_result = File;
+ m_waitCondition.wakeOne();
+ }
+}
+
+void QQmlPreviewFileLoader::directory(const QString &path, const QStringList &entries)
+{
+ QMutexLocker locker(&m_mutex);
+ m_blacklist.whitelist(path);
+ m_directoryCache[path] = entries;
+ if (path == m_path) {
+ m_entries = entries;
+ m_result = Directory;
+ m_waitCondition.wakeOne();
+ }
+}
+
+void QQmlPreviewFileLoader::error(const QString &path)
+{
+ QMutexLocker locker(&m_mutex);
+ m_blacklist.blacklist(path);
+ if (path == m_path) {
+ m_result = Fallback;
+ m_waitCondition.wakeOne();
+ }
+}
+
+void QQmlPreviewFileLoader::clearCache()
+{
+ QMutexLocker locker(&m_mutex);
+ m_fileCache.clear();
+ m_directoryCache.clear();
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h
new file mode 100644
index 0000000000..de0a9cadea
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLPREVIEWFILELOADER_H
+#define QQMLPREVIEWFILELOADER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmlpreviewblacklist.h"
+
+#include <QtCore/qobject.h>
+#include <QtCore/qthread.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qwaitcondition.h>
+#include <QtCore/qvector.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/qset.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlPreviewServiceImpl;
+class QQmlPreviewFileLoader : public QObject
+{
+ Q_OBJECT
+public:
+ enum Result {
+ File,
+ Directory,
+ Fallback,
+ Unknown
+ };
+
+ QQmlPreviewFileLoader(QQmlPreviewServiceImpl *service);
+ ~QQmlPreviewFileLoader();
+
+ Result load(const QString &file);
+ QByteArray contents();
+ QStringList entries();
+
+ void whitelist(const QUrl &url);
+ bool isBlacklisted(const QString &file);
+
+signals:
+ void request(const QString &file);
+
+private:
+ QMutex m_mutex;
+ QWaitCondition m_waitCondition;
+
+ QThread m_thread;
+ QPointer<QQmlPreviewServiceImpl> m_service;
+
+ QString m_path;
+ QByteArray m_contents;
+ QStringList m_entries;
+ Result m_result;
+
+ QQmlPreviewBlacklist m_blacklist;
+ QHash<QString, QByteArray> m_fileCache;
+ QHash<QString, QStringList> m_directoryCache;
+
+ void file(const QString &file, const QByteArray &contents);
+ void directory(const QString &file, const QStringList &entries);
+ void error(const QString &file);
+ void clearCache();
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLPREVIEWFILELOADER_H
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.cpp
new file mode 100644
index 0000000000..fdfa9dcc34
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.cpp
@@ -0,0 +1,313 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlpreviewhandler.h"
+
+#include <QtCore/qtimer.h>
+#include <QtCore/qsettings.h>
+#include <QtGui/qwindow.h>
+#include <QtGui/qguiapplication.h>
+#include <QtQuick/qquickwindow.h>
+#include <QtQuick/qquickitem.h>
+#include <QtQml/qqmlcomponent.h>
+
+#include <private/qquickpixmapcache_p.h>
+#include <private/qquickview_p.h>
+#include <private/qhighdpiscaling_p.h>
+
+QT_BEGIN_NAMESPACE
+
+struct QuitLockDisabler
+{
+ const bool quitLockEnabled;
+
+ QuitLockDisabler() : quitLockEnabled(QCoreApplication::isQuitLockEnabled())
+ {
+ QCoreApplication::setQuitLockEnabled(false);
+ }
+
+ ~QuitLockDisabler()
+ {
+ QCoreApplication::setQuitLockEnabled(quitLockEnabled);
+ }
+};
+
+QQmlPreviewHandler::QQmlPreviewHandler(QObject *parent) : QObject(parent)
+{
+ m_dummyItem.reset(new QQuickItem);
+
+ // TODO: Is there a better way to determine this? We want to keep the window alive when possible
+ // as otherwise it will reappear in a different place when (re)loading a file. However,
+ // the file we load might create another window, in which case the eglfs plugin (and
+ // others?) will do a qFatal as it only supports a single window.
+ const QString platformName = QGuiApplication::platformName();
+ m_supportsMultipleWindows = (platformName == QStringLiteral("windows")
+ || platformName == QStringLiteral("cocoa")
+ || platformName == QStringLiteral("xcb")
+ || platformName == QStringLiteral("wayland"));
+
+ QCoreApplication::instance()->installEventFilter(this);
+}
+
+QQmlPreviewHandler::~QQmlPreviewHandler()
+{
+ clear();
+}
+
+static void closeAllWindows()
+{
+ const QWindowList windows = QGuiApplication::allWindows();
+ for (QWindow *window : windows)
+ window->close();
+}
+
+bool QQmlPreviewHandler::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::Show) {
+ if (QWindow *window = qobject_cast<QQuickWindow*>(obj)) {
+ m_lastPosition.initLastSavedWindowPosition(window);
+ }
+ }
+ if ((event->type() == QEvent::Move || event->type() == QEvent::Resize) &&
+ qobject_cast<QQuickWindow*>(obj) == m_currentWindow) {
+ // we always start with factor 1 so calculate and save the origin as it would be not scaled
+ m_lastPosition.setPosition(m_currentWindow->framePosition() *
+ QHighDpiScaling::factor(m_currentWindow));
+ }
+
+ return QObject::eventFilter(obj, event);
+}
+
+void QQmlPreviewHandler::addEngine(QQmlEngine *qmlEngine)
+{
+ m_engines.append(qmlEngine);
+}
+
+void QQmlPreviewHandler::removeEngine(QQmlEngine *qmlEngine)
+{
+ const bool found = m_engines.removeOne(qmlEngine);
+ Q_ASSERT(found);
+ for (QObject *obj : m_createdObjects)
+ if (obj && QtQml::qmlEngine(obj) == qmlEngine)
+ delete obj;
+ m_createdObjects.removeAll(nullptr);
+}
+
+void QQmlPreviewHandler::loadUrl(const QUrl &url)
+{
+ QSharedPointer<QuitLockDisabler> disabler(new QuitLockDisabler);
+
+ clear();
+ m_component.reset(nullptr);
+ QQuickPixmap::purgeCache();
+
+ const int numEngines = m_engines.count();
+ 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 = m_engines.front();
+ 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 QQmlPreviewHandler::rerun()
+{
+ if (m_component.isNull() || !m_component->isReady())
+ emit error(QLatin1String("Component is not ready."));
+
+ QuitLockDisabler disabler;
+ Q_UNUSED(disabler);
+ clear();
+ tryCreateObject();
+}
+
+void QQmlPreviewHandler::zoom(qreal newFactor)
+{
+ if (!m_currentWindow)
+ return;
+ if (qFuzzyIsNull(newFactor)) {
+ emit error(QString::fromLatin1("Zooming with factor: %1 will result in nothing " \
+ "so it will be ignored.").arg(newFactor));
+ return;
+ }
+ QString errorMessage;
+ bool resetZoom = false;
+
+ if (newFactor < 0) {
+ resetZoom = true;
+ newFactor = 1.0;
+ }
+
+ // On single-window devices we allow any scale factor as the window will adapt to the screen.
+ if (m_supportsMultipleWindows) {
+ const QSize newAvailableScreenSize = QQmlPreviewPosition::currentScreenSize(m_currentWindow)
+ * QHighDpiScaling::factor(m_currentWindow) / newFactor;
+ if (m_currentWindow->size().width() > newAvailableScreenSize.width()) {
+ errorMessage = QString::fromLatin1(
+ "Zooming with factor: "
+ "%1 will result in a too wide preview.").arg(newFactor);
+ }
+ if (m_currentWindow->size().height() > newAvailableScreenSize.height()) {
+ errorMessage = QString::fromLatin1(
+ "Zooming with factor: "
+ "%1 will result in a too heigh preview.").arg(newFactor);
+ }
+ }
+
+ if (errorMessage.isEmpty()) {
+ const QPoint newToOriginMappedPosition = m_currentWindow->position() *
+ QHighDpiScaling::factor(m_currentWindow) / newFactor;
+ m_currentWindow->destroy();
+ QHighDpiScaling::setScreenFactor(m_currentWindow->screen(), newFactor);
+ if (resetZoom)
+ QHighDpiScaling::updateHighDpiScaling();
+ m_currentWindow->setPosition(newToOriginMappedPosition);
+ m_currentWindow->show();
+ } else {
+ emit error(errorMessage);
+ }
+}
+
+void QQmlPreviewHandler::clear()
+{
+ qDeleteAll(m_createdObjects);
+ m_createdObjects.clear();
+ m_currentWindow = nullptr;
+}
+
+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;
+ }
+}
+
+void QQmlPreviewHandler::showObject(QObject *object)
+{
+ if (QWindow *window = qobject_cast<QWindow *>(object)) {
+ m_currentWindow = qobject_cast<QQuickWindow *>(window);
+ for (QWindow *otherWindow : QGuiApplication::allWindows()) {
+ if (QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(otherWindow)) {
+ if (quickWindow == m_currentWindow)
+ continue;
+ quickWindow->setVisible(false);
+ quickWindow->setFlags(quickWindow->flags() & ~Qt::WindowStaysOnTopHint);
+ }
+ }
+ } else if (QQuickItem *item = qobject_cast<QQuickItem *>(object)) {
+ m_currentWindow = nullptr;
+ for (QWindow *window : QGuiApplication::allWindows()) {
+ if (QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(window)) {
+ if (m_currentWindow != nullptr) {
+ emit error(QLatin1String("Multiple QQuickWindows available. We cannot "
+ "decide which one to use."));
+ return;
+ }
+ m_currentWindow = quickWindow;
+ } else {
+ window->setVisible(false);
+ window->setFlag(Qt::WindowStaysOnTopHint, false);
+ }
+ }
+
+ if (m_currentWindow == nullptr) {
+ m_currentWindow = new QQuickWindow;
+ m_createdObjects.append(m_currentWindow.data());
+ }
+
+ for (QQuickItem *oldItem : m_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 *>(m_currentWindow))
+ QQuickViewPrivate::get(view)->setRootObject(item);
+ else
+ item->setParentItem(m_currentWindow->contentItem());
+
+ m_currentWindow->resize(item->size().toSize());
+ } else {
+ emit error(QLatin1String("Created object is neither a QWindow nor a QQuickItem."));
+ }
+
+ if (m_currentWindow) {
+ m_lastPosition.initLastSavedWindowPosition(m_currentWindow);
+ m_currentWindow->setFlags(fixFlags(m_currentWindow->flags()) | Qt::WindowStaysOnTopHint);
+ m_currentWindow->setVisible(true);
+ }
+}
+
+void QQmlPreviewHandler::tryCreateObject()
+{
+ if (!m_supportsMultipleWindows)
+ closeAllWindows();
+ QObject *object = m_component->create();
+ m_createdObjects.append(object);
+ showObject(object);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.h
new file mode 100644
index 0000000000..78099fb1e5
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLPREVIEWHANDLER_H
+#define QQMLPREVIEWHANDLER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmlpreviewposition.h"
+
+#include <QtCore/qobject.h>
+#include <QtCore/qvector.h>
+#include <QtCore/qrect.h>
+#include <QtCore/qpointer.h>
+#include <QtQml/qqmlengine.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlEngine;
+class QQuickItem;
+class QQmlPreviewUrlInterceptor;
+class QQuickWindow;
+class QQmlPreviewHandler : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QQmlPreviewHandler(QObject *parent = nullptr);
+ ~QQmlPreviewHandler();
+
+ void addEngine(QQmlEngine *engine);
+ void removeEngine(QQmlEngine *engine);
+
+ void loadUrl(const QUrl &url);
+ void rerun();
+ void zoom(qreal newFactor);
+
+ void clear();
+
+signals:
+ void error(const QString &message);
+protected:
+ bool eventFilter(QObject *obj, QEvent *event);
+private:
+ void tryCreateObject();
+ void showObject(QObject *object);
+
+ QScopedPointer<QQuickItem> m_dummyItem;
+ QList<QQmlEngine *> m_engines;
+ QVector<QPointer<QObject>> m_createdObjects;
+ QScopedPointer<QQmlComponent> m_component;
+ QPointer<QQuickWindow> m_currentWindow;
+ bool m_supportsMultipleWindows;
+ QQmlPreviewPosition m_lastPosition;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLPREVIEWHANDLER_H
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp
new file mode 100644
index 0000000000..2382f72fe3
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlpreviewposition.h"
+
+#include <QtGui/qwindow.h>
+#include <QtGui/qscreen.h>
+#include <QtGui/qguiapplication.h>
+
+QT_BEGIN_NAMESPACE
+
+static const QSize availableScreenSize(const QPoint &point)
+{
+ if (const QScreen *screen = QGuiApplication::screenAt(point))
+ return screen->availableGeometry().size();
+ return QSize();
+}
+
+QQmlPreviewPosition::QQmlPreviewPosition()
+ : m_settings("QtProject", "QtQmlPreview")
+{
+ m_savePositionTimer.setSingleShot(true);
+ m_savePositionTimer.setInterval(500);
+ QObject::connect(&m_savePositionTimer, &QTimer::timeout, [this]() {
+ saveWindowPosition();
+ });
+}
+
+void QQmlPreviewPosition::setPosition(const QPoint &point)
+{
+ m_hasPosition = true;
+ m_lastWindowPosition = point;
+ m_savePositionTimer.start();
+}
+
+void QQmlPreviewPosition::saveWindowPosition()
+{
+ if (m_hasPosition) {
+ if (!m_settingsKey.isNull())
+ m_settings.setValue(m_settingsKey, m_lastWindowPosition);
+
+ m_settings.setValue(QLatin1String("global_lastpostion"), m_lastWindowPosition);
+ }
+}
+
+void QQmlPreviewPosition::loadWindowPositionSettings(const QUrl &url)
+{
+ m_settingsKey = url.toString(QUrl::PreferLocalFile) + QLatin1String("_lastpostion");
+
+ if (m_settings.contains(m_settingsKey)) {
+ m_hasPosition = true;
+ m_lastWindowPosition = m_settings.value(m_settingsKey).toPoint();
+ }
+}
+
+void QQmlPreviewPosition::initLastSavedWindowPosition(QWindow *window)
+{
+ if (m_positionedWindows.contains(window))
+ return;
+ if (!m_hasPosition) {
+ // in case there was nothing saved, we do not want to set anything
+ if (!m_settings.contains(QLatin1String("global_lastpostion")))
+ return;
+ m_lastWindowPosition = m_settings.value(QLatin1String("global_lastpostion")).toPoint();
+ }
+ if (QGuiApplication::screenAt(m_lastWindowPosition))
+ window->setFramePosition(m_lastWindowPosition);
+
+ m_positionedWindows.append(window);
+}
+
+const QSize QQmlPreviewPosition::currentScreenSize(QWindow *window)
+{
+ return availableScreenSize(window->position());
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.h
new file mode 100644
index 0000000000..8683757007
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLPREVIEWPOSITION_H
+#define QQMLPREVIEWPOSITION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qvector.h>
+#include <QtCore/qpoint.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qsettings.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWindow;
+
+class QQmlPreviewPosition
+{
+public:
+ QQmlPreviewPosition();
+
+ void setPosition(const QPoint &point);
+ void saveWindowPosition();
+ void loadWindowPositionSettings(const QUrl &url);
+ void initLastSavedWindowPosition(QWindow *window);
+ static const QSize currentScreenSize(QWindow *window);
+
+private:
+ bool m_hasPosition = false;
+ QPoint m_lastWindowPosition;
+ QSettings m_settings;
+ QString m_settingsKey;
+ QTimer m_savePositionTimer;
+ QVector<QWindow *> m_positionedWindows;
+};
+
+
+QT_END_NAMESPACE
+
+#endif // QQMLPREVIEWPOSITION_H
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp
new file mode 100644
index 0000000000..4e96fc62ef
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp
@@ -0,0 +1,150 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlpreviewservice.h"
+
+#include <QtCore/qpointer.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQuick/qquickwindow.h>
+#include <QtQuick/qquickitem.h>
+#include <QtGui/qguiapplication.h>
+
+#include <private/qquickpixmapcache_p.h>
+#include <private/qqmldebugconnector_p.h>
+#include <private/qversionedpacket_p.h>
+
+QT_BEGIN_NAMESPACE
+
+const QString QQmlPreviewServiceImpl::s_key = QStringLiteral("QmlPreview");
+using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
+
+QQmlPreviewServiceImpl::QQmlPreviewServiceImpl(QObject *parent) :
+ QQmlDebugService(s_key, 1.0f, parent)
+{
+ m_loader.reset(new QQmlPreviewFileLoader(this));
+ connect(this, &QQmlPreviewServiceImpl::load,
+ m_loader.data(), &QQmlPreviewFileLoader::whitelist, Qt::DirectConnection);
+ connect(this, &QQmlPreviewServiceImpl::load, &m_handler, &QQmlPreviewHandler::loadUrl);
+ connect(this, &QQmlPreviewServiceImpl::rerun, &m_handler, &QQmlPreviewHandler::rerun);
+ connect(this, &QQmlPreviewServiceImpl::zoom, &m_handler, &QQmlPreviewHandler::zoom);
+ connect(&m_handler, &QQmlPreviewHandler::error, this, &QQmlPreviewServiceImpl::forwardError,
+ Qt::DirectConnection);
+
+}
+
+QQmlPreviewServiceImpl::~QQmlPreviewServiceImpl()
+{
+}
+
+void QQmlPreviewServiceImpl::messageReceived(const QByteArray &data)
+{
+ QQmlDebugPacket packet(data);
+ qint8 command;
+
+ packet >> command;
+ switch (command) {
+ case File: {
+ QString path;
+ QByteArray contents;
+ packet >> path >> contents;
+ emit file(path, contents);
+ break;
+ }
+ case Directory: {
+ QString path;
+ QStringList entries;
+ packet >> path >> entries;
+ emit directory(path, entries);
+ break;
+ }
+ case Load: {
+ QUrl url;
+ packet >> url;
+ emit load(url);
+ break;
+ }
+ case Error: {
+ QString file;
+ packet >> file;
+ emit error(file);
+ break;
+ }
+ case Rerun:
+ emit rerun();
+ break;
+ case ClearCache:
+ emit clearCache();
+ break;
+ case Zoom: {
+ float factor;
+ packet >> factor;
+ emit zoom(static_cast<qreal>(factor));
+ break;
+ }
+ default:
+ forwardError(QString::fromLatin1("Invalid command: %1").arg(command));
+ break;
+ }
+}
+
+void QQmlPreviewServiceImpl::engineAboutToBeAdded(QJSEngine *engine)
+{
+ if (QQmlEngine *qmlEngine = qobject_cast<QQmlEngine *>(engine))
+ m_handler.addEngine(qmlEngine);
+ emit attachedToEngine(engine);
+}
+
+void QQmlPreviewServiceImpl::engineAboutToBeRemoved(QJSEngine *engine)
+{
+ if (QQmlEngine *qmlEngine = qobject_cast<QQmlEngine *>(engine))
+ m_handler.removeEngine(qmlEngine);
+ emit detachedFromEngine(engine);
+}
+
+void QQmlPreviewServiceImpl::stateChanged(QQmlDebugService::State state)
+{
+ m_fileEngine.reset(state == Enabled ? new QQmlPreviewFileEngineHandler(m_loader.data())
+ : nullptr);
+}
+
+void QQmlPreviewServiceImpl::forwardRequest(const QString &file)
+{
+ QQmlDebugPacket packet;
+ packet << static_cast<qint8>(Request) << file;
+ emit messageToClient(name(), packet.data());
+}
+
+void QQmlPreviewServiceImpl::forwardError(const QString &error)
+{
+ QQmlDebugPacket packet;
+ packet << static_cast<qint8>(Error) << error;
+ emit messageToClient(name(), packet.data());
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h
new file mode 100644
index 0000000000..084d3a14b1
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of th QML Preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLPREVIEWSERVICE_H
+#define QQMLPREVIEWSERVICE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qqmlpreviewhandler.h"
+#include "qqmlpreviewfileengine.h"
+#include <private/qqmldebugserviceinterfaces_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlPreviewFileEngineHandler;
+class QQmlPreviewHandler;
+class QQmlPreviewServiceImpl : public QQmlDebugService
+{
+ Q_OBJECT
+
+public:
+ enum Command {
+ File,
+ Load,
+ Request,
+ Error,
+ Rerun,
+ Directory,
+ ClearCache,
+ Zoom
+ };
+
+ static const QString s_key;
+
+ QQmlPreviewServiceImpl(QObject *parent = nullptr);
+ virtual ~QQmlPreviewServiceImpl();
+
+ void messageReceived(const QByteArray &message) override;
+ void engineAboutToBeAdded(QJSEngine *engine) override;
+ void engineAboutToBeRemoved(QJSEngine *engine) override;
+ void stateChanged(State state) override;
+
+ void forwardRequest(const QString &file);
+ void forwardError(const QString &error);
+
+signals:
+ void error(const QString &file);
+ void file(const QString &file, const QByteArray &contents);
+ void directory(const QString &file, const QStringList &entries);
+ void load(const QUrl &url);
+ void rerun();
+ void clearCache();
+ void zoom(qreal factor);
+
+private:
+ QScopedPointer<QQmlPreviewFileEngineHandler> m_fileEngine;
+ QScopedPointer<QQmlPreviewFileLoader> m_loader;
+ QQmlPreviewHandler m_handler;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLPREVIEWSERVICE_H
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.json b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.json
new file mode 100644
index 0000000000..d7e1ef1f10
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.json
@@ -0,0 +1,3 @@
+{
+ "Keys" : [ "QmlPreview" ]
+}
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.cpp
new file mode 100644
index 0000000000..4e09bd1002
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.cpp
@@ -0,0 +1,39 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmlpreviewservicefactory.h"
+#include "qqmlpreviewservice.h"
+
+QT_BEGIN_NAMESPACE
+
+QQmlDebugService *QQmlPreviewServiceFactory::create(const QString &key)
+{
+ return key == QQmlPreviewServiceImpl::s_key ? new QQmlPreviewServiceImpl(this) : nullptr;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.h
new file mode 100644
index 0000000000..f51e696fe6
--- /dev/null
+++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.h
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QML preview debug service.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLPREVIEWSERVCIEFACTORY_H
+#define QQMLPREVIEWSERVCIEFACTORY_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qqmldebugservicefactory_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlPreviewServiceFactory : public QQmlDebugServiceFactory
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID QQmlDebugServiceFactory_iid FILE "qqmlpreviewservice.json")
+
+public:
+ QQmlDebugService *create(const QString &key) override;
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLPREVIEWSERVCIEFACTORY_H
diff --git a/src/plugins/qmltooling/qmltooling.pro b/src/plugins/qmltooling/qmltooling.pro
index 119415372b..fd4a3eac65 100644
--- a/src/plugins/qmltooling/qmltooling.pro
+++ b/src/plugins/qmltooling/qmltooling.pro
@@ -35,7 +35,10 @@ qmldbg_nativedebugger.depends = packetprotocol
qtHaveModule(quick) {
SUBDIRS += \
qmldbg_inspector \
- qmldbg_quickprofiler
+ qmldbg_quickprofiler \
+ qmldbg_preview
+
qmldbg_inspector.depends = packetprotocol
qmldbg_quickprofiler.depends = packetprotocol
+ qmldbg_preview.depends = packetprotocol
}
diff --git a/src/qmldebug/qmldebug.pro b/src/qmldebug/qmldebug.pro
index 2c1b71797d..0807482d23 100644
--- a/src/qmldebug/qmldebug.pro
+++ b/src/qmldebug/qmldebug.pro
@@ -9,6 +9,7 @@ SOURCES += \
qqmldebugconnection.cpp \
qqmldebugmessageclient.cpp \
qqmlenginecontrolclient.cpp \
+ qqmlpreviewclient.cpp \
qqmlprofilerclient.cpp \
qqmlprofilerevent.cpp \
qqmlprofilereventlocation.cpp \
@@ -22,6 +23,8 @@ HEADERS += \
qqmldebugmessageclient_p.h \
qqmlenginecontrolclient_p.h \
qqmlenginecontrolclient_p_p.h \
+ qqmlpreviewclient_p.h \
+ qqmlpreviewclient_p_p.h \
qqmlprofilerclient_p.h \
qqmlprofilerclient_p_p.h \
qqmlprofilerevent_p.h \
diff --git a/src/qmldebug/qqmlpreviewclient.cpp b/src/qmldebug/qqmlpreviewclient.cpp
new file mode 100644
index 0000000000..fa040e226b
--- /dev/null
+++ b/src/qmldebug/qqmlpreviewclient.cpp
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include "qqmlpreviewclient_p_p.h"
+#include <private/qpacket_p.h>
+
+#include <QtCore/qurl.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qdir.h>
+#include <QtQml/qqmlfile.h>
+
+QT_BEGIN_NAMESPACE
+
+QQmlPreviewClient::QQmlPreviewClient(QQmlDebugConnection *connection)
+ : QQmlDebugClient(*(new QQmlPreviewClientPrivate(connection)))
+{
+}
+
+void QQmlPreviewClient::messageReceived(const QByteArray &message)
+{
+ QPacket packet(connection()->currentDataStreamVersion(), message);
+
+ qint8 command;
+ packet >> command;
+
+ if (command == Error) {
+ QString seviceError;
+ packet >> seviceError;
+ emit error(seviceError);
+ return;
+ }
+
+ Q_ASSERT(command == Request);
+
+ QString fileName;
+ packet >> fileName;
+ emit request(fileName);
+}
+
+void QQmlPreviewClient::sendDirectory(const QString &path, const QStringList &entries)
+{
+ QPacket packet(connection()->currentDataStreamVersion());
+ packet << static_cast<qint8>(Directory) << path << entries;
+ sendMessage(packet.data());
+}
+
+void QQmlPreviewClient::sendFile(const QString &path, const QByteArray &contents)
+{
+ QPacket packet(connection()->currentDataStreamVersion());
+ packet << static_cast<qint8>(File) << path << contents;
+ sendMessage(packet.data());
+}
+
+void QQmlPreviewClient::sendError(const QString &path)
+{
+ QPacket packet(connection()->currentDataStreamVersion());
+ packet << static_cast<qint8>(Error) << path;
+ sendMessage(packet.data());
+}
+
+void QQmlPreviewClient::triggerLoad(const QUrl &url)
+{
+ QPacket packet(connection()->currentDataStreamVersion());
+ packet << static_cast<qint8>(Load) << url;
+ sendMessage(packet.data());
+}
+
+void QQmlPreviewClient::triggerRerun()
+{
+ QPacket packet(connection()->currentDataStreamVersion());
+ packet << static_cast<qint8>(Rerun);
+ sendMessage(packet.data());
+}
+
+void QQmlPreviewClient::triggerZoom(float factor)
+{
+ QPacket packet(connection()->currentDataStreamVersion());
+ packet << static_cast<qint8>(Zoom) << factor;
+ sendMessage(packet.data());
+}
+
+QT_END_NAMESPACE
diff --git a/src/qmldebug/qqmlpreviewclient_p.h b/src/qmldebug/qqmlpreviewclient_p.h
new file mode 100644
index 0000000000..49d147c866
--- /dev/null
+++ b/src/qmldebug/qqmlpreviewclient_p.h
@@ -0,0 +1,95 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef QQMLPREVIEWCLIENT_P_H
+#define QQMLPREVIEWCLIENT_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qqmldebugclient_p.h>
+#include <private/qqmldebugconnection_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QQmlPreviewClientPrivate;
+class QQmlPreviewClient : public QQmlDebugClient
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(QQmlPreviewClient)
+public:
+ enum Command {
+ File,
+ Load,
+ Request,
+ Error,
+ Rerun,
+ Directory,
+ ClearCache,
+ Zoom
+ };
+
+ QQmlPreviewClient(QQmlDebugConnection *parent);
+ void messageReceived(const QByteArray &message) override;
+
+ void sendDirectory(const QString &path, const QStringList &entries);
+ void sendFile(const QString &path, const QByteArray &contents);
+ void sendError(const QString &path);
+
+ void triggerLoad(const QUrl &url);
+ void triggerRerun();
+ void triggerZoom(float factor);
+
+signals:
+ void request(const QString &path);
+ void error(const QString &message);
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLPREVIEWCLIENT_P_H
diff --git a/src/qmldebug/qqmlpreviewclient_p_p.h b/src/qmldebug/qqmlpreviewclient_p_p.h
new file mode 100644
index 0000000000..8fb2b4b18c
--- /dev/null
+++ b/src/qmldebug/qqmlpreviewclient_p_p.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLPREVIEWCLIENT_P_P_H
+#define QQMLPREVIEWCLIENT_P_P_H
+
+#include "qqmlpreviewclient_p.h"
+#include "qqmldebugclient_p_p.h"
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class QQmlPreviewClientPrivate : public QQmlDebugClientPrivate
+{
+ Q_DECLARE_PUBLIC(QQmlPreviewClient)
+public:
+ QQmlPreviewClientPrivate(QQmlDebugConnection *connection)
+ : QQmlDebugClientPrivate(QLatin1String("QmlPreview"), connection)
+ {}
+};
+
+QT_END_NAMESPACE
+
+#endif // QQMLPREVIEWCLIENT_P_P_H
diff --git a/tests/auto/qml/debugger/debugger.pro b/tests/auto/qml/debugger/debugger.pro
index 9ee778bc2c..5c328fbfcc 100644
--- a/tests/auto/qml/debugger/debugger.pro
+++ b/tests/auto/qml/debugger/debugger.pro
@@ -13,7 +13,8 @@ PUBLICTESTS += \
qqmlenginecontrol \
qqmldebuggingenabler \
qqmlnativeconnector \
- qqmldebugprocess
+ qqmldebugprocess \
+ qqmlpreview
PRIVATETESTS += \
qqmldebugclient \
diff --git a/tests/auto/qml/debugger/qqmlpreview/data/broken.qml b/tests/auto/qml/debugger/qqmlpreview/data/broken.qml
new file mode 100644
index 0000000000..4ebbf7576a
--- /dev/null
+++ b/tests/auto/qml/debugger/qqmlpreview/data/broken.qml
@@ -0,0 +1,31 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+Item {
diff --git a/tests/auto/qml/debugger/qqmlpreview/data/qtquick2.qml b/tests/auto/qml/debugger/qqmlpreview/data/qtquick2.qml
new file mode 100644
index 0000000000..b525ab4fa0
--- /dev/null
+++ b/tests/auto/qml/debugger/qqmlpreview/data/qtquick2.qml
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+Rectangle {
+ width: 100
+ height: 100
+ color: "blue"
+
+ RotationAnimation on rotation {
+ duration: 3600
+ loops: Animation.Infinite
+ from: 0
+ to: 360
+ }
+
+ Timer {
+ interval: 300
+ repeat: true
+ running: true
+ property int prevHit: -1
+ property int prevRotation: -1
+ onTriggered: {
+ var date = new Date;
+ var millis = date.getMilliseconds()
+
+ if (prevHit < 0) {
+ prevHit = millis;
+ prevRotation = parent.rotation
+ return;
+ }
+
+ var milliDelta = millis - prevHit;
+ if (milliDelta <= 0)
+ milliDelta += 1000;
+ prevHit = millis;
+
+ var delta = parent.rotation - prevRotation;
+ if (delta < 0)
+ delta += 360
+ prevRotation = parent.rotation
+ console.log(milliDelta, delta, "ms/degrees ");
+ }
+ }
+}
diff --git a/tests/auto/qml/debugger/qqmlpreview/data/window.qml b/tests/auto/qml/debugger/qqmlpreview/data/window.qml
new file mode 100644
index 0000000000..f9f8d5aeb1
--- /dev/null
+++ b/tests/auto/qml/debugger/qqmlpreview/data/window.qml
@@ -0,0 +1,44 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Window 2.0
+
+Window {
+ visible: true
+
+ height: 100
+ width: 100
+
+ Timer {
+ repeat: false
+ interval: 1000
+ running: true
+ onTriggered: console.log("window.qml");
+ }
+}
diff --git a/tests/auto/qml/debugger/qqmlpreview/data/window1.qml b/tests/auto/qml/debugger/qqmlpreview/data/window1.qml
new file mode 100644
index 0000000000..e8e9ad706d
--- /dev/null
+++ b/tests/auto/qml/debugger/qqmlpreview/data/window1.qml
@@ -0,0 +1,43 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Window 2.3
+
+Window {
+ visible: true
+
+ height: 200
+ width: 100
+
+ Timer {
+ interval: 1000
+ running: true
+ onTriggered: console.log("window1.qml");
+ }
+}
diff --git a/tests/auto/qml/debugger/qqmlpreview/data/window2.qml b/tests/auto/qml/debugger/qqmlpreview/data/window2.qml
new file mode 100644
index 0000000000..9ad42d2ee2
--- /dev/null
+++ b/tests/auto/qml/debugger/qqmlpreview/data/window2.qml
@@ -0,0 +1,43 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Window 2.3
+
+Window {
+ visible: true
+
+ height: 100
+ width: 200
+
+ Timer {
+ interval: 1000
+ running: true
+ onTriggered: console.log("window2.qml");
+ }
+}
diff --git a/tests/auto/qml/debugger/qqmlpreview/data/zoom.qml b/tests/auto/qml/debugger/qqmlpreview/data/zoom.qml
new file mode 100644
index 0000000000..0aca235de1
--- /dev/null
+++ b/tests/auto/qml/debugger/qqmlpreview/data/zoom.qml
@@ -0,0 +1,51 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Window 2.2
+
+Window {
+ id: w
+ height: 100
+ width: 100
+ visible: true
+
+ Rectangle {
+ width: 50
+ height: 50
+ color: "blue"
+ anchors.centerIn: parent
+ }
+
+ Timer {
+ interval: 100
+ running: true
+ repeat: true
+ onTriggered: console.log("zoom", w.screen.devicePixelRatio)
+ }
+}
diff --git a/tests/auto/qml/debugger/qqmlpreview/qqmlpreview.pro b/tests/auto/qml/debugger/qqmlpreview/qqmlpreview.pro
new file mode 100644
index 0000000000..4a0d385e1a
--- /dev/null
+++ b/tests/auto/qml/debugger/qqmlpreview/qqmlpreview.pro
@@ -0,0 +1,24 @@
+CONFIG += testcase
+TARGET = tst_qqmlpreview
+
+QT += qml testlib core qmldebug-private
+macos:CONFIG -= app_bundle
+
+INCLUDEPATH += ../../../../../src/plugins/qmltooling/qmldbg_preview/
+
+SOURCES += \
+ tst_qqmlpreview.cpp \
+ ../../../../../src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp
+
+HEADERS += \
+ ../../../../../src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h
+
+include(../shared/debugutil.pri)
+
+TESTDATA = \
+ data/window.qml \
+ data/qtquick2.qml \
+ data/window2.qml \
+ data/window1.qml \
+ data/broken.qml \
+ data/zoom.qml
diff --git a/tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp b/tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp
new file mode 100644
index 0000000000..ebe09fbd69
--- /dev/null
+++ b/tests/auto/qml/debugger/qqmlpreview/tst_qqmlpreview.cpp
@@ -0,0 +1,307 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmldebugprocess_p.h"
+#include "debugutil_p.h"
+#include "qqmlpreviewblacklist.h"
+
+#include <QtTest/qtest.h>
+#include <QtTest/qsignalspy.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qthread.h>
+#include <QtCore/qlibraryinfo.h>
+#include <QtNetwork/qhostaddress.h>
+
+#include <private/qqmldebugconnection_p.h>
+#include <private/qqmlpreviewclient_p.h>
+
+class tst_QQmlPreview : public QQmlDebugTest
+{
+ Q_OBJECT
+
+private:
+ ConnectResult startQmlProcess(const QString &qmlFile);
+ void serveRequest(const QString &path);
+ QList<QQmlDebugClient *> createClients() override;
+
+ QPointer<QQmlPreviewClient> m_client;
+
+ QStringList m_files;
+ QStringList m_filesNotFound;
+ QStringList m_directories;
+ QStringList m_serviceErrors;
+
+private slots:
+ void cleanup() final;
+
+ void connect();
+ void load();
+ void rerun();
+ void blacklist();
+ void error();
+ void zoom();
+};
+
+QQmlDebugTest::ConnectResult tst_QQmlPreview::startQmlProcess(const QString &qmlFile)
+{
+ return QQmlDebugTest::connect(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qml",
+ QStringLiteral("QmlPreview"), testFile(qmlFile), true);
+}
+
+void tst_QQmlPreview::serveRequest(const QString &path)
+{
+ QFileInfo info(path);
+
+ if (info.isDir()) {
+ m_directories.append(path);
+ m_client->sendDirectory(path, QDir(path).entryList());
+ } else {
+ QFile file(path);
+ if (file.open(QIODevice::ReadOnly)) {
+ m_files.append(path);
+ m_client->sendFile(path, file.readAll());
+ } else {
+ m_filesNotFound.append(path);
+ m_client->sendError(path);
+ }
+ }
+}
+
+QList<QQmlDebugClient *> tst_QQmlPreview::createClients()
+{
+ m_client = new QQmlPreviewClient(m_connection);
+
+ QObject::connect(m_client, &QQmlPreviewClient::request, this, &tst_QQmlPreview::serveRequest);
+ QObject::connect(m_client, &QQmlPreviewClient::error, this, [this](const QString &error) {
+ m_serviceErrors.append(error);
+ });
+
+ return QList<QQmlDebugClient *>({m_client});
+}
+
+void tst_QQmlPreview::cleanup()
+{
+ QQmlDebugTest::cleanup();
+ if (QTest::currentTestFailed()) {
+ qDebug() << "Files loaded:" << m_files;
+ qDebug() << "Files not loaded:" << m_filesNotFound;
+ qDebug() << "Directories loaded:" << m_directories;
+ qDebug() << "Errors reported:" << m_serviceErrors;
+ }
+
+ m_directories.clear();
+ m_files.clear();
+ m_filesNotFound.clear();
+ m_serviceErrors.clear();
+}
+
+void tst_QQmlPreview::connect()
+{
+ const QString file("window.qml");
+ QCOMPARE(startQmlProcess(file), ConnectSuccess);
+ QVERIFY(m_client);
+ QTRY_COMPARE(m_client->state(), QQmlDebugClient::Enabled);
+ m_client->triggerLoad(testFileUrl(file));
+ QTRY_VERIFY(m_files.contains(testFile(file)));
+ QTRY_VERIFY_WITH_TIMEOUT(m_process->output().contains("qml: window.qml"), 10000);
+ m_process->stop();
+ QTRY_COMPARE(m_client->state(), QQmlDebugClient::NotConnected);
+ QVERIFY(m_serviceErrors.isEmpty());
+}
+
+void tst_QQmlPreview::load()
+{
+ const QString file("qtquick2.qml");
+ QCOMPARE(startQmlProcess(file), ConnectSuccess);
+ QVERIFY(m_client);
+ QTRY_COMPARE(m_client->state(), QQmlDebugClient::Enabled);
+ m_client->triggerLoad(testFileUrl(file));
+ QTRY_VERIFY(m_files.contains(testFile(file)));
+ QTRY_VERIFY(m_process->output().contains("ms/degrees"));
+
+ const QStringList files({"window2.qml", "window1.qml", "window.qml"});
+ for (const QString &newFile : files) {
+ m_client->triggerLoad(testFileUrl(newFile));
+ QTRY_VERIFY(m_files.contains(testFile(newFile)));
+ QTRY_VERIFY(m_process->output().contains(QString::fromLatin1("qml: %1").arg(newFile)));
+ }
+
+ m_process->stop();
+ QTRY_COMPARE(m_client->state(), QQmlDebugClient::NotConnected);
+ QVERIFY(m_serviceErrors.isEmpty());
+}
+
+void tst_QQmlPreview::rerun()
+{
+ const QString file("window.qml");
+ QCOMPARE(startQmlProcess(file), ConnectSuccess);
+ QVERIFY(m_client);
+ m_client->triggerLoad(testFileUrl(file));
+ const QLatin1String message("qml: window.qml");
+ QTRY_VERIFY(m_process->output().contains(message));
+ const int pos = m_process->output().lastIndexOf(message) + message.size();
+ QVERIFY(pos >= 0);
+
+ m_client->triggerRerun();
+ QTRY_VERIFY(m_process->output().indexOf(message, pos) >= pos);
+
+ m_process->stop();
+ QVERIFY(m_serviceErrors.isEmpty());
+}
+
+void tst_QQmlPreview::blacklist()
+{
+ QQmlPreviewBlacklist blacklist;
+
+ QStringList strings({
+ "lalala", "lulul", "trakdkd", "suppe", "zack"
+ });
+
+ for (const QString &string : strings)
+ QVERIFY(!blacklist.isBlacklisted(string));
+
+ for (const QString &string : strings)
+ blacklist.blacklist(string);
+
+ for (const QString &string : strings) {
+ QVERIFY(blacklist.isBlacklisted(string));
+ QVERIFY(!blacklist.isBlacklisted(string.left(string.size() / 2)));
+ QVERIFY(!blacklist.isBlacklisted(string + "45"));
+ QVERIFY(!blacklist.isBlacklisted(" " + string));
+ QVERIFY(blacklist.isBlacklisted(string + "/45"));
+ }
+
+ for (auto begin = strings.begin(), it = begin, end = strings.end(); it != end; ++it) {
+ std::rotate(begin, it, end);
+ QString path = "/" + strings.join('/');
+ blacklist.blacklist(path);
+ QVERIFY(blacklist.isBlacklisted(path));
+ QVERIFY(blacklist.isBlacklisted(path + "/file"));
+ QVERIFY(!blacklist.isBlacklisted(path + "more"));
+ path.chop(1);
+ QVERIFY(!blacklist.isBlacklisted(path));
+ std::reverse(begin, end);
+ }
+
+ blacklist.clear();
+ for (const QString &string : strings)
+ QVERIFY(!blacklist.isBlacklisted(string));
+
+ blacklist.blacklist(":/qt-project.org");
+ QVERIFY(blacklist.isBlacklisted(":/qt-project.org/QmlRuntime/conf/configuration.qml"));
+ QVERIFY(!blacklist.isBlacklisted(":/qt-project.orgQmlRuntime/conf/configuration.qml"));
+
+ QQmlPreviewBlacklist blacklist2;
+
+ blacklist2.blacklist(":/qt-project.org");
+ blacklist2.blacklist(":/QtQuick/Controls/Styles");
+ blacklist2.blacklist(":/ExtrasImports/QtQuick/Controls/Styles");
+ blacklist2.blacklist(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath));
+ blacklist2.blacklist("/home/ulf/.local/share/QtProject/Qml Runtime/configuration.qml");
+ blacklist2.blacklist("/usr/share");
+ blacklist2.blacklist("/usr/share/QtProject/Qml Runtime/configuration.qml");
+ QVERIFY(blacklist2.isBlacklisted(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)));
+ blacklist2.blacklist("/usr/local/share/QtProject/Qml Runtime/configuration.qml");
+ blacklist2.blacklist("qml");
+ blacklist2.blacklist(""); // This should not remove all other paths.
+
+ QVERIFY(blacklist2.isBlacklisted(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath) +
+ "/QtQuick/Window.2.0"));
+ QVERIFY(blacklist2.isBlacklisted(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)));
+ QVERIFY(blacklist2.isBlacklisted("/usr/share/QtProject/Qml Runtime/configuration.qml"));
+ QVERIFY(blacklist2.isBlacklisted("/usr/share/stuff"));
+ QVERIFY(blacklist2.isBlacklisted(""));
+
+ QQmlPreviewBlacklist blacklist3;
+ blacklist3.blacklist("/usr/share");
+ blacklist3.blacklist("/usr");
+ blacklist3.blacklist("/usrdings");
+ QVERIFY(blacklist3.isBlacklisted("/usrdings"));
+ QVERIFY(blacklist3.isBlacklisted("/usr/src"));
+ QVERIFY(!blacklist3.isBlacklisted("/opt/share"));
+ QVERIFY(!blacklist3.isBlacklisted("/opt"));
+
+ blacklist3.whitelist("/usr/share");
+ QVERIFY(blacklist3.isBlacklisted("/usrdings"));
+ QVERIFY(!blacklist3.isBlacklisted("/usr"));
+ QVERIFY(!blacklist3.isBlacklisted("/usr/share"));
+ QVERIFY(!blacklist3.isBlacklisted("/usr/src"));
+ QVERIFY(!blacklist3.isBlacklisted("/opt/share"));
+ QVERIFY(!blacklist3.isBlacklisted("/opt"));
+}
+
+void tst_QQmlPreview::error()
+{
+ QCOMPARE(startQmlProcess("window.qml"), ConnectSuccess);
+ QVERIFY(m_client);
+ m_client->triggerLoad(testFileUrl("broken.qml"));
+ QTRY_COMPARE_WITH_TIMEOUT(m_serviceErrors.count(), 1, 10000);
+ QVERIFY(m_serviceErrors.first().contains("broken.qml:32 Expected token `}'"));
+}
+
+static float parseZoomFactor(const QString &output)
+{
+ const QString prefix("zoom ");
+ const int start = output.lastIndexOf(prefix) + prefix.length();
+ if (start < 0)
+ return -1;
+ const int end = output.indexOf('\n', start);
+ if (end < 0)
+ return -1;
+ bool ok = false;
+ const float zoomFactor = output.mid(start, end - start).toFloat(&ok);
+ if (!ok)
+ return -1;
+ return zoomFactor;
+}
+
+void tst_QQmlPreview::zoom()
+{
+ const QString file("zoom.qml");
+ QCOMPARE(startQmlProcess(file), ConnectSuccess);
+ QVERIFY(m_client);
+ m_client->triggerLoad(testFileUrl(file));
+ QTRY_VERIFY(m_files.contains(testFile(file)));
+ float baseZoomFactor = -1;
+ QTRY_VERIFY((baseZoomFactor = parseZoomFactor(m_process->output())) > 0);
+ m_client->triggerZoom(2.0f);
+ QTRY_VERIFY(qFuzzyCompare(parseZoomFactor(m_process->output()), baseZoomFactor * 2.0f));
+ m_client->triggerZoom(1.5f);
+ QTRY_VERIFY(qFuzzyCompare(parseZoomFactor(m_process->output()), baseZoomFactor * 1.5f));
+ m_client->triggerZoom(0.5f);
+ QTRY_VERIFY(qFuzzyCompare(parseZoomFactor(m_process->output()), baseZoomFactor * 0.5f));
+ m_client->triggerZoom(-1.0f);
+ QTRY_VERIFY(qFuzzyCompare(parseZoomFactor(m_process->output()), baseZoomFactor));
+ m_process->stop();
+ QVERIFY(m_serviceErrors.isEmpty());
+}
+
+QTEST_MAIN(tst_QQmlPreview)
+
+#include "tst_qqmlpreview.moc"
diff --git a/tests/auto/qml/qmlmin/tst_qmlmin.cpp b/tests/auto/qml/qmlmin/tst_qmlmin.cpp
index e1ba6d12c0..c393149f59 100644
--- a/tests/auto/qml/qmlmin/tst_qmlmin.cpp
+++ b/tests/auto/qml/qmlmin/tst_qmlmin.cpp
@@ -127,6 +127,7 @@ void tst_qmlmin::initTestCase()
invalidFiles << "tests/auto/qml/qqmlecmascript/data/numberParsing_error.2.qml";
invalidFiles << "tests/auto/qml/parserstress/tests/ecma_3/FunExpr/fe-001.js";
invalidFiles << "tests/auto/qml/qjsengine/script/com/trolltech/syntaxerror/__init__.js";
+ invalidFiles << "tests/auto/qml/debugger/qqmlpreview/data/broken.qml";
}
QStringList tst_qmlmin::findFiles(const QDir &d)