From d13b8000a8d1a0499a462216e46341c6c7f440dc Mon Sep 17 00:00:00 2001 From: Martin Kampas Date: Fri, 20 Jan 2017 11:03:05 +0100 Subject: Bench: Fix locating previewGenerator on Windows Change-Id: Ic14b478876600b358b72bd36a8cf9ff54483bcae Reviewed-by: Svetlana Abramenkova --- src/bench/qmlpreviewadapter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"); -- cgit v1.2.3 From 75e572a2a47bcd0dc01f36933ae6a21fb6507337 Mon Sep 17 00:00:00 2001 From: Martin Kampas Date: Fri, 20 Jan 2017 11:26:54 +0100 Subject: Bench: Fix assertion failure on double click in directory preview Change-Id: I43e26c1d844fd39f41311114488dbf7c42234d4d Reviewed-by: Svetlana Abramenkova --- src/bench/benchlivenodeengine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); } -- cgit v1.2.3 From ea81f1d1885f05636020a6deb8251d4ce3fa72b5 Mon Sep 17 00:00:00 2001 From: Martin Kampas Date: Tue, 31 Jan 2017 08:49:39 +0100 Subject: Bench: Fix About dialog title Change-Id: I3838e16413d6572b3d321ddbaad5c5f7eff975ed Reviewed-by: Svetlana Abramenkova --- src/bench/aboutdialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); -- cgit v1.2.3 From 78d050d7010a35b752504f54686475a11f6f4f5c Mon Sep 17 00:00:00 2001 From: Martin Kampas Date: Mon, 13 Feb 2017 16:18:07 +0100 Subject: Bench: Destroy main window on exit This fixes crash on exit when previewGenerator is running. Change-Id: I56f98a87b2427556fd793ae680bef75219718849 Reviewed-by: Svetlana Abramenkova --- src/bench/main.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/bench/main.cpp b/src/bench/main.cpp index 728cf9b..cffc627 100644 --- a/src/bench/main.cpp +++ b/src/bench/main.cpp @@ -62,6 +62,7 @@ class MasterApplication : public Application public: MasterApplication(int &argc, char **argv); + ~MasterApplication(); private: void listenForArguments(); @@ -335,6 +336,11 @@ MasterApplication::MasterApplication(int &argc, char **argv) } } +MasterApplication::~MasterApplication() +{ + delete m_window; +} + void MasterApplication::listenForArguments() { QLocalServer *server = new QLocalServer(this); -- cgit v1.2.3 From ee4c464efb4153639f1616b50594dcfde5a240be Mon Sep 17 00:00:00 2001 From: Martin Kampas Date: Thu, 15 Feb 2018 10:15:17 +0100 Subject: Bench: Fix warning about hidden setModel Change-Id: Ie5d01af51c07c67bd76e38cc598b31c29e4d7edf Reviewed-by: Svetlana Abramenkova --- src/bench/hostmanager.h | 2 ++ 1 file changed, 2 insertions(+) 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 m_engine; HostModel* m_model; -- cgit v1.2.3 From 2feb1bddf81e2b69e8af9532b3da3affd1d42ddb Mon Sep 17 00:00:00 2001 From: Martin Kampas Date: Mon, 13 Feb 2017 14:58:55 +0100 Subject: Use user-specific keys for IPC resources Allow to be run by multiple users on the same machine. Change-Id: Id8388f87a3c512f1daf39a5179c59fa9bc6d87b1 Reviewed-by: Svetlana Abramenkova --- src/bench/main.cpp | 29 +++++++++++++++++++++++++---- src/previewGenerator/main.cpp | 27 +++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/bench/main.cpp b/src/bench/main.cpp index cffc627..9336b33 100644 --- a/src/bench/main.cpp +++ b/src/bench/main.cpp @@ -54,6 +54,9 @@ protected: QString serverName() const; void setDarkStyle(); void parseArguments(const QStringList &arguments, Options *options); + +private: + static QString userName(); }; class MasterApplication : public Application @@ -119,9 +122,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); @@ -146,9 +150,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() @@ -303,6 +308,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 */ 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 #include +#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); -- cgit v1.2.3 From 71a415c66ea1b5280705da46ebbf9f6ebe0c8aa4 Mon Sep 17 00:00:00 2001 From: Martin Kampas Date: Thu, 8 Feb 2018 12:16:06 +0100 Subject: Improve log output with blocking connect Change-Id: I920524a535b0b84a7d20facfeecd261a7134cdc8 Reviewed-by: Svetlana Abramenkova --- src/remotereceiver.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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; } } -- cgit v1.2.3 From 3e8e1de30c09fda84a66c31df1ac28ee33689ad1 Mon Sep 17 00:00:00 2001 From: Martin Kampas Date: Wed, 21 Feb 2018 11:27:34 +0100 Subject: HostModel: Fix unintended fallthrough Change-Id: I59c6a9becd25f1043d567dc48445f016161243cc Reviewed-by: Svetlana Abramenkova --- src/bench/hostmodel.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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(); -- cgit v1.2.3 From 4433fffbdec06241dcbb57c52c26d15bc4007e28 Mon Sep 17 00:00:00 2001 From: Martin Kampas Date: Wed, 21 Feb 2018 11:31:04 +0100 Subject: Allow to limit the number of watched directories Consider starting bench e.g. in home directory. Different limitation on the number of watches applies depending on the platform. On linux the limit is per-user. Change-Id: If7a8cf6e3caa47ebaeb8cbd1fdc28d34d4d330b9 Reviewed-by: Svetlana Abramenkova --- src/bench/main.cpp | 14 ++++++ src/bench/mainwindow.cpp | 77 +++++++++++++++++++++++++++++- src/bench/options.cpp | 11 +++++ src/bench/options.h | 4 ++ src/livehubengine.cpp | 74 +++++++++++++++++++++++++++++ src/livehubengine.h | 15 ++++++ src/watcher.cpp | 108 +++++++++++++++++++++++++++++++++++------- src/watcher.h | 17 ++++++- src/widgets/workspaceview.cpp | 2 +- 9 files changed, 301 insertions(+), 21 deletions(-) diff --git a/src/bench/main.cpp b/src/bench/main.cpp index 9336b33..bf12122 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" @@ -211,6 +212,8 @@ void Application::parseArguments(const QStringList &arguments, Options *options) 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); @@ -237,6 +240,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)); @@ -417,6 +429,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); diff --git a/src/bench/mainwindow.cpp b/src/bench/mainwindow.cpp index 61924aa..bd7063a 100644 --- a/src/bench/mainwindow.cpp +++ b/src/bench/mainwindow.cpp @@ -49,6 +49,46 @@ #include "hostdiscoverymanager.h" #include "options.h" +class ErrorBar : public QFrame +{ + Q_OBJECT + +public: + ErrorBar(QWidget *parent = 0) + : 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) @@ -132,9 +172,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); } @@ -517,3 +590,5 @@ void MainWindow::stayOnTop() } show(); } + +#include "mainwindow.moc" 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 m_hostsToAdd; QStringList m_hostsToRemove; QStringList m_hostsToProbe; + int m_maximumWatches; }; diff --git a/src/livehubengine.cpp b/src/livehubengine.cpp index 39208bf..91c65dc 100644 --- a/src/livehubengine.cpp +++ b/src/livehubengine.cpp @@ -47,6 +47,18 @@ * node can run on the same device or even on a remote device using a RemotePublisher. */ +/*! + \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 */ @@ -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); } /*! @@ -94,6 +107,38 @@ LiveDocument LiveHubEngine::activePath() const return m_activePath; } +/*! + * 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. */ @@ -109,6 +154,31 @@ void LiveHubEngine::directoriesChanged(const QStringList &changes) emit activateDocument(m_activePath); } +/*! + * 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. */ @@ -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/watcher.cpp b/src/watcher.cpp index 2be3abb..4bfb3b1 100644 --- a/src/watcher.cpp +++ b/src/watcher.cpp @@ -40,6 +40,20 @@ A class which watches Directories and notifies you about every change in this Directory or in it's SubDirectories */ +/*! + \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 */ @@ -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()); } @@ -80,29 +90,91 @@ QString Watcher::directory() const return m_rootDir.absolutePath(); } +/*! + \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); -- cgit v1.2.3 From 448d7ce1e2cfad09dbe2de87d9cc9b1615bd92ff Mon Sep 17 00:00:00 2001 From: Martin Kampas Date: Fri, 18 May 2018 12:16:58 +0200 Subject: Bench: Use temporary QCoreApplication instance for arguments parsing Remove the restriction on --noremote option and allow full argument processing prior to instantiating the actual application. Change-Id: I2a4215cda5851c98532ae925148b47ac5dfbfcfd Reviewed-by: Svetlana Abramenkova --- src/bench/main.cpp | 48 +++++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/bench/main.cpp b/src/bench/main.cpp index bf12122..4a2f2ea 100644 --- a/src/bench/main.cpp +++ b/src/bench/main.cpp @@ -47,6 +47,7 @@ class Application : public QApplication public: static Application *create(int &argc, char **argv); + ~Application() override; protected: Application(int &argc, char **argv); @@ -54,10 +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 @@ -88,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); @@ -112,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; @@ -206,7 +222,7 @@ 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); @@ -344,27 +360,24 @@ 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(); } } @@ -515,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(); } -- cgit v1.2.3 From a0f2af7fc25b49d082111f6c853e64188e6b4743 Mon Sep 17 00:00:00 2001 From: Svetlana Abramenkova Date: Thu, 14 Jun 2018 14:33:28 +0300 Subject: Update docs for release 5.11 Fixed a few misspellings, referencies to the parent docs Change-Id: Iafeeec9c09a08eaf03a071b80838b84ec653ffaf Task-Id: AUTOSUITE-475 Reviewed-by: Ilya Gluschenko Reviewed-by: Nikita Krupitskas --- doc/concepts.qdoc | 12 ++++++------ doc/index.qdoc | 4 ++-- doc/installation.qdoc | 11 +++++------ doc/qmllive-project.qdocconf | 3 ++- doc/usage.qdoc | 10 +++++----- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/doc/concepts.qdoc b/doc/concepts.qdoc index 73e1634..b67e59c 100644 --- a/doc/concepts.qdoc +++ b/doc/concepts.qdoc @@ -34,7 +34,7 @@ \page qmllive-concepts.html \title Concepts -\chapter Live Reloading +\section1 Live Reloading During a typical user interface design phase, designers create many graphical documents describing the desired user interface. Transferring such graphical @@ -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. @@ -94,7 +94,7 @@ you can also connect more devices, or devices with different sizes. \image runtime.png -\chapter Display, Screens, Panels, Components, Fragments +\section1 Display, Screens, Panels, Components, Fragments \code +- Display @@ -131,7 +131,7 @@ and its screen navigation structure, and of the structure of individual screens their panels. It is also required to define a common set of components to be used inside the panels. The fragments are implementation-specific. -\chapter UX Sheets - \e{visual component testing} +\section1 UX Sheets - \e{visual component testing} UXSheet gives guidance on how to successfully develop user interface components with diff --git a/doc/index.qdoc b/doc/index.qdoc index c560534..b634978 100644 --- a/doc/index.qdoc +++ b/doc/index.qdoc @@ -36,7 +36,7 @@ \indexpage \title Qt QmlLive -\chapter Overview +\section1 Overview Qt QmlLive is a local and remote Qt Quick live reloading system. It allows you to change your QML user interface source code and view the result in almost @@ -58,7 +58,7 @@ your work so much more delightful. \endquotation -\chapter Contents +\section1 Contents The content of the documentation: diff --git a/doc/installation.qdoc b/doc/installation.qdoc index bf552b4..2fcb29e 100644 --- a/doc/installation.qdoc +++ b/doc/installation.qdoc @@ -34,15 +34,14 @@ \page qmllive-installation.html \title Installation -\chapter Dependencies +\section1 Dependencies \list \li Windows, Linux or macOS \li Qt5.4 or higher \endlist - -\chapter Building for desktop +\section1 Building for desktop \code $ qmake @@ -57,7 +56,7 @@ QmlLive Bench can be started directly from build directory by executing \endcode -\chapter Building for device +\section1 Building for device \e{Note: Only needed when you want to have live reloading enabled on the target.} @@ -78,7 +77,7 @@ Optionally it can be packaged with the help of \endcode -\chapter Building documentation +\section1 Building documentation \code $ qmake CONFIG+=force_independent @@ -88,7 +87,7 @@ Optionally it can be packaged with the help of The documentation will be available at \tt{doc/qmllive/index.html}. -\chapter Build options reference +\section1 Build options reference The following values can be added to qmake \c CONFIG variable: 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/doc/usage.qdoc b/doc/usage.qdoc index 3a669a6..35282fd 100644 --- a/doc/usage.qdoc +++ b/doc/usage.qdoc @@ -34,7 +34,7 @@ \page qmllive-usage.html \title Usage -\chapter Introduction +\section1 Introduction The QmlLive system was designed from the ground up to support your needs. It is structured in a modular fashion to be able to meet various usage @@ -52,7 +52,7 @@ For C++ developers, we also offer the ability to integrate the QmlLive system into your own custom runtime using our \l LiveNodeEngine class with a few lines of code and then use the QmlLive Bench to control it. -\chapter The Workbench +\section1 The Workbench The standard workbench is the all inclusive QML live reloading tool. It allows you to select a workspace to watch over and provides a default QML runtime for the @@ -79,7 +79,7 @@ options: -stayontop .........................keep viewer window on top \endcode -\chapter Qt Creator Integration +\section1 Qt Creator Integration You can integrate the QmlLive Bench into Qt Creator as an external tool. For this you need to open the Settings/Options dialog from Qt Creator and open the @@ -102,7 +102,7 @@ current project root folder open as workspace. -\chapter QmlLive Runtime +\section1 QmlLive Runtime A default runtime is provided by the QmlLive Runtime tool. It provides a default qml viewer and listens on a given port for IPC calls from @@ -149,7 +149,7 @@ this is used all workspace documents will be updated prior to instantiation of any QML component. -\chapter Custom Runtime +\section1 Custom Runtime You can create your own custom runtime with the QmlLive features. This allows you to use your QML view setup with your additional C++ code together with the QmlLive system. -- cgit v1.2.3 From e400161c6ec932c180e8b9fe5e5d00a593ee254a Mon Sep 17 00:00:00 2001 From: Svetlana Abramenkova Date: Wed, 11 Jul 2018 18:10:35 +0300 Subject: QmlLive project concept Make it easier to create and manage workspace projects with QmlLive based on a common project format based on a JSON format. Change-Id: I6b17329c655786ea8b7ca4c4b1bc539f23b3e719 Task-Id: AUTOSUITE-589 Reviewed-by: Ilya A, Galkin --- .qmake.conf | 2 +- src/bench/bench.pro | 6 +- src/bench/mainwindow.cpp | 84 +++++++++++++- src/bench/mainwindow.h | 14 ++- src/bench/newprojectwizard.cpp | 246 +++++++++++++++++++++++++++++++++++++++++ src/bench/newprojectwizard.h | 106 ++++++++++++++++++ src/projectmanager.cpp | 160 +++++++++++++++++++++++++++ src/projectmanager.h | 63 +++++++++++ src/src.pri | 6 +- 9 files changed, 677 insertions(+), 10 deletions(-) create mode 100644 src/bench/newprojectwizard.cpp create mode 100644 src/bench/newprojectwizard.h create mode 100644 src/projectmanager.cpp create mode 100644 src/projectmanager.h 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/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/mainwindow.cpp b/src/bench/mainwindow.cpp index bd7063a..982d962 100644 --- a/src/bench/mainwindow.cpp +++ b/src/bench/mainwindow.cpp @@ -48,13 +48,15 @@ #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 = 0) + ErrorBar(QWidget *parent = nullptr) : QFrame(parent) { setFrameShape(QFrame::StyledPanel); @@ -101,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(); @@ -128,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(); } @@ -250,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); @@ -414,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); @@ -591,4 +612,61 @@ 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; iimports()); + 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 +#include +#include +#include +#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 +#include +#include +#include +#include + +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/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 +#include +#include +#include +#include + +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 + +#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/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 \ -- cgit v1.2.3 From a239d45ce6e185eb0a3b5e947698a8f08e783189 Mon Sep 17 00:00:00 2001 From: Svetlana Abramenkova Date: Mon, 27 Aug 2018 14:51:47 +0300 Subject: Fix examples installation path Corrected default examples install path to QT_INSTALL_EXAMPLES Change-Id: Ibe288fe44e2c424177b35c8fe2ee18ab5a9de4d9 Task-number: AUTOSUITE-561 Reviewed-by: Ilya A, Galkin --- doc/installation.qdoc | 2 +- examples/examples.pro | 3 +-- qmllive.pri | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/installation.qdoc b/doc/installation.qdoc index 2fcb29e..061e514 100644 --- a/doc/installation.qdoc +++ b/doc/installation.qdoc @@ -133,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/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) -- cgit v1.2.3 From 8f344fdc478cd18a935adc41f9ef31b1495efad3 Mon Sep 17 00:00:00 2001 From: Martin Kampas Date: Thu, 25 Oct 2018 08:47:56 +0200 Subject: Add *.qmlc to .gitignore Change-Id: Ib3b9c4a063ca612cc7e8a6dfbd0e15d4c273d689 Reviewed-by: Svetlana Abramenkova --- .gitignore | 1 + 1 file changed, 1 insertion(+) 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 -- cgit v1.2.3