diff options
author | Samuli Piippo <samuli.piippo@qt.io> | 2018-12-05 15:07:18 +0200 |
---|---|---|
committer | Samuli Piippo <samuli.piippo@qt.io> | 2018-12-05 15:07:43 +0200 |
commit | 329a996f6a52494ef677107593a0146c71a2880a (patch) | |
tree | c191128601f69090fe40e730a330d20ac0cf3e3e | |
parent | 80d6c28c692c466fbc819ec3b327f3cd24492834 (diff) | |
parent | 8f344fdc478cd18a935adc41f9ef31b1495efad3 (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
30 files changed, 1090 insertions, 74 deletions
@@ -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); |