aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2019-01-17 10:47:47 +0100
committerUlf Hermann <ulf.hermann@qt.io>2019-01-17 11:12:47 +0000
commitc7f22213ee522f8ee8972ded32444c9b5a8ee25c (patch)
tree1abc41ef73238d43dd4b67f395bd2d09138cca42
parent413c5c3b8a6479e0240d9f89bdc1086911659a88 (diff)
Import QML preview plugin into Qt Creator
... and relicense it as GPL or Commercial. Fixes: QTCREATORBUG-21838 Change-Id: I28d58ee963e1c63aa4a8bbe4525faade7201c353 Reviewed-by: Lars Knoll <lars.knoll@qt.io> Reviewed-by: Alessandro Portale <alessandro.portale@qt.io> Reviewed-by: Eike Ziller <eike.ziller@qt.io>
-rw-r--r--src/plugins/plugins.pro3
-rw-r--r--src/plugins/plugins.qbs1
-rw-r--r--src/plugins/qmlpreview/QmlPreview.json.in20
-rw-r--r--src/plugins/qmlpreview/qmlpreview.pro26
-rw-r--r--src/plugins/qmlpreview/qmlpreview.qbs49
-rw-r--r--src/plugins/qmlpreview/qmlpreview_dependencies.pri13
-rw-r--r--src/plugins/qmlpreview/qmlpreview_global.h34
-rw-r--r--src/plugins/qmlpreview/qmlpreviewclient.cpp131
-rw-r--r--src/plugins/qmlpreview/qmlpreviewclient.h85
-rw-r--r--src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp228
-rw-r--r--src/plugins/qmlpreview/qmlpreviewconnectionmanager.h78
-rw-r--r--src/plugins/qmlpreview/qmlpreviewfileontargetfinder.cpp124
-rw-r--r--src/plugins/qmlpreview/qmlpreviewfileontargetfinder.h53
-rw-r--r--src/plugins/qmlpreview/qmlpreviewplugin.cpp455
-rw-r--r--src/plugins/qmlpreview/qmlpreviewplugin.h132
-rw-r--r--src/plugins/qmlpreview/qmlpreviewruncontrol.cpp138
-rw-r--r--src/plugins/qmlpreview/qmlpreviewruncontrol.h70
-rw-r--r--src/plugins/qmlpreview/tests/qmlpreviewclient_test.cpp162
-rw-r--r--src/plugins/qmlpreview/tests/qmlpreviewclient_test.h44
-rw-r--r--src/plugins/qmlpreview/tests/qmlpreviewplugin_test.cpp109
-rw-r--r--src/plugins/qmlpreview/tests/qmlpreviewplugin_test.h50
-rw-r--r--src/plugins/qmlpreview/tests/tests.pri7
22 files changed, 2011 insertions, 1 deletions
diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro
index d5ff871028..e935e7b5e8 100644
--- a/src/plugins/plugins.pro
+++ b/src/plugins/plugins.pro
@@ -59,7 +59,8 @@ SUBDIRS = \
languageclient \
cppcheck \
compilationdatabaseprojectmanager \
- perfprofiler
+ perfprofiler \
+ qmlpreview
qtHaveModule(serialport) {
SUBDIRS += serialterminal
diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs
index 3f8ca16986..a0c44b33ca 100644
--- a/src/plugins/plugins.qbs
+++ b/src/plugins/plugins.qbs
@@ -55,6 +55,7 @@ Project {
"qmldesigner/qmldesigner.qbs",
"qmljseditor/qmljseditor.qbs",
"qmljstools/qmljstools.qbs",
+ "qmlpreview/qmlpreview.qbs",
"qmlprofiler/qmlprofiler.qbs",
"qmlprojectmanager/qmlprojectmanager.qbs",
"qnx/qnx.qbs",
diff --git a/src/plugins/qmlpreview/QmlPreview.json.in b/src/plugins/qmlpreview/QmlPreview.json.in
new file mode 100644
index 0000000000..37fcbd69b5
--- /dev/null
+++ b/src/plugins/qmlpreview/QmlPreview.json.in
@@ -0,0 +1,20 @@
+{
+ \"Name\" : \"QmlPreview\",
+ \"Version\" : \"$$QTCREATOR_VERSION\",
+ \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
+ \"Revision\" : \"$$QTC_PLUGIN_REVISION\",
+ \"Vendor\" : \"The Qt Company Ltd\",
+ \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
+ \"License\" : [ \"Commercial Usage\",
+ \"\",
+ \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt 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.\",
+ \"\",
+ \"GNU General Public License Usage\",
+ \"\",
+ \"Alternatively, this plugin 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 plugin. 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.\"
+ ],
+ \"Category\" : \"Qt Quick\",
+ \"Description\" : \"Qml Preview Plugin.\",
+ \"Url\" : \"http://www.qt.io\",
+ $$dependencyList
+}
diff --git a/src/plugins/qmlpreview/qmlpreview.pro b/src/plugins/qmlpreview/qmlpreview.pro
new file mode 100644
index 0000000000..534542ec00
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreview.pro
@@ -0,0 +1,26 @@
+DEFINES += QMLPREVIEW_LIBRARY
+
+include(../../qtcreatorplugin.pri)
+include(qmlpreview_dependencies.pri)
+
+equals(TEST, 1) {
+include(tests/tests.pri)
+}
+
+HEADERS += \
+ qmlpreview_global.h \
+ qmlpreviewclient.h \
+ qmlpreviewplugin.h \
+ qmlpreviewruncontrol.h \
+ qmlpreviewconnectionmanager.h \
+ qmlpreviewfileontargetfinder.h
+
+SOURCES += \
+ qmlpreviewplugin.cpp \
+ qmlpreviewclient.cpp \
+ qmlpreviewruncontrol.cpp \
+ qmlpreviewconnectionmanager.cpp \
+ qmlpreviewfileontargetfinder.cpp
+
+OTHER_FILES += \
+ QmlPreview.json.in
diff --git a/src/plugins/qmlpreview/qmlpreview.qbs b/src/plugins/qmlpreview/qmlpreview.qbs
new file mode 100644
index 0000000000..d59d5a9925
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreview.qbs
@@ -0,0 +1,49 @@
+import qbs
+
+QtcPlugin {
+ name: "QmlPreview"
+
+ Depends { name: "Core" }
+ Depends { name: "ExtensionSystem" }
+ Depends { name: "ProjectExplorer" }
+ Depends { name: "QmlDebug" }
+ Depends { name: "QmlJS" }
+ Depends { name: "QmlJSTools" }
+ Depends { name: "QtSupport" }
+ Depends { name: "ResourceEditor" }
+ Depends { name: "Utils" }
+
+ Depends {
+ name: "Qt"
+ submodules: ["core"]
+ }
+
+ Group {
+ name: "General"
+ files: [
+ "qmlpreviewclient.cpp",
+ "qmlpreviewclient.h",
+ "qmlpreviewconnectionmanager.cpp",
+ "qmlpreviewconnectionmanager.h",
+ "qmlpreviewfileontargetfinder.cpp",
+ "qmlpreviewfileontargetfinder.h",
+ "qmlpreview_global.h",
+ "qmlpreviewplugin.cpp",
+ "qmlpreviewplugin.h",
+ "qmlpreviewruncontrol.cpp",
+ "qmlpreviewruncontrol.h",
+ ]
+ }
+
+ Group {
+ name: "Unit tests"
+ condition: qtc.testsEnabled
+ prefix: "tests/"
+ files: [
+ "qmlpreviewclient_test.cpp",
+ "qmlpreviewclient_test.h",
+ "qmlpreviewplugin_test.cpp",
+ "qmlpreviewplugin_test.h",
+ ]
+ }
+}
diff --git a/src/plugins/qmlpreview/qmlpreview_dependencies.pri b/src/plugins/qmlpreview/qmlpreview_dependencies.pri
new file mode 100644
index 0000000000..3bd81f7cd7
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreview_dependencies.pri
@@ -0,0 +1,13 @@
+QTC_PLUGIN_NAME = QmlPreview
+QTC_LIB_DEPENDS += \
+ extensionsystem \
+ qmldebug \
+ qmljs \
+ utils
+
+QTC_PLUGIN_DEPENDS += \
+ coreplugin \
+ projectexplorer \
+ qmljstools \
+ qtsupport \
+ resourceeditor
diff --git a/src/plugins/qmlpreview/qmlpreview_global.h b/src/plugins/qmlpreview/qmlpreview_global.h
new file mode 100644
index 0000000000..372847578f
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreview_global.h
@@ -0,0 +1,34 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtGlobal>
+
+#if defined(QMLPREVIEW_LIBRARY)
+# define QMLPREVIEW_EXPORT Q_DECL_EXPORT
+#else
+# define QMLPREVIEW_EXPORT Q_DECL_IMPORT
+#endif
diff --git a/src/plugins/qmlpreview/qmlpreviewclient.cpp b/src/plugins/qmlpreview/qmlpreviewclient.cpp
new file mode 100644
index 0000000000..ae89d75d4a
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreviewclient.cpp
@@ -0,0 +1,131 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#include "qmlpreviewclient.h"
+#include <qmldebug/qpacketprotocol.h>
+
+#include <QUrl>
+
+namespace QmlPreview {
+
+QmlPreviewClient::QmlPreviewClient(QmlDebug::QmlDebugConnection *connection) :
+ QmlDebug::QmlDebugClient(QLatin1String("QmlPreview"), connection)
+{
+}
+
+void QmlPreviewClient::loadUrl(const QUrl &url)
+{
+ QmlDebug::QPacket packet(dataStreamVersion());
+ packet << static_cast<qint8>(Load) << url;
+ sendMessage(packet.data());
+}
+
+void QmlPreviewClient::rerun()
+{
+ QmlDebug::QPacket packet(dataStreamVersion());
+ packet << static_cast<qint8>(Rerun);
+ sendMessage(packet.data());
+}
+
+void QmlPreviewClient::zoom(float zoomFactor)
+{
+ QmlDebug::QPacket packet(dataStreamVersion());
+ packet << static_cast<qint8>(Zoom) << zoomFactor;
+ sendMessage(packet.data());
+}
+
+void QmlPreviewClient::language(const QUrl &context, const QString &locale)
+{
+ QmlDebug::QPacket packet(dataStreamVersion());
+ packet << static_cast<qint8>(Language) << context << locale;
+ sendMessage(packet.data());
+}
+
+void QmlPreviewClient::announceFile(const QString &path, const QByteArray &contents)
+{
+ QmlDebug::QPacket packet(dataStreamVersion());
+ packet << static_cast<qint8>(File) << path << contents;
+ sendMessage(packet.data());
+}
+
+void QmlPreviewClient::announceDirectory(const QString &path, const QStringList &entries)
+{
+ QmlDebug::QPacket packet(dataStreamVersion());
+ packet << static_cast<qint8>(Directory) << path << entries;
+ sendMessage(packet.data());
+}
+
+void QmlPreviewClient::announceError(const QString &path)
+{
+ QmlDebug::QPacket packet(dataStreamVersion());
+ packet << static_cast<qint8>(Error) << path;
+ sendMessage(packet.data());
+}
+
+void QmlPreviewClient::clearCache()
+{
+ QmlDebug::QPacket packet(dataStreamVersion());
+ packet << static_cast<qint8>(ClearCache);
+ sendMessage(packet.data());
+}
+
+void QmlPreviewClient::messageReceived(const QByteArray &data)
+{
+ QmlDebug::QPacket packet(dataStreamVersion(), data);
+ qint8 command;
+ packet >> command;
+ switch (command) {
+ case Request: {
+ QString path;
+ packet >> path;
+ emit pathRequested(path);
+ break;
+ }
+ case Error: {
+ QString error;
+ packet >> error;
+ emit errorReported(error);
+ break;
+ }
+ case Fps: {
+ FpsInfo info;
+ packet >> info.numSyncs >> info.minSync >> info.maxSync >> info.totalSync
+ >> info.numRenders >> info.minRender >> info.maxRender >> info.totalRender;
+ emit fpsReported(info);
+ break;
+ }
+ default:
+ qDebug() << "invalid command" << command;
+ break;
+ }
+}
+
+void QmlPreviewClient::stateChanged(QmlDebug::QmlDebugClient::State state)
+{
+ if (state == Unavailable)
+ emit debugServiceUnavailable();
+}
+
+} // namespace QmlPreview
diff --git a/src/plugins/qmlpreview/qmlpreviewclient.h b/src/plugins/qmlpreview/qmlpreviewclient.h
new file mode 100644
index 0000000000..4e84f6b3ee
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreviewclient.h
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "qmlpreview_global.h"
+#include <qmldebug/qmldebugclient.h>
+
+namespace QmlPreview {
+
+class QMLPREVIEW_EXPORT QmlPreviewClient : public QmlDebug::QmlDebugClient
+{
+ Q_OBJECT
+public:
+ enum Command {
+ File,
+ Load,
+ Request,
+ Error,
+ Rerun,
+ Directory,
+ ClearCache,
+ Zoom,
+ Fps,
+ Language
+ };
+
+ struct FpsInfo {
+ quint16 numSyncs = 0;
+ quint16 minSync = std::numeric_limits<quint16>::max();
+ quint16 maxSync = 0;
+ quint16 totalSync = 0;
+
+ quint16 numRenders = 0;
+ quint16 minRender = std::numeric_limits<quint16>::max();
+ quint16 maxRender = 0;
+ quint16 totalRender = 0;
+ };
+
+ explicit QmlPreviewClient(QmlDebug::QmlDebugConnection *connection);
+
+ void loadUrl(const QUrl &url);
+ void rerun();
+ void zoom(float zoomFactor);
+ void language(const QUrl &context, const QString &locale);
+ void announceFile(const QString &path, const QByteArray &contents);
+ void announceDirectory(const QString &path, const QStringList &entries);
+ void announceError(const QString &path);
+ void clearCache();
+
+ void messageReceived(const QByteArray &message) override;
+ void stateChanged(State state) override;
+
+signals:
+ void pathRequested(const QString &path);
+ void errorReported(const QString &error);
+ void fpsReported(const FpsInfo &fpsInfo);
+ void debugServiceUnavailable();
+};
+
+} // namespace QmlPreview
+
+Q_DECLARE_METATYPE(QmlPreview::QmlPreviewClient::FpsInfo)
diff --git a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp
new file mode 100644
index 0000000000..ad9c6467b6
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp
@@ -0,0 +1,228 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#include "qmlpreviewconnectionmanager.h"
+
+#include <coreplugin/icore.h>
+#include <coreplugin/messagemanager.h>
+#include <qtsupport/baseqtversion.h>
+
+#include <QFileInfo>
+#include <QDir>
+#include <QMessageBox>
+
+namespace QmlPreview {
+namespace Internal {
+
+QmlPreviewConnectionManager::~QmlPreviewConnectionManager()
+{
+}
+
+QmlPreviewConnectionManager::QmlPreviewConnectionManager(QObject *parent) :
+ QmlDebug::QmlDebugConnectionManager(parent)
+{
+ setTarget(nullptr);
+}
+
+void QmlPreviewConnectionManager::setTarget(ProjectExplorer::Target *target)
+{
+ QtSupport::BaseQtVersion::populateQmlFileFinder(&m_projectFileFinder, target);
+ m_projectFileFinder.setAdditionalSearchDirectories(Utils::FileNameList());
+ m_targetFileFinder.setTarget(target);
+}
+
+void QmlPreviewConnectionManager::setFileLoader(QmlPreviewFileLoader fileLoader)
+{
+ m_fileLoader = fileLoader;
+}
+
+void QmlPreviewConnectionManager::setFileClassifier(QmlPreviewFileClassifier fileClassifier)
+{
+ m_fileClassifier = fileClassifier;
+}
+
+void QmlPreviewConnectionManager::setFpsHandler(QmlPreviewFpsHandler fpsHandler)
+{
+ m_fpsHandler = fpsHandler;
+}
+
+void QmlPreviewConnectionManager::createClients()
+{
+ m_clientPlugin = new QmlPreviewClient(connection());
+
+ QObject::connect(
+ this, &QmlPreviewConnectionManager::loadFile, m_clientPlugin.data(),
+ [this](const QString &filename, const QString &changedFile,
+ const QByteArray &contents) {
+ if (!m_fileClassifier(changedFile)) {
+ emit restart();
+ return;
+ }
+
+ bool success = false;
+ const QString remoteChangedFile = m_targetFileFinder.findPath(changedFile, &success);
+ if (success)
+ m_clientPlugin->announceFile(remoteChangedFile, contents);
+ else
+ m_clientPlugin->clearCache();
+
+ m_lastLoadedUrl = m_targetFileFinder.findUrl(filename);
+ m_clientPlugin->loadUrl(m_lastLoadedUrl);
+ });
+
+ QObject::connect(this, &QmlPreviewConnectionManager::rerun,
+ m_clientPlugin.data(), &QmlPreviewClient::rerun);
+
+ QObject::connect(this, &QmlPreviewConnectionManager::zoom,
+ m_clientPlugin.data(), &QmlPreviewClient::zoom);
+
+ QObject::connect(this, &QmlPreviewConnectionManager::language,
+ m_clientPlugin.data(), [this](const QString &locale) {
+
+ // The preview service expects a context URL.
+ // Search the parent directories of the last loaded URL for i18n files.
+
+ const QString shortLocale = locale.left(locale.indexOf("_"));
+ QString path = m_lastLoadedUrl.path();
+
+ while (!path.isEmpty()) {
+ path = path.left(qMax(0, path.lastIndexOf("/")));
+ QUrl url = m_lastLoadedUrl;
+
+ auto tryPath = [&](const QString &postfix) {
+ url.setPath(path + "/i18n/qml_" + postfix);
+ bool success = false;
+ m_projectFileFinder.findFile(url, &success);
+ return success;
+ };
+
+ if (tryPath(locale + ".qm"))
+ break;
+ if (tryPath(locale))
+ break;
+ if (tryPath(shortLocale + ".qm"))
+ break;
+ if (tryPath(shortLocale))
+ break;
+ }
+
+ QUrl url = m_lastLoadedUrl;
+ url.setPath(path);
+
+ m_clientPlugin->language(url, locale);
+ });
+
+ QObject::connect(m_clientPlugin.data(), &QmlPreviewClient::pathRequested,
+ this, [this](const QString &path) {
+ const bool found = m_projectFileFinder.findFileOrDirectory(
+ path, [&](const QString &filename, int confidence) {
+ if (m_fileLoader && confidence == path.length()) {
+ bool success = false;
+ QByteArray contents = m_fileLoader(filename, &success);
+ if (success) {
+ if (!m_fileSystemWatcher.watchesFile(filename)) {
+ m_fileSystemWatcher.addFile(filename,
+ Utils::FileSystemWatcher::WatchModifiedDate);
+ }
+ m_clientPlugin->announceFile(path, contents);
+ } else {
+ m_clientPlugin->announceError(path);
+ }
+ } else {
+ m_clientPlugin->announceError(path);
+ }
+ }, [&](const QStringList &entries, int confidence) {
+ if (confidence == path.length())
+ m_clientPlugin->announceDirectory(path, entries);
+ else
+ m_clientPlugin->announceError(path);
+ });
+
+ if (!found)
+ m_clientPlugin->announceError(path);
+ });
+
+ QObject::connect(m_clientPlugin.data(), &QmlPreviewClient::errorReported,
+ this, [](const QString &error) {
+ Core::MessageManager::write("Error loading QML Live Preview:");
+ Core::MessageManager::write(error);
+ });
+
+ QObject::connect(m_clientPlugin.data(), &QmlPreviewClient::fpsReported,
+ this, [this](const QmlPreviewClient::FpsInfo &frames) {
+ if (m_fpsHandler) {
+ quint16 stats[] = {
+ frames.numSyncs, frames.minSync, frames.maxSync, frames.totalSync,
+ frames.numRenders, frames.minRender, frames.maxRender, frames.totalRender
+ };
+ m_fpsHandler(stats);
+ }
+ });
+
+ QObject::connect(m_clientPlugin.data(), &QmlPreviewClient::debugServiceUnavailable,
+ this, []() {
+ QMessageBox::warning(Core::ICore::mainWindow(), "Error loading QML Live Preview",
+ "QML Live Preview is not available for this version of Qt.");
+ }, Qt::QueuedConnection); // Queue it, so that it interfere with the connection timer
+
+ QObject::connect(&m_fileSystemWatcher, &Utils::FileSystemWatcher::fileChanged,
+ m_clientPlugin.data(), [this](const QString &changedFile) {
+ if (!m_fileLoader || !m_lastLoadedUrl.isValid())
+ return;
+
+ bool success = false;
+
+ const QByteArray contents = m_fileLoader(changedFile, &success);
+ if (!success)
+ return;
+
+ if (!m_fileClassifier(changedFile)) {
+ emit restart();
+ return;
+ }
+
+ const QString remoteChangedFile = m_targetFileFinder.findPath(changedFile, &success);
+ if (success)
+ m_clientPlugin->announceFile(remoteChangedFile, contents);
+ else
+ m_clientPlugin->clearCache();
+
+ m_clientPlugin->loadUrl(m_lastLoadedUrl);
+ });
+}
+
+void QmlPreviewConnectionManager::destroyClients()
+{
+ disconnect(m_clientPlugin.data(), nullptr, this, nullptr);
+ disconnect(this, nullptr, m_clientPlugin.data(), nullptr);
+ m_clientPlugin->deleteLater();
+ m_clientPlugin.clear();
+ m_fileSystemWatcher.removeFiles(m_fileSystemWatcher.files());
+ QTC_ASSERT(m_fileSystemWatcher.directories().isEmpty(),
+ m_fileSystemWatcher.removeDirectories(m_fileSystemWatcher.directories()));
+}
+
+} // namespace Internal
+} // namespace QmlPreview
diff --git a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h
new file mode 100644
index 0000000000..77489cba14
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "qmlpreviewplugin.h"
+#include "qmlpreviewclient.h"
+#include "qmlpreviewfileontargetfinder.h"
+
+#include <qmldebug/qmldebugconnectionmanager.h>
+#include <utils/fileinprojectfinder.h>
+#include <utils/filesystemwatcher.h>
+
+namespace ProjectExplorer {
+class Target;
+}
+
+namespace QmlPreview {
+namespace Internal {
+
+class QmlPreviewConnectionManager : public QmlDebug::QmlDebugConnectionManager
+{
+ Q_OBJECT
+public:
+ virtual ~QmlPreviewConnectionManager();
+
+ explicit QmlPreviewConnectionManager(QObject *parent = nullptr);
+ void setTarget(ProjectExplorer::Target *target);
+ void setFileLoader(QmlPreviewFileLoader fileLoader);
+ void setFileClassifier(QmlPreviewFileClassifier fileClassifier);
+ void setFpsHandler(QmlPreviewFpsHandler fpsHandler);
+
+signals:
+ void loadFile(const QString &filename, const QString &changedFile, const QByteArray &contents);
+ void zoom(float zoomFactor);
+ void language(const QString &locale);
+ void rerun();
+ void restart();
+
+protected:
+ void createClients() override;
+ void destroyClients() override;
+
+private:
+ Utils::FileInProjectFinder m_projectFileFinder;
+ QmlPreviewFileOnTargetFinder m_targetFileFinder;
+ QPointer<QmlPreviewClient> m_clientPlugin;
+ Utils::FileSystemWatcher m_fileSystemWatcher;
+ QUrl m_lastLoadedUrl;
+ QmlPreviewFileLoader m_fileLoader = nullptr;
+ QmlPreviewFileClassifier m_fileClassifier = nullptr;
+ QmlPreviewFpsHandler m_fpsHandler = nullptr;
+};
+
+} // namespace Internal
+} // namespace QmlPreview
diff --git a/src/plugins/qmlpreview/qmlpreviewfileontargetfinder.cpp b/src/plugins/qmlpreview/qmlpreviewfileontargetfinder.cpp
new file mode 100644
index 0000000000..95ed107fea
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreviewfileontargetfinder.cpp
@@ -0,0 +1,124 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#include "qmlpreviewfileontargetfinder.h"
+
+#include <projectexplorer/deploymentdata.h>
+#include <projectexplorer/kitinformation.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectnodes.h>
+#include <projectexplorer/projecttree.h>
+#include <projectexplorer/target.h>
+
+#include <resourceeditor/resourcenode.h>
+
+#include <utils/qtcassert.h>
+
+namespace QmlPreview {
+namespace Internal {
+
+void QmlPreviewFileOnTargetFinder::setTarget(ProjectExplorer::Target *target)
+{
+ m_target = target;
+}
+
+ProjectExplorer::Target *QmlPreviewFileOnTargetFinder::target() const
+{
+ return m_target.data();
+}
+
+QString resourceNodePath(const ProjectExplorer::Node *node)
+{
+ if (auto resourceNode = dynamic_cast<const ResourceEditor::ResourceFileNode *>(node))
+ return ":" + resourceNode->qrcPath();
+ return QString();
+}
+
+QString QmlPreviewFileOnTargetFinder::findPath(const QString &filePath, bool *success) const
+{
+ if (success)
+ *success = (m_target != nullptr);
+
+ if (!m_target)
+ return filePath;
+
+ ProjectExplorer::DeployableFile file
+ = m_target->deploymentData().deployableForLocalFile(filePath);
+ if (file.isValid())
+ return file.remoteFilePath();
+
+ // Try the current node first. It's likely that this is the one we're looking for and if there
+ // is any ambiguity (same file mapped to multiple qrc paths) it should take precedence.
+ ProjectExplorer::Node *currentNode = ProjectExplorer::ProjectTree::findCurrentNode();
+ if (currentNode && currentNode->filePath().toString() == filePath) {
+ const QString path = resourceNodePath(currentNode);
+ if (!path.isEmpty())
+ return path;
+ }
+
+ if (ProjectExplorer::Project *project = m_target->project()) {
+ if (ProjectExplorer::ProjectNode *rootNode = project->rootProjectNode()) {
+ const QList<ProjectExplorer::Node *> nodes = rootNode->findNodes(
+ [&](ProjectExplorer::Node *node) {
+ return node->filePath().toString() == filePath;
+ });
+
+ for (const ProjectExplorer::Node *node : nodes) {
+ const QString path = resourceNodePath(node);
+ if (!path.isEmpty())
+ return path;
+ }
+ } else {
+ // Can there be projects without root node?
+ }
+ } else {
+ // Targets should always have a project.
+ QTC_CHECK(false);
+ }
+
+ if (success) {
+ // On desktop, if there is no "remote" path, then the application will load the local path.
+ *success = ProjectExplorer::DeviceTypeKitInformation::deviceTypeId(m_target->kit())
+ == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE;
+ }
+ return filePath;
+}
+
+QUrl QmlPreviewFileOnTargetFinder::findUrl(const QString &filePath, bool *success) const
+{
+ const QString remotePath = findPath(filePath, success);
+ if (remotePath.startsWith(':')) {
+ QUrl result;
+ result.setPath(remotePath.mid(1));
+ result.setScheme("qrc");
+ return result;
+ } else {
+ return QUrl::fromLocalFile(remotePath);
+ }
+}
+
+} // namespace Internal
+} // namespace QmlPreview
diff --git a/src/plugins/qmlpreview/qmlpreviewfileontargetfinder.h b/src/plugins/qmlpreview/qmlpreviewfileontargetfinder.h
new file mode 100644
index 0000000000..e62396b8cf
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreviewfileontargetfinder.h
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QUrl>
+#include <QString>
+#include <QPointer>
+
+namespace ProjectExplorer {
+class Target;
+}
+
+namespace QmlPreview {
+namespace Internal {
+
+class QmlPreviewFileOnTargetFinder
+{
+public:
+ void setTarget(ProjectExplorer::Target *target);
+ ProjectExplorer::Target *target() const;
+
+ QString findPath(const QString &filePath, bool *success = 0) const;
+ QUrl findUrl(const QString &filePath, bool *success = 0) const;
+
+private:
+ QPointer<ProjectExplorer::Target> m_target;
+};
+
+} // namespace Internal
+} // namespace QmlPreview
diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.cpp b/src/plugins/qmlpreview/qmlpreviewplugin.cpp
new file mode 100644
index 0000000000..b60996ae1f
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreviewplugin.cpp
@@ -0,0 +1,455 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#include "qmlpreviewplugin.h"
+#include "qmlpreviewruncontrol.h"
+
+#ifdef WITH_TESTS
+#include "tests/qmlpreviewclient_test.h"
+#include "tests/qmlpreviewplugin_test.h"
+#endif
+
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/actionmanager/actioncontainer.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/editormanager/ieditor.h>
+#include <coreplugin/messagemanager.h>
+
+#include <extensionsystem/pluginmanager.h>
+
+#include <projectexplorer/kit.h>
+#include <projectexplorer/kitinformation.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectnodes.h>
+#include <projectexplorer/projecttree.h>
+#include <projectexplorer/runconfiguration.h>
+#include <projectexplorer/session.h>
+#include <projectexplorer/target.h>
+
+#include <qmljs/qmljsdocument.h>
+#include <qmljs/qmljsmodelmanagerinterface.h>
+#include <qmljstools/qmljstoolsconstants.h>
+
+#include <QAction>
+
+using namespace ProjectExplorer;
+
+namespace QmlPreview {
+namespace Internal {
+
+class QmlPreviewParser : public QObject
+{
+ Q_OBJECT
+public:
+ QmlPreviewParser();
+ void parse(const QString &name, const QByteArray &contents,
+ QmlJS::Dialect::Enum dialect);
+
+signals:
+ void success(const QString &changedFile, const QByteArray &contents);
+ void failure();
+};
+
+static QByteArray defaultFileLoader(const QString &filename, bool *success)
+{
+ if (Core::DocumentModel::Entry *entry
+ = Core::DocumentModel::entryForFilePath(Utils::FileName::fromString(filename))) {
+ if (!entry->isSuspended) {
+ *success = true;
+ return entry->document->contents();
+ }
+ }
+
+ QFile file(filename);
+ *success = file.open(QIODevice::ReadOnly);
+ return (*success) ? file.readAll() : QByteArray();
+}
+
+static bool defaultFileClassifier(const QString &filename)
+{
+ // We cannot dynamically load changes in qtquickcontrols2.conf
+ return !filename.endsWith("qtquickcontrols2.conf");
+}
+
+static void defaultFpsHandler(quint16 frames[8])
+{
+ Core::MessageManager::write(QString::fromLatin1("QML preview: %1 fps").arg(frames[0]));
+}
+
+bool QmlPreviewPlugin::initialize(const QStringList &arguments, QString *errorString)
+{
+ Q_UNUSED(arguments);
+ Q_UNUSED(errorString);
+
+ setFileLoader(&defaultFileLoader);
+ setFileClassifier(&defaultFileClassifier);
+ setFpsHandler(&defaultFpsHandler);
+
+ auto constraint = [](RunConfiguration *runConfiguration) {
+ Target *target = runConfiguration ? runConfiguration->target() : nullptr;
+ Kit *kit = target ? target->kit() : nullptr;
+ return DeviceTypeKitInformation::deviceTypeId(kit) == Constants::DESKTOP_DEVICE_TYPE;
+ };
+
+ RunControl::registerWorker<LocalQmlPreviewSupport>(Constants::QML_PREVIEW_RUN_MODE, constraint);
+
+ Core::ActionContainer *menu = Core::ActionManager::actionContainer(
+ Constants::M_BUILDPROJECT);
+ QAction *action = new QAction(tr("QML Preview"), this);
+ action->setToolTip(QLatin1String("Preview changes to QML code live in your application."));
+ action->setEnabled(SessionManager::startupProject() != nullptr);
+ connect(SessionManager::instance(), &SessionManager::startupProjectChanged, action,
+ &QAction::setEnabled);
+ connect(action, &QAction::triggered, this, []() {
+ ProjectExplorerPlugin::runStartupProject(Constants::QML_PREVIEW_RUN_MODE);
+ });
+ menu->addAction(Core::ActionManager::registerAction(action, "QmlPreview.Internal"),
+ Constants::G_BUILD_RUN);
+
+ Core::Context projectTreeContext(Constants::C_PROJECT_TREE);
+ menu = Core::ActionManager::actionContainer(Constants::M_FILECONTEXT);
+ action = new QAction(tr("Preview File"), this);
+ action->setEnabled(false);
+ connect(this, &QmlPreviewPlugin::runningPreviewsChanged,
+ action, [action](const QmlPreviewRunControlList &previews) {
+ action->setEnabled(!previews.isEmpty());
+ });
+ connect(action, &QAction::triggered, this, &QmlPreviewPlugin::previewCurrentFile);
+ menu->addAction(Core::ActionManager::registerAction(action, "QmlPreview.Preview",
+ projectTreeContext),
+ Constants::G_FILE_OTHER);
+ action->setVisible(false);
+ connect(ProjectTree::instance(), &ProjectTree::currentNodeChanged, action, [action]() {
+ const Node *node = ProjectTree::findCurrentNode();
+ const FileNode *fileNode = node ? node->asFileNode() : nullptr;
+ action->setVisible(fileNode ? fileNode->fileType() == FileType::QML : false);
+ });
+
+ m_parseThread.start();
+ QmlPreviewParser *parser = new QmlPreviewParser;
+ parser->moveToThread(&m_parseThread);
+ connect(this, &QObject::destroyed, parser, &QObject::deleteLater);
+ connect(this, &QmlPreviewPlugin::checkDocument, parser, &QmlPreviewParser::parse);
+ connect(this, &QmlPreviewPlugin::previewedFileChanged, this, &QmlPreviewPlugin::checkFile);
+ connect(parser, &QmlPreviewParser::success, this, &QmlPreviewPlugin::triggerPreview);
+
+ RunControl::registerWorkerCreator(Constants::QML_PREVIEW_RUN_MODE,
+ [this](RunControl *runControl) {
+ QmlPreviewRunner *runner = new QmlPreviewRunner(runControl, m_fileLoader, m_fileClassifer,
+ m_fpsHandler, m_zoomFactor, m_locale);
+ QObject::connect(this, &QmlPreviewPlugin::updatePreviews,
+ runner, &QmlPreviewRunner::loadFile);
+ QObject::connect(this, &QmlPreviewPlugin::rerunPreviews,
+ runner, &QmlPreviewRunner::rerun);
+ QObject::connect(runner, &QmlPreviewRunner::ready,
+ this, &QmlPreviewPlugin::previewCurrentFile);
+ QObject::connect(this, &QmlPreviewPlugin::zoomFactorChanged,
+ runner, &QmlPreviewRunner::zoom);
+ QObject::connect(this, &QmlPreviewPlugin::localeChanged,
+ runner, &QmlPreviewRunner::language);
+
+ QObject::connect(runner, &RunWorker::started, this, [this, runControl]() {
+ addPreview(runControl);
+ });
+ QObject::connect(runner, &RunWorker::stopped, this, [this, runControl]() {
+ removePreview(runControl);
+ });
+
+ return runner;
+ });
+
+ attachToEditor();
+ return true;
+}
+
+void QmlPreviewPlugin::extensionsInitialized()
+{
+}
+
+ExtensionSystem::IPlugin::ShutdownFlag QmlPreviewPlugin::aboutToShutdown()
+{
+ m_parseThread.quit();
+ m_parseThread.wait();
+ return SynchronousShutdown;
+}
+
+QList<QObject *> QmlPreviewPlugin::createTestObjects() const
+{
+ QList<QObject *> tests;
+#ifdef WITH_TESTS
+ tests.append(new QmlPreviewClientTest);
+ tests.append(new QmlPreviewPluginTest);
+#endif
+ return tests;
+}
+
+QString QmlPreviewPlugin::previewedFile() const
+{
+ return m_previewedFile;
+}
+
+void QmlPreviewPlugin::setPreviewedFile(const QString &previewedFile)
+{
+ if (m_previewedFile == previewedFile)
+ return;
+
+ m_previewedFile = previewedFile;
+ emit previewedFileChanged(m_previewedFile);
+}
+
+QmlPreviewRunControlList QmlPreviewPlugin::runningPreviews() const
+{
+ return m_runningPreviews;
+}
+
+QmlPreviewFileLoader QmlPreviewPlugin::fileLoader() const
+{
+ return m_fileLoader;
+}
+
+QmlPreviewFileClassifier QmlPreviewPlugin::fileClassifier() const
+{
+ return m_fileClassifer;
+}
+
+void QmlPreviewPlugin::setFileClassifier(QmlPreviewFileClassifier fileClassifer)
+{
+ if (m_fileClassifer == fileClassifer)
+ return;
+
+ m_fileClassifer = fileClassifer;
+ emit fileClassifierChanged(m_fileClassifer);
+}
+
+float QmlPreviewPlugin::zoomFactor() const
+{
+ return m_zoomFactor;
+}
+
+void QmlPreviewPlugin::setZoomFactor(float zoomFactor)
+{
+ if (m_zoomFactor == zoomFactor)
+ return;
+
+ m_zoomFactor = zoomFactor;
+ emit zoomFactorChanged(m_zoomFactor);
+}
+
+QmlPreviewFpsHandler QmlPreviewPlugin::fpsHandler() const
+{
+ return m_fpsHandler;
+}
+
+void QmlPreviewPlugin::setFpsHandler(QmlPreviewFpsHandler fpsHandler)
+{
+ if (m_fpsHandler == fpsHandler)
+ return;
+
+ m_fpsHandler = fpsHandler;
+ emit fpsHandlerChanged(m_fpsHandler);
+}
+
+QString QmlPreviewPlugin::locale() const
+{
+ return m_locale;
+}
+
+void QmlPreviewPlugin::setLocale(const QString &locale)
+{
+ if (m_locale == locale)
+ return;
+
+ m_locale = locale;
+ emit localeChanged(m_locale);
+}
+
+void QmlPreviewPlugin::setFileLoader(QmlPreviewFileLoader fileLoader)
+{
+ if (m_fileLoader == fileLoader)
+ return;
+
+ m_fileLoader = fileLoader;
+ emit fileLoaderChanged(m_fileLoader);
+}
+
+void QmlPreviewPlugin::previewCurrentFile()
+{
+ const Node *currentNode = ProjectTree::findCurrentNode();
+ if (!currentNode || currentNode->nodeType() != NodeType::File
+ || currentNode->asFileNode()->fileType() != FileType::QML)
+ return;
+
+ const QString file = currentNode->filePath().toString();
+ if (file != m_previewedFile)
+ setPreviewedFile(file);
+ else
+ checkFile(file);
+}
+
+void QmlPreviewPlugin::onEditorChanged(Core::IEditor *editor)
+{
+ if (m_lastEditor) {
+ Core::IDocument *doc = m_lastEditor->document();
+ disconnect(doc, &Core::IDocument::contentsChanged, this, &QmlPreviewPlugin::setDirty);
+ if (m_dirty) {
+ m_dirty = false;
+ checkEditor();
+ }
+ }
+
+ m_lastEditor = editor;
+ if (m_lastEditor) {
+ // Handle new editor
+ connect(m_lastEditor->document(), &Core::IDocument::contentsChanged,
+ this, &QmlPreviewPlugin::setDirty);
+ }
+}
+
+void QmlPreviewPlugin::onEditorAboutToClose(Core::IEditor *editor)
+{
+ if (m_lastEditor != editor)
+ return;
+
+ // Oh no our editor is going to be closed
+ // get the content first
+ Core::IDocument *doc = m_lastEditor->document();
+ disconnect(doc, &Core::IDocument::contentsChanged, this, &QmlPreviewPlugin::setDirty);
+ if (m_dirty) {
+ m_dirty = false;
+ checkEditor();
+ }
+ m_lastEditor = nullptr;
+}
+
+void QmlPreviewPlugin::setDirty()
+{
+ m_dirty = true;
+ QTimer::singleShot(1000, this, [this](){
+ if (m_dirty && m_lastEditor) {
+ m_dirty = false;
+ checkEditor();
+ }
+ });
+}
+
+void QmlPreviewPlugin::addPreview(ProjectExplorer::RunControl *preview)
+{
+ m_runningPreviews.append(preview);
+ emit runningPreviewsChanged(m_runningPreviews);
+}
+
+void QmlPreviewPlugin::removePreview(ProjectExplorer::RunControl *preview)
+{
+ m_runningPreviews.removeOne(preview);
+ emit runningPreviewsChanged(m_runningPreviews);
+}
+
+void QmlPreviewPlugin::attachToEditor()
+{
+ Core::EditorManager *editorManager = Core::EditorManager::instance();
+ connect(editorManager, &Core::EditorManager::currentEditorChanged,
+ this, &QmlPreviewPlugin::onEditorChanged);
+ connect(editorManager, &Core::EditorManager::editorAboutToClose,
+ this, &QmlPreviewPlugin::onEditorAboutToClose);
+}
+
+void QmlPreviewPlugin::checkEditor()
+{
+ QmlJS::Dialect::Enum dialect = QmlJS::Dialect::AnyLanguage;
+ Core::IDocument *doc = m_lastEditor->document();
+ const QString mimeType = doc->mimeType();
+ if (mimeType == QmlJSTools::Constants::JS_MIMETYPE)
+ dialect = QmlJS::Dialect::JavaScript;
+ else if (mimeType == QmlJSTools::Constants::JSON_MIMETYPE)
+ dialect = QmlJS::Dialect::Json;
+ else if (mimeType == QmlJSTools::Constants::QML_MIMETYPE)
+ dialect = QmlJS::Dialect::Qml;
+// --- Can we detect those via mime types?
+// else if (mimeType == ???)
+// dialect = QmlJS::Dialect::QmlQtQuick1;
+// else if (mimeType == ???)
+// dialect = QmlJS::Dialect::QmlQtQuick2;
+ else if (mimeType == QmlJSTools::Constants::QBS_MIMETYPE)
+ dialect = QmlJS::Dialect::QmlQbs;
+ else if (mimeType == QmlJSTools::Constants::QMLPROJECT_MIMETYPE)
+ dialect = QmlJS::Dialect::QmlProject;
+ else if (mimeType == QmlJSTools::Constants::QMLTYPES_MIMETYPE)
+ dialect = QmlJS::Dialect::QmlTypeInfo;
+ else if (mimeType == QmlJSTools::Constants::QMLUI_MIMETYPE)
+ dialect = QmlJS::Dialect::QmlQtQuick2Ui;
+ else
+ dialect = QmlJS::Dialect::NoLanguage;
+ emit checkDocument(doc->filePath().toString(), doc->contents(), dialect);
+}
+
+void QmlPreviewPlugin::checkFile(const QString &fileName)
+{
+ if (!m_fileLoader)
+ return;
+
+ bool success = false;
+ const QByteArray contents = m_fileLoader(fileName, &success);
+
+ if (success) {
+ emit checkDocument(fileName, contents,
+ QmlJS::ModelManagerInterface::guessLanguageOfFile(fileName).dialect());
+ }
+}
+
+void QmlPreviewPlugin::triggerPreview(const QString &changedFile, const QByteArray &contents)
+{
+ if (m_previewedFile.isEmpty())
+ previewCurrentFile();
+ else
+ emit updatePreviews(m_previewedFile, changedFile, contents);
+}
+
+QmlPreviewParser::QmlPreviewParser()
+{
+ static const int dialectMeta = qRegisterMetaType<QmlJS::Dialect::Enum>();
+ Q_UNUSED(dialectMeta);
+}
+
+void QmlPreviewParser::parse(const QString &name, const QByteArray &contents,
+ QmlJS::Dialect::Enum dialect)
+{
+ if (!QmlJS::Dialect(dialect).isQmlLikeOrJsLanguage()) {
+ emit success(name, contents);
+ return;
+ }
+
+ QmlJS::Document::MutablePtr qmljsDoc = QmlJS::Document::create(name, dialect);
+ qmljsDoc->setSource(QString::fromUtf8(contents));
+ if (qmljsDoc->parse())
+ emit success(name, contents);
+ else
+ emit failure();
+}
+
+} // namespace Internal
+} // namespace QmlPreview
+
+#include <qmlpreviewplugin.moc>
diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.h b/src/plugins/qmlpreview/qmlpreviewplugin.h
new file mode 100644
index 0000000000..f1e780cf1d
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreviewplugin.h
@@ -0,0 +1,132 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <projectexplorer/runconfiguration.h>
+#include <extensionsystem/iplugin.h>
+#include <qmljs/qmljsdialect.h>
+#include <QUrl>
+#include <QThread>
+
+namespace Core { class IEditor; }
+
+namespace QmlPreview {
+
+typedef bool (*QmlPreviewFileClassifier) (const QString &);
+typedef QByteArray (*QmlPreviewFileLoader)(const QString &, bool *);
+typedef void (*QmlPreviewFpsHandler)(quint16[8]);
+typedef QList<ProjectExplorer::RunControl *> QmlPreviewRunControlList;
+
+namespace Internal {
+
+class QmlPreviewPlugin : public ExtensionSystem::IPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "QmlPreview.json")
+ Q_PROPERTY(QString previewedFile READ previewedFile WRITE setPreviewedFile
+ NOTIFY previewedFileChanged)
+ Q_PROPERTY(QmlPreview::QmlPreviewRunControlList runningPreviews READ runningPreviews
+ NOTIFY runningPreviewsChanged)
+ Q_PROPERTY(QmlPreview::QmlPreviewFileLoader fileLoader READ fileLoader
+ WRITE setFileLoader NOTIFY fileLoaderChanged)
+ Q_PROPERTY(QmlPreview::QmlPreviewFileClassifier fileClassifer READ fileClassifier
+ WRITE setFileClassifier NOTIFY fileClassifierChanged)
+ Q_PROPERTY(QmlPreview::QmlPreviewFpsHandler fpsHandler READ fpsHandler
+ WRITE setFpsHandler NOTIFY fpsHandlerChanged)
+ Q_PROPERTY(float zoomFactor READ zoomFactor WRITE setZoomFactor NOTIFY zoomFactorChanged)
+ Q_PROPERTY(QString locale READ locale WRITE setLocale NOTIFY localeChanged)
+
+public:
+ bool initialize(const QStringList &arguments, QString *errorString) override;
+ void extensionsInitialized() override;
+ ShutdownFlag aboutToShutdown() override;
+ QList<QObject *> createTestObjects() const override;
+
+ QString previewedFile() const;
+ void setPreviewedFile(const QString &previewedFile);
+ QmlPreviewRunControlList runningPreviews() const;
+
+ void setFileLoader(QmlPreviewFileLoader fileLoader);
+ QmlPreviewFileLoader fileLoader() const;
+
+ QmlPreviewFileClassifier fileClassifier() const;
+ void setFileClassifier(QmlPreviewFileClassifier fileClassifer);
+
+ float zoomFactor() const;
+ void setZoomFactor(float zoomFactor);
+
+ QmlPreview::QmlPreviewFpsHandler fpsHandler() const;
+ void setFpsHandler(QmlPreview::QmlPreviewFpsHandler fpsHandler);
+
+ QString locale() const;
+ void setLocale(const QString &locale);
+
+signals:
+ void checkDocument(const QString &name, const QByteArray &contents,
+ QmlJS::Dialect::Enum dialect);
+ void updatePreviews(const QString &previewedFile, const QString &changedFile,
+ const QByteArray &contents);
+ void previewedFileChanged(const QString &previewedFile);
+ void runningPreviewsChanged(const QmlPreviewRunControlList &runningPreviews);
+ void rerunPreviews();
+ void fileLoaderChanged(QmlPreviewFileLoader fileLoader);
+ void fileClassifierChanged(QmlPreviewFileClassifier fileClassifer);
+ void fpsHandlerChanged(QmlPreview::QmlPreviewFpsHandler fpsHandler);
+
+ void zoomFactorChanged(float zoomFactor);
+ void localeChanged(const QString &locale);
+
+private:
+ void previewCurrentFile();
+ void onEditorChanged(Core::IEditor *editor);
+ void onEditorAboutToClose(Core::IEditor *editor);
+ void setDirty();
+ void addPreview(ProjectExplorer::RunControl *preview);
+ void removePreview(ProjectExplorer::RunControl *preview);
+ void attachToEditor();
+ void checkEditor();
+ void checkFile(const QString &fileName);
+ void triggerPreview(const QString &changedFile, const QByteArray &contents);
+
+ QThread m_parseThread;
+ QString m_previewedFile;
+ QmlPreviewFileLoader m_fileLoader = nullptr;
+ Core::IEditor *m_lastEditor = nullptr;
+ QmlPreviewRunControlList m_runningPreviews;
+ bool m_dirty = false;
+ QmlPreview::QmlPreviewFileClassifier m_fileClassifer = nullptr;
+ float m_zoomFactor = -1.0;
+ QmlPreview::QmlPreviewFpsHandler m_fpsHandler = nullptr;
+ QString m_locale;
+};
+
+} // namespace Internal
+} // namespace QmlPreview
+
+Q_DECLARE_METATYPE(QmlPreview::QmlPreviewFileLoader)
+Q_DECLARE_METATYPE(QmlPreview::QmlPreviewFileClassifier)
+Q_DECLARE_METATYPE(QmlPreview::QmlPreviewFpsHandler)
+Q_DECLARE_METATYPE(QmlPreview::QmlPreviewRunControlList)
diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp
new file mode 100644
index 0000000000..c54208f2ee
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp
@@ -0,0 +1,138 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#include "qmlpreviewruncontrol.h"
+
+#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/session.h>
+#include <projectexplorer/target.h>
+
+#include <qmldebug/qmldebugcommandlinearguments.h>
+#include <qtsupport/baseqtversion.h>
+#include <qtsupport/qtkitinformation.h>
+
+#include <utils/port.h>
+#include <utils/qtcprocess.h>
+#include <utils/url.h>
+
+namespace QmlPreview {
+
+static const QString QmlServerUrl = "QmlServerUrl";
+
+QmlPreviewRunner::QmlPreviewRunner(ProjectExplorer::RunControl *runControl,
+ QmlPreviewFileLoader fileLoader,
+ QmlPreviewFileClassifier fileClassifier,
+ QmlPreviewFpsHandler fpsHandler,
+ float initialZoom,
+ const QString &initialLocale)
+ : RunWorker(runControl)
+{
+ setId("QmlPreviewRunner");
+ m_connectionManager.reset(new Internal::QmlPreviewConnectionManager(this));
+ m_connectionManager->setFileLoader(fileLoader);
+ m_connectionManager->setFileClassifier(fileClassifier);
+ m_connectionManager->setFpsHandler(fpsHandler);
+
+ connect(this, &QmlPreviewRunner::loadFile,
+ m_connectionManager.data(), &Internal::QmlPreviewConnectionManager::loadFile);
+ connect(this, &QmlPreviewRunner::rerun,
+ m_connectionManager.data(), &Internal::QmlPreviewConnectionManager::rerun);
+
+ connect(this, &QmlPreviewRunner::zoom,
+ m_connectionManager.data(), &Internal::QmlPreviewConnectionManager::zoom);
+ connect(this, &QmlPreviewRunner::language,
+ m_connectionManager.data(), &Internal::QmlPreviewConnectionManager::language);
+ connect(m_connectionManager.data(), &Internal::QmlPreviewConnectionManager::connectionOpened,
+ this, [this, initialZoom, initialLocale]() {
+ if (initialZoom > 0)
+ emit zoom(initialZoom);
+ if (!initialLocale.isEmpty())
+ emit language(initialLocale);
+ emit ready();
+ });
+
+ connect(m_connectionManager.data(), &Internal::QmlPreviewConnectionManager::restart,
+ runControl, [runControl, this]() {
+ if (!runControl->isRunning())
+ return;
+
+ ProjectExplorer::RunConfiguration *runConfig = runControl->runConfiguration();
+ connect(runControl, &ProjectExplorer::RunControl::stopped, runConfig, [runConfig](){
+ ProjectExplorer::ProjectExplorerPlugin::runRunConfiguration(
+ runConfig, ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE, true);
+ });
+
+ runControl->initiateStop();
+ });
+}
+
+void QmlPreviewRunner::start()
+{
+ ProjectExplorer::Target *target = nullptr;
+ if (ProjectExplorer::RunConfiguration *config = runControl()->runConfiguration())
+ target = config->target();
+ m_connectionManager->setTarget(target);
+ m_connectionManager->connectToServer(serverUrl());
+ reportStarted();
+}
+
+void QmlPreviewRunner::stop()
+{
+ m_connectionManager->disconnectFromServer();
+ reportStopped();
+}
+
+void QmlPreviewRunner::setServerUrl(const QUrl &serverUrl)
+{
+ recordData(QmlServerUrl, serverUrl);
+}
+
+QUrl QmlPreviewRunner::serverUrl() const
+{
+ return recordedData(QmlServerUrl).toUrl();
+}
+
+LocalQmlPreviewSupport::LocalQmlPreviewSupport(ProjectExplorer::RunControl *runControl)
+ : SimpleTargetRunner(runControl)
+{
+ setId("LocalQmlPreviewSupport");
+ const QUrl serverUrl = Utils::urlFromLocalSocket();
+
+ QmlPreviewRunner *preview = qobject_cast<QmlPreviewRunner *>(
+ runControl->createWorker(ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE));
+ preview->setServerUrl(serverUrl);
+
+ addStopDependency(preview);
+ addStartDependency(preview);
+
+ ProjectExplorer::Runnable run = runnable();
+
+ Utils::QtcProcess::addArg(&run.commandLineArguments,
+ QmlDebug::qmlDebugLocalArguments(QmlDebug::QmlPreviewServices,
+ serverUrl.path()));
+ setRunnable(run);
+}
+
+} // namespace QmlPreview
diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.h b/src/plugins/qmlpreview/qmlpreviewruncontrol.h
new file mode 100644
index 0000000000..889e7f641b
--- /dev/null
+++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.h
@@ -0,0 +1,70 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "qmlpreviewconnectionmanager.h"
+#include "qmlpreviewfileontargetfinder.h"
+#include "qmlpreviewplugin.h"
+#include <projectexplorer/runconfiguration.h>
+
+namespace QmlPreview {
+
+class QmlPreviewRunner : public ProjectExplorer::RunWorker
+{
+ Q_OBJECT
+
+public:
+ QmlPreviewRunner(ProjectExplorer::RunControl *runControl, QmlPreviewFileLoader fileLoader,
+ QmlPreviewFileClassifier fileClassifier, QmlPreviewFpsHandler fpsHandler,
+ float initialZoom, const QString &initialLocale);
+
+ void setServerUrl(const QUrl &serverUrl);
+ QUrl serverUrl() const;
+
+signals:
+ void loadFile(const QString &previewedFile, const QString &changedFile,
+ const QByteArray &contents);
+ void language(const QString &locale);
+ void zoom(float zoomFactor);
+ void rerun();
+ void ready();
+
+private:
+ void start() override;
+ void stop() override;
+
+ QScopedPointer<Internal::QmlPreviewConnectionManager> m_connectionManager;
+};
+
+class LocalQmlPreviewSupport : public ProjectExplorer::SimpleTargetRunner
+{
+ Q_OBJECT
+
+public:
+ LocalQmlPreviewSupport(ProjectExplorer::RunControl *runControl);
+};
+
+} // namespace QmlPreview
diff --git a/src/plugins/qmlpreview/tests/qmlpreviewclient_test.cpp b/src/plugins/qmlpreview/tests/qmlpreviewclient_test.cpp
new file mode 100644
index 0000000000..1194702e6b
--- /dev/null
+++ b/src/plugins/qmlpreview/tests/qmlpreviewclient_test.cpp
@@ -0,0 +1,162 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#include "qmlpreviewclient_test.h"
+#include <qmlpreview/qmlpreviewclient.h>
+#include <qmldebug/qpacketprotocol.h>
+#include <QtTest>
+
+namespace QmlPreview {
+namespace Internal {
+
+class TestableQmlPreviewClient : public QmlPreviewClient
+{
+ Q_OBJECT
+public:
+ QByteArrayList messages;
+
+ explicit TestableQmlPreviewClient() : QmlPreviewClient(nullptr) {}
+ void sendMessage(const QByteArray &message) override
+ {
+ messages.append(message);
+ }
+};
+
+void QmlPreviewClientTest::testLoadFile()
+{
+ TestableQmlPreviewClient client;
+ QUrl url("file:///some/file.qml");
+ client.loadUrl(url);
+ QCOMPARE(client.messages.count(), 1);
+ QmlDebug::QPacket packet(client.dataStreamVersion(), client.messages.takeFirst());
+ qint8 command;
+ QUrl sent;
+ packet >> command >> sent;
+ QCOMPARE(static_cast<QmlPreviewClient::Command>(command), QmlPreviewClient::Load);
+ QCOMPARE(sent, url);
+ QVERIFY(packet.atEnd());
+}
+
+void QmlPreviewClientTest::testAnnounceFile()
+{
+ TestableQmlPreviewClient client;
+ QString file("/some/file.qml");
+ QByteArray contents("tralala");
+ client.announceFile(file, contents);
+ QCOMPARE(client.messages.count(), 1);
+ QmlDebug::QPacket packet(client.dataStreamVersion(), client.messages.takeFirst());
+ qint8 command;
+ QString sentFile;
+ QByteArray sentContents;
+ packet >> command >> sentFile >> sentContents;
+ QCOMPARE(static_cast<QmlPreviewClient::Command>(command), QmlPreviewClient::File);
+ QCOMPARE(sentFile, file);
+ QCOMPARE(sentContents, contents);
+ QVERIFY(packet.atEnd());
+}
+
+void QmlPreviewClientTest::testZoom()
+{
+ TestableQmlPreviewClient client;
+ client.zoom(2.0f);
+ QCOMPARE(client.messages.count(), 1);
+ QmlDebug::QPacket packet(client.dataStreamVersion(), client.messages.takeFirst());
+ qint8 command;
+ float zoomFactor;
+ packet >> command >> zoomFactor;
+ QCOMPARE(static_cast<QmlPreviewClient::Command>(command), QmlPreviewClient::Zoom);
+ QCOMPARE(zoomFactor, 2.0f);
+ QVERIFY(packet.atEnd());
+}
+
+void QmlPreviewClientTest::testLanguate()
+{
+ TestableQmlPreviewClient client;
+ QUrl url("file:///some/file.qml");
+ QString locale("qt_QT");
+ client.language(url, locale);
+ QCOMPARE(client.messages.count(), 1);
+ QmlDebug::QPacket packet(client.dataStreamVersion(), client.messages.takeFirst());
+ qint8 command;
+ QUrl resultUrl;
+ QString resultLocale;
+ packet >> command >> resultUrl >> resultLocale;
+ QCOMPARE(static_cast<QmlPreviewClient::Command>(command), QmlPreviewClient::Language);
+ QCOMPARE(resultUrl, url);
+ QCOMPARE(resultLocale, locale);
+ QVERIFY(packet.atEnd());
+}
+
+void QmlPreviewClientTest::testMessageReceived()
+{
+ TestableQmlPreviewClient client;
+ QString file("/some/file.qml");
+ QString error("wrong things");
+ int numRequests = 0;
+ connect(&client, &QmlPreviewClient::pathRequested, [&](const QString &requestedFile) {
+ QCOMPARE(requestedFile, file);
+ ++numRequests;
+ });
+ int numErrors = 0;
+ connect(&client, &QmlPreviewClient::errorReported, [&](const QString &reportedError) {
+ QCOMPARE(reportedError, error);
+ ++numErrors;
+ });
+ quint16 numFrames = 0;
+ connect(&client, &QmlPreviewClient::fpsReported, [&](const QmlPreviewClient::FpsInfo &frames) {
+ numFrames += frames.numRenders;
+ });
+ {
+ QmlDebug::QPacket packet(client.dataStreamVersion());
+ packet << static_cast<qint8>(QmlPreviewClient::Request) << file;
+ client.messageReceived(packet.data());
+ QCOMPARE(numRequests, 1);
+ QCOMPARE(numErrors, 0);
+ QCOMPARE(numFrames, quint16(0));
+ }
+ {
+ QmlDebug::QPacket packet(client.dataStreamVersion());
+ packet << static_cast<qint8>(QmlPreviewClient::Error) << error;
+ client.messageReceived(packet.data());
+ QCOMPARE(numRequests, 1);
+ QCOMPARE(numErrors, 1);
+ QCOMPARE(numFrames, quint16(0));
+ }
+ {
+ QmlDebug::QPacket packet(client.dataStreamVersion());
+ quint16 frames = 58;
+ packet << static_cast<qint8>(QmlPreviewClient::Fps) << frames << 6 << 7 << 8
+ << frames << 1 << 2 << 3;
+ client.messageReceived(packet.data());
+ QCOMPARE(numRequests, 1);
+ QCOMPARE(numErrors, 1);
+ QCOMPARE(numFrames, frames);
+ }
+}
+
+} // namespace Internal
+} // namespace QmlPreview
+
+#include "qmlpreviewclient_test.moc"
diff --git a/src/plugins/qmlpreview/tests/qmlpreviewclient_test.h b/src/plugins/qmlpreview/tests/qmlpreviewclient_test.h
new file mode 100644
index 0000000000..e5e1ad089e
--- /dev/null
+++ b/src/plugins/qmlpreview/tests/qmlpreviewclient_test.h
@@ -0,0 +1,44 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#include <QObject>
+
+namespace QmlPreview {
+namespace Internal {
+
+class QmlPreviewClientTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void testLoadFile();
+ void testAnnounceFile();
+ void testZoom();
+ void testLanguate();
+ void testMessageReceived();
+};
+
+} // namespace Internal
+} // namespace QmlPreview
diff --git a/src/plugins/qmlpreview/tests/qmlpreviewplugin_test.cpp b/src/plugins/qmlpreview/tests/qmlpreviewplugin_test.cpp
new file mode 100644
index 0000000000..3b84cd4d4f
--- /dev/null
+++ b/src/plugins/qmlpreview/tests/qmlpreviewplugin_test.cpp
@@ -0,0 +1,109 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#include "qmlpreviewplugin_test.h"
+#include <extensionsystem/iplugin.h>
+#include <extensionsystem/pluginmanager.h>
+#include <extensionsystem/pluginspec.h>
+
+#include <QtTest>
+#include <QVariant>
+
+Q_DECLARE_METATYPE(QmlPreview::Internal::TestFileLoader)
+Q_DECLARE_METATYPE(QmlPreview::Internal::TestFpsHandler)
+
+namespace QmlPreview {
+namespace Internal {
+
+QmlPreviewPluginTest::QmlPreviewPluginTest(QObject *parent) : QObject(parent)
+{
+}
+
+static ExtensionSystem::IPlugin *getPlugin()
+{
+ const QList<ExtensionSystem::PluginSpec *> plugins = ExtensionSystem::PluginManager::plugins();
+ auto it = std::find_if(plugins.begin(), plugins.end(), [](ExtensionSystem::PluginSpec *spec) {
+ return spec->name() == "QmlPreview";
+ });
+
+ return (it == plugins.end()) ? nullptr : (*it)->plugin();
+}
+
+void QmlPreviewPluginTest::testFileLoaderProperty()
+{
+ ExtensionSystem::IPlugin *plugin = getPlugin();
+ QVERIFY(plugin);
+
+ QVariant var = plugin->property("fileLoader");
+ TestFileLoader loader = qvariant_cast<TestFileLoader>(var);
+ bool success = true;
+ loader(QString("testzzzztestzzzztest"), &success);
+ QVERIFY(!success);
+}
+
+void QmlPreviewPluginTest::testZoomFactorProperty()
+{
+ ExtensionSystem::IPlugin *plugin = getPlugin();
+ QVERIFY(plugin);
+
+ QSignalSpy spy(plugin, SIGNAL(zoomFactorChanged(float)));
+
+ QCOMPARE(qvariant_cast<float>(plugin->property("zoomFactor")), -1.0f);
+ plugin->setProperty("zoomFactor", 2.0f);
+ QCOMPARE(qvariant_cast<float>(plugin->property("zoomFactor")), 2.0f);
+ plugin->setProperty("zoomFactor", 1.0f);
+ QCOMPARE(qvariant_cast<float>(plugin->property("zoomFactor")), 1.0f);
+ QCOMPARE(spy.count(), 2);
+}
+
+void QmlPreviewPluginTest::testLocaleProperty()
+{
+ ExtensionSystem::IPlugin *plugin = getPlugin();
+ QVERIFY(plugin);
+
+ QSignalSpy spy(plugin, SIGNAL(localeChanged(QString)));
+
+ QCOMPARE(plugin->property("locale").toString(), QString());
+ plugin->setProperty("locale", "de_DE");
+ QCOMPARE(plugin->property("locale").toString(), QString("de_DE"));
+ plugin->setProperty("locale", "qt_QT");
+ QCOMPARE(plugin->property("locale").toString(), QString("qt_QT"));
+ QCOMPARE(spy.count(), 2);
+}
+
+void QmlPreviewPluginTest::testFpsHandlerProperty()
+{
+ ExtensionSystem::IPlugin *plugin = getPlugin();
+ QVERIFY(plugin);
+
+ QVariant var = plugin->property("fpsHandler");
+ TestFpsHandler handler = qvariant_cast<TestFpsHandler>(var);
+ QVERIFY(handler);
+ quint16 stats[] = { 43, 44, 45, 46, 47, 48, 49, 50 };
+ handler(stats);
+}
+
+} // namespace Internal
+} // namespace QmlPreview
diff --git a/src/plugins/qmlpreview/tests/qmlpreviewplugin_test.h b/src/plugins/qmlpreview/tests/qmlpreviewplugin_test.h
new file mode 100644
index 0000000000..ce3cef1dfc
--- /dev/null
+++ b/src/plugins/qmlpreview/tests/qmlpreviewplugin_test.h
@@ -0,0 +1,50 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** 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.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QObject>
+
+namespace QmlPreview {
+namespace Internal {
+
+typedef QByteArray (*TestFileLoader)(const QString &, bool *);
+typedef void (*TestFpsHandler)(quint16[8]);
+
+class QmlPreviewPluginTest : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QmlPreviewPluginTest(QObject *parent = nullptr);
+
+private slots:
+ void testFileLoaderProperty();
+ void testZoomFactorProperty();
+ void testLocaleProperty();
+ void testFpsHandlerProperty();
+};
+
+} // namespace Internal
+} // namespace QmlPreview
diff --git a/src/plugins/qmlpreview/tests/tests.pri b/src/plugins/qmlpreview/tests/tests.pri
new file mode 100644
index 0000000000..131aa34bfd
--- /dev/null
+++ b/src/plugins/qmlpreview/tests/tests.pri
@@ -0,0 +1,7 @@
+SOURCES += \
+ $$PWD/qmlpreviewclient_test.cpp \
+ $$PWD/qmlpreviewplugin_test.cpp
+
+HEADERS += \
+ $$PWD/qmlpreviewclient_test.h \
+ $$PWD/qmlpreviewplugin_test.h