diff options
author | Oswald Buddenhagen <oswald.buddenhagen@qt.io> | 2018-06-04 14:55:20 +0200 |
---|---|---|
committer | Oswald Buddenhagen <oswald.buddenhagen@qt.io> | 2018-06-04 14:55:20 +0200 |
commit | 73b81cd379569f3fa54924738e00e35444ccbea4 (patch) | |
tree | d5c4b1392bd11318a0bec3f9543ffefab6164dda | |
parent | 4d360892c7a21b4b2aad0ec47ef3f60fb0b0b381 (diff) | |
parent | a9449980b08e94ae648d38ea98e5e7a20fc9365e (diff) |
Merge branch '5.8' into dev
Conflicts:
qmllive.pro
Change-Id: I14a957912a4caaf7a2d3734819d98097f79f2810
65 files changed, 1241 insertions, 609 deletions
@@ -25,9 +25,9 @@ target. ## Build documentation - $ export QT_INSTALL_DOCS=$QTSRC/qtbase/doc + $ qmake CONFIG+=force_independent $ make docs -The documentation will be avilable at 'doc/html/index.html'. +The documentation will be avilable at 'doc/qmllive/index.html'. Copyright (C) 2016 Pelagicore AG diff --git a/doc/classes.qdoc b/doc/classes.qdoc deleted file mode 100644 index 952f2d9..0000000 --- a/doc/classes.qdoc +++ /dev/null @@ -1,100 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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 -** -****************************************************************************/ - -/*! - \group classlists - \title Class and Function Documentation - \brief Lists and Indexes of classes, functions, and types. - - Links to indexes and lists for finding class and function - reference documentation. - - \section2 Class Lists - - \annotatedlist classlists - - \section2 Function Lists - - \annotatedlist funclists - -*/ - -/*! - \page classes.html - \title All Classes - \ingroup classlists - - \brief If you know the name of the class you want, find it here. - - This is a list of all Qt classes. - - \generatelist classes -*/ - -/*! - \page hierarchy.html - - \title Inheritance Hierarchy - \ingroup classlists - - \brief The C++ class inheritance hierarchy for all classes in the - Qt API. - - \generatelist classhierarchy -*/ - -/*! - \page functions.html - \title All Functions - \ingroup funclists - - \brief All documented Qt functions listed alphabetically with a - link to their declarations. - - This is the list of all documented member functions and global - functions in the Qt API. Each function has a link to the class or - header file where it is declared and documented. - - \generatelist functionindex -*/ - - -/*! - \page namespaces.html - \title All Namespaces - \ingroup classlists - - \brief A Qt namespace contains enum types, functions, and sometimes classes. - - This is a list of the main namespaces in Qt. - - \generatelist{namespaces} -*/ diff --git a/doc/concepts.qdoc b/doc/concepts.qdoc index 527c696..73e1634 100644 --- a/doc/concepts.qdoc +++ b/doc/concepts.qdoc @@ -31,7 +31,7 @@ /*! -\page concepts.html +\page qmllive-concepts.html \title Concepts \chapter Live Reloading @@ -87,8 +87,8 @@ display for the embedded device. There are subtle changes in the color appearance, pixel density, font rendering and proportions. So it is vital to ensure that a user experience designed on a PC looks just as brilliant on the embedded device. In the past this was always cumbersome and required that you -manually copy the code to the device and restart the application. With -QmlLive and the QmlLive Runtime you simply connect to the device, propagate your +manually copy the code to the device and restart the application. With QmlLive +Bench and QmlLive Runtime you simply connect to the device, propagate your workspace and from then on all changes are reflected on the device display. Of course, you can also connect more devices, or devices with different sizes. diff --git a/doc/examples/contentplugin.qdoc b/doc/examples/contentplugin.qdoc index 870d466..a62267e 100644 --- a/doc/examples/contentplugin.qdoc +++ b/doc/examples/contentplugin.qdoc @@ -44,7 +44,7 @@ contentadapterinterface.h in the QmlLive source code. This interface can be used to add a new ContentAdapter to QmlLive. The ContentAdapter will be used to display any content that shouldn't be - handled by the LiveRuntime, like displaying an image. + handled by the QmlLive Runtime, like displaying an image. \snippet contentadapterinterface.h 0 diff --git a/doc/index.qdoc b/doc/index.qdoc index 90eecb3..c560534 100644 --- a/doc/index.qdoc +++ b/doc/index.qdoc @@ -31,14 +31,14 @@ /*! -\page index.html -\keyword Pelagicore QML Live Reference Documentation +\page qmllive-index.html +\keyword Pelagicore QmlLive Reference Documentation \indexpage -\title Qt QML Live +\title Qt QmlLive \chapter Overview -QmlLive is a local and remote QtQuick live reloading system. It allows you to +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 realtime. @@ -68,7 +68,7 @@ The content of the documentation: \li \l{Concepts} - General overview of the system \li \l{qmllive-reference}{Reference} - The API reference to build your custom render scene \list - \li \l{qmllive}{QmlLive} - The QmlLIve library API + \li \l{qmllive}{QmlLive} - The QmlLive library API \li \l{ipc}{IPC} - The internal IPC API \endlist @@ -79,7 +79,7 @@ The content of the documentation: /*! * \page qmllive-reference - * \title Reference + * \title API Reference * * * \list diff --git a/doc/installation.qdoc b/doc/installation.qdoc index bed44d8..bf552b4 100644 --- a/doc/installation.qdoc +++ b/doc/installation.qdoc @@ -31,7 +31,7 @@ /*! -\page installation.html +\page qmllive-installation.html \title Installation \chapter Dependencies @@ -49,7 +49,7 @@ $ make \endcode -QML Live Bench can be started directly from build directory by executing +QmlLive Bench can be started directly from build directory by executing \c{./bin/qmllivebench}. Optionally it can be installed with \code @@ -81,11 +81,11 @@ Optionally it can be packaged with the help of \chapter Building documentation \code - $ export QT_INSTALL_DOCS=$QTSRC/qtbase/doc + $ qmake CONFIG+=force_independent $ make docs \endcode -The documentation will be available at \tt{doc/html/index.html}. +The documentation will be available at \tt{doc/qmllive/index.html}. \chapter Build options reference @@ -99,7 +99,7 @@ The following values can be added to qmake \c CONFIG variable: \row \li skip-bench - \li Do not build \l{Workbench}{QML Live Bench} + \li Do not build \l{The Workbench}{QmlLive Bench} \row \li skip-examples @@ -107,7 +107,7 @@ The following values can be added to qmake \c CONFIG variable: \row \li skip-runtime - \li Do not build \l{QmlLive Runtime}{QML Live Runtime} + \li Do not build \l{QmlLive Runtime} \row \li skip-tests @@ -115,8 +115,8 @@ The following values can be added to qmake \c CONFIG variable: \row \li static-link-runtime - \li Produce a single-binary QML Live Runtime executable. Without this option - enabled QML Live Runtime executable requires the \c libqmllive dynamic + \li Produce a single-binary QmlLive Runtime executable. Without this option + enabled QmlLive Runtime executable requires the \c libqmllive dynamic library to be copied to the target. \endtable diff --git a/doc/qmllive-group.qdoc b/doc/qmllive-group.qdoc index 6f81e42..7c41db1 100644 --- a/doc/qmllive-group.qdoc +++ b/doc/qmllive-group.qdoc @@ -33,9 +33,10 @@ /*! \module qmllive - \title Live Module - \brief Classes for watching a workspace and for local and remote reloading of QtQuick user interfaces + \title QmlLive Module + \brief Classes for watching a workspace and for local and remote reloading of Qt Quick user interfaces - The live module allows a developer to quickly create his own instance of a workbench or a live runtime. + The QmlLive module allows a developer to quickly create his own instance of + a QmlLive workbench or a QmlLive runtime. */ diff --git a/doc/qmllive-online.qdocconf b/doc/qmllive-online.qdocconf index 09b46fe..087139e 100644 --- a/doc/qmllive-online.qdocconf +++ b/doc/qmllive-online.qdocconf @@ -15,6 +15,9 @@ HTML.footer = \ include($QT_INSTALL_DOCS/global/qt-html-templates-online.qdocconf) # Add an .html file with sidebar content, used in the online style -# HTML.stylesheets += style/qt5-sidebar.html +HTML.stylesheets += style/qt5-sidebar.html + +HTML.nosubdirs = "false" +HTML.outputsubdir = "qmllive" include(qmllive-project.qdocconf) diff --git a/doc/qmllive-project.qdocconf b/doc/qmllive-project.qdocconf index d93882b..5744d9f 100644 --- a/doc/qmllive-project.qdocconf +++ b/doc/qmllive-project.qdocconf @@ -1,6 +1,6 @@ -project = QML Live -description = Pelagicore QML Live Reference Documentation -url = https://doc.qt.io/QmlLive +project = QmlLive +description = Pelagicore QmlLive Reference Documentation +url = https://doc.qt.io/QtQmlLive version = $QT_VERSION sources.fileextensions = "*.cpp *.qdoc *.mm *.qml" @@ -14,7 +14,7 @@ exampledirs = ../examples ../src headerdirs = \ ../src -sourcedirs = \ +sourcedirs += \ . \ ../src \ ../examples @@ -25,17 +25,17 @@ qhp.projects = QmlLive qhp.QmlLive.file = qmllive.qhp qhp.QmlLive.namespace = io.qt.qmllive.$QT_VERSION_TAG qhp.QmlLive.virtualFolder = qmllive -qhp.QmlLive.indexTitle = Qt QML Live +qhp.QmlLive.indexTitle = Qt QmlLive qhp.QmlLive.indexRoot = qhp.QmlLive.filterAttributes = QmlLive $QT_VERSION -qhp.QmlLive.customFilters.QmlLive.name = QML Live $QT_VERSION +qhp.QmlLive.customFilters.QmlLive.name = QmlLive $QT_VERSION qhp.QmlLive.customFilters.QmlLive.filterAttributes = QmlLive $QT_VERSION qhp.QmlLive.subprojects = manual -qhp.QmlLive.subprojects.manual.title = Qt QML Live -qhp.QmlLive.subprojects.manual.indexTitle = Qt QML Live +qhp.QmlLive.subprojects.manual.title = Qt QmlLive +qhp.QmlLive.subprojects.manual.indexTitle = Qt QmlLive qhp.QmlLive.subprojects.manual.type = manual -navigation.landingpage = "Qt QML Live" -buildversion = "Qt QML Live $QT_VERSION" +navigation.homepage = "Qt QmlLive" +buildversion = "Qt QmlLive $QT_VERSION" diff --git a/doc/style/qt5-sidebar.html b/doc/style/qt5-sidebar.html new file mode 100644 index 0000000..7b6ce39 --- /dev/null +++ b/doc/style/qt5-sidebar.html @@ -0,0 +1,13 @@ +<div class="sectionlist normallist"> + <div class="heading"> + <h2>Qt QML Live</h2> + </div> + <ul> + <li><a href="index.html">Home</a></li> + <li><a href="qmllive-installation.html">Installation</a></li> + <li><a href="qmllive-usage.html">Usage</a></li> + <li><a href="qmllive-concepts.html">Concepts</a></li> + <li><a href="qmllive-reference.html">Reference</a></li> + <li><a href="qmllive-examples.html">Examples</a></li> + </ul> +</div> diff --git a/doc/usage.qdoc b/doc/usage.qdoc index ed67e4a..3a669a6 100644 --- a/doc/usage.qdoc +++ b/doc/usage.qdoc @@ -31,7 +31,7 @@ /*! -\page usage.html +\page qmllive-usage.html \title Usage \chapter Introduction @@ -40,75 +40,76 @@ 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 requirements. -In the early phase of a project you normally want to use the \b QmlLiveBench, +In the early phase of a project you normally want to use QmlLive \b Bench, which has everything included in a typical desktop application. Later in the project you may want to test your UI code on a device. For this we -have designed the \b{QmlLiveBench} in combination with the -\b{QmlLiveRuntime}. This combi pack offers you a default qml renderer to be run +have designed the QmlLive Bench in combination with the +QmlLive \b Runtime. This combi pack offers you a default QML renderer to be run on the device and a small remote application on the desktop to control it. 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 \b{QmlLiveRuntime} to implement it. +lines of code and then use the QmlLive Bench to control it. -\chapter Workbench +\chapter The Workbench -The standard workbench is the all inclusve qml live tool. It allows you to -select a workspace to watch over and provides a default qml runtime for the -active selected qml document. +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 +active selected QML document. -\image workbench.png Workbench +\image workbench.png The Workbench You launch it by just executing the \tt qmllivebench executable \code -{$(QMLIVEPROJECT)/bin/qmllivebench[.exe] +$(QMLIVEPROJECT)/bin/qmllivebench[.exe] \endcode -The QmlLive Bench can also be run from the command line +The QmlLive Bench can also be passed a few command line arguments \code Usage qmllivebench [options] <workspace> Usage qmllivebench [options] <workspace/file.qml> options: - -pluginpath ........................path to qmllive plugins - -importpath ........................path to the qml import path + -pluginpath ........................path to QmlLive plugins + -importpath ........................path to the QML import path -stayontop .........................keep viewer window on top \endcode -\chapter Creator Integration +\chapter Qt Creator Integration -You can integrate the QmlLiveBench into Qt Creator as an external tool. For this -you need to open the Settings/Options dialog from QtCreator and open the -\b{Environment} group. There you will find the \tt{External Tools} tab. +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 +\uicontrol{Environment} group. There you will find the \uicontrol{External Tools} tab. -Under exectuble enter the path of your QmlLiveBench executable. +Under \uicontrol{Executable} enter the path to your QmlLive Bench executable. -\image creator_tool.png Creator +\image creator_tool.png Qt Creator -Now QmlLiveBench is availabe under the menu entry \uicontrol{Tool->External->QmlLiveBench}. -To be able to easier launch QmlLiveBench you can also assign a shortcut to the +Now QmlLive Bench is availabe under the menu entry \uicontrol{Tool > External > QmlLive Bench}. +To be able to easier launch QmlLive Bench you can also assign a shortcut to the tool. -\image creator_shortcut.png Creator +\image creator_shortcut.png Qt Creator -Now when you press \uicontrol{Alt-F8} QmlLiveBench will be launched with the current project root folder open as workspace. +Now when you press \uicontrol{Alt-F8} QmlLive Bench will be launched with the +current project root folder open as workspace. -\image creator_result.png Creator +\image creator_result.png Qt Creator \chapter QmlLive Runtime -The default runtime is meant to be used with the QmlLiveRuntime tool. It -provides a default qml viewer and listens on a given port for ipc calls from +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 the remote. As such it's ideal to start developing on a target device, when no extra c++ code is required. -\image runtime.png Runtime +\image runtime.png QmlLive Runtime Calling the runtime @@ -122,11 +123,11 @@ Usage of the runtime Usage qmlliveruntime [options] <workspace> options: - -ipcport <port> ....................the port the ipc shall listen on + -ipcport <port> ....................the port the IPC shall listen on -updates-as-overlay ................allow receiving updates with read only workspace -update-on-connect .................update all workspace documents initially (blocking) - -pluginpath ........................path to qmllive plugins - -importpath ........................path to the qml import path + -pluginpath ........................path to QmlLive plugins + -importpath ........................path to the QML import path -fullscreen ........................shows in fullscreen mode -transparent .......................Make the window transparent -frameless .........................run with no window frame @@ -139,7 +140,7 @@ order to execute it. Receiving updates normally requires write access to the deployed files. Depending on the target platform, the project may be deployed to a location which is not user writable. In most cases hacking on the file permissions after deployment can help, but a more convenient method is available -- let the runtime store all updates in a writable workspace overlay. Use the \c +- let QmlLive Runtime store all updates in a writable workspace overlay. Use the \c -updates-as-overlay option to enable this feature. Another constraints may exist on updating documents later after application @@ -152,16 +153,16 @@ any QML component. 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. -For this you need to use the \b{LiveNodeEngine} class to be able to receive +For this you need to use the \l LiveNodeEngine class to be able to receive workspace changes and active document updates. By default, the IPC will listen on the port 10234. -Here is a short example of a minimal custom runtime: +Here is a short example of a minimal custom QmlLive runtime: \snippet ../examples/app/main.cpp 0 On platforms where pkg-config is supported simply add the following to your -project file if QML Live is installed on your build host: +project file if QmlLive is installed on your build host: \code CONFIG += link_pkgconfig diff --git a/examples/app/app.pro b/examples/app/app.pro index 3ffe5dc..cc41e36 100644 --- a/examples/app/app.pro +++ b/examples/app/app.pro @@ -1,5 +1,6 @@ TEMPLATE = app TARGET = app +CONFIG += c++11 macx*: CONFIG -= app_bundle QT *= quick diff --git a/examples/app/main.cpp b/examples/app/main.cpp index 62b5d50..d02c60d 100644 --- a/examples/app/main.cpp +++ b/examples/app/main.cpp @@ -37,46 +37,96 @@ #include "livenodeengine.h" #include "remotereceiver.h" -class CustomQmlEngine : public QQmlEngine +class MyQmlApplicationEngine : public QQmlApplicationEngine { Q_OBJECT public: - CustomQmlEngine(); // Perform some setup here + MyQmlApplicationEngine(const QString &mainQml); // Perform some setup here + + QString mainQml() const; + QQuickWindow *mainWindow(); + QList<QQmlError> warnings() const; + + // ... }; int main(int argc, char **argv) { QGuiApplication app(argc, argv); + MyQmlApplicationEngine engine(QStringLiteral("qml/window.qml")); - CustomQmlEngine qmlEngine; - QQuickView fallbackView(&qmlEngine, 0); + if (!qEnvironmentVariableIsSet("MY_APP_ENABLE_QMLLIVE")) + return app.exec(); +#if defined(QT_NO_DEBUG) + qWarning() << "QmlLive support was disabled at compile time"; +#else LiveNodeEngine node; - // Let qml live know your runtime - node.setQmlEngine(&qmlEngine); + + // Let QmlLive know your runtime + node.setQmlEngine(&engine); + // Allow it to display QML components with non-QQuickWindow root object + QQuickView fallbackView(&engine, 0); node.setFallbackView(&fallbackView); + // Tell it where file updates should be stored relative to node.setWorkspace(app.applicationDirPath(), LiveNodeEngine::AllowUpdates | LiveNodeEngine::UpdatesAsOverlay); - // Listen to ipc call from remote QmlLiveBench + + // Listen to IPC call from remote QmlLive Bench RemoteReceiver receiver; receiver.registerNode(&node); receiver.listen(10234); + // Advanced use: let it know the initially loaded QML component (do this + // only after registering to receiver!) + node.usePreloadedDocument(engine.mainQml(), engine.mainWindow(), engine.warnings()); +#endif + return app.exec(); } //![0] -CustomQmlEngine::CustomQmlEngine() +// Keep the snippet simple! +static QString MyQmlApplicationEngine_mainQml; +static QList<QQmlError> MyQmlApplicationEngine_warnings; + +MyQmlApplicationEngine::MyQmlApplicationEngine(const QString &mainQml) { + // Would be nice to have this in QQmlApplicationEngine + MyQmlApplicationEngine_mainQml = mainQml; + connect(this, &QQmlEngine::warnings, [](const QList<QQmlError> &warnings) { + MyQmlApplicationEngine_warnings.append(warnings); + }); + QStringList colors; colors.append(QStringLiteral("red")); colors.append(QStringLiteral("green")); colors.append(QStringLiteral("blue")); colors.append(QStringLiteral("black")); rootContext()->setContextProperty("myColors", colors); + + load(QDir(qApp->applicationDirPath()).absoluteFilePath(mainQml)); }; +QString MyQmlApplicationEngine::mainQml() const +{ + return MyQmlApplicationEngine_mainQml; +} + +QQuickWindow *MyQmlApplicationEngine::mainWindow() +{ + if (rootObjects().isEmpty()) + return nullptr; + + return qobject_cast<QQuickWindow *>(rootObjects().first()); +} + +QList<QQmlError> MyQmlApplicationEngine::warnings() const +{ + return MyQmlApplicationEngine_warnings; +} + #include "main.moc" diff --git a/qmllive.pro b/qmllive.pro index 21ed1aa..9aeb50f 100755 --- a/qmllive.pro +++ b/qmllive.pro @@ -1,4 +1,4 @@ -!CONFIG(skip-bench): requires(qtHaveModule(widgets)) +!skip-bench: requires(qtHaveModule(widgets)) requires(!winrt:!integrity) load(configure) @@ -6,14 +6,16 @@ load(config-output) include(qmllive.pri) include(doc/doc.pri) -!minQtVersion(5, 4, 0):error("You need at least Qt 5.4.0 to build this application") +!skip-bench:!minQtVersion(5, 4, 0): error("You need at least Qt 5.4.0 to build QmlLive Bench") +!skip-tests:!minQtVersion(5, 4, 0): error("You need at least Qt 5.4.0 to build QmlLive tests") +!minQtVersion(5, 2, 0): error("You need at least Qt 5.4.0 to build QmlLive Bench and/or tests, 5.2.0 for the rest") TEMPLATE = subdirs CONFIG += ordered SUBDIRS += src -!CONFIG(skip-tests): SUBDIRS += tests -!CONFIG(skip-examples): SUBDIRS += examples +!skip-tests: SUBDIRS += tests +!skip-examples: SUBDIRS += examples OTHER_FILES += \ README.md \ diff --git a/src/bench/aboutdialog.cpp b/src/bench/aboutdialog.cpp index ca6b3a5..c9c37bf 100644 --- a/src/bench/aboutdialog.cpp +++ b/src/bench/aboutdialog.cpp @@ -60,7 +60,7 @@ AboutDialog::AboutDialog(QWidget *parent) #endif QString about = tr( - "<h3>Qt QML Live %1%2</h3>" + "<h3>Qt QmlLive %1%2</h3>" "%3" "<p>%4</p>" "<p>The program is provided AS IS with NO WARRANTY OF ANY KIND, " diff --git a/src/bench/allhostswidget.cpp b/src/bench/allhostswidget.cpp index add6e0e..114eba7 100644 --- a/src/bench/allhostswidget.cpp +++ b/src/bench/allhostswidget.cpp @@ -31,6 +31,7 @@ #include "allhostswidget.h" +#include "livedocument.h" AllHostsWidget::AllHostsWidget(QWidget *parent) : QWidget(parent) @@ -38,11 +39,11 @@ AllHostsWidget::AllHostsWidget(QWidget *parent) : setContentsMargins(0,0,0,0); m_publishAction = new QAction("Publish", this); m_publishAction->setIcon(QIcon(":images/publish.svg")); - connect(m_publishAction, SIGNAL(triggered(bool)), this, SLOT(onPublishTriggered())); + connect(m_publishAction, &QAction::triggered, this, &AllHostsWidget::onPublishTriggered); m_refreshAction = new QAction("Refresh", this); m_refreshAction->setIcon(QIcon(":images/refresh.svg")); - connect(m_refreshAction, SIGNAL(triggered(bool)), this, SIGNAL(refreshAll())); + connect(m_refreshAction, &QAction::triggered, this, &AllHostsWidget::refreshAll); setAcceptDrops(true); @@ -95,6 +96,6 @@ void AllHostsWidget::dropEvent(QDropEvent *event) QUrl url(event->mimeData()->text()); if (url.isLocalFile()) - emit currentFileChanged(url.toLocalFile()); + emit currentFileChanged(LiveDocument::resolve(m_workspace, url.toLocalFile())); event->acceptProposedAction(); } diff --git a/src/bench/allhostswidget.h b/src/bench/allhostswidget.h index 47f4460..a53785f 100644 --- a/src/bench/allhostswidget.h +++ b/src/bench/allhostswidget.h @@ -33,6 +33,8 @@ #include <QtWidgets> +class LiveDocument; + class AllHostsWidget : public QWidget { Q_OBJECT @@ -44,7 +46,7 @@ public: signals: void refreshAll(); void publishAll(); - void currentFileChanged(const QString file); + void currentFileChanged(const LiveDocument &file); protected: void dropEvent(QDropEvent *event); diff --git a/src/bench/benchlivenodeengine.cpp b/src/bench/benchlivenodeengine.cpp index abe1910..30dd5b5 100644 --- a/src/bench/benchlivenodeengine.cpp +++ b/src/bench/benchlivenodeengine.cpp @@ -108,7 +108,11 @@ void BenchLiveNodeEngine::initPlugins() DirectoryPreviewAdapter *adapter = new DirectoryPreviewAdapter(this); if (m_workspaceView) { //This needs to be QueuedConnection because Qt5 doesn't like it to destruct it's object while it is in a signalHandler - connect(adapter, SIGNAL(loadDocument(QString)), m_workspaceView, SLOT(activateDocument(QString)), Qt::QueuedConnection); + connect(adapter, &DirectoryPreviewAdapter::loadDocument, + this, [this](const QString &document) { + m_workspaceView->activateDocument(LiveDocument(document)); + }, + Qt::QueuedConnection); } QmlPreviewAdapter *previewAdapter = new QmlPreviewAdapter(this); diff --git a/src/bench/host.cpp b/src/bench/host.cpp index fb1f366..2103e43 100644 --- a/src/bench/host.cpp +++ b/src/bench/host.cpp @@ -73,7 +73,7 @@ QString Host::address() const return m_address; } -QString Host::currentFile() const +LiveDocument Host::currentFile() const { return m_currentFile; } @@ -109,7 +109,7 @@ void Host::setAddress(QString arg) } } -void Host::setCurrentFile(QString arg) +void Host::setCurrentFile(LiveDocument arg) { m_currentFile = arg; emit currentFileChanged(arg); @@ -144,6 +144,8 @@ void Host::setOnline(bool arg) if (m_online != arg) { m_online = arg; emit onlineChanged(arg); + if (m_type == AutoDiscovery) + emit availableChanged(arg); } } @@ -191,6 +193,11 @@ bool Host::online() const return m_online; } +bool Host::available() const +{ + return m_type == Manual || m_online; +} + bool Host::followTreeSelection() const { return m_followTreeSelection; @@ -208,7 +215,6 @@ void Host::saveToSettings(QSettings *s) s->setValue("xOffset", xOffset()); s->setValue("yOffset", yOffset()); s->setValue("rotation", rotation()); - s->setValue("currentFile", currentFile()); s->setValue("autoDiscoveryId", autoDiscoveryId().toString()); s->setValue("systemName", systemName()); s->setValue("productVersion", productVersion()); @@ -226,7 +232,6 @@ void Host::restoreFromSettings(QSettings *s) setXOffset(s->value("xOffset").toInt()); setYOffset(s->value("yOffset").toInt()); setRotation(s->value("rotation").toInt()); - setCurrentFile(s->value("currentFile").toString()); setAutoDiscoveryId(QUuid(s->value("autoDiscoveryId").toString())); setSystemName(s->value("systemName").toString()); setProductVersion(s->value("productVersion").toString()); diff --git a/src/bench/host.h b/src/bench/host.h index 658303c..0e7d1bb 100644 --- a/src/bench/host.h +++ b/src/bench/host.h @@ -31,6 +31,8 @@ #pragma once +#include "livedocument.h" + #include <QObject> #include <QUuid> #include <QMetaType> @@ -51,11 +53,12 @@ public: Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(QString address READ address WRITE setAddress NOTIFY addressChanged) Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged) - Q_PROPERTY(QString currentFile READ currentFile WRITE setCurrentFile NOTIFY currentFileChanged) + Q_PROPERTY(LiveDocument currentFile READ currentFile WRITE setCurrentFile NOTIFY currentFileChanged) Q_PROPERTY(int xOffset READ xOffset WRITE setXOffset NOTIFY xOffsetChanged) Q_PROPERTY(int yOffset READ yOffset WRITE setYOffset NOTIFY yOffsetChanged) Q_PROPERTY(int rotation READ rotation WRITE setRotation NOTIFY rotationChanged) Q_PROPERTY(bool online READ online WRITE setOnline NOTIFY onlineChanged) + Q_PROPERTY(bool available READ available NOTIFY availableChanged) Q_PROPERTY(bool followTreeSelection READ followTreeSelection WRITE setFollowTreeSelection NOTIFY followTreeSelectionChanged) Q_PROPERTY(QUuid autoDiscoveryId READ autoDiscoveryId WRITE setAutoDiscoveryId NOTIFY autoDiscoveryIdChanged) Q_PROPERTY(QString productVersion READ productVersion WRITE setProductVersion) @@ -67,13 +70,14 @@ public: QString name() const; QString address() const; int port() const; - QString currentFile() const; + LiveDocument currentFile() const; int xOffset() const; int yOffset() const; int rotation() const; Type type() const; bool online() const; + bool available() const; bool followTreeSelection() const; QUuid autoDiscoveryId() const; QString productVersion() const; @@ -88,11 +92,12 @@ signals: void nameChanged(QString arg); void addressChanged(QString arg); void portChanged(int arg); - void currentFileChanged(QString arg); + void currentFileChanged(LiveDocument arg); void xOffsetChanged(int arg); void yOffsetChanged(int arg); void rotationChanged(int arg); void onlineChanged(bool arg); + void availableChanged(bool arg); void followTreeSelectionChanged(bool arg); void autoDiscoveryIdChanged(QUuid arg); @@ -102,7 +107,7 @@ public slots: void setName(QString arg); void setAddress(QString arg); void setPort(int arg); - void setCurrentFile(QString arg); + void setCurrentFile(LiveDocument arg); void setXOffset(int arg); void setYOffset(int arg); void setRotation(int arg); @@ -117,7 +122,7 @@ private: QString m_name; QString m_address; int m_port; - QString m_currentFile; + LiveDocument m_currentFile; int m_xOffset; int m_yOffset; int m_rotation; diff --git a/src/bench/hostmanager.cpp b/src/bench/hostmanager.cpp index c48937e..633bfcd 100644 --- a/src/bench/hostmanager.cpp +++ b/src/bench/hostmanager.cpp @@ -36,9 +36,10 @@ #include "livehubengine.h" #include "logreceiver.h" #include "widgets/logview.h" -#include <QDockWidget> +#include <QDockWidget> #include <QDebug> +#include <QFileInfo> HostManager::HostManager(QWidget *parent) : QListView(parent) @@ -67,7 +68,7 @@ void HostManager::setModel(HostModel *model) m_model = model; QListView::setModel(model); - connect(model, SIGNAL(modelReset()), this, SLOT(modelReseted())); + connect(model, &HostModel::modelReset, this, &HostManager::modelReseted); } void HostManager::setLiveHubEngine(LiveHubEngine *engine) @@ -81,8 +82,11 @@ void HostManager::setLiveHubEngine(LiveHubEngine *engine) } } -void HostManager::followTreeSelection(const QString ¤tFile) +void HostManager::followTreeSelection(const LiveDocument ¤tFile) { + if (!currentFile.isFileIn(m_engine->workspace())) + return; + for (int i=0; i < m_model->rowCount(); i++) { HostWidget *widget = qobject_cast<HostWidget*>(indexWidget(m_model->index(i, 0))); if (widget && widget->followTreeSelection()) @@ -90,8 +94,11 @@ void HostManager::followTreeSelection(const QString ¤tFile) } } -void HostManager::setCurrentFile(const QString ¤tFile) +void HostManager::setCurrentFile(const LiveDocument ¤tFile) { + if (!currentFile.isFileIn(m_engine->workspace())) + return; + for (int i=0; i < m_model->rowCount(); i++) { HostWidget *widget = qobject_cast<HostWidget*>(indexWidget(m_model->index(i, 0))); if (widget) @@ -147,14 +154,15 @@ void HostManager::addHost(int index) widget->setLiveHubEngine(m_engine.data()); widget->setHost(host); setIndexWidget(m_model->index(index,0), widget); - connect(widget, SIGNAL(openHostConfig(Host*)), this, SIGNAL(openHostConfig(Host*))); + connect(widget, &HostWidget::openHostConfig, this, &HostManager::openHostConfig); QDockWidget *dock = new QDockWidget(host->name()); dock->setObjectName(host->name() + "LogDock"); - connect(host, SIGNAL(nameChanged(QString)), dock, SLOT(setWindowTitle(QString))); + connect(host, &Host::nameChanged, dock, &QDockWidget::setWindowTitle); LogView *view = new LogView(false, dock); - connect(widget, SIGNAL(remoteLog(int,QString,QUrl,int,int)), view, SLOT(appendToLog(int,QString,QUrl,int,int))); - connect(widget, SIGNAL(clearLog()), view, SLOT(clear())); + connect(widget, &HostWidget::remoteLog, view, &LogView::appendToLog); + connect(widget, &HostWidget::clearLog, view, &LogView::clear); + connect(widget, &HostWidget::connected, view, &LogView::clear); dock->setWidget(view); m_logList.append(dock); emit logWidgetAdded(dock); diff --git a/src/bench/hostmanager.h b/src/bench/hostmanager.h index f37b915..68f241e 100644 --- a/src/bench/hostmanager.h +++ b/src/bench/hostmanager.h @@ -34,6 +34,7 @@ #include <QListView> #include <QPointer> +class LiveDocument; class LiveHubEngine; class HostModel; class Host; @@ -54,8 +55,8 @@ signals: void openHostConfig(Host* host); public slots: - void followTreeSelection(const QString& currentFile); - void setCurrentFile(const QString& currentFile); + void followTreeSelection(const LiveDocument& currentFile); + void setCurrentFile(const LiveDocument& currentFile); void publishAll(); void refreshAll(); void probe(const QString &hostName); diff --git a/src/bench/hostmodel.cpp b/src/bench/hostmodel.cpp index a253614..98c102e 100644 --- a/src/bench/hostmodel.cpp +++ b/src/bench/hostmodel.cpp @@ -100,15 +100,15 @@ void HostModel::addHost(Host *host) beginInsertRows(QModelIndex(), m_hosts.count(), m_hosts.count()); m_hosts.append(host); - connect(host, SIGNAL(nameChanged(QString)), this, SLOT(onHostChanged())); - connect(host, SIGNAL(addressChanged(QString)), this, SLOT(onHostChanged())); - connect(host, SIGNAL(portChanged(int)), this, SLOT(onHostChanged())); - connect(host, SIGNAL(followTreeSelectionChanged(bool)), this, SLOT(onHostChanged())); - connect(host, SIGNAL(currentFileChanged(QString)), this, SLOT(onHostChanged())); - connect(host, SIGNAL(xOffsetChanged(int)), this, SLOT(onHostChanged())); - connect(host, SIGNAL(yOffsetChanged(int)), this, SLOT(onHostChanged())); - connect(host, SIGNAL(rotationChanged(int)), this, SLOT(onHostChanged())); - connect(host, SIGNAL(onlineChanged(bool)), this, SLOT(onHostChanged())); + connect(host, &Host::nameChanged, this, &HostModel::onHostChanged); + connect(host, &Host::addressChanged, this, &HostModel::onHostChanged); + connect(host, &Host::portChanged, this, &HostModel::onHostChanged); + connect(host, &Host::followTreeSelectionChanged, this, &HostModel::onHostChanged); + connect(host, &Host::currentFileChanged, this, &HostModel::onHostChanged); + connect(host, &Host::xOffsetChanged, this, &HostModel::onHostChanged); + connect(host, &Host::yOffsetChanged, this, &HostModel::onHostChanged); + connect(host, &Host::rotationChanged, this, &HostModel::onHostChanged); + connect(host, &Host::onlineChanged, this, &HostModel::onHostChanged); endInsertRows(); } diff --git a/src/bench/hostsoptionpage.cpp b/src/bench/hostsoptionpage.cpp index e463b44..c7ac997 100644 --- a/src/bench/hostsoptionpage.cpp +++ b/src/bench/hostsoptionpage.cpp @@ -48,20 +48,26 @@ HostsOptionsPage::HostsOptionsPage(QWidget *parent) : ui->hostUI->setVisible(false); - connect(ui->nameField, SIGNAL(textEdited(QString)), this, SLOT(updateName(QString))); - connect(ui->ipField, SIGNAL(textEdited(QString)), this, SLOT(updateAddress(QString))); - connect(ui->portField, SIGNAL(valueChanged(int)), this, SLOT(updatePort(int))); - connect(ui->followTreeSelectionField, SIGNAL(clicked(bool)), this, SLOT(updateFollowTreeSelection(bool))); - connect(ui->xField, SIGNAL(valueChanged(int)), this, SLOT(updateXOffset(int))); - connect(ui->yField, SIGNAL(valueChanged(int)), this, SLOT(updateYOffset(int))); - connect(ui->rotationField, SIGNAL(valueChanged(int)), this, SLOT(updateRotation(int))); + connect(ui->nameField, &QLineEdit::textEdited, this, &HostsOptionsPage::updateName); + connect(ui->ipField, &QLineEdit::textEdited, this, &HostsOptionsPage::updateAddress); + void (QSpinBox::*QSpinBox__valueChanged)(int) = &QSpinBox::valueChanged; + connect(ui->portField, QSpinBox__valueChanged, this, &HostsOptionsPage::updatePort); + connect(ui->followTreeSelectionField, &QCheckBox::clicked, this, &HostsOptionsPage::updateFollowTreeSelection); + connect(ui->xField, QSpinBox__valueChanged, this, &HostsOptionsPage::updateXOffset); + connect(ui->yField, QSpinBox__valueChanged, this, &HostsOptionsPage::updateYOffset); + connect(ui->rotationField, QSpinBox__valueChanged, this, &HostsOptionsPage::updateRotation); QMenu *menu = new QMenu(ui->addHostButton); +#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) menu->addAction("Auto Discovery...", this, SLOT(showAutoDiscoveryDialog())); menu->addAction("Manual", this, SLOT(addHost())); +#else + menu->addAction("Auto Discovery...", this, &HostsOptionsPage::showAutoDiscoveryDialog); + menu->addAction("Manual", this, [this] { addHost(); }); +#endif ui->addHostButton->setMenu(menu); - connect(ui->removeHostButton, SIGNAL(clicked()), this, SLOT(removeHost())); + connect(ui->removeHostButton, &QAbstractButton::clicked, this, &HostsOptionsPage::removeHost); } HostsOptionsPage::~HostsOptionsPage() @@ -74,8 +80,8 @@ void HostsOptionsPage::setHostModel(HostModel *model) m_model = model; m_currentIndex = -1; - connect(ui->hostsWidget->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), - this, SLOT(onCurrentRowChanged(QModelIndex,QModelIndex))); + connect(ui->hostsWidget->selectionModel(), &QItemSelectionModel::currentRowChanged, + this, &HostsOptionsPage::onCurrentRowChanged); for (int i=0; i< m_model->rowCount(); i++) { Host *host = m_model->hostAt(i); diff --git a/src/bench/hostwidget.cpp b/src/bench/hostwidget.cpp index 0da4f24..804de9b 100644 --- a/src/bench/hostwidget.cpp +++ b/src/bench/hostwidget.cpp @@ -34,6 +34,10 @@ #include "host.h" #include "livehubengine.h" +#include <QMessageBox> + +Q_DECLARE_LOGGING_CATEGORY(csLog) +Q_LOGGING_CATEGORY(csLog, "QmlLive.Bench.ConnectionState", QtInfoMsg) const int LABEL_STACK_INDEX=0; const int PROGRESS_STACK_INDEX=1; @@ -46,15 +50,15 @@ HostWidget::HostWidget(QWidget *parent) : m_connectDisconnectAction = new QAction("Offline", this); m_connectDisconnectAction->setIcon(QIcon(":images/error_ball.svg")); - connect(m_connectDisconnectAction, SIGNAL(triggered(bool)), this, SLOT(connectAndSendFile())); + connect(m_connectDisconnectAction, &QAction::triggered, this, &HostWidget::connectToServer); m_refreshAction = new QAction("Refresh", this); m_refreshAction->setIcon(QIcon(":images/refresh.svg")); - connect(m_refreshAction, SIGNAL(triggered(bool)), this, SLOT(refresh())); + connect(m_refreshAction, &QAction::triggered, this, &HostWidget::refresh); m_publishAction = new QAction("Publish", this); m_publishAction->setIcon(QIcon(":/images/publish.svg")); - connect(m_publishAction, SIGNAL(triggered(bool)), this, SLOT(publishAll())); + connect(m_publishAction, &QAction::triggered, this, &HostWidget::publishAll); m_followTreeSelectionAction = new QAction("Follow", this); m_followTreeSelectionAction->setIcon(QIcon(":images/linked.svg")); @@ -62,7 +66,7 @@ HostWidget::HostWidget(QWidget *parent) : m_editHostAction = new QAction("Setup", this); m_editHostAction->setIcon(QIcon(":images/edit.svg")); - connect(m_editHostAction, SIGNAL(triggered(bool)), this, SLOT(onEditHost())); + connect(m_editHostAction, &QAction::triggered, this, &HostWidget::onEditHost); QGridLayout *layout = new QGridLayout(this); layout->setContentsMargins(0,0,0,0); @@ -99,46 +103,43 @@ HostWidget::HostWidget(QWidget *parent) : vbox->addWidget(toolBar);; - connect(&m_publisher, SIGNAL(connected()), this, SIGNAL(connected())); - connect(&m_publisher, SIGNAL(connected()), this, SLOT(onConnected())); - connect(&m_publisher, SIGNAL(disconnected()), this, SLOT(onDisconnected())); - connect(&m_publisher, SIGNAL(connectionError(QAbstractSocket::SocketError)), - this, SLOT(onConnectionError(QAbstractSocket::SocketError))); - connect(&m_publisher, SIGNAL(sendingError(QUuid,QAbstractSocket::SocketError)), - this, SLOT(onSendingError(QUuid,QAbstractSocket::SocketError))); - connect(&m_publisher, SIGNAL(sentSuccessfully(QUuid)), - this, SLOT(onSentSuccessfully(QUuid))); - connect(&m_publisher, SIGNAL(needsPinAuthentication()), this, SLOT(showPinDialog())); - connect(&m_publisher, SIGNAL(pinOk(bool)), this, SLOT(onPinOk(bool))); - connect(&m_publisher, SIGNAL(remoteLog(int,QString,QUrl,int,int)), - this, SIGNAL(remoteLog(int,QString,QUrl,int,int))); - connect(&m_publisher, SIGNAL(clearLog()), this, SIGNAL(clearLog())); - - onDisconnected(); + connect(&m_publisher, &RemotePublisher::connected, this, &HostWidget::connected); + connect(&m_publisher, &RemotePublisher::connected, this, &HostWidget::onConnected); + connect(&m_publisher, &RemotePublisher::disconnected, this, &HostWidget::onDisconnected); + connect(&m_publisher, &RemotePublisher::connectionError, this, &HostWidget::onConnectionError); + connect(&m_publisher, &RemotePublisher::sendingError, this, &HostWidget::onSendingError); + connect(&m_publisher, &RemotePublisher::sentSuccessfully, this, &HostWidget::onSentSuccessfully); + connect(&m_publisher, &RemotePublisher::needsPinAuthentication, this, &HostWidget::showPinDialog); + connect(&m_publisher, &RemotePublisher::pinOk, this, &HostWidget::onPinOk); + connect(&m_publisher, &RemotePublisher::remoteLog, this, &HostWidget::remoteLog); + connect(&m_publisher, &RemotePublisher::clearLog, this, &HostWidget::clearLog); } void HostWidget::setHost(Host *host) { m_host = host; - updateName(m_host->name()); - updateIp(m_host->address()); + updateTitle(); updateFile(m_host->currentFile()); - updateOnlineState(m_host->online()); m_followTreeSelectionAction->setChecked(m_host->followTreeSelection()); - connect(host, SIGNAL(addressChanged(QString)), this, SLOT(updateIp(QString))); - connect(host, SIGNAL(portChanged(int)), this, SLOT(updatePort(int))); - connect(host, SIGNAL(onlineChanged(bool)), this, SLOT(updateOnlineState(bool))); - connect(host, SIGNAL(currentFileChanged(QString)), this, SLOT(updateFile(QString))); - connect(host, SIGNAL(nameChanged(QString)), this, SLOT(updateName(QString))); - connect(host, SIGNAL(xOffsetChanged(int)), this, SLOT(sendXOffset(int))); - connect(host, SIGNAL(yOffsetChanged(int)), this, SLOT(sendYOffset(int))); - connect(host, SIGNAL(rotationChanged(int)), this, SLOT(sendRotation(int))); - connect(host, SIGNAL(followTreeSelectionChanged(bool)), - m_followTreeSelectionAction, SLOT(setChecked(bool))); + connect(host, &Host::addressChanged, this, &HostWidget::updateTitle); + connect(host, &Host::addressChanged, this, &HostWidget::scheduleConnectToServer); + connect(host, &Host::portChanged, this, &HostWidget::updateTitle); + connect(host, &Host::portChanged, this, &HostWidget::scheduleConnectToServer); + connect(host, &Host::availableChanged, this, &HostWidget::updateAvailableState); + connect(host, &Host::currentFileChanged, this, &HostWidget::updateFile); + connect(host, &Host::nameChanged, this, &HostWidget::updateTitle); + connect(host, &Host::xOffsetChanged, this, &HostWidget::sendXOffset); + connect(host, &Host::yOffsetChanged, this, &HostWidget::sendYOffset); + connect(host, &Host::rotationChanged, this, &HostWidget::sendRotation); + connect(host, &Host::followTreeSelectionChanged, this, &HostWidget::updateFollowTreeSelection); - connect(m_followTreeSelectionAction, SIGNAL(triggered(bool)), host, SLOT(setFollowTreeSelection(bool))); + connect(m_followTreeSelectionAction, &QAction::triggered, host, &Host::setFollowTreeSelection); + connect(&m_publisher, &RemotePublisher::activeDocumentChanged, host, &Host::setCurrentFile); + + updateAvailableState(m_host->available()); + updateRemoteActions(); } void HostWidget::setLiveHubEngine(LiveHubEngine *engine) @@ -147,16 +148,20 @@ void HostWidget::setLiveHubEngine(LiveHubEngine *engine) m_publisher.setWorkspace(m_engine->workspace()); - connect(m_engine.data(), SIGNAL(workspaceChanged(QString)), &m_publisher, SLOT(setWorkspace(QString))); - connect(m_engine.data(), SIGNAL(fileChanged(QString)), this, SLOT(sendDocument(QString))); - connect(m_engine.data(), SIGNAL(beginPublishWorkspace()), &m_publisher, SLOT(beginBulkSend())); - connect(m_engine.data(), SIGNAL(endPublishWorkspace()), &m_publisher, SLOT(endBulkSend())); - connect(&m_publisher, SIGNAL(needsPublishWorkspace()), this, SLOT(publishWorkspace())); + connect(m_engine.data(), &LiveHubEngine::workspaceChanged, &m_publisher, &RemotePublisher::setWorkspace); + connect(m_engine.data(), &LiveHubEngine::workspaceChanged, this, &HostWidget::refreshDocumentLabel); + connect(m_engine.data(), &LiveHubEngine::fileChanged, this, &HostWidget::sendDocument); + connect(m_engine.data(), &LiveHubEngine::beginPublishWorkspace, &m_publisher, &RemotePublisher::beginBulkSend); + connect(m_engine.data(), &LiveHubEngine::endPublishWorkspace, &m_publisher, &RemotePublisher::endBulkSend); + connect(&m_publisher, &RemotePublisher::needsPublishWorkspace, this, &HostWidget::publishWorkspace); } -void HostWidget::setCurrentFile(const QString currentFile) +void HostWidget::setCurrentFile(const LiveDocument ¤tFile) { - m_host->setCurrentFile(currentFile); + if (m_publisher.state() != QAbstractSocket::ConnectedState) + return; + + m_publisher.activateDocument(currentFile); } bool HostWidget::followTreeSelection() const @@ -164,70 +169,104 @@ bool HostWidget::followTreeSelection() const return m_followTreeSelectionAction->isChecked(); } -void HostWidget::updateName(const QString &name) +void HostWidget::updateTitle() { - Q_UNUSED(name) - if(m_host) { - m_groupBox->setTitle(QString("%1 (%2:%3)").arg(m_host->name(), m_host->address(), QString::number(m_host->port()))); - } + m_groupBox->setTitle(QString("%1 (%2:%3)") + .arg(m_host->name()) + .arg(m_host->address()) + .arg(m_host->port())); } -void HostWidget::updateIp(const QString &ip) +void HostWidget::updateFile(const LiveDocument &file) { - Q_UNUSED(ip) - if(m_host) { - m_groupBox->setTitle(QString("%1 (%2:%3)").arg(m_host->name(), m_host->address(), QString::number(m_host->port()))); + QFont font(this->font()); + QPalette palette(this->palette()); + QString text; + QString toolTip; + + if (file.isNull()) { + if (m_publisher.state() != QAbstractSocket::ConnectedState) { + text = tr("Host offline"); + } else { + text = tr("No active document"); + toolTip = tr("No active document. Drop one."); + } + font.setItalic(true); + } else { + text = file.relativeFilePath(); + + if (file.isFileIn(m_engine->workspace())) { + toolTip = file.absoluteFilePathIn(m_engine->workspace()); + } else { + // red color from http://clrs.cc + palette.setColor(QPalette::Text, QColor(0xff, 0x41, 0x36)); + toolTip = file.errorString(); + } } - QTimer::singleShot(0, this, SLOT(connectToServer())); -} + QFontMetrics metrics(font); + m_documentLabel->setFont(font); + m_documentLabel->setPalette(palette); + m_documentLabel->setText(metrics.elidedText(text, Qt::ElideLeft, + m_documentLabel->width())); + m_documentLabel->setToolTip(toolTip); -void HostWidget::updatePort(int port) -{ - Q_UNUSED(port) - if(m_host) { - m_groupBox->setTitle(QString("%1 (%2:%3)").arg(m_host->name(), m_host->address(), QString::number(m_host->port()))); + if (m_host->followTreeSelection() && !file.isNull() && file != m_engine->activePath()) { + if (m_publisher.state() == QAbstractSocket::ConnectedState) + m_publisher.activateDocument(m_engine->activePath()); } - QTimer::singleShot(0, this, SLOT(connectToServer())); + updateRemoteActions(); +} + +void HostWidget::refreshDocumentLabel() +{ + updateFile(m_host->currentFile()); } -void HostWidget::updateFile(const QString &file) +void HostWidget::updateAvailableState(bool available) { - QString relFile = QDir(m_engine->workspace()).relativeFilePath(file); - setUpdateFile(relFile); - m_documentLabel->setToolTip(relFile); + qCDebug(csLog) << "updateAvailableState()" << available; - connectAndSendFile(); + if (available) + scheduleConnectToServer(); + else + onDisconnected(); } -void HostWidget::setUpdateFile(const QString &file) +void HostWidget::updateFollowTreeSelection(bool follow) { - QFontMetrics metrics(font()); - m_documentLabel->setText(metrics.elidedText(file, Qt::ElideLeft, m_documentLabel->width())); + m_followTreeSelectionAction->setChecked(follow); + + if (follow && m_publisher.state() == QAbstractSocket::ConnectedState + && m_host->currentFile() != m_engine->activePath()) { + m_publisher.activateDocument(m_engine->activePath()); + } } -void HostWidget::updateOnlineState(bool online) +void HostWidget::updateRemoteActions() { - qDebug() << "updateOnline"; + m_refreshAction->setEnabled(m_publisher.state() == QAbstractSocket::ConnectedState + && !m_host->currentFile().isNull()); + m_publishAction->setEnabled(m_publisher.state() == QAbstractSocket::ConnectedState); +} - bool available = online || m_host->type() == Host::Manual; +void HostWidget::scheduleConnectToServer() +{ + qCDebug(csLog) << "scheduleConnectToServer()" << m_host->name(); - if (available) - QTimer::singleShot(0, this, SLOT(connectToServer())); - else - onDisconnected(); + m_connectToServerTimer.start(0, this); } void HostWidget::connectToServer() { - qDebug() << "connectToServer"; + qCDebug(csLog) << "connectToServer()" << m_host->name() + << m_publisher.state() << "available:" << m_host->available(); - if (m_publisher.state() == QAbstractSocket::ConnectedState || m_publisher.state() == QAbstractSocket::ConnectingState) { + if (m_publisher.state() != QAbstractSocket::UnconnectedState) return; - } - if (m_host->online() || m_host->type() == Host::Manual) { + if (m_host->available()) { m_publisher.connectToServer(m_host->address(), m_host->port()); m_activateId = QUuid(); m_rotationId = QUuid(); @@ -237,39 +276,45 @@ void HostWidget::connectToServer() } } -void HostWidget::connectAndSendFile() -{ - connectToServer(); - m_activateId = m_publisher.activateDocument(QDir(m_engine->workspace()).relativeFilePath(m_host->currentFile())); -} - void HostWidget::onConnected() { + qCDebug(csLog) << "Host connected:" << m_host->name(); + m_connectDisconnectAction->setIcon(QIcon(":images/okay_ball.svg")); m_connectDisconnectAction->setToolTip("Host online"); m_connectDisconnectAction->setText("Online"); + updateFile(m_host->currentFile()); + updateRemoteActions(); + sendXOffset(m_host->xOffset()); sendYOffset(m_host->yOffset()); sendRotation(m_host->rotation()); - disconnect(m_connectDisconnectAction, SIGNAL(triggered()), 0, 0); - connect(m_connectDisconnectAction, SIGNAL(triggered()), &m_publisher, SLOT(disconnectFromServer())); + disconnect(m_connectDisconnectAction, &QAction::triggered, 0, 0); + connect(m_connectDisconnectAction, &QAction::triggered, &m_publisher, &RemotePublisher::disconnectFromServer); } void HostWidget::onDisconnected() { + qCDebug(csLog) << "Host disconnected:" << m_host->name(); + m_connectDisconnectAction->setIcon(QIcon(":images/error_ball.svg")); m_connectDisconnectAction->setToolTip("Host Offline"); m_connectDisconnectAction->setText("Offline"); resetProgressBar(); - disconnect(m_connectDisconnectAction, SIGNAL(triggered()), 0, 0); - connect(m_connectDisconnectAction, SIGNAL(triggered()), this, SLOT(connectToServer())); + m_host->setCurrentFile(LiveDocument()); + updateRemoteActions(); + + disconnect(m_connectDisconnectAction, &QAction::triggered, 0, 0); + connect(m_connectDisconnectAction, &QAction::triggered, this, &HostWidget::connectToServer); } void HostWidget::onConnectionError(QAbstractSocket::SocketError error) { + qCDebug(csLog) << "Host connection error:" << m_host->name() << error; + m_connectDisconnectAction->setToolTip(m_publisher.errorToString(error)); m_connectDisconnectAction->setIcon(QIcon(":images/warning_ball.svg")); @@ -282,7 +327,11 @@ void HostWidget::onConnectionError(QAbstractSocket::SocketError error) void HostWidget::refresh() { - connectAndSendFile(); + if (m_publisher.state() != QAbstractSocket::ConnectedState) + return; + + if (!m_host->currentFile().isNull()) + m_publisher.activateDocument(m_host->currentFile()); } void HostWidget::probe() @@ -292,13 +341,15 @@ void HostWidget::probe() void HostWidget::publishWorkspace() { - connectToServer(); - connect(m_engine.data(), SIGNAL(publishFile(QString)), this, SLOT(sendDocument(QString))); + if (m_publisher.state() != QAbstractSocket::ConnectedState) + return; + + connect(m_engine.data(), &LiveHubEngine::publishFile, this, &HostWidget::sendDocument); m_engine->publishWorkspace(); - disconnect(m_engine.data(), SIGNAL(publishFile(QString)), this, SLOT(sendDocument(QString))); + disconnect(m_engine.data(), &LiveHubEngine::publishFile, this, &HostWidget::sendDocument); } -void HostWidget::sendDocument(const QString& document) +void HostWidget::sendDocument(const LiveDocument& document) { if (m_publisher.state() != QAbstractSocket::ConnectedState) return; @@ -407,7 +458,7 @@ void HostWidget::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); - setUpdateFile(m_documentLabel->toolTip()); + refreshDocumentLabel(); } void HostWidget::showPinDialog() @@ -423,19 +474,33 @@ void HostWidget::showPinDialog() void HostWidget::dragEnterEvent(QDragEnterEvent *event) { - if (event->mimeData()->hasFormat("text/uri-list") && (m_host->online() || m_host->type() == Host::Manual)) { + if (m_publisher.state() != QAbstractSocket::ConnectedState) + return; + if (event->mimeData()->hasUrls()) event->acceptProposedAction(); - } } void HostWidget::dropEvent(QDropEvent *event) { - QUrl url(event->mimeData()->text()); + if (m_publisher.state() != QAbstractSocket::ConnectedState) + return; - if (url.isLocalFile()) - m_host->setCurrentFile(url.toLocalFile()); event->acceptProposedAction(); + + QUrl url(event->mimeData()->urls().first()); + if (url.isLocalFile()) { + LiveDocument document = LiveDocument::resolve(m_engine->workspace(), url.toLocalFile()); + if (!document.isNull() && document.isFileIn(m_engine->workspace())) { + if (m_host->followTreeSelection() && document != m_engine->activePath()) + m_host->setFollowTreeSelection(false); + m_publisher.activateDocument(document); + } else { + QMessageBox::warning(this, tr("Not a workspace document"), + tr("The dropped document is not a file in the current workspace:<br/>%1") + .arg(url.toString())); + } + } } bool HostWidget::eventFilter(QObject *object, QEvent *event) @@ -449,3 +514,11 @@ bool HostWidget::eventFilter(QObject *object, QEvent *event) return false; } + +void HostWidget::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_connectToServerTimer.timerId()) { + m_connectToServerTimer.stop(); + connectToServer(); + } +} diff --git a/src/bench/hostwidget.h b/src/bench/hostwidget.h index 42188e7..7bb3366 100644 --- a/src/bench/hostwidget.h +++ b/src/bench/hostwidget.h @@ -35,6 +35,7 @@ #include <remotepublisher.h> class Host; +class LiveDocument; class HostWidget : public QWidget { @@ -45,7 +46,7 @@ public: void setHost(Host* host); void setLiveHubEngine(LiveHubEngine* engine); - void setCurrentFile(const QString currentFile); + void setCurrentFile(const LiveDocument ¤tFile); bool followTreeSelection() const; signals: @@ -63,23 +64,24 @@ protected: void dragEnterEvent(QDragEnterEvent *event); void dropEvent(QDropEvent *event); bool eventFilter(QObject *, QEvent *); + void timerEvent(QTimerEvent *event); private slots: - void updateName(const QString& name); - void updateIp(const QString& ip); - void updatePort(int port); - void updateFile(const QString& file); - void setUpdateFile(const QString& file); - void updateOnlineState(bool online); - + void updateTitle(); + void updateFile(const LiveDocument& file); + void refreshDocumentLabel(); + void updateAvailableState(bool available); + void updateFollowTreeSelection(bool follow); + void updateRemoteActions(); + + void scheduleConnectToServer(); void connectToServer(); - void connectAndSendFile(); void onConnected(); void onDisconnected(); void onConnectionError(QAbstractSocket::SocketError error); - void sendDocument(const QString &document); + void sendDocument(const LiveDocument &document); void sendXOffset(int offset); void sendYOffset(int offset); @@ -116,6 +118,7 @@ private: RemotePublisher m_publisher; QPointer<LiveHubEngine> m_engine; + QBasicTimer m_connectToServerTimer; QUuid m_activateId; QList<QUuid> m_changeIds; diff --git a/src/bench/importpathoptionpage.cpp b/src/bench/importpathoptionpage.cpp index 151e919..34fd3e1 100644 --- a/src/bench/importpathoptionpage.cpp +++ b/src/bench/importpathoptionpage.cpp @@ -48,9 +48,9 @@ ImportPathOptionPage::ImportPathOptionPage(QWidget *parent) : } s.endArray(); - connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addItem())); - connect(ui->removeButton, SIGNAL(clicked()), this, SLOT(removeItem())); - connect(ui->editButton, SIGNAL(clicked()), this, SLOT(editItem())); + connect(ui->addButton, &QAbstractButton::clicked, this, &ImportPathOptionPage::addItem); + connect(ui->removeButton, &QAbstractButton::clicked, this, &ImportPathOptionPage::removeItem); + connect(ui->editButton, &QAbstractButton::clicked, this, &ImportPathOptionPage::editItem); } ImportPathOptionPage::~ImportPathOptionPage() diff --git a/src/bench/main.cpp b/src/bench/main.cpp index 62b8bb3..9c8b519 100644 --- a/src/bench/main.cpp +++ b/src/bench/main.cpp @@ -29,6 +29,8 @@ ** ****************************************************************************/ +#include <functional> + #include <QtGui> #include <QtWidgets> @@ -166,6 +168,7 @@ void Application::setDarkStyle() palette.setColor(QPalette::Text, QColor("#F0F0F0")); palette.setColor(QPalette::Button, QColor("#353535")); palette.setColor(QPalette::ButtonText, QColor("#FFFFFF")); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor("#A0A0A0")); palette.setColor(QPalette::BrightText, QColor("#D0021B")); palette.setColor(QPalette::Highlight, QColor("#F19300")); palette.setColor(QPalette::HighlightedText, QColor("#1C1C1C")); @@ -181,9 +184,9 @@ void Application::parseArguments(const QStringList &arguments, Options *options) parser.addPositionalArgument("workspace", "workspace folder to watch. If this points to a QML document, than the directory is asssumed to be the workspace and the file the active document."); parser.addPositionalArgument("document", "main QML document to load initially."); - QCommandLineOption pluginPathOption("pluginpath", "path to qmllive plugins", "pluginpath"); + QCommandLineOption pluginPathOption("pluginpath", "path to QmlLive plugins", "pluginpath"); parser.addOption(pluginPathOption); - QCommandLineOption importPathOption("importpath", "path to qml import path. Can appear multiple times", "importpath"); + QCommandLineOption importPathOption("importpath", "path to QML import path. Can appear multiple times", "importpath"); parser.addOption(importPathOption); QCommandLineOption stayOnTopOption("stayontop", "keep viewer window on top"); parser.addOption(stayOnTopOption); @@ -271,7 +274,7 @@ void Application::parseArguments(const QStringList &arguments, Options *options) parser.showHelp(-1); } options->setWorkspace(fi.absolutePath()); - options->setActiveDocument(fi.absoluteFilePath()); + options->setActiveDocument(LiveDocument(fi.absoluteFilePath())); } else { qDebug() << "First argument does not ending with \".qml\". Assuming it is a workspace."; if (!fi.exists() || !fi.isDir()) { @@ -286,11 +289,12 @@ void Application::parseArguments(const QStringList &arguments, Options *options) QFileInfo fi(argument); if (argument.endsWith(".qml")) { qDebug() << "Second argument ends with \".qml\". Assuming it is a file."; - if (!fi.exists() || !fi.isFile()) { - qWarning() << "Document does not exist or is not a file: " << fi.absoluteFilePath(); + LiveDocument document = LiveDocument::resolve(options->workspace(), argument); + if (document.isNull() || !document.isFileIn(options->workspace())) { + qWarning() << document.errorString(); parser.showHelp(-1); } - options->setActiveDocument(fi.absoluteFilePath()); + options->setActiveDocument(document); } else { qWarning() << "If second argument is present it needs to be a QML document: " << fi.absoluteFilePath(); parser.showHelp(-1); @@ -381,7 +385,7 @@ void MasterApplication::listenForArguments() void MasterApplication::applyOptions(const Options &options) { if (!options.workspace().isEmpty()) - m_window->setWorkspace(QDir(options.workspace()).absolutePath()); + m_window->setWorkspace(QDir(options.workspace()).absolutePath(), false); if (!options.pluginPath().isEmpty()) { if (!m_window->isInitialized()) @@ -397,7 +401,7 @@ void MasterApplication::applyOptions(const Options &options) qDebug() << "Ignoring attempt to set import paths after initialization."; } - if (!options.activeDocument().isEmpty()) { + if (!options.activeDocument().isNull()) { m_window->activateDocument(options.activeDocument()); } diff --git a/src/bench/mainwindow.cpp b/src/bench/mainwindow.cpp index ac418c9..61924aa 100644 --- a/src/bench/mainwindow.cpp +++ b/src/bench/mainwindow.cpp @@ -77,17 +77,17 @@ MainWindow::MainWindow(QWidget *parent) m_hub->setFilePublishingActive(true); m_node->setWorkspaceView(m_workspace); - connect(m_workspace, SIGNAL(pathActivated(QString)), m_hub, SLOT(setActivePath(QString))); - connect(m_workspace, SIGNAL(pathActivated(QString)), m_hostManager, SLOT(followTreeSelection(QString))); - connect(m_hub, SIGNAL(activateDocument(QString)), this, SLOT(updateWindowTitle())); - connect(m_hub, SIGNAL(activateDocument(QString)), m_node, SLOT(setActiveDocument(QString))); - connect(m_node, SIGNAL(activeWindowChanged(QQuickWindow*)), this, SLOT(onActiveWindowChanged(QQuickWindow*))); - connect(m_node->qmlEngine(), SIGNAL(quit()), this, SLOT(logQuitEvent())); - connect(m_allHosts, SIGNAL(publishAll()), m_hostManager, SLOT(publishAll())); - connect(m_allHosts, SIGNAL(currentFileChanged(QString)), m_hostManager, SLOT(setCurrentFile(QString))); - connect(m_allHosts, SIGNAL(refreshAll()), m_hostManager, SLOT(refreshAll())); - connect(m_hostManager, SIGNAL(logWidgetAdded(QDockWidget*)), this, SLOT(onLogWidgetAdded(QDockWidget*))); - connect(m_hostManager, SIGNAL(openHostConfig(Host*)), this, SLOT(openPreferences(Host*))); + connect(m_workspace, &WorkspaceView::pathActivated, m_hub, &LiveHubEngine::setActivePath); + connect(m_workspace, &WorkspaceView::pathActivated, m_hostManager, &HostManager::followTreeSelection); + connect(m_hub, &LiveHubEngine::activateDocument, this, &MainWindow::updateWindowTitle); + connect(m_hub, &LiveHubEngine::activateDocument, m_node, &LiveNodeEngine::loadDocument); + connect(m_node, &LiveNodeEngine::activeWindowChanged, this, &MainWindow::onActiveWindowChanged); + connect(m_node->qmlEngine(), &QQmlEngine::quit, this, &MainWindow::logQuitEvent); + connect(m_allHosts, &AllHostsWidget::publishAll, m_hostManager, &HostManager::publishAll); + connect(m_allHosts, &AllHostsWidget::currentFileChanged, m_hostManager, &HostManager::setCurrentFile); + 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); m_qmlDefaultimportList = m_node->qmlEngine()->importPathList(); } @@ -168,34 +168,78 @@ void MainWindow::setupLogView() m_logDock->setFeatures(QDockWidget::AllDockWidgetFeatures); addDockWidget(Qt::BottomDockWidgetArea, m_logDock); - connect(m_node, SIGNAL(clearLog()), m_log, SLOT(clear())); - connect(m_node, SIGNAL(logIgnoreMessages(bool)), m_log, SLOT(setIgnoreMessages(bool))); - connect(m_node, SIGNAL(logErrors(QList<QQmlError>)), m_log, SLOT(appendToLog(QList<QQmlError>))); + connect(m_node, &LiveNodeEngine::clearLog, m_log, &LogView::clear); + connect(m_node, &LiveNodeEngine::logIgnoreMessages, m_log, &LogView::setIgnoreMessages); + connect(m_node, &LiveNodeEngine::logErrors, m_log, &LogView::appendAllToLog); } 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); +#else + m_openWorkspace = file->addAction(QIcon::fromTheme("folder-open"), tr("&Open Workspace..."), + this, &MainWindow::openWorkspace, QKeySequence::Open); +#endif m_recentMenu = file->addMenu(QIcon::fromTheme("document-open-recent"), "&Recent"); m_recentMenu->setEnabled(false); +#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) file->addAction(tr("Preferences..."), this, SLOT(openPreferences()), QKeySequence::Preferences); +#else + file->addAction(tr("Preferences..."), this, [this] { openPreferences(); }, + QKeySequence::Preferences); +#endif file->addSeparator(); +#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) file->addAction(QIcon::fromTheme("application-exit"), tr("&Quit"), this, SLOT(close()), QKeySequence::Quit); +#else + file->addAction(QIcon::fromTheme("application-exit"), tr("&Quit"), + this, &MainWindow::close, QKeySequence::Quit); +#endif QMenu *view = menuBar()->addMenu(tr("&View")); +#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) view->addAction(tr("Take Snapshot"), this, SLOT(takeSnapshot()), QKeySequence("Ctrl+F3")); +#else + view->addAction(tr("Take Snapshot"), this, &MainWindow::takeSnapshot, QKeySequence("Ctrl+F3")); +#endif view->addSeparator(); +#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) QAction *slow = view->addAction(tr("Slow Down Animations"), this, SLOT(slowDownAnimations(bool)), QKeySequence(tr("Ctrl+."))); +#else + QAction *slow = view->addAction(tr("Slow Down Animations"), + this, &MainWindow::slowDownAnimations, + QKeySequence(tr("Ctrl+."))); +#endif slow->setCheckable(true); view->addSeparator(); +#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) m_refresh = view->addAction(QIcon::fromTheme("view-refresh"), tr("Refresh"), m_node, SLOT(refresh()), QKeySequence::Refresh); +#else + m_refresh = view->addAction(QIcon::fromTheme("view-refresh"), tr("Refresh"), + m_node, &BenchLiveNodeEngine::refresh, QKeySequence::Refresh); +#endif +#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) m_resizeFit = view->addAction(QIcon::fromTheme("zoom-fit-best"), tr("Resize to Fit"), this, SLOT(resizeToFit())); +#else + m_resizeFit = view->addAction(QIcon::fromTheme("zoom-fit-best"), tr("Resize to Fit"), + this, &MainWindow::resizeToFit); +#endif +#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) view->addAction(tr("Show Containing Folder"), m_workspace, SLOT(goUp()), QKeySequence("Ctrl+Esc")); +#else + view->addAction(tr("Show Containing Folder"), m_workspace, &WorkspaceView::goUp, + QKeySequence("Ctrl+Esc")); +#endif +#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) m_stayOnTop = view->addAction(tr("Stay on Top"), this, SLOT(stayOnTop())); +#else + m_stayOnTop = view->addAction(tr("Stay on Top"), this, &MainWindow::stayOnTop); +#endif m_stayOnTop->setCheckable(true); view->addSeparator(); @@ -205,7 +249,7 @@ void MainWindow::setupMenuBar() m_logDockMenu->addAction(m_logDock->toggleViewAction()); QMenu *help = menuBar()->addMenu(tr("&Help")); - QAction *about = help->addAction(tr("About Qt QML Live...")); + QAction *about = help->addAction(tr("About Qt QmlLive...")); connect(about, &QAction::triggered, this, [this]() { AboutDialog::exec(this); }); about->setMenuRole(QAction::AboutRole); } @@ -216,7 +260,7 @@ void MainWindow::init() restoreGeometry(s.value("geometry").toByteArray()); //Only set the workspace if we didn't already set it by command line if (m_workspacePath.isEmpty()) { - setWorkspace(s.value("workspace", QDir::currentPath()).toString()); + setWorkspace(s.value("workspace", QDir::currentPath()).toString(), false); } if (s.value("http_proxy/enabled").toBool()) { @@ -240,13 +284,15 @@ void MainWindow::init() updateRecentFolder(); - //Only set the workspace if we didn't already set it by command line - if (m_node->activeDocument().isEmpty()) { - if (s.contains("activeDocument")) { - activateDocument(s.value("activeDocument").toString()); - } else { + //Only set the document if we didn't already set it by command line + if (m_node->activeDocument().isNull()) { + LiveDocument last; + if (s.contains("activeDocument")) + last = LiveDocument::resolve(m_workspacePath, s.value("activeDocument").toString()); + if (!last.isNull()) + activateDocument(last); + else m_workspace->activateRootPath(); - } } resetImportPaths(); @@ -263,7 +309,8 @@ void MainWindow::writeSettings() s.setValue("geometry", saveGeometry()); s.setValue("windowState", saveState()); s.setValue("workspace", m_workspacePath); - s.setValue("activeDocument", m_node->activeDocument().toLocalFile()); + if (!m_node->activeDocument().isNull()) + s.setValue("activeDocument", m_node->activeDocument().relativeFilePath()); s.beginWriteArray("recentFolder"); for (int i = 0; i < m_recentFolder.count(); i++) { @@ -300,7 +347,7 @@ void MainWindow::setupToolBar() m_toolBar->addAction(m_resizeFit); } -void MainWindow::activateDocument(const QString path) +void MainWindow::activateDocument(const LiveDocument &path) { m_workspace->activateDocument(path); } @@ -338,13 +385,15 @@ void MainWindow::slowDownAnimations(bool enable) } -void MainWindow::setWorkspace(const QString& path) +void MainWindow::setWorkspace(const QString& path, bool activateRootPath) { m_workspacePath = path; m_workspace->setRootPath(path); m_node->setWorkspace(path); m_hub->setWorkspace(path); m_allHosts->setWorkspace(path); + if (activateRootPath) + m_workspace->activateRootPath(); updateRecentFolder(path); } @@ -393,11 +442,11 @@ void MainWindow::logQuitEvent() void MainWindow::updateWindowTitle() { setWindowFilePath(QString()); - if (m_hub->activePath().isEmpty()) { + if (m_hub->activePath().isNull()) { setWindowTitle(QApplication::applicationName()); } else { setWindowTitle(QString()); - setWindowFilePath(m_hub->activePath()); + setWindowFilePath(m_hub->activePath().absoluteFilePathIn(m_workspacePath)); } } @@ -443,11 +492,19 @@ void MainWindow::updateRecentFolder(const QString& path) m_recentMenu->clear(); foreach (const QString file, m_recentFolder) { +#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) m_recentMenu->addAction(file, this, SLOT(openRecentFolder())); +#else + m_recentMenu->addAction(file, this, &MainWindow::openRecentFolder); +#endif } m_recentMenu->addSeparator(); +#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) m_recentMenu->addAction("Clear Menu", this, SLOT(clearRecentFolder())); +#else + m_recentMenu->addAction("Clear Menu", this, &MainWindow::clearRecentFolder); +#endif } void MainWindow::stayOnTop() diff --git a/src/bench/mainwindow.h b/src/bench/mainwindow.h index f410121..4a7e8f1 100644 --- a/src/bench/mainwindow.h +++ b/src/bench/mainwindow.h @@ -39,6 +39,7 @@ class BenchQuickView; class WorkspaceView; class LogView; +class LiveDocument; class LiveRuntime; class LiveHubEngine; class BenchLiveNodeEngine; @@ -57,8 +58,8 @@ class MainWindow : public QMainWindow public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); - void activateDocument(const QString path); - void setWorkspace(const QString& path); + void activateDocument(const LiveDocument &path); + void setWorkspace(const QString& path, bool activateRootPath = true); void setPluginPath(const QString& path); void setImportPaths(const QStringList& pathList); void setStaysOnTop(bool enabled); diff --git a/src/bench/options.cpp b/src/bench/options.cpp index 7fa8386..5baedc2 100644 --- a/src/bench/options.cpp +++ b/src/bench/options.cpp @@ -85,12 +85,12 @@ void Options::setPing(bool ping) m_ping = ping; } -QString Options::activeDocument() const +LiveDocument Options::activeDocument() const { return m_activeDocument; } -void Options::setActiveDocument(const QString &activeDocument) +void Options::setActiveDocument(const LiveDocument &activeDocument) { m_activeDocument = activeDocument; } diff --git a/src/bench/options.h b/src/bench/options.h index 2760059..e7c84bd 100644 --- a/src/bench/options.h +++ b/src/bench/options.h @@ -31,6 +31,8 @@ #pragma once +#include "livedocument.h" + #include <QtCore> class Options : public QObject @@ -58,8 +60,8 @@ public: bool ping() const; void setPing(bool ping); - QString activeDocument() const; - void setActiveDocument(const QString &activeDocument); + LiveDocument activeDocument() const; + void setActiveDocument(const LiveDocument &activeDocument); QString workspace() const; void setWorkspace(const QString &workspace); @@ -88,7 +90,7 @@ private: bool m_noRemote; bool m_remoteOnly; bool m_ping; - QString m_activeDocument; + LiveDocument m_activeDocument; QString m_workspace; QString m_pluginPath; QStringList m_importPaths; diff --git a/src/bench/optionsdialog.cpp b/src/bench/optionsdialog.cpp index ef99807..00c5898 100644 --- a/src/bench/optionsdialog.cpp +++ b/src/bench/optionsdialog.cpp @@ -60,8 +60,8 @@ OptionsDialog::OptionsDialog(QWidget *parent) item->setData(Qt::UserRole, index); ui->optionsView->addItem(item); - connect(ui->optionsView, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), - this, SLOT(optionSelected(QListWidgetItem*))); + connect(ui->optionsView, &QListWidget::currentItemChanged, + this, &OptionsDialog::optionSelected); } OptionsDialog::~OptionsDialog() diff --git a/src/ipc/ipcclient.cpp b/src/ipc/ipcclient.cpp index 50ef363..ada2511 100644 --- a/src/ipc/ipcclient.cpp +++ b/src/ipc/ipcclient.cpp @@ -84,13 +84,14 @@ IpcClient::IpcClient(QObject *parent) , m_written(0) , m_connection(new IpcConnection(m_socket)) { - connect(m_socket, SIGNAL(connected()), this, SIGNAL(connected())); - connect(m_socket, SIGNAL(connected()), this, SLOT(processQueue())); - connect(m_socket, SIGNAL(disconnected()), this, SIGNAL(disconnected())); - connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); - connect(m_socket, SIGNAL(bytesWritten(qint64)), this , SLOT(onBytesWritten(qint64))); - - connect(m_connection, SIGNAL(received(QString,QByteArray)), this, SIGNAL(received(QString,QByteArray))); + connect(m_socket, &QAbstractSocket::connected, this, &IpcClient::connected); + connect(m_socket, &QAbstractSocket::connected, this, &IpcClient::processQueue); + connect(m_socket, &QAbstractSocket::disconnected, this, &IpcClient::disconnected); + void (QAbstractSocket::*QAbstractSocket__error)(QAbstractSocket::SocketError) = &QAbstractSocket::error; + connect(m_socket, QAbstractSocket__error, this, &IpcClient::onError); + connect(m_socket, &QAbstractSocket::bytesWritten, this, &IpcClient::onBytesWritten); + + connect(m_connection, &IpcConnection::received, this, &IpcClient::received); } IpcClient::IpcClient(QTcpSocket *socket, QObject *parent) @@ -100,10 +101,11 @@ IpcClient::IpcClient(QTcpSocket *socket, QObject *parent) , m_written(0) , m_connection(0) { - connect(m_socket, SIGNAL(connected()), this, SIGNAL(connected())); - connect(m_socket, SIGNAL(disconnected()), this, SIGNAL(disconnected())); - connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); - connect(m_socket, SIGNAL(bytesWritten(qint64)), this , SLOT(onBytesWritten(qint64))); + connect(m_socket, &QAbstractSocket::connected, this, &IpcClient::connected); + connect(m_socket, &QAbstractSocket::disconnected, this, &IpcClient::disconnected); + void (QAbstractSocket::*QAbstractSocket__error)(QAbstractSocket::SocketError) = &QAbstractSocket::error; + connect(m_socket, QAbstractSocket__error, this, &IpcClient::onError); + connect(m_socket, &QAbstractSocket::bytesWritten, this, &IpcClient::onBytesWritten); } /*! @@ -115,7 +117,7 @@ QAbstractSocket::SocketState IpcClient::state() const } /*! - * Sets the Ip-Address to \a hostName and port to \a port to be used for a ipc call. + * Sets the Ip-Address to \a hostName and port to \a port to be used for a IPC call. */ void IpcClient::connectToServer(const QString &hostName, int port) { @@ -140,7 +142,11 @@ QUuid IpcClient::send(const QString &method, const QByteArray &data) pkg->m_tries = 0; m_queue.enqueue(pkg); +#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) QTimer::singleShot(0, this, SLOT(processQueue())); +#else + QTimer::singleShot(0, this, &IpcClient::processQueue); +#endif return pkg->m_uuid; } @@ -295,7 +301,11 @@ void IpcClient::processQueue() DEBUG << "Tried to sent the package" << m_current->m_tries << "times, but didn't succeed"; m_queue.dequeue(); onError(QAbstractSocket::ConnectionRefusedError); +#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) QTimer::singleShot(0, this, SLOT(processQueue())); +#else + QTimer::singleShot(0, this, &IpcClient::processQueue); +#endif return; } @@ -305,7 +315,11 @@ void IpcClient::processQueue() m_queue.dequeue(); m_current->m_bytes = size; } else { +#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) QTimer::singleShot(1000, this, SLOT(processQueue())); +#else + QTimer::singleShot(1000, this, &IpcClient::processQueue); +#endif m_current = 0; } } @@ -335,7 +349,11 @@ void IpcClient::onError(QAbstractSocket::SocketError socketError) delete m_current; m_current = 0; +#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) QTimer::singleShot(0, this, SLOT(processQueue())); +#else + QTimer::singleShot(0, this, &IpcClient::processQueue); +#endif } if ((m_socket->state() != QAbstractSocket::ConnectedState && diff --git a/src/ipc/ipcconnection.cpp b/src/ipc/ipcconnection.cpp index 8d35e07..32c038e 100644 --- a/src/ipc/ipcconnection.cpp +++ b/src/ipc/ipcconnection.cpp @@ -54,9 +54,10 @@ IpcConnection::IpcConnection(QTcpSocket *socket, QObject *parent) { DEBUG << "IpcConnection()"; - connect(m_socket, SIGNAL(disconnected()), this, SLOT(close())); - connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(closeWithError())); - connect(m_socket, SIGNAL(readyRead()), this, SLOT(readData())); + connect(m_socket, &QAbstractSocket::disconnected, this, &IpcConnection::close); + void (QAbstractSocket::*QAbstractSocket__error)(QAbstractSocket::SocketError) = &QAbstractSocket::error; + connect(m_socket, QAbstractSocket__error, this, &IpcConnection::closeWithError); + connect(m_socket, &QAbstractSocket::readyRead, this, &IpcConnection::readData); } /** diff --git a/src/ipc/ipcserver.cpp b/src/ipc/ipcserver.cpp index f23aaa6..3d8288c 100644 --- a/src/ipc/ipcserver.cpp +++ b/src/ipc/ipcserver.cpp @@ -49,10 +49,7 @@ * * \code{.cpp} * m_server = new IpcServer(this); - * connect( - * m_server, SIGNAL(received(QString,QByteArray)), - * this, SLOT(handleCall(QString, QByteArray)) - * ); + * connect(m_server, &IpcServer::received, this, &MyHandler::handleCall); * m_server->listen(10234); * * ... @@ -75,7 +72,7 @@ IpcServer::IpcServer(QObject *parent) : QObject(parent) , m_server(new QTcpServer(this)) { - connect(m_server, SIGNAL(newConnection()), this, SLOT(newConnection())); + connect(m_server, &QTcpServer::newConnection, this, &IpcServer::newConnection); } /*! @@ -98,8 +95,8 @@ void IpcServer::newConnection() emit clientConnected(socket->peerAddress()); emit clientConnected(socket); IpcConnection *connection = new IpcConnection(socket, this); - connect(connection, SIGNAL(connectionClosed()), this, SLOT(onConnectionClosed())); - connect(connection, SIGNAL(received(QString,QByteArray)), this, SIGNAL(received(QString,QByteArray))); + connect(connection, &IpcConnection::connectionClosed, this, &IpcServer::onConnectionClosed); + connect(connection, &IpcConnection::received, this, &IpcServer::received); } } @@ -126,9 +123,9 @@ void IpcServer::setMaxConnections(int num) /*! * \fn void IpcServer::received(const QString& method, const QByteArray& content) - * \brief signals a ipc call has arrived + * \brief signals a IPC call has arrived * - * A ipc call requesting \a method and using \a content a the parameters for the method + * A IPC call requesting \a method and using \a content a the parameters for the method */ /*! diff --git a/src/lib.pro b/src/lib.pro index 89baf59..b5f8ab6 100755 --- a/src/lib.pro +++ b/src/lib.pro @@ -28,7 +28,7 @@ INSTALLS += headers !win32 { CONFIG += create_pc create_prl no_install_prl QMAKE_PKGCONFIG_NAME = qmllive - QMAKE_PKGCONFIG_DESCRIPTION = Qt QML Live Library + QMAKE_PKGCONFIG_DESCRIPTION = Qt QmlLive Library QMAKE_PKGCONFIG_PREFIX = $$PREFIX QMAKE_PKGCONFIG_LIBDIR = ${prefix}/lib QMAKE_PKGCONFIG_INCDIR = ${prefix}/include/qmllive diff --git a/src/livedocument.cpp b/src/livedocument.cpp new file mode 100644 index 0000000..d4347cf --- /dev/null +++ b/src/livedocument.cpp @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "livedocument.h" + +#include <QDebug> + +/*! + * \class LiveDocument + * \brief Encapsulates a relative path to a workspace document + * \inmodule qmllive + */ + +/*! + * Constructs a null instance. + * + * \sa isNull(), errorString() + */ +LiveDocument::LiveDocument() +{ + m_errorString = tr("Internal error: A null LiveDocument passed"); +} + +/*! + * Constructs instance for the given \a relativeFilePath. + * + * The \a relativeFilePath MUST NOT be an empty string, it MUST be a relative + * path, and when resolved relatively to a directory it MUST NOT resolve to a + * path outside of the directory. + */ +LiveDocument::LiveDocument(const QString &relativeFilePath) +{ + LIVE_ASSERT(!relativeFilePath.isEmpty(), return); + LIVE_ASSERT(QDir::isRelativePath(relativeFilePath), return); + LIVE_ASSERT(!QDir::cleanPath(relativeFilePath).startsWith(QLatin1String("../")), return); + + m_relativeFilePath = relativeFilePath; +} + +/*! + * \fn bool LiveDocument::isNull() const + * + * Returns true if this is a null instance. + * + * A null instance has been either contructed with the default constructor or + * the resolve() call failed. + * + * \sa errorString() + */ + +/*! + * \fn QString LiveDocument::errorString() const + * + * When called just after resolve(), existsIn() or isFileIn() failed, returns a + * descriptive message suitable for displaying in user interface. When called in + * other context, the result is undefined. + */ + +/*! + * Returns \c true if this document exists in the given \a workspace directory. + * + * \sa errorString() + */ +bool LiveDocument::existsIn(const QDir &workspace) const +{ + LIVE_ASSERT(!isNull(), return false); + + bool exists = QFileInfo(workspace, m_relativeFilePath).exists(); + if (!exists) { + m_errorString = tr("Document '%1' does not exist in workspace '%2'") + .arg(m_relativeFilePath) + .arg(workspace.path()); + } + return exists; +} + +/*! + * Returns \c true if this document exists as a regular file (or a symbolic link + * to a regular file) in the given \a workspace directory. + * + * Symbolic links resolution applies. + * + * \sa errorString() + */ +bool LiveDocument::isFileIn(const QDir &workspace) const +{ + LIVE_ASSERT(!isNull(), return false); + + if (!existsIn(workspace)) + return false; + + bool isFile = QFileInfo(workspace, m_relativeFilePath).isFile(); + if (!isFile) { + m_errorString = tr("Document '%1' is a non-regular file in workspace '%2'") + .arg(m_relativeFilePath) + .arg(workspace.path()); + } + return isFile; +} + +/*! + * Returns the relative file path including the file name. + */ +QString LiveDocument::relativeFilePath() const +{ + LIVE_ASSERT(!isNull(), return QString()); + + return m_relativeFilePath; +} + +/*! + * Returns the absolute file path within a \a workspace, including the file name. + */ +QString LiveDocument::absoluteFilePathIn(const QDir &workspace) const +{ + LIVE_ASSERT(!isNull(), return QString()); + + return QDir::cleanPath(workspace.absoluteFilePath(m_relativeFilePath)); +} + +/*! + * Constructs a non-null instance unless the \a filePath resolves outside of the + * \a workspace directory. + * + * \a filePath may be an absolute or relative file path to a file which is NOT + * required to exist. + * + * \sa isNull(), errorString() + */ +LiveDocument LiveDocument::resolve(const QDir &workspace, const QString &filePath) +{ + LiveDocument retv; + + if (filePath.isEmpty()) { + qWarning() << "filePath is an empty string"; + retv.m_errorString = tr("Not a valid file path: ''"); + return retv; + } + + QString relativeFilePath = workspace.relativeFilePath(filePath); + QString cleanPath = QDir::cleanPath(relativeFilePath); + if (cleanPath.isEmpty()) { + retv.m_relativeFilePath = QStringLiteral("."); + } else if (!cleanPath.startsWith(QLatin1String("../"))) { + retv.m_relativeFilePath = relativeFilePath; + } else { + retv.m_errorString = tr("Document path '%1' is outside the workspace directory '%2'") + .arg(filePath).arg(workspace.path()); + } + + return retv; +} + +/*! + * Allows to print LiveDocument \a document via debug stream \a dbg. + */ +QDebug operator<<(QDebug dbg, const LiveDocument &document) +{ + if (document.isNull()) + dbg << QStringLiteral("<null>"); + else + dbg << document.relativeFilePath(); + + return dbg; +} diff --git a/src/livedocument.h b/src/livedocument.h new file mode 100644 index 0000000..6aa9b0c --- /dev/null +++ b/src/livedocument.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 LiveDocument +{ + Q_DECLARE_TR_FUNCTIONS(LiveDocument) + +public: + LiveDocument(); + explicit LiveDocument(const QString &relativeFilePath); + + bool isNull() const { return m_relativeFilePath.isEmpty(); } + QString errorString() const { return m_errorString; } + + bool existsIn(const QDir &workspace) const; + bool isFileIn(const QDir &workspace) const; + + QString relativeFilePath() const; + QString absoluteFilePathIn(const QDir &workspace) const; + + static LiveDocument resolve(const QDir &workspace, const QString &filePath); + + friend inline bool operator==(const LiveDocument &d1, const LiveDocument &d2) + { return d1.m_relativeFilePath == d2.m_relativeFilePath; } + friend inline bool operator!=(const LiveDocument &d1, const LiveDocument &d2) + { return !(d1 == d2); } + +private: + QString m_relativeFilePath; + mutable QString m_errorString; +}; + +QDebug QMLLIVESHARED_EXPORT operator<<(QDebug dbg, const LiveDocument &document); diff --git a/src/livehubengine.cpp b/src/livehubengine.cpp index 6db5520..39208bf 100644 --- a/src/livehubengine.cpp +++ b/src/livehubengine.cpp @@ -55,7 +55,7 @@ LiveHubEngine::LiveHubEngine(QObject *parent) , m_watcher(new Watcher(this)) , m_filePublishingActive(false) { - connect(m_watcher, SIGNAL(directoriesChanged(QStringList)), this, SLOT(directoriesChanged(QStringList))); + connect(m_watcher, &Watcher::directoriesChanged, this, &LiveHubEngine::directoriesChanged); } /*! @@ -78,18 +78,18 @@ QString LiveHubEngine::workspace() const /*! * Sets the active document path to \a path. - * Emits activateDocument() with the workspace relative path. + * Emits activateDocument() with this path. */ -void LiveHubEngine::setActivePath(const QString &path) +void LiveHubEngine::setActivePath(const LiveDocument &path) { m_activePath = path; - emit activateDocument(m_watcher->relativeFilePath(path)); + emit activateDocument(m_activePath); } /*! * Returns the active Document */ -QString LiveHubEngine::activePath() const +LiveDocument LiveHubEngine::activePath() const { return m_activePath; } @@ -106,7 +106,7 @@ void LiveHubEngine::directoriesChanged(const QStringList &changes) } } - emit activateDocument(m_watcher->relativeFilePath(m_activePath)); + emit activateDocument(m_activePath); } /*! @@ -132,10 +132,11 @@ void LiveHubEngine::publishDirectory(const QString& dirPath, bool fileChange) if (!m_filePublishingActive) { return; } QDirIterator iter(dirPath, QDir::Files); while (iter.hasNext()) { + LiveDocument document = LiveDocument::resolve(m_watcher->directory(), iter.next()); if (fileChange) - emit fileChanged(iter.next()); + emit fileChanged(document); else - emit publishFile(iter.next()); + emit publishFile(document); } } @@ -162,21 +163,21 @@ void LiveHubEngine::setFilePublishingActive(bool on) */ /*! - * \fn void LiveHubEngine::publishFile(const QString& document) + * \fn void LiveHubEngine::publishFile(const LiveDocument& document) * * This signal is emitted during publishing the directory to inform a connected * node to publish the \a document to the remote device form the hub */ /*! - * \fn void LiveHubEngine::fileChanged(const QString& document) + * \fn void LiveHubEngine::fileChanged(const LiveDocument& document) * * This signal is emitted during publishing a directory to inform a connected * node that \a document has changed on the hub. */ /*! - * \fn void LiveHubEngine::activateDocument(const QString& document) + * \fn void LiveHubEngine::activateDocument(const LiveDocument& document) * The signal is emitted when the document identified by \a document has been activated */ diff --git a/src/livehubengine.h b/src/livehubengine.h index 362c7db..266e07e 100644 --- a/src/livehubengine.h +++ b/src/livehubengine.h @@ -33,6 +33,7 @@ #include <QtCore> +#include "livedocument.h" #include "qmllive_global.h" class Watcher; @@ -46,17 +47,17 @@ public: void setWorkspace(const QString& path); QString workspace() const; - QString activePath() const; + LiveDocument activePath() const; public Q_SLOTS: - void setActivePath(const QString& path); + void setActivePath(const LiveDocument& path); void setFilePublishingActive(bool on); void publishWorkspace(); Q_SIGNALS: void beginPublishWorkspace(); void endPublishWorkspace(); - void publishFile(const QString& document); - void fileChanged(const QString& document); - void activateDocument(const QString& document); + void publishFile(const LiveDocument& document); + void fileChanged(const LiveDocument& document); + void activateDocument(const LiveDocument& document); void workspaceChanged(const QString& workspace); private Q_SLOTS: void directoriesChanged(const QStringList& changes); @@ -65,6 +66,6 @@ private: private: Watcher *m_watcher; bool m_filePublishingActive; - QString m_activePath; + LiveDocument m_activePath; }; diff --git a/src/livenodeengine.cpp b/src/livenodeengine.cpp index fd146c8..5feb843 100644 --- a/src/livenodeengine.cpp +++ b/src/livenodeengine.cpp @@ -57,14 +57,14 @@ const char OVERLAY_PATH_SEPARATOR = '-'; * \brief The LiveNodeEngine class instantiates QML components in cooperation with LiveHubEngine. * \inmodule qmllive * - * LiveNodeEngine provides ways to reload qml documents based incoming requests + * LiveNodeEngine provides ways to reload QML documents based incoming requests * from a hub. A hub can be connected via a RemotePublisher/RemoteReceiver pair. * * The primary use case is to allow loading of QML components instantiating * QQuickWindow, i.e., inheriting QML Window. A fallbackView can be set in order * to support also QML Item based components. * - * In Addition to showing qml-Files the LiveNodeEngine can be extended by plugins to show any other datatype. + * In Addition to showing QML files the LiveNodeEngine can be extended by plugins to show any other filetype. * One need to set the Plugin path to the right destination and the LiveNodeEngine will load all the plugins * it finds there. * @@ -119,12 +119,12 @@ public: QDir overlay() const { return m_overlay; } - QString reserve(const QString &document) + QString reserve(const LiveDocument &document) { QWriteLocker locker(&m_lock); - QString overlayingPath = m_overlay.absoluteFilePath(document); - m_mappings.insert(QUrl::fromLocalFile(m_base.absoluteFilePath(document)), + QString overlayingPath = document.absoluteFilePathIn(m_overlay); + m_mappings.insert(QUrl::fromLocalFile(document.absoluteFilePathIn(m_base)), QUrl::fromLocalFile(overlayingPath)); return overlayingPath; } @@ -163,7 +163,7 @@ LiveNodeEngine::LiveNodeEngine(QObject *parent) { m_delayReload->setInterval(250); m_delayReload->setSingleShot(true); - connect(m_delayReload, SIGNAL(timeout()), this, SLOT(reloadDocument())); + connect(m_delayReload, &QTimer::timeout, this, &LiveNodeEngine::reloadDocument); } /*! @@ -193,7 +193,6 @@ void LiveNodeEngine::setQmlEngine(QQmlEngine *qmlEngine) m_qmlEngine = qmlEngine; connect(m_qmlEngine.data(), &QQmlEngine::warnings, this, &LiveNodeEngine::logErrors); - qmlEngine->setOutputWarningsToStandardError(false); m_qmlEngine->rootContext()->setContextProperty("livert", m_runtime); } @@ -285,14 +284,93 @@ int LiveNodeEngine::rotation() const } /*! - * Loads the given \a url onto the qml view. Clears any caches. + * Allows to initialize active document with an instance preloaded beyond + * LiveNodeEngine's control. + * + * This can be called at most once and only before a document has been loaded + * with loadDocument(). + * + * \a document is the source of the component that was used to instantiate the + * \a object. \a window should be either the \a object itself or the + * fallbackView(). \a errors (if any) will be added to log. + * + * Note that \a window will be destroyed on next loadDocument() call unless it + * is the fallbackView(). \a object will be destroyed unconditionally. + */ +void LiveNodeEngine::usePreloadedDocument(const LiveDocument &document, QObject *object, + QQuickWindow *window, const QList<QQmlError> &errors) +{ + LIVE_ASSERT(m_activeFile.isNull(), return); + LIVE_ASSERT(!document.isNull(), return); + + m_activeFile = document; + + if (!m_activeFile.existsIn(m_workspace)) { + QQmlError error; + error.setUrl(QUrl::fromLocalFile(m_activeFile.absoluteFilePathIn(m_workspace))); + error.setDescription(tr("File not found")); + emit logErrors(QList<QQmlError>() << error); + } + + m_object = object; + m_activeWindow = window; + + if (m_activeWindow) { + m_activeWindowConnections << connect(m_activeWindow.data(), &QWindow::widthChanged, + this, &LiveNodeEngine::onSizeChanged); + m_activeWindowConnections << connect(m_activeWindow.data(), &QWindow::heightChanged, + this, &LiveNodeEngine::onSizeChanged); + onSizeChanged(); + } + + emit activeDocumentChanged(m_activeFile); + emit documentLoaded(); + emit activeWindowChanged(m_activeWindow); + emit logErrors(errors); +} + +/*! + * This is an overloaded function provided for convenience. It is suitable for + * use with QQmlApplicationEngine. + * + * Tries to resolve \a document against current workspace(). \a window is the + * root object. \a errors (if any) will be added to log. + */ +void LiveNodeEngine::usePreloadedDocument(const QString &document, QQuickWindow *window, + const QList<QQmlError> &errors) +{ + LIVE_ASSERT(m_activeFile.isNull(), return); + + LiveDocument resolved = LiveDocument::resolve(workspace(), document); + if (resolved.isNull()) { + qWarning() << "Failed to resolve preloaded document path:" << document + << "Workspace: " << workspace(); + return; + } + + usePreloadedDocument(resolved, window, window, errors); +} + +/*! + * Loads or reloads the given \a document onto the QML view. Clears any caches. + * + * The activeDocumentChanged() signal is emitted when this results in change of + * the activeDocument(). + * + * \sa documentLoaded() */ -void LiveNodeEngine::loadDocument(const QUrl& url) +void LiveNodeEngine::loadDocument(const LiveDocument& document) { - DEBUG << "LiveNodeEngine::loadDocument: " << url; - m_activeFile = url; + DEBUG << "LiveNodeEngine::loadDocument: " << document; + + LiveDocument oldActiveFile = m_activeFile; + + m_activeFile = document; - if (!m_activeFile.isEmpty()) + if (m_activeFile != oldActiveFile) + emit activeDocumentChanged(m_activeFile); + + if (!m_activeFile.isNull()) reloadDocument(); } @@ -330,7 +408,7 @@ QUrl LiveNodeEngine::errorScreenUrl() const } /*! - * Reloads the active qml document. + * Reloads the active QML document. * * Emits documentLoaded() when finished. * @@ -346,10 +424,8 @@ void LiveNodeEngine::reloadDocument() } // Do this unconditionally! - if (m_fallbackView) { + if (m_fallbackView) m_fallbackView->setSource(QUrl()); - m_fallbackView->close(); - } m_activeWindow = 0; @@ -360,26 +436,51 @@ void LiveNodeEngine::reloadDocument() checkQmlFeatures(); + qInfo() << "----------------------------------------"; + qInfo() << "QmlLive: (Re)loading" << m_activeFile; + emit clearLog(); - const QUrl url = queryDocumentViewer(m_activeFile); + const QUrl originalUrl = QUrl::fromLocalFile(m_activeFile.absoluteFilePathIn(m_workspace)); + const QUrl url = queryDocumentViewer(originalUrl); - QScopedPointer<QQmlComponent> component(new QQmlComponent(m_qmlEngine, url)); - m_object = component->create(); + auto showErrorScreen = [this] { + Q_ASSERT(m_fallbackView); + m_fallbackView->setResizeMode(QQuickView::SizeRootObjectToView); + m_fallbackView->setSource(errorScreenUrl()); + m_activeWindow = m_fallbackView; + }; + + auto logError = [this, url](const QString &description) { + QQmlError error; + error.setObject(m_object); + error.setUrl(url); + error.setLine(0); + error.setColumn(0); + error.setDescription(description); + emit logErrors(QList<QQmlError>() << error); + }; + + QScopedPointer<QQmlComponent> component(new QQmlComponent(m_qmlEngine)); + if (url.path().endsWith(QLatin1String(".qml"), Qt::CaseInsensitive)) { + component->loadUrl(url); + m_object = component->create(); + } else if (url == originalUrl) { + logError(tr("LiveNodeEngine: Cannot display this file type")); + } else { + logError(tr("LiveNodeEngine: Internal error: Cannot display this file type")); + } if (!component->isReady()) { if (component->isLoading()) { qCritical() << "Component did not load synchronously." << "URL:" << url.toString() - << "(original URL:" << m_activeFile.toString() << ")"; + << "(original URL:" << originalUrl.toString() << ")"; } else { emit logErrors(component->errors()); delete m_object; - if (m_fallbackView) { - m_fallbackView->setResizeMode(QQuickView::SizeRootObjectToView); - m_fallbackView->setSource(errorScreenUrl()); - m_activeWindow = m_fallbackView; - } + if (m_fallbackView) + showErrorScreen(); } } else if (QQuickWindow *window = qobject_cast<QQuickWindow *>(m_object)) { // TODO (why) is this needed? @@ -396,29 +497,14 @@ void LiveNodeEngine::reloadDocument() m_fallbackView->setContent(url, component.take(), m_object); m_activeWindow = m_fallbackView; } else { - QQmlError error; - error.setObject(m_object); - error.setUrl(url); - error.setLine(0); - error.setColumn(0); - error.setDescription(tr("LiveNodeEngine: Cannot display this component: " - "Root object is not a QQuickWindow and no LiveNodeEngine::fallbackView set.")); - emit logErrors(QList<QQmlError>() << error); + logError(tr("LiveNodeEngine: Cannot display this component: " + "Root object is not a QQuickWindow and no LiveNodeEngine::fallbackView set.")); } } else { - QQmlError error; - error.setObject(m_object); - error.setUrl(url); - error.setLine(0); - error.setColumn(0); - error.setDescription(tr("LiveNodeEngine: Cannot display this component: " - "Root object is not a QQuickWindow nor a QQuickItem.")); - emit logErrors(QList<QQmlError>() << error); - if (m_fallbackView) { - m_fallbackView->setResizeMode(QQuickView::SizeRootObjectToView); - m_fallbackView->setSource(errorScreenUrl()); - m_activeWindow = m_fallbackView; - } + logError(tr("LiveNodeEngine: Cannot display this component: " + "Root object is not a QQuickWindow nor a QQuickItem.")); + if (m_fallbackView) + showErrorScreen(); } if (m_activeWindow) { @@ -432,6 +518,9 @@ void LiveNodeEngine::reloadDocument() emit documentLoaded(); emit activeWindowChanged(m_activeWindow); + if (m_fallbackView && m_fallbackView != m_activeWindow) + m_fallbackView->close(); + // Delay showing the window after activeWindowChanged is handled by // WindowWidget::setHostedWindow() - it would be destroyed there anyway. // (Applies when this is instantiated for the bench.) @@ -444,7 +533,7 @@ void LiveNodeEngine::reloadDocument() * * The behavior of this function is controlled by WorkspaceOptions passed to setWorkspace(). */ -void LiveNodeEngine::updateDocument(const QString &document, const QByteArray &content) +void LiveNodeEngine::updateDocument(const LiveDocument &document, const QByteArray &content) { if (!(m_workspaceOptions & AllowUpdates)) { return; @@ -452,7 +541,7 @@ void LiveNodeEngine::updateDocument(const QString &document, const QByteArray &c QString filePath = (m_workspaceOptions & UpdatesAsOverlay) ? m_overlayUrlInterceptor->reserve(document) - : m_workspace.absoluteFilePath(document); + : document.absoluteFilePathIn(m_workspace); QString dirPath = QFileInfo(filePath).absoluteDir().absolutePath(); QDir().mkpath(dirPath); @@ -464,13 +553,13 @@ void LiveNodeEngine::updateDocument(const QString &document, const QByteArray &c file.write(content); file.close(); - if (!m_activeFile.isEmpty()) + if (!m_activeFile.isNull()) delayReload(); } /*! - * Allows to adapt a \a url to display not native qml documents (e.g. images). + * Allows to adapt a \a url to display not native QML documents (e.g. images). */ QUrl LiveNodeEngine::queryDocumentViewer(const QUrl& url) { @@ -493,20 +582,6 @@ QUrl LiveNodeEngine::queryDocumentViewer(const QUrl& url) } /*! - * Sets the document \a document to be shown - */ -void LiveNodeEngine::setActiveDocument(const QString &document) -{ - QUrl url; - if (!document.isEmpty()) { - url = QUrl::fromLocalFile(m_workspace.absoluteFilePath(document)); - } - - loadDocument(url); - emit activateDocument(document); -} - -/*! * Returns the current workspace path. */ QString LiveNodeEngine::workspace() const @@ -597,9 +672,9 @@ QString LiveNodeEngine::pluginPath() const } /*! - * Returns the current active document url. + * Returns the current active document. */ -QUrl LiveNodeEngine::activeDocument() const +LiveDocument LiveNodeEngine::activeDocument() const { return m_activeFile; } @@ -650,9 +725,13 @@ void LiveNodeEngine::onSizeChanged() } /*! - * \fn void LiveNodeEngine::activateDocument(const QString& document) + * \fn void LiveNodeEngine::activeDocumentChanged(const LiveDocument& document) + * + * The document \a document was loaded with loadDocument() and is now the + * activeDocument(). This signal is only emitted when the new document differs + * from the previously loaded one. * - * The document \a document was activated + * \sa documentLoaded() */ /*! diff --git a/src/livenodeengine.h b/src/livenodeengine.h index df030e1..78eebfe 100644 --- a/src/livenodeengine.h +++ b/src/livenodeengine.h @@ -35,6 +35,7 @@ #include <QtQuick> #include "contentadapterinterface.h" +#include "livedocument.h" #include "qmllive_global.h" class LiveRuntime; @@ -79,22 +80,26 @@ public: void setPluginPath(const QString& path); QString pluginPath() const; - QUrl activeDocument() const; + LiveDocument activeDocument() const; ContentAdapterInterface *activePlugin() const; QQuickWindow *activeWindow() const; + void usePreloadedDocument(const LiveDocument &document, QObject *object, QQuickWindow *window, + const QList<QQmlError> &errors); + void usePreloadedDocument(const QString &document, QQuickWindow *window, + const QList<QQmlError> &errors); + public Q_SLOTS: void setXOffset(int offset); void setYOffset(int offset); void setRotation(int rotation); - void setActiveDocument(const QString& document); - void loadDocument(const QUrl& url); + void loadDocument(const LiveDocument& document); void delayReload(); virtual void reloadDocument(); - void updateDocument(const QString &document, const QByteArray &content); + void updateDocument(const LiveDocument &document, const QByteArray &content); Q_SIGNALS: - void activateDocument(const QString& document); + void activeDocumentChanged(const LiveDocument& document); void clearLog(); void logIgnoreMessages(bool on); void documentLoaded(); @@ -105,7 +110,7 @@ Q_SIGNALS: protected: virtual void initPlugins(); QList<ContentAdapterInterface*> m_plugins; - QUrl m_activeFile; + LiveDocument m_activeFile; LiveRuntime *m_runtime; private Q_SLOTS: diff --git a/src/liveruntime.cpp b/src/liveruntime.cpp index bdfef98..22c8327 100644 --- a/src/liveruntime.cpp +++ b/src/liveruntime.cpp @@ -36,10 +36,10 @@ /*! * \class LiveRuntime - * \brief Collects properties to be used for an enhanced live runtime. + * \brief Collects properties to be used for an enhanced QmlLive runtime. * \inmodule qmllive * - * This runtime is used in an live enhanced qml project to be able to access more + * This runtime is used in a live enhanced QML project to be able to access more * advanced features. Currently it does nothing */ diff --git a/src/logreceiver.cpp b/src/logreceiver.cpp index 5e3aaba..7cec063 100644 --- a/src/logreceiver.cpp +++ b/src/logreceiver.cpp @@ -50,7 +50,7 @@ LogReceiver::LogReceiver(QObject *parent) : m_socket(new QUdpSocket(this)) { setPort(45454); - connect(m_socket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams())); + connect(m_socket, &QAbstractSocket::readyRead, this, &LogReceiver::processPendingDatagrams); } /*! diff --git a/src/previewGenerator/main.cpp b/src/previewGenerator/main.cpp index 3ef884c..a17e66d 100644 --- a/src/previewGenerator/main.cpp +++ b/src/previewGenerator/main.cpp @@ -54,7 +54,7 @@ public: PreviewServer() : m_server(new QLocalServer()) { - connect(m_server, SIGNAL(newConnection()), this, SLOT(onNewConnection())); + connect(m_server, &QLocalServer::newConnection, this, &PreviewServer::onNewConnection); } bool listen() @@ -89,7 +89,7 @@ private: }; Q_DECLARE_LOGGING_CATEGORY(pg) -Q_LOGGING_CATEGORY(pg, "PreviewGenerator", QtWarningMsg) +Q_LOGGING_CATEGORY(pg, "PreviewGenerator", QtInfoMsg) int main (int argc, char** argv) { diff --git a/src/qmlhelper.cpp b/src/qmlhelper.cpp index 2b5d472..72dc1af 100644 --- a/src/qmlhelper.cpp +++ b/src/qmlhelper.cpp @@ -34,7 +34,7 @@ /*! * \class QmlHelper - * \brief Provides a set of helper functions to setup your qml viewer + * \brief Provides a set of helper functions to setup your QML viewer * \inmodule qmllive */ @@ -66,11 +66,7 @@ void QmlHelper::loadDummyData(QQmlEngine *engine, const QString &workspace) } } if (obj) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) qInfo() << "loaded dummy data: " << dir.filePath(entry); -#else - qWarning() << "loaded dummy data: " << dir.filePath(entry); -#endif entry.chop(4); engine->rootContext()->setContextProperty(entry, obj); obj->setParent(engine); diff --git a/src/qmllive_global.h b/src/qmllive_global.h index d64f3e9..cba8d4b 100755 --- a/src/qmllive_global.h +++ b/src/qmllive_global.h @@ -44,4 +44,28 @@ # define QMLLIVESHARED_EXPORT #endif +#if defined(QMLLIVE_VERSION) +# define QMLLIVE_SOURCE +#endif + +#if defined(QMLLIVE_SOURCE) + +# if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) +# define LIVE_ASSERT(cond, action) Q_ASSERT(cond) +# else +# define LIVE_ASSERT(cond, action) if (cond) {} else { \ + QMessageLogger(__FILE__, __LINE__, Q_FUNC_INFO).critical() \ + << "Assertion \"" #cond "\" failed"; \ + action; \ + } do {} while (0) +# endif + +#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) +# define QtInfoMsg QtWarningMsg +# define qInfo qWarning +# define qCInfo qCWarning +#endif + +#endif // defined(QMLLIVE_SOURCE) + #endif // QMLLIVELIB_GLOBAL_H diff --git a/src/remotelogger.cpp b/src/remotelogger.cpp index 8aaec69..57a9ace 100644 --- a/src/remotelogger.cpp +++ b/src/remotelogger.cpp @@ -48,7 +48,7 @@ RemoteLogger::RemoteLogger(QObject *parent) : m_socket(new QUdpSocket(this)) , m_port(45454) { - connect(this, SIGNAL(message(int,QString)), this, SLOT(broadcast(int,QString))); + connect(this, &Logger::message, this, &RemoteLogger::broadcast); } /*! diff --git a/src/remotepublisher.cpp b/src/remotepublisher.cpp index 99a643c..ca24924 100644 --- a/src/remotepublisher.cpp +++ b/src/remotepublisher.cpp @@ -31,6 +31,7 @@ #include "remotepublisher.h" #include "ipc/ipcclient.h" +#include "livedocument.h" #include "livehubengine.h" #ifdef QMLLIVE_DEBUG @@ -57,19 +58,16 @@ RemotePublisher::RemotePublisher(QObject *parent) , m_ipc(new IpcClient(this)) , m_hub(0) { - connect(m_ipc, SIGNAL(sentSuccessfully(QUuid)), this, SIGNAL(sentSuccessfully(QUuid))); - connect(m_ipc, SIGNAL(sendingError(QUuid,QAbstractSocket::SocketError)), - this, SIGNAL(sendingError(QUuid,QAbstractSocket::SocketError))); - connect(m_ipc, SIGNAL(connectionError(QAbstractSocket::SocketError)), - this, SIGNAL(connectionError(QAbstractSocket::SocketError))); - connect(m_ipc, SIGNAL(connected()), this, SIGNAL(connected())); - connect(m_ipc, SIGNAL(disconnected()), this, SIGNAL(disconnected())); - - connect(m_ipc, SIGNAL(received(QString,QByteArray)), this, SLOT(handleCall(QString,QByteArray))); - - connect(m_ipc, SIGNAL(sentSuccessfully(QUuid)), this, SLOT(onSentSuccessfully(QUuid))); - connect(m_ipc, SIGNAL(sendingError(QUuid,QAbstractSocket::SocketError)), - this, SLOT(onSendingError(QUuid,QAbstractSocket::SocketError))); + connect(m_ipc, &IpcClient::sentSuccessfully, this, &RemotePublisher::sentSuccessfully); + connect(m_ipc, &IpcClient::sendingError, this, &RemotePublisher::sendingError); + connect(m_ipc, &IpcClient::connectionError, this, &RemotePublisher::connectionError); + connect(m_ipc, &IpcClient::connected, this, &RemotePublisher::connected); + connect(m_ipc, &IpcClient::disconnected, this, &RemotePublisher::disconnected); + + connect(m_ipc, &IpcClient::received, this, &RemotePublisher::handleCall); + + connect(m_ipc, &IpcClient::sentSuccessfully, this, &RemotePublisher::onSentSuccessfully); + connect(m_ipc, &IpcClient::sendingError, this, &RemotePublisher::onSendingError); } /*! @@ -91,12 +89,12 @@ void RemotePublisher::registerHub(LiveHubEngine *hub) disconnect(m_hub); } m_hub = hub; - connect(hub, SIGNAL(activateDocument(QString)), this, SLOT(activateDocument(QString))); - connect(hub, SIGNAL(fileChanged(QString)), this, SLOT(sendDocument(QString))); - connect(hub, SIGNAL(publishFile(QString)), this, SLOT(sendDocument(QString))); - connect(this, SIGNAL(needsPublishWorkspace()), hub, SLOT(publishWorkspace())); - connect(hub, SIGNAL(beginPublishWorkspace()), this, SLOT(beginBulkSend())); - connect(hub, SIGNAL(endPublishWorkspace()), this, SLOT(endBulkSend())); + connect(hub, &LiveHubEngine::activateDocument, this, &RemotePublisher::activateDocument); + connect(hub, &LiveHubEngine::fileChanged, this, &RemotePublisher::sendDocument); + connect(hub, &LiveHubEngine::publishFile, this, &RemotePublisher::sendDocument); + connect(this, &RemotePublisher::needsPublishWorkspace, hub, &LiveHubEngine::publishWorkspace); + connect(hub, &LiveHubEngine::beginPublishWorkspace, this, &RemotePublisher::beginBulkSend); + connect(hub, &LiveHubEngine::endPublishWorkspace, this, &RemotePublisher::endBulkSend); } /*! @@ -134,15 +132,15 @@ void RemotePublisher::disconnectFromServer() } /*! - * Send "activateDocument(QString)" to ipc-server on activate document. + * Send "activateDocument(QString)" to IPC-server on activate document. * \a document defines the Document which should be activated */ -QUuid RemotePublisher::activateDocument(const QString &document) +QUuid RemotePublisher::activateDocument(const LiveDocument &document) { DEBUG << "RemotePublisher::activateDocument" << document; QByteArray bytes; QDataStream out(&bytes, QIODevice::WriteOnly); - out << document; + out << document.relativeFilePath(); return m_ipc->send("activateDocument(QString)", bytes); } @@ -165,10 +163,10 @@ QUuid RemotePublisher::endBulkSend() } /*! - * Sends "sendDocument(QString)" using \a document as path to the document to be + * Sends "sendDocument(QString)" using \a document to identify the document to be *send to via IPC. */ -QUuid RemotePublisher::sendDocument(const QString& document) +QUuid RemotePublisher::sendDocument(const LiveDocument& document) { DEBUG << "RemotePublisher::sendDocument" << document; return sendWholeDocument(document); @@ -224,10 +222,10 @@ QUuid RemotePublisher::setRotation(int rotation) /*! Sends the \e sendWholeDocument with \a document as argument via IPC */ -QUuid RemotePublisher::sendWholeDocument(const QString& document) +QUuid RemotePublisher::sendWholeDocument(const LiveDocument& document) { DEBUG << "RemotePublisher::sendWholeDocument" << document; - QFile file(document); + QFile file(document.absoluteFilePathIn(m_workspace)); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "ERROR: can't open file: " << document; return QUuid(); @@ -236,7 +234,7 @@ QUuid RemotePublisher::sendWholeDocument(const QString& document) QByteArray bytes; QDataStream out(&bytes, QIODevice::WriteOnly); - out << m_workspace.relativeFilePath(document); + out << document.relativeFilePath(); out << data; return m_ipc->send("sendDocument(QString,QByteArray)", bytes); } @@ -295,6 +293,19 @@ void RemotePublisher::handleCall(const QString &method, const QByteArray &conten emit remoteLog(msgType, description, url, line, column); } else if (method == "clearLog()") { emit clearLog(); + } else if (method == "activeDocumentChanged(QString)") { + QString path; + + QDataStream in(content); + in >> path; + + if (path.isEmpty() || !QDir::isRelativePath(path)) { + qCritical() << "Invalid argument to remote call activeDocumentChanged." + << "Relative file path expected:" << path; + return; + } + + emit activeDocumentChanged(LiveDocument(path)); } } @@ -351,6 +362,13 @@ void RemotePublisher::handleCall(const QString &method, const QByteArray &conten * to indicate the client asks for (re)sending all workspace documents. */ +/*! + * \fn RemotePublisher::activeDocumentChanged(const LiveDocument &document) + * + * The signal is emitted after receiving the activeDocumentChanged IPC call, + * to indicate the client's active \a document has changed. + */ + /*! \fn RemotePublisher::sentSuccessfully(const QUuid& uuid) * * The signal is emitted after the package identified by \a uuid has been send diff --git a/src/remotepublisher.h b/src/remotepublisher.h index 8d69f9b..13840f4 100644 --- a/src/remotepublisher.h +++ b/src/remotepublisher.h @@ -36,6 +36,7 @@ #include "qmllive_global.h" +class LiveDocument; class LiveHubEngine; class IpcClient; @@ -57,6 +58,7 @@ Q_SIGNALS: void connectionError(QAbstractSocket::SocketError error); void needsPinAuthentication(); void needsPublishWorkspace(); + void activeDocumentChanged(const LiveDocument &document); void pinOk(bool ok); void remoteLog(int type, const QString &msg, const QUrl &url = QUrl(), int line = -1, int column = -1); void clearLog(); @@ -64,10 +66,10 @@ Q_SIGNALS: public Q_SLOTS: void setWorkspace(const QString &path); void disconnectFromServer(); - QUuid activateDocument(const QString& document); + QUuid activateDocument(const LiveDocument& document); QUuid beginBulkSend(); QUuid endBulkSend(); - QUuid sendDocument(const QString& document); + QUuid sendDocument(const LiveDocument& document); QUuid checkPin(const QString& pin); QUuid setXOffset(int offset); QUuid setYOffset(int offset); @@ -75,7 +77,7 @@ public Q_SLOTS: private Q_SLOTS: void handleCall(const QString &method, const QByteArray &content); - QUuid sendWholeDocument(const QString &document); + QUuid sendWholeDocument(const LiveDocument &document); void onSentSuccessfully(const QUuid& uuid); void onSendingError(const QUuid& uuid, QAbstractSocket::SocketError socketError); diff --git a/src/remotereceiver.cpp b/src/remotereceiver.cpp index 5877892..0175d44 100644 --- a/src/remotereceiver.cpp +++ b/src/remotereceiver.cpp @@ -82,11 +82,16 @@ RemoteReceiver::RemoteReceiver(QObject *parent) , m_client(0) , m_bulkUpdateInProgress(false) , m_updateDocumentsOnConnectState(UpdateNotStarted) + , m_logSentPosition(0) { - connect(m_server, SIGNAL(received(QString,QByteArray)), this, SLOT(handleCall(QString,QByteArray))); - connect(m_server, SIGNAL(clientConnected(QTcpSocket*)), this, SLOT(onClientConnected(QTcpSocket*))); - connect(m_server, SIGNAL(clientConnected(QHostAddress)), this, SIGNAL(clientConnected(QHostAddress))); - connect(m_server, SIGNAL(clientDisconnected(QHostAddress)), this, SIGNAL(clientDisconnected(QHostAddress))); + void (IpcServer::*IpcServer__clientConnected_socket)(QTcpSocket*) = &IpcServer::clientConnected; + void (IpcServer::*IpcServer__clientConnected_address)(const QHostAddress &) = &IpcServer::clientConnected; + void (IpcServer::*IpcServer__clientDisconnected_address)(const QHostAddress &) = &IpcServer::clientDisconnected; + + connect(m_server, &IpcServer::received, this, &RemoteReceiver::handleCall); + connect(m_server, IpcServer__clientConnected_socket, this, &RemoteReceiver::onClientConnected); + connect(m_server, IpcServer__clientConnected_address, this, &RemoteReceiver::clientConnected); + connect(m_server, IpcServer__clientDisconnected_address, this, &RemoteReceiver::clientDisconnected); } /*! @@ -101,11 +106,7 @@ bool RemoteReceiver::listen(int port, ConnectionOptions options) m_server->listen(port); if (m_connectionOptions & BlockingConnect) { -#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) - qWarning() << "Waiting for connection from QML Live bench…"; -#else - qInfo() << "Waiting for connection from QML Live bench…"; -#endif + qInfo() << "Waiting for connection from QmlLive Bench…"; QEventLoop loop; @@ -216,6 +217,7 @@ void RemoteReceiver::handleCall(const QString &method, const QByteArray &content emit endBulkUpdate(); if (m_updateDocumentsOnConnectState == UpdateStarted) { m_updateDocumentsOnConnectState = UpdateFinished; + finishConnectionInitialization(); emit updateDocumentsOnConnectFinished(true); } } else { @@ -227,13 +229,13 @@ void RemoteReceiver::handleCall(const QString &method, const QByteArray &content QDataStream in(content); in >> document; in >> data; - emit updateDocument(document, data); + emit updateDocument(LiveDocument(document), data); } else if (method == "activateDocument(QString)") { QString document; QDataStream in(content); in >> document; qDebug() << "\tactivate document: " << document; - emit activateDocument(document); + emit activateDocument(LiveDocument(document)); } else if (method == "ping()") { if (m_client) m_client->send("pong()", QByteArray()); @@ -247,13 +249,14 @@ void RemoteReceiver::registerNode(LiveNodeEngine *node) { if (m_node) { disconnect(m_node); } m_node = node; - connect(m_node, SIGNAL(logErrors(QList<QQmlError>)), this, SLOT(appendToLog(QList<QQmlError>))); - connect(m_node, SIGNAL(clearLog()), this, SLOT(clearLog())); - connect(this, SIGNAL(activateDocument(QString)), m_node, SLOT(setActiveDocument(QString))); - connect(this, SIGNAL(updateDocument(QString,QByteArray)), m_node, SLOT(updateDocument(QString,QByteArray))); - connect(this, SIGNAL(xOffsetChanged(int)), m_node, SLOT(setXOffset(int))); - connect(this, SIGNAL(yOffsetChanged(int)), m_node, SLOT(setYOffset(int))); - connect(this, SIGNAL(rotationChanged(int)), m_node, SLOT(setRotation(int))); + connect(m_node, &LiveNodeEngine::logErrors, this, &RemoteReceiver::appendToLog); + connect(m_node, &LiveNodeEngine::clearLog, this, &RemoteReceiver::clearLog); + connect(m_node, &LiveNodeEngine::activeDocumentChanged, this, &RemoteReceiver::onActiveDocumentChanged); + connect(this, &RemoteReceiver::activateDocument, m_node, &LiveNodeEngine::loadDocument); + connect(this, &RemoteReceiver::updateDocument, m_node, &LiveNodeEngine::updateDocument); + connect(this, &RemoteReceiver::xOffsetChanged, m_node, &LiveNodeEngine::setXOffset); + connect(this, &RemoteReceiver::yOffsetChanged, m_node, &LiveNodeEngine::setYOffset); + connect(this, &RemoteReceiver::rotationChanged, m_node, &LiveNodeEngine::setRotation); } /*! @@ -293,21 +296,41 @@ void RemoteReceiver::onClientDisconnected(QTcpSocket *socket) } void RemoteReceiver::maybeStartUpdateDocumentsOnConnect() { - if (!(m_connectionOptions & UpdateDocumentsOnConnect)) - return; - - if (m_updateDocumentsOnConnectState == UpdateNotStarted) { + if (m_connectionOptions & UpdateDocumentsOnConnect + && m_updateDocumentsOnConnectState == UpdateNotStarted) { m_client->send("needsPublishWorkspace()", QByteArray()); m_updateDocumentsOnConnectState = UpdateRequested; + } else { + finishConnectionInitialization(); } } +void RemoteReceiver::finishConnectionInitialization() +{ + if (!m_node->activeDocument().isNull()) + onActiveDocumentChanged(m_node->activeDocument()); + + m_logSentPosition = 0; + flushLog(); +} + /*! * Called to send \a errors to remote for remote logging */ void RemoteReceiver::appendToLog(const QList<QQmlError> &errors) { - foreach (const QQmlError &err, errors) { + m_log.append(errors); + + if (!m_client) + return; + + flushLog(); +} + +void RemoteReceiver::flushLog() +{ + for (; m_logSentPosition < m_log.count(); ++m_logSentPosition) { + const QQmlError &err = m_log.at(m_logSentPosition); if (!err.isValid()) continue; @@ -337,11 +360,32 @@ void RemoteReceiver::appendToLog(const QList<QQmlError> &errors) */ void RemoteReceiver::clearLog() { + m_log.clear(); + m_logSentPosition = 0; + + if (!m_client) + return; + m_client->send("clearLog()", QByteArray()); } /*! - * \fn void RemoteReceiver::activateDocument(const QString& document) + * Called to notify bench about active document change + */ +void RemoteReceiver::onActiveDocumentChanged(const LiveDocument &document) +{ + if (!m_client) + return; + + QByteArray bytes; + QDataStream out(&bytes, QIODevice::WriteOnly); + out << document.relativeFilePath(); + + m_client->send("activeDocumentChanged(QString)", bytes); +} + +/*! + * \fn void RemoteReceiver::activateDocument(const LiveDocument& document) * * This signal is emitted when the remote active document \a document has changed */ @@ -410,7 +454,7 @@ void RemoteReceiver::clearLog() */ /*! - * \fn void RemoteReceiver::updateDocument(const QString &document, const QByteArray &content) + * \fn void RemoteReceiver::updateDocument(const LiveDocument &document, const QByteArray &content) * * This signal is emitted to notify that a \a document has changed its \a content */ diff --git a/src/remotereceiver.h b/src/remotereceiver.h index aba3eed..4309a30 100644 --- a/src/remotereceiver.h +++ b/src/remotereceiver.h @@ -38,6 +38,7 @@ #include "qmllive_global.h" +class LiveDocument; class LiveNodeEngine; class IpcServer; class IpcClient; @@ -80,7 +81,7 @@ public: void setMaxConnections(int max); Q_SIGNALS: - void activateDocument(const QString& document); + void activateDocument(const LiveDocument& document); void reload(); void clientConnected(const QHostAddress& address); void clientDisconnected(const QHostAddress& address); @@ -91,17 +92,22 @@ Q_SIGNALS: void beginBulkUpdate(); void endBulkUpdate(); void updateDocumentsOnConnectFinished(bool ok); - void updateDocument(const QString &document, const QByteArray &content); + void updateDocument(const LiveDocument &document, const QByteArray &content); private Q_SLOTS: void handleCall(const QString& method, const QByteArray& content); void appendToLog(const QList<QQmlError> &errors); void clearLog(); + void onActiveDocumentChanged(const LiveDocument &document); void onClientConnected(QTcpSocket *socket); void onClientDisconnected(QTcpSocket *socket); void maybeStartUpdateDocumentsOnConnect(); + void finishConnectionInitialization(); + +private: + void flushLog(); private: IpcServer *m_server; @@ -116,6 +122,9 @@ private: ConnectionOptions m_connectionOptions; bool m_bulkUpdateInProgress; UpdateState m_updateDocumentsOnConnectState; + + QList<QQmlError> m_log; + int m_logSentPosition; }; Q_DECLARE_OPERATORS_FOR_FLAGS(RemoteReceiver::ConnectionOptions) diff --git a/src/runtime/main.cpp b/src/runtime/main.cpp index db26054..3058e24 100644 --- a/src/runtime/main.cpp +++ b/src/runtime/main.cpp @@ -75,13 +75,13 @@ static void parseArguments(const QStringList &arguments) parser.addPositionalArgument("workspace", "workspace folder to watch"); - QCommandLineOption ipcPortOption("ipcport", "the port the ipc shall listen on, default is 10234", "ipcport"); + QCommandLineOption ipcPortOption("ipcport", "the port the IPC shall listen on, default is 10234", "ipcport"); parser.addOption(ipcPortOption); - QCommandLineOption pluginPathOption("pluginpath", "path to qmllive plugins", "pluginpath"); + QCommandLineOption pluginPathOption("pluginpath", "path to QmlLive plugins", "pluginpath"); parser.addOption(pluginPathOption); - QCommandLineOption importPathOption("importpath", "path to qml import path. Can appear multiple times", "importpath"); + QCommandLineOption importPathOption("importpath", "path to QML import path. Can appear multiple times", "importpath"); parser.addOption(importPathOption); QCommandLineOption stayOnTopOption("stayontop", "keep viewer window on top"); @@ -189,12 +189,13 @@ int main(int argc, char** argv) engine.setFallbackView(&fallbackView); engine.setWorkspace(options.workspace, workspaceOptions); engine.setPluginPath(options.pluginPath); - engine.loadDocument(QUrl("qrc:/qml/qmlsplash/splash-qt5.qml")); RemoteReceiver receiver; receiver.registerNode(&engine); if (!receiver.listen(options.ipcPort, connectionOptions)) return EXIT_FAILURE; + fallbackView.setSource(QUrl("qrc:/qml/qmlsplash/splash-qt5.qml")); + int ret = app.exec(); return ret; diff --git a/src/src.pri b/src/src.pri index 8a629e6..1af18e4 100644 --- a/src/src.pri +++ b/src/src.pri @@ -8,6 +8,7 @@ DEFINES += NO_LIBRSYNC SOURCES += \ $$PWD/watcher.cpp \ + $$PWD/livedocument.cpp \ $$PWD/livehubengine.cpp \ $$PWD/livenodeengine.cpp \ $$PWD/qmlhelper.cpp \ @@ -22,6 +23,7 @@ SOURCES += \ $$PWD/fontadapter.cpp public_headers += \ + $$PWD/livedocument.h \ $$PWD/livehubengine.h \ $$PWD/livenodeengine.h \ $$PWD/qmlhelper.h \ diff --git a/src/watcher.cpp b/src/watcher.cpp index c97fa8f..2be3abb 100644 --- a/src/watcher.cpp +++ b/src/watcher.cpp @@ -48,8 +48,8 @@ Watcher::Watcher(QObject *parent) , m_watcher(new QFileSystemWatcher(this)) , m_waitTimer(new QTimer(this)) { - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(recordChange(QString))); - connect(m_waitTimer, SIGNAL(timeout()), this, SLOT(notifyChanges())); + connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &Watcher::recordChange); + connect(m_waitTimer, &QTimer::timeout, this, &Watcher::notifyChanges); m_waitTimer->setInterval(100); m_waitTimer->setSingleShot(true); } @@ -105,15 +105,6 @@ void Watcher::addDirectoriesRecursively(const QString &path) } -/*! - Returns the given path relative to the watcher's Directory - /sa setDirectory, directory() - */ -QString Watcher::relativeFilePath(const QString &path) -{ - return m_rootDir.relativeFilePath(path); -} - void Watcher::recordChange(const QString &path) { // qDebug() << "Watcher::recordChange: " << path; diff --git a/src/watcher.h b/src/watcher.h index 67f39ae..a17e28a 100644 --- a/src/watcher.h +++ b/src/watcher.h @@ -40,7 +40,6 @@ public: explicit Watcher(QObject *parent = 0); void setDirectory(const QString& path); QString directory() const; - QString relativeFilePath(const QString& path); private Q_SLOTS: void recordChange(const QString &path); void notifyChanges(); diff --git a/src/widgets/filesystemmodel.cpp b/src/widgets/filesystemmodel.cpp index f375f43..f3622b9 100644 --- a/src/widgets/filesystemmodel.cpp +++ b/src/widgets/filesystemmodel.cpp @@ -62,6 +62,7 @@ Qt::ItemFlags FileSystemModel::flags(const QModelIndex &index) const Qt::ItemFlags f = QFileSystemModel::flags(index); if (isDir(index)) { + f &= ~Qt::ItemIsDragEnabled; if (m_dirSelectable) return f; else diff --git a/src/widgets/logview.cpp b/src/widgets/logview.cpp index 2117921..f291faf 100644 --- a/src/widgets/logview.cpp +++ b/src/widgets/logview.cpp @@ -47,7 +47,7 @@ LogView::LogView(bool createLogger, QWidget *parent) if (createLogger) { m_logger = new Logger(this); - connect(m_logger, SIGNAL(message(int,QString)), this, SLOT(appendToLog(int,QString))); + connect(m_logger, &Logger::message, this, &LogView::appendToLog); } } @@ -104,7 +104,7 @@ void LogView::appendToLog(int type, const QString &msg, const QUrl &url, int lin m_log->appendHtml(s); } -void LogView::appendToLog(const QList<QQmlError> &errors) +void LogView::appendAllToLog(const QList<QQmlError> &errors) { foreach (const QQmlError &err, errors) { if (!err.isValid()) diff --git a/src/widgets/logview.h b/src/widgets/logview.h index 7ecdd7f..7d1874a 100644 --- a/src/widgets/logview.h +++ b/src/widgets/logview.h @@ -53,7 +53,7 @@ public slots: void setIgnoreMessages(bool ignoreMessages); void clear(); void appendToLog(int type, const QString &msg, const QUrl &url = QUrl(), int line = -1, int column = -1); - void appendToLog(const QList<QQmlError> &errors); + void appendAllToLog(const QList<QQmlError> &errors); private: QPlainTextEdit *m_log; diff --git a/src/widgets/workspacedelegate.cpp b/src/widgets/workspacedelegate.cpp index 0f886fd..8f61cee 100644 --- a/src/widgets/workspacedelegate.cpp +++ b/src/widgets/workspacedelegate.cpp @@ -31,9 +31,12 @@ #include "workspacedelegate.h" -WorkspaceDelegate::WorkspaceDelegate(FileSystemModel *model, QObject *parent) : - QStyledItemDelegate(parent), - m_model(model) +#include "filesystemmodel.h" +#include "workspaceview.h" + +WorkspaceDelegate::WorkspaceDelegate(WorkspaceView *view) : + QStyledItemDelegate(view), + m_view(view) { } @@ -42,11 +45,31 @@ void WorkspaceDelegate::initStyleOption(QStyleOptionViewItem *option, const QMod QStyledItemDelegate::initStyleOption(option, index); if (option) { - if (m_model->isDir(index)) + QString path = m_view->model()->filePath(index); + + // Do not paint selected items any special way - only the current item + // and the item corresponding to the active document will be + // highlighted. + option->state &= ~QStyle::State_Selected; + + // Highlighting in the view is suppressed with customized palette. + // See WorkspaceView's constructor. + QColor highlight = qApp->palette().color(QPalette::Highlight); + QColor highlightedText = qApp->palette().color(QPalette::HighlightedText); + option->palette.setColor(QPalette::Highlight, highlight); + option->palette.setColor(QPalette::HighlightedText, highlightedText); + + LiveDocument document = LiveDocument::resolve(m_view->rootPath(), path); + if (document == m_view->activeDocument()) { + option->backgroundBrush = highlight; + option->palette.setColor(QPalette::Base, highlight); + option->palette.setColor(QPalette::Text, highlightedText); + } + + if (m_view->model()->isDir(index)) return; - QString path = m_model->filePath(index); - foreach (QString type, m_model->allowedTypesFilter()) + foreach (QString type, m_view->model()->allowedTypesFilter()) { if (path.contains(QRegExp(type, Qt::CaseInsensitive, QRegExp::Wildcard))) return; diff --git a/src/widgets/workspacedelegate.h b/src/widgets/workspacedelegate.h index ad1572d..2d0451a 100644 --- a/src/widgets/workspacedelegate.h +++ b/src/widgets/workspacedelegate.h @@ -32,18 +32,19 @@ #pragma once #include <QStyledItemDelegate> -#include "filesystemmodel.h" + +class WorkspaceView; class WorkspaceDelegate : public QStyledItemDelegate { Q_OBJECT public: - explicit WorkspaceDelegate(FileSystemModel* model, QObject *parent = 0); + explicit WorkspaceDelegate(WorkspaceView *view); virtual void initStyleOption(QStyleOptionViewItem * option, const QModelIndex & index )const; private: - FileSystemModel* m_model; + WorkspaceView* m_view; }; diff --git a/src/widgets/workspaceview.cpp b/src/widgets/workspaceview.cpp index d6523ab..a1f26f8 100644 --- a/src/widgets/workspaceview.cpp +++ b/src/widgets/workspaceview.cpp @@ -57,8 +57,16 @@ WorkspaceView::WorkspaceView(QWidget *parent) m_view->hideColumn(2); // type m_view->hideColumn(3); // modified time - m_view->setItemDelegate(new WorkspaceDelegate(m_model, this)); - connect(m_view, SIGNAL(activated(QModelIndex)), this, SLOT(indexActivated(QModelIndex))); + // Prevent view highlighting background of a selected row. Only the + // active-document's row should be highlighted. See also + // WorkspaceDelegate::initStyleOption() + QPalette noHighlightPalette = palette(); + noHighlightPalette.setColor(QPalette::Highlight, palette().color(QPalette::Base)); + noHighlightPalette.setColor(QPalette::HighlightedText, palette().color(QPalette::Text)); + m_view->setPalette(noHighlightPalette); + + m_view->setItemDelegate(new WorkspaceDelegate(this)); + connect(m_view, &QTreeView::activated, this, &WorkspaceView::indexActivated); m_model->setAllowedTypesFilter(QStringList() << "*.qml" << "*.png" << "*.otf" << "*.ttf"); @@ -68,7 +76,6 @@ WorkspaceView::WorkspaceView(QWidget *parent) layout->setMargin(1); setLayout(layout); - m_view->setDragEnabled(true); m_view->setDragDropMode(QAbstractItemView::DragOnly); } @@ -85,17 +92,17 @@ void WorkspaceView::setRootPath(const QString &dirPath) /*! * Activates the document by the given \a path */ -void WorkspaceView::activateDocument(const QString &path) +void WorkspaceView::activateDocument(const LiveDocument &path) { //qDebug() << "WorkspaceView::activateDocument" << path; - QModelIndex index = m_model->index(path); + QModelIndex index = m_model->index(path.absoluteFilePathIn(rootPath())); selectIndex(index); } void WorkspaceView::activateRootPath() { selectIndex(m_rootIndex); - emit pathActivated(m_model->rootPath()); + emit pathActivated(LiveDocument(QStringLiteral("."))); } void WorkspaceView::goUp() @@ -110,7 +117,7 @@ void WorkspaceView::goUp() /*! * Returns the active, selected document. */ -QString WorkspaceView::activeDocument() const +LiveDocument WorkspaceView::activeDocument() const { return m_currentDocument; } @@ -140,8 +147,14 @@ void WorkspaceView::indexActivated(const QModelIndex &index) QString path = m_model->filePath(index); - m_currentDocument = path; - emit pathActivated(path); + LiveDocument oldDocument = m_currentDocument; + + m_currentDocument = LiveDocument::resolve(m_model->rootDirectory(), path); + emit pathActivated(m_currentDocument); + m_view->update(index); + + if (!oldDocument.isNull()) + m_view->update(m_model->index(oldDocument.absoluteFilePathIn(rootPath()))); } void WorkspaceView::selectIndex(const QModelIndex &index) diff --git a/src/widgets/workspaceview.h b/src/widgets/workspaceview.h index 4e1c9c3..dab0df6 100644 --- a/src/widgets/workspaceview.h +++ b/src/widgets/workspaceview.h @@ -31,6 +31,8 @@ #pragma once +#include "livedocument.h" + #include <QtGui> #include <QtWidgets> @@ -41,19 +43,20 @@ class WorkspaceView : public QWidget Q_OBJECT public: explicit WorkspaceView(QWidget *parent = 0); - QString activeDocument() const; + FileSystemModel *model() const { return m_model; } + LiveDocument activeDocument() const; QString rootPath() const; void setDirectoriesSelectable(bool enabled); bool directoriesSelectable() const; public Q_SLOTS: void setRootPath(const QString& dirPath); - void activateDocument(const QString& path); + void activateDocument(const LiveDocument& path); void activateRootPath(); void goUp(); Q_SIGNALS: - void pathActivated(const QString& path); + void pathActivated(const LiveDocument& path); private Q_SLOTS: void indexActivated(const QModelIndex& index); @@ -63,5 +66,5 @@ private: QTreeView *m_view; FileSystemModel *m_model; QModelIndex m_rootIndex; - QString m_currentDocument; + LiveDocument m_currentDocument; }; diff --git a/tests/testipc/tst_testipc.cpp b/tests/testipc/tst_testipc.cpp index f73d37a..197b4d2 100644 --- a/tests/testipc/tst_testipc.cpp +++ b/tests/testipc/tst_testipc.cpp @@ -60,21 +60,21 @@ private Q_SLOTS: void call() { IpcServer peer1; peer1.listen(10234); - connect(&peer1, SIGNAL(received(QString,QByteArray)), this, SLOT(handleCall(QString,QByteArray))); + connect(&peer1, &IpcServer::received, this, &TestIpc::handleCall); IpcClient peer2; peer2.connectToServer("127.0.0.1", 10234); QByteArray bytes; QDataStream stream(&bytes, QIODevice::ReadWrite); stream << QString("Hello IPC!"); peer2.send("echo(QString)", bytes); - QSignalSpy received(&peer1, SIGNAL(received(QString,QByteArray))); + QSignalSpy received(&peer1, &IpcServer::received); QTRY_COMPARE(received.count(), 1); } void sendFile() { IpcServer peer1; peer1.listen(10234); - connect(&peer1, SIGNAL(received(QString,QByteArray)), this, SLOT(handleCall(QString,QByteArray))); + connect(&peer1, &IpcServer::received, this, &TestIpc::handleCall); IpcClient peer2; peer2.connectToServer("127.0.0.1", 10234); QByteArray bytes; @@ -83,7 +83,7 @@ private Q_SLOTS: stream << filePath; stream << QString("hello").toLatin1(); peer2.send("sendFile(QString,QByteArray)", bytes); - QSignalSpy received(&peer1, SIGNAL(received(QString,QByteArray))); + QSignalSpy received(&peer1, &IpcServer::received); QTRY_COMPARE(received.count(), 1); } }; |