2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2019 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
2019-01-17 10:47:47 +01:00
|
|
|
|
|
|
|
|
#include "qmlpreviewplugin.h"
|
|
|
|
|
#include "qmlpreviewruncontrol.h"
|
|
|
|
|
|
|
|
|
|
#ifdef WITH_TESTS
|
|
|
|
|
#include "tests/qmlpreviewclient_test.h"
|
|
|
|
|
#include "tests/qmlpreviewplugin_test.h"
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-07-23 08:37:37 +02:00
|
|
|
#include <coreplugin/icore.h>
|
2019-01-17 10:47:47 +01:00
|
|
|
#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>
|
2022-06-22 15:43:33 +02:00
|
|
|
#include <projectexplorer/projectexplorerconstants.h>
|
2019-01-17 10:47:47 +01:00
|
|
|
#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>
|
|
|
|
|
|
2020-07-20 20:43:46 +02:00
|
|
|
#include <qmlprojectmanager/qmlmultilanguageaspect.h>
|
2020-07-23 08:37:37 +02:00
|
|
|
|
|
|
|
|
#include <qtsupport/qtkitinformation.h>
|
|
|
|
|
#include <qtsupport/qtversionmanager.h>
|
|
|
|
|
#include <qtsupport/baseqtversion.h>
|
|
|
|
|
|
2021-08-16 13:48:26 +02:00
|
|
|
#include <android/androidconstants.h>
|
|
|
|
|
|
2019-01-17 10:47:47 +01:00
|
|
|
#include <QAction>
|
|
|
|
|
|
|
|
|
|
using namespace ProjectExplorer;
|
|
|
|
|
|
|
|
|
|
namespace QmlPreview {
|
|
|
|
|
|
|
|
|
|
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
|
2019-05-28 13:49:26 +02:00
|
|
|
= Core::DocumentModel::entryForFilePath(Utils::FilePath::fromString(filename))) {
|
2019-01-17 10:47:47 +01:00
|
|
|
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)
|
|
|
|
|
{
|
2021-02-12 12:53:25 +02:00
|
|
|
// We cannot dynamically load changes in qtquickcontrols2.conf
|
|
|
|
|
return !filename.endsWith("qtquickcontrols2.conf");
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void defaultFpsHandler(quint16 frames[8])
|
|
|
|
|
{
|
2020-12-17 10:30:03 +01:00
|
|
|
Core::MessageManager::writeSilently(QString::fromLatin1("QML preview: %1 fps").arg(frames[0]));
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-16 16:47:29 +02:00
|
|
|
static std::unique_ptr<QmlDebugTranslationClient> defaultCreateDebugTranslationClientMethod(QmlDebug::QmlDebugConnection *connection)
|
|
|
|
|
{
|
|
|
|
|
auto client = std::make_unique<QmlPreview::QmlDebugTranslationClient>(connection);
|
|
|
|
|
return client;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
class QmlPreviewPluginPrivate : public QObject
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
public:
|
|
|
|
|
explicit QmlPreviewPluginPrivate(QmlPreviewPlugin *parent);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
QString previewedFile() const;
|
|
|
|
|
void setPreviewedFile(const QString &previewedFile);
|
|
|
|
|
QmlPreviewRunControlList runningPreviews() 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);
|
|
|
|
|
|
|
|
|
|
QmlPreviewPlugin *q = nullptr;
|
|
|
|
|
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;
|
2020-09-28 08:20:23 +02:00
|
|
|
QString m_localeIsoCode;
|
2021-03-25 02:45:40 +01:00
|
|
|
QmlDebugTranslationClientCreator m_createDebugTranslationClientMethod;
|
ProjectExplorer: Standardize RunWorker creation logic
This unifies the remaining paths of RunWorker creation to always
use RunWorkerFactories in the plugin pimpls.
There were, and are, still effectively three basic kinds of workers:
- "toplevel" tools corresponding to the run modes, that are often all
that's used for local runs and directly started via the fat buttons
or e.g. entries in the analyze menu, with factories already previously
located in the plugin pimpls
- core "tool helpers", providing tool specific functionality typically
used in conjunction with a remote device specific run mechanism,
set up via RunControl::registerWorkerCreator
- target/device specific runhelper like port gatherers contructed e.g.
via *Device::workerCreator(Core::Id id)
Worse, these categories are partially overlapping, so it was not
clear how a "clean" setup would look like, instead some ad-hoc cobbling
"to make it work" happened.
In some cases, the runMode id was used throughout the whole ensemble
of run workers for a given run, and which worker exactly was created
depended on which of the mechanism above was used in which order.
With the new central setup, the top-level runmodes remain, but the
second kind gets new ids, so the implicit dependencies on order
of setup mechanism are avoided.
This also helps in the cases where there was previously unclarity of where
and how to set up worker factories: It's always and only the plugin
pimpl now.
Change-Id: Icd9a08e2d53e19abe8b21fe546f469fae353a69f
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2019-08-23 15:31:35 +02:00
|
|
|
|
2023-01-05 13:15:04 +01:00
|
|
|
LocalQmlPreviewSupportFactory localRunWorkerFactory;
|
ProjectExplorer: Standardize RunWorker creation logic
This unifies the remaining paths of RunWorker creation to always
use RunWorkerFactories in the plugin pimpls.
There were, and are, still effectively three basic kinds of workers:
- "toplevel" tools corresponding to the run modes, that are often all
that's used for local runs and directly started via the fat buttons
or e.g. entries in the analyze menu, with factories already previously
located in the plugin pimpls
- core "tool helpers", providing tool specific functionality typically
used in conjunction with a remote device specific run mechanism,
set up via RunControl::registerWorkerCreator
- target/device specific runhelper like port gatherers contructed e.g.
via *Device::workerCreator(Core::Id id)
Worse, these categories are partially overlapping, so it was not
clear how a "clean" setup would look like, instead some ad-hoc cobbling
"to make it work" happened.
In some cases, the runMode id was used throughout the whole ensemble
of run workers for a given run, and which worker exactly was created
depended on which of the mechanism above was used in which order.
With the new central setup, the top-level runmodes remain, but the
second kind gets new ids, so the implicit dependencies on order
of setup mechanism are avoided.
This also helps in the cases where there was previously unclarity of where
and how to set up worker factories: It's always and only the plugin
pimpl now.
Change-Id: Icd9a08e2d53e19abe8b21fe546f469fae353a69f
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2019-08-23 15:31:35 +02:00
|
|
|
|
|
|
|
|
RunWorkerFactory runWorkerFactory{
|
|
|
|
|
[this](RunControl *runControl) {
|
2020-09-28 08:20:23 +02:00
|
|
|
QmlPreviewRunner *runner = new QmlPreviewRunner(QmlPreviewRunnerSetting{
|
|
|
|
|
runControl,
|
|
|
|
|
m_fileLoader,
|
|
|
|
|
m_fileClassifer,
|
|
|
|
|
m_fpsHandler,
|
|
|
|
|
m_zoomFactor,
|
|
|
|
|
m_localeIsoCode,
|
2021-03-25 02:45:40 +01:00
|
|
|
m_createDebugTranslationClientMethod
|
2020-09-28 08:20:23 +02:00
|
|
|
});
|
ProjectExplorer: Standardize RunWorker creation logic
This unifies the remaining paths of RunWorker creation to always
use RunWorkerFactories in the plugin pimpls.
There were, and are, still effectively three basic kinds of workers:
- "toplevel" tools corresponding to the run modes, that are often all
that's used for local runs and directly started via the fat buttons
or e.g. entries in the analyze menu, with factories already previously
located in the plugin pimpls
- core "tool helpers", providing tool specific functionality typically
used in conjunction with a remote device specific run mechanism,
set up via RunControl::registerWorkerCreator
- target/device specific runhelper like port gatherers contructed e.g.
via *Device::workerCreator(Core::Id id)
Worse, these categories are partially overlapping, so it was not
clear how a "clean" setup would look like, instead some ad-hoc cobbling
"to make it work" happened.
In some cases, the runMode id was used throughout the whole ensemble
of run workers for a given run, and which worker exactly was created
depended on which of the mechanism above was used in which order.
With the new central setup, the top-level runmodes remain, but the
second kind gets new ids, so the implicit dependencies on order
of setup mechanism are avoided.
This also helps in the cases where there was previously unclarity of where
and how to set up worker factories: It's always and only the plugin
pimpl now.
Change-Id: Icd9a08e2d53e19abe8b21fe546f469fae353a69f
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2019-08-23 15:31:35 +02:00
|
|
|
connect(q, &QmlPreviewPlugin::updatePreviews,
|
|
|
|
|
runner, &QmlPreviewRunner::loadFile);
|
|
|
|
|
connect(q, &QmlPreviewPlugin::rerunPreviews,
|
|
|
|
|
runner, &QmlPreviewRunner::rerun);
|
|
|
|
|
connect(runner, &QmlPreviewRunner::ready,
|
|
|
|
|
this, &QmlPreviewPluginPrivate::previewCurrentFile);
|
|
|
|
|
connect(q, &QmlPreviewPlugin::zoomFactorChanged,
|
|
|
|
|
runner, &QmlPreviewRunner::zoom);
|
2020-09-28 08:20:23 +02:00
|
|
|
connect(q, &QmlPreviewPlugin::localeIsoCodeChanged,
|
ProjectExplorer: Standardize RunWorker creation logic
This unifies the remaining paths of RunWorker creation to always
use RunWorkerFactories in the plugin pimpls.
There were, and are, still effectively three basic kinds of workers:
- "toplevel" tools corresponding to the run modes, that are often all
that's used for local runs and directly started via the fat buttons
or e.g. entries in the analyze menu, with factories already previously
located in the plugin pimpls
- core "tool helpers", providing tool specific functionality typically
used in conjunction with a remote device specific run mechanism,
set up via RunControl::registerWorkerCreator
- target/device specific runhelper like port gatherers contructed e.g.
via *Device::workerCreator(Core::Id id)
Worse, these categories are partially overlapping, so it was not
clear how a "clean" setup would look like, instead some ad-hoc cobbling
"to make it work" happened.
In some cases, the runMode id was used throughout the whole ensemble
of run workers for a given run, and which worker exactly was created
depended on which of the mechanism above was used in which order.
With the new central setup, the top-level runmodes remain, but the
second kind gets new ids, so the implicit dependencies on order
of setup mechanism are avoided.
This also helps in the cases where there was previously unclarity of where
and how to set up worker factories: It's always and only the plugin
pimpl now.
Change-Id: Icd9a08e2d53e19abe8b21fe546f469fae353a69f
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2019-08-23 15:31:35 +02:00
|
|
|
runner, &QmlPreviewRunner::language);
|
|
|
|
|
|
|
|
|
|
connect(runner, &RunWorker::started, this, [this, runControl] {
|
|
|
|
|
addPreview(runControl);
|
|
|
|
|
});
|
|
|
|
|
connect(runner, &RunWorker::stopped, this, [this, runControl] {
|
|
|
|
|
removePreview(runControl);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return runner;
|
|
|
|
|
},
|
|
|
|
|
{Constants::QML_PREVIEW_RUNNER}
|
|
|
|
|
};
|
2019-08-13 10:57:37 +02:00
|
|
|
};
|
2019-01-17 10:47:47 +01:00
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
QmlPreviewPluginPrivate::QmlPreviewPluginPrivate(QmlPreviewPlugin *parent)
|
|
|
|
|
: q(parent)
|
|
|
|
|
{
|
|
|
|
|
m_fileLoader = &defaultFileLoader;
|
|
|
|
|
m_fileClassifer = &defaultFileClassifier;
|
|
|
|
|
m_fpsHandler = &defaultFpsHandler;
|
2021-04-16 16:47:29 +02:00
|
|
|
m_createDebugTranslationClientMethod = &defaultCreateDebugTranslationClientMethod;
|
2019-01-17 10:47:47 +01:00
|
|
|
|
|
|
|
|
Core::ActionContainer *menu = Core::ActionManager::actionContainer(
|
|
|
|
|
Constants::M_BUILDPROJECT);
|
2019-08-13 10:57:37 +02:00
|
|
|
QAction *action = new QAction(QmlPreviewPlugin::tr("QML Preview"), this);
|
2019-01-17 10:47:47 +01:00
|
|
|
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);
|
2020-07-20 20:45:08 +02:00
|
|
|
connect(action, &QAction::triggered, this, [this]() {
|
|
|
|
|
if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current())
|
2020-09-28 08:20:23 +02:00
|
|
|
m_localeIsoCode = multiLanguageAspect->currentLocale();
|
2021-08-16 13:48:26 +02:00
|
|
|
bool skipDeploy = false;
|
|
|
|
|
const Kit *kit = SessionManager::startupTarget()->kit();
|
|
|
|
|
if (SessionManager::startupTarget() && kit)
|
2021-11-10 19:49:59 +01:00
|
|
|
skipDeploy = kit->supportedPlatforms().contains(Android::Constants::ANDROID_DEVICE_TYPE)
|
|
|
|
|
|| DeviceTypeKitAspect::deviceTypeId(kit) == Android::Constants::ANDROID_DEVICE_TYPE;
|
2021-08-16 13:48:26 +02:00
|
|
|
ProjectExplorerPlugin::runStartupProject(Constants::QML_PREVIEW_RUN_MODE, skipDeploy);
|
2019-01-17 10:47:47 +01:00
|
|
|
});
|
2020-07-23 08:37:37 +02:00
|
|
|
menu->addAction(
|
|
|
|
|
Core::ActionManager::registerAction(action, "QmlPreview.RunPreview"),
|
|
|
|
|
Constants::G_BUILD_RUN);
|
|
|
|
|
|
2019-01-17 10:47:47 +01:00
|
|
|
menu = Core::ActionManager::actionContainer(Constants::M_FILECONTEXT);
|
2019-11-01 12:21:53 +01:00
|
|
|
action = new QAction(QmlPreviewPlugin::tr("Preview File"), this);
|
2019-01-17 10:47:47 +01:00
|
|
|
action->setEnabled(false);
|
2019-08-13 10:57:37 +02:00
|
|
|
connect(q, &QmlPreviewPlugin::runningPreviewsChanged,
|
2019-01-17 10:47:47 +01:00
|
|
|
action, [action](const QmlPreviewRunControlList &previews) {
|
|
|
|
|
action->setEnabled(!previews.isEmpty());
|
|
|
|
|
});
|
2019-08-13 10:57:37 +02:00
|
|
|
connect(action, &QAction::triggered, this, &QmlPreviewPluginPrivate::previewCurrentFile);
|
2020-07-23 08:37:37 +02:00
|
|
|
menu->addAction(
|
|
|
|
|
Core::ActionManager::registerAction(action, "QmlPreview.PreviewFile", Core::Context(Constants::C_PROJECT_TREE)),
|
|
|
|
|
Constants::G_FILE_OTHER);
|
2019-01-17 10:47:47 +01:00
|
|
|
action->setVisible(false);
|
2021-06-18 08:46:38 +02:00
|
|
|
connect(ProjectTree::instance(), &ProjectTree::currentNodeChanged, action, [action](Node *node) {
|
2019-01-17 10:47:47 +01:00
|
|
|
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);
|
2020-01-23 10:10:11 +01:00
|
|
|
connect(&m_parseThread, &QThread::finished, parser, &QObject::deleteLater);
|
2019-08-13 10:57:37 +02:00
|
|
|
connect(q, &QmlPreviewPlugin::checkDocument, parser, &QmlPreviewParser::parse);
|
|
|
|
|
connect(q, &QmlPreviewPlugin::previewedFileChanged, this, &QmlPreviewPluginPrivate::checkFile);
|
|
|
|
|
connect(parser, &QmlPreviewParser::success, this, &QmlPreviewPluginPrivate::triggerPreview);
|
2019-01-17 10:47:47 +01:00
|
|
|
|
|
|
|
|
attachToEditor();
|
2019-08-13 10:57:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QmlPreviewPlugin::~QmlPreviewPlugin()
|
|
|
|
|
{
|
|
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QmlPreviewPlugin::initialize(const QStringList &arguments, QString *errorString)
|
|
|
|
|
{
|
|
|
|
|
Q_UNUSED(arguments)
|
|
|
|
|
Q_UNUSED(errorString)
|
|
|
|
|
|
|
|
|
|
d = new QmlPreviewPluginPrivate(this);
|
|
|
|
|
|
2019-01-17 10:47:47 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExtensionSystem::IPlugin::ShutdownFlag QmlPreviewPlugin::aboutToShutdown()
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
d->m_parseThread.quit();
|
|
|
|
|
d->m_parseThread.wait();
|
2019-01-17 10:47:47 +01:00
|
|
|
return SynchronousShutdown;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-27 14:12:11 +02:00
|
|
|
QVector<QObject *> QmlPreviewPlugin::createTestObjects() const
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
2019-05-27 14:12:11 +02:00
|
|
|
QVector<QObject *> tests;
|
2019-01-17 10:47:47 +01:00
|
|
|
#ifdef WITH_TESTS
|
|
|
|
|
tests.append(new QmlPreviewClientTest);
|
|
|
|
|
tests.append(new QmlPreviewPluginTest);
|
|
|
|
|
#endif
|
|
|
|
|
return tests;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QmlPreviewPlugin::previewedFile() const
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
return d->m_previewedFile;
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QmlPreviewPlugin::setPreviewedFile(const QString &previewedFile)
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
if (d->m_previewedFile == previewedFile)
|
2019-01-17 10:47:47 +01:00
|
|
|
return;
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
d->m_previewedFile = previewedFile;
|
|
|
|
|
emit previewedFileChanged(d->m_previewedFile);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QmlPreviewRunControlList QmlPreviewPlugin::runningPreviews() const
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
return d->m_runningPreviews;
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QmlPreviewFileLoader QmlPreviewPlugin::fileLoader() const
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
return d->m_fileLoader;
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QmlPreviewFileClassifier QmlPreviewPlugin::fileClassifier() const
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
return d->m_fileClassifer;
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QmlPreviewPlugin::setFileClassifier(QmlPreviewFileClassifier fileClassifer)
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
if (d->m_fileClassifer == fileClassifer)
|
2019-01-17 10:47:47 +01:00
|
|
|
return;
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
d->m_fileClassifer = fileClassifer;
|
|
|
|
|
emit fileClassifierChanged(d->m_fileClassifer);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float QmlPreviewPlugin::zoomFactor() const
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
return d->m_zoomFactor;
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QmlPreviewPlugin::setZoomFactor(float zoomFactor)
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
if (d->m_zoomFactor == zoomFactor)
|
2019-01-17 10:47:47 +01:00
|
|
|
return;
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
d->m_zoomFactor = zoomFactor;
|
|
|
|
|
emit zoomFactorChanged(d->m_zoomFactor);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QmlPreviewFpsHandler QmlPreviewPlugin::fpsHandler() const
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
return d->m_fpsHandler;
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QmlPreviewPlugin::setFpsHandler(QmlPreviewFpsHandler fpsHandler)
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
if (d->m_fpsHandler == fpsHandler)
|
2019-01-17 10:47:47 +01:00
|
|
|
return;
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
d->m_fpsHandler = fpsHandler;
|
|
|
|
|
emit fpsHandlerChanged(d->m_fpsHandler);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 08:20:23 +02:00
|
|
|
QString QmlPreviewPlugin::localeIsoCode() const
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
2020-09-28 08:20:23 +02:00
|
|
|
return d->m_localeIsoCode;
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
2020-09-28 08:20:23 +02:00
|
|
|
void QmlPreviewPlugin::setLocaleIsoCode(const QString &localeIsoCode)
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
2020-07-20 20:45:08 +02:00
|
|
|
if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current())
|
2020-09-28 08:20:23 +02:00
|
|
|
multiLanguageAspect->setCurrentLocale(localeIsoCode);
|
|
|
|
|
if (d->m_localeIsoCode == localeIsoCode)
|
2019-01-17 10:47:47 +01:00
|
|
|
return;
|
|
|
|
|
|
2020-09-28 08:20:23 +02:00
|
|
|
d->m_localeIsoCode = localeIsoCode;
|
|
|
|
|
emit localeIsoCodeChanged(d->m_localeIsoCode);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
2021-03-25 02:45:40 +01:00
|
|
|
void QmlPreviewPlugin::setQmlDebugTranslationClientCreator(QmlDebugTranslationClientCreator creator)
|
2020-07-23 08:37:37 +02:00
|
|
|
{
|
2021-03-25 02:45:40 +01:00
|
|
|
d->m_createDebugTranslationClientMethod = creator;
|
2020-07-23 08:37:37 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-17 10:47:47 +01:00
|
|
|
void QmlPreviewPlugin::setFileLoader(QmlPreviewFileLoader fileLoader)
|
|
|
|
|
{
|
2019-08-13 10:57:37 +02:00
|
|
|
if (d->m_fileLoader == fileLoader)
|
2019-01-17 10:47:47 +01:00
|
|
|
return;
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
d->m_fileLoader = fileLoader;
|
|
|
|
|
emit fileLoaderChanged(d->m_fileLoader);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
void QmlPreviewPluginPrivate::previewCurrentFile()
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
2019-04-30 12:58:33 +02:00
|
|
|
const Node *currentNode = ProjectTree::currentNode();
|
2019-02-28 17:19:18 +01:00
|
|
|
if (!currentNode || !currentNode->asFileNode()
|
2019-01-17 10:47:47 +01:00
|
|
|
|| currentNode->asFileNode()->fileType() != FileType::QML)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const QString file = currentNode->filePath().toString();
|
|
|
|
|
if (file != m_previewedFile)
|
2019-08-13 10:57:37 +02:00
|
|
|
q->setPreviewedFile(file);
|
2019-01-17 10:47:47 +01:00
|
|
|
else
|
|
|
|
|
checkFile(file);
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
void QmlPreviewPluginPrivate::onEditorChanged(Core::IEditor *editor)
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
|
|
|
|
if (m_lastEditor) {
|
|
|
|
|
Core::IDocument *doc = m_lastEditor->document();
|
2019-08-13 10:57:37 +02:00
|
|
|
disconnect(doc, &Core::IDocument::contentsChanged, this, &QmlPreviewPluginPrivate::setDirty);
|
2019-01-17 10:47:47 +01:00
|
|
|
if (m_dirty) {
|
|
|
|
|
m_dirty = false;
|
|
|
|
|
checkEditor();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_lastEditor = editor;
|
|
|
|
|
if (m_lastEditor) {
|
|
|
|
|
// Handle new editor
|
|
|
|
|
connect(m_lastEditor->document(), &Core::IDocument::contentsChanged,
|
2019-08-13 10:57:37 +02:00
|
|
|
this, &QmlPreviewPluginPrivate::setDirty);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
void QmlPreviewPluginPrivate::onEditorAboutToClose(Core::IEditor *editor)
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
|
|
|
|
if (m_lastEditor != editor)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Oh no our editor is going to be closed
|
|
|
|
|
// get the content first
|
|
|
|
|
Core::IDocument *doc = m_lastEditor->document();
|
2019-08-13 10:57:37 +02:00
|
|
|
disconnect(doc, &Core::IDocument::contentsChanged, this, &QmlPreviewPluginPrivate::setDirty);
|
2019-01-17 10:47:47 +01:00
|
|
|
if (m_dirty) {
|
|
|
|
|
m_dirty = false;
|
|
|
|
|
checkEditor();
|
|
|
|
|
}
|
|
|
|
|
m_lastEditor = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
void QmlPreviewPluginPrivate::setDirty()
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
|
|
|
|
m_dirty = true;
|
|
|
|
|
QTimer::singleShot(1000, this, [this](){
|
|
|
|
|
if (m_dirty && m_lastEditor) {
|
|
|
|
|
m_dirty = false;
|
|
|
|
|
checkEditor();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
void QmlPreviewPluginPrivate::addPreview(ProjectExplorer::RunControl *preview)
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
|
|
|
|
m_runningPreviews.append(preview);
|
2019-08-13 10:57:37 +02:00
|
|
|
emit q->runningPreviewsChanged(m_runningPreviews);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
void QmlPreviewPluginPrivate::removePreview(ProjectExplorer::RunControl *preview)
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
|
|
|
|
m_runningPreviews.removeOne(preview);
|
2019-08-13 10:57:37 +02:00
|
|
|
emit q->runningPreviewsChanged(m_runningPreviews);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
void QmlPreviewPluginPrivate::attachToEditor()
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
|
|
|
|
Core::EditorManager *editorManager = Core::EditorManager::instance();
|
|
|
|
|
connect(editorManager, &Core::EditorManager::currentEditorChanged,
|
2019-08-13 10:57:37 +02:00
|
|
|
this, &QmlPreviewPluginPrivate::onEditorChanged);
|
2019-01-17 10:47:47 +01:00
|
|
|
connect(editorManager, &Core::EditorManager::editorAboutToClose,
|
2019-08-13 10:57:37 +02:00
|
|
|
this, &QmlPreviewPluginPrivate::onEditorAboutToClose);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
void QmlPreviewPluginPrivate::checkEditor()
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
|
|
|
|
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;
|
2019-08-13 10:57:37 +02:00
|
|
|
emit q->checkDocument(doc->filePath().toString(), doc->contents(), dialect);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
void QmlPreviewPluginPrivate::checkFile(const QString &fileName)
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
|
|
|
|
if (!m_fileLoader)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
bool success = false;
|
|
|
|
|
const QByteArray contents = m_fileLoader(fileName, &success);
|
|
|
|
|
|
|
|
|
|
if (success) {
|
2022-06-20 12:35:13 +02:00
|
|
|
emit q->checkDocument(fileName,
|
|
|
|
|
contents,
|
|
|
|
|
QmlJS::ModelManagerInterface::guessLanguageOfFile(
|
|
|
|
|
Utils::FilePath::fromUserInput(fileName))
|
|
|
|
|
.dialect());
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-13 10:57:37 +02:00
|
|
|
void QmlPreviewPluginPrivate::triggerPreview(const QString &changedFile, const QByteArray &contents)
|
2019-01-17 10:47:47 +01:00
|
|
|
{
|
|
|
|
|
if (m_previewedFile.isEmpty())
|
|
|
|
|
previewCurrentFile();
|
|
|
|
|
else
|
2019-08-13 10:57:37 +02:00
|
|
|
emit q->updatePreviews(m_previewedFile, changedFile, contents);
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QmlPreviewParser::QmlPreviewParser()
|
|
|
|
|
{
|
|
|
|
|
static const int dialectMeta = qRegisterMetaType<QmlJS::Dialect::Enum>();
|
2019-07-23 10:58:00 +02:00
|
|
|
Q_UNUSED(dialectMeta)
|
2019-01-17 10:47:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QmlPreviewParser::parse(const QString &name, const QByteArray &contents,
|
|
|
|
|
QmlJS::Dialect::Enum dialect)
|
|
|
|
|
{
|
|
|
|
|
if (!QmlJS::Dialect(dialect).isQmlLikeOrJsLanguage()) {
|
|
|
|
|
emit success(name, contents);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-20 12:35:13 +02:00
|
|
|
QmlJS::Document::MutablePtr qmljsDoc = QmlJS::Document::create(Utils::FilePath::fromString(name),
|
|
|
|
|
dialect);
|
2019-01-17 10:47:47 +01:00
|
|
|
qmljsDoc->setSource(QString::fromUtf8(contents));
|
|
|
|
|
if (qmljsDoc->parse())
|
|
|
|
|
emit success(name, contents);
|
|
|
|
|
else
|
|
|
|
|
emit failure();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace QmlPreview
|
|
|
|
|
|
|
|
|
|
#include <qmlpreviewplugin.moc>
|