diff options
author | Tim Jenssen <tim.jenssen@qt.io> | 2020-08-13 08:34:24 +0200 |
---|---|---|
committer | Tim Jenssen <tim.jenssen@qt.io> | 2020-08-13 06:39:40 +0000 |
commit | 4fe5b923cddc9110094a883b5b7dd3051d8c8d90 (patch) | |
tree | 34e3984ab983882423bc563542a041b99b35f065 | |
parent | 24ca17f714ec133acc2bf0fd93c051ef730d69dc (diff) | |
parent | dc870f538dd3e11464a3ece179edf024161e14ba (diff) |
Merge remote-tracking branch 'origin/qds-1.59' into 4.13
Change-Id: Ic720f3adab305c5ef8cd10e713ccabf510eff0c0
93 files changed, 3686 insertions, 1048 deletions
diff --git a/scripts/createDevPackage.py b/scripts/createDevPackage.py index 1c8b85d143..f6613b3830 100755 --- a/scripts/createDevPackage.py +++ b/scripts/createDevPackage.py @@ -59,6 +59,7 @@ source_include_patterns = [ # directories r"^(?!(share|tests)/.*$)(.*/)?$", # look into all directories except under share/ and tests/ r"^share/(qtcreator/(qml/(qmlpuppet/(.*/)?)?)?)?$", # for shared headers for qt quick designer plugins + r"^src/plugins/help/qlitehtml/.*\.(h|pri|cpp|c|txt|md)$", # litehtml is used by extra plugins # files r"^HACKING$", r"^LICENSE.*$", diff --git a/share/qtcreator/qml/qmlpuppet/commands/captureddatacommand.h b/share/qtcreator/qml/qmlpuppet/commands/captureddatacommand.h new file mode 100644 index 0000000000..c7950e278b --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/commands/captureddatacommand.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#pragma once + +#include <QMetaType> + +#include "imagecontainer.h" + +namespace QmlDesigner { + +class CapturedDataCommand +{ +public: + struct NodeData + { + friend QDataStream &operator<<(QDataStream &out, const NodeData &data) + { + out << data.nodeId; + out << data.contentRect; + out << data.sceneTransform; + out << data.text; + + return out; + } + + friend QDataStream &operator>>(QDataStream &in, NodeData &data) + { + in >> data.nodeId; + in >> data.contentRect; + in >> data.sceneTransform; + in >> data.text; + + return in; + } + + qint32 nodeId = -1; + QRectF contentRect; + QTransform sceneTransform; + QString text; + }; + + struct StateData + { + friend QDataStream &operator<<(QDataStream &out, const StateData &data) + { + out << data.image; + out << data.nodeData; + + return out; + } + + friend QDataStream &operator>>(QDataStream &in, StateData &data) + { + in >> data.image; + in >> data.nodeData; + + return in; + } + + ImageContainer image; + QVector<NodeData> nodeData; + }; + + friend QDataStream &operator<<(QDataStream &out, const CapturedDataCommand &command) + { + out << command.stateData; + + return out; + } + + friend QDataStream &operator>>(QDataStream &in, CapturedDataCommand &command) + { + in >> command.stateData; + + return in; + } + +public: + QVector<StateData> stateData; +}; + +} // namespace QmlDesigner + +Q_DECLARE_METATYPE(QmlDesigner::CapturedDataCommand) diff --git a/share/qtcreator/qml/qmlpuppet/commands/commands.pri b/share/qtcreator/qml/qmlpuppet/commands/commands.pri index 03a44ae750..f343cbbd47 100644 --- a/share/qtcreator/qml/qmlpuppet/commands/commands.pri +++ b/share/qtcreator/qml/qmlpuppet/commands/commands.pri @@ -1,69 +1,70 @@ -INCLUDEPATH += $$PWD/ +INCLUDEPATH += $$PWD -HEADERS += $$PWD/synchronizecommand.h -HEADERS += $$PWD/changepreviewimagesizecommand.h -HEADERS += $$PWD/changelanguagecommand.h -HEADERS += $$PWD//debugoutputcommand.h -HEADERS += $$PWD/endpuppetcommand.h -HEADERS += $$PWD/tokencommand.h -HEADERS += $$PWD/componentcompletedcommand.h -HEADERS += $$PWD/completecomponentcommand.h -HEADERS += $$PWD/statepreviewimagechangedcommand.h -HEADERS += $$PWD/childrenchangedcommand.h -HEADERS += $$PWD/changebindingscommand.h -HEADERS += $$PWD/changefileurlcommand.h -HEADERS += $$PWD/changeidscommand.h -HEADERS += $$PWD/changenodesourcecommand.h -HEADERS += $$PWD/changestatecommand.h -HEADERS += $$PWD/changevaluescommand.h -HEADERS += $$PWD/createscenecommand.h -HEADERS += $$PWD/clearscenecommand.h -HEADERS += $$PWD/createinstancescommand.h -HEADERS += $$PWD/informationchangedcommand.h -HEADERS += $$PWD/pixmapchangedcommand.h -HEADERS += $$PWD/removeinstancescommand.h -HEADERS += $$PWD/removepropertiescommand.h -HEADERS += $$PWD/reparentinstancescommand.h -HEADERS += $$PWD/valueschangedcommand.h -HEADERS += $$PWD/changeauxiliarycommand.h -HEADERS += $$PWD/removesharedmemorycommand.h -HEADERS += $$PWD/puppetalivecommand.h -HEADERS += $$PWD/changeselectioncommand.h -HEADERS += $$PWD/update3dviewstatecommand.h -HEADERS += $$PWD/puppettocreatorcommand.h -HEADERS += $$PWD/inputeventcommand.h -HEADERS += $$PWD/view3dactioncommand.h +HEADERS += $$PWD/synchronizecommand.h \ \ + $$PWD/captureddatacommand.h \ + $$PWD/changepreviewimagesizecommand.h \ + $$PWD/changelanguagecommand.h \ + $$PWD//debugoutputcommand.h \ + $$PWD/endpuppetcommand.h \ + $$PWD/tokencommand.h \ + $$PWD/componentcompletedcommand.h \ + $$PWD/completecomponentcommand.h \ + $$PWD/statepreviewimagechangedcommand.h \ + $$PWD/childrenchangedcommand.h \ + $$PWD/changebindingscommand.h \ + $$PWD/changefileurlcommand.h \ + $$PWD/changeidscommand.h \ + $$PWD/changenodesourcecommand.h \ + $$PWD/changestatecommand.h \ + $$PWD/changevaluescommand.h \ + $$PWD/createscenecommand.h \ + $$PWD/clearscenecommand.h \ + $$PWD/createinstancescommand.h \ + $$PWD/informationchangedcommand.h \ + $$PWD/pixmapchangedcommand.h \ + $$PWD/removeinstancescommand.h \ + $$PWD/removepropertiescommand.h \ + $$PWD/reparentinstancescommand.h \ + $$PWD/valueschangedcommand.h \ + $$PWD/changeauxiliarycommand.h \ + $$PWD/removesharedmemorycommand.h \ + $$PWD/puppetalivecommand.h \ + $$PWD/changeselectioncommand.h \ + $$PWD/update3dviewstatecommand.h \ + $$PWD/puppettocreatorcommand.h \ + $$PWD/inputeventcommand.h \ + $$PWD/view3dactioncommand.h -SOURCES += $$PWD/synchronizecommand.cpp -SOURCES += $$PWD/changepreviewimagesizecommand.cpp -SOURCES += $$PWD/changelanguagecommand.cpp -SOURCES += $$PWD/debugoutputcommand.cpp -SOURCES += $$PWD/endpuppetcommand.cpp -SOURCES += $$PWD/tokencommand.cpp -SOURCES += $$PWD/componentcompletedcommand.cpp -SOURCES += $$PWD/completecomponentcommand.cpp -SOURCES += $$PWD/statepreviewimagechangedcommand.cpp -SOURCES += $$PWD/childrenchangedcommand.cpp -SOURCES += $$PWD/changebindingscommand.cpp -SOURCES += $$PWD/changefileurlcommand.cpp -SOURCES += $$PWD/changeidscommand.cpp -SOURCES += $$PWD/changenodesourcecommand.cpp -SOURCES += $$PWD/changestatecommand.cpp -SOURCES += $$PWD/changevaluescommand.cpp -SOURCES += $$PWD/informationchangedcommand.cpp -SOURCES += $$PWD/removeinstancescommand.cpp -SOURCES += $$PWD/removepropertiescommand.cpp -SOURCES += $$PWD/reparentinstancescommand.cpp -SOURCES += $$PWD/valueschangedcommand.cpp -SOURCES += $$PWD/clearscenecommand.cpp -SOURCES += $$PWD/createinstancescommand.cpp -SOURCES += $$PWD/createscenecommand.cpp -SOURCES += $$PWD/pixmapchangedcommand.cpp -SOURCES += $$PWD/changeauxiliarycommand.cpp -SOURCES += $$PWD/removesharedmemorycommand.cpp -SOURCES += $$PWD/puppetalivecommand.cpp -SOURCES += $$PWD/changeselectioncommand.cpp -SOURCES += $$PWD/update3dviewstatecommand.cpp -SOURCES += $$PWD/puppettocreatorcommand.cpp -SOURCES += $$PWD/inputeventcommand.cpp -SOURCES += $$PWD/view3dactioncommand.cpp +SOURCES += $$PWD/synchronizecommand.cpp \ + $$PWD/changepreviewimagesizecommand.cpp \ + $$PWD/changelanguagecommand.cpp \ + $$PWD/debugoutputcommand.cpp \ + $$PWD/endpuppetcommand.cpp \ + $$PWD/tokencommand.cpp \ + $$PWD/componentcompletedcommand.cpp \ + $$PWD/completecomponentcommand.cpp \ + $$PWD/statepreviewimagechangedcommand.cpp \ + $$PWD/childrenchangedcommand.cpp \ + $$PWD/changebindingscommand.cpp \ + $$PWD/changefileurlcommand.cpp \ + $$PWD/changeidscommand.cpp \ + $$PWD/changenodesourcecommand.cpp \ + $$PWD/changestatecommand.cpp \ + $$PWD/changevaluescommand.cpp \ + $$PWD/informationchangedcommand.cpp \ + $$PWD/removeinstancescommand.cpp \ + $$PWD/removepropertiescommand.cpp \ + $$PWD/reparentinstancescommand.cpp \ + $$PWD/valueschangedcommand.cpp \ + $$PWD/clearscenecommand.cpp \ + $$PWD/createinstancescommand.cpp \ + $$PWD/createscenecommand.cpp \ + $$PWD/pixmapchangedcommand.cpp \ + $$PWD/changeauxiliarycommand.cpp \ + $$PWD/removesharedmemorycommand.cpp \ + $$PWD/puppetalivecommand.cpp \ + $$PWD/changeselectioncommand.cpp \ + $$PWD/update3dviewstatecommand.cpp \ + $$PWD/puppettocreatorcommand.cpp \ + $$PWD/inputeventcommand.cpp \ + $$PWD/view3dactioncommand.cpp diff --git a/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.cpp b/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.cpp index 8a338df93d..24dad65090 100644 --- a/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.cpp +++ b/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.cpp @@ -35,6 +35,7 @@ #include "nodeinstanceserverinterface.h" +#include "captureddatacommand.h" #include "changeauxiliarycommand.h" #include "changebindingscommand.h" #include "changefileurlcommand.h" @@ -84,12 +85,11 @@ constexpr void (QLocalSocket::*LocalSocketErrorFunction)(QLocalSocket::LocalSock #endif NodeInstanceClientProxy::NodeInstanceClientProxy(QObject *parent) - : QObject(parent), - m_inputIoDevice(nullptr), - m_outputIoDevice(nullptr), - m_nodeInstanceServer(nullptr), - m_writeCommandCounter(0), - m_synchronizeId(-1) + : QObject(parent) + , m_inputIoDevice(nullptr) + , m_outputIoDevice(nullptr) + , m_writeCommandCounter(0) + , m_synchronizeId(-1) { connect(&m_puppetAliveTimer, &QTimer::timeout, this, &NodeInstanceClientProxy::sendPuppetAliveCommand); m_puppetAliveTimer.setInterval(2000); @@ -174,7 +174,8 @@ bool compareCommands(const QVariant &command, const QVariant &controlCommand) else if (command.userType() == debugOutputCommandType) return command.value<DebugOutputCommand>() == controlCommand.value<DebugOutputCommand>(); else if (command.userType() == changeSelectionCommandType) - return command.value<ChangeSelectionCommand>() == controlCommand.value<ChangeSelectionCommand>(); + return command.value<ChangeSelectionCommand>() + == controlCommand.value<ChangeSelectionCommand>(); } return false; @@ -267,6 +268,11 @@ void NodeInstanceClientProxy::handlePuppetToCreatorCommand(const PuppetToCreator writeCommand(QVariant::fromValue(command)); } +void NodeInstanceClientProxy::capturedData(const CapturedDataCommand &command) +{ + writeCommand(QVariant::fromValue(command)); +} + void NodeInstanceClientProxy::flush() { } @@ -365,12 +371,13 @@ void NodeInstanceClientProxy::sendPuppetAliveCommand() NodeInstanceServerInterface *NodeInstanceClientProxy::nodeInstanceServer() const { - return m_nodeInstanceServer; + return m_nodeInstanceServer.get(); } -void NodeInstanceClientProxy::setNodeInstanceServer(NodeInstanceServerInterface *nodeInstanceServer) +void NodeInstanceClientProxy::setNodeInstanceServer( + std::unique_ptr<NodeInstanceServerInterface> nodeInstanceServer) { - m_nodeInstanceServer = nodeInstanceServer; + m_nodeInstanceServer = std::move(nodeInstanceServer); } void NodeInstanceClientProxy::createInstances(const CreateInstancesCommand &command) diff --git a/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.h b/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.h index e6f4b58df4..fd681b6990 100644 --- a/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.h +++ b/share/qtcreator/qml/qmlpuppet/instances/nodeinstanceclientproxy.h @@ -69,7 +69,7 @@ class NodeInstanceClientProxy : public QObject, public NodeInstanceClientInterfa Q_OBJECT public: - NodeInstanceClientProxy(QObject *parent = nullptr); + NodeInstanceClientProxy(QObject *parent); void informationChanged(const InformationChangedCommand &command) override; void valuesChanged(const ValuesChangedCommand &command) override; @@ -83,6 +83,7 @@ public: void puppetAlive(const PuppetAliveCommand &command); void selectionChanged(const ChangeSelectionCommand &command) override; void handlePuppetToCreatorCommand(const PuppetToCreatorCommand &command) override; + void capturedData(const CapturedDataCommand &capturedData) override; void flush() override; void synchronizeWithClientProcess() override; @@ -94,7 +95,7 @@ protected: void writeCommand(const QVariant &command); void dispatchCommand(const QVariant &command); NodeInstanceServerInterface *nodeInstanceServer() const; - void setNodeInstanceServer(NodeInstanceServerInterface *nodeInstanceServer); + void setNodeInstanceServer(std::unique_ptr<NodeInstanceServerInterface> nodeInstanceServer); void createInstances(const CreateInstancesCommand &command); void changeFileUrl(const ChangeFileUrlCommand &command); @@ -130,7 +131,7 @@ private: QTimer m_puppetAliveTimer; QIODevice *m_inputIoDevice; QIODevice *m_outputIoDevice; - NodeInstanceServerInterface *m_nodeInstanceServer; + std::unique_ptr<NodeInstanceServerInterface> m_nodeInstanceServer; quint32 m_writeCommandCounter; int m_synchronizeId; }; diff --git a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceclientinterface.h b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceclientinterface.h index 9498cfb1f7..d60e0d7ff0 100644 --- a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceclientinterface.h +++ b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceclientinterface.h @@ -42,6 +42,7 @@ class DebugOutputCommand; class PuppetAliveCommand; class ChangeSelectionCommand; class PuppetToCreatorCommand; +class CapturedDataCommand; class NodeInstanceClientInterface { @@ -57,6 +58,7 @@ public: virtual void debugOutput(const DebugOutputCommand &command) = 0; virtual void selectionChanged(const ChangeSelectionCommand &command) = 0; virtual void handlePuppetToCreatorCommand(const PuppetToCreatorCommand &command) = 0; + virtual void capturedData(const CapturedDataCommand &command) = 0; virtual void flush() {} virtual void synchronizeWithClientProcess() {} diff --git a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.cpp b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.cpp index 62669d3158..e3a05376e2 100644 --- a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.cpp +++ b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.cpp @@ -27,16 +27,17 @@ #include <qmetatype.h> #include "addimportcontainer.h" +#include "captureddatacommand.h" #include "changeauxiliarycommand.h" #include "changebindingscommand.h" #include "changefileurlcommand.h" #include "changeidscommand.h" #include "changelanguagecommand.h" #include "changenodesourcecommand.h" +#include "changepreviewimagesizecommand.h" #include "changeselectioncommand.h" #include "changestatecommand.h" #include "changevaluescommand.h" -#include "changepreviewimagesizecommand.h" #include "childrenchangedcommand.h" #include "clearscenecommand.h" #include "completecomponentcommand.h" @@ -219,6 +220,9 @@ void NodeInstanceServerInterface::registerCommands() qRegisterMetaType<ChangePreviewImageSizeCommand>("ChangePreviewImageSizeCommand"); qRegisterMetaTypeStreamOperators<ChangePreviewImageSizeCommand>("ChangePreviewImageSizeCommand"); + + qRegisterMetaType<CapturedDataCommand>("CapturedDataCommand"); + qRegisterMetaTypeStreamOperators<CapturedDataCommand>("CapturedDataCommand"); } } diff --git a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h index 10c2d1fdbb..39eb2618d6 100644 --- a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h +++ b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h @@ -60,11 +60,6 @@ class NodeInstanceServerInterface : public QObject { Q_OBJECT public: - enum RunModus { - NormalModus, - TestModus // No preview images and synchronized - }; - explicit NodeInstanceServerInterface(QObject *parent = nullptr); virtual void createInstances(const CreateInstancesCommand &command) = 0; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri index 7072196437..b57e3aab8f 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri @@ -5,54 +5,56 @@ versionAtLeast(QT_VERSION, 5.15.0):qtHaveModule(quick3d) { DEFINES *= QUICK3D_MODULE } -HEADERS += $$PWD/qt5nodeinstanceserver.h -HEADERS += $$PWD/qt5testnodeinstanceserver.h -HEADERS += $$PWD/qt5informationnodeinstanceserver.h -HEADERS += $$PWD/qt5rendernodeinstanceserver.h -HEADERS += $$PWD/qt5previewnodeinstanceserver.h -HEADERS += $$PWD/qt5nodeinstanceclientproxy.h -HEADERS += $$PWD/quickitemnodeinstance.h -HEADERS += $$PWD/behaviornodeinstance.h -HEADERS += $$PWD/dummycontextobject.h -HEADERS += $$PWD/childrenchangeeventfilter.h -HEADERS += $$PWD/componentnodeinstance.h -HEADERS += $$PWD/dummynodeinstance.h -HEADERS += $$PWD/nodeinstanceserver.h -HEADERS += $$PWD/nodeinstancesignalspy.h -HEADERS += $$PWD/objectnodeinstance.h -HEADERS += $$PWD/qmlpropertychangesnodeinstance.h -HEADERS += $$PWD/qmlstatenodeinstance.h -HEADERS += $$PWD/qmltransitionnodeinstance.h -HEADERS += $$PWD/servernodeinstance.h -HEADERS += $$PWD/anchorchangesnodeinstance.h -HEADERS += $$PWD/positionernodeinstance.h -HEADERS += $$PWD/layoutnodeinstance.h -HEADERS += $$PWD/qt3dpresentationnodeinstance.h -HEADERS += $$PWD/quick3dnodeinstance.h -HEADERS += $$PWD/quick3dtexturenodeinstance.h +HEADERS += $$PWD/qt5nodeinstanceserver.h \ + $$PWD/qt5capturenodeinstanceserver.h \ + $$PWD/qt5testnodeinstanceserver.h \ + $$PWD/qt5informationnodeinstanceserver.h \ + $$PWD/qt5rendernodeinstanceserver.h \ + $$PWD/qt5previewnodeinstanceserver.h \ + $$PWD/qt5nodeinstanceclientproxy.h \ + $$PWD/quickitemnodeinstance.h \ + $$PWD/behaviornodeinstance.h \ + $$PWD/dummycontextobject.h \ + $$PWD/childrenchangeeventfilter.h \ + $$PWD/componentnodeinstance.h \ + $$PWD/dummynodeinstance.h \ + $$PWD/nodeinstanceserver.h \ + $$PWD/nodeinstancesignalspy.h \ + $$PWD/objectnodeinstance.h \ + $$PWD/qmlpropertychangesnodeinstance.h \ + $$PWD/qmlstatenodeinstance.h \ + $$PWD/qmltransitionnodeinstance.h \ + $$PWD/servernodeinstance.h \ + $$PWD/anchorchangesnodeinstance.h \ + $$PWD/positionernodeinstance.h \ + $$PWD/layoutnodeinstance.h \ + $$PWD/qt3dpresentationnodeinstance.h \ + $$PWD/quick3dnodeinstance.h \ + $$PWD/quick3dtexturenodeinstance.h -SOURCES += $$PWD/qt5nodeinstanceserver.cpp -SOURCES += $$PWD/qt5testnodeinstanceserver.cpp -SOURCES += $$PWD/qt5informationnodeinstanceserver.cpp -SOURCES += $$PWD/qt5rendernodeinstanceserver.cpp -SOURCES += $$PWD/qt5previewnodeinstanceserver.cpp -SOURCES += $$PWD/qt5nodeinstanceclientproxy.cpp -SOURCES += $$PWD/quickitemnodeinstance.cpp -SOURCES += $$PWD/behaviornodeinstance.cpp -SOURCES += $$PWD/dummycontextobject.cpp -SOURCES += $$PWD/childrenchangeeventfilter.cpp -SOURCES += $$PWD/componentnodeinstance.cpp -SOURCES += $$PWD/dummynodeinstance.cpp -SOURCES += $$PWD/nodeinstanceserver.cpp -SOURCES += $$PWD/nodeinstancesignalspy.cpp -SOURCES += $$PWD/objectnodeinstance.cpp -SOURCES += $$PWD/qmlpropertychangesnodeinstance.cpp -SOURCES += $$PWD/qmlstatenodeinstance.cpp -SOURCES += $$PWD/qmltransitionnodeinstance.cpp -SOURCES += $$PWD/servernodeinstance.cpp -SOURCES += $$PWD/anchorchangesnodeinstance.cpp -SOURCES += $$PWD/positionernodeinstance.cpp -SOURCES += $$PWD/layoutnodeinstance.cpp -SOURCES += $$PWD/qt3dpresentationnodeinstance.cpp -SOURCES += $$PWD/quick3dnodeinstance.cpp -SOURCES += $$PWD/quick3dtexturenodeinstance.cpp +SOURCES += $$PWD/qt5nodeinstanceserver.cpp \ + $$PWD/qt5capturenodeinstanceserver.cpp \ + $$PWD/qt5testnodeinstanceserver.cpp \ + $$PWD/qt5informationnodeinstanceserver.cpp \ + $$PWD/qt5rendernodeinstanceserver.cpp \ + $$PWD/qt5previewnodeinstanceserver.cpp \ + $$PWD/qt5nodeinstanceclientproxy.cpp \ + $$PWD/quickitemnodeinstance.cpp \ + $$PWD/behaviornodeinstance.cpp \ + $$PWD/dummycontextobject.cpp \ + $$PWD/childrenchangeeventfilter.cpp \ + $$PWD/componentnodeinstance.cpp \ + $$PWD/dummynodeinstance.cpp \ + $$PWD/nodeinstanceserver.cpp \ + $$PWD/nodeinstancesignalspy.cpp \ + $$PWD/objectnodeinstance.cpp \ + $$PWD/qmlpropertychangesnodeinstance.cpp \ + $$PWD/qmlstatenodeinstance.cpp \ + $$PWD/qmltransitionnodeinstance.cpp \ + $$PWD/servernodeinstance.cpp \ + $$PWD/anchorchangesnodeinstance.cpp \ + $$PWD/positionernodeinstance.cpp \ + $$PWD/layoutnodeinstance.cpp \ + $$PWD/qt3dpresentationnodeinstance.cpp \ + $$PWD/quick3dnodeinstance.cpp \ + $$PWD/quick3dtexturenodeinstance.cpp diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp index 1b48a9ae14..ce5cf29953 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp @@ -179,6 +179,8 @@ NodeInstanceServer::NodeInstanceServer(NodeInstanceClientInterface *nodeInstance m_childrenChangeEventFilter(new Internal::ChildrenChangeEventFilter(this)), m_nodeInstanceClient(nodeInstanceClient) { + m_idInstances.reserve(1000); + qmlRegisterType<DummyContextObject>("QmlDesigner", 1, 0, "DummyContextObject"); connect(m_childrenChangeEventFilter.data(), &Internal::ChildrenChangeEventFilter::childrenChanged, this, &NodeInstanceServer::emitParentChanged); @@ -226,8 +228,8 @@ ServerNodeInstance NodeInstanceServer::instanceForId(qint32 id) const if (id < 0) return ServerNodeInstance(); - Q_ASSERT(m_idInstanceHash.contains(id)); - return m_idInstanceHash.value(id); + Q_ASSERT(m_idInstances.size() > id); + return m_idInstances[id]; } bool NodeInstanceServer::hasInstanceForId(qint32 id) const @@ -235,7 +237,7 @@ bool NodeInstanceServer::hasInstanceForId(qint32 id) const if (id < 0) return false; - return m_idInstanceHash.contains(id) && m_idInstanceHash.value(id).isValid(); + return m_idInstances.size() > id && m_idInstances[id].isValid(); } ServerNodeInstance NodeInstanceServer::instanceForObject(QObject *object) const @@ -790,7 +792,7 @@ void NodeInstanceServer::removeAllInstanceRelationships() instance.makeInvalid(); } - m_idInstanceHash.clear(); + m_idInstances.clear(); m_objectInstanceHash.clear(); } @@ -1243,10 +1245,11 @@ void NodeInstanceServer::notifyPropertyChange(qint32 instanceid, const PropertyN void NodeInstanceServer::insertInstanceRelationship(const ServerNodeInstance &instance) { Q_ASSERT(instance.isValid()); - Q_ASSERT(!m_idInstanceHash.contains(instance.instanceId())); Q_ASSERT(!m_objectInstanceHash.contains(instance.internalObject())); m_objectInstanceHash.insert(instance.internalObject(), instance); - m_idInstanceHash.insert(instance.instanceId(), instance); + if (instance.instanceId() >= m_idInstances.size()) + m_idInstances.resize(instance.instanceId() + 1); + m_idInstances[instance.instanceId()] = instance; } void NodeInstanceServer::removeInstanceRelationsip(qint32 instanceId) @@ -1255,7 +1258,7 @@ void NodeInstanceServer::removeInstanceRelationsip(qint32 instanceId) ServerNodeInstance instance = instanceForId(instanceId); if (instance.isValid()) instance.setId(QString()); - m_idInstanceHash.remove(instanceId); + m_idInstances[instanceId] = ServerNodeInstance{}; m_objectInstanceHash.remove(instance.internalObject()); instance.makeInvalid(); } @@ -1383,8 +1386,8 @@ void NodeInstanceServer::removeInstanceRelationsipForDeletedObject(QObject *obje ServerNodeInstance instance = instanceForObject(object); m_objectInstanceHash.remove(object); - if (m_idInstanceHash.contains(instance.instanceId())) - m_idInstanceHash.remove(instance.instanceId()); + if (instance.instanceId() >= 0 && m_idInstances.size() > instance.instanceId()) + m_idInstances[instance.instanceId()] = ServerNodeInstance{}; } } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h index b0dc39a2a2..140e216049 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h @@ -160,6 +160,8 @@ public: ServerNodeInstance instanceForObject(QObject *object) const; bool hasInstanceForObject(QObject *object) const; + const QVector<ServerNodeInstance> &nodeInstances() const { return m_idInstances; } + virtual QQmlEngine *engine() const = 0; QQmlContext *context() const; @@ -277,7 +279,7 @@ private: void setupOnlyWorkingImports(const QStringList &workingImportStatementList); ServerNodeInstance m_rootNodeInstance; ServerNodeInstance m_activeStateInstance; - QHash<qint32, ServerNodeInstance> m_idInstanceHash; + QVector<ServerNodeInstance> m_idInstances; QHash<QObject*, ServerNodeInstance> m_objectInstanceHash; QMultiHash<QString, ObjectPropertyPair> m_fileSystemWatcherHash; QList<QPair<QString, QPointer<QObject> > > m_dummyObjectList; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.cpp new file mode 100644 index 0000000000..aea75a76c7 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#include "qt5capturenodeinstanceserver.h" +#include "servernodeinstance.h" + +#include <captureddatacommand.h> +#include <createscenecommand.h> +#include <nodeinstanceclientinterface.h> + +#include <QImage> +#include <QQuickView> + +namespace QmlDesigner { + +namespace { + +QImage renderPreviewImage(ServerNodeInstance rootNodeInstance) +{ + rootNodeInstance.updateDirtyNodeRecursive(); + + QSize previewImageSize = rootNodeInstance.boundingRect().size().toSize(); + + QImage previewImage = rootNodeInstance.renderPreviewImage(previewImageSize); + + return previewImage; +} + +CapturedDataCommand::StateData collectStateData(ServerNodeInstance rootNodeInstance, + const QVector<ServerNodeInstance> &nodeInstances, + qint32 stateInstanceId) +{ + CapturedDataCommand::StateData stateData; + stateData.image = ImageContainer(stateInstanceId, + QmlDesigner::renderPreviewImage(rootNodeInstance), + stateInstanceId); + + for (const ServerNodeInstance &instance : nodeInstances) { + auto textProperty = instance.property("text"); + if (!textProperty.isNull() && instance.holdsGraphical()) { + CapturedDataCommand::NodeData nodeData; + nodeData.nodeId = instance.instanceId(); + nodeData.contentRect = instance.contentItemBoundingRect(); + nodeData.sceneTransform = instance.sceneTransform(); + nodeData.text = textProperty.toString(); + stateData.nodeData.push_back(std::move(nodeData)); + } + } + + return stateData; +} +} // namespace + +void Qt5CaptureNodeInstanceServer::collectItemChangesAndSendChangeCommands() +{ + static bool inFunction = false; + + if (!rootNodeInstance().holdsGraphical()) + return; + + if (!inFunction) { + inFunction = true; + + DesignerSupport::polishItems(quickView()); + + QVector<CapturedDataCommand::StateData> stateDatas; + stateDatas.push_back(collectStateData(rootNodeInstance(), nodeInstances(), 0)); + + for (ServerNodeInstance stateInstance : rootNodeInstance().stateInstances()) { + stateInstance.activateState(); + stateDatas.push_back( + collectStateData(rootNodeInstance(), nodeInstances(), stateInstance.instanceId())); + stateInstance.deactivateState(); + } + + nodeInstanceClient()->capturedData(CapturedDataCommand{stateDatas}); + + slowDownRenderTimer(); + inFunction = false; + } +} + +} // namespace QmlDesigner diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.h new file mode 100644 index 0000000000..cd0208e563 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturenodeinstanceserver.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#pragma once + +#include <qt5previewnodeinstanceserver.h> + +namespace QmlDesigner { + +class Qt5CaptureNodeInstanceServer : public Qt5PreviewNodeInstanceServer +{ +public: + explicit Qt5CaptureNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient) + : Qt5PreviewNodeInstanceServer(nodeInstanceClient) + {} + +protected: + void collectItemChangesAndSendChangeCommands() override; + +private: +}; + +} // namespace QmlDesigner diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp index 449e4ff188..1cdfc91074 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp @@ -27,6 +27,7 @@ #include <QCoreApplication> +#include "qt5capturenodeinstanceserver.h" #include "qt5informationnodeinstanceserver.h" #include "qt5previewnodeinstanceserver.h" #include "qt5rendernodeinstanceserver.h" @@ -37,7 +38,7 @@ #if defined(Q_OS_UNIX) #include <unistd.h> #elif defined(Q_OS_WIN) -#include <windows.h> +#include <Windows.h> #endif namespace QmlDesigner { @@ -57,18 +58,21 @@ Qt5NodeInstanceClientProxy::Qt5NodeInstanceClientProxy(QObject *parent) : DesignerSupport::activateDesignerWindowManager(); if (QCoreApplication::arguments().at(1) == QLatin1String("--readcapturedstream")) { qputenv("DESIGNER_DONT_USE_SHARED_MEMORY", "1"); - setNodeInstanceServer(new Qt5TestNodeInstanceServer(this)); + setNodeInstanceServer(std::make_unique<Qt5TestNodeInstanceServer>(this)); initializeCapturedStream(QCoreApplication::arguments().at(2)); readDataStream(); QCoreApplication::exit(); } else if (QCoreApplication::arguments().at(2) == QLatin1String("previewmode")) { - setNodeInstanceServer(new Qt5PreviewNodeInstanceServer(this)); + setNodeInstanceServer(std::make_unique<Qt5PreviewNodeInstanceServer>(this)); initializeSocket(); } else if (QCoreApplication::arguments().at(2) == QLatin1String("editormode")) { - setNodeInstanceServer(new Qt5InformationNodeInstanceServer(this)); + setNodeInstanceServer(std::make_unique<Qt5InformationNodeInstanceServer>(this)); initializeSocket(); } else if (QCoreApplication::arguments().at(2) == QLatin1String("rendermode")) { - setNodeInstanceServer(new Qt5RenderNodeInstanceServer(this)); + setNodeInstanceServer(std::make_unique<Qt5RenderNodeInstanceServer>(this)); + initializeSocket(); + } else if (QCoreApplication::arguments().at(2) == QLatin1String("capturemode")) { + setNodeInstanceServer(std::make_unique<Qt5CaptureNodeInstanceServer>(this)); initializeSocket(); } } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp index 852ef16bad..810c8f0dcf 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp @@ -84,7 +84,8 @@ void Qt5PreviewNodeInstanceServer::collectItemChangesAndSendChangeCommands() instance.deactivateState(); } - nodeInstanceClient()->statePreviewImagesChanged(StatePreviewImageChangedCommand(imageContainerVector)); + nodeInstanceClient()->statePreviewImagesChanged( + StatePreviewImageChangedCommand(imageContainerVector)); slowDownRenderTimer(); handleExtraRender(); @@ -105,7 +106,7 @@ QImage Qt5PreviewNodeInstanceServer::renderPreviewImage() QSize previewImageSize = boundingRect.size().toSize(); - if (!m_previewSize.isNull()) + if (m_previewSize.isValid() && !m_previewSize.isNull()) previewImageSize.scale(m_previewSize, Qt::KeepAspectRatio); QImage previewImage = rootNodeInstance().renderPreviewImage(previewImageSize); @@ -124,9 +125,6 @@ void Qt5PreviewNodeInstanceServer::changePreviewImageSize( { m_previewSize = command.size; - if (!command.size.isValid()) - m_previewSize = {160, 160}; - collectItemChangesAndSendChangeCommands(); } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h index 10943a676a..ea27429c66 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h @@ -72,6 +72,7 @@ class ServerNodeInstance friend class Qt5InformationNodeInstanceServer; friend class Qt5NodeInstanceServer; friend class Qt5PreviewNodeInstanceServer; + friend class Qt5CaptureNodeInstanceServer; friend class Qt5TestNodeInstanceServer; friend class QHash<qint32, ServerNodeInstance>; friend uint qHash(const ServerNodeInstance &instance); @@ -171,6 +172,8 @@ public: static bool isSubclassOf(QObject *object, const QByteArray &superTypeName); void setModifiedFlag(bool b); + void updateDirtyNodeRecursive(); + bool holdsGraphical() const; private: // functions ServerNodeInstance(const QSharedPointer<Internal::ObjectNodeInstance> &abstractInstance); @@ -197,7 +200,6 @@ private: // functions void setDeleteHeldInstance(bool deleteInstance); void reparent(const ServerNodeInstance &oldParentInstance, const PropertyName &oldParentProperty, const ServerNodeInstance &newParentInstance, const PropertyName &newParentProperty); - void setId(const QString &id); static QSharedPointer<Internal::ObjectNodeInstance> createInstance(QObject *objectToBeWrapped); @@ -206,10 +208,6 @@ private: // functions void setNodeSource(const QString &source); - bool holdsGraphical() const; - - void updateDirtyNodeRecursive(); - QObject *internalObject() const; // should be not used outside of the nodeinstances!!!! private: // variables diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml index 6c5e6fde86..6132cfd261 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/StandardTextSection.qml @@ -26,6 +26,8 @@ import QtQuick 2.1 import HelperWidgets 2.0 import QtQuick.Layouts 1.0 +import StudioControls 1.0 as StudioControls +import StudioTheme 1.0 as StudioTheme Section { anchors.left: parent.left @@ -46,9 +48,29 @@ Section { Label { text: qsTr("Text") } - LineEdit { - backendValue: backendValues.text - Layout.fillWidth: true + + RowLayout { + LineEdit { + backendValue: backendValues.text + Layout.fillWidth: true + } + + StudioControls.AbstractButton { + id: richTextEditorButton + buttonIcon: StudioTheme.Constants.textAlignTop + onClicked: { + richTextDialogLoader.show() + } + } + + RichTextEditor{ + onRejected: { + hideWidget() + } + onAccepted: { + hideWidget() + } + } } Label { @@ -219,4 +241,39 @@ Section { Layout.fillWidth: true } } + + Loader { + id: richTextDialogLoader + + visible: false + active: visible + + function show() { + richTextDialogLoader.visible = true + } + + sourceComponent: Item { + id: richTextEditorParent + + Component.onCompleted: { + richTextEditor.showWidget() + richTextEditor.richText = backendValues.text.value + } + + RichTextEditor { + id: richTextEditor + + onRejected: { + hideWidget() + richTextDialogLoader.visible = false + } + onAccepted: { + backendValues.text.value = richTextEditor.richText + backendValues.textFormat.setEnumeration("Text", "RichText") + hideWidget() + richTextDialogLoader.visible = false + } + } + } + } } diff --git a/src/libs/advanceddockingsystem/elidinglabel.cpp b/src/libs/advanceddockingsystem/elidinglabel.cpp index 75808ff9c1..d58494b7e1 100644 --- a/src/libs/advanceddockingsystem/elidinglabel.cpp +++ b/src/libs/advanceddockingsystem/elidinglabel.cpp @@ -1,4 +1,4 @@ -/**************************************************************************** +/**************************************************************************** ** ** Copyright (C) 2020 Uwe Kindler ** Contact: https://www.qt.io/licensing/ diff --git a/src/libs/sqlite/sqlite-lib.pri b/src/libs/sqlite/sqlite-lib.pri index 1ab7631387..8ded8ca106 100644 --- a/src/libs/sqlite/sqlite-lib.pri +++ b/src/libs/sqlite/sqlite-lib.pri @@ -67,3 +67,5 @@ CONFIG(debug, debug|release): DEFINES += SQLITE_ENABLE_API_ARMOR OTHER_FILES += README.md contains(QT_CONFIG, reduce_exports):CONFIG += hide_symbols + +CONFIG += exceptions diff --git a/src/libs/utils/smallstringio.h b/src/libs/utils/smallstringio.h index 233b9b5d97..308b44f155 100644 --- a/src/libs/utils/smallstringio.h +++ b/src/libs/utils/smallstringio.h @@ -78,12 +78,7 @@ QDebug &operator<<(QDebug &debug, const String &string) template <uint Size> std::ostream &operator<<(std::ostream &out, const BasicSmallString<Size> &string) { - BasicSmallString<Size> formatedString = string.clone(); - - formatedString.replace("\n", "\\n"); - formatedString.replace("\t", "\\t"); - - out.write(formatedString.data(), std::streamsize(formatedString.size())); + out.write(string.data(), std::streamsize(string.size())); return out; } diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp index d808ca8d7f..03e24ee3af 100644 --- a/src/plugins/projectexplorer/project.cpp +++ b/src/plugins/projectexplorer/project.cpp @@ -1000,6 +1000,21 @@ QVariant Project::extraData(const QString &key) const return d->m_extraData.value(key); } +QStringList Project::availableQmlPreviewTranslations(QString *errorMessage) +{ + const auto projectDirectory = rootProjectDirectory().toFileInfo().absoluteFilePath(); + const QDir languageDirectory(projectDirectory + "/i18n"); + const auto qmFiles = languageDirectory.entryList({"qml_*.qm"}); + if (qmFiles.isEmpty() && errorMessage) + errorMessage->append(tr("Could not find any qml_*.qm file at '%1'").arg(languageDirectory.absolutePath())); + return Utils::transform(qmFiles, [](const QString &qmFile) { + const int localeStartPosition = qmFile.lastIndexOf("_") + 1; + const int localeEndPosition = qmFile.size() - QString(".qm").size(); + const QString locale = qmFile.left(localeEndPosition).mid(localeStartPosition); + return locale; + }); +} + #if defined(WITH_TESTS) } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h index f03fe6c7c9..8d12e4c15c 100644 --- a/src/plugins/projectexplorer/project.h +++ b/src/plugins/projectexplorer/project.h @@ -170,6 +170,8 @@ public: void setExtraData(const QString &key, const QVariant &data); QVariant extraData(const QString &key) const; + QStringList availableQmlPreviewTranslations(QString *errorMessage); + signals: void projectFileIsDirty(const Utils::FilePath &path); diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 6f7cd737cf..5c6479653d 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -505,6 +505,12 @@ extend_qtc_plugin(QmlDesigner puppetbuildprogressdialog.cpp puppetbuildprogressdialog.h puppetbuildprogressdialog.ui puppetcreator.cpp puppetcreator.h puppetdialog.cpp puppetdialog.h puppetdialog.ui + connectionmanagerinterface.cpp connectionmanagerinterface.h + baseconnectionmanager.cpp baseconnectionmanager.h + connectionmanager.cpp connectionmanager.h + capturingconnectionmanager.cpp capturingconnectionmanager.h + interactiveconnectionmanager.cpp interactiveconnectionmanager.h + qprocessuniqueptr.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h index a1c0e2181c..5f09b74860 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h @@ -45,14 +45,19 @@ const char DocumentNameTag[] = "name"; // Layer data tags const char ArtboardListTag[] = "artboards"; +const char NameTag[] = "name"; + const char XPosTag[] = "x"; const char YPosTag[] = "y"; const char WidthTag[] = "width"; const char HeightTag[] = "height"; - +const char MetadataTag[] = "metadata"; +const char ChildrenTag[] = "children"; const char QmlIdTag[] = "qmlId"; const char ExportTypeTag[] = "exportType"; +const char ExportTypeComponent[] = "component"; +const char ExportTypeChild[] = "child"; const char QmlPropertiesTag[] = "qmlProperties"; const char ImportsTag[] = "extraImports"; const char UuidTag[] = "uuid"; diff --git a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp index 059b6ecb16..973cb6e013 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp @@ -53,6 +53,7 @@ static void populateLineage(const QmlDesigner::ModelNode &node, QByteArrayList & } namespace QmlDesigner { +using namespace Constants; std::vector<std::unique_ptr<Internal::NodeParserCreatorBase>> Component::m_readers; Component::Component(AssetExporter &exporter, const ModelNode &rootNode): @@ -76,6 +77,10 @@ void Component::exportComponent() { QTC_ASSERT(m_rootNode.isValid(), return); m_json = nodeToJson(m_rootNode); + // Change the export type to component + QJsonObject metadata = m_json.value(MetadataTag).toObject(); + metadata.insert(ExportTypeTag, ExportTypeComponent); + m_json.insert(MetadataTag, metadata); addImports(); } @@ -124,7 +129,7 @@ QJsonObject Component::nodeToJson(const ModelNode &node) children.append(nodeToJson(childnode)); if (!children.isEmpty()) - jsonObject.insert("children", children); + jsonObject.insert(ChildrenTag, children); return jsonObject; } diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp index a42b730062..adc46678ae 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp @@ -58,7 +58,10 @@ QJsonObject AssetNodeParser::json(Component &component) const Utils::FilePath assetPath = component.exporter().exportAsset(objectNode(), uuid()); QJsonObject assetData; assetData.insert(AssetPathTag, assetPath.toString()); - jsonObject.insert(AssetDataTag, assetData); + + QJsonObject metadata = jsonObject.value(MetadataTag).toObject(); + metadata.insert(AssetDataTag, assetData); + jsonObject.insert(MetadataTag, metadata); return jsonObject; } } diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp index 5104732e1c..43963aa8b2 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp @@ -28,6 +28,17 @@ #include "qmlitemnode.h" +namespace { +static QString capitalize(const QString &str) +{ + if (str.isEmpty()) + return {}; + QString tmp = str; + tmp[0] = QChar(str[0]).toUpper().toLatin1(); + return tmp; +} +} + namespace QmlDesigner { using namespace Constants; ItemNodeParser::ItemNodeParser(const QByteArrayList &lineage, @@ -47,10 +58,16 @@ QJsonObject QmlDesigner::ItemNodeParser::json(QmlDesigner::Component &component) Q_UNUSED(component); const QmlObjectNode &qmlObjectNode = objectNode(); QJsonObject jsonObject; - jsonObject.insert(QmlIdTag, qmlObjectNode.id()); - QmlItemNode itemNode = qmlObjectNode.toQmlItemNode(); + + const QString qmlId = qmlObjectNode.id(); + QString name = m_node.simplifiedTypeName(); + if (!qmlId.isEmpty()) + name.append("_" + capitalize(qmlId)); + + jsonObject.insert(NameTag, name); // Position relative to parent + QmlItemNode itemNode = qmlObjectNode.toQmlItemNode(); QPointF pos = itemNode.instancePosition(); jsonObject.insert(XPosTag, pos.x()); jsonObject.insert(YPosTag, pos.y()); @@ -60,10 +77,13 @@ QJsonObject QmlDesigner::ItemNodeParser::json(QmlDesigner::Component &component) jsonObject.insert(WidthTag, size.width()); jsonObject.insert(HeightTag, size.height()); - jsonObject.insert(UuidTag, uuid()); - jsonObject.insert(ExportTypeTag, "child"); - jsonObject.insert(TypeNameTag, QString::fromLatin1(m_node.type())); + QJsonObject metadata; + metadata.insert(QmlIdTag, qmlId); + metadata.insert(UuidTag, uuid()); + metadata.insert(ExportTypeTag, ExportTypeChild); + metadata.insert(TypeNameTag, QString::fromLatin1(m_node.type())); - return jsonObject; + jsonObject.insert(MetadataTag, metadata); + return jsonObject; } } diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp index bffe5ed8d5..9b797ad77d 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp @@ -81,7 +81,9 @@ QJsonObject TextNodeParser::json(Component &component) const textDetails.insert(IsMultilineTag, propertyValue("wrapMode").toString().compare("NoWrap") != 0); - jsonObject.insert(TextDetailsTag, textDetails); + QJsonObject metadata = jsonObject.value(MetadataTag).toObject(); + metadata.insert(TextDetailsTag, textDetails); + jsonObject.insert(MetadataTag, metadata); return jsonObject; } } diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index cbc987651a..feff075582 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -363,32 +363,30 @@ public: bool isEnabled(const SelectionContext &) const override { return true; } - static ModelNode listModelNode(const ModelNode &listViewNode) - { - if (listViewNode.hasProperty("model")) { - if (listViewNode.hasBindingProperty("model")) - return listViewNode.bindingProperty("model").resolveToModelNode(); - else if (listViewNode.hasNodeProperty("model")) - return listViewNode.nodeProperty("model").modelNode(); - } - - ModelNode newModel = listViewNode.view()->createModelNode("QtQml.Models.ListModel", 2, 15); - listViewNode.nodeProperty("mode").reparentHere(newModel); - - return newModel; - } - static void openDialog(const SelectionContext &selectionState) { - ListModelEditorModel model; - ModelNode targetNode = selectionState.targetNode(); if (!targetNode.isValid()) targetNode = selectionState.currentSingleSelectedNode(); if (!targetNode.isValid()) return; - model.setListModel(listModelNode(targetNode)); + AbstractView *view = targetNode.view(); + NodeMetaInfo modelMetaInfo = view->model()->metaInfo("ListModel"); + NodeMetaInfo elementMetaInfo = view->model()->metaInfo("ListElement"); + + ListModelEditorModel model{[&] { + return view->createModelNode(modelMetaInfo.typeName(), + modelMetaInfo.majorVersion(), + modelMetaInfo.minorVersion()); + }, + [&] { + return view->createModelNode(elementMetaInfo.typeName(), + elementMetaInfo.majorVersion(), + elementMetaInfo.minorVersion()); + }}; + + model.setListView(targetNode); ListModelEditorDialog dialog{Core::ICore::mainWindow()}; dialog.setModel(&model); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp index 6a4acacb97..b621dacb4a 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp @@ -283,6 +283,11 @@ bool FormEditorItem::flowHitTest(const QPointF & ) const return false; } +void FormEditorItem::setFrameColor(const QColor &color) +{ + m_frameColor = color; +} + FormEditorItem::~FormEditorItem() { scene()->removeItemFromHash(this); @@ -313,14 +318,21 @@ void FormEditorItem::paintBoundingRect(QPainter *painter) const pen.setJoinStyle(Qt::MiterJoin); const QColor frameColor(0xaa, 0xaa, 0xaa); - static const QColor selectionColor = Utils::creatorTheme()->color(Utils::Theme::QmlDesigner_FormEditorSelectionColor); + static const QColor selectionColor = Utils::creatorTheme()->color( + Utils::Theme::QmlDesigner_FormEditorSelectionColor); if (scene()->showBoundingRects()) { pen.setColor(frameColor.darker(150)); pen.setStyle(Qt::DotLine); painter->setPen(pen); painter->drawRect(m_boundingRect.adjusted(0., 0., -1., -1.)); + } + if (m_frameColor.isValid()) { + pen.setColor(m_frameColor); + pen.setStyle(Qt::SolidLine); + painter->setPen(pen); + painter->drawRect(m_boundingRect.adjusted(0., 0., -1., -1.)); } if (m_highlightBoundingRect) { diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h index 61ade49916..37e34fc4f7 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.h +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.h @@ -117,6 +117,8 @@ public: virtual bool flowHitTest(const QPointF &point) const; + void setFrameColor(const QColor &color); + protected: AbstractFormEditorTool* tool() const; void paintBoundingRect(QPainter *painter) const; @@ -129,6 +131,7 @@ protected: QRectF m_boundingRect; QRectF m_paintedBoundingRect; QRectF m_selectionBoundingRect; + QColor m_frameColor{0xaa, 0xaa, 0xaa}; private: // functions void setup(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp index 6eec3f933c..96dda07c51 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp @@ -633,6 +633,11 @@ void FormEditorView::auxiliaryDataChanged(const ModelNode &node, const PropertyN editorItem->update(); } } + + if (name == "FrameColor@Internal") { + if (FormEditorItem *editorItem = scene()->itemForQmlItemNode(item)) + editorItem->setFrameColor(data.value<QColor>()); + } } void FormEditorView::instancesCompleted(const QVector<ModelNode> &completedNodeList) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index 6a5edb18a3..1fc817cd01 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -86,7 +86,7 @@ void ItemLibraryAssetImporter::importQuick3D(const QStringList &inputFiles, if (!isCancelled()) { // Wait for icon generation processes to finish - if (m_qmlPuppetProcesses.isEmpty()) { + if (m_qmlPuppetProcesses.empty()) { finalizeQuick3DImport(); } else { m_qmlPuppetCount = m_qmlPuppetProcesses.size(); @@ -186,10 +186,12 @@ void ItemLibraryAssetImporter::processFinished(int exitCode, QProcess::ExitStatu auto process = qobject_cast<QProcess *>(sender()); if (process) { - m_qmlPuppetProcesses.remove(process); - process->deleteLater(); + m_qmlPuppetProcesses.erase( + std::remove_if(m_qmlPuppetProcesses.begin(), + m_qmlPuppetProcesses.end(), + [&](const auto &entry) { return entry.get() == process; })); const QString progressTitle = tr("Generating icons."); - if (m_qmlPuppetProcesses.isEmpty()) { + if (m_qmlPuppetProcesses.empty()) { notifyProgress(100, progressTitle); finalizeQuick3DImport(); } else { @@ -215,7 +217,6 @@ void ItemLibraryAssetImporter::reset() m_tempDir = new QTemporaryDir; m_importFiles.clear(); m_overwrittenImports.clear(); - qDeleteAll(m_qmlPuppetProcesses); m_qmlPuppetProcesses.clear(); m_qmlPuppetCount = 0; #endif @@ -498,16 +499,21 @@ bool ItemLibraryAssetImporter::generateComponentIcon(int size, const QString &ic puppetCreator.createQml2PuppetExecutableIfMissing(); QStringList puppetArgs; puppetArgs << "--rendericon" << QString::number(size) << iconFile << iconSource; - QProcess *process = puppetCreator.createPuppetProcess( - "custom", {}, this, "", SLOT(processFinished(int, QProcess::ExitStatus)), puppetArgs); + QProcessUniquePointer process = puppetCreator.createPuppetProcess( + "custom", + {}, + this, + std::function<void()>(), + [&](int exitCode, QProcess::ExitStatus exitStatus) { + processFinished(exitCode, exitStatus); + }, + puppetArgs); if (process->waitForStarted(5000)) { - connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), - process, &QProcess::deleteLater); - m_qmlPuppetProcesses << process; + m_qmlPuppetProcesses.push_back(std::move(process)); return true; } else { - delete process; + process.reset(); } } return false; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h index 4bdccad6af..5921144151 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h @@ -24,14 +24,16 @@ ****************************************************************************/ #pragma once +#include "import.h" + +#include <qprocessuniqueptr.h> + #include <QSet> -#include <QtCore/qobject.h> -#include <QtCore/qstringlist.h> #include <QtCore/qhash.h> #include <QtCore/qjsonobject.h> +#include <QtCore/qobject.h> #include <QtCore/qprocess.h> - -#include "import.h" +#include <QtCore/qstringlist.h> QT_BEGIN_NAMESPACE class QSSGAssetImportManager; @@ -99,7 +101,7 @@ private: bool m_cancelled = false; QString m_importPath; QTemporaryDir *m_tempDir = nullptr; - QSet<QProcess *> m_qmlPuppetProcesses; + std::vector<QProcessUniquePointer> m_qmlPuppetProcesses; int m_qmlPuppetCount = 0; }; } // QmlDesigner diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp index 283acab78b..0cf73976a8 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.cpp @@ -27,6 +27,7 @@ #include "listmodeleditormodel.h" #include <theme.h> +#include <qmldesignericons.h> #include <coreplugin/icore.h> #include <utils/algorithm.h> @@ -71,6 +72,10 @@ ListModelEditorDialog::ListModelEditorDialog(QWidget *parent) m_addColumnAction = toolBar->addAction(getIcon(Theme::Icon::addColumnAfter), tr("Add Column")); m_removeColumnsAction = toolBar->addAction(getIcon(Theme::Icon::deleteColumn), tr("Remove Columns")); + m_moveDownAction = toolBar->addAction(Icons::ARROW_DOWN.icon(), tr("Move down (CTRL + Down).")); + m_moveDownAction->setShortcut(QKeySequence(Qt::Key_Down | Qt::CTRL)); + m_moveUpAction = toolBar->addAction(Icons::ARROW_UP.icon(), tr("Move up (CTRL + Up).")); + m_moveDownAction->setShortcut(QKeySequence(Qt::Key_Up | Qt::CTRL)); } ListModelEditorDialog::~ListModelEditorDialog() = default; @@ -83,6 +88,8 @@ void ListModelEditorDialog::setModel(ListModelEditorModel *model) connect(m_addColumnAction, &QAction::triggered, this, &ListModelEditorDialog::openColumnDialog); connect(m_removeRowsAction, &QAction::triggered, this, &ListModelEditorDialog::removeRows); connect(m_removeColumnsAction, &QAction::triggered, this, &ListModelEditorDialog::removeColumns); + connect(m_moveDownAction, &QAction::triggered, this, &ListModelEditorDialog::moveRowsDown); + connect(m_moveUpAction, &QAction::triggered, this, &ListModelEditorDialog::moveRowsUp); connect(m_tableView->horizontalHeader(), &QHeaderView::sectionDoubleClicked, this, @@ -114,40 +121,12 @@ void ListModelEditorDialog::openColumnDialog() void ListModelEditorDialog::removeRows() { - const QList<QModelIndex> indices = m_tableView->selectionModel()->selectedRows(); - std::vector<int> rows; - rows.reserve(indices.size()); - - for (QModelIndex index : indices) - rows.push_back(index.row()); - - std::sort(rows.begin(), rows.end()); - - rows.erase(std::unique(rows.begin(), rows.end()), rows.end()); - - std::reverse(rows.begin(), rows.end()); - - for (int row : rows) - m_model->removeRow(row); + m_model->removeRows(m_tableView->selectionModel()->selectedRows()); } void ListModelEditorDialog::removeColumns() { - const QList<QModelIndex> indices = m_tableView->selectionModel()->selectedColumns(); - std::vector<int> columns; - columns.reserve(indices.size()); - - for (QModelIndex index : indices) - columns.push_back(index.column()); - - std::sort(columns.begin(), columns.end()); - - columns.erase(std::unique(columns.begin(), columns.end()), columns.end()); - - std::reverse(columns.begin(), columns.end()); - - for (int row : columns) - m_model->removeColumn(row); + m_model->removeColumns(m_tableView->selectionModel()->selectedColumns()); } void ListModelEditorDialog::changeHeader(int column) @@ -162,4 +141,16 @@ void ListModelEditorDialog::changeHeader(int column) m_model->renameColumn(column, newPropertyName); } +void ListModelEditorDialog::moveRowsDown() +{ + QItemSelection selection = m_model->moveRowsDown(m_tableView->selectionModel()->selectedRows()); + m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select); +} + +void ListModelEditorDialog::moveRowsUp() +{ + QItemSelection selection = m_model->moveRowsUp(m_tableView->selectionModel()->selectedRows()); + m_tableView->selectionModel()->select(selection, QItemSelectionModel::Select); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h index 519d0869fa..24e19c8ff9 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditordialog.h @@ -59,6 +59,8 @@ private: void removeRows(); void removeColumns(); void changeHeader(int column); + void moveRowsDown(); + void moveRowsUp(); private: ListModelEditorModel *m_model{}; @@ -66,6 +68,8 @@ private: QAction *m_removeRowsAction{}; QAction *m_addColumnAction{}; QAction *m_removeColumnsAction{}; + QAction *m_moveUpAction{}; + QAction *m_moveDownAction{}; QTableView *m_tableView{}; }; diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp index 98722c3e8f..b73a53f76c 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.cpp @@ -26,7 +26,9 @@ #include "listmodeleditormodel.h" #include <abstractview.h> +#include <bindingproperty.h> #include <nodelistproperty.h> +#include <nodeproperty.h> #include <variantproperty.h> #include <QVariant> @@ -49,6 +51,17 @@ public: QVariant maybeConvertToNumber(const QVariant &value) { + if (value.type() == QVariant::Bool) + return value; + + if (value.type() == QVariant::String) { + const QString text = value.toString(); + if (text == "true") + return QVariant(true); + if (text == "false") + return QVariant(false); + } + bool canConvert = false; double convertedValue = value.toDouble(&canConvert); if (canConvert) { @@ -185,6 +198,22 @@ void renameProperties(const QStandardItemModel *model, static_cast<ListModelItem *>(model->item(rowIndex, columnIndex))->renameProperty(newPropertyName); } +ModelNode listModelNode(const ModelNode &listViewNode, + const std::function<ModelNode()> &createModelCallback) +{ + if (listViewNode.hasProperty("model")) { + if (listViewNode.hasBindingProperty("model")) + return listViewNode.bindingProperty("model").resolveToModelNode(); + else if (listViewNode.hasNodeProperty("model")) + return listViewNode.nodeProperty("model").modelNode(); + } + + ModelNode newModel = createModelCallback(); + listViewNode.nodeProperty("model").reparentHere(newModel); + + return newModel; +} + } // namespace void ListModelEditorModel::populateModel() @@ -214,9 +243,20 @@ void ListModelEditorModel::appendItems(const ModelNode &listElementNode) appendRow(row); } +void ListModelEditorModel::setListModel(ModelNode node) +{ + m_listModelNode = node; + populateModel(); +} + +void ListModelEditorModel::setListView(ModelNode listView) +{ + setListModel(listModelNode(listView, m_createModelCallback)); +} + void ListModelEditorModel::addRow() { - auto newElement = m_listModelNode.view()->createModelNode("QtQml.Models.ListElement", 2, 15); + auto newElement = m_createElementCallback(); m_listModelNode.defaultNodeListProperty().reparentHere(newElement); appendItems(newElement); @@ -260,6 +300,26 @@ void ListModelEditorModel::removeColumn(int column) } } +void ListModelEditorModel::removeColumns(const QList<QModelIndex> &indices) +{ + std::vector<int> columns = filterColumns(indices); + + std::reverse(columns.begin(), columns.end()); + + for (int column : columns) + removeColumn(column); +} + +void ListModelEditorModel::removeRows(const QList<QModelIndex> &indices) +{ + std::vector<int> rows = filterRows(indices); + + std::reverse(rows.begin(), rows.end()); + + for (int row : rows) + removeRow(row); +} + void ListModelEditorModel::removeRow(int row) { QList<QStandardItem *> rowItems = QStandardItemModel::takeRow(row); @@ -299,4 +359,74 @@ void ListModelEditorModel::renameColumn(int oldColumn, const QString &newColumnN setHorizontalHeaderLabels(convertToStringList(m_propertyNames)); } +QItemSelection ListModelEditorModel::moveRowsUp(const QList<QModelIndex> &indices) +{ + std::vector<int> rows = filterRows(indices); + + if (rows.empty() || rows.front() < 1) + return {}; + + auto nodeListProperty = m_listModelNode.defaultNodeListProperty(); + + for (int row : rows) { + insertRow(row - 1, takeRow(row)); + nodeListProperty.slide(row, row - 1); + } + + return {index(rows.front() - 1, 0), index(rows.back() - 1, columnCount() - 1)}; +} + +QItemSelection ListModelEditorModel::moveRowsDown(const QList<QModelIndex> &indices) +{ + std::vector<int> rows = filterRows(indices); + + if (rows.empty() || rows.back() >= (rowCount() - 1)) + return {}; + + auto nodeListProperty = m_listModelNode.defaultNodeListProperty(); + + std::reverse(rows.begin(), rows.end()); + + for (int row : rows) { + insertRow(row + 1, takeRow(row)); + nodeListProperty.slide(row, row + 1); + } + + return {index(rows.front() + 1, 0), index(rows.back() + 1, columnCount() - 1)}; +} + +std::vector<int> ListModelEditorModel::filterColumns(const QList<QModelIndex> &indices) +{ + std::vector<int> columns; + columns.reserve(indices.size()); + + for (QModelIndex index : indices) { + if (index.column() >= 0) + columns.push_back(index.column()); + } + + std::sort(columns.begin(), columns.end()); + + columns.erase(std::unique(columns.begin(), columns.end()), columns.end()); + + return columns; +} + +std::vector<int> ListModelEditorModel::filterRows(const QList<QModelIndex> &indices) +{ + std::vector<int> rows; + rows.reserve(indices.size()); + + for (QModelIndex index : indices) { + if (index.row() >= 0) + rows.push_back(index.row()); + } + + std::sort(rows.begin(), rows.end()); + + rows.erase(std::unique(rows.begin(), rows.end()), rows.end()); + + return rows; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h index 35d41bee68..97bd9c18d8 100644 --- a/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h +++ b/src/plugins/qmldesigner/components/listmodeleditor/listmodeleditormodel.h @@ -27,19 +27,26 @@ #include <modelnode.h> +#include <QItemSelection> #include <QStandardItemModel> namespace QmlDesigner { class ListModelEditorModel : public QStandardItemModel { + using QStandardItemModel::removeColumns; + using QStandardItemModel::removeRows; public: - void setListModel(ModelNode node) - { - m_listModelNode = node; - populateModel(); - } + ListModelEditorModel(std::function<ModelNode()> createModelCallback, + std::function<ModelNode()> createElementCallback) + : m_createModelCallback(std::move(createModelCallback)) + , m_createElementCallback(std::move(createElementCallback)) + {} + + void setListModel(ModelNode node); + + void setListView(ModelNode listView); void addRow(); void addColumn(const QString &columnName); @@ -48,11 +55,18 @@ public: bool setValue(int row, int column, QVariant value, Qt::ItemDataRole role = Qt::EditRole); - void removeColumn(int column); - void removeRow(int row); + void removeColumns(const QList<QModelIndex> &indices); + void removeRows(const QList<QModelIndex> &indices); void renameColumn(int column, const QString &newColumnName); + QItemSelection moveRowsUp(const QList<QModelIndex> &indices); + QItemSelection moveRowsDown(const QList<QModelIndex> &indices); + + static std::vector<int> filterColumns(const QList<QModelIndex> &indices); + static std::vector<int> filterRows(const QList<QModelIndex> &indices); private: + void removeRow(int row); + void removeColumn(int column); void populateModel(); void createItems(const QList<ModelNode> &listElementNodes); void appendItems(const ModelNode &listElementNode); @@ -60,6 +74,8 @@ private: private: ModelNode m_listModelNode; QList<QmlDesigner::PropertyName> m_propertyNames; + std::function<ModelNode()> m_createModelCallback; + std::function<ModelNode()> m_createElementCallback; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index e8c85bc1b6..7032a26ef7 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -40,6 +40,7 @@ #include "aligndistribute.h" #include "propertyeditorcontextobject.h" #include "tooltip.h" +#include "richtexteditor/richtexteditorproxy.h" namespace QmlDesigner { @@ -69,6 +70,7 @@ void Quick2PropertyEditorView::registerQmlTypes() AlignDistribute::registerDeclarativeType(); Tooltip::registerDeclarativeType(); EasingCurveEditor::registerDeclarativeType(); + RichTextEditorProxy::registerDeclarativeType(); } } diff --git a/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.pri b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.pri index 68b6dbe026..b71be2268d 100644 --- a/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.pri +++ b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.pri @@ -1,7 +1,9 @@ -HEADERS += $$PWD/richtexteditor.h +HEADERS += $$PWD/richtexteditor.h \ + $$PWD/richtexteditorproxy.h HEADERS += $$PWD/hyperlinkdialog.h -SOURCES += $$PWD/richtexteditor.cpp +SOURCES += $$PWD/richtexteditor.cpp \ + $$PWD/richtexteditorproxy.cpp SOURCES += $$PWD/hyperlinkdialog.cpp FORMS += $$PWD/richtexteditor.ui diff --git a/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.cpp b/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.cpp new file mode 100644 index 0000000000..ba914724db --- /dev/null +++ b/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#include "richtexteditorproxy.h" + +#include <QDialog> +#include <QDialogButtonBox> +#include <QGridLayout> + +#include "richtexteditor.h" + +namespace QmlDesigner { + +RichTextEditorProxy::RichTextEditorProxy(QObject *parent) + : QObject(parent) + , m_dialog(new QDialog{}) + , m_widget(new RichTextEditor{}) +{ + QGridLayout *layout = new QGridLayout{}; + + layout->addWidget(m_widget); + QDialogButtonBox *standardButtons = new QDialogButtonBox{QDialogButtonBox::Ok + | QDialogButtonBox::Cancel}; + + connect(standardButtons, &QDialogButtonBox::accepted, m_dialog, &QDialog::accept); + connect(standardButtons, &QDialogButtonBox::rejected, m_dialog, &QDialog::reject); + + layout->addWidget(standardButtons); + + m_dialog->setLayout(layout); + + connect(m_dialog, &QDialog::accepted, [this]() { emit accepted(); }); + connect(m_dialog, &QDialog::rejected, [this]() { emit rejected(); }); +} + +RichTextEditorProxy::~RichTextEditorProxy() +{ + delete m_dialog; +} + +void RichTextEditorProxy::registerDeclarativeType() +{ + qmlRegisterType<RichTextEditorProxy>("HelperWidgets", 2, 0, "RichTextEditor"); +} + +void RichTextEditorProxy::showWidget() +{ + m_dialog->show(); +} + +void RichTextEditorProxy::hideWidget() +{ + m_dialog->hide(); +} + +QString RichTextEditorProxy::richText() const +{ + return m_widget->richText(); +} + +void RichTextEditorProxy::setRichText(const QString &text) +{ + m_widget->setRichText(text); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.h b/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.h new file mode 100644 index 0000000000..7e62a0e388 --- /dev/null +++ b/src/plugins/qmldesigner/components/richtexteditor/richtexteditorproxy.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#pragma once + +#include <QObject> +#include <QQuickItem> + +QT_BEGIN_NAMESPACE +class QDialog; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class RichTextEditor; + +class RichTextEditorProxy : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString richText READ richText WRITE setRichText) +public: + explicit RichTextEditorProxy(QObject *parent = nullptr); + ~RichTextEditorProxy(); + + static void registerDeclarativeType(); + + Q_INVOKABLE void showWidget(); + Q_INVOKABLE void hideWidget(); + + QString richText() const; + void setRichText(const QString &text); + +signals: + void accepted(); + void rejected(); + +private: + QDialog *m_dialog; + RichTextEditor *m_widget; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp index 39874f51b0..7d11f57cb3 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp @@ -103,7 +103,7 @@ static QAction *createAction(const Core::Id &id, TimelineToolBar::TimelineToolBar(QWidget *parent) : QToolBar(parent) , m_grp() - , m_dialog(new AnimationCurveDialog(Core::ICore::dialogParent())) + , m_dialog(new AnimationCurveDialog(this)) , m_curveModel(new AnimationCurveEditorModel(0., 500.)) { m_dialog->setModel(m_curveModel); diff --git a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h index 63b73cbdff..78e56d46cd 100644 --- a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h +++ b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h @@ -62,6 +62,7 @@ class RemovePropertiesCommand; class CompleteComponentCommand; class InformationContainer; class TokenCommand; +class ConnectionManagerInterface; class QMLDESIGNERCORE_EXPORT NodeInstanceView : public AbstractView, public NodeInstanceClientInterface { @@ -72,7 +73,7 @@ class QMLDESIGNERCORE_EXPORT NodeInstanceView : public AbstractView, public Node public: using Pointer = QWeakPointer<NodeInstanceView>; - explicit NodeInstanceView(QObject *parent = nullptr, NodeInstanceServerInterface::RunModus runModus = NodeInstanceServerInterface::NormalModus); + explicit NodeInstanceView(ConnectionManagerInterface &connectionManager); ~NodeInstanceView() override; void modelAttached(Model *model) override; @@ -94,7 +95,7 @@ public: void auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) override; void customNotification(const AbstractView *view, const QString &identifier, const QList<ModelNode> &nodeList, const QList<QVariant> &data) override; void nodeSourceChanged(const ModelNode &modelNode, const QString &newNodeSource) override; - + void capturedData(const CapturedDataCommand &capturedData) override; void currentStateChanged(const ModelNode &node) override; QList<NodeInstance> instances() const; @@ -142,6 +143,7 @@ protected: void timerEvent(QTimerEvent *event) override; private: // functions + std::unique_ptr<NodeInstanceServerProxy> createNodeInstanceServerProxy(); void activateState(const NodeInstance &instance); void activateBaseState(); @@ -161,9 +163,8 @@ private: // functions void setStateInstance(const NodeInstance &stateInstance); void clearStateInstance(); - NodeInstanceServerInterface *nodeInstanceServer() const; - QMultiHash<ModelNode, InformationName> informationChanged(const QVector<InformationContainer> &containerVector); - + QMultiHash<ModelNode, InformationName> informationChanged( + const QVector<InformationContainer> &containerVector); CreateSceneCommand createCreateSceneCommand(); ClearSceneCommand createClearSceneCommand() const; @@ -196,16 +197,15 @@ private: // functions // puppet to creator command handlers void handlePuppetKeyPress(int key, Qt::KeyboardModifiers modifiers); +private: NodeInstance m_rootNodeInstance; NodeInstance m_activeStateInstance; - QHash<ModelNode, NodeInstance> m_nodeInstanceHash; QHash<ModelNode, QImage> m_statePreviewImage; - - QPointer<NodeInstanceServerProxy> m_nodeInstanceServer; + ConnectionManagerInterface &m_connectionManager; + std::unique_ptr<NodeInstanceServerProxy> m_nodeInstanceServer; QImage m_baseStatePreviewImage; QElapsedTimer m_lastCrashTime; - NodeInstanceServerInterface::RunModus m_runModus; ProjectExplorer::Target *m_currentTarget = nullptr; int m_restartProcessTimerId; RewriterTransaction m_puppetTransaction; diff --git a/src/plugins/qmldesigner/designercore/include/viewmanager.h b/src/plugins/qmldesigner/designercore/include/viewmanager.h index ef0f29c2c1..cad7d55221 100644 --- a/src/plugins/qmldesigner/designercore/include/viewmanager.h +++ b/src/plugins/qmldesigner/designercore/include/viewmanager.h @@ -122,7 +122,7 @@ private: // functions const QList<QPointer<AbstractView> > standardViews() const; private: // variables - ViewManagerData *d; + std::unique_ptr<ViewManagerData> d; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp new file mode 100644 index 0000000000..b7a2cc282e --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#include "baseconnectionmanager.h" +#include "endpuppetcommand.h" +#include "nodeinstanceserverproxy.h" +#include "nodeinstanceview.h" + +#include <QLocalSocket> +#include <QTimer> + +namespace QmlDesigner { + +void BaseConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &, + ProjectExplorer::Target *) +{ + m_nodeInstanceServerProxy = nodeInstanceServerProxy; + m_isActive = true; +} + +void BaseConnectionManager::shutDown() +{ + m_isActive = false; + + writeCommand(QVariant::fromValue(EndPuppetCommand())); + + m_nodeInstanceServerProxy = nullptr; +} + +bool BaseConnectionManager::isActive() const +{ + return m_isActive; +} + +void BaseConnectionManager::showCannotConnectToPuppetWarningAndSwitchToEditMode() {} + +void BaseConnectionManager::processFinished() +{ + processFinished(-1, QProcess::CrashExit); +} + +void BaseConnectionManager::writeCommandToIODevice(const QVariant &command, + QIODevice *ioDevice, + unsigned int commandCounter) +{ + if (ioDevice) { + QByteArray block; + QDataStream out(&block, QIODevice::WriteOnly); + out.setVersion(QDataStream::Qt_4_8); + out << quint32(0); + out << quint32(commandCounter); + out << command; + out.device()->seek(0); + out << quint32(static_cast<unsigned long long>(block.size()) - sizeof(quint32)); + + ioDevice->write(block); + } +} + +void BaseConnectionManager::dispatchCommand(const QVariant &command, Connection &) +{ + if (!isActive()) + return; + + m_nodeInstanceServerProxy->dispatchCommand(command); +} + +void BaseConnectionManager::readDataStream(Connection &connection) +{ + QList<QVariant> commandList; + + while (!connection.socket->atEnd()) { + if (connection.socket->bytesAvailable() < int(sizeof(quint32))) + break; + + QDataStream in(connection.socket.get()); + in.setVersion(QDataStream::Qt_4_8); + + if (connection.blockSize == 0) + in >> connection.blockSize; + + if (connection.socket->bytesAvailable() < connection.blockSize) + break; + + quint32 commandCounter = 0; + in >> commandCounter; + bool commandLost = !((connection.lastReadCommandCounter == 0 && commandCounter == 0) + || (connection.lastReadCommandCounter + 1 == commandCounter)); + if (commandLost) + qDebug() << "server command lost: " << connection.lastReadCommandCounter << commandCounter; + connection.lastReadCommandCounter = commandCounter; + + QVariant command; + in >> command; + connection.blockSize = 0; + + commandList.append(command); + } + + for (const QVariant &command : commandList) + dispatchCommand(command, connection); +} +} // namespace QmlDesigner + diff --git a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h new file mode 100644 index 0000000000..83a41a2bd8 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#pragma once + +#include "connectionmanagerinterface.h" + +#include <QProcess> + +QT_BEGIN_NAMESPACE +class QLocalSocket; +QT_END_NAMESPACE + +namespace ProjectExplorer { +class Target; +} + +namespace QmlDesigner { + +class AbstractView; +class NodeInstanceServerProxy; + +class QMLDESIGNERCORE_EXPORT BaseConnectionManager : public QObject, public ConnectionManagerInterface +{ + Q_OBJECT + +public: + BaseConnectionManager() = default; + + void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) override; + void shutDown() override; + + bool isActive() const; + +protected: + void dispatchCommand(const QVariant &command, Connection &connection) override; + virtual void showCannotConnectToPuppetWarningAndSwitchToEditMode(); + using ConnectionManagerInterface::processFinished; + void processFinished(); + void writeCommandToIODevice(const QVariant &command, + QIODevice *ioDevice, + unsigned int commandCounter); + void readDataStream(Connection &connection); + + NodeInstanceServerProxy *nodeInstanceServerProxy() const { return m_nodeInstanceServerProxy; } + +private: + NodeInstanceServerProxy *m_nodeInstanceServerProxy{}; + bool m_isActive = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp new file mode 100644 index 0000000000..4be52a327c --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#include "capturingconnectionmanager.h" + +#include <coreplugin/messagebox.h> + +#include <QCoreApplication> +#include <QDebug> +#include <QTimer> + +namespace QmlDesigner { + +void CapturingConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) +{ + InteractiveConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + + int indexOfCapturePuppetStream = QCoreApplication::arguments().indexOf( + "-capture-puppet-stream"); + if (indexOfCapturePuppetStream > 0) { + m_captureFileForTest.setFileName( + QCoreApplication::arguments().at(indexOfCapturePuppetStream + 1)); + bool isOpen = m_captureFileForTest.open(QIODevice::WriteOnly); + qDebug() << "file is open: " << isOpen; + } +} + +void CapturingConnectionManager::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (m_captureFileForTest.isOpen()) { + m_captureFileForTest.close(); + Core::AsynchronousMessageBox::warning( + tr("QML Emulation Layer (QML Puppet) Crashed"), + tr("You are recording a puppet stream and the emulations layer crashed. " + "It is recommended to reopen the Qt Quick Designer and start again.")); + } + + InteractiveConnectionManager::processFinished(exitCode, exitStatus); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h new file mode 100644 index 0000000000..de63da87fc --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#pragma once + +#include <interactiveconnectionmanager.h> + +namespace QmlDesigner { + +class CapturingConnectionManager : public InteractiveConnectionManager +{ +public: + void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) override; + + void processFinished(int exitCode, QProcess::ExitStatus exitStatus) override; + +private: + QFile m_captureFileForTest; +}; + +} // namespace QmlDesigner + diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp new file mode 100644 index 0000000000..fa8528579d --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#include "connectionmanager.h" +#include "endpuppetcommand.h" +#include "nodeinstanceserverproxy.h" +#include "nodeinstanceview.h" +#include "puppetcreator.h" + +#ifndef QMLDESIGNER_TEST +#include <qmldesignerplugin.h> +#endif + +#include <projectexplorer/target.h> + +#include <QLocalServer> +#include <QLocalSocket> +#include <QTimer> +#include <QUuid> + +namespace QmlDesigner { + +ConnectionManager::ConnectionManager() = default; + +ConnectionManager::~ConnectionManager() = default; + +void ConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) +{ + BaseConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + + m_localServer = std::make_unique<QLocalServer>(); + QString socketToken(QUuid::createUuid().toString()); + m_localServer->listen(socketToken); + m_localServer->setMaxPendingConnections(3); + + NodeInstanceView *nodeInstanceView = nodeInstanceServerProxy->nodeInstanceView(); + PuppetCreator puppetCreator(target, nodeInstanceView->model()); + puppetCreator.setQrcMappingString(qrcMappingString); + + puppetCreator.createQml2PuppetExecutableIfMissing(); + + for (Connection &connection : m_connections) { + connection.qmlPuppetProcess = puppetCreator.createPuppetProcess( + connection.mode, + socketToken, + nodeInstanceView, + [&] { printProcessOutput(connection.qmlPuppetProcess.get(), connection.name); }, + [&](int exitCode, QProcess::ExitStatus exitStatus) { + processFinished(exitCode, exitStatus); + }); + + const int second = 1000; + int waitConstant = 8 * second; + + if (!connection.qmlPuppetProcess->waitForStarted(waitConstant)) { + closeSocketsAndKillProcesses(); + showCannotConnectToPuppetWarningAndSwitchToEditMode(); + return; + } + + waitConstant /= 2; + + bool connectedToPuppet = true; + if (!m_localServer->hasPendingConnections()) + connectedToPuppet = m_localServer->waitForNewConnection(waitConstant); + + if (connectedToPuppet) { + connection.socket.reset(m_localServer->nextPendingConnection()); + QObject::connect(connection.socket.get(), &QIODevice::readyRead, [&] { + readDataStream(connection); + }); + } else { + closeSocketsAndKillProcesses(); + showCannotConnectToPuppetWarningAndSwitchToEditMode(); + return; + } + } + + m_localServer->close(); + + connect(this, + &ConnectionManager::processCrashed, + nodeInstanceServerProxy, + &NodeInstanceServerProxy::processCrashed); +} + +void ConnectionManager::shutDown() +{ + BaseConnectionManager::shutDown(); + + closeSocketsAndKillProcesses(); + + m_localServer.reset(); + + for (Connection &connection : m_connections) + connection.clear(); +} + +void ConnectionManager::writeCommand(const QVariant &command) +{ + for (Connection &connection : m_connections) + writeCommandToIODevice(command, connection.socket.get(), m_writeCommandCounter); + + m_writeCommandCounter++; +} + +void ConnectionManager::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + auto finishedProcess = qobject_cast<QProcess *>(sender()); + if (finishedProcess) + qWarning() << "Process" << (exitStatus == QProcess::CrashExit ? "crashed:" : "finished:") + << finishedProcess->arguments() << "exitCode:" << exitCode; + else + qWarning() << "Process" << (exitStatus == QProcess::CrashExit ? "crashed:" : "finished:") + << sender() << "exitCode:" << exitCode; + + writeCommand(QVariant::fromValue(EndPuppetCommand())); + + closeSocketsAndKillProcesses(); + + if (exitStatus == QProcess::CrashExit) + emit processCrashed(); +} + +void ConnectionManager::closeSocketsAndKillProcesses() +{ + for (Connection &connection : m_connections) { + if (connection.socket) { + disconnect(connection.socket.get()); + disconnect(connection.qmlPuppetProcess.get()); + connection.socket->waitForBytesWritten(1000); + connection.socket->abort(); + } + + if (connection.qmlPuppetProcess) { + QTimer::singleShot(3000, connection.qmlPuppetProcess.get(), &QProcess::terminate); + QTimer::singleShot(6000, connection.qmlPuppetProcess.get(), &QProcess::kill); + } + + connection.clear(); + } +} + +void ConnectionManager::printProcessOutput(QProcess *process, const QString &connectionName) +{ + while (process && process->canReadLine()) { + QByteArray line = process->readLine(); + line.chop(1); + qDebug().nospace() << connectionName << " Puppet: " << line; + } + qDebug() << "\n"; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanager.h b/src/plugins/qmldesigner/designercore/instances/connectionmanager.h new file mode 100644 index 0000000000..4bb85b5ff2 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanager.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#pragma once + +#include "baseconnectionmanager.h" +#include "nodeinstanceserverinterface.h" + +#include <QElapsedTimer> +#include <QFile> +#include <QProcess> + +QT_BEGIN_NAMESPACE +class QLocalServer; +class QLocalSocket; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class QMLDESIGNERCORE_EXPORT ConnectionManager : public BaseConnectionManager +{ + Q_OBJECT + +public: + ConnectionManager(); + ~ConnectionManager() override; + enum PuppetStreamType { FirstPuppetStream, SecondPuppetStream, ThirdPuppetStream }; + + void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) override; + void shutDown() override; + + void writeCommand(const QVariant &command) override; + +signals: + void processCrashed(); + +protected: + using BaseConnectionManager::processFinished; + void processFinished(int exitCode, QProcess::ExitStatus exitStatus) override; + +private: + void printProcessOutput(QProcess *process, const QString &connectionName); + void closeSocketsAndKillProcesses(); + +protected: + std::vector<Connection> m_connections; + quint32 m_writeCommandCounter = 0; + +private: + std::unique_ptr<QLocalServer> m_localServer; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.cpp b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.cpp new file mode 100644 index 0000000000..b4fdb70794 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.cpp @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#include "connectionmanagerinterface.h" + +#include <QLocalSocket> +#include <QProcess> +#include <QTimer> + +namespace QmlDesigner { + +ConnectionManagerInterface::~ConnectionManagerInterface() = default; + +ConnectionManagerInterface::Connection::~Connection() = default; + +ConnectionManagerInterface::Connection::Connection(const QString &name, const QString &mode) + : name{name} + , mode{mode} + , timer{std::make_unique<QTimer>()} +{} + +ConnectionManagerInterface::Connection::Connection(Connection &&connection) = default; + +void ConnectionManagerInterface::Connection::clear() +{ + qmlPuppetProcess.reset(); + socket.reset(); + blockSize = 0; + lastReadCommandCounter = 0; + timer->stop(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h new file mode 100644 index 0000000000..92d7449bc0 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#include "qprocessuniqueptr.h" +#include <qmldesignercorelib_global.h> + +#include <QTimer> + +QT_BEGIN_NAMESPACE +class QLocalSocket; +QT_END_NAMESPACE + +namespace ProjectExplorer { +class Target; +} + +namespace QmlDesigner { + +class NodeInstanceServerProxy; + +class QMLDESIGNERCORE_EXPORT ConnectionManagerInterface +{ +public: + class QMLDESIGNERCORE_EXPORT Connection final + { + public: + Connection(const QString &name, const QString &mode); + Connection(Connection &&connection); + + ~Connection(); + + void clear(); + + public: + QString name; + QString mode; + QProcessUniquePointer qmlPuppetProcess; + std::unique_ptr<QLocalSocket> socket; + quint32 blockSize = 0; + quint32 lastReadCommandCounter = 0; + std::unique_ptr<QTimer> timer; + }; + + virtual ~ConnectionManagerInterface(); + + virtual void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) + = 0; + virtual void shutDown() = 0; + + virtual void writeCommand(const QVariant &command) = 0; + +protected: + virtual void dispatchCommand(const QVariant &command, Connection &connection) = 0; + virtual void processFinished(int exitCode, QProcess::ExitStatus exitStatus) = 0; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/instances.pri b/src/plugins/qmldesigner/designercore/instances/instances.pri index 9856e1243f..7d6b71a080 100644 --- a/src/plugins/qmldesigner/designercore/instances/instances.pri +++ b/src/plugins/qmldesigner/designercore/instances/instances.pri @@ -1,14 +1,25 @@ INCLUDEPATH += $$PWD/ -HEADERS += $$PWD/../include/nodeinstance.h -HEADERS += $$PWD/nodeinstanceserverproxy.h -HEADERS += $$PWD/puppetcreator.h -HEADERS += $$PWD/puppetbuildprogressdialog.h +HEADERS += $$PWD/../include/nodeinstance.h \ + $$PWD/baseconnectionmanager.h \ + $$PWD/capturingconnectionmanager.h \ + $$PWD/connectionmanager.h \ + $$PWD/connectionmanagerinterface.h \ + $$PWD/interactiveconnectionmanager.h \ + $$PWD/nodeinstanceserverproxy.h \ + $$PWD/puppetcreator.h \ + $$PWD/puppetbuildprogressdialog.h \ + $$PWD/qprocessuniqueptr.h -SOURCES += $$PWD/nodeinstanceserverproxy.cpp -SOURCES += $$PWD/nodeinstance.cpp -SOURCES += $$PWD/nodeinstanceview.cpp -SOURCES += $$PWD/puppetcreator.cpp -SOURCES += $$PWD/puppetbuildprogressdialog.cpp +SOURCES += $$PWD/nodeinstanceserverproxy.cpp \ + $$PWD/baseconnectionmanager.cpp \ + $$PWD/capturingconnectionmanager.cpp \ + $$PWD/connectionmanager.cpp \ + $$PWD/connectionmanagerinterface.cpp \ + $$PWD/interactiveconnectionmanager.cpp \ + $$PWD/nodeinstance.cpp \ + $$PWD/nodeinstanceview.cpp \ + $$PWD/puppetcreator.cpp \ + $$PWD/puppetbuildprogressdialog.cpp FORMS += $$PWD/puppetbuildprogressdialog.ui diff --git a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp new file mode 100644 index 0000000000..6da44603df --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#include "interactiveconnectionmanager.h" +#include "nodeinstanceserverproxy.h" +#include "nodeinstanceview.h" + +#include <qmldesignerplugin.h> + +#include <coreplugin/messagebox.h> + +#include <QLocalSocket> +#include <QTimer> + +namespace QmlDesigner { + +InteractiveConnectionManager::InteractiveConnectionManager() +{ + m_connections.emplace_back("Editor", "editormode"); + m_connections.emplace_back("Render", "rendermode"); + m_connections.emplace_back("Preview", "previewmode"); +} + +void InteractiveConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) +{ + ConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + + DesignerSettings settings = QmlDesignerPlugin::instance()->settings(); + int timeOutTime = settings.value(DesignerSettingsKey::PUPPET_KILL_TIMEOUT).toInt(); + for (Connection &connection : m_connections) + connection.timer->setInterval(timeOutTime); + + if (QmlDesignerPlugin::instance() + ->settings() + .value(DesignerSettingsKey::DEBUG_PUPPET) + .toString() + .isEmpty()) { + for (Connection &connection : m_connections) { + QObject::connect(connection.timer.get(), &QTimer::timeout, [&]() { + puppetTimeout(connection); + }); + } + } +} + +void InteractiveConnectionManager::showCannotConnectToPuppetWarningAndSwitchToEditMode() +{ + Core::AsynchronousMessageBox::warning( + tr("Cannot Connect to QML Emulation Layer (QML Puppet)"), + tr("The executable of the QML emulation layer (QML Puppet) may not be responding. " + "Switching to another kit might help.")); + + QmlDesignerPlugin::instance()->switchToTextModeDeferred(); + nodeInstanceServerProxy()->nodeInstanceView()->emitDocumentMessage( + tr("Cannot Connect to QML Emulation Layer (QML Puppet)")); +} + +void InteractiveConnectionManager::dispatchCommand(const QVariant &command, Connection &connection) +{ + static const int puppetAliveCommandType = QMetaType::type("PuppetAliveCommand"); + + if (command.userType() == puppetAliveCommandType) { + puppetAlive(connection); + } else { + BaseConnectionManager::dispatchCommand(command, connection); + } +} + +void InteractiveConnectionManager::puppetTimeout(Connection &connection) +{ + if (connection.socket && connection.socket->waitForReadyRead(10)) { + connection.timer->stop(); + connection.timer->start(); + return; + } + + processFinished(); +} + +void InteractiveConnectionManager::puppetAlive(Connection &connection) +{ + connection.timer->stop(); + connection.timer->start(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h new file mode 100644 index 0000000000..1946620a43 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#pragma once + +#include "connectionmanager.h" + +namespace QmlDesigner { + +class InteractiveConnectionManager : public ConnectionManager +{ +public: + InteractiveConnectionManager(); + + void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + const QString &qrcMappingString, + ProjectExplorer::Target *target) override; + + void showCannotConnectToPuppetWarningAndSwitchToEditMode() override; + +protected: + void dispatchCommand(const QVariant &command, Connection &connection) override; + +private: + void puppetTimeout(Connection &connection); + void puppetAlive(Connection &connection); +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp index 8db9aa3a98..026d587233 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp @@ -25,6 +25,7 @@ #include "nodeinstanceserverproxy.h" +#include "connectionmanagerinterface.h" #include "puppetcreator.h" #include <changeauxiliarycommand.h> @@ -60,218 +61,55 @@ #include <valueschangedcommand.h> #include <view3dactioncommand.h> -#include <nodeinstanceview.h> #include <import.h> +#include <nodeinstanceview.h> #include <rewriterview.h> -#ifndef QMLDESIGNER_TEST -#include <qmldesignerplugin.h> -#endif - -#include <coreplugin/icore.h> -#include <utils/hostosinfo.h> -#include <coreplugin/messagebox.h> #include <coreplugin/editormanager/editormanager.h> +#include <coreplugin/icore.h> #include <projectexplorer/kit.h> -#include <qtsupport/qtkitinformation.h> +#include <utils/hostosinfo.h> #include <qtsupport/baseqtversion.h> +#include <qtsupport/qtkitinformation.h> #include <qtsupport/qtsupportconstants.h> +#include <QCoreApplication> +#include <QDir> +#include <QFileInfo> #include <QLocalServer> #include <QLocalSocket> #include <QLoggingCategory> +#include <QMessageBox> #include <QProcess> -#include <QCoreApplication> -#include <QUuid> -#include <QFileInfo> -#include <QDir> -#include <QTimer> #include <QTextStream> -#include <QMessageBox> +#include <QTimer> +#include <QUuid> namespace QmlDesigner { -static Q_LOGGING_CATEGORY(instanceViewBenchmark, "qtc.nodeinstances.init", QtWarningMsg) - -void NodeInstanceServerProxy::showCannotConnectToPuppetWarningAndSwitchToEditMode() -{ -#ifndef QMLDESIGNER_TEST - Core::AsynchronousMessageBox::warning(tr("Cannot Connect to QML Emulation Layer (QML Puppet)"), - tr("The executable of the QML emulation layer (QML Puppet) may not be responding. " - "Switching to another kit might help.")); - - QmlDesignerPlugin::instance()->switchToTextModeDeferred(); - m_nodeInstanceView->emitDocumentMessage(tr("Cannot Connect to QML Emulation Layer (QML Puppet)")); -#endif - -} +static Q_LOGGING_CATEGORY(instanceViewBenchmark, "qtc.nodeinstances.init", QtWarningMsg); NodeInstanceServerProxy::NodeInstanceServerProxy(NodeInstanceView *nodeInstanceView, - RunModus runModus, - ProjectExplorer::Target *target) - : NodeInstanceServerInterface(nodeInstanceView), - m_localServer(new QLocalServer(this)), - m_nodeInstanceView(nodeInstanceView), - m_runModus(runModus) + ProjectExplorer::Target *target, + ConnectionManagerInterface &connectionManager) + : m_nodeInstanceView(nodeInstanceView) + , m_connectionManager{connectionManager} + { if (instanceViewBenchmark().isInfoEnabled()) m_benchmarkTimer.start(); - QString socketToken(QUuid::createUuid().toString()); - m_localServer->listen(socketToken); - m_localServer->setMaxPendingConnections(3); - - PuppetCreator puppetCreator(target, nodeInstanceView->model()); - puppetCreator.setQrcMappingString(qrcMappingString()); - - puppetCreator.createQml2PuppetExecutableIfMissing(); - - m_qmlPuppetEditorProcess = puppetCreator.createPuppetProcess("editormode", - socketToken, - this, - SLOT(printEditorProcessOutput()), - SLOT(processFinished(int,QProcess::ExitStatus))); - - if (runModus == NormalModus) { - m_qmlPuppetRenderProcess = puppetCreator.createPuppetProcess("rendermode", - socketToken, - this, - SLOT(printRenderProcessOutput()), - SLOT(processFinished(int,QProcess::ExitStatus))); - m_qmlPuppetPreviewProcess = puppetCreator.createPuppetProcess("previewmode", - socketToken, - this, - SLOT(printPreviewProcessOutput()), - SLOT(processFinished(int,QProcess::ExitStatus))); - } - - const int second = 1000; - const int waitConstant = 8 * second; - if (m_qmlPuppetEditorProcess->waitForStarted(waitConstant)) { - connect(m_qmlPuppetEditorProcess.data(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), - m_qmlPuppetEditorProcess.data(), &QProcess::deleteLater); - qCInfo(instanceViewBenchmark) << "puppets started:" << m_benchmarkTimer.elapsed(); - - if (runModus == NormalModus) { - m_qmlPuppetPreviewProcess->waitForStarted(waitConstant / 2); - connect(m_qmlPuppetPreviewProcess.data(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), - m_qmlPuppetPreviewProcess.data(), &QProcess::deleteLater); - - m_qmlPuppetRenderProcess->waitForStarted(waitConstant / 2); - connect(m_qmlPuppetRenderProcess.data(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), - m_qmlPuppetRenderProcess.data(), &QProcess::deleteLater); - } - - bool connectedToPuppet = true; - - if (!m_localServer->hasPendingConnections()) - connectedToPuppet = m_localServer->waitForNewConnection(waitConstant / 4); - - if (connectedToPuppet) { - m_firstSocket = m_localServer->nextPendingConnection(); - connect(m_firstSocket.data(), &QIODevice::readyRead, this, - &NodeInstanceServerProxy::readFirstDataStream); - - if (runModus == NormalModus) { - if (!m_localServer->hasPendingConnections()) - connectedToPuppet = m_localServer->waitForNewConnection(waitConstant / 4); - - if (connectedToPuppet) { - m_secondSocket = m_localServer->nextPendingConnection(); - connect(m_secondSocket.data(), &QIODevice::readyRead, this, &NodeInstanceServerProxy::readSecondDataStream); - - if (!m_localServer->hasPendingConnections()) - connectedToPuppet = m_localServer->waitForNewConnection(waitConstant / 4); - - qCInfo(instanceViewBenchmark) << "puppets connected:" << m_benchmarkTimer.elapsed(); - if (connectedToPuppet) { - m_thirdSocket = m_localServer->nextPendingConnection(); - connect(m_thirdSocket.data(), &QIODevice::readyRead, this, &NodeInstanceServerProxy::readThirdDataStream); - } else { - showCannotConnectToPuppetWarningAndSwitchToEditMode(); - } - } else { - showCannotConnectToPuppetWarningAndSwitchToEditMode(); - } - } - } else { - showCannotConnectToPuppetWarningAndSwitchToEditMode(); - } - - } else { - showCannotConnectToPuppetWarningAndSwitchToEditMode(); - } - - m_localServer->close(); - - - int indexOfCapturePuppetStream = QCoreApplication::arguments().indexOf("-capture-puppet-stream"); - if (indexOfCapturePuppetStream > 0) { - m_captureFileForTest.setFileName(QCoreApplication::arguments().at(indexOfCapturePuppetStream + 1)); - bool isOpen = m_captureFileForTest.open(QIODevice::WriteOnly); - qDebug() << "file is open: " << isOpen; - } - -#ifndef QMLDESIGNER_TEST - DesignerSettings settings = QmlDesignerPlugin::instance()->settings(); - int timeOutTime = settings.value(DesignerSettingsKey::PUPPET_KILL_TIMEOUT).toInt(); - m_firstTimer.setInterval(timeOutTime); - m_secondTimer.setInterval(timeOutTime); - m_thirdTimer.setInterval(timeOutTime); - - if (QmlDesignerPlugin::instance()->settings().value(DesignerSettingsKey:: - DEBUG_PUPPET).toString().isEmpty()) { - - connect(&m_firstTimer, &QTimer::timeout, this, - [this](){ NodeInstanceServerProxy::puppetTimeout(FirstPuppetStream); }); - connect(&m_secondTimer, &QTimer::timeout, this, - [this](){ NodeInstanceServerProxy::puppetTimeout(SecondPuppetStream); }); - connect(&m_thirdTimer, &QTimer::timeout, this, - [this](){ NodeInstanceServerProxy::puppetTimeout(ThirdPuppetStream); }); - } -#endif + m_connectionManager.setUp(this, qrcMappingString(), target); + + qCInfo(instanceViewBenchmark) << "puppets setup:" << m_benchmarkTimer.elapsed(); } NodeInstanceServerProxy::~NodeInstanceServerProxy() { - m_destructing = true; - - disconnect(this, SLOT(processFinished(int,QProcess::ExitStatus))); - - writeCommand(QVariant::fromValue(EndPuppetCommand())); - - if (m_firstSocket) { - m_firstSocket->waitForBytesWritten(1000); - m_firstSocket->abort(); - } - - if (m_secondSocket) { - m_secondSocket->waitForBytesWritten(1000); - m_secondSocket->abort(); - } - - if (m_thirdSocket) { - m_thirdSocket->waitForBytesWritten(1000); - m_thirdSocket->abort(); - } - - if (m_qmlPuppetEditorProcess) { - QTimer::singleShot(3000, m_qmlPuppetEditorProcess.data(), &QProcess::terminate); - QTimer::singleShot(6000, m_qmlPuppetEditorProcess.data(), &QProcess::kill); - } - - if (m_qmlPuppetPreviewProcess) { - QTimer::singleShot(3000, m_qmlPuppetPreviewProcess.data(), &QProcess::terminate); - QTimer::singleShot(6000, m_qmlPuppetPreviewProcess.data(), &QProcess::kill); - } - - if (m_qmlPuppetRenderProcess) { - QTimer::singleShot(3000, m_qmlPuppetRenderProcess.data(), &QProcess::terminate); - QTimer::singleShot(6000, m_qmlPuppetRenderProcess.data(), &QProcess::kill); - } + m_connectionManager.shutDown(); } -void NodeInstanceServerProxy::dispatchCommand(const QVariant &command, PuppetStreamType puppetStreamType) +void NodeInstanceServerProxy::dispatchCommand(const QVariant &command) { static const int informationChangedCommandType = QMetaType::type("InformationChangedCommand"); static const int valuesChangedCommandType = QMetaType::type("ValuesChangedCommand"); @@ -280,15 +118,10 @@ void NodeInstanceServerProxy::dispatchCommand(const QVariant &command, PuppetStr static const int childrenChangedCommandType = QMetaType::type("ChildrenChangedCommand"); static const int statePreviewImageChangedCommandType = QMetaType::type("StatePreviewImageChangedCommand"); static const int componentCompletedCommandType = QMetaType::type("ComponentCompletedCommand"); - static const int synchronizeCommandType = QMetaType::type("SynchronizeCommand"); static const int tokenCommandType = QMetaType::type("TokenCommand"); static const int debugOutputCommandType = QMetaType::type("DebugOutputCommand"); - static const int puppetAliveCommandType = QMetaType::type("PuppetAliveCommand"); static const int changeSelectionCommandType = QMetaType::type("ChangeSelectionCommand"); - static const int puppetToCreatorCommand = QMetaType::type("PuppetToCreatorCommand"); - - if (m_destructing) - return; + static const int puppetToCreatorCommandType = QMetaType::type("PuppetToCreatorCommand"); qCInfo(instanceViewBenchmark) << "dispatching command" << command.userType() << command.typeName(); if (command.userType() == informationChangedCommandType) { @@ -311,13 +144,8 @@ void NodeInstanceServerProxy::dispatchCommand(const QVariant &command, PuppetStr nodeInstanceClient()->debugOutput(command.value<DebugOutputCommand>()); } else if (command.userType() == changeSelectionCommandType) { nodeInstanceClient()->selectionChanged(command.value<ChangeSelectionCommand>()); - } else if (command.userType() == puppetToCreatorCommand) { + } else if (command.userType() == puppetToCreatorCommandType) { nodeInstanceClient()->handlePuppetToCreatorCommand(command.value<PuppetToCreatorCommand>()); - } else if (command.userType() == puppetAliveCommandType) { - puppetAlive(puppetStreamType); - } else if (command.userType() == synchronizeCommandType) { - SynchronizeCommand synchronizeCommand = command.value<SynchronizeCommand>(); - m_synchronizeId = synchronizeCommand.synchronizeId(); } else { Q_ASSERT(false); } @@ -327,33 +155,13 @@ void NodeInstanceServerProxy::dispatchCommand(const QVariant &command, PuppetStr NodeInstanceClientInterface *NodeInstanceServerProxy::nodeInstanceClient() const { - return m_nodeInstanceView.data(); -} - -void NodeInstanceServerProxy::puppetAlive(NodeInstanceServerProxy::PuppetStreamType puppetStreamType) -{ - switch (puppetStreamType) { - case FirstPuppetStream: - m_firstTimer.stop(); - m_firstTimer.start(); - break; - case SecondPuppetStream: - m_secondTimer.stop(); - m_secondTimer.start(); - break; - case ThirdPuppetStream: - m_thirdTimer.stop(); - m_thirdTimer.start(); - break; - default: - break; - } + return m_nodeInstanceView; } QString NodeInstanceServerProxy::qrcMappingString() const { - if (m_nodeInstanceView && m_nodeInstanceView.data()->model()) { - RewriterView *rewriterView = m_nodeInstanceView.data()->model()->rewriterView(); + if (m_nodeInstanceView && m_nodeInstanceView->model()) { + RewriterView *rewriterView = m_nodeInstanceView->model()->rewriterView(); if (rewriterView) { QString mappingString; @@ -374,265 +182,9 @@ QString NodeInstanceServerProxy::qrcMappingString() const return QString(); } -void NodeInstanceServerProxy::processFinished() -{ - processFinished(-1, QProcess::CrashExit); -} - -void NodeInstanceServerProxy::puppetTimeout(PuppetStreamType puppetStreamType) -{ - switch (puppetStreamType) { - case FirstPuppetStream: - if (m_firstSocket->waitForReadyRead(10)) { - m_firstTimer.stop(); - m_firstTimer.start(); - return; - } - break; - case SecondPuppetStream: - if (m_secondSocket->waitForReadyRead(10)) { - m_secondTimer.stop(); - m_secondTimer.start(); - return; - } - break; - case ThirdPuppetStream: - if (m_thirdSocket->waitForReadyRead(10)) { - m_thirdTimer.stop(); - m_thirdTimer.start(); - return; - } - break; - default: - break; - } - - processFinished(); -} - -static void writeCommandToIODecive(const QVariant &command, QIODevice *ioDevice, unsigned int commandCounter) -{ - if (ioDevice) { - QByteArray block; - QDataStream out(&block, QIODevice::WriteOnly); - out.setVersion(QDataStream::Qt_4_8); - out << quint32(0); - out << quint32(commandCounter); - out << command; - out.device()->seek(0); - out << quint32(block.size() - sizeof(quint32)); - - ioDevice->write(block); - } -} - void NodeInstanceServerProxy::writeCommand(const QVariant &command) { - writeCommandToIODecive(command, m_firstSocket.data(), m_writeCommandCounter); - writeCommandToIODecive(command, m_secondSocket.data(), m_writeCommandCounter); - writeCommandToIODecive(command, m_thirdSocket.data(), m_writeCommandCounter); - - if (m_captureFileForTest.isWritable()) { - qDebug() << "Write stream to file: " << m_captureFileForTest.fileName(); - writeCommandToIODecive(command, &m_captureFileForTest, m_writeCommandCounter); - qDebug() << "\twrite file: " << m_captureFileForTest.pos(); - } - - m_writeCommandCounter++; - if (m_runModus == TestModus) { - static int synchronizeId = 0; - synchronizeId++; - SynchronizeCommand synchronizeCommand(synchronizeId); - - writeCommandToIODecive(QVariant::fromValue(synchronizeCommand), m_firstSocket.data(), m_writeCommandCounter); - m_writeCommandCounter++; - - while (m_firstSocket->waitForReadyRead(100)) { - readFirstDataStream(); - if (m_synchronizeId == synchronizeId) - return; - } - } -} - -void NodeInstanceServerProxy::processFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - auto finishedProcess = qobject_cast<QProcess*>(sender()); - if (finishedProcess) - qWarning() << "Process" << (exitStatus == QProcess::CrashExit ? "crashed:" : "finished:") << finishedProcess->arguments() << "exitCode:" << exitCode; - else - qWarning() << "Process" << (exitStatus == QProcess::CrashExit ? "crashed:" : "finished:") << sender() << "exitCode:" << exitCode; - - if (m_captureFileForTest.isOpen()) { - m_captureFileForTest.close(); - Core::AsynchronousMessageBox::warning(tr("QML Emulation Layer (QML Puppet) Crashed"), - tr("You are recording a puppet stream and the emulations layer crashed. " - "It is recommended to reopen the Qt Quick Designer and start again.")); - } - - - writeCommand(QVariant::fromValue(EndPuppetCommand())); - - if (m_firstSocket) { - m_firstSocket->waitForBytesWritten(1000); - m_firstSocket->abort(); - } - - if (m_secondSocket) { - m_secondSocket->waitForBytesWritten(1000); - m_secondSocket->abort(); - } - - if (m_thirdSocket) { - m_thirdSocket->waitForBytesWritten(1000); - m_thirdSocket->abort(); - } - - if (exitStatus == QProcess::CrashExit) - emit processCrashed(); -} - - -void NodeInstanceServerProxy::readFirstDataStream() -{ - QList<QVariant> commandList; - - while (!m_firstSocket->atEnd()) { - if (m_firstSocket->bytesAvailable() < int(sizeof(quint32))) - break; - - QDataStream in(m_firstSocket.data()); - in.setVersion(QDataStream::Qt_4_8); - - if (m_firstBlockSize == 0) - in >> m_firstBlockSize; - - if (m_firstSocket->bytesAvailable() < m_firstBlockSize) - break; - - quint32 commandCounter; - in >> commandCounter; - bool commandLost = !((m_firstLastReadCommandCounter == 0 && commandCounter == 0) || (m_firstLastReadCommandCounter + 1 == commandCounter)); - if (commandLost) - qDebug() << "server command lost: " << m_firstLastReadCommandCounter << commandCounter; - m_firstLastReadCommandCounter = commandCounter; - - - QVariant command; - in >> command; - m_firstBlockSize = 0; - - commandList.append(command); - } - - foreach (const QVariant &command, commandList) { - dispatchCommand(command, FirstPuppetStream); - } -} - -void NodeInstanceServerProxy::readSecondDataStream() -{ - QList<QVariant> commandList; - - while (!m_secondSocket->atEnd()) { - if (m_secondSocket->bytesAvailable() < int(sizeof(quint32))) - break; - - QDataStream in(m_secondSocket.data()); - in.setVersion(QDataStream::Qt_4_8); - - if (m_secondBlockSize == 0) - in >> m_secondBlockSize; - - if (m_secondSocket->bytesAvailable() < m_secondBlockSize) - break; - - quint32 commandCounter; - in >> commandCounter; - bool commandLost = !((m_secondLastReadCommandCounter == 0 && commandCounter == 0) || (m_secondLastReadCommandCounter + 1 == commandCounter)); - if (commandLost) - qDebug() << "server command lost: " << m_secondLastReadCommandCounter << commandCounter; - m_secondLastReadCommandCounter = commandCounter; - - - QVariant command; - in >> command; - m_secondBlockSize = 0; - - commandList.append(command); - } - - foreach (const QVariant &command, commandList) { - dispatchCommand(command, SecondPuppetStream); - } -} - -void NodeInstanceServerProxy::readThirdDataStream() -{ - QList<QVariant> commandList; - - while (!m_thirdSocket->atEnd()) { - if (m_thirdSocket->bytesAvailable() < int(sizeof(quint32))) - break; - - QDataStream in(m_thirdSocket.data()); - in.setVersion(QDataStream::Qt_4_8); - - if (m_thirdBlockSize == 0) - in >> m_thirdBlockSize; - - if (m_thirdSocket->bytesAvailable() < m_thirdBlockSize) - break; - - quint32 commandCounter; - in >> commandCounter; - bool commandLost = !((m_thirdLastReadCommandCounter == 0 && commandCounter == 0) || (m_thirdLastReadCommandCounter + 1 == commandCounter)); - if (commandLost) - qDebug() << "server command lost: " << m_thirdLastReadCommandCounter << commandCounter; - m_thirdLastReadCommandCounter = commandCounter; - - - QVariant command; - in >> command; - m_thirdBlockSize = 0; - - commandList.append(command); - } - - foreach (const QVariant &command, commandList) { - dispatchCommand(command, ThirdPuppetStream); - } -} - -void NodeInstanceServerProxy::printEditorProcessOutput() -{ - while (m_qmlPuppetEditorProcess && m_qmlPuppetEditorProcess->canReadLine()) { - QByteArray line = m_qmlPuppetEditorProcess->readLine(); - line.chop(1); - qDebug().nospace() << "Editor Puppet: " << line; - } - qDebug() << "\n"; -} - -void NodeInstanceServerProxy::printPreviewProcessOutput() -{ - while (m_qmlPuppetPreviewProcess && m_qmlPuppetPreviewProcess->canReadLine()) { - QByteArray line = m_qmlPuppetPreviewProcess->readLine(); - line.chop(1); - qDebug().nospace() << "Preview Puppet: " << line; - } - qDebug() << "\n"; -} - -void NodeInstanceServerProxy::printRenderProcessOutput() -{ - while (m_qmlPuppetRenderProcess && m_qmlPuppetRenderProcess->canReadLine()) { - QByteArray line = m_qmlPuppetRenderProcess->readLine(); - line.chop(1); - qDebug().nospace() << "Render Puppet: " << line; - } - - qDebug() << "\n"; + m_connectionManager.writeCommand(command); } void NodeInstanceServerProxy::createInstances(const CreateInstancesCommand &command) diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h index 4b0df0fd9a..2efc7ea8a4 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h @@ -48,21 +48,17 @@ namespace QmlDesigner { class NodeInstanceClientInterface; class NodeInstanceView; class NodeInstanceClientProxy; +class ConnectionManagerInterface; class NodeInstanceServerProxy : public NodeInstanceServerInterface { + friend class BaseConnectionManager; Q_OBJECT public: - enum PuppetStreamType { - FirstPuppetStream, - SecondPuppetStream, - ThirdPuppetStream, - }; - explicit NodeInstanceServerProxy(NodeInstanceView *nodeInstanceView, - RunModus runModus, - ProjectExplorer::Target *target); + ProjectExplorer::Target *target, + ConnectionManagerInterface &connectionManager); ~NodeInstanceServerProxy() override; void createInstances(const CreateInstancesCommand &command) override; void changeFileUrl(const ChangeFileUrlCommand &command) override; @@ -88,52 +84,22 @@ public: void changeLanguage(const ChangeLanguageCommand &command) override; void changePreviewImageSize(const ChangePreviewImageSizeCommand &command) override; + NodeInstanceView *nodeInstanceView() const { return m_nodeInstanceView; } + + QString qrcMappingString() const; + protected: void writeCommand(const QVariant &command); - void dispatchCommand(const QVariant &command, PuppetStreamType puppetStreamType); + void dispatchCommand(const QVariant &command); NodeInstanceClientInterface *nodeInstanceClient() const; - void puppetAlive(PuppetStreamType puppetStreamType); - QString qrcMappingString() const; signals: void processCrashed(); -private slots: - void processFinished(); - void puppetTimeout(PuppetStreamType puppetStreamType); - void processFinished(int exitCode, QProcess::ExitStatus exitStatus); - void readFirstDataStream(); - void readSecondDataStream(); - void readThirdDataStream(); - - void printEditorProcessOutput(); - void printPreviewProcessOutput(); - void printRenderProcessOutput(); - void showCannotConnectToPuppetWarningAndSwitchToEditMode(); private: - QFile m_captureFileForTest; - QTimer m_firstTimer; - QTimer m_secondTimer; - QTimer m_thirdTimer; - QPointer<QLocalServer> m_localServer; - QPointer<QLocalSocket> m_firstSocket; - QPointer<QLocalSocket> m_secondSocket; - QPointer<QLocalSocket> m_thirdSocket; - QPointer<NodeInstanceView> m_nodeInstanceView; - QPointer<QProcess> m_qmlPuppetEditorProcess; - QPointer<QProcess> m_qmlPuppetPreviewProcess; - QPointer<QProcess> m_qmlPuppetRenderProcess; - quint32 m_firstBlockSize = 0; - quint32 m_secondBlockSize = 0; - quint32 m_thirdBlockSize = 0; - quint32 m_writeCommandCounter = 0; - quint32 m_firstLastReadCommandCounter = 0; - quint32 m_secondLastReadCommandCounter = 0; - quint32 m_thirdLastReadCommandCounter = 0; - RunModus m_runModus; - int m_synchronizeId = -1; + NodeInstanceView *m_nodeInstanceView{}; QElapsedTimer m_benchmarkTimer; - bool m_destructing = false; + ConnectionManagerInterface &m_connectionManager; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 7cf2784a7d..21623f865c 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -27,6 +27,7 @@ #include "abstractproperty.h" #include "bindingproperty.h" +#include "captureddatacommand.h" #include "changeauxiliarycommand.h" #include "changebindingscommand.h" #include "changefileurlcommand.h" @@ -41,6 +42,7 @@ #include "clearscenecommand.h" #include "completecomponentcommand.h" #include "componentcompletedcommand.h" +#include "connectionmanagerinterface.h" #include "createinstancescommand.h" #include "createscenecommand.h" #include "debugoutputcommand.h" @@ -124,11 +126,10 @@ namespace QmlDesigner { \sa ~NodeInstanceView, setRenderOffScreen() */ -NodeInstanceView::NodeInstanceView(QObject *parent, NodeInstanceServerInterface::RunModus runModus) - : AbstractView(parent), - m_baseStatePreviewImage(QSize(100, 100), QImage::Format_ARGB32), - m_runModus(runModus), - m_restartProcessTimerId(0) +NodeInstanceView::NodeInstanceView(ConnectionManagerInterface &connectionManager) + : m_connectionManager(connectionManager) + , m_baseStatePreviewImage(QSize(100, 100), QImage::Format_ARGB32) + , m_restartProcessTimerId(0) { m_baseStatePreviewImage.fill(0xFFFFFF); } @@ -140,7 +141,6 @@ NodeInstanceView::NodeInstanceView(QObject *parent, NodeInstanceServerInterface: NodeInstanceView::~NodeInstanceView() { removeAllInstanceNodeRelationships(); - delete nodeInstanceServer(); m_currentTarget = nullptr; } @@ -191,14 +191,16 @@ bool static parentTakesOverRendering(const ModelNode &modelNode) void NodeInstanceView::modelAttached(Model *model) { AbstractView::modelAttached(model); - auto server = new NodeInstanceServerProxy(this, m_runModus, m_currentTarget); - m_nodeInstanceServer = server; + m_nodeInstanceServer = createNodeInstanceServerProxy(); m_lastCrashTime.start(); - connect(server, &NodeInstanceServerProxy::processCrashed, this, &NodeInstanceView::handleCrash); + connect(m_nodeInstanceServer.get(), + &NodeInstanceServerProxy::processCrashed, + this, + &NodeInstanceView::handleCrash); if (!isSkippedRootNode(rootModelNode())) { - nodeInstanceServer()->createScene(createCreateSceneCommand()); - nodeInstanceServer()->changeSelection(createChangeSelectionCommand(model->selectedNodes(this))); + m_nodeInstanceServer->createScene(createCreateSceneCommand()); + m_nodeInstanceServer->changeSelection(createChangeSelectionCommand(model->selectedNodes(this))); } ModelNode stateNode = currentStateNode(); @@ -206,15 +208,14 @@ void NodeInstanceView::modelAttached(Model *model) NodeInstance newStateInstance = instanceForModelNode(stateNode); activateState(newStateInstance); } - } void NodeInstanceView::modelAboutToBeDetached(Model * model) { removeAllInstanceNodeRelationships(); - if (nodeInstanceServer()) { - nodeInstanceServer()->clearScene(createClearSceneCommand()); - delete nodeInstanceServer(); + if (m_nodeInstanceServer) { + m_nodeInstanceServer->clearScene(createClearSceneCommand()); + m_nodeInstanceServer.reset(); } m_statePreviewImage.clear(); m_baseStatePreviewImage = QImage(); @@ -274,15 +275,18 @@ void NodeInstanceView::restartProcess() killTimer(m_restartProcessTimerId); if (model()) { - delete nodeInstanceServer(); + m_nodeInstanceServer.reset(); + m_nodeInstanceServer = createNodeInstanceServerProxy(); - auto server = new NodeInstanceServerProxy(this, m_runModus, m_currentTarget); - m_nodeInstanceServer = server; - connect(server, &NodeInstanceServerProxy::processCrashed, this, &NodeInstanceView::handleCrash); + connect(m_nodeInstanceServer.get(), + &NodeInstanceServerProxy::processCrashed, + this, + &NodeInstanceView::handleCrash); if (!isSkippedRootNode(rootModelNode())) { - nodeInstanceServer()->createScene(createCreateSceneCommand()); - nodeInstanceServer()->changeSelection(createChangeSelectionCommand(model()->selectedNodes(this))); + m_nodeInstanceServer->createScene(createCreateSceneCommand()); + m_nodeInstanceServer->changeSelection( + createChangeSelectionCommand(model()->selectedNodes(this))); } ModelNode stateNode = currentStateNode(); @@ -313,17 +317,19 @@ void NodeInstanceView::nodeCreated(const ModelNode &createdNode) propertyList.append(createdNode.variantProperty("y")); updatePosition(propertyList); - nodeInstanceServer()->createInstances(createCreateInstancesCommand({instance})); - nodeInstanceServer()->changePropertyValues(createChangeValueCommand(createdNode.variantProperties())); - nodeInstanceServer()->completeComponent(createComponentCompleteCommand({instance})); + m_nodeInstanceServer->createInstances(createCreateInstancesCommand({instance})); + m_nodeInstanceServer->changePropertyValues( + createChangeValueCommand(createdNode.variantProperties())); + m_nodeInstanceServer->completeComponent(createComponentCompleteCommand({instance})); } /*! Notifies the view that \a removedNode will be removed. */ void NodeInstanceView::nodeAboutToBeRemoved(const ModelNode &removedNode) { - nodeInstanceServer()->removeInstances(createRemoveInstancesCommand(removedNode)); - nodeInstanceServer()->removeSharedMemory(createRemoveSharedMemoryCommand("Image", removedNode.internalId())); + m_nodeInstanceServer->removeInstances(createRemoveInstancesCommand(removedNode)); + m_nodeInstanceServer->removeSharedMemory( + createRemoveSharedMemoryCommand("Image", removedNode.internalId())); removeInstanceAndSubInstances(removedNode); } @@ -343,11 +349,10 @@ void NodeInstanceView::resetHorizontalAnchors(const ModelNode &modelNode) valueList.append(modelNode.variantProperty("width")); if (!valueList.isEmpty()) - nodeInstanceServer()->changePropertyValues(createChangeValueCommand(valueList)); + m_nodeInstanceServer->changePropertyValues(createChangeValueCommand(valueList)); if (!bindingList.isEmpty()) - nodeInstanceServer()->changePropertyBindings(createChangeBindingCommand(bindingList)); - + m_nodeInstanceServer->changePropertyBindings(createChangeBindingCommand(bindingList)); } void NodeInstanceView::resetVerticalAnchors(const ModelNode &modelNode) @@ -355,8 +360,8 @@ void NodeInstanceView::resetVerticalAnchors(const ModelNode &modelNode) QList<BindingProperty> bindingList; QList<VariantProperty> valueList; - if (modelNode.hasBindingProperty("yx")) - bindingList.append(modelNode.bindingProperty("yx")); + if (modelNode.hasBindingProperty("x")) + bindingList.append(modelNode.bindingProperty("x")); else if (modelNode.hasVariantProperty("y")) valueList.append(modelNode.variantProperty("y")); @@ -366,10 +371,10 @@ void NodeInstanceView::resetVerticalAnchors(const ModelNode &modelNode) valueList.append(modelNode.variantProperty("height")); if (!valueList.isEmpty()) - nodeInstanceServer()->changePropertyValues(createChangeValueCommand(valueList)); + m_nodeInstanceServer->changePropertyValues(createChangeValueCommand(valueList)); if (!bindingList.isEmpty()) - nodeInstanceServer()->changePropertyBindings(createChangeBindingCommand(bindingList)); + m_nodeInstanceServer->changePropertyBindings(createChangeBindingCommand(bindingList)); } void NodeInstanceView::propertiesAboutToBeRemoved(const QList<AbstractProperty>& propertyList) @@ -388,10 +393,10 @@ void NodeInstanceView::propertiesAboutToBeRemoved(const QList<AbstractProperty>& RemoveInstancesCommand removeInstancesCommand = createRemoveInstancesCommand(nodeList); if (!removeInstancesCommand.instanceIds().isEmpty()) - nodeInstanceServer()->removeInstances(removeInstancesCommand); + m_nodeInstanceServer->removeInstances(removeInstancesCommand); - nodeInstanceServer()->removeSharedMemory(createRemoveSharedMemoryCommand("Image", nodeList)); - nodeInstanceServer()->removeProperties(createRemovePropertiesCommand(nonNodePropertyList)); + m_nodeInstanceServer->removeSharedMemory(createRemoveSharedMemoryCommand("Image", nodeList)); + m_nodeInstanceServer->removeProperties(createRemovePropertiesCommand(nonNodePropertyList)); foreach (const AbstractProperty &property, propertyList) { const PropertyName &name = property.name(); @@ -445,7 +450,7 @@ void NodeInstanceView::nodeTypeChanged(const ModelNode &, const TypeName &, int, void NodeInstanceView::bindingPropertiesChanged(const QList<BindingProperty>& propertyList, PropertyChangeFlags /*propertyChange*/) { - nodeInstanceServer()->changePropertyBindings(createChangeBindingCommand(propertyList)); + m_nodeInstanceServer->changePropertyBindings(createChangeBindingCommand(propertyList)); } /*! @@ -460,7 +465,7 @@ void NodeInstanceView::bindingPropertiesChanged(const QList<BindingProperty>& pr void NodeInstanceView::variantPropertiesChanged(const QList<VariantProperty>& propertyList, PropertyChangeFlags /*propertyChange*/) { updatePosition(propertyList); - nodeInstanceServer()->changePropertyValues(createChangeValueCommand(propertyList)); + m_nodeInstanceServer->changePropertyValues(createChangeValueCommand(propertyList)); } /*! Notifies the view that the property parent of the model node \a node has @@ -476,20 +481,21 @@ void NodeInstanceView::nodeReparented(const ModelNode &node, const NodeAbstractP { if (!isSkippedNode(node)) { updateChildren(newPropertyParent); - nodeInstanceServer()->reparentInstances(createReparentInstancesCommand(node, newPropertyParent, oldPropertyParent)); + m_nodeInstanceServer->reparentInstances( + createReparentInstancesCommand(node, newPropertyParent, oldPropertyParent)); } } void NodeInstanceView::fileUrlChanged(const QUrl &/*oldUrl*/, const QUrl &newUrl) { - nodeInstanceServer()->changeFileUrl(createChangeFileUrlCommand(newUrl)); + m_nodeInstanceServer->changeFileUrl(createChangeFileUrlCommand(newUrl)); } void NodeInstanceView::nodeIdChanged(const ModelNode& node, const QString& /*newId*/, const QString& /*oldId*/) { if (hasInstanceForModelNode(node)) { NodeInstance instance = instanceForModelNode(node); - nodeInstanceServer()->changeIds(createChangeIdsCommand({instance})); + m_nodeInstanceServer->changeIds(createChangeIdsCommand({instance})); } } @@ -512,7 +518,7 @@ void NodeInstanceView::nodeOrderChanged(const NodeListProperty & listProperty, } } - nodeInstanceServer()->reparentInstances(ReparentInstancesCommand(containerList)); + m_nodeInstanceServer->reparentInstances(ReparentInstancesCommand(containerList)); } void NodeInstanceView::importsChanged(const QList<Import> &/*addedImports*/, const QList<Import> &/*removedImports*/) @@ -531,30 +537,26 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node, if (value.isValid() || name == "invisible") { PropertyValueContainer container(instance.instanceId(), name, value, TypeName()); ChangeAuxiliaryCommand changeAuxiliaryCommand({container}); - nodeInstanceServer()->changeAuxiliaryValues(changeAuxiliaryCommand); + m_nodeInstanceServer->changeAuxiliaryValues(changeAuxiliaryCommand); } else { if (node.hasVariantProperty(name)) { PropertyValueContainer container(instance.instanceId(), name, node.variantProperty(name).value(), TypeName()); ChangeValuesCommand changeValueCommand({container}); - nodeInstanceServer()->changePropertyValues(changeValueCommand); + m_nodeInstanceServer->changePropertyValues(changeValueCommand); } else if (node.hasBindingProperty(name)) { PropertyBindingContainer container(instance.instanceId(), name, node.bindingProperty(name).expression(), TypeName()); ChangeBindingsCommand changeValueCommand({container}); - nodeInstanceServer()->changePropertyBindings(changeValueCommand); + m_nodeInstanceServer->changePropertyBindings(changeValueCommand); } } } } else if (node.isRootNode() && name == "language@Internal") { const QString languageAsString = value.toString(); - if (m_currentTarget) { - if (auto rc = m_currentTarget->activeRunConfiguration()) { - if (auto multiLanguageAspect = rc->aspect<QmlProjectManager::QmlMultiLanguageAspect>()) - multiLanguageAspect->setLastUsedLanguage(languageAsString); - } - } - nodeInstanceServer()->changeLanguage({languageAsString}); + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(m_currentTarget)) + multiLanguageAspect->setCurrentLocale(languageAsString); + m_nodeInstanceServer->changeLanguage({languageAsString}); } else if (node.isRootNode() && name == "previewSize@Internal") { - nodeInstanceServer()->changePreviewImageSize(value.toSize()); + m_nodeInstanceServer->changePreviewImageSize(value.toSize()); } } @@ -569,10 +571,12 @@ void NodeInstanceView::nodeSourceChanged(const ModelNode &node, const QString & if (hasInstanceForModelNode(node)) { NodeInstance instance = instanceForModelNode(node); ChangeNodeSourceCommand changeNodeSourceCommand(instance.instanceId(), newNodeSource); - nodeInstanceServer()->changeNodeSource(changeNodeSourceCommand); + m_nodeInstanceServer->changeNodeSource(changeNodeSourceCommand); } } +void NodeInstanceView::capturedData(const CapturedDataCommand &) {} + void NodeInstanceView::currentStateChanged(const ModelNode &node) { NodeInstance newStateInstance = instanceForModelNode(node); @@ -794,12 +798,6 @@ void NodeInstanceView::updatePosition(const QList<VariantProperty> &propertyList emitInstanceInformationsChange(informationChangeHash); } -NodeInstanceServerInterface *NodeInstanceView::nodeInstanceServer() const -{ - return m_nodeInstanceServer.data(); -} - - NodeInstance NodeInstanceView::loadNode(const ModelNode &node) { NodeInstance instance(NodeInstance::create(node)); @@ -814,12 +812,12 @@ NodeInstance NodeInstanceView::loadNode(const ModelNode &node) void NodeInstanceView::activateState(const NodeInstance &instance) { - nodeInstanceServer()->changeState(ChangeStateCommand(instance.instanceId())); + m_nodeInstanceServer->changeState(ChangeStateCommand(instance.instanceId())); } void NodeInstanceView::activateBaseState() { - nodeInstanceServer()->changeState(ChangeStateCommand(-1)); + m_nodeInstanceServer->changeState(ChangeStateCommand(-1)); } void NodeInstanceView::removeRecursiveChildRelationship(const ModelNode &removedNode) @@ -994,12 +992,8 @@ CreateSceneCommand NodeInstanceView::createCreateSceneCommand() } QString lastUsedLanguage; - if (m_currentTarget) { - if (auto rc = m_currentTarget->activeRunConfiguration()) { - if (auto multiLanguageAspect = rc->aspect<QmlProjectManager::QmlMultiLanguageAspect>()) - lastUsedLanguage = multiLanguageAspect->lastUsedLanguage(); - } - } + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(m_currentTarget)) + lastUsedLanguage = multiLanguageAspect->currentLocale(); return CreateSceneCommand( instanceContainerList, @@ -1256,7 +1250,8 @@ void NodeInstanceView::valuesChanged(const ValuesChangedCommand &command) } } - nodeInstanceServer()->removeSharedMemory(createRemoveSharedMemoryCommand(QStringLiteral("Values"), command.keyNumber())); + m_nodeInstanceServer->removeSharedMemory( + createRemoveSharedMemoryCommand(QStringLiteral("Values"), command.keyNumber())); if (!valuePropertyChangeList.isEmpty()) emitInstancePropertyChange(valuePropertyChangeList); @@ -1464,7 +1459,7 @@ void NodeInstanceView::sendToken(const QString &token, int number, const QVector foreach (const ModelNode &node, nodeVector) instanceIdVector.append(node.internalId()); - nodeInstanceServer()->token(TokenCommand(token, number, instanceIdVector)); + m_nodeInstanceServer->token(TokenCommand(token, number, instanceIdVector)); } void NodeInstanceView::selectionChanged(const ChangeSelectionCommand &command) @@ -1479,7 +1474,7 @@ void NodeInstanceView::selectionChanged(const ChangeSelectionCommand &command) void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand &command) { if (command.type() == PuppetToCreatorCommand::Edit3DToolState) { - if (!m_nodeInstanceServer.isNull()) { + if (m_nodeInstanceServer) { auto data = qvariant_cast<QVariantList>(command.data()); if (data.size() == 3) { QString qmlId = data[0].toString(); @@ -1496,25 +1491,30 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand } } +std::unique_ptr<NodeInstanceServerProxy> NodeInstanceView::createNodeInstanceServerProxy() +{ + return std::make_unique<NodeInstanceServerProxy>(this, m_currentTarget, m_connectionManager); +} + void NodeInstanceView::selectedNodesChanged(const QList<ModelNode> &selectedNodeList, const QList<ModelNode> & /*lastSelectedNodeList*/) { - nodeInstanceServer()->changeSelection(createChangeSelectionCommand(selectedNodeList)); + m_nodeInstanceServer->changeSelection(createChangeSelectionCommand(selectedNodeList)); } void NodeInstanceView::sendInputEvent(QInputEvent *e) const { - nodeInstanceServer()->inputEvent(InputEventCommand(e)); + m_nodeInstanceServer->inputEvent(InputEventCommand(e)); } void NodeInstanceView::view3DAction(const View3DActionCommand &command) { - nodeInstanceServer()->view3DAction(command); + m_nodeInstanceServer->view3DAction(command); } void NodeInstanceView::edit3DViewResized(const QSize &size) const { - nodeInstanceServer()->update3DViewState(Update3dViewStateCommand(size)); + m_nodeInstanceServer->update3DViewState(Update3dViewStateCommand(size)); } void NodeInstanceView::timerEvent(QTimerEvent *event) diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp index c74c41b323..bdae76c0d3 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp @@ -178,39 +178,46 @@ PuppetCreator::PuppetCreator(ProjectExplorer::Target *target, const Model *model { } -QProcess *PuppetCreator::createPuppetProcess(const QString &puppetMode, - const QString &socketToken, - QObject *handlerObject, - const char *outputSlot, - const char *finishSlot, - const QStringList &customOptions) const +QProcessUniquePointer PuppetCreator::createPuppetProcess( + const QString &puppetMode, + const QString &socketToken, + QObject *handlerObject, + std::function<void()> processOutputCallback, + std::function<void(int, QProcess::ExitStatus)> processFinishCallback, + const QStringList &customOptions) const { return puppetProcess(qml2PuppetPath(m_availablePuppetType), qmlPuppetDirectory(m_availablePuppetType), puppetMode, socketToken, handlerObject, - outputSlot, - finishSlot, + processOutputCallback, + processFinishCallback, customOptions); } - -QProcess *PuppetCreator::puppetProcess(const QString &puppetPath, - const QString &workingDirectory, - const QString &puppetMode, - const QString &socketToken, - QObject *handlerObject, - const char *outputSlot, - const char *finishSlot, - const QStringList &customOptions) const +QProcessUniquePointer PuppetCreator::puppetProcess( + const QString &puppetPath, + const QString &workingDirectory, + const QString &puppetMode, + const QString &socketToken, + QObject *handlerObject, + std::function<void()> processOutputCallback, + std::function<void(int, QProcess::ExitStatus)> processFinishCallback, + const QStringList &customOptions) const { - auto puppetProcess = new QProcess; + QProcessUniquePointer puppetProcess{new QProcess}; puppetProcess->setObjectName(puppetMode); puppetProcess->setProcessEnvironment(processEnvironment()); - QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, puppetProcess, &QProcess::kill); - QObject::connect(puppetProcess, SIGNAL(finished(int,QProcess::ExitStatus)), handlerObject, finishSlot); + QObject::connect(QCoreApplication::instance(), + &QCoreApplication::aboutToQuit, + puppetProcess.get(), + &QProcess::kill); + QObject::connect(puppetProcess.get(), + static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), + handlerObject, + processFinishCallback); #ifndef QMLDESIGNER_TEST QString forwardOutput = m_designerSettings.value(DesignerSettingsKey:: @@ -220,7 +227,7 @@ QProcess *PuppetCreator::puppetProcess(const QString &puppetPath, #endif if (forwardOutput == puppetMode || forwardOutput == "all") { puppetProcess->setProcessChannelMode(QProcess::MergedChannels); - QObject::connect(puppetProcess, SIGNAL(readyRead()), handlerObject, outputSlot); + QObject::connect(puppetProcess.get(), &QProcess::readyRead, handlerObject, processOutputCallback); } puppetProcess->setWorkingDirectory(workingDirectory); @@ -514,11 +521,9 @@ QProcessEnvironment PuppetCreator::processEnvironment() const customFileSelectors = m_target->additionalData("CustomFileSelectorsData").toStringList(); - if (auto *rc = m_target->activeRunConfiguration()) { - if (auto multiLanguageAspect = rc->aspect<QmlProjectManager::QmlMultiLanguageAspect>()) { - if (!multiLanguageAspect->databaseFilePath().isEmpty()) - environment.set("QT_MULTILANGUAGE_DATABASE", multiLanguageAspect->databaseFilePath().toString()); - } + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(m_target)) { + if (!multiLanguageAspect->databaseFilePath().isEmpty()) + environment.set("QT_MULTILANGUAGE_DATABASE", multiLanguageAspect->databaseFilePath().toString()); } } diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h index 00b5a0e921..c19a0e1381 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h @@ -25,6 +25,8 @@ #pragma once +#include "qprocessuniqueptr.h" + #include <QString> #include <QProcessEnvironment> @@ -53,12 +55,13 @@ public: void createQml2PuppetExecutableIfMissing(); - QProcess *createPuppetProcess(const QString &puppetMode, - const QString &socketToken, - QObject *handlerObject, - const char *outputSlot, - const char *finishSlot, - const QStringList &customOptions = {}) const; + QProcessUniquePointer createPuppetProcess( + const QString &puppetMode, + const QString &socketToken, + QObject *handlerObject, + std::function<void()> processOutputCallback, + std::function<void(int, QProcess::ExitStatus)> processFinishCallback, + const QStringList &customOptions = {}) const; void setQrcMappingString(const QString qrcMapping); @@ -82,14 +85,14 @@ protected: bool checkPuppetIsReady(const QString &puppetPath) const; bool qtIsSupported() const; - QProcess *puppetProcess(const QString &puppetPath, - const QString &workingDirectory, - const QString &puppetMode, - const QString &socketToken, - QObject *handlerObject, - const char *outputSlot, - const char *finishSlot, - const QStringList &customOptions) const; + QProcessUniquePointer puppetProcess(const QString &puppetPath, + const QString &workingDirectory, + const QString &puppetMode, + const QString &socketToken, + QObject *handlerObject, + std::function<void()> processOutputCallback, + std::function<void(int, QProcess::ExitStatus)> processFinishCallback, + const QStringList &customOptions) const; QProcessEnvironment processEnvironment() const; diff --git a/src/plugins/qmldesigner/designercore/instances/qprocessuniqueptr.h b/src/plugins/qmldesigner/designercore/instances/qprocessuniqueptr.h new file mode 100644 index 0000000000..b03c86f772 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/instances/qprocessuniqueptr.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#pragma once + +#include <QProcess> + +#include <memory> + +namespace QmlDesigner { + +class QProcessUniquePointerDeleter +{ +public: + void operator()(QProcess *process) + { + process->disconnect(); + QObject::connect(process, + QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), + process, + &QProcess::deleteLater); + + process->terminate(); + + process->deleteLater(); + } +}; + +using QProcessUniquePointer = std::unique_ptr<QProcess, QProcessUniquePointerDeleter>; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/model/viewmanager.cpp b/src/plugins/qmldesigner/designercore/model/viewmanager.cpp index 80c23acad7..c3946a5378 100644 --- a/src/plugins/qmldesigner/designercore/model/viewmanager.cpp +++ b/src/plugins/qmldesigner/designercore/model/viewmanager.cpp @@ -27,23 +27,24 @@ #ifndef QMLDESIGNER_TEST +#include <abstractview.h> #include <componentaction.h> -#include <designmodewidget.h> +#include <componentview.h> #include <crumblebar.h> -#include <abstractview.h> -#include <rewriterview.h> -#include <nodeinstanceview.h> +#include <debugview.h> +#include <designeractionmanagerview.h> +#include <designmodewidget.h> +#include <edit3dview.h> +#include <formeditorview.h> +#include <importmanagerview.h> +#include <interactiveconnectionmanager.h> #include <itemlibraryview.h> #include <navigatorview.h> +#include <nodeinstanceview.h> +#include <propertyeditorview.h> +#include <rewriterview.h> #include <stateseditorview.h> -#include <edit3dview.h> -#include <formeditorview.h> #include <texteditorview.h> -#include <propertyeditorview.h> -#include <componentview.h> -#include <debugview.h> -#include <importmanagerview.h> -#include <designeractionmanagerview.h> #include <qmldesignerplugin.h> #include <utils/algorithm.h> @@ -59,10 +60,11 @@ static Q_LOGGING_CATEGORY(viewBenchmark, "qtc.viewmanager.attach", QtWarningMsg) class ViewManagerData { public: + InteractiveConnectionManager connectionManager; QmlModelState savedState; Internal::DebugView debugView; DesignerActionManagerView designerActionManagerView; - NodeInstanceView nodeInstanceView; + NodeInstanceView nodeInstanceView{connectionManager}; ComponentView componentView; Edit3DView edit3DView; FormEditorView formEditorView; @@ -81,7 +83,7 @@ static CrumbleBar *crumbleBar() { } ViewManager::ViewManager() - : d(new ViewManagerData) + : d(std::make_unique<ViewManagerData>()) { d->formEditorView.setGotoErrorCallback([this](int line, int column) { d->textEditorView.gotoCursorPosition(line, column); @@ -92,10 +94,9 @@ ViewManager::ViewManager() ViewManager::~ViewManager() { - foreach (const QPointer<AbstractView> &view, d->additionalViews) + for (const QPointer<AbstractView> &view : d->additionalViews) delete view.data(); - delete d; } DesignDocument *ViewManager::currentDesignDocument() const diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 0e9ee73191..b08766d46b 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -134,6 +134,7 @@ Project { "commands/changestatecommand.h", "commands/changevaluescommand.cpp", "commands/changevaluescommand.h", + "commands/captureddatacommand.h", "commands/childrenchangedcommand.cpp", "commands/childrenchangedcommand.h", "commands/clearscenecommand.cpp", @@ -321,6 +322,17 @@ Project { "instances/puppetdialog.cpp", "instances/puppetdialog.h", "instances/puppetdialog.ui", + "instances/connectionmanagerinterface.cpp", + "instances/connectionmanagerinterface.h", + "instances/baseconnectionmanager.cpp ", + "instances/baseconnectionmanager.h", + "instances/connectionmanager.cpp", + "instances/connectionmanager.h", + "instances/capturingconnectionmanager.cpp", + "instances/capturingconnectionmanager.h", + "instances/interactiveconnectionmanager.cpp", + "instances/interactiveconnectionmanager.h", + "instances/qprocessuniqueptr.h", "metainfo/itemlibraryinfo.cpp", "metainfo/metainfo.cpp", "metainfo/metainforeader.cpp", diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp index b94f87bc40..616678d855 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp @@ -212,24 +212,26 @@ void FpsAction::currentContextChanged(const SelectionContext &) SwitchLanguageComboboxAction::SwitchLanguageComboboxAction(QObject *parent) : QWidgetAction(parent) { - connect(ProjectExplorer::SessionManager::instance(), - &ProjectExplorer::SessionManager::startupProjectChanged, - this, - &SwitchLanguageComboboxAction::updateProjectLocales); } QWidget *SwitchLanguageComboboxAction::createWidget(QWidget *parent) { QPointer<QComboBox> comboBox = new QComboBox(parent); - comboBox->setToolTip(tr("Switch the language used by preview.")); + const QString toolTip(tr("Switch the language used by preview.")); + comboBox->setToolTip(toolTip); comboBox->addItem(tr("Default")); - auto refreshComboBoxFunction = [this, comboBox] (ProjectExplorer::Project *project) { - if (comboBox) { - if (updateProjectLocales(project)) { + auto refreshComboBoxFunction = [this, comboBox, toolTip] (ProjectExplorer::Project *project) { + if (comboBox && project) { + QString errorMessage; + auto locales = project->availableQmlPreviewTranslations(&errorMessage); + if (!errorMessage.isEmpty()) + comboBox->setToolTip(QString("%1<br/>(%2)").arg(toolTip, errorMessage)); + if (m_previousLocales != locales) { comboBox->clear(); comboBox->addItem(tr("Default")); - comboBox->addItems(m_localeStrings); + comboBox->addItems(locales); + m_previousLocales = locales; } } }; @@ -250,24 +252,6 @@ QWidget *SwitchLanguageComboboxAction::createWidget(QWidget *parent) return comboBox; } -bool SwitchLanguageComboboxAction::updateProjectLocales(Project *project) -{ - if (!project) - return false; - auto previousLocales = m_localeStrings; - m_localeStrings.clear(); - const auto projectDirectory = project->rootProjectDirectory().toFileInfo().absoluteFilePath(); - const QDir languageDirectory(projectDirectory + "/i18n"); - const auto qmFiles = languageDirectory.entryList({"qml_*.qm"}); - m_localeStrings = Utils::transform(qmFiles, [](const QString &qmFile) { - const int localeStartPosition = qmFile.lastIndexOf("_") + 1; - const int localeEndPosition = qmFile.size() - QString(".qm").size(); - const QString locale = qmFile.left(localeEndPosition).mid(localeStartPosition); - return locale; - }); - return previousLocales != m_localeStrings; -} - SwitchLanguageAction::SwitchLanguageAction() : m_switchLanguageAction(new SwitchLanguageComboboxAction(nullptr)) { diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h index a23f125ca1..8bbbc453d6 100644 --- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h +++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h @@ -113,8 +113,7 @@ signals: protected: QWidget *createWidget(QWidget *parent) override; private: - bool updateProjectLocales(ProjectExplorer::Project *project); - QStringList m_localeStrings; + QStringList m_previousLocales; }; class SwitchLanguageAction : public ActionInterface diff --git a/src/plugins/qmlpreview/CMakeLists.txt b/src/plugins/qmlpreview/CMakeLists.txt index 345f429e81..00bda33ab0 100644 --- a/src/plugins/qmlpreview/CMakeLists.txt +++ b/src/plugins/qmlpreview/CMakeLists.txt @@ -9,6 +9,8 @@ add_qtc_plugin(QmlPreview qmlpreviewruncontrol.cpp qmlpreviewruncontrol.h qmldebugtranslationclient.cpp qmldebugtranslationclient.h qmlpreview_global.h + projectfileselectionswidget.cpp projectfileselectionswidget.h + qmldebugtranslationwidget.cpp qmldebugtranslationwidget.h ) extend_qtc_plugin(QmlPreview diff --git a/src/plugins/qmlpreview/projectfileselectionswidget.cpp b/src/plugins/qmlpreview/projectfileselectionswidget.cpp new file mode 100644 index 0000000000..f6f20fc2d4 --- /dev/null +++ b/src/plugins/qmlpreview/projectfileselectionswidget.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#include "projectfileselectionswidget.h" + +#include <projectexplorer/target.h> +#include <projectexplorer/project.h> +#include <projectexplorer/session.h> + +#include <utils/treemodel.h> + +#include <QAbstractTableModel> +#include <QBoxLayout> +#include <QHeaderView> +#include <QTreeView> + +namespace QmlPreview { + +class ProjectFileItem : public Utils::TreeItem +{ +public: + ProjectFileItem() = default; + ProjectFileItem(const Utils::FilePath &f, bool d) + : filePath(f) + , disabled(d) + {} + + Qt::ItemFlags flags(int) const override + { + return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled; + } + + QVariant data(int , int role) const override + { + if (role == Qt::DisplayRole) + return filePath.toUserOutput(); + if (role == Qt::CheckStateRole) { + if (disabled) + return Qt::Unchecked; + else + return Qt::Checked; + } + return QVariant(); + } + + bool setData(int , const QVariant &data, int role) override + { + if (role != Qt::CheckStateRole) + return false; + disabled = (data == Qt::Unchecked); + return true; + } + + Utils::FilePath filePath; + bool disabled = false; +}; + + +ProjectFileSelectionsWidget::ProjectFileSelectionsWidget(const QString &projectSettingsKey, ProjectExplorer::FileType fileType, QWidget *parent) + : QWidget(parent) + , m_projectSettingsKey(projectSettingsKey) + , m_fileType(fileType) +{ + auto model = new Utils::TreeModel<ProjectFileItem>(this); + model->setHeader({tr("Files to test:")}); + auto updateCheckedFiles = [this, model] () { + m_checkedFiles.clear(); + QStringList uncheckedFiles; + model->forAllItems([&, this](ProjectFileItem *item) { + if (item->disabled) + uncheckedFiles.append(item->filePath.toString()); + else + m_checkedFiles.append(item->filePath); + }); + if (auto project = ProjectExplorer::SessionManager::startupProject()) + project->setNamedSettings(m_projectSettingsKey, uncheckedFiles); + emit selectionChanged(m_checkedFiles); + }; + + connect(model, &QAbstractItemModel::dataChanged, updateCheckedFiles); + + auto view = new QTreeView(this); + view->setMinimumSize(QSize(100, 100)); + view->setTextElideMode(Qt::ElideMiddle); + view->setWordWrap(false); + view->setUniformRowHeights(true); + view->setModel(model); + + const auto viewLayout = new QHBoxLayout; + viewLayout->addWidget(view); + + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addLayout(viewLayout); + + auto initModel = [this, model, updateCheckedFiles](ProjectExplorer::Project *project) { + if (!project) + return; + + auto refreshModel = [this, model, updateCheckedFiles] () { + model->clear(); + if (auto project = ProjectExplorer::SessionManager::startupProject()) { + const auto settingsDisabledFiles = project->namedSettings(m_projectSettingsKey).toStringList(); + + if (auto rootProjectNode = project->rootProjectNode()) { + rootProjectNode->forEachNode([this, settingsDisabledFiles, model](ProjectExplorer::FileNode *fileNode) { + if (fileNode->fileType() == m_fileType) { + bool isDisabled = settingsDisabledFiles.contains(fileNode->filePath().toString()); + model->rootItem()->appendChild(new ProjectFileItem(fileNode->filePath(), isDisabled)); + } + }); + } + updateCheckedFiles(); + } + }; + // deploymentDataChanged is only triggered if the active project changed, so it is not a + // problem that maybe many different targets are connected to refreshModel + this->connect(project->activeTarget(), &ProjectExplorer::Target::deploymentDataChanged, + model, refreshModel, Qt::UniqueConnection); + refreshModel(); + }; + + if (auto project = ProjectExplorer::SessionManager::startupProject()) { + initModel(project); + } + + connect(ProjectExplorer::SessionManager::instance(), + &ProjectExplorer::SessionManager::startupProjectChanged, + initModel); +} + +Utils::FilePaths ProjectFileSelectionsWidget::checkedFiles() +{ + return m_checkedFiles; +} + +} // QmlPreview diff --git a/src/plugins/qmlpreview/projectfileselectionswidget.h b/src/plugins/qmlpreview/projectfileselectionswidget.h new file mode 100644 index 0000000000..4bacb41d49 --- /dev/null +++ b/src/plugins/qmlpreview/projectfileselectionswidget.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#pragma once +#include <projectexplorer/projectnodes.h> +#include <utils/fileutils.h> + +#include <QWidget> + +namespace QmlPreview { + +class ProjectFileSelectionsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ProjectFileSelectionsWidget(const QString &projectSettingsKey, ProjectExplorer::FileType fileType, QWidget *parent = nullptr); + Utils::FilePaths checkedFiles(); +signals: + void selectionChanged(const Utils::FilePaths &selectedFiles); +private: + const QString m_projectSettingsKey; + ProjectExplorer::FileType m_fileType; + + Utils::FilePaths m_checkedFiles; +}; + +} // QmlPreview diff --git a/src/plugins/qmlpreview/qmldebugtranslationclient.cpp b/src/plugins/qmlpreview/qmldebugtranslationclient.cpp index 0ccea79300..d62d3d0d20 100644 --- a/src/plugins/qmlpreview/qmldebugtranslationclient.cpp +++ b/src/plugins/qmlpreview/qmldebugtranslationclient.cpp @@ -57,6 +57,14 @@ void QmlDebugTranslationClient::changeElidedTextWarningString(const QString &war sendMessage(packet.data()); } +void QmlDebugTranslationClient::changeElideWarning(bool elideWarning) +{ + if (elideWarning) + enableElidedTextWarning(); + else + disableElidedTextWarning(); +} + void QmlDebugTranslationClient::setDebugTranslationServiceLogFile(const QString &logFilePath) { QmlDebug::QPacket packet(dataStreamVersion()); diff --git a/src/plugins/qmlpreview/qmldebugtranslationclient.h b/src/plugins/qmlpreview/qmldebugtranslationclient.h index c27726a7eb..2b69e99b0a 100644 --- a/src/plugins/qmlpreview/qmldebugtranslationclient.h +++ b/src/plugins/qmlpreview/qmldebugtranslationclient.h @@ -50,6 +50,7 @@ public: void changeLanguage(const QUrl &url, const QString &locale); void changeWarningColor(const QColor &warningColor); void changeElidedTextWarningString(const QString &warningString); //is QByteArray better here? + void changeElideWarning(bool elideWarning); void setDebugTranslationServiceLogFile(const QString &logFilePath); void enableElidedTextWarning(); void disableElidedTextWarning(); diff --git a/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp new file mode 100644 index 0000000000..3de3eff2c2 --- /dev/null +++ b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp @@ -0,0 +1,452 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#include "qmldebugtranslationwidget.h" +#include "qmlpreviewruncontrol.h" +#include "qmlpreviewplugin.h" +#include "projectfileselectionswidget.h" + +#include <coreplugin/editormanager/editormanager.h> +#include <coreplugin/editormanager/ieditor.h> +#include <coreplugin/outputwindow.h> + +#include <projectexplorer/runcontrol.h> +#include <projectexplorer/projecttree.h> + +#include <utils/outputformatter.h> +#include <utils/utilsicons.h> +#include <utils/fileutils.h> + +#include <extensionsystem/pluginmanager.h> +#include <extensionsystem/pluginspec.h> +#include <extensionsystem/iplugin.h> + +#include <projectexplorer/projectnodes.h> +#include <projectexplorer/projectexplorer.h> +#include <projectexplorer/session.h> +#include <projectexplorer/target.h> +#include <projectexplorer/project.h> +#include <projectexplorer/task.h> +#include <projectexplorer/taskhub.h> + +#include <qmlprojectmanager/qmlmultilanguageaspect.h> + +#include <qtsupport/qtoutputformatter.h> + +#include <QIcon> +#include <QRegularExpression> + +#include <QCheckBox> +#include <QLabel> +#include <QVBoxLayout> +#include <QAction> +#include <QPushButton> +#include <QCheckBox> +#include <QLineEdit> +#include <QButtonGroup> +#include <QRadioButton> +#include <QSpacerItem> +#include <QToolButton> +#include <QTextBlock> +#include <QFileDialog> + +namespace { +QObject *getPreviewPlugin() +{ + auto pluginIt = std::find_if(ExtensionSystem::PluginManager::plugins().begin(), + ExtensionSystem::PluginManager::plugins().end(), + [](const ExtensionSystem::PluginSpec *p) { + return p->name() == "QmlPreview"; + }); + + if (pluginIt != ExtensionSystem::PluginManager::plugins().constEnd()) + return (*pluginIt)->plugin(); + + return nullptr; +} + +} + +namespace QmlPreview { + +QmlDebugTranslationWidget::QmlDebugTranslationWidget(QWidget *parent) + : QWidget(parent) +{ + auto mainLayout = new QVBoxLayout(this); + + auto buttonGroup = new QButtonGroup(this); + // it gets the text from updateCurrentEditor method + m_singleFileButton = new QRadioButton(); + m_singleFileButton->setChecked(true); + buttonGroup->addButton(m_singleFileButton); + + const QString projectSettingsKey = "QmlPreview.DisabledDebugTranslationFiles"; + const ProjectExplorer::FileType filterFileType = ProjectExplorer::FileType::QML; + auto checkableProjectFileView = new ProjectFileSelectionsWidget(projectSettingsKey, filterFileType); + checkableProjectFileView->setVisible(false); + connect(checkableProjectFileView, &ProjectFileSelectionsWidget::selectionChanged, this, &QmlDebugTranslationWidget::setFiles); + m_multipleFileButton = new QRadioButton(tr("multiple files")); + // TODO: fix multiple files issues, because it have some issues disable it for now + m_multipleFileButton->setDisabled(true); + buttonGroup->addButton(m_multipleFileButton); + connect(m_multipleFileButton, &QAbstractButton::toggled, [checkableProjectFileView, this](bool checked) { + checkableProjectFileView->setVisible(checked); + setFiles(checkableProjectFileView->checkedFiles()); + }); + + mainLayout->addWidget(m_singleFileButton); + mainLayout->addWidget(m_multipleFileButton); + mainLayout->addWidget(checkableProjectFileView); + + // language checkboxes are add in updateAvailableTranslations method + m_selectLanguageLayout = new QHBoxLayout; + mainLayout->addLayout(m_selectLanguageLayout); + + auto elideWarningCheckBox = new QCheckBox(tr("Enable elide warning")); + layout()->addWidget(elideWarningCheckBox); + connect(elideWarningCheckBox, &QCheckBox::stateChanged, [this] (int state) { + m_elideWarning = (state == Qt::Checked); + }); + + auto controlLayout = new QHBoxLayout; + mainLayout->addLayout(controlLayout); + + auto showLogButton = new QToolButton; + showLogButton->setText(tr("Show log")); + showLogButton->setCheckable(true); + controlLayout->addWidget(showLogButton); + + // TODO: do we still need this buttons? +// auto pauseButton = new QToolButton; +// pauseButton->setText(tr("Pause")); +// pauseButton->setCheckable(true); +// controlLayout->addWidget(pauseButton); + +// auto onTheFlyButton = new QToolButton; +// onTheFlyButton->setText(tr("On the fly")); +// controlLayout->addWidget(onTheFlyButton); + + m_runTestButton = new QPushButton(); + m_runTestButton->setCheckable(true); + m_runTestButton->setText(runButtonText()); + connect(m_runTestButton, &QPushButton::toggled, [this](bool checked) { + m_runTestButton->setText(runButtonText(checked)); + }); + + connect(m_runTestButton, &QPushButton::clicked, [this](bool checked) { + if (checked) + runTest(); + else { + if (m_currentRunControl) + m_currentRunControl->initiateStop(); + // TODO: what happens if we already have a preview running? +// QmlPreviewPlugin::stopAllRunControls(); +// qWarning() << "not implemented"; // TODO: stop still running tests + } + }); + controlLayout->addWidget(m_runTestButton); + + m_runOutputWindow = new Core::OutputWindow(Core::Context("QmlPreview.DebugTranslation"), + "QmlPreview/OutputWindow/Zoom"); + + m_runOutputWindow->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_runOutputWindow->setReadOnly(true); + m_runOutputWindow->setVisible(false); + mainLayout->addWidget(m_runOutputWindow); + + QSpacerItem *endSpacerItem = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding); + mainLayout->addItem(endSpacerItem); + + connect(showLogButton, &QToolButton::toggled, m_runOutputWindow, [this, mainLayout, endSpacerItem](bool checked) { + m_runOutputWindow->setVisible(checked); + if (m_runOutputWindow->isVisible()) + mainLayout->takeAt(mainLayout->count() - 1); + else + mainLayout->addItem(endSpacerItem); + }); + + auto loadLogButton = new QToolButton; + loadLogButton->setText(tr("Load")); + controlLayout->addWidget(loadLogButton); + connect(loadLogButton, &QToolButton::clicked, this, &QmlDebugTranslationWidget::loadLogFile); + + auto saveLogButton = new QToolButton; + saveLogButton->setText(tr("Save")); + controlLayout->addWidget(saveLogButton); + connect(saveLogButton, &QToolButton::clicked, this, &QmlDebugTranslationWidget::saveLogToFile); + + auto clearButton = new QToolButton; + clearButton->setText(tr("Clear")); + controlLayout->addWidget(clearButton); + connect(clearButton, &QToolButton::clicked, this, &QmlDebugTranslationWidget::clear); + + Core::EditorManager *editorManager = Core::EditorManager::instance(); + connect(editorManager, &Core::EditorManager::currentEditorChanged, this, &QmlDebugTranslationWidget::updateCurrentEditor); + updateCurrentEditor(Core::EditorManager::currentEditor()); + + connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::startupProjectChanged, + this, &QmlDebugTranslationWidget::updateCurrentTranslations); + + updateStartupProjectTranslations(); + + ProjectExplorer::TaskHub::addCategory("QmlPreview.Translation", tr("Translation issues")); +} + +QmlDebugTranslationWidget::~QmlDebugTranslationWidget() +{ + +} + +void QmlDebugTranslationWidget::updateCurrentEditor(const Core::IEditor *editor) +{ + if (editor && editor->document()) + m_currentFilePath = editor->document()->filePath(); + else + m_currentFilePath.clear(); + m_singleFileButton->setText(singleFileButtonText(m_currentFilePath.toString())); + +} + +void QmlDebugTranslationWidget::updateStartupProjectTranslations() +{ + updateCurrentTranslations(ProjectExplorer::SessionManager::startupProject()); +} + +void QmlDebugTranslationWidget::updateCurrentTranslations(ProjectExplorer::Project *project) +{ + for (int i = m_selectLanguageLayout->count()-1; i >= 0; --i) { + auto layoutItem = m_selectLanguageLayout->takeAt(i); + delete layoutItem->widget(); + delete layoutItem; + } + if (!project) + return; + + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(project)) { + connect(multiLanguageAspect, &QmlProjectManager::QmlMultiLanguageAspect::changed, + this, &QmlDebugTranslationWidget::updateStartupProjectTranslations, + Qt::UniqueConnection); + if (multiLanguageAspect->value()) { + m_selectLanguageLayout->addWidget(new QLabel( + tr("Current language is \'<b>%1</b>\' can be changed in the 'Translation' tab.") + .arg(multiLanguageAspect->currentLocale()))); + m_testLanguages.clear(); + } else { + m_selectLanguageLayout->addWidget(new QLabel(tr("Select which language should be tested:"))); + QString errorMessage; + for (auto language : project->availableQmlPreviewTranslations(&errorMessage)) { + auto languageCheckBox = new QCheckBox(language); + m_selectLanguageLayout->addWidget(languageCheckBox); + connect(languageCheckBox, &QCheckBox::stateChanged, [this, language] (int state) { + if (state == Qt::Checked) + m_testLanguages.append(language); + else + m_testLanguages.removeAll(language); + }); + languageCheckBox->setChecked(true); + } + m_selectLanguageLayout->addItem(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); + } + } +} + +void QmlDebugTranslationWidget::setFiles(const Utils::FilePaths &filePathes) +{ + m_selectedFilePaths = filePathes; +} + +void QmlDebugTranslationWidget::runTest() +{ + m_runOutputWindow->grayOutOldContent(); + + auto runControl = new ProjectExplorer::RunControl(ProjectExplorer::Constants::QML_PREVIEW_RUN_MODE); + QTC_ASSERT(runControl, qWarning("Can not create a QmlPreviewRunner"); return;); + auto previewPlugin = qobject_cast<Internal::QmlPreviewPlugin*>(getPreviewPlugin()); + + connect(runControl, &ProjectExplorer::RunControl::started, [this, runControl, previewPlugin]() { + //Q_ASSERT(m_currentRunControl == nullptr); //TODO: who deletes the runcontrol + m_currentRunControl = runControl; + m_runOutputWindow->setLineParsers( + ProjectExplorer::OutputFormatterFactory::createFormatters(runControl->target())); + int timerCounter = 1; + const auto testLanguageList = m_testLanguages; + + auto testLanguages = [previewPlugin, runControl, testLanguageList](int timerCounter, const QString &previewedFile) { + qDebug() << "testLanguages" << previewedFile; + for (auto language : testLanguageList) { + QTimer::singleShot(timerCounter * 1000, previewPlugin, [previewPlugin, runControl, language, previewedFile]() { + if (runControl && runControl->isRunning()) { + if (!previewedFile.isEmpty()) + previewPlugin->setPreviewedFile(previewedFile); + previewPlugin->setLocale(language); + } + }); + } + }; + if (m_multipleFileButton->isChecked()) { + for (auto filePath : m_selectedFilePaths) { + testLanguages(timerCounter++, filePath.toString()); + } + } else { + testLanguages(timerCounter, QString()); + } + + }); + connect(runControl, &ProjectExplorer::RunControl::stopped, [this]() { + m_runTestButton->setChecked(false); + //delete m_currentRunControl; // who deletes the runcontrol? + m_currentRunControl = nullptr; + if (auto previewPlugin = qobject_cast<Internal::QmlPreviewPlugin*>(getPreviewPlugin())) + previewPlugin->setLocale(m_lastUsedLanguageBeforeTest); + }); + + connect(runControl, &ProjectExplorer::RunControl::appendMessage, + this, &QmlDebugTranslationWidget::appendMessage); + + if (auto project = ProjectExplorer::SessionManager::startupProject()) { + if (auto target = project->activeTarget()) { + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(target)) + m_lastUsedLanguageBeforeTest = multiLanguageAspect->currentLocale(); + if (auto runConfiguration = target->activeRunConfiguration()) { + runControl->setRunConfiguration(runConfiguration); + if (runControl->createMainWorker()) { + previewPlugin->setLocale(QString()); + runControl->initiateStart(); + } + } + } + } +} + +void QmlDebugTranslationWidget::clear() +{ + m_runOutputWindow->clear(); + ProjectExplorer::TaskHub::clearTasks("QmlPreview.Translation"); +} + +QString QmlDebugTranslationWidget::currentDir() const +{ + return m_lastDir.isEmpty() ? + ProjectExplorer::ProjectTree::currentFilePath().parentDir().toString() : m_lastDir; +} + +void QmlDebugTranslationWidget::setCurrentDir(const QString &path) +{ + m_lastDir = path; + const QString currentDir = m_lastDir.isEmpty() ? + ProjectExplorer::ProjectTree::currentFilePath().parentDir().toString() : m_lastDir; +} + +void QmlDebugTranslationWidget::loadLogFile() +{ + const auto fileName = QFileDialog::getOpenFileName(this, QStringLiteral("Open File"), currentDir()); + if (!fileName.isEmpty()) { + setCurrentDir(QFileInfo(fileName).absolutePath()); + QFile f(fileName); + if (f.open(QFile::ReadOnly)) { + clear(); + while (!f.atEnd()) + appendMessage(QString::fromUtf8(f.readLine()), Utils::DebugFormat); + } else { + // TODO: maybe add this message to log and tasks + qWarning() << "Failed to open" << fileName << ":" << f.errorString(); + } + } +} + +void QmlDebugTranslationWidget::saveLogToFile() +{ + const QString fileName = QFileDialog::getSaveFileName( + this, tr("Choose file to save logged issues."), currentDir()); + if (!fileName.isEmpty()) { + setCurrentDir(QFileInfo(fileName).absolutePath()); + QFile f(fileName); + if (f.open(QFile::WriteOnly | QFile::Text)) + f.write(m_runOutputWindow->toPlainText().toUtf8()); + } +} + +void QmlDebugTranslationWidget::appendMessage(const QString &message, Utils::OutputFormat format) +{ + const auto newLine = QRegularExpression("[\r\n]"); + const auto messages = message.split(newLine, QString::SkipEmptyParts); + + if (messages.count() > 1) { + for (auto m : messages) + appendMessage(m + "\n", format); + return; + } + const QString serviceSeperator = ": QQmlDebugTranslationService: "; + if (!message.contains(serviceSeperator) || message.contains("DebugTranslation service - language changed")) + return; + QString locationString = message; + locationString = locationString.split(serviceSeperator).first(); + static const QRegularExpression qmlLineColumnLink("^(" QT_QML_URL_REGEXP ")" // url + ":(\\d+)" // line + ":(\\d+)$"); // column + const QRegularExpressionMatch qmlLineColumnMatch = qmlLineColumnLink.match(locationString); + + auto fileLine = -1; + QUrl fileUrl; + if (qmlLineColumnMatch.hasMatch()) { + fileUrl = QUrl(qmlLineColumnMatch.captured(1)); + fileLine = qmlLineColumnMatch.captured(2).toInt(); + } + + m_runOutputWindow->appendMessage(message, format); + + + auto type = ProjectExplorer::Task::TaskType::Warning; + auto description = message.split(serviceSeperator).at(1); + auto filePath = Utils::FilePath::fromString(fileUrl.toLocalFile()); + auto category = "QmlPreview.Translation"; + auto icon = Utils::Icons::WARNING.icon(); + + ProjectExplorer::TaskHub::addTask(ProjectExplorer::Task(type, + description, + filePath, + fileLine, + category, + icon, + ProjectExplorer::Task::NoOptions)); +} + +QString QmlDebugTranslationWidget::singleFileButtonText(const QString &filePath) +{ + auto buttonText = tr("current file: %1"); + if (filePath.isEmpty()) + return buttonText.arg(tr("empty")); + return buttonText.arg(filePath); +} + +QString QmlDebugTranslationWidget::runButtonText(bool isRunning) +{ + if (isRunning) { + return tr("Stop"); + } + return tr("Run language tests"); +} + +} // namespace QmlPreview diff --git a/src/plugins/qmlpreview/qmldebugtranslationwidget.h b/src/plugins/qmlpreview/qmldebugtranslationwidget.h new file mode 100644 index 0000000000..ef9deb14e9 --- /dev/null +++ b/src/plugins/qmlpreview/qmldebugtranslationwidget.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#pragma once + +#include "qmlpreview_global.h" + +#include <utils/fileutils.h> +#include <utils/outputformat.h> + +#include <QWidget> + +QT_BEGIN_NAMESPACE +class QRadioButton; +class QPushButton; +class QHBoxLayout; +QT_END_NAMESPACE + +namespace Core { +class IEditor; +class OutputWindow; +} +namespace ProjectExplorer { +class Project; +class RunControl; +} + +namespace QmlPreview { + +class QMLPREVIEW_EXPORT QmlDebugTranslationWidget : public QWidget +{ + Q_OBJECT +public: + explicit QmlDebugTranslationWidget(QWidget *parent = nullptr); + ~QmlDebugTranslationWidget() override; + + void setCurrentFile(const Utils::FilePath &filepath); + void setFiles(const Utils::FilePaths &filePathes); + void updateStartupProjectTranslations(); +private: + void updateCurrentEditor(const Core::IEditor *editor); + void updateCurrentTranslations(ProjectExplorer::Project *project); + void runTest(); + void appendMessage(const QString &message, Utils::OutputFormat format); + void clear(); + void loadLogFile(); + void saveLogToFile(); + QString currentDir() const; + void setCurrentDir(const QString &path); + + QString singleFileButtonText(const QString &filePath); + QString runButtonText(bool isRunning = false); + + QStringList m_testLanguages; + QString m_lastUsedLanguageBeforeTest; + bool m_elideWarning = false; + + Core::OutputWindow *m_runOutputWindow = nullptr; + + QRadioButton *m_singleFileButton = nullptr; + QRadioButton *m_multipleFileButton = nullptr; + QPushButton *m_runTestButton = nullptr; + + Utils::FilePath m_currentFilePath; + Utils::FilePaths m_selectedFilePaths; + ProjectExplorer::RunControl *m_currentRunControl = nullptr; + + QString m_lastDir; + + QHBoxLayout *m_selectLanguageLayout; +}; + +} // namespace QmlPreview diff --git a/src/plugins/qmlpreview/qmlpreview.pro b/src/plugins/qmlpreview/qmlpreview.pro index cfc571521f..ea53ac2f4e 100644 --- a/src/plugins/qmlpreview/qmlpreview.pro +++ b/src/plugins/qmlpreview/qmlpreview.pro @@ -10,19 +10,23 @@ include(tests/tests.pri) HEADERS += \ qmlpreview_global.h \ qmldebugtranslationclient.h \ + qmldebugtranslationwidget.h \ qmlpreviewclient.h \ qmlpreviewplugin.h \ qmlpreviewruncontrol.h \ qmlpreviewconnectionmanager.h \ - qmlpreviewfileontargetfinder.h + qmlpreviewfileontargetfinder.h \ + projectfileselectionswidget.h SOURCES += \ qmlpreviewplugin.cpp \ qmldebugtranslationclient.cpp \ + qmldebugtranslationwidget.cpp \ qmlpreviewclient.cpp \ qmlpreviewruncontrol.cpp \ qmlpreviewconnectionmanager.cpp \ - qmlpreviewfileontargetfinder.cpp + qmlpreviewfileontargetfinder.cpp \ + projectfileselectionswidget.cpp OTHER_FILES += \ QmlPreview.json.in diff --git a/src/plugins/qmlpreview/qmlpreview.qbs b/src/plugins/qmlpreview/qmlpreview.qbs index 08e6472578..6773487962 100644 --- a/src/plugins/qmlpreview/qmlpreview.qbs +++ b/src/plugins/qmlpreview/qmlpreview.qbs @@ -26,6 +26,8 @@ QtcPlugin { "qmlpreviewclient.h", "qmldebugtranslationclient.cpp", "qmldebugtranslationclient.h", + "qmldebugtranslationwidget.cpp", + "qmldebugtranslationwidget.h", "qmlpreviewconnectionmanager.cpp", "qmlpreviewconnectionmanager.h", "qmlpreviewfileontargetfinder.cpp", @@ -35,6 +37,8 @@ QtcPlugin { "qmlpreviewplugin.h", "qmlpreviewruncontrol.cpp", "qmlpreviewruncontrol.h", + "projectfileselectionswidget.cpp", + "projectfileselectionswidget.h" ] } diff --git a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp index 0fcf6a36b8..7da4bdea13 100644 --- a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp +++ b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.cpp @@ -76,17 +76,22 @@ void QmlPreviewConnectionManager::createClients() QUrl QmlPreviewConnectionManager::findValidI18nDirectoryAsUrl(const QString &locale) { + QTC_ASSERT(!m_lastLoadedUrl.isEmpty(), return {};); + const QString shortLocale = locale.left(locale.indexOf("_")); QString path = m_lastLoadedUrl.path(); + QString foundPath; while (!path.isEmpty()) { path = path.left(qMax(0, path.lastIndexOf("/"))); QUrl url = m_lastLoadedUrl; + auto tryPath = [&](const QString &postfix) { url.setPath(path + "/i18n/qml_" + postfix); bool success = false; - m_projectFileFinder.findFile(url, &success); + foundPath = m_projectFileFinder.findFile(url, &success).first().toString(); + foundPath = foundPath.left(qMax(0, foundPath.lastIndexOf("/i18n"))); return success; }; @@ -101,21 +106,32 @@ QUrl QmlPreviewConnectionManager::findValidI18nDirectoryAsUrl(const QString &loc } QUrl url = m_lastLoadedUrl; - url.setPath(path); + if (foundPath.isEmpty()) + url.setPath(path); + else + url.setPath(foundPath); return url; } void QmlPreviewConnectionManager::createDebugTranslationClient() { m_qmlDebugTranslationClient = new QmlDebugTranslationClient(connection()); - QObject::connect(this, &QmlPreviewConnectionManager::language, + connect(this, &QmlPreviewConnectionManager::language, m_qmlDebugTranslationClient.data(), [this](const QString &locale) { - // service expects a context URL. - // Search the parent directories of the last loaded URL for i18n files. - m_qmlDebugTranslationClient->changeLanguage(findValidI18nDirectoryAsUrl(locale), locale); + if (m_lastLoadedUrl.isEmpty()) { + // findValidI18nDirectoryAsUrl does not work if we didn't load any file + m_initLocale = locale; + } else { + // service expects a context URL. + // Search the parent directories of the last loaded URL for i18n files. + m_qmlDebugTranslationClient->changeLanguage(findValidI18nDirectoryAsUrl(locale), locale); + } }); - QObject::connect(m_qmlDebugTranslationClient.data(), &QmlDebugTranslationClient::debugServiceUnavailable, + connect(this, &QmlPreviewConnectionManager::changeElideWarning, + m_qmlDebugTranslationClient, &QmlDebugTranslationClient::changeElideWarning); + + connect(m_qmlDebugTranslationClient.data(), &QmlDebugTranslationClient::debugServiceUnavailable, this, []() { QMessageBox::warning(Core::ICore::dialogParent(), "Error connect to QML DebugTranslation service", "QML DebugTranslation feature is not available for this version of Qt."); @@ -126,8 +142,7 @@ void QmlPreviewConnectionManager::createPreviewClient() { m_qmlPreviewClient = new QmlPreviewClient(connection()); - QObject::connect( - this, &QmlPreviewConnectionManager::loadFile, m_qmlPreviewClient.data(), + connect(this, &QmlPreviewConnectionManager::loadFile, m_qmlPreviewClient.data(), [this](const QString &filename, const QString &changedFile, const QByteArray &contents) { if (!m_fileClassifier(changedFile)) { @@ -144,23 +159,32 @@ void QmlPreviewConnectionManager::createPreviewClient() m_lastLoadedUrl = m_targetFileFinder.findUrl(filename); m_qmlPreviewClient->loadUrl(m_lastLoadedUrl); + if (!m_initLocale.isEmpty()) { + emit language(m_initLocale); + m_initLocale.clear(); + } }); - QObject::connect(this, &QmlPreviewConnectionManager::rerun, + connect(this, &QmlPreviewConnectionManager::rerun, m_qmlPreviewClient.data(), &QmlPreviewClient::rerun); - QObject::connect(this, &QmlPreviewConnectionManager::zoom, + connect(this, &QmlPreviewConnectionManager::zoom, m_qmlPreviewClient.data(), &QmlPreviewClient::zoom); - QObject::connect(this, &QmlPreviewConnectionManager::language, + connect(this, &QmlPreviewConnectionManager::language, m_qmlPreviewClient.data(), [this](const QString &locale) { - // service expects a context URL. - // Search the parent directories of the last loaded URL for i18n files. - m_qmlPreviewClient->language(findValidI18nDirectoryAsUrl(locale), locale); + if (m_lastLoadedUrl.isEmpty()) { + // findValidI18nDirectoryAsUrl does not work if we didn't load any file + m_initLocale = locale; + } else { + // service expects a context URL. + // Search the parent directories of the last loaded URL for i18n files. + m_qmlPreviewClient->language(findValidI18nDirectoryAsUrl(locale), locale); + } }); - QObject::connect(m_qmlPreviewClient.data(), &QmlPreviewClient::pathRequested, + connect(m_qmlPreviewClient.data(), &QmlPreviewClient::pathRequested, this, [this](const QString &path) { const bool found = m_projectFileFinder.findFileOrDirectory( path, [&](const QString &filename, int confidence) { @@ -190,13 +214,13 @@ void QmlPreviewConnectionManager::createPreviewClient() m_qmlPreviewClient->announceError(path); }); - QObject::connect(m_qmlPreviewClient.data(), &QmlPreviewClient::errorReported, + connect(m_qmlPreviewClient.data(), &QmlPreviewClient::errorReported, this, [](const QString &error) { Core::MessageManager::write("Error loading QML Live Preview:"); Core::MessageManager::write(error); }); - QObject::connect(m_qmlPreviewClient.data(), &QmlPreviewClient::fpsReported, + connect(m_qmlPreviewClient.data(), &QmlPreviewClient::fpsReported, this, [this](const QmlPreviewClient::FpsInfo &frames) { if (m_fpsHandler) { quint16 stats[] = { @@ -207,13 +231,13 @@ void QmlPreviewConnectionManager::createPreviewClient() } }); - QObject::connect(m_qmlPreviewClient.data(), &QmlPreviewClient::debugServiceUnavailable, + connect(m_qmlPreviewClient.data(), &QmlPreviewClient::debugServiceUnavailable, this, []() { QMessageBox::warning(Core::ICore::dialogParent(), "Error loading QML Live Preview", "QML Live Preview is not available for this version of Qt."); }, Qt::QueuedConnection); // Queue it, so that it interfere with the connection timer - QObject::connect(&m_fileSystemWatcher, &Utils::FileSystemWatcher::fileChanged, + connect(&m_fileSystemWatcher, &Utils::FileSystemWatcher::fileChanged, m_qmlPreviewClient.data(), [this](const QString &changedFile) { if (!m_fileLoader || !m_lastLoadedUrl.isValid()) return; diff --git a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h index 7d87ca79f3..7693eda695 100644 --- a/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h +++ b/src/plugins/qmlpreview/qmlpreviewconnectionmanager.h @@ -57,6 +57,7 @@ signals: void loadFile(const QString &filename, const QString &changedFile, const QByteArray &contents); void zoom(float zoomFactor); void language(const QString &locale); + void changeElideWarning(bool elideWarning); void rerun(); void restart(); @@ -78,6 +79,7 @@ private: QmlPreviewFileLoader m_fileLoader = nullptr; QmlPreviewFileClassifier m_fileClassifier = nullptr; QmlPreviewFpsHandler m_fpsHandler = nullptr; + QString m_initLocale; }; } // namespace Internal diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.cpp b/src/plugins/qmlpreview/qmlpreviewplugin.cpp index 6c92c12b0a..ffae9a1815 100644 --- a/src/plugins/qmlpreview/qmlpreviewplugin.cpp +++ b/src/plugins/qmlpreview/qmlpreviewplugin.cpp @@ -26,11 +26,14 @@ #include "qmlpreviewplugin.h" #include "qmlpreviewruncontrol.h" +#include "qmldebugtranslationwidget.h" + #ifdef WITH_TESTS #include "tests/qmlpreviewclient_test.h" #include "tests/qmlpreviewplugin_test.h" #endif +#include <coreplugin/icore.h> #include <coreplugin/actionmanager/actionmanager.h> #include <coreplugin/actionmanager/actioncontainer.h> #include <coreplugin/editormanager/editormanager.h> @@ -53,6 +56,12 @@ #include <qmljs/qmljsmodelmanagerinterface.h> #include <qmljstools/qmljstoolsconstants.h> +#include <qmlprojectmanager/qmlmultilanguageaspect.h> + +#include <qtsupport/qtkitinformation.h> +#include <qtsupport/qtversionmanager.h> +#include <qtsupport/baseqtversion.h> + #include <QAction> using namespace ProjectExplorer; @@ -142,6 +151,8 @@ public: float m_zoomFactor = -1.0; QmlPreview::QmlPreviewFpsHandler m_fpsHandler = nullptr; QString m_locale; + bool elideWarning = false; + QPointer<QmlDebugTranslationWidget> m_qmlDebugTranslationWidget; RunWorkerFactory localRunWorkerFactory{ RunWorkerFactory::make<LocalQmlPreviewSupport>(), @@ -153,7 +164,7 @@ public: RunWorkerFactory runWorkerFactory{ [this](RunControl *runControl) { QmlPreviewRunner *runner = new QmlPreviewRunner(runControl, m_fileLoader, m_fileClassifer, - m_fpsHandler, m_zoomFactor, m_locale); + m_fpsHandler, m_zoomFactor); connect(q, &QmlPreviewPlugin::updatePreviews, runner, &QmlPreviewRunner::loadFile); connect(q, &QmlPreviewPlugin::rerunPreviews, @@ -164,6 +175,8 @@ public: runner, &QmlPreviewRunner::zoom); connect(q, &QmlPreviewPlugin::localeChanged, runner, &QmlPreviewRunner::language); + connect(q, &QmlPreviewPlugin::elideWarningChanged, + runner, &QmlPreviewRunner::changeElideWarning); connect(runner, &RunWorker::started, this, [this, runControl] { addPreview(runControl); @@ -192,13 +205,60 @@ QmlPreviewPluginPrivate::QmlPreviewPluginPrivate(QmlPreviewPlugin *parent) action->setEnabled(SessionManager::startupProject() != nullptr); connect(SessionManager::instance(), &SessionManager::startupProjectChanged, action, &QAction::setEnabled); - connect(action, &QAction::triggered, this, []() { + connect(action, &QAction::triggered, this, [this]() { + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current()) + m_locale = multiLanguageAspect->currentLocale(); + ProjectExplorerPlugin::runStartupProject(Constants::QML_PREVIEW_RUN_MODE); }); - menu->addAction(Core::ActionManager::registerAction(action, "QmlPreview.Internal"), - Constants::G_BUILD_RUN); + menu->addAction( + Core::ActionManager::registerAction(action, "QmlPreview.RunPreview"), + Constants::G_BUILD_RUN); + + action = new QAction(QmlPreviewPlugin::tr("Test translations"), this); + action->setToolTip(QLatin1String("Runs the preview with all available translations and collects all issues.")); + action->setEnabled(SessionManager::startupProject() != nullptr); + connect(SessionManager::instance(), &SessionManager::startupProjectChanged, action, + &QAction::setEnabled); + connect(action, &QAction::triggered, this, [this]() { + if (SessionManager::startupProject()) { + // Deletion for this widget is taken care of in aboutToShutdown() and registerWindow() + m_qmlDebugTranslationWidget = new QmlDebugTranslationWidget(); + Core::ICore::registerWindow(m_qmlDebugTranslationWidget, Core::Context("Core.DebugTranslation")); + m_qmlDebugTranslationWidget->show(); + } + }); + menu->addAction( + Core::ActionManager::registerAction(action, "QmlPreview.TestTranslations"), + Constants::G_BUILD_RUN); + auto updateTestTranslationAction = [action]() { + bool showTestTranslationAction = false; + bool enableTestTranslationAction = false; + QtSupport::BaseQtVersion *activeQt{}; + if (auto project = SessionManager::startupProject()) { + if (auto target = project->activeTarget()) { + if (auto activeKit = target->kit()) + activeQt = QtSupport::QtKitAspect::qtVersion(activeKit); + } + } + for (auto qtVersion : QtSupport::QtVersionManager::versions()) { + if (qtVersion->features().contains("QtStudio")) { + showTestTranslationAction = true; + if (qtVersion == activeQt) + enableTestTranslationAction = true; + } + } + action->setVisible(showTestTranslationAction); + action->setEnabled(enableTestTranslationAction); + }; + connect(ProjectExplorer::SessionManager::instance(), + &ProjectExplorer::SessionManager::startupProjectChanged, + updateTestTranslationAction); + + connect(QtSupport::QtVersionManager::instance(), + &QtSupport::QtVersionManager::qtVersionsChanged, + updateTestTranslationAction); - Core::Context projectTreeContext(Constants::C_PROJECT_TREE); menu = Core::ActionManager::actionContainer(Constants::M_FILECONTEXT); action = new QAction(QmlPreviewPlugin::tr("Preview File"), this); action->setEnabled(false); @@ -207,9 +267,9 @@ QmlPreviewPluginPrivate::QmlPreviewPluginPrivate(QmlPreviewPlugin *parent) action->setEnabled(!previews.isEmpty()); }); connect(action, &QAction::triggered, this, &QmlPreviewPluginPrivate::previewCurrentFile); - menu->addAction(Core::ActionManager::registerAction(action, "QmlPreview.Preview", - projectTreeContext), - Constants::G_FILE_OTHER); + menu->addAction( + Core::ActionManager::registerAction(action, "QmlPreview.PreviewFile", Core::Context(Constants::C_PROJECT_TREE)), + Constants::G_FILE_OTHER); action->setVisible(false); connect(ProjectTree::instance(), &ProjectTree::currentNodeChanged, action, [action]() { const Node *node = ProjectTree::currentNode(); @@ -247,6 +307,7 @@ ExtensionSystem::IPlugin::ShutdownFlag QmlPreviewPlugin::aboutToShutdown() { d->m_parseThread.quit(); d->m_parseThread.wait(); + delete d->m_qmlDebugTranslationWidget; return SynchronousShutdown; } @@ -333,6 +394,8 @@ QString QmlPreviewPlugin::locale() const void QmlPreviewPlugin::setLocale(const QString &locale) { + if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current()) + multiLanguageAspect->setCurrentLocale(locale); if (d->m_locale == locale) return; @@ -340,6 +403,16 @@ void QmlPreviewPlugin::setLocale(const QString &locale) emit localeChanged(d->m_locale); } +bool QmlPreviewPlugin::elideWarning() const +{ + return d->elideWarning; +} + +void QmlPreviewPlugin::changeElideWarning(bool elideWarning) +{ + d->elideWarning = elideWarning; +} + void QmlPreviewPlugin::setFileLoader(QmlPreviewFileLoader fileLoader) { if (d->m_fileLoader == fileLoader) @@ -412,6 +485,10 @@ void QmlPreviewPluginPrivate::setDirty() void QmlPreviewPluginPrivate::addPreview(ProjectExplorer::RunControl *preview) { m_runningPreviews.append(preview); + if (auto multiLanguageAspect = preview->aspect<QmlProjectManager::QmlMultiLanguageAspect>()) { + connect(multiLanguageAspect, &QmlProjectManager::QmlMultiLanguageAspect::changed, + preview, &ProjectExplorer::RunControl::initiateStop); + } emit q->runningPreviewsChanged(m_runningPreviews); } diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.h b/src/plugins/qmlpreview/qmlpreviewplugin.h index df0a59f2b8..13146105ac 100644 --- a/src/plugins/qmlpreview/qmlpreviewplugin.h +++ b/src/plugins/qmlpreview/qmlpreviewplugin.h @@ -59,6 +59,7 @@ class QmlPreviewPlugin : public ExtensionSystem::IPlugin WRITE setFpsHandler NOTIFY fpsHandlerChanged) Q_PROPERTY(float zoomFactor READ zoomFactor WRITE setZoomFactor NOTIFY zoomFactorChanged) Q_PROPERTY(QString locale READ locale WRITE setLocale NOTIFY localeChanged) + Q_PROPERTY(bool elideWarning READ elideWarning WRITE changeElideWarning NOTIFY elideWarningChanged) public: ~QmlPreviewPlugin() override; @@ -86,6 +87,9 @@ public: QString locale() const; void setLocale(const QString &locale); + bool elideWarning() const; + void changeElideWarning(bool elideWarning); + signals: void checkDocument(const QString &name, const QByteArray &contents, QmlJS::Dialect::Enum dialect); @@ -100,6 +104,7 @@ signals: void zoomFactorChanged(float zoomFactor); void localeChanged(const QString &locale); + void elideWarningChanged(bool elideWarning); private: class QmlPreviewPluginPrivate *d = nullptr; diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp index f1caffa1a7..518e33794f 100644 --- a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp +++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp @@ -27,7 +27,6 @@ #include <qmlprojectmanager/qmlproject.h> #include <qmlprojectmanager/qmlmainfileaspect.h> -#include <qmlprojectmanager/qmlmultilanguageaspect.h> #include <projectexplorer/projectexplorer.h> #include <projectexplorer/session.h> @@ -50,8 +49,7 @@ QmlPreviewRunner::QmlPreviewRunner(ProjectExplorer::RunControl *runControl, QmlPreviewFileLoader fileLoader, QmlPreviewFileClassifier fileClassifier, QmlPreviewFpsHandler fpsHandler, - float initialZoom, - const QString &initialLocale) + float initialZoom) : RunWorker(runControl) { setId("QmlPreviewRunner"); @@ -68,12 +66,13 @@ QmlPreviewRunner::QmlPreviewRunner(ProjectExplorer::RunControl *runControl, &m_connectionManager, &Internal::QmlPreviewConnectionManager::zoom); connect(this, &QmlPreviewRunner::language, &m_connectionManager, &Internal::QmlPreviewConnectionManager::language); + connect(this, &QmlPreviewRunner::changeElideWarning, + &m_connectionManager, &Internal::QmlPreviewConnectionManager::changeElideWarning); + connect(&m_connectionManager, &Internal::QmlPreviewConnectionManager::connectionOpened, - this, [this, initialZoom, initialLocale]() { + this, [this, initialZoom]() { if (initialZoom > 0) emit zoom(initialZoom); - if (!initialLocale.isEmpty()) - emit language(initialLocale); emit ready(); }); @@ -153,11 +152,6 @@ LocalQmlPreviewSupport::LocalQmlPreviewSupport(ProjectExplorer::RunControl *runC } } - if (auto multiLanguageAspect = runControl->aspect<QmlProjectManager::QmlMultiLanguageAspect>()) { - if (!multiLanguageAspect->databaseFilePath().isEmpty()) - runnable.environment.set("QT_MULTILANGUAGE_DATABASE", multiLanguageAspect->databaseFilePath().toString()); - } - Utils::QtcProcess::addArg(&runnable.commandLineArguments, QmlDebug::qmlDebugLocalArguments(QmlDebug::QmlPreviewServices, serverUrl.path())); diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.h b/src/plugins/qmlpreview/qmlpreviewruncontrol.h index 7a25a62c10..38740b31a3 100644 --- a/src/plugins/qmlpreview/qmlpreviewruncontrol.h +++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.h @@ -39,7 +39,7 @@ class QmlPreviewRunner : public ProjectExplorer::RunWorker public: QmlPreviewRunner(ProjectExplorer::RunControl *runControl, QmlPreviewFileLoader fileLoader, QmlPreviewFileClassifier fileClassifier, QmlPreviewFpsHandler fpsHandler, - float initialZoom, const QString &initialLocale); + float initialZoom); void setServerUrl(const QUrl &serverUrl); QUrl serverUrl() const; @@ -51,7 +51,7 @@ signals: void zoom(float zoomFactor); void rerun(); void ready(); - + void changeElideWarning(bool elideWarning); private: void start() override; void stop() override; diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp index 64dd8a93d6..1e5e88712e 100644 --- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp +++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp @@ -30,6 +30,7 @@ #include <projectexplorer/project.h> #include <projectexplorer/projectexplorer.h> +#include <projectexplorer/session.h> #include <projectexplorer/target.h> static bool isMultilanguagePresent() @@ -46,7 +47,8 @@ static bool isMultilanguagePresent() static Utils::FilePath getMultilanguageDatabaseFilePath(ProjectExplorer::Target *target) { if (target) { - auto filePath = target->project()->projectDirectory().pathAppended("/multilanguage-experimental-v1.db"); + auto filePath = target->project()->projectDirectory().pathAppended( + "multilanguage-experimental-v2.db"); if (filePath.exists()) return filePath; } @@ -81,28 +83,24 @@ QmlMultiLanguageAspect::QmlMultiLanguageAspect(ProjectExplorer::Target *target) setDefaultValue(!databaseFilePath().isEmpty()); QVariantMap getDefaultValues; fromMap(getDefaultValues); - - if (auto previewPlugin = getPreviewPlugin()) - connect(previewPlugin, SIGNAL(localeChanged(QString)), this, SLOT(setLastUsedLanguage(QString))); } QmlMultiLanguageAspect::~QmlMultiLanguageAspect() { } -void QmlMultiLanguageAspect::setLastUsedLanguage(const QString &language) +void QmlMultiLanguageAspect::setCurrentLocale(const QString &locale) { + if (m_currentLocale == locale) + return; + m_currentLocale = locale; if (auto previewPlugin = getPreviewPlugin()) - previewPlugin->setProperty("locale", language); - if (m_lastUsedLanguage != language) { - m_lastUsedLanguage = language; - emit changed(); - } + previewPlugin->setProperty("locale", locale); } -QString QmlMultiLanguageAspect::lastUsedLanguage() const +QString QmlMultiLanguageAspect::currentLocale() const { - return m_lastUsedLanguage; + return m_currentLocale; } Utils::FilePath QmlMultiLanguageAspect::databaseFilePath() const @@ -115,14 +113,37 @@ Utils::FilePath QmlMultiLanguageAspect::databaseFilePath() const void QmlMultiLanguageAspect::toMap(QVariantMap &map) const { BaseBoolAspect::toMap(map); - if (!m_lastUsedLanguage.isEmpty()) - map.insert(Constants::LAST_USED_LANGUAGE, m_lastUsedLanguage); + if (!m_currentLocale.isEmpty()) + map.insert(Constants::LAST_USED_LANGUAGE, m_currentLocale); } void QmlMultiLanguageAspect::fromMap(const QVariantMap &map) { BaseBoolAspect::fromMap(map); - setLastUsedLanguage(map.value(Constants::LAST_USED_LANGUAGE, "en").toString()); + setCurrentLocale(map.value(Constants::LAST_USED_LANGUAGE, "en").toString()); +} + +QmlMultiLanguageAspect *QmlMultiLanguageAspect::current() +{ + if (auto project = ProjectExplorer::SessionManager::startupProject()) + return current(project); + return {}; +} + +QmlMultiLanguageAspect *QmlMultiLanguageAspect::current(ProjectExplorer::Project *project) +{ + if (auto target = project->activeTarget()) + return current(target); + return {}; +} + +QmlMultiLanguageAspect *QmlMultiLanguageAspect::current(ProjectExplorer::Target *target) +{ + if (auto runConfiguration = target->activeRunConfiguration()) { + if (auto multiLanguageAspect = runConfiguration->aspect<QmlProjectManager::QmlMultiLanguageAspect>()) + return multiLanguageAspect; + } + return {}; } } // namespace QmlProjectManager diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h index 163552caf0..c98c5e2aec 100644 --- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h +++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h @@ -40,18 +40,23 @@ public: explicit QmlMultiLanguageAspect(ProjectExplorer::Target *target); ~QmlMultiLanguageAspect() override; - QString lastUsedLanguage() const; + QString currentLocale() const; + void setCurrentLocale(const QString &locale); Utils::FilePath databaseFilePath() const; void toMap(QVariantMap &map) const final; void fromMap(const QVariantMap &map) final; -public slots: - void setLastUsedLanguage(const QString &language); + static QmlMultiLanguageAspect *current(); + static QmlMultiLanguageAspect *current(ProjectExplorer::Project *project); + static QmlMultiLanguageAspect *current(ProjectExplorer::Target *target); + +signals: + void currentLocaleChanged(const QString &locale); private: ProjectExplorer::Target *m_target = nullptr; mutable Utils::FilePath m_databaseFilePath; - QString m_lastUsedLanguage; + QString m_currentLocale; }; } // namespace QmlProjectManager diff --git a/src/plugins/qmlprojectmanager/qmlproject.cpp b/src/plugins/qmlprojectmanager/qmlproject.cpp index 7d6297db94..9b4820586e 100644 --- a/src/plugins/qmlprojectmanager/qmlproject.cpp +++ b/src/plugins/qmlprojectmanager/qmlproject.cpp @@ -372,7 +372,7 @@ void QmlBuildSystem::generateProjectTree() auto newRoot = std::make_unique<QmlProjectNode>(project()); - for (const QString &f : m_projectItem.data()->files()) { + for (const QString &f : m_projectItem->files()) { const Utils::FilePath fileName = Utils::FilePath::fromString(f); const FileType fileType = (fileName == projectFilePath()) ? FileType::Project : FileNode::fileTypeForFileName(fileName); diff --git a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp index a0b77fffb7..7c30dfc156 100644 --- a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp +++ b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp @@ -117,7 +117,10 @@ QmlProjectRunConfiguration::QmlProjectRunConfiguration(Target *target, Id id) if (m_multiLanguageAspect && m_multiLanguageAspect->value() && !m_multiLanguageAspect->databaseFilePath().isEmpty()) { env.set("QT_MULTILANGUAGE_DATABASE", m_multiLanguageAspect->databaseFilePath().toString()); - env.set("QT_MULTILANGUAGE_LANGUAGE", m_multiLanguageAspect->lastUsedLanguage()); + env.set("QT_MULTILANGUAGE_LANGUAGE", m_multiLanguageAspect->currentLocale()); + } else { + env.unset("QT_MULTILANGUAGE_DATABASE"); + env.unset("QT_MULTILANGUAGE_LANGUAGE"); } return env; }; diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index 26285896ec..2b80c8ca29 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -52,6 +52,7 @@ extend_qtc_executable(qml2puppet inputeventcommand.cpp inputeventcommand.h view3dactioncommand.cpp view3dactioncommand.h valueschangedcommand.cpp + captureddatacommand.h ) extend_qtc_executable(qml2puppet @@ -155,6 +156,7 @@ extend_qtc_executable(qml2puppet quick3dtexturenodeinstance.cpp quick3dtexturenodeinstance.h quickitemnodeinstance.cpp quickitemnodeinstance.h servernodeinstance.cpp servernodeinstance.h + qt5capturenodeinstanceserver.cpp qt5capturenodeinstanceserver.h ) extend_qtc_executable(qml2puppet diff --git a/src/tools/qml2puppet/qml2puppet.qbs b/src/tools/qml2puppet/qml2puppet.qbs index a09f106d76..3aab61eb9c 100644 --- a/src/tools/qml2puppet/qml2puppet.qbs +++ b/src/tools/qml2puppet/qml2puppet.qbs @@ -116,6 +116,7 @@ QtcTool { "commands/inputeventcommand.h", "commands/view3dactioncommand.cpp", "commands/view3dactioncommand.h", + "commands/captureddatacommand.h", "container/addimportcontainer.cpp", "container/addimportcontainer.h", "container/idcontainer.cpp", @@ -218,6 +219,8 @@ QtcTool { "instances/qt5testnodeinstanceserver.h", "instances/servernodeinstance.cpp", "instances/servernodeinstance.h", + "instances/qt5capturenodeinstanceserver.cpp", + "instances/qt5capturenodeinstanceserver.h", "editor3d/generalhelper.cpp", "editor3d/mousearea3d.cpp", "editor3d/camerageometry.cpp", diff --git a/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt b/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt index 83f9da98a0..fb22fbf06b 100644 --- a/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt +++ b/tests/auto/qml/qmldesigner/coretests/CMakeLists.txt @@ -17,4 +17,5 @@ add_qtc_test(tst_qml_testcore ../testview.cpp ../testview.h testrewriterview.cpp testrewriterview.h tst_testcore.cpp tst_testcore.h + ../testconnectionmanager.cpp ../testconnectionmanager.h ) diff --git a/tests/auto/qml/qmldesigner/coretests/coretests.pro b/tests/auto/qml/qmldesigner/coretests/coretests.pro index 7afb0fda2e..c179025da5 100644 --- a/tests/auto/qml/qmldesigner/coretests/coretests.pro +++ b/tests/auto/qml/qmldesigner/coretests/coretests.pro @@ -61,11 +61,13 @@ TEMPLATE = app SOURCES += \ ../testview.cpp \ testrewriterview.cpp \ - tst_testcore.cpp + tst_testcore.cpp \ + ../testconnectionmanager.cpp HEADERS += \ ../testview.h \ testrewriterview.h \ - tst_testcore.h + tst_testcore.h \ + ../testconnectionmanager.h RESOURCES += ../data/testfiles.qrc diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp index fc5d9dcfa9..965d9b7aee 100644 --- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp +++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp @@ -46,11 +46,12 @@ #include <stylesheetmerger.h> #include <QDebug> +#include "../testconnectionmanager.h" #include "../testview.h" -#include <variantproperty.h> #include <abstractproperty.h> #include <bindingproperty.h> #include <nodeproperty.h> +#include <variantproperty.h> #include <nodelistproperty.h> #include <nodeabstractproperty.h> @@ -1999,8 +2000,9 @@ void tst_TestCore::testModelRemoveNode() QVERIFY(view.data()); model->attachView(view.data()); - NodeInstanceView *nodeInstanceView = new NodeInstanceView(model.data(), NodeInstanceServerInterface::TestModus); - model->attachView(nodeInstanceView); + TestConnectionManager connectionManager; + NodeInstanceView nodeInstanceView{connectionManager}; + model->attachView(&nodeInstanceView); QCOMPARE(view->rootModelNode().directSubModelNodes().count(), 0); @@ -2051,7 +2053,7 @@ void tst_TestCore::testModelRemoveNode() childNode = view->createModelNode("QtQuick.Item", 1, 1); childNode.destroy(); - model->detachView(nodeInstanceView); + model->detachView(&nodeInstanceView); } void tst_TestCore::reparentingNode() @@ -6140,17 +6142,21 @@ void tst_TestCore::testInstancesAttachToExistingModel() // Attach NodeInstanceView - QScopedPointer<NodeInstanceView> instanceView(new NodeInstanceView(0, NodeInstanceServerInterface::TestModus)); - QVERIFY(instanceView.data()); - model->attachView(instanceView.data()); + TestConnectionManager connectionManager; + + NodeInstanceView instanceView{connectionManager}; - NodeInstance rootInstance = instanceView->instanceForModelNode(rootNode); - NodeInstance rectangleInstance = instanceView->instanceForModelNode(rectangleNode); + model->attachView(&instanceView); + + NodeInstance rootInstance = instanceView.instanceForModelNode(rootNode); + NodeInstance rectangleInstance = instanceView.instanceForModelNode(rectangleNode); QVERIFY(rootInstance.isValid()); QVERIFY(rectangleInstance.isValid()); QCOMPARE(QVariant(100), rectangleInstance.property("width")); QVERIFY(rootInstance.instanceId() >= 0); QVERIFY(rectangleInstance.instanceId() >= 0); + + model->detachView(&instanceView); } void tst_TestCore::testQmlModelAddMultipleStates() diff --git a/tests/auto/qml/qmldesigner/testconnectionmanager.cpp b/tests/auto/qml/qmldesigner/testconnectionmanager.cpp new file mode 100644 index 0000000000..f637fb1f1a --- /dev/null +++ b/tests/auto/qml/qmldesigner/testconnectionmanager.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#include "testconnectionmanager.h" +#include "synchronizecommand.h" + +#include <QLocalSocket> + +namespace QmlDesigner { + +TestConnectionManager::TestConnectionManager() +{ + m_connections.emplace_back("Editor", "editormode"); +} + +void TestConnectionManager::writeCommand(const QVariant &command) +{ + TestConnectionManager::writeCommand(command); + + m_writeCommandCounter++; + + static int synchronizeId = 0; + synchronizeId++; + SynchronizeCommand synchronizeCommand(synchronizeId); + + QLocalSocket *socket = m_connections.front().socket.get(); + + writeCommandToIODevice(QVariant::fromValue(synchronizeCommand), socket, m_writeCommandCounter); + m_writeCommandCounter++; + + while (socket->waitForReadyRead(100)) { + readDataStream(m_connections.front()); + if (m_synchronizeId == synchronizeId) + return; + } +} + +void TestConnectionManager::dispatchCommand(const QVariant &command, Connection &connection) +{ + static const int synchronizeCommandType = QMetaType::type("SynchronizeCommand"); + + if (command.userType() == synchronizeCommandType) { + SynchronizeCommand synchronizeCommand = command.value<SynchronizeCommand>(); + m_synchronizeId = synchronizeCommand.synchronizeId(); + } else { + ConnectionManager::dispatchCommand(command, connection); + } +} + +} // namespace QmlDesigner diff --git a/tests/auto/qml/qmldesigner/testconnectionmanager.h b/tests/auto/qml/qmldesigner/testconnectionmanager.h new file mode 100644 index 0000000000..6ea44c1c1d --- /dev/null +++ b/tests/auto/qml/qmldesigner/testconnectionmanager.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt 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 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** 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. +** +****************************************************************************/ + +#pragma once + +#include <connectionmanager.h> + +#include <QFile> +#include <QPointer> +#include <QProcess> +#include <QTimer> + +QT_BEGIN_NAMESPACE +class QLocalServer; +class QLocalSocket; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class TestConnectionManager final : public ConnectionManager +{ +public: + TestConnectionManager(); + + void writeCommand(const QVariant &command) override; + +protected: + void dispatchCommand(const QVariant &command, Connection &connection) override; + +private: + int m_synchronizeId = -1; +}; + +} // namespace QmlDesigner diff --git a/tests/unit/unittest/listmodeleditor-test.cpp b/tests/unit/unittest/listmodeleditor-test.cpp index ca0913f865..bdd5e9c070 100644 --- a/tests/unit/unittest/listmodeleditor-test.cpp +++ b/tests/unit/unittest/listmodeleditor-test.cpp @@ -31,14 +31,17 @@ #include <qmldesigner/components/listmodeleditor/listmodeleditormodel.h> #include <qmldesigner/designercore/include/abstractview.h> +#include <qmldesigner/designercore/include/bindingproperty.h> #include <qmldesigner/designercore/include/model.h> #include <qmldesigner/designercore/include/nodelistproperty.h> +#include <qmldesigner/designercore/include/nodeproperty.h> #include <qmldesigner/designercore/include/variantproperty.h> namespace { using QmlDesigner::AbstractProperty; using QmlDesigner::AbstractView; +using QmlDesigner::ListModelEditorModel; using QmlDesigner::ModelNode; MATCHER_P2(HasItem, @@ -93,6 +96,7 @@ public: emptyListModelNode = mockView.createModelNode("QtQml.Models.ListModel", 2, 15); + listViewNode = mockView.createModelNode("QtQuick.ListView", 2, 15); listModelNode = mockView.createModelNode("QtQml.Models.ListModel", 2, 15); mockView.rootModelNode().defaultNodeListProperty().reparentHere(listModelNode); element1 = createElement({{"name", "foo"}, {"value", 1}, {"value2", 42}}); @@ -172,10 +176,20 @@ public: return properties; } + QModelIndex index(int row, int column) const { return model.index(row, column); } + + QList<ModelNode> elements(const ModelNode &node) const + { + return node.defaultNodeListProperty().toModelNodeList(); + } + protected: std::unique_ptr<QmlDesigner::Model> designerModel{QmlDesigner::Model::create("QtQuick.Item", 1, 1)}; NiceMock<MockListModelEditorView> mockView; - QmlDesigner::ListModelEditorModel model; + QmlDesigner::ListModelEditorModel model{ + [&] { return mockView.createModelNode("QtQml.Models.ListModel", 2, 15); }, + [&] { return mockView.createModelNode("QtQml.Models.ListElement", 2, 15); }}; + ModelNode listViewNode; ModelNode listModelNode; ModelNode emptyListModelNode; ModelNode element1; @@ -427,7 +441,7 @@ TEST_F(ListModelEditor, RemoveColumnRemovesDisplayValues) { model.setListModel(listModelNode); - model.removeColumn(2); + model.removeColumns({index(0, 2)}); ASSERT_THAT(displayValues(), ElementsAre(ElementsAre(IsInvalid(), "foo", 42), @@ -442,14 +456,14 @@ TEST_F(ListModelEditor, RemoveColumnRemovesProperties) EXPECT_CALL(mockView, propertiesRemoved(ElementsAre(IsAbstractProperty(element2, "image")))); EXPECT_CALL(mockView, propertiesRemoved(ElementsAre(IsAbstractProperty(element3, "image")))); - model.removeColumn(0); + model.removeColumns({index(0, 0)}); } TEST_F(ListModelEditor, RemoveColumnRemovesPropertyName) { model.setListModel(listModelNode); - model.removeColumn(1); + model.removeColumns({index(0, 1)}); ASSERT_THAT(model.propertyNames(), ElementsAre("image", "value", "value2")); } @@ -458,7 +472,7 @@ TEST_F(ListModelEditor, RemoveRowRemovesDisplayValues) { model.setListModel(listModelNode); - model.removeRow(1); + model.removeRows({index(1, 0)}); ASSERT_THAT(displayValues(), ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), @@ -471,7 +485,7 @@ TEST_F(ListModelEditor, RemoveRowRemovesElementInListModel) EXPECT_CALL(mockView, nodeRemoved(Eq(element2), _, _)); - model.removeRow(1); + model.removeRows({index(1, 0)}); } TEST_F(ListModelEditor, ConvertStringFloatToFloat) @@ -721,7 +735,7 @@ TEST_F(ListModelEditor, RemoveColumnAfterRenameColumn) model.setListModel(listModelNode); model.renameColumn(1, "mood"); - model.removeColumn(1); + model.removeColumns({index(0, 1)}); ASSERT_THAT(properties(), ElementsAre(UnorderedElementsAre(IsVariantProperty("value", 1), @@ -909,44 +923,457 @@ TEST_F(ListModelEditor, RemoveLastRow) model.addColumn("mood"); model.addRow(); - model.removeRow(0); + model.removeRows({index(0, 0)}); ASSERT_THAT(displayValues(), IsEmpty()); } -TEST_F(ListModelEditor, RemoveLastColumn) +TEST_F(ListModelEditor, RemoveLastEmptyRow) { model.setListModel(emptyListModelNode); model.addColumn("mood"); model.addRow(); + model.removeColumns({index(0, 0)}); - model.removeColumn(0); + model.removeRows({index(0, 0)}); ASSERT_THAT(displayValues(), ElementsAre(IsEmpty())); } -TEST_F(ListModelEditor, RemoveLastEmptyColumn) +TEST_F(ListModelEditor, RemoveLastColumn) { model.setListModel(emptyListModelNode); model.addColumn("mood"); model.addRow(); - model.removeRow(0); - model.removeColumn(0); + model.removeColumns({index(0, 0)}); - ASSERT_THAT(displayValues(), IsEmpty()); + ASSERT_THAT(displayValues(), ElementsAre(IsEmpty())); } -TEST_F(ListModelEditor, RemoveLastEmptyRow) +TEST_F(ListModelEditor, RemoveLastEmptyColumn) { model.setListModel(emptyListModelNode); model.addColumn("mood"); model.addRow(); - model.removeColumn(0); + model.removeRows({index(0, 0)}); - model.removeRow(0); + model.removeColumns({index(0, 0)}); ASSERT_THAT(displayValues(), IsEmpty()); } +TEST_F(ListModelEditor, RemoveColumns) +{ + model.setListModel(listModelNode); + model.removeColumns({index(0, 1), index(0, 3), index(1, 1), index(0, 4)}); + + ASSERT_THAT(properties(), + ElementsAre(UnorderedElementsAre(IsVariantProperty("value", 1)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("value", 4)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("value", 111)))); +} + +TEST_F(ListModelEditor, RemoveRows) +{ + model.setListModel(listModelNode); + + model.removeRows({index(1, 0), index(2, 0), index(3, 0), index(2, 0)}); + + ASSERT_THAT(properties(), + ElementsAre(UnorderedElementsAre(IsVariantProperty("name", "foo"), + IsVariantProperty("value", 1), + IsVariantProperty("value2", 42)))); +} + +TEST_F(ListModelEditor, FilterColumns) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(0, 0), index(1, 1), index(0, 2), index(0, 1)}; + + auto columns = ListModelEditorModel::filterColumns(indices); + + ASSERT_THAT(columns, ElementsAre(0, 1, 2)); +} + +TEST_F(ListModelEditor, FilterColumnsInvalidColumns) +{ + QList<QModelIndex> indices = {index(0, 0), index(1, 1), index(0, 2), index(0, 1)}; + + auto columns = ListModelEditorModel::filterColumns(indices); + + ASSERT_THAT(columns, IsEmpty()); +} + +TEST_F(ListModelEditor, FilterColumnsEmptyInput) +{ + QList<QModelIndex> indices; + + auto columns = ListModelEditorModel::filterColumns(indices); + + ASSERT_THAT(columns, IsEmpty()); +} + +TEST_F(ListModelEditor, FilterRows) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(0, 0), index(1, 1), index(2, 2), index(0, 1)}; + + auto rows = ListModelEditorModel::filterRows(indices); + + ASSERT_THAT(rows, ElementsAre(0, 1, 2)); +} + +TEST_F(ListModelEditor, FilterRowsInvalidColumns) +{ + QList<QModelIndex> indices = {index(0, 0), index(1, 1), index(2, 2), index(0, 1)}; + + auto rows = ListModelEditorModel::filterRows(indices); + + ASSERT_THAT(rows, IsEmpty()); +} + +TEST_F(ListModelEditor, FilterRowsEmptyInput) +{ + QList<QModelIndex> indices; + + auto rows = ListModelEditorModel::filterRows(indices); + + ASSERT_THAT(rows, IsEmpty()); +} + +TEST_F(ListModelEditor, CannotMoveEmptyRowsUp) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(-1, 1)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element1, element2, element3)); +} + +TEST_F(ListModelEditor, MoveRowUp) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(1, 1), index(1, 2), index(1, 0)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element2, element1, element3)); +} + +TEST_F(ListModelEditor, MoveRowsUp) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(1, 1), index(2, 2), index(1, 0)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element2, element3, element1)); +} + +TEST_F(ListModelEditor, CannotMoveFirstRowsUp) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(0, 1), index(1, 2), index(0, 0)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element1, element2, element3)); +} + +TEST_F(ListModelEditor, CannotMoveEmptyRowsUpDisplayValues) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(-1, 1)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, CannotMoveFirstRowUpDisplayValues) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(0, 1), index(1, 2), index(0, 0)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, MoveRowsUpDisplayValues) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(1, 1), index(2, 2), index(1, 0)}; + + model.moveRowsUp(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()), + ElementsAre(IsInvalid(), "foo", 1, 42))); +} + +TEST_F(ListModelEditor, NoSelectionAfterCannotMoveLastRowsDown) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(0, 1), index(1, 2), index(0, 0)}; + + auto selection = model.moveRowsUp(indices); + + ASSERT_THAT(selection.indexes(), IsEmpty()); +} + +TEST_F(ListModelEditor, NoSelectionAfterMoveEmptyRowsDown) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(-1, 1)}; + + auto selection = model.moveRowsUp(indices); + + ASSERT_THAT(selection.indexes(), IsEmpty()); +} + +TEST_F(ListModelEditor, SelectionAfterMoveRowsDown) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(1, 1), index(2, 2), index(1, 0)}; + + auto selection = model.moveRowsUp(indices); + + ASSERT_THAT(selection.indexes(), + ElementsAre(index(0, 0), + index(0, 1), + index(0, 2), + index(0, 3), + index(1, 0), + index(1, 1), + index(1, 2), + index(1, 3))); +} + +TEST_F(ListModelEditor, CannotMoveEmptyRowsDown) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(-1, 1)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element1, element2, element3)); +} + +TEST_F(ListModelEditor, MoveRowDown) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(1, 1), index(1, 2), index(1, 0)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element1, element3, element2)); +} + +TEST_F(ListModelEditor, MoveRowsDown) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(1, 1), index(0, 2), index(1, 0)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element3, element1, element2)); +} + +TEST_F(ListModelEditor, CannotMoveLastRowsDown) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(2, 1), index(1, 2), index(2, 0)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(elements(listModelNode), ElementsAre(element1, element2, element3)); +} + +TEST_F(ListModelEditor, CannotMoveEmptyRowsDownDisplayValues) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(-1, 1)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, CannotMoveLastRowDownDisplayValues) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(2, 1), index(1, 2), index(2, 0)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, MoveRowsDownDisplayValues) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(1, 1), index(0, 2), index(1, 0)}; + + model.moveRowsDown(indices); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre("pic.png", "poo", 111, IsInvalid()), + ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()))); +} + +TEST_F(ListModelEditor, NoSelectionAfterCannotMoveLastRowsUp) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(2, 1), index(1, 2), index(2, 0)}; + + auto selection = model.moveRowsDown(indices); + + ASSERT_THAT(selection.indexes(), IsEmpty()); +} + +TEST_F(ListModelEditor, NoSelectionAfterMoveEmptyRowsUp) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(-1, 1)}; + + auto selection = model.moveRowsDown(indices); + + ASSERT_THAT(selection.indexes(), IsEmpty()); +} + +TEST_F(ListModelEditor, SelectionAfterMoveRowsUp) +{ + model.setListModel(listModelNode); + QList<QModelIndex> indices = {index(1, 1), index(0, 2), index(1, 0)}; + + auto selection = model.moveRowsDown(indices); + + ASSERT_THAT(selection.indexes(), + ElementsAre(index(1, 0), + index(1, 1), + index(1, 2), + index(1, 3), + index(2, 0), + index(2, 1), + index(2, 2), + index(2, 3))); +} + +TEST_F(ListModelEditor, ListViewHasNoModel) +{ + model.setListView(listViewNode); + + ASSERT_THAT(listViewNode.nodeProperty("model").modelNode().type(), Eq("QtQml.Models.ListModel")); +} + +TEST_F(ListModelEditor, ListViewHasModelInside) +{ + listViewNode.nodeProperty("model").reparentHere(listModelNode); + + model.setListView(listViewNode); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, ListViewHasModelBinding) +{ + listModelNode.setIdWithoutRefactoring("listModel"); + listViewNode.bindingProperty("model").setExpression("listModel"); + + model.setListView(listViewNode); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), "foo", 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, AddBooleanDisplayValues) +{ + model.setListModel(listModelNode); + + model.setValue(0, 1, true); + + ASSERT_THAT(displayValues(), + ElementsAre(ElementsAre(IsInvalid(), true, 1, 42), + ElementsAre("pic.png", "bar", 4, IsInvalid()), + ElementsAre("pic.png", "poo", 111, IsInvalid()))); +} + +TEST_F(ListModelEditor, AddBooleanProperties) +{ + model.setListModel(listModelNode); + + model.setValue(0, 1, true); + + ASSERT_THAT(properties(), + ElementsAre(UnorderedElementsAre(IsVariantProperty("name", "foo"), + IsVariantProperty("value", true), + IsVariantProperty("value2", 42)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "bar"), + IsVariantProperty("value", 4)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "poo"), + IsVariantProperty("value", 111)))); +} + +TEST_F(ListModelEditor, AddTrueAsStringProperties) +{ + model.setListModel(listModelNode); + + model.setValue(0, 1, "true"); + + ASSERT_THAT(properties(), + ElementsAre(UnorderedElementsAre(IsVariantProperty("name", true), + IsVariantProperty("value", 1), + IsVariantProperty("value2", 42)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "bar"), + IsVariantProperty("value", 4)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "poo"), + IsVariantProperty("value", 111)))); +} + +TEST_F(ListModelEditor, AddFalseAsStringProperties) +{ + model.setListModel(listModelNode); + + model.setValue(0, 1, "false"); + + ASSERT_THAT(properties(), + ElementsAre(UnorderedElementsAre(IsVariantProperty("name", false), + IsVariantProperty("value", 1), + IsVariantProperty("value2", 42)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "bar"), + IsVariantProperty("value", 4)), + UnorderedElementsAre(IsVariantProperty("image", "pic.png"), + IsVariantProperty("name", "poo"), + IsVariantProperty("value", 111)))); +} + } // namespace |