aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSamuli Piippo <samuli.piippo@qt.io>2018-12-05 15:07:18 +0200
committerSamuli Piippo <samuli.piippo@qt.io>2018-12-05 15:07:43 +0200
commit329a996f6a52494ef677107593a0146c71a2880a (patch)
treec191128601f69090fe40e730a330d20ac0cf3e3e
parent80d6c28c692c466fbc819ec3b327f3cd24492834 (diff)
parent8f344fdc478cd18a935adc41f9ef31b1495efad3 (diff)
Merge remote-tracking branch 'origin/5.11' into 5.12
* origin/5.11: Add *.qmlc to .gitignore Fix examples installation path QmlLive project concept Update docs for release 5.11 Bench: Use temporary QCoreApplication instance for arguments parsing Allow to limit the number of watched directories HostModel: Fix unintended fallthrough Improve log output with blocking connect Use user-specific keys for IPC resources Bench: Fix warning about hidden setModel Bench: Destroy main window on exit Bench: Fix About dialog title Bench: Fix assertion failure on double click in directory preview Bench: Fix locating previewGenerator on Windows Change-Id: Ib416b116b4fdf475eb35e7d8fba985b6d6fb9026
-rw-r--r--.gitignore1
-rw-r--r--.qmake.conf2
-rw-r--r--doc/concepts.qdoc6
-rw-r--r--doc/installation.qdoc3
-rw-r--r--doc/qmllive-project.qdocconf3
-rw-r--r--examples/examples.pro3
-rwxr-xr-xqmllive.pri2
-rw-r--r--src/bench/aboutdialog.cpp2
-rw-r--r--src/bench/bench.pro6
-rw-r--r--src/bench/benchlivenodeengine.cpp2
-rw-r--r--src/bench/hostmanager.h2
-rw-r--r--src/bench/hostmodel.cpp10
-rw-r--r--src/bench/main.cpp97
-rw-r--r--src/bench/mainwindow.cpp159
-rw-r--r--src/bench/mainwindow.h14
-rw-r--r--src/bench/newprojectwizard.cpp246
-rw-r--r--src/bench/newprojectwizard.h106
-rw-r--r--src/bench/options.cpp11
-rw-r--r--src/bench/options.h4
-rw-r--r--src/bench/qmlpreviewadapter.cpp2
-rw-r--r--src/livehubengine.cpp74
-rw-r--r--src/livehubengine.h15
-rw-r--r--src/previewGenerator/main.cpp27
-rw-r--r--src/projectmanager.cpp160
-rw-r--r--src/projectmanager.h63
-rw-r--r--src/remotereceiver.cpp11
-rw-r--r--src/src.pri6
-rw-r--r--src/watcher.cpp108
-rw-r--r--src/watcher.h17
-rw-r--r--src/widgets/workspaceview.cpp2
30 files changed, 1090 insertions, 74 deletions
diff --git a/.gitignore b/.gitignore
index ae0c341..0e242bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,4 @@ branch-coverage
*.so
*.pc
.qmake.stash
+*.qmlc
diff --git a/.qmake.conf b/.qmake.conf
index 03d3435..10f313d 100644
--- a/.qmake.conf
+++ b/.qmake.conf
@@ -2,4 +2,4 @@ SOURCE_DIR=$$PWD
BUILD_DIR=$$shadowed($$PWD)
QMAKEFEATURES=$$SOURCE_DIR/qmake-features
-VERSION = 1.0.0
+VERSION = 1.1.0
diff --git a/doc/concepts.qdoc b/doc/concepts.qdoc
index 6117265..b67e59c 100644
--- a/doc/concepts.qdoc
+++ b/doc/concepts.qdoc
@@ -69,11 +69,11 @@ For a local session you only need the QmlLive Bench. It contains all of the
required components in an easy-to-use interface. You type and save, we show
you the outcome on your PC in a fraction of a second. This is ideally suited
for a multi-monitor setup where you see your code on one display and the
-the live results of your changes on the other display. Seamless user
-interface creation is the target, where you see every change immediately. This
+live results of your changes on the other display. Seamless user interface
+creation is the target, where you see every change immediately. This
is great for sketching out a scene or getting the last touch on animation
behavior. It also encourages you to think in terms of components; instead of
-developing a whole scene, you can can break apart the scene into smaller parts
+developing a whole scene, you can break apart the scene into smaller parts
or components. You can work on these small components and see how they look
standalone or embedded into a larger scene.
diff --git a/doc/installation.qdoc b/doc/installation.qdoc
index c25a8cb..061e514 100644
--- a/doc/installation.qdoc
+++ b/doc/installation.qdoc
@@ -41,7 +41,6 @@
\li Qt5.4 or higher
\endlist
-
\section1 Building for desktop
\code
@@ -134,7 +133,7 @@ The following custom qmake variables are recognized:
\row
\li EXAMPLES_PREFIX
\li Installation prefix for examples. Defaults to
- \c{$$[QT_INSTALL_LIBS]/qmllive/examples}.
+ \c{$$[QT_INSTALL_EXAMPLES]/qmllive}.
\row
\li QMLLIVE_VERSION_EXTRA
diff --git a/doc/qmllive-project.qdocconf b/doc/qmllive-project.qdocconf
index 5744d9f..deab2e4 100644
--- a/doc/qmllive-project.qdocconf
+++ b/doc/qmllive-project.qdocconf
@@ -37,5 +37,6 @@ qhp.QmlLive.subprojects.manual.title = Qt QmlLive
qhp.QmlLive.subprojects.manual.indexTitle = Qt QmlLive
qhp.QmlLive.subprojects.manual.type = manual
-navigation.homepage = "Qt QmlLive"
+navigation.homepage = "Qt Automotive Suite"
+navigation.landingpage = "Qt QmlLive"
buildversion = "Qt QmlLive $QT_VERSION"
diff --git a/examples/examples.pro b/examples/examples.pro
index 202f1f1..b288ed9 100644
--- a/examples/examples.pro
+++ b/examples/examples.pro
@@ -1,3 +1,2 @@
TEMPLATE = subdirs
-SUBDIRS = \
- app \
+SUBDIRS += app
diff --git a/qmllive.pri b/qmllive.pri
index 6487905..1494509 100755
--- a/qmllive.pri
+++ b/qmllive.pri
@@ -4,7 +4,7 @@ android|ios|qnx {
}
isEmpty(PREFIX): PREFIX = $$[QT_INSTALL_PREFIX]
-isEmpty(EXAMPLES_PREFIX): EXAMPLES_PREFIX = $$[QT_INSTALL_LIBS]/qmllive/examples
+isEmpty(EXAMPLES_PREFIX): EXAMPLES_PREFIX = $$[QT_INSTALL_EXAMPLES]/qmllive
VERSIONS = $$split(VERSION, ".")
VERSION_MAJOR = $$member(VERSIONS, 0)
diff --git a/src/bench/aboutdialog.cpp b/src/bench/aboutdialog.cpp
index c9c37bf..fdae1f7 100644
--- a/src/bench/aboutdialog.cpp
+++ b/src/bench/aboutdialog.cpp
@@ -41,7 +41,7 @@
AboutDialog::AboutDialog(QWidget *parent)
: QDialog(parent)
{
- setWindowTitle(tr("About Qt QML Live"));
+ setWindowTitle(tr("About Qt QmlLive"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
QVBoxLayout *layout = new QVBoxLayout(this);
diff --git a/src/bench/bench.pro b/src/bench/bench.pro
index d6a94ae..038da41 100644
--- a/src/bench/bench.pro
+++ b/src/bench/bench.pro
@@ -27,7 +27,8 @@ SOURCES += \
importpathoptionpage.cpp \
hostdiscoverymanager.cpp \
autodiscoveryhostsdialog.cpp \
- options.cpp
+ options.cpp \
+ newprojectwizard.cpp
HEADERS += \
aboutdialog.h \
@@ -48,7 +49,8 @@ HEADERS += \
httpproxyoptionpage.h \
hostdiscoverymanager.h \
autodiscoveryhostsdialog.h \
- options.h
+ options.h \
+ newprojectwizard.h
FORMS += \
optionsdialog.ui \
diff --git a/src/bench/benchlivenodeengine.cpp b/src/bench/benchlivenodeengine.cpp
index 30dd5b5..b671062 100644
--- a/src/bench/benchlivenodeengine.cpp
+++ b/src/bench/benchlivenodeengine.cpp
@@ -110,7 +110,7 @@ void BenchLiveNodeEngine::initPlugins()
//This needs to be QueuedConnection because Qt5 doesn't like it to destruct it's object while it is in a signalHandler
connect(adapter, &DirectoryPreviewAdapter::loadDocument,
this, [this](const QString &document) {
- m_workspaceView->activateDocument(LiveDocument(document));
+ m_workspaceView->activateDocument(LiveDocument::resolve(workspace(), document));
},
Qt::QueuedConnection);
}
diff --git a/src/bench/hostmanager.h b/src/bench/hostmanager.h
index 68f241e..11978c0 100644
--- a/src/bench/hostmanager.h
+++ b/src/bench/hostmanager.h
@@ -68,6 +68,8 @@ private slots:
void addHost(int index);
private:
+ using QListView::setModel;
+
QPointer<LiveHubEngine> m_engine;
HostModel* m_model;
diff --git a/src/bench/hostmodel.cpp b/src/bench/hostmodel.cpp
index 98c102e..2d9418d 100644
--- a/src/bench/hostmodel.cpp
+++ b/src/bench/hostmodel.cpp
@@ -84,11 +84,11 @@ QVariant HostModel::headerData(int section, Qt::Orientation orientation, int rol
return QVariant();
switch (section) {
- case 0: if (role == Qt::DisplayRole) return QVariant();
- case 1: if (role == Qt::DisplayRole) return QString("Name");
- case 2: if (role == Qt::DisplayRole) return QString("Version");
- case 3: if (role == Qt::DisplayRole) return QString("System");
- case 4: if (role == Qt::DisplayRole) return QString("Ip");
+ case 0: if (role == Qt::DisplayRole) return QVariant(); break;
+ case 1: if (role == Qt::DisplayRole) return QString("Name"); break;
+ case 2: if (role == Qt::DisplayRole) return QString("Version"); break;
+ case 3: if (role == Qt::DisplayRole) return QString("System"); break;
+ case 4: if (role == Qt::DisplayRole) return QString("Ip"); break;
}
return QVariant();
diff --git a/src/bench/main.cpp b/src/bench/main.cpp
index 728cf9b..4a2f2ea 100644
--- a/src/bench/main.cpp
+++ b/src/bench/main.cpp
@@ -36,6 +36,7 @@
#include "hostmanager.h"
#include "hostmodel.h"
+#include "livehubengine.h"
#include "options.h"
#include "mainwindow.h"
#include "qmllive_version.h"
@@ -46,6 +47,7 @@ class Application : public QApplication
public:
static Application *create(int &argc, char **argv);
+ ~Application() override;
protected:
Application(int &argc, char **argv);
@@ -53,7 +55,14 @@ protected:
QString serverName() const;
void setDarkStyle();
- void parseArguments(const QStringList &arguments, Options *options);
+ static void parseArguments(const QStringList &arguments, Options *options);
+ static const Options *options() { return s_options; }
+
+private:
+ static QString userName();
+
+private:
+ static Options *s_options;
};
class MasterApplication : public Application
@@ -62,6 +71,7 @@ class MasterApplication : public Application
public:
MasterApplication(int &argc, char **argv);
+ ~MasterApplication();
private:
void listenForArguments();
@@ -83,16 +93,18 @@ private:
void forwardArguments();
};
+Options *Application::s_options = 0;
+
Application *Application::create(int &argc, char **argv)
{
setApplicationName("QmlLiveBench");
setOrganizationDomain(QLatin1String(QMLLIVE_ORGANIZATION_DOMAIN));
setOrganizationName(QLatin1String(QMLLIVE_ORGANIZATION_NAME));
- // Workaround: Cannot use QCoreApplication::arguments before app is instantiated
- const bool hasNoRemoteOption = argc >= 2 && QString::fromLocal8Bit(argv[1]) == QLatin1String("--noremote");
+ // Cannot instantiate the actual application yet
+ parseArguments(QCoreApplication(argc, argv).arguments(), s_options = new Options);
- if (hasNoRemoteOption || isMaster())
+ if (isMaster())
return new MasterApplication(argc, argv);
else
return new SlaveApplication(argc, argv);
@@ -107,10 +119,19 @@ Application::Application(int &argc, char **argv)
setDarkStyle();
}
+Application::~Application()
+{
+ delete s_options, s_options = 0;
+}
+
bool Application::isMaster()
{
Q_ASSERT(!applicationName().isEmpty());
Q_ASSERT(!organizationDomain().isEmpty() || !organizationName().isEmpty());
+ Q_ASSERT(s_options);
+
+ if (s_options->noRemote())
+ return true;
static QSharedMemory *lock = 0;
static bool retv = false;
@@ -118,9 +139,10 @@ bool Application::isMaster()
if (lock != 0)
return retv;
- const QString key = QString::fromLatin1("%1.%2-lock")
+ const QString key = QString::fromLatin1("%1.%2-%3-lock")
.arg(organizationDomain().isEmpty() ? organizationName() : organizationDomain())
- .arg(applicationName());
+ .arg(applicationName())
+ .arg(userName());
lock = new QSharedMemory(key, qApp);
@@ -145,9 +167,10 @@ QString Application::serverName() const
Q_ASSERT(!applicationName().isEmpty());
Q_ASSERT(!organizationDomain().isEmpty() || !organizationName().isEmpty());
- return QString::fromLatin1("%1.%2-app")
+ return QString::fromLatin1("%1.%2-%3-app")
.arg(organizationDomain().isEmpty() ? organizationName() : organizationDomain())
- .arg(applicationName());
+ .arg(applicationName())
+ .arg(userName());
}
void Application::setDarkStyle()
@@ -199,12 +222,14 @@ void Application::parseArguments(const QStringList &arguments, Options *options)
"(implies --remoteonly)", "name");
parser.addOption(probeHostOption);
QCommandLineOption noRemoteOption("noremote", "do not try to talk to a running bench, do not listen for remote "
- "connections. It MUST BE the VERY FIRST argument on command line.");
+ "connections.");
parser.addOption(noRemoteOption);
QCommandLineOption remoteOnlyOption("remoteonly", "talk to a running bench, do nothing if none is running.");
parser.addOption(remoteOnlyOption);
QCommandLineOption pingOption("ping", "just check if there is a bench running and accepting remote connections.");
parser.addOption(pingOption);
+ QCommandLineOption maxWatchesOption("maxdirwatch", "limit the number of directories to watch for changes", "number", QString::number(options->maximumWatches()));
+ parser.addOption(maxWatchesOption);
parser.process(arguments);
@@ -231,6 +256,15 @@ void Application::parseArguments(const QStringList &arguments, Options *options)
parser.showHelp(-1);
}
+ if (parser.isSet(maxWatchesOption)) {
+ bool ok;
+ int value = parser.value(maxWatchesOption).toInt(&ok);
+ if (!ok) {
+ qWarning() << "Invalid argument to --maxdirwatch option";
+ parser.showHelp(-1);
+ }
+ options->setMaximumWatches(value);
+ }
options->setPluginPath(parser.value(pluginPathOption));
options->setImportPaths(parser.values(importPathOption));
@@ -302,6 +336,22 @@ void Application::parseArguments(const QStringList &arguments, Options *options)
}
}
+QString Application::userName()
+{
+ QString retv;
+
+#if defined(Q_OS_UNIX)
+ retv = QString::fromLocal8Bit(qgetenv("USER"));
+#elif defined(Q_OS_WIN)
+ retv = QString::fromLocal8Bit(qgetenv("USERNAME"));
+#endif
+
+ if (retv.isEmpty())
+ qWarning("Failed to determine system user name");
+
+ return retv;
+}
+
/*
* class MasterApplication
*/
@@ -310,31 +360,33 @@ MasterApplication::MasterApplication(int &argc, char **argv)
: Application(argc, argv)
, m_window(new MainWindow)
{
- Options options;
- parseArguments(arguments(), &options);
-
- if (options.ping()) {
+ if (options()->ping()) {
QTimer::singleShot(0, [] { QCoreApplication::exit(1); });
return;
}
- if (options.remoteOnly()) {
+ if (options()->remoteOnly()) {
QTimer::singleShot(0, this, &QCoreApplication::quit);
return;
}
- applyOptions(options);
+ applyOptions(*options());
- if (options.hasNoninteractiveOptions()) {
+ if (options()->hasNoninteractiveOptions()) {
QTimer::singleShot(0, this, &QCoreApplication::quit);
} else {
m_window->init();
m_window->show();
- if (!options.noRemote())
+ if (!options()->noRemote())
listenForArguments();
}
}
+MasterApplication::~MasterApplication()
+{
+ delete m_window;
+}
+
void MasterApplication::listenForArguments()
{
QLocalServer *server = new QLocalServer(this);
@@ -390,6 +442,8 @@ void MasterApplication::listenForArguments()
void MasterApplication::applyOptions(const Options &options)
{
+ LiveHubEngine::setMaximumWatches(options.maximumWatches());
+
if (!options.workspace().isEmpty())
m_window->setWorkspace(QDir(options.workspace()).absolutePath(), false);
@@ -474,18 +528,15 @@ void MasterApplication::applyOptions(const Options &options)
SlaveApplication::SlaveApplication(int &argc, char **argv)
: Application(argc, argv)
{
- Options options;
- parseArguments(arguments(), &options);
-
- if (options.ping()) {
+ if (options()->ping()) {
QTimer::singleShot(0, &QCoreApplication::quit);
return;
}
- if (!options.remoteOnly() && !options.hasNoninteractiveOptions())
+ if (!options()->remoteOnly() && !options()->hasNoninteractiveOptions())
qInfo() << "Another instance running. Activating...";
- warnAboutIgnoredOptions(options);
+ warnAboutIgnoredOptions(*options());
forwardArguments();
}
diff --git a/src/bench/mainwindow.cpp b/src/bench/mainwindow.cpp
index 61924aa..982d962 100644
--- a/src/bench/mainwindow.cpp
+++ b/src/bench/mainwindow.cpp
@@ -48,7 +48,49 @@
#include "allhostswidget.h"
#include "hostdiscoverymanager.h"
#include "options.h"
+#include "newprojectwizard.h"
+#include "projectmanager.h"
+class ErrorBar : public QFrame
+{
+ Q_OBJECT
+
+public:
+ ErrorBar(QWidget *parent = nullptr)
+ : QFrame(parent)
+ {
+ setFrameShape(QFrame::StyledPanel);
+ setAutoFillBackground(true);
+ QPalette p = palette();
+ p.setColor(QPalette::Window, Qt::red);
+ setPalette(p);
+
+ auto layout = new QHBoxLayout(this);
+ layout->setContentsMargins(4, 4, 4, 4);
+
+ m_label = new QLabel;
+ m_label->setWordWrap(true);
+ layout->addWidget(m_label);
+
+ auto button = new QToolButton;
+ button->setAutoRaise(true);
+ button->setIcon(QIcon(":images/refresh.svg"));
+ connect(button, &QAbstractButton::clicked, this, &ErrorBar::retry);
+ layout->addWidget(button);
+ }
+
+ void setError(const QString &errorString)
+ {
+ m_label->setText(errorString);
+ setVisible(!errorString.isEmpty());
+ }
+
+signals:
+ void retry();
+
+private:
+ QLabel *m_label;
+};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
@@ -61,6 +103,8 @@ MainWindow::MainWindow(QWidget *parent)
, m_allHosts(new AllHostsWidget(this))
, m_hub(new LiveHubEngine(this))
, m_node(new BenchLiveNodeEngine(this))
+ , m_newProjectWizard(new NewProjectWizard(this))
+ , m_projectManager(new ProjectManager(this))
{
setupContent();
setupMenuBar();
@@ -88,6 +132,7 @@ MainWindow::MainWindow(QWidget *parent)
connect(m_allHosts, &AllHostsWidget::refreshAll, m_hostManager, &HostManager::refreshAll);
connect(m_hostManager, &HostManager::logWidgetAdded, this, &MainWindow::onLogWidgetAdded);
connect(m_hostManager, &HostManager::openHostConfig, this, &MainWindow::openPreferences);
+ connect(m_newProjectWizard, &NewProjectWizard::accepted, this, &MainWindow::newProject);
m_qmlDefaultimportList = m_node->qmlEngine()->importPathList();
}
@@ -132,9 +177,42 @@ void MainWindow::setupWorkspaceView()
{
m_workspaceDock = new QDockWidget("Workspace", this);
m_workspaceDock->setObjectName("workspace");
- m_workspaceDock->setWidget(m_workspace);
m_workspaceDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
m_workspaceDock->setFeatures(QDockWidget::AllDockWidgetFeatures);
+
+ auto contents = new QWidget;
+ auto layout = new QVBoxLayout(contents);
+ layout->setContentsMargins(1, 1, 1, 1);
+ layout->setSpacing(1);
+
+ auto errorBar = new ErrorBar;
+ layout->addWidget(errorBar);
+
+ auto updateErrorBar = [this, errorBar]() {
+ QString error;
+ switch (m_hub->error()) {
+ case LiveHubEngine::NoError:
+ break;
+ case LiveHubEngine::WatcherMaximumReached:
+ error = tr("Unable to monitor file changes: The configured limit of %1 directories was exceeded.")
+ .arg(LiveHubEngine::maximumWatches());
+ break;
+ case LiveHubEngine::WatcherSystemError:
+ error = tr("Unable to monitor file changes. System limit exceeded?");
+ break;
+ }
+ errorBar->setError(error);
+ };
+ updateErrorBar();
+ connect(m_hub, &LiveHubEngine::errorChanged, errorBar, updateErrorBar);
+
+ connect(errorBar, &ErrorBar::retry, this, [this]() {
+ m_hub->setWorkspace(m_hub->workspace());
+ });
+
+ layout->addWidget(m_workspace);
+
+ m_workspaceDock->setWidget(contents);
addDockWidget(Qt::LeftDockWidgetArea, m_workspaceDock);
}
@@ -177,10 +255,24 @@ void MainWindow::setupMenuBar()
{
QMenu *file = menuBar()->addMenu(tr("&File"));
#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
- m_openWorkspace = file->addAction(QIcon::fromTheme("folder-open"), tr("&Open Workspace..."), this, SLOT(openWorkspace()), QKeySequence::Open);
+ m_createProject = file->addAction(QIcon::fromTheme("folder-new"), tr("&New Project"), this, SLOT(newProject()), QKeySequence::New);
+#else
+ m_createProject = file->addAction(QIcon::fromTheme("folder-new"), tr("&New Project"),
+ this, &MainWindow::newProjectWizard, QKeySequence::New);
+#endif
+
+#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
+ m_openProject = file->addAction(QIcon::fromTheme("folder-open"), tr("&Open Project..."), this, SLOT(openProject()), QKeySequence::Open);
+#else
+ m_openProject = file->addAction(QIcon::fromTheme("folder-open"), tr("&Open Project..."),
+ this, &MainWindow::openProject, QKeySequence::Open);
+#endif
+
+#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
+ m_openWorkspace = file->addAction(QIcon::fromTheme("folder-open"), tr("&Open Workspace..."), this, SLOT(openWorkspace()), QKeySequence("Ctrl+W"));
#else
m_openWorkspace = file->addAction(QIcon::fromTheme("folder-open"), tr("&Open Workspace..."),
- this, &MainWindow::openWorkspace, QKeySequence::Open);
+ this, &MainWindow::openWorkspace, QKeySequence("Ctrl+W"));
#endif
m_recentMenu = file->addMenu(QIcon::fromTheme("document-open-recent"), "&Recent");
m_recentMenu->setEnabled(false);
@@ -341,6 +433,8 @@ void MainWindow::setupToolBar()
m_toolBar = addToolBar("ToolBar");
m_toolBar->setObjectName("toolbar");
m_toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ m_toolBar->addAction(m_createProject);
+ m_toolBar->addAction(m_openProject);
m_toolBar->addAction(m_openWorkspace);
m_toolBar->addSeparator();
m_toolBar->addAction(m_refresh);
@@ -517,3 +611,62 @@ void MainWindow::stayOnTop()
}
show();
}
+
+void MainWindow::openProject()
+{
+ QString filter = tr("QmlLive (*.qmllive);; All files (*.*)");
+ QString path = QFileDialog::getOpenFileName(this, "Open Project", filter, filter);
+ if (path.isEmpty()) {
+ return;
+ }
+ if (m_projectManager->read(path))
+ {
+ QStringList paths;
+ QSettings s;
+ int count = s.beginReadArray("imports");
+ for (int i=0; i<count; i++) {
+ s.setArrayIndex(i);
+ paths.append(s.value("path").toString());
+ }
+ s.endArray();
+ paths.append(m_projectManager->imports());
+ paths.removeDuplicates();
+
+ //write Application settings
+ s.beginWriteArray("imports");
+ for (int i = 0; i < paths.count(); i++) {
+ s.setArrayIndex(i);
+ s.setValue("path", paths.at(i));
+ }
+ s.endArray();
+
+ setImportPaths(paths);
+ setWorkspace(m_projectManager->workspace());
+ activateDocument(LiveDocument(m_projectManager->mainDocument()));
+ }
+}
+
+void MainWindow::newProjectWizard()
+{
+ if (!m_newProjectWizard) {
+ m_newProjectWizard = new NewProjectWizard(this);
+ } else {
+ m_newProjectWizard->restart();
+ }
+ m_newProjectWizard->show();
+}
+
+void MainWindow::newProject()
+{
+ m_projectManager->setImports(m_newProjectWizard->imports());
+ m_projectManager->setMainDocument(m_newProjectWizard->mainDocument());
+ m_projectManager->setWorkspace(m_newProjectWizard->workspace());
+
+ m_projectManager->create(m_newProjectWizard->projectName());
+
+ setImportPaths(m_newProjectWizard->imports());
+ setWorkspace(m_newProjectWizard->workspace());
+ activateDocument(LiveDocument(m_newProjectWizard->mainDocument()));
+}
+
+#include "mainwindow.moc"
diff --git a/src/bench/mainwindow.h b/src/bench/mainwindow.h
index 4a7e8f1..c3725a2 100644
--- a/src/bench/mainwindow.h
+++ b/src/bench/mainwindow.h
@@ -50,13 +50,16 @@ class AllHostsWidget;
class Host;
class HostDiscoveryManager;
class Options;
+class NewProjectWizard;
+class ProjectManager;
+
QT_FORWARD_DECLARE_CLASS(QToolBar);
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
- explicit MainWindow(QWidget *parent = 0);
+ explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void activateDocument(const LiveDocument &path);
void setWorkspace(const QString& path, bool activateRootPath = true);
@@ -88,10 +91,13 @@ private slots:
void openWorkspace();
void logQuitEvent();
void updateWindowTitle();
- void openPreferences(Host *host = 0);
+ void openPreferences(Host *host = nullptr);
void openRecentFolder();
void clearRecentFolder();
void stayOnTop();
+ void openProject();
+ void newProjectWizard();
+ void newProject();
void onActiveWindowChanged(QQuickWindow *activeWindow);
@@ -126,4 +132,8 @@ private:
QAction *m_clipRootObject;
QToolBar* m_toolBar;
QStringList m_qmlDefaultimportList;
+ QAction *m_openProject;
+ QAction *m_createProject;
+ NewProjectWizard *m_newProjectWizard;
+ ProjectManager *m_projectManager;
};
diff --git a/src/bench/newprojectwizard.cpp b/src/bench/newprojectwizard.cpp
new file mode 100644
index 0000000..f2fcbbc
--- /dev/null
+++ b/src/bench/newprojectwizard.cpp
@@ -0,0 +1,246 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QmlLive tool.
+**
+** $QT_BEGIN_LICENSE:GPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: GPL-3.0
+**
+****************************************************************************/
+
+#include "newprojectwizard.h"
+
+#include <QGridLayout>
+#include <QListWidget>
+#include <QPushButton>
+#include <QFileDialog>
+#include "livedocument.h"
+
+NewProjectWizard::NewProjectWizard(QWidget *parent)
+ : QWizard(parent)
+ , m_importListWidget(nullptr)
+ , m_projectPage(new ProjectPage())
+ , m_workspacePage(new WorkspacePage())
+ , m_mainDocumentPage(new MainDocumentPage())
+{
+ setWizardStyle(QWizard::ClassicStyle);
+ setOptions(QWizard::NoBackButtonOnStartPage);
+ addPage(m_projectPage);
+ addPage(m_workspacePage);
+ addPage(createImportsPage());
+ addPage(m_mainDocumentPage);
+}
+
+MainDocumentPage::MainDocumentPage(QWidget *parent)
+ : QWizardPage (parent)
+{
+ setTitle("Main Document");
+ QGridLayout *layout = new QGridLayout;
+
+ QLabel *label = new QLabel("Main document: ");
+ layout->addWidget(label, 0, 0);
+
+ m_mainDocumentField = new QLineEdit;
+ registerField("mainDocument*", m_mainDocumentField);
+ layout->addWidget(m_mainDocumentField, 0, 1);
+
+ layout->setColumnStretch(1, 1);
+ layout->setRowStretch(1, 1);
+ setLayout(layout);
+
+}
+
+QString MainDocumentPage::mainDocument() const
+{
+ if (m_mainDocumentField) {
+ return m_mainDocumentField->text();
+ }
+ return "";
+}
+
+WorkspacePage::WorkspacePage(QWidget *parent)
+ : QWizardPage (parent)
+{
+ setTitle("Select Workspace");
+ QGridLayout *layout = new QGridLayout;
+
+ QLabel *label = new QLabel("Workspace: ");
+ layout->addWidget(label, 0, 0);
+
+ m_workspaceField = new QLineEdit;
+ registerField("workspace*", m_workspaceField);
+ layout->addWidget(m_workspaceField, 0, 1);
+
+ QPushButton *button = new QPushButton("Select");
+ layout->addWidget(button, 0, 2);
+ connect(button, SIGNAL(clicked()), this, SLOT(selectWorkspacePath()));
+
+ m_warningLabel = new QLabel;
+ layout->addWidget(m_warningLabel, 1, 0, 1, 3, Qt::AlignTop);
+
+ layout->setColumnStretch(1, 1);
+ layout->setRowStretch(1, 1);
+ setLayout(layout);
+}
+
+QString WorkspacePage::workspace() const
+{
+ if (m_workspaceField) {
+ return m_workspaceField->text();
+ }
+ return "";
+}
+
+void WorkspacePage::selectWorkspacePath()
+{
+ m_warningLabel->setText("");
+ QString workspace = QFileDialog::getExistingDirectory(this, "Select Workspace");
+ if (!workspace.isEmpty() && m_workspaceField) {
+ m_workspaceField->setText(workspace);
+ }
+}
+
+bool WorkspacePage::validatePage()
+{
+ if (QDir(workspace()).exists()) {
+ m_warningLabel->setText("");
+ return true;
+ } else {
+ m_warningLabel->setText("The path you entered does not exist.");
+ return false;
+ }
+}
+
+QWizardPage *NewProjectWizard::createImportsPage()
+{
+ QWizardPage *page = new QWizardPage;
+ page->setTitle("Imports");
+ QGridLayout *layout = new QGridLayout;
+
+ m_importListWidget = new QListWidget;
+ layout->addWidget(m_importListWidget, 0, 0, 4, 1);
+
+ QPushButton *add = new QPushButton("Add");
+ connect(add, SIGNAL(clicked()), this, SLOT(addImportPath()));
+ layout->addWidget(add, 0, 1);
+
+ QPushButton *edit = new QPushButton("Edit");
+ connect(edit, SIGNAL(clicked()), this, SLOT(editImportPath()));
+ layout->addWidget(edit, 1, 1);
+
+ QPushButton *remove = new QPushButton("Remove");
+ connect(remove, SIGNAL(clicked()), this, SLOT(removeImportPath()));
+ layout->addWidget(remove, 2, 1);
+
+ layout->setRowStretch(4, 1);
+ page->setLayout(layout);
+ return page;
+}
+
+ProjectPage::ProjectPage(QWidget *parent)
+ : QWizardPage (parent)
+{
+ setTitle("Project Name");
+ setSubTitle("This wizard generates a Qt QmlLive project. The QmlLive project file shall describe the"
+ "common options for a QmlLive project by specifying the workspace folder, the main document"
+ "and the import paths relative to the project document location.");
+
+ QGridLayout *layout = new QGridLayout;
+
+ QLabel *label = new QLabel("Project name: ");
+ layout->addWidget(label, 0, 0);
+
+ m_projectField = new QLineEdit;
+ registerField("projectName*", m_projectField);
+ m_projectField->setPlaceholderText("MyQmlLiveProject");
+ layout->addWidget(m_projectField, 0, 1);
+
+ layout->setColumnStretch(1, 1);
+ layout->setRowStretch(1, 1);
+ setLayout(layout);
+}
+
+QString ProjectPage::projectName() const
+{
+ if (m_projectField) {
+ return m_projectField->text();
+ }
+ return "";
+}
+
+QString NewProjectWizard::mainDocument() const
+{
+ return m_mainDocumentPage->mainDocument();
+}
+
+QString NewProjectWizard::workspace() const
+{
+ return m_workspacePage->workspace();
+}
+
+QStringList NewProjectWizard::imports() const
+{
+ QStringList list;
+ if (m_importListWidget) {
+ for (int i = 0; i < m_importListWidget->count(); i++) {
+ list.append(m_importListWidget->takeItem(i)->text());
+ }
+ }
+
+ return list;
+}
+
+QString NewProjectWizard::projectName() const
+{
+ return m_projectPage->projectName();
+}
+
+void NewProjectWizard::addImportPath()
+{
+ QString path = QFileDialog::getExistingDirectory(this, "Add Import Path");
+ if (path.isEmpty()) {
+ return;
+ }
+ QListWidgetItem *item = new QListWidgetItem(path);
+ item->setFlags(item->flags () | Qt::ItemIsEditable);
+ m_importListWidget->addItem(item);
+
+}
+
+void NewProjectWizard::editImportPath()
+{
+ QListWidgetItem *item = m_importListWidget->currentItem();
+ if (item) {
+ m_importListWidget->editItem(item);
+ }
+
+}
+
+void NewProjectWizard::removeImportPath()
+{
+ QListWidgetItem *item = m_importListWidget->currentItem();
+ if (item) {
+ delete item;
+ }
+}
diff --git a/src/bench/newprojectwizard.h b/src/bench/newprojectwizard.h
new file mode 100644
index 0000000..b2f5fd6
--- /dev/null
+++ b/src/bench/newprojectwizard.h
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QmlLive tool.
+**
+** $QT_BEGIN_LICENSE:GPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: GPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QWizard>
+#include <QWizardPage>
+#include <QLineEdit>
+#include <QListWidget>
+#include <QLabel>
+
+class ProjectPage : public QWizardPage
+{
+ Q_OBJECT
+
+public:
+ ProjectPage(QWidget *parent = nullptr);
+ QString projectName() const;
+
+private:
+ QLineEdit *m_projectField;
+};
+
+class WorkspacePage : public QWizardPage
+{
+ Q_OBJECT
+
+public:
+ WorkspacePage(QWidget *parent = nullptr);
+ QString workspace() const;
+ bool validatePage() override;
+
+private slots:
+ void selectWorkspacePath();
+
+private:
+ QLineEdit *m_workspaceField;
+ QLabel *m_warningLabel;
+};
+
+class MainDocumentPage : public QWizardPage
+{
+ Q_OBJECT
+
+public:
+ MainDocumentPage(QWidget *parent = nullptr);
+ QString mainDocument() const;
+
+private:
+ QLineEdit *m_mainDocumentField;
+};
+
+class NewProjectWizard : public QWizard
+{
+ Q_OBJECT
+public:
+ explicit NewProjectWizard(QWidget *parent = 0);
+ QWizardPage* createMainDocumentPage();
+ QWizardPage* createWorkspacePage();
+ QWizardPage* createImportsPage();
+ QWizardPage* createProjectPage();
+
+ QString mainDocument() const;
+ QString workspace() const;
+ QStringList imports() const;
+ QString projectName() const;
+
+private slots:
+ void addImportPath();
+ void editImportPath();
+ void removeImportPath();
+
+private:
+ QListWidget *m_importListWidget;
+ ProjectPage *m_projectPage;
+ WorkspacePage *m_workspacePage;
+ MainDocumentPage *m_mainDocumentPage;
+};
diff --git a/src/bench/options.cpp b/src/bench/options.cpp
index 5baedc2..1e1f28b 100644
--- a/src/bench/options.cpp
+++ b/src/bench/options.cpp
@@ -37,6 +37,7 @@ Options::Options(QObject *parent)
, m_remoteOnly(false)
, m_ping(false)
, m_stayOnTop(false)
+ , m_maximumWatches(100)
{
}
@@ -174,3 +175,13 @@ void Options::setHostsToProbe(const QStringList &hostNames)
{
m_hostsToProbe = hostNames;
}
+
+int Options::maximumWatches() const
+{
+ return m_maximumWatches;
+}
+
+void Options::setMaximumWatches(int maximumWatches)
+{
+ m_maximumWatches = maximumWatches;
+}
diff --git a/src/bench/options.h b/src/bench/options.h
index e7c84bd..a0ab44f 100644
--- a/src/bench/options.h
+++ b/src/bench/options.h
@@ -86,6 +86,9 @@ public:
QStringList hostsToProbe() const;
void setHostsToProbe(const QStringList &hostNames);
+ int maximumWatches() const;
+ void setMaximumWatches(int maximumWatches);
+
private:
bool m_noRemote;
bool m_remoteOnly;
@@ -98,5 +101,6 @@ private:
QList<HostOptions> m_hostsToAdd;
QStringList m_hostsToRemove;
QStringList m_hostsToProbe;
+ int m_maximumWatches;
};
diff --git a/src/bench/qmlpreviewadapter.cpp b/src/bench/qmlpreviewadapter.cpp
index ac5415e..21a9623 100644
--- a/src/bench/qmlpreviewadapter.cpp
+++ b/src/bench/qmlpreviewadapter.cpp
@@ -62,7 +62,7 @@ QImage QmlPreviewAdapter::preview(const QString &path, const QSize &requestedSiz
}
static const QString program = QCoreApplication::applicationDirPath() +
-#ifdef Q_OS_WINDOWS
+#ifdef Q_OS_WIN
QStringLiteral("/previewGenerator.exe");
#else
QStringLiteral("/../libexec/qmllive/previewGenerator");
diff --git a/src/livehubengine.cpp b/src/livehubengine.cpp
index 39208bf..91c65dc 100644
--- a/src/livehubengine.cpp
+++ b/src/livehubengine.cpp
@@ -48,6 +48,18 @@
*/
/*!
+ \enum LiveHubEngine::Error
+ \brief Describes error state of an engine
+
+ \value NoError
+ No error
+ \value WatcherMaximumReached
+ The maximum number of watches set with setMaximumWatches() was exceeded
+ \value WatcherSystemError
+ Watching a directory for changes failed for an unspecified reason
+ */
+
+/*!
* Standard constructor using \a parent as parent
*/
LiveHubEngine::LiveHubEngine(QObject *parent)
@@ -56,6 +68,7 @@ LiveHubEngine::LiveHubEngine(QObject *parent)
, m_filePublishingActive(false)
{
connect(m_watcher, &Watcher::directoriesChanged, this, &LiveHubEngine::directoriesChanged);
+ connect(m_watcher, &Watcher::errorChanged, this, &LiveHubEngine::watcherErrorChanged);
}
/*!
@@ -95,6 +108,38 @@ LiveDocument LiveHubEngine::activePath() const
}
/*!
+ * Returns true if error() is not NoError
+ */
+bool LiveHubEngine::hasError()
+{
+ return m_error != NoError;
+}
+
+/*!
+ * Returns current Error
+ */
+LiveHubEngine::Error LiveHubEngine::error()
+{
+ return m_error;
+}
+
+/*!
+ * Returns the maximum number of watched directories
+ */
+int LiveHubEngine::maximumWatches()
+{
+ return Watcher::maximumWatches();
+}
+
+/*!
+ * Sets the maximum number of watched directories to \a maximumWatches
+ */
+void LiveHubEngine::setMaximumWatches(int maximumWatches)
+{
+ Watcher::setMaximumWatches(maximumWatches);
+}
+
+/*!
* Handles watcher changes signals.
*/
void LiveHubEngine::directoriesChanged(const QStringList &changes)
@@ -110,6 +155,31 @@ void LiveHubEngine::directoriesChanged(const QStringList &changes)
}
/*!
+ * Handles watcher error signals
+ */
+void LiveHubEngine::watcherErrorChanged()
+{
+ Error newError = NoError;
+ switch (m_watcher->error()) {
+ case Watcher::NoError:
+ newError = NoError;
+ break;
+ case Watcher::MaximumReached:
+ newError = WatcherMaximumReached;
+ break;
+ case Watcher::SystemError:
+ newError = WatcherSystemError;
+ break;
+ }
+
+ if (newError == m_error)
+ return;
+
+ m_error = newError;
+ emit errorChanged();
+}
+
+/*!
* Publish the whole workspace to a connected node.
*/
void LiveHubEngine::publishWorkspace()
@@ -186,3 +256,7 @@ void LiveHubEngine::setFilePublishingActive(bool on)
* The signal is emitted when the workspace identified by \a workspace has changed
*/
+/*!
+ * \fn void LiveHubEngine::errorChanged()
+ * The signal is emitted when the error state desctibed by error() changed
+ */
diff --git a/src/livehubengine.h b/src/livehubengine.h
index 266e07e..eba7203 100644
--- a/src/livehubengine.h
+++ b/src/livehubengine.h
@@ -43,11 +43,23 @@ class QMLLIVESHARED_EXPORT LiveHubEngine : public QObject
{
Q_OBJECT
public:
+ enum Error {
+ NoError,
+ WatcherMaximumReached,
+ WatcherSystemError,
+ };
+
explicit LiveHubEngine(QObject *parent = 0);
void setWorkspace(const QString& path);
QString workspace() const;
LiveDocument activePath() const;
+
+ bool hasError();
+ Error error();
+
+ static int maximumWatches();
+ static void setMaximumWatches(int maximumWatches);
public Q_SLOTS:
void setActivePath(const LiveDocument& path);
void setFilePublishingActive(bool on);
@@ -59,13 +71,16 @@ Q_SIGNALS:
void fileChanged(const LiveDocument& document);
void activateDocument(const LiveDocument& document);
void workspaceChanged(const QString& workspace);
+ void errorChanged();
private Q_SLOTS:
void directoriesChanged(const QStringList& changes);
+ void watcherErrorChanged();
private:
void publishDirectory(const QString& dirPath, bool fileChange);
private:
Watcher *m_watcher;
bool m_filePublishingActive;
LiveDocument m_activePath;
+ Error m_error = NoError;
};
diff --git a/src/previewGenerator/main.cpp b/src/previewGenerator/main.cpp
index a17e66d..32c07bf 100644
--- a/src/previewGenerator/main.cpp
+++ b/src/previewGenerator/main.cpp
@@ -45,6 +45,8 @@
#include <QDebug>
#include <QTextStream>
+#include "../qmllive_version.h"
+
void handlePreview(QLocalSocket *socket);
class PreviewServer : public QObject
@@ -59,8 +61,25 @@ public:
bool listen()
{
- QLocalServer::removeServer("QmlLivePreviewGenerator");
- return m_server->listen("QmlLivePreviewGenerator");
+ Q_ASSERT(!qApp->applicationName().isEmpty());
+ Q_ASSERT(!qApp->organizationDomain().isEmpty() || !qApp->organizationName().isEmpty());
+
+ QString userName;
+#if defined(Q_OS_UNIX)
+ userName = qgetenv("USER");
+#elif defined(Q_OS_WIN)
+ userName = qgetenv("USERNAME");
+#endif
+ if (userName.isEmpty())
+ qWarning("Failed to determine system user name");
+
+ QString serverName = QString::fromLatin1("%1.%2-%3")
+ .arg(qApp->organizationDomain().isEmpty() ? qApp->organizationName() : qApp->organizationDomain())
+ .arg(qApp->applicationName())
+ .arg(userName);
+
+ QLocalServer::removeServer(serverName);
+ return m_server->listen(serverName);
}
QString serverName() const
@@ -93,6 +112,10 @@ Q_LOGGING_CATEGORY(pg, "PreviewGenerator", QtInfoMsg)
int main (int argc, char** argv)
{
+ QGuiApplication::setApplicationName("QmlLivePreviewGenerator");
+ QGuiApplication::setOrganizationDomain(QLatin1String(QMLLIVE_ORGANIZATION_DOMAIN));
+ QGuiApplication::setOrganizationName(QLatin1String(QMLLIVE_ORGANIZATION_NAME));
+
QGuiApplication app(argc, argv);
app.setQuitOnLastWindowClosed(false);
diff --git a/src/projectmanager.cpp b/src/projectmanager.cpp
new file mode 100644
index 0000000..514ffec
--- /dev/null
+++ b/src/projectmanager.cpp
@@ -0,0 +1,160 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QmlLive tool.
+**
+** $QT_BEGIN_LICENSE:GPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: GPL-3.0
+**
+****************************************************************************/
+
+#include "projectmanager.h"
+
+#include <QFile>
+#include <QDebug>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+
+const QLatin1String MainKey("main");
+const QLatin1String WorkspaceKey("workspace");
+const QLatin1String ImportsKey("imports");
+const QLatin1String QmlLiveExtension(".qmllive");
+
+ProjectManager::ProjectManager(QObject *parent)
+ : QObject(parent)
+ , m_mainDocument("main.qml")
+ , m_workspace("")
+ , m_projectName("")
+{
+
+}
+
+bool ProjectManager::read(const QString &path)
+{
+ reset();
+
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly)) {
+ qWarning() << "Unable to open document " << path;
+ return false;
+ }
+ m_projectName = file.fileName();
+ QByteArray data = file.readAll();
+ QJsonParseError error;
+ QJsonDocument document = QJsonDocument::fromJson(data, &error);
+ if (error.error) {
+ qWarning() << "error parsing JSON document" << error.errorString();
+ return false;
+ }
+ if (!document.isObject()) {
+ qWarning() << "Document must be a JSON object";
+ return false;
+ }
+ QJsonObject root = document.object();
+ if (root.contains(MainKey))
+ m_mainDocument = root.value(MainKey).toString();
+ if (root.contains(WorkspaceKey))
+ {
+ QString workspace = root.value(WorkspaceKey).toString();
+ if ((workspace.compare(".") == 0) || (workspace.compare("./") == 0)) {
+ m_workspace = QFileInfo(path).absolutePath();
+ }
+ else {
+ m_workspace = QDir(workspace).absolutePath();
+ }
+ }
+ if (root.contains(ImportsKey) && root.value(ImportsKey).isArray()) {
+ QJsonArray imports = root.value(ImportsKey).toArray();
+ for (QJsonValue value : imports)
+ m_imports.append(value.toString());
+ }
+ return true;
+}
+
+void ProjectManager::write(const QString &path)
+{
+ QFile file(path);
+ if (!file.open(QIODevice::WriteOnly)) {
+ qWarning() << "Unable to write to document: " << path;
+ return;
+ }
+ QJsonObject root;
+ root.insert(MainKey, QJsonValue(m_mainDocument));
+ root.insert(WorkspaceKey, QJsonValue(m_workspace));
+ QJsonArray imports;
+ for (const QString &import : m_imports)
+ imports.append(QJsonValue(import));
+ root.insert(ImportsKey, imports);
+ QJsonDocument document(root);
+ file.write(document.toJson());
+}
+
+void ProjectManager::create(const QString &projectName)
+{
+ m_projectName = projectName;
+ QString path = QString(m_workspace).append(QDir::separator()).append(projectName).append(QmlLiveExtension);
+ qWarning() << path;
+ write(path);
+}
+
+QString ProjectManager::mainDocument() const
+{
+ return m_mainDocument;
+}
+
+QString ProjectManager::workspace() const
+{
+ return m_workspace;
+}
+
+QStringList ProjectManager::imports() const
+{
+ return m_imports;
+}
+
+void ProjectManager::reset()
+{
+ m_mainDocument = QString("main.qml");
+ m_workspace = QString("");
+ m_imports.clear();
+}
+
+void ProjectManager::setProjectName(const QString &projectName)
+{
+ m_projectName = projectName;
+}
+void ProjectManager::setMainDocument(const QString &mainDocument)
+{
+ m_mainDocument = mainDocument;
+}
+void ProjectManager::setWorkspace(const QString &workspace)
+{
+ m_workspace = workspace;
+}
+void ProjectManager::setImports(const QStringList &imports)
+{
+ m_imports = imports;
+}
+
diff --git a/src/projectmanager.h b/src/projectmanager.h
new file mode 100644
index 0000000..d8d10d1
--- /dev/null
+++ b/src/projectmanager.h
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** Copyright (C) 2018 Pelagicore AG
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QmlLive tool.
+**
+** $QT_BEGIN_LICENSE:GPL-QTAS$
+** Commercial License Usage
+** Licensees holding valid commercial Qt Automotive Suite 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 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+** SPDX-License-Identifier: GPL-3.0
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QtCore>
+
+#include "qmllive_global.h"
+
+class QMLLIVESHARED_EXPORT ProjectManager : public QObject
+{
+ Q_OBJECT
+public:
+ explicit ProjectManager(QObject *parent = nullptr);
+
+ bool read(const QString &path);
+ void write(const QString &path=QString());
+ void create(const QString &projectName);
+ QString mainDocument() const;
+ QString workspace() const;
+ QStringList imports() const;
+
+ void setProjectName(const QString &projectName);
+ void setMainDocument(const QString &mainDocument);
+ void setWorkspace(const QString &workspace);
+ void setImports(const QStringList &imports);
+private:
+ void reset();
+
+private:
+ QString m_mainDocument;
+ QString m_workspace;
+ QStringList m_imports;
+ QString m_projectName;
+};
diff --git a/src/remotereceiver.cpp b/src/remotereceiver.cpp
index 0175d44..d3de7f7 100644
--- a/src/remotereceiver.cpp
+++ b/src/remotereceiver.cpp
@@ -117,8 +117,10 @@ bool RemoteReceiver::listen(int port, ConnectionOptions options)
loop.quit();
});
loop.exec();
- if (!pinOk)
+ if (!pinOk) {
+ qWarning() << "Refused connection from QmlLive Bench: Wrong pin";
return false;
+ }
}
if (m_connectionOptions & UpdateDocumentsOnConnect) {
@@ -128,9 +130,13 @@ bool RemoteReceiver::listen(int port, ConnectionOptions options)
loop.quit();
});
loop.exec();
- if (!finishedOk)
+ if (!finishedOk) {
+ qWarning() << "Initial workspace synchronization with QmlLive Bench failed";
return false;
+ }
}
+
+ qInfo() << "QmlLive Bench connected";
}
return true;
@@ -179,6 +185,7 @@ void RemoteReceiver::handleCall(const QString &method, const QByteArray &content
} else if (m_client) {
emit pinOk(false);
m_client->send("pinOK(bool)", QByteArray::number(0));
+ return;
}
}
diff --git a/src/src.pri b/src/src.pri
index 1af18e4..38504b4 100644
--- a/src/src.pri
+++ b/src/src.pri
@@ -20,7 +20,8 @@ SOURCES += \
$$PWD/logger.cpp \
$$PWD/remotelogger.cpp \
$$PWD/logreceiver.cpp \
- $$PWD/fontadapter.cpp
+ $$PWD/fontadapter.cpp \
+ $$PWD/projectmanager.cpp
public_headers += \
$$PWD/livedocument.h \
@@ -33,7 +34,8 @@ public_headers += \
$$PWD/remotepublisher.h \
$$PWD/remotereceiver.h \
$$PWD/contentadapterinterface.h \
- $$PWD/remotelogger.h
+ $$PWD/remotelogger.h \
+ $$PWD/projectmanager.h
HEADERS += \
$$public_headers \
diff --git a/src/watcher.cpp b/src/watcher.cpp
index 2be3abb..4bfb3b1 100644
--- a/src/watcher.cpp
+++ b/src/watcher.cpp
@@ -41,6 +41,20 @@
*/
/*!
+ \enum Watcher::Error
+ \brief Describes error state of a Watcher
+
+ \value NoError
+ No error
+ \value MaximumReached
+ The maximum number of watches set with setMaximumWatches() was exceeded
+ \value SystemError
+ QFileSystemWatcher::addPath failed for an unspecified reason
+ */
+
+int Watcher::s_maximumWatches = -1;
+
+/*!
Default Constructor using parent as parent
*/
Watcher::Watcher(QObject *parent)
@@ -63,12 +77,8 @@ Watcher::Watcher(QObject *parent)
void Watcher::setDirectory(const QString &path)
{
m_rootDir = QDir(path);
- if (!m_watcher->directories().isEmpty()) {
- m_watcher->removePaths(m_watcher->directories());
- }
- if (!m_watcher->files().isEmpty()) {
- m_watcher->removePaths(m_watcher->files());
- }
+ removeAllPaths();
+ setError(NoError);
addDirectoriesRecursively(m_rootDir.absolutePath());
}
@@ -81,28 +91,90 @@ QString Watcher::directory() const
}
/*!
+ \fn Watcher::maximumWatches()
+
+ Returns the maximum number of watched directories
+ */
+
+/*!
+ Sets the maximum number of watched directories
+
+ This will only take effect with next setDirectory() call.
+ */
+void Watcher::setMaximumWatches(int maximumWatches)
+{
+ s_maximumWatches = maximumWatches;
+}
+
+/*!
+ \fn Watcher::hasError() const
+
+ Returns true if error() is not NoError
+ */
+
+/*!
+ \fn Watcher::error() const
+
+ Describes the current error state of this watcher
+ */
+
+/*!
+ \fn Watcher::errorChanged()
+
+ Notifies about error() change
+ */
+
+/*!
Add path and all it's SubDirectory to the internal used QFileSystemWatcher
*/
void Watcher::addDirectoriesRecursively(const QString &path)
{
// qDebug() << "scan: " << path;
- if (!m_watcher->directories().contains(path)) {
- m_watcher->addPath(path);
- }
+ addDirectory(path);
QDirIterator iter(path, QDir::Dirs|QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
- while (iter.hasNext()) {
+ while (iter.hasNext() && !hasError()) {
QDir entry(iter.next());
- if (!m_watcher->directories().contains(entry.absolutePath())) {
- m_watcher->addPath(entry.absolutePath());
+ addDirectory(entry.absolutePath());
+ }
+}
- //If we couldn't add it we reached the filesystem limit
- if (!m_watcher->directories().contains(entry.absolutePath())) {
- qWarning() << "Watcher limit reached. Please reduce the number of files you are watching !!!";
- return;
- }
- }
+void Watcher::addDirectory(const QString &path)
+{
+ if (m_watcher->directories().contains(path))
+ return;
+
+ if (s_maximumWatches > 0 &&
+ m_watcher->directories().count() + m_watcher->files().count() > s_maximumWatches) {
+ removeAllPaths();
+ setError(MaximumReached);
+ return;
}
+ m_watcher->addPath(path);
+
+ if (!m_watcher->directories().contains(path)) {
+ removeAllPaths();
+ setError(SystemError);
+ }
+}
+
+void Watcher::removeAllPaths()
+{
+ if (!m_watcher->directories().isEmpty()) {
+ m_watcher->removePaths(m_watcher->directories());
+ }
+ if (!m_watcher->files().isEmpty()) {
+ m_watcher->removePaths(m_watcher->files());
+ }
+}
+
+void Watcher::setError(Watcher::Error error)
+{
+ if (m_error == error)
+ return;
+
+ m_error = error;
+ emit errorChanged();
}
void Watcher::recordChange(const QString &path)
diff --git a/src/watcher.h b/src/watcher.h
index a17e28a..434f552 100644
--- a/src/watcher.h
+++ b/src/watcher.h
@@ -37,20 +37,35 @@ class Watcher : public QObject
{
Q_OBJECT
public:
+ enum Error {
+ NoError,
+ MaximumReached,
+ SystemError,
+ };
+
explicit Watcher(QObject *parent = 0);
void setDirectory(const QString& path);
QString directory() const;
+ bool hasError() const { return m_error != NoError; }
+ Error error() const { return m_error; }
+ static int maximumWatches() { return s_maximumWatches; }
+ static void setMaximumWatches(int maximumWatches);
private Q_SLOTS:
void recordChange(const QString &path);
void notifyChanges();
Q_SIGNALS:
void directoriesChanged(const QStringList& changes);
-
+ void errorChanged();
private:
void addDirectoriesRecursively(const QString& path);
+ void addDirectory(const QString &path);
+ void removeAllPaths();
+ void setError(Error error);
+ static int s_maximumWatches;
QFileSystemWatcher *m_watcher;
QDir m_rootDir;
QTimer *m_waitTimer;
QStringList m_changes;
+ Error m_error = NoError;
};
diff --git a/src/widgets/workspaceview.cpp b/src/widgets/workspaceview.cpp
index a1f26f8..44e621a 100644
--- a/src/widgets/workspaceview.cpp
+++ b/src/widgets/workspaceview.cpp
@@ -73,7 +73,7 @@ WorkspaceView::WorkspaceView(QWidget *parent)
// setup layout
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(m_view);
- layout->setMargin(1);
+ layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
m_view->setDragEnabled(true);