pax_global_header00006660000000000000000000000064136152126200014510gustar00rootroot0000000000000052 comment=d5f203e2c4836b6ac37a489ee8f7e1ddc7db5e20 qnodeeditor-2.1.5+ds1/000077500000000000000000000000001361521262000145155ustar00rootroot00000000000000qnodeeditor-2.1.5+ds1/.appveyor.yml000066400000000000000000000031761361521262000171720ustar00rootroot00000000000000clone_depth: 5 environment: matrix: - GENERATOR : "Visual Studio 15 2017 Win64" APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 QTDIR: C:\Qt\5.12.2\msvc2017_64 PLATFORM: x64 - GENERATOR : "Visual Studio 15 2017" APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 QTDIR: C:\Qt\5.12.2\msvc2017 PLATFORM: Win32 - GENERATOR : "MinGW Makefiles" QTDIR: C:\Qt\5.11.3\mingw53_32 PLATFORM: x86 CMAKE_CXX_FLAGS_INIT: -DCATCH_CONFIG_NO_CPP11_TO_STRING configuration: - Release install: - set PATH=%QTDIR%\bin;%PATH% - set Qt5_DIR=%QTDIR%\lib\cmake\Qt5 - set PATH=C:\MinGW\bin;C:\MinGW\msys\1.0;%PATH% - set PATH=C:\Qt\Tools\mingw530_32;%PATH% - set PATH=%PATH:C:\Program Files\Git\usr\bin=% # trick to remove sh.exe before_build: - mkdir build - cd build - mkdir bin - set OUTPUT_DIR=%cd%\bin - cmake "-G%GENERATOR%" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="%OUTPUT_DIR%" -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="%OUTPUT_DIR%" -DCMAKE_CXX_FLAGS_INIT="%CMAKE_CXX_FLAGS_INIT%" .. build_script: - cmake --build . test_script: - ps: $env:isX86 = $env:PLATFORM.Contains("x86") - IF %isX86% == False ctest --output-on-failure -C Debug after_build: - 7z a examples.zip %APPVEYOR_BUILD_FOLDER%/build/bin - cmd: cd - cmd: dir \S \P "examples.zip" artifacts: - path: build\examples.zip name: ex #deploy: #release: $(APPVEYOR_REPO_TAG_NAME) #provider: GitHub #artifact: /.*\.exe/ #auth_token: #secure: j0nBV9xVItdG3j6d0gHoyvrzi7TOhAy9/QIeyCbFeP8PTqq7DPr1oYwL5WIkPaXe #draft: false #prerelease: false #on: #appveyor_repo_tag: true qnodeeditor-2.1.5+ds1/.codeclimate.yml000066400000000000000000000001131361521262000175620ustar00rootroot00000000000000engines: fixme: enabled: true ratings: paths: [] exclude_paths: [] qnodeeditor-2.1.5+ds1/.gitignore000066400000000000000000000001051361521262000165010ustar00rootroot00000000000000*.py *.pyc *.user _build CMakeLists.txt.user build/ examples/build*/ qnodeeditor-2.1.5+ds1/.travis.yml000066400000000000000000000031271361521262000166310ustar00rootroot00000000000000language: cpp matrix: include: - os: osx osx_image: xcode8.3 compiler: clang env: Qt5_DIR=/usr/local/opt/qt5/lib/cmake/Qt5 - os: linux dist: xenial sudo: false compiler: clang env: CXX=clang++-7 CC=clang-7 QT=512 addons: apt: sources: - llvm-toolchain-xenial-7 packages: - clang-7 - os: linux dist: xenial sudo: false compiler: gcc env: CXX=g++-7 CC=gcc-7 QT=512 addons: apt: sources: - ubuntu-toolchain-r-test packages: - g++-7 git: depth: 10 before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install qt; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install build-essential libgl1-mesa-dev ; fi - if [[ "$QT" == "512" ]]; then sudo add-apt-repository ppa:beineri/opt-qt-5.12.1-xenial -y; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -yqq install qt${QT}base; source /opt/qt${QT}/bin/qt${QT}-env.sh; fi script: - mkdir build - cd build - cmake -DCMAKE_VERBOSE_MAKEFILE=$VERBOSE_BUILD .. && make -j - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then xvfb-run --server-args="-screen 0 1024x768x24" ctest --output-on-failure; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ctest --output-on-failure; fi notifications: email: false qnodeeditor-2.1.5+ds1/.vscode/000077500000000000000000000000001361521262000160565ustar00rootroot00000000000000qnodeeditor-2.1.5+ds1/.vscode/settings.json000066400000000000000000000035021361521262000206110ustar00rootroot00000000000000{ "files.associations": { "cctype": "cpp", "clocale": "cpp", "cmath": "cpp", "csignal": "cpp", "cstdarg": "cpp", "cstddef": "cpp", "cstdio": "cpp", "cstdlib": "cpp", "cstring": "cpp", "ctime": "cpp", "cwchar": "cpp", "cwctype": "cpp", "array": "cpp", "atomic": "cpp", "hash_map": "cpp", "strstream": "cpp", "bit": "cpp", "*.tcc": "cpp", "bitset": "cpp", "chrono": "cpp", "complex": "cpp", "condition_variable": "cpp", "cstdint": "cpp", "deque": "cpp", "list": "cpp", "map": "cpp", "set": "cpp", "unordered_map": "cpp", "unordered_set": "cpp", "vector": "cpp", "exception": "cpp", "algorithm": "cpp", "functional": "cpp", "iterator": "cpp", "memory": "cpp", "memory_resource": "cpp", "numeric": "cpp", "optional": "cpp", "random": "cpp", "ratio": "cpp", "regex": "cpp", "string": "cpp", "string_view": "cpp", "system_error": "cpp", "tuple": "cpp", "type_traits": "cpp", "utility": "cpp", "fstream": "cpp", "future": "cpp", "initializer_list": "cpp", "iomanip": "cpp", "iosfwd": "cpp", "iostream": "cpp", "istream": "cpp", "limits": "cpp", "mutex": "cpp", "new": "cpp", "ostream": "cpp", "shared_mutex": "cpp", "sstream": "cpp", "stdexcept": "cpp", "streambuf": "cpp", "thread": "cpp", "cfenv": "cpp", "cinttypes": "cpp", "typeindex": "cpp", "typeinfo": "cpp", "valarray": "cpp", "variant": "cpp" } }qnodeeditor-2.1.5+ds1/LICENSE000066400000000000000000000027171361521262000155310ustar00rootroot00000000000000Copyright (c) 2017, Dmitry Pinaev All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of copyright holder, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. qnodeeditor-2.1.5+ds1/QNodeEditor.pri000066400000000000000000000120371361521262000174110ustar00rootroot00000000000000QT += core widgets gui DEFINES += NODE_EDITOR_SHARED NODE_EDITOR_EXPORTS SOURCES += $$PWD/src/Connection.cpp \ $$PWD/src/ConnectionBlurEffect.cpp \ $$PWD/src/ConnectionGeometry.cpp \ $$PWD/src/ConnectionGraphicsObject.cpp \ $$PWD/src/ConnectionPainter.cpp \ $$PWD/src/ConnectionState.cpp \ $$PWD/src/ConnectionStyle.cpp \ $$PWD/src/DataModelRegistry.cpp \ $$PWD/src/FlowScene.cpp \ $$PWD/src/FlowView.cpp \ $$PWD/src/FlowViewStyle.cpp \ $$PWD/src/Node.cpp \ $$PWD/src/NodeConnectionInteraction.cpp \ $$PWD/src/NodeDataModel.cpp \ $$PWD/src/NodeGeometry.cpp \ $$PWD/src/NodeGraphicsObject.cpp \ $$PWD/src/NodePainter.cpp \ $$PWD/src/NodeState.cpp \ $$PWD/src/NodeStyle.cpp \ $$PWD/src/Properties.cpp \ $$PWD/src/StyleCollection.cpp INCLUDEPATH += $$PWD/src/ \ $$PWD/include/ \ $$PWD/include/nodes/ \ $$PWD/include/nodes/internal HEADERS += $$PWD/src/ConnectionBlurEffect.hpp \ $$PWD/src/ConnectionPainter.hpp \ $$PWD/src/NodeConnectionInteraction.hpp \ $$PWD/src/NodePainter.hpp \ $$PWD/src/Properties.hpp \ $$PWD/src/StyleCollection.hpp \ $$PWD/include/nodes/internal/Compiler.hpp \ $$PWD/include/nodes/internal/Connection.hpp \ $$PWD/include/nodes/internal/ConnectionGeometry.hpp \ $$PWD/include/nodes/internal/ConnectionGraphicsObject.hpp \ $$PWD/include/nodes/internal/ConnectionState.hpp \ $$PWD/include/nodes/internal/ConnectionStyle.hpp \ $$PWD/include/nodes/internal/DataModelRegistry.hpp \ $$PWD/include/nodes/internal/Export.hpp \ $$PWD/include/nodes/internal/FlowScene.hpp \ $$PWD/include/nodes/internal/FlowView.hpp \ $$PWD/include/nodes/internal/FlowViewStyle.hpp \ $$PWD/include/nodes/internal/memory.hpp \ $$PWD/include/nodes/internal/Node.hpp \ $$PWD/include/nodes/internal/NodeData.hpp \ $$PWD/include/nodes/internal/NodeDataModel.hpp \ $$PWD/include/nodes/internal/NodeGeometry.hpp \ $$PWD/include/nodes/internal/NodeGraphicsObject.hpp \ $$PWD/include/nodes/internal/NodePainterDelegate.hpp \ $$PWD/include/nodes/internal/NodeState.hpp \ $$PWD/include/nodes/internal/NodeStyle.hpp \ $$PWD/include/nodes/internal/OperatingSystem.hpp \ $$PWD/include/nodes/internal/PortType.hpp \ $$PWD/include/nodes/internal/QStringStdHash.hpp \ $$PWD/include/nodes/internal/QUuidStdHash.hpp \ $$PWD/include/nodes/internal/Serializable.hpp \ $$PWD/include/nodes/internal/Style.hpp \ $$PWD/include/nodes/internal/TypeConverter.hpp RESOURCES += \ $$PWD/resources/QNodeEditor_resources.qrc qnodeeditor-2.1.5+ds1/QNodeEditor.pro000066400000000000000000000001371361521262000174150ustar00rootroot00000000000000TARGET = QNodeEditor TEMPLATE = lib VERSION = 1.0.0 include(QNodeEditor.pri) qnodeeditor-2.1.5+ds1/README.md000066400000000000000000000070231361521262000157760ustar00rootroot00000000000000### Purpose **NodeEditor** is conceived as a general-purpose Qt-based library aimed at graph-controlled data processing. Nodes represent algorithms with certain inputs and outputs. Connections transfer data from the output (source) of the first node to the input (sink) of the second one. **NodeEditor** framework is a Visual [Dataflow Programming](https://en.wikipedia.org/wiki/Dataflow_programming) tool. A library client defines models and registers them in the data model registry. Further work is driven by events taking place in DataModels and Nodes. The model computing is triggered upon arriving of any new input data. The computed result is propagated to the output connections. Each new connection fetches available data and propagates is further. Each change in the source node is immediately propagated through all the connections updating the whole graph. ### Platforms * OSX (Apple Clang - LLVM 3.6), Linux (x64, gcc-7.0, clang-7): [![Build Status](https://travis-ci.org/paceholder/nodeeditor.svg?branch=master)](https://travis-ci.org/paceholder/nodeeditor) * Windows (Win32, x64, msvc2017, MinGW 5.3): [![Build status](https://ci.appveyor.com/api/projects/status/wxp47wv3uyyiujjw/branch/master?svg=true)](https://ci.appveyor.com/project/paceholder/nodeeditor/branch/master) ### Dependencies * Qt >5.2 * CMake 3.2 * Catch2 ### Current state * Model-based nodes * Automatic data propagation * Datatype-aware connections * Embedded Qt widgets * One-output to many-input connections * JSON-based interface styles * Saving scenes to JSON files ### Building #### Linux ~~~ git clone git@github.com:paceholder/nodeeditor.git cd nodeeditor mkdir build cd build cmake .. make -j && make install ~~~ #### Qt Creator 1. Open `CMakeLists.txt` as project. 2. If you don't have the `Catch2` library installed, go to `Build Settings`, disable the checkbox `BUILD_TESTING`. 3. `Build -> Run CMake` 4. `Build -> Build All` 5. Click the button `Run` ### Roadmap 1. Extend set of examples 2. GUI: fix scrolling for scene view window scrolling 3. Implement grouping nodes 4. Split graph and GUI parts 5. Build data propagation on top of the graph code ### Citing Dmitry Pinaev et al, Qt5 Node Editor, (2017), GitHub repository, https://github.com/paceholder/nodeeditor BibTeX @misc{Pinaev2017, author = {Dmitry Pinaev et al}, title = {Qt5 Node Editor}, year = {2017}, publisher = {GitHub}, journal = {GitHub repository}, howpublished = {\url{https://github.com/paceholder/nodeeditor}}, commit = {1d1757d09b03cea0e4921bc19659465fe6e65b9b} } ### Youtube video: [![Youtube demonstration](https://bitbucket.org/paceholder/nodeeditor/raw/master/pictures/vid1.png)](https://www.youtube.com/watch?v=pxMXjSvlOFw) ### Now with styles [![Styles](https://bitbucket.org/paceholder/nodeeditor/raw/master/pictures/style_example.png)](https://www.youtube.com/watch?v=i_pB-Y0hCYQ) ### Buy me a beer [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/DmitryPinaev) ### Showcase #### [Chigraph](https://github.com/chigraph/chigraph) Chigraph is a programming language for beginners that is unique in that it is an intuitive flow graph: ![chigraph screenshot](pictures/chigraph.png) It features easy bindings to C/C++, package management, and a cool interface. #### [Spkgen particle engine editor](https://github.com/fredakilla/spkgen) ![spkgen screenshot](pictures/spkgen.png) Spkgen is an editor for the SPARK particles engine using a node-based interface to create particles effects for games. qnodeeditor-2.1.5+ds1/include/000077500000000000000000000000001361521262000161405ustar00rootroot00000000000000qnodeeditor-2.1.5+ds1/include/nodes/000077500000000000000000000000001361521262000172505ustar00rootroot00000000000000qnodeeditor-2.1.5+ds1/include/nodes/internal/000077500000000000000000000000001361521262000210645ustar00rootroot00000000000000qnodeeditor-2.1.5+ds1/include/nodes/internal/Compiler.hpp000066400000000000000000000034151361521262000233520ustar00rootroot00000000000000#pragma once #if \ defined (__MINGW32__) || \ defined (__MINGW64__) # define NODE_EDITOR_COMPILER "MinGW" # define NODE_EDITOR_COMPILER_MINGW #elif \ defined (__GNUC__) # define NODE_EDITOR_COMPILER "GNU" # define NODE_EDITOR_COMPILER_GNU # define NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR __GNUC__ # define NODE_EDITOR_COMPILER_GNU_VERSION_MINOR __GNUC_MINOR__ # define NODE_EDITOR_COMPILER_GNU_VERSION_PATCH __GNUC_PATCHLEVEL__ #elif \ defined (__clang__) # define NODE_EDITOR_COMPILER "Clang" # define NODE_EDITOR_COMPILER_CLANG #elif \ defined (_MSC_VER) # define NODE_EDITOR_COMPILER "Microsoft Visual C++" # define NODE_EDITOR_COMPILER_MICROSOFT #elif \ defined (__BORLANDC__) # define NODE_EDITOR_COMPILER "Borland C++ Builder" # define NODE_EDITOR_COMPILER_BORLAND #elif \ defined (__CODEGEARC__) # define NODE_EDITOR_COMPILER "CodeGear C++ Builder" # define NODE_EDITOR_COMPILER_CODEGEAR #elif \ defined (__INTEL_COMPILER) || \ defined (__ICL) # define NODE_EDITOR_COMPILER "Intel C++" # define NODE_EDITOR_COMPILER_INTEL #elif \ defined (__xlC__) || \ defined (__IBMCPP__) # define NODE_EDITOR_COMPILER "IBM XL C++" # define NODE_EDITOR_COMPILER_IBM #elif \ defined (__HP_aCC) # define NODE_EDITOR_COMPILER "HP aC++" # define NODE_EDITOR_COMPILER_HP #elif \ defined (__WATCOMC__) # define NODE_EDITOR_COMPILER "Watcom C++" # define NODE_EDITOR_COMPILER_WATCOM #endif #ifndef NODE_EDITOR_COMPILER # error "Current compiler is not supported." #endif qnodeeditor-2.1.5+ds1/include/nodes/internal/Connection.hpp000066400000000000000000000075751361521262000237120ustar00rootroot00000000000000#pragma once #include #include #include #include "PortType.hpp" #include "NodeData.hpp" #include "Serializable.hpp" #include "ConnectionState.hpp" #include "ConnectionGeometry.hpp" #include "TypeConverter.hpp" #include "QUuidStdHash.hpp" #include "Export.hpp" #include "memory.hpp" class QPointF; namespace QtNodes { class Node; class NodeData; class ConnectionGraphicsObject; /// class NODE_EDITOR_PUBLIC Connection : public QObject , public Serializable { Q_OBJECT public: /// New Connection is attached to the port of the given Node. /// The port has parameters (portType, portIndex). /// The opposite connection end will require anothre port. Connection(PortType portType, Node &node, PortIndex portIndex); Connection(Node &nodeIn, PortIndex portIndexIn, Node &nodeOut, PortIndex portIndexOut, TypeConverter converter = TypeConverter{}); Connection(const Connection &) = delete; Connection operator=(const Connection &) = delete; ~Connection(); public: QJsonObject save() const override; public: QUuid id() const; /// Remembers the end being dragged. /// Invalidates Node address. /// Grabs mouse. void setRequiredPort(PortType portType); PortType requiredPort() const; void setGraphicsObject(std::unique_ptr &&graphics); /// Assigns a node to the required port. /// It is assumed that there is a required port, no extra checks void setNodeToPort(Node &node, PortType portType, PortIndex portIndex); void removeFromNodes() const; public: ConnectionGraphicsObject & getConnectionGraphicsObject() const; ConnectionState const & connectionState() const; ConnectionState & connectionState(); ConnectionGeometry & connectionGeometry(); ConnectionGeometry const & connectionGeometry() const; Node * getNode(PortType portType) const; Node *& getNode(PortType portType); PortIndex getPortIndex(PortType portType) const; void clearNode(PortType portType); NodeDataType dataType(PortType portType) const; TypeConverter getTypeConverter() const; public: void setTypeConverter(TypeConverter converter); bool complete() const; public: // data propagation void propagateData(std::shared_ptr nodeData) const; void propagateEmptyData() const; Q_SIGNALS: void connectionCompleted(Connection const &) const; void connectionMadeIncomplete(Connection const &) const; private: QUuid _uid; private: Node *_outNode = nullptr; Node *_inNode = nullptr; PortIndex _outPortIndex; PortIndex _inPortIndex; private: ConnectionState _connectionState; ConnectionGeometry _connectionGeometry; std::unique_ptr_connectionGraphicsObject; TypeConverter _converter; Q_SIGNALS: void updated(Connection &conn) const; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/ConnectionGeometry.hpp000066400000000000000000000025321361521262000254120ustar00rootroot00000000000000#pragma once #include "PortType.hpp" #include #include #include namespace QtNodes { class ConnectionGeometry { public: ConnectionGeometry(); public: QPointF const & getEndPoint(PortType portType) const; void setEndPoint(PortType portType, QPointF const &point); void moveEndPoint(PortType portType, QPointF const &offset); QRectF boundingRect() const; std::pair pointsC1C2() const; QPointF source() const { return _out; } QPointF sink() const { return _in; } double lineWidth() const { return _lineWidth; } bool hovered() const { return _hovered; } void setHovered(bool hovered) { _hovered = hovered; } private: // local object coordinates QPointF _in; QPointF _out; //int _animationPhase; double _lineWidth; bool _hovered; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/ConnectionGraphicsObject.hpp000066400000000000000000000036251361521262000265120ustar00rootroot00000000000000#pragma once #include #include #include "Export.hpp" class QGraphicsSceneMouseEvent; namespace QtNodes { class FlowScene; class Connection; class ConnectionGeometry; class Node; /// Graphic Object for connection. Adds itself to scene class NODE_EDITOR_PUBLIC ConnectionGraphicsObject : public QGraphicsObject { Q_OBJECT public: ConnectionGraphicsObject(FlowScene &scene, Connection &connection); virtual ~ConnectionGraphicsObject(); enum { Type = UserType + 2 }; int type() const override { return Type; } public: Connection & connection(); QRectF boundingRect() const override; QPainterPath shape() const override; void setGeometryChanged(); /// Updates the position of both ends void move(); void lock(bool locked); protected: void paint(QPainter *painter, QStyleOptionGraphicsItem const *option, QWidget *widget = 0) override; void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; private: void addGraphicsEffect(); private: FlowScene &_scene; Connection &_connection; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/ConnectionState.hpp000066400000000000000000000025621361521262000247020ustar00rootroot00000000000000#pragma once #include #include "PortType.hpp" class QPointF; namespace QtNodes { class Node; /// Stores currently draggind end. /// Remembers last hovered Node. class ConnectionState { public: ConnectionState(PortType port = PortType::None) : _requiredPort(port) {} ConnectionState(const ConnectionState &) = delete; ConnectionState operator=(const ConnectionState &) = delete; ~ConnectionState(); public: void setRequiredPort(PortType end) { _requiredPort = end; } PortType requiredPort() const { return _requiredPort; } bool requiresPort() const { return _requiredPort != PortType::None; } void setNoRequiredPort() { _requiredPort = PortType::None; } public: void interactWithNode(Node *node); void setLastHoveredNode(Node *node); Node * lastHoveredNode() const { return _lastHoveredNode; } void resetLastHoveredNode(); private: PortType _requiredPort; Node *_lastHoveredNode{nullptr}; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/ConnectionStyle.hpp000066400000000000000000000025331361521262000247200ustar00rootroot00000000000000#pragma once #include #include "Export.hpp" #include "Style.hpp" namespace QtNodes { class NODE_EDITOR_PUBLIC ConnectionStyle : public Style { public: ConnectionStyle(); ConnectionStyle(QString jsonText); public: static void setConnectionStyle(QString jsonText); private: void loadJsonText(QString jsonText) override; void loadJsonFile(QString fileName) override; void loadJsonFromByteArray(QByteArray const &byteArray) override; public: QColor constructionColor() const; QColor normalColor() const; QColor normalColor(QString typeId) const; QColor selectedColor() const; QColor selectedHaloColor() const; QColor hoveredColor() const; float lineWidth() const; float constructionLineWidth() const; float pointDiameter() const; bool useDataDefinedColors() const; private: QColor ConstructionColor; QColor NormalColor; QColor SelectedColor; QColor SelectedHaloColor; QColor HoveredColor; float LineWidth; float ConstructionLineWidth; float PointDiameter; bool UseDataDefinedColors; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/DataModelRegistry.hpp000066400000000000000000000127771361521262000251760ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include "NodeDataModel.hpp" #include "TypeConverter.hpp" #include "Export.hpp" #include "QStringStdHash.hpp" #include "memory.hpp" namespace QtNodes { inline bool operator<(QtNodes::NodeDataType const &d1, QtNodes::NodeDataType const &d2) { return d1.id < d2.id; } /// Class uses map for storing models (name, model) class NODE_EDITOR_PUBLIC DataModelRegistry { public: using RegistryItemPtr = std::unique_ptr; using RegistryItemCreator = std::function; using RegisteredModelCreatorsMap = std::unordered_map; using RegisteredModelsCategoryMap = std::unordered_map; using CategoriesSet = std::set; using RegisteredTypeConvertersMap = std::map; DataModelRegistry() = default; ~DataModelRegistry() = default; DataModelRegistry(DataModelRegistry const &) = delete; DataModelRegistry(DataModelRegistry &&) = default; DataModelRegistry &operator=(DataModelRegistry const &) = delete; DataModelRegistry &operator=(DataModelRegistry &&) = default; public: template void registerModel(RegistryItemCreator creator, QString const &category = "Nodes") { registerModelImpl(std::move(creator), category); } template void registerModel(QString const &category = "Nodes") { RegistryItemCreator creator = [this]() { return make_unique(); }; registerModelImpl(std::move(creator), category); } template void registerModel(QString const &category, RegistryItemCreator creator) { registerModelImpl(std::move(creator), category); } void registerTypeConverter(TypeConverterId const &id, TypeConverter typeConverter) { _registeredTypeConverters[id] = std::move(typeConverter); } std::unique_ptrcreate(QString const &modelName); RegisteredModelCreatorsMap const ®isteredModelCreators() const; RegisteredModelsCategoryMap const ®isteredModelsCategoryAssociation() const; CategoriesSet const &categories() const; TypeConverter getTypeConverter(NodeDataType const &d1, NodeDataType const &d2) const; private: RegisteredModelsCategoryMap _registeredModelsCategory; CategoriesSet _categories; RegisteredModelCreatorsMap _registeredItemCreators; RegisteredTypeConvertersMap _registeredTypeConverters; private: // If the registered ModelType class has the static member method // // static Qstring Name(); // // use it. Otherwise use the non-static method: // // virtual QString name() const; template struct HasStaticMethodName : std::false_type { }; template struct HasStaticMethodName::value>::type> : std::true_type { }; template typename std::enable_if< HasStaticMethodName::value>::type registerModelImpl(RegistryItemCreator creator, QString const &category) { const QString name = ModelType::Name(); if (_registeredItemCreators.count(name) == 0) { _registeredItemCreators[name] = std::move(creator); _categories.insert(category); _registeredModelsCategory[name] = category; } } // THIS IS ADDED FOR c++11 template std::unique_ptr make_unique(Args &&... args) { return std::unique_ptr(new T(std::forward(args)...)); } private: // If the registered ModelType class has the static member method // // static Qstring Name(); // // use it. Otherwise use the non-static method: // // virtual QString name() const; template typename std::enable_if < !HasStaticMethodName::value >::type registerModelImpl(RegistryItemCreator creator, QString const &category) { const QString name = creator()->name(); if (_registeredItemCreators.count(name) == 0) { _registeredItemCreators[name] = std::move(creator); _categories.insert(category); _registeredModelsCategory[name] = category; } } }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/Export.hpp000066400000000000000000000031451361521262000230610ustar00rootroot00000000000000#pragma once #include "Compiler.hpp" #include "OperatingSystem.hpp" #ifdef NODE_EDITOR_PLATFORM_WINDOWS # define NODE_EDITOR_EXPORT __declspec(dllexport) # define NODE_EDITOR_IMPORT __declspec(dllimport) # define NODE_EDITOR_LOCAL #elif \ NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR >= 4 || \ defined (NODE_EDITOR_COMPILER_CLANG) # define NODE_EDITOR_EXPORT __attribute__((visibility("default"))) # define NODE_EDITOR_IMPORT __attribute__((visibility("default"))) # define NODE_EDITOR_LOCAL __attribute__((visibility("hidden"))) #else # define NODE_EDITOR_EXPORT # define NODE_EDITOR_IMPORT # define NODE_EDITOR_LOCAL #endif #ifdef __cplusplus # define NODE_EDITOR_DEMANGLED extern "C" #else # define NODE_EDITOR_DEMANGLED #endif #if defined (NODE_EDITOR_SHARED) && !defined (NODE_EDITOR_STATIC) # ifdef NODE_EDITOR_EXPORTS # define NODE_EDITOR_PUBLIC NODE_EDITOR_EXPORT # else # define NODE_EDITOR_PUBLIC NODE_EDITOR_IMPORT # endif # define NODE_EDITOR_PRIVATE NODE_EDITOR_LOCAL #elif !defined (NODE_EDITOR_SHARED) && defined (NODE_EDITOR_STATIC) # define NODE_EDITOR_PUBLIC # define NODE_EDITOR_PRIVATE #elif defined (NODE_EDITOR_SHARED) && defined (NODE_EDITOR_STATIC) # ifdef NODE_EDITOR_EXPORTS # error "Cannot build as shared and static simultaneously." # else # error "Cannot link against shared and static simultaneously." # endif #else # ifdef NODE_EDITOR_EXPORTS # error "Choose whether to build as shared or static." # else # error "Choose whether to link against shared or static." # endif #endif qnodeeditor-2.1.5+ds1/include/nodes/internal/FlowScene.hpp000066400000000000000000000114541361521262000234670ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "QUuidStdHash.hpp" #include "Export.hpp" #include "DataModelRegistry.hpp" #include "TypeConverter.hpp" #include "memory.hpp" namespace QtNodes { class NodeDataModel; class FlowItemInterface; class Node; class NodeGraphicsObject; class Connection; class ConnectionGraphicsObject; class NodeStyle; /// Scene holds connections and nodes. class NODE_EDITOR_PUBLIC FlowScene : public QGraphicsScene { Q_OBJECT public: FlowScene(std::shared_ptr registry, QObject *parent = Q_NULLPTR); FlowScene(QObject *parent = Q_NULLPTR); ~FlowScene(); public: std::shared_ptr createConnection(PortType connectedPort, Node &node, PortIndex portIndex); std::shared_ptr createConnection(Node &nodeIn, PortIndex portIndexIn, Node &nodeOut, PortIndex portIndexOut, TypeConverter const &converter = TypeConverter{}); std::shared_ptr restoreConnection(QJsonObject const &connectionJson); void deleteConnection(Connection &connection); Node &createNode(std::unique_ptr &&dataModel); Node &restoreNode(QJsonObject const &nodeJson); void removeNode(Node &node); DataModelRegistry ®istry() const; void setRegistry(std::shared_ptr registry); void iterateOverNodes(std::function const &visitor); void iterateOverNodeData(std::function const &visitor); void iterateOverNodeDataDependentOrder(std::function const &visitor); QPointF getNodePosition(Node const &node) const; void setNodePosition(Node &node, QPointF const &pos) const; QSizeF getNodeSize(Node const &node) const; public: std::unordered_map > const &nodes() const; std::unordered_map > const &connections() const; std::vector allNodes() const; std::vector selectedNodes() const; public: void clearScene(); void save() const; void load(); QByteArray saveToMemory() const; void loadFromMemory(const QByteArray &data); Q_SIGNALS: /** * @brief Node has been created but not on the scene yet. * @see nodePlaced() */ void nodeCreated(Node &n); /** * @brief Node has been added to the scene. * @details Connect to this signal if need a correct position of node. * @see nodeCreated() */ void nodePlaced(Node &n); void nodeDeleted(Node &n); void connectionCreated(Connection const &c); void connectionDeleted(Connection const &c); void nodeMoved(Node &n, const QPointF &newLocation); void nodeDoubleClicked(Node &n); void connectionHovered(Connection &c, QPoint screenPos); void connectionClicked(Connection &c, QPoint screenPos); void nodeHovered(Node &n, QPoint screenPos); void connectionHoverLeft(Connection &c); void nodeHoverLeft(Node &n); void nodeContextMenu(Node &n, const QPointF &pos); private: using SharedConnection = std::shared_ptr; using UniqueNode = std::unique_ptr; std::unordered_map _connections; std::unordered_map _nodes; std::shared_ptr _registry; private Q_SLOTS: void setupConnectionSignals(Connection const &c); void sendConnectionCreatedToNodes(Connection const &c); void sendConnectionDeletedToNodes(Connection const &c); Q_SIGNALS: /** * @brief Node has been added to the scene. * @details Connect to this signal if need a correct position of node. * @see nodeCreated() */ void nodeClicked(Node &n); private Q_SLOTS: void killConnection(Connection &connection); }; Node * locateNodeAt(QPointF scenePoint, FlowScene &scene, QTransform const &viewTransform); } qnodeeditor-2.1.5+ds1/include/nodes/internal/FlowView.hpp000066400000000000000000000032311361521262000233360ustar00rootroot00000000000000#pragma once #include #include "Export.hpp" namespace QtNodes { class FlowScene; class NODE_EDITOR_PUBLIC FlowView : public QGraphicsView { Q_OBJECT public: FlowView(QWidget *parent = Q_NULLPTR); FlowView(FlowScene *scene, QWidget *parent = Q_NULLPTR); FlowView(const FlowView &) = delete; FlowView operator=(const FlowView &) = delete; QAction * clearSelectionAction() const; QAction * deleteSelectionAction() const; void setScene(FlowScene *scene); FlowScene *scene(); public Q_SLOTS: virtual void scaleUp(); virtual void scaleDown(); virtual void deleteSelectedNodes(); protected: void contextMenuEvent(QContextMenuEvent *event) override; void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void keyReleaseEvent(QKeyEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void drawBackground(QPainter *painter, const QRectF &r) override; void showEvent(QShowEvent *event) override; private: QAction *_clearSelectionAction; QAction *_deleteSelectionAction; QPointF _clickPos; FlowScene *_scene; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/FlowViewStyle.hpp000066400000000000000000000012461361521262000243630ustar00rootroot00000000000000#pragma once #include #include "Export.hpp" #include "Style.hpp" namespace QtNodes { class NODE_EDITOR_PUBLIC FlowViewStyle : public Style { public: FlowViewStyle(); FlowViewStyle(QString jsonText); public: static void setStyle(QString jsonText); private: void loadJsonText(QString jsonText) override; void loadJsonFile(QString fileName) override; void loadJsonFromByteArray(QByteArray const &byteArray) override; public: QColor BackgroundColor; QColor FineGridColor; QColor CoarseGridColor; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/Node.hpp000066400000000000000000000077571361521262000225020ustar00rootroot00000000000000#pragma once #include #include #include #include "PortType.hpp" #include "Export.hpp" #include "NodeState.hpp" #include "NodeGeometry.hpp" #include "NodeData.hpp" #include "NodeGraphicsObject.hpp" #include "ConnectionGraphicsObject.hpp" #include "Serializable.hpp" #include "memory.hpp" namespace QtNodes { class Connection; class ConnectionState; class NodeGraphicsObject; class NodeDataModel; class NODE_EDITOR_PUBLIC Node : public QObject , public Serializable { Q_OBJECT public: /// NodeDataModel should be an rvalue and is moved into the Node Node(std::unique_ptr &&dataModel); virtual ~Node(); public: QJsonObject save() const override; void restore(QJsonObject const &json) override; public: QUuid id() const; void reactToPossibleConnection(PortType, NodeDataType const &, QPointF const &scenePoint); void resetReactionToConnection(); public: NodeGraphicsObject const & nodeGraphicsObject() const; NodeGraphicsObject & nodeGraphicsObject(); void setGraphicsObject(std::unique_ptr &&graphics); NodeGeometry & nodeGeometry(); NodeGeometry const & nodeGeometry() const; NodeState const & nodeState() const; NodeState & nodeState(); NodeDataModel * nodeDataModel() const; public Q_SLOTS: // data propagation /// Propagates incoming data to the underlying model. void propagateData(PortIndex inPortIndex) const; /// Fetches data from model's OUT #index port /// and propagates it to the connection void onDataUpdated(PortIndex index); /// update the graphic part if the size of the embeddedwidget changes void onNodeSizeUpdated(); public Q_SLOTS: // data propagation /// Propagates incoming data to the underlying model. void propagateData(std::shared_ptr nodeData, PortIndex inPortIndex) const; /// Reallocate NodeState's connection sets to account for the new number of /// input/output ports /// NB: There is no general way of knowing how to maintain connections when /// port count changes, especially when the removed ones are not the last /// one, resulting in port shift that may plug connections of the wrong data /// type. For now, the best thing to do is to first remove all connections, /// change port count and then rebuild the connections appropriately, all of /// this from the node's data model. A more generic solution would be to /// split this port into insertPorts(row, count) and removePorts(row, count) void onPortCountChanged(); Q_SIGNALS: /// Ask flow scene to remove this connection void killConnection(Connection &connection); private: /// Recalculate the nodes visuals. A data change can result in the node /// taking more space than before, so this forces a recalculate+repaint on /// the affected node void recalculateVisuals() const; private: // addressing QUuid _uid; // data std::unique_ptr _nodeDataModel; NodeState _nodeState; // painting NodeGeometry _nodeGeometry; std::unique_ptr _nodeGraphicsObject; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/NodeData.hpp000066400000000000000000000012431361521262000232540ustar00rootroot00000000000000#pragma once #include #include "Export.hpp" namespace QtNodes { struct NodeDataType { QString id; QString name; }; /// Class represents data transferred between nodes. /// @param type is used for comparing the types /// The actual data is stored in subtypes class NODE_EDITOR_PUBLIC NodeData { public: virtual ~NodeData() = default; virtual bool sameType(NodeData const &nodeData) const { return (this->type().id == nodeData.type().id); } /// Type for inner use virtual NodeDataType type() const = 0; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/NodeDataModel.hpp000066400000000000000000000103751361521262000242430ustar00rootroot00000000000000#pragma once #include #include "PortType.hpp" #include "NodeData.hpp" #include "Serializable.hpp" #include "NodeGeometry.hpp" #include "NodePainterDelegate.hpp" #include "NodeStyle.hpp" #include "Export.hpp" #include "memory.hpp" namespace QtNodes { enum class NodeValidationState { Valid, Warning, Error }; class Connection; class StyleCollection; class NODE_EDITOR_PUBLIC NodeDataModel : public QObject , public Serializable { Q_OBJECT public: NodeDataModel(); virtual ~NodeDataModel() = default; /// Caption is used in GUI virtual QString caption() const = 0; /// It is possible to hide caption in GUI virtual bool captionVisible() const { return true; } /// Port caption is used in GUI to label individual ports virtual QString portCaption(PortType, PortIndex) const { return QString(); } /// It is possible to hide port caption in GUI virtual bool portCaptionVisible(PortType, PortIndex) const { return false; } /// Name makes this model unique virtual QString name() const = 0; public: QJsonObject save() const override; public: virtual unsigned int nPorts(PortType portType) const = 0; virtual NodeDataType dataType(PortType portType, PortIndex portIndex) const = 0; public: enum class ConnectionPolicy { One, Many }; ConnectionPolicy portConnectionPolicy(PortType portType, PortIndex portIndex) const; virtual ConnectionPolicy portOutConnectionPolicy(PortIndex) const { return ConnectionPolicy::Many; } virtual ConnectionPolicy portInConnectionPolicy(PortIndex) const { return ConnectionPolicy::One; } NodeStyle const & nodeStyle() const; void setNodeStyle(NodeStyle const &style); public: /// Triggers the algorithm virtual void setInData(std::shared_ptr nodeData, PortIndex port) = 0; virtual void setInData(std::vector > nodeData, PortIndex port); virtual std::shared_ptr outData(PortIndex port) = 0; virtual QWidget * embeddedWidget() = 0; virtual bool resizable() const { return false; } virtual NodeValidationState validationState() const { return NodeValidationState::Valid; } virtual QString validationMessage() const { return QString(""); } virtual NodePainterDelegate * painterDelegate() const { return nullptr; } public Q_SLOTS: virtual void inputConnectionCreated(Connection const &) {} virtual void inputConnectionDeleted(Connection const &) {} virtual void outputConnectionCreated(Connection const &) {} virtual void outputConnectionDeleted(Connection const &) {} Q_SIGNALS: void dataUpdated(PortIndex index); void dataInvalidated(PortIndex index); void computingStarted(); void computingFinished(); void embeddedWidgetSizeUpdated(); //void //portCountChanged(); private: NodeStyle _nodeStyle; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/NodeGeometry.hpp000066400000000000000000000127561361521262000242110ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include "PortType.hpp" #include "Export.hpp" #include "memory.hpp" namespace QtNodes { class NodeState; class NodeDataModel; class Node; class NODE_EDITOR_PUBLIC NodeGeometry { public: NodeGeometry(std::unique_ptr const &dataModel); public: unsigned int height() const { return _height; } void setHeight(unsigned int h) { _height = h; } unsigned int width() const { return _width; } void setWidth(unsigned int w) { _width = w; } unsigned int entryHeight() const { return _entryHeight; } void setEntryHeight(unsigned int h) { _entryHeight = h; } unsigned int entryWidth() const { return _entryWidth; } void setEntryWidth(unsigned int w) { _entryWidth = w; } unsigned int spacing() const { return _spacing; } void setSpacing(unsigned int s) { _spacing = s; } bool hovered() const { return _hovered; } void setHovered(unsigned int h) { _hovered = h; } unsigned int nSources() const; unsigned int nSinks() const; void updatePortCount(); QPointF const & draggingPos() const { return _draggingPos; } void setDraggingPosition(QPointF const &pos) { _draggingPos = pos; } unsigned int portsXoffset() const { return _portsXoffset; } public: QRectF entryBoundingRect() const; QRectF boundingRect() const; /// Updates size unconditionally void recalculateSize() const; /// Updates size if the QFontMetrics is changed void recalculateSize(QFont const &font) const; // TODO removed default QTransform() QPointF portScenePosition(PortIndex index, PortType portType, QTransform const &t = QTransform()) const; PortIndex checkHitScenePoint(PortType portType, QPointF point, QTransform const &t = QTransform()) const; QRect resizeRect() const; /// Returns the position of a widget on the Node surface QPointF widgetPosition() const; /// Returns the maximum height a widget can be without causing the node to grow. int equivalentWidgetHeight() const; unsigned int validationHeight() const; unsigned int validationWidth() const; static QPointF calculateNodePositionBetweenNodePorts(PortIndex targetPortIndex, PortType targetPort, Node *targetNode, PortIndex sourcePortIndex, PortType sourcePort, Node *sourceNode, Node &newNode); static QRectF calculateDocRect(const QTextDocument &td); private: unsigned int captionHeight() const; unsigned int captionWidth() const; unsigned int portWidth(PortType portType) const; private: // some variables are mutable because // we need to change drawing metrics // corresponding to fontMetrics // but this doesn't change constness of Node mutable unsigned int _width; mutable unsigned int _height; unsigned int _entryWidth; mutable unsigned int _inputPortWidth; mutable unsigned int _outputPortWidth; mutable unsigned int _entryHeight; unsigned int _spacing; bool _hovered; unsigned int _nSources; unsigned int _nSinks; QPointF _draggingPos; std::unique_ptr const &_dataModel; mutable QFontMetrics _fontMetrics; mutable QFontMetrics _boldFontMetrics; private: QRectF portRect(PortType portType, const PortIndex &index) const; private: unsigned int _portsXoffset; mutable qreal _captionHeight; mutable qreal _captionWidth; mutable QMap _cachedPortRects[2]; mutable bool _entryHeightCalculated; mutable bool _portsWidthCalculated; mutable bool _captionHeightCalculated; mutable bool _captionWidthCalculated; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/NodeGraphicsObject.hpp000066400000000000000000000047551361521262000253050ustar00rootroot00000000000000#pragma once #include #include #include "Connection.hpp" #include "NodeGeometry.hpp" #include "NodeState.hpp" #include "Export.hpp" class QGraphicsProxyWidget; namespace QtNodes { class FlowScene; class FlowItemEntry; /// Class reacts on GUI events, mouse clicks and /// forwards painting operation. class NODE_EDITOR_PUBLIC NodeGraphicsObject : public QGraphicsObject { Q_OBJECT public: NodeGraphicsObject(FlowScene &scene, Node &node); virtual ~NodeGraphicsObject(); Node & node(); Node const & node() const; QRectF boundingRect() const override; void setGeometryChanged(); /// Visits all attached connections and corrects /// their corresponding end points. void moveConnections() const; enum { Type = UserType + 1 }; int type() const override { return Type; } void lock(bool locked); protected: void paint(QPainter *painter, QStyleOptionGraphicsItem const *option, QWidget *widget = 0) override; QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; void hoverMoveEvent(QGraphicsSceneHoverEvent *) override; void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; private: void embedQWidget(); private: FlowScene &_scene; Node &_node; bool _locked; // either nullptr or owned by parent QGraphicsItem QGraphicsProxyWidget *_proxyWidget; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/NodePainterDelegate.hpp000066400000000000000000000007101361521262000254360ustar00rootroot00000000000000#pragma once #include #include "NodeGeometry.hpp" #include "Export.hpp" namespace QtNodes { /// Class to allow for custom painting class NODE_EDITOR_PUBLIC NodePainterDelegate { public: virtual ~NodePainterDelegate() = default; virtual void paint(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model) = 0; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/NodeState.hpp000066400000000000000000000045571361521262000234760ustar00rootroot00000000000000#pragma once #include #include #include #include "Export.hpp" #include "PortType.hpp" #include "NodeData.hpp" #include "memory.hpp" namespace QtNodes { class Connection; class NodeDataModel; /// Contains vectors of connected input and output connections. /// Stores bool for reacting on hovering connections class NODE_EDITOR_PUBLIC NodeState { public: enum ReactToConnectionState { REACTING, NOT_REACTING }; public: NodeState(std::unique_ptr const &model); public: using ConnectionPtrSet = std::unordered_map; /// Returns vector of connections ID. /// Some of them can be empty (null) std::vector const & getEntries(PortType) const; std::vector & getEntries(PortType); ConnectionPtrSet connections(PortType portType, PortIndex portIndex) const; void setConnection(PortType portType, PortIndex portIndex, Connection &connection); void eraseConnection(PortType portType, PortIndex portIndex, QUuid id); ReactToConnectionState reaction() const; PortType reactingPortType() const; NodeDataType reactingDataType() const; void setReaction(ReactToConnectionState reaction, PortType reactingPortType = PortType::None, NodeDataType reactingDataType = NodeDataType()); bool isReacting() const; void setResizing(bool resizing); bool resizing() const; public: void updatePortCount(int inPorts, int outPorts); private: std::vector _inConnections; std::vector _outConnections; ReactToConnectionState _reaction; PortType _reactingPortType; NodeDataType _reactingDataType; bool _resizing; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/NodeStyle.hpp000066400000000000000000000023531361521262000235060ustar00rootroot00000000000000#pragma once #include #include "Export.hpp" #include "Style.hpp" namespace QtNodes { class NODE_EDITOR_PUBLIC NodeStyle : public Style { public: NodeStyle(); NodeStyle(QString jsonText); public: static void setNodeStyle(QString jsonText); private: void loadJsonText(QString jsonText) override; void loadJsonFile(QString fileName) override; void loadJsonFromByteArray(QByteArray const &byteArray) override; public: QColor NormalBoundaryColor; QColor SelectedBoundaryColor; QColor GradientColor0; QColor GradientColor1; QColor GradientColor2; QColor GradientColor3; QColor ShadowColor; QColor FontColor; QColor FontColorFaded; QColor ConnectionPointColor; QColor FilledConnectionPointColor; QColor WarningColor; QColor ErrorColor; float PenWidth; float HoveredPenWidth; float ConnectionPointDiameter; float Opacity; public: QString PortTextCss; QString NodeCaptionCss; }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/OperatingSystem.hpp000066400000000000000000000075551361521262000247460ustar00rootroot00000000000000#pragma once #if \ defined (__CYGWIN__) || \ defined (__CYGWIN32__) # define NODE_EDITOR_PLATFORM "Cygwin" # define NODE_EDITOR_PLATFORM_CYGWIN # define NODE_EDITOR_PLATFORM_UNIX # define NODE_EDITOR_PLATFORM_WINDOWS #elif \ defined (_WIN16) || \ defined (_WIN32) || \ defined (_WIN64) || \ defined (__WIN32__) || \ defined (__TOS_WIN__) || \ defined (__WINDOWS__) # define NODE_EDITOR_PLATFORM "Windows" # define NODE_EDITOR_PLATFORM_WINDOWS #elif \ defined (macintosh) || \ defined (Macintosh) || \ defined (__TOS_MACOS__) || \ (defined (__APPLE__) && defined (__MACH__)) # define NODE_EDITOR_PLATFORM "Mac" # define NODE_EDITOR_PLATFORM_MAC # define NODE_EDITOR_PLATFORM_UNIX #elif \ defined (linux) || \ defined (__linux) || \ defined (__linux__) || \ defined (__TOS_LINUX__) # define NODE_EDITOR_PLATFORM "Linux" # define NODE_EDITOR_PLATFORM_LINUX # define NODE_EDITOR_PLATFORM_UNIX #elif \ defined (__FreeBSD__) || \ defined (__OpenBSD__) || \ defined (__NetBSD__) || \ defined (__bsdi__) || \ defined (__DragonFly__) # define NODE_EDITOR_PLATFORM "BSD" # define NODE_EDITOR_PLATFORM_BSD # define NODE_EDITOR_PLATFORM_UNIX #elif \ defined (sun) || \ defined (__sun) # define NODE_EDITOR_PLATFORM "Solaris" # define NODE_EDITOR_PLATFORM_SOLARIS # define NODE_EDITOR_PLATFORM_UNIX #elif \ defined (_AIX) || \ defined (__TOS_AIX__) # define NODE_EDITOR_PLATFORM "AIX" # define NODE_EDITOR_PLATFORM_AIX # define NODE_EDITOR_PLATFORM_UNIX #elif \ defined (hpux) || \ defined (_hpux) || \ defined (__hpux) # define NODE_EDITOR_PLATFORM "HPUX" # define NODE_EDITOR_PLATFORM_HPUX # define NODE_EDITOR_PLATFORM_UNIX #elif \ defined (__QNX__) # define NODE_EDITOR_PLATFORM "QNX" # define NODE_EDITOR_PLATFORM_QNX # define NODE_EDITOR_PLATFORM_UNIX #elif \ defined (unix) || \ defined (__unix) || \ defined (__unix__) # define NODE_EDITOR_PLATFORM "Unix" # define NODE_EDITOR_PLATFORM_UNIX #endif #ifndef NODE_EDITOR_PLATFORM # error "Current platform is not supported." #endif qnodeeditor-2.1.5+ds1/include/nodes/internal/PortType.hpp000066400000000000000000000021561361521262000233670ustar00rootroot00000000000000#pragma once #include #include namespace QtNodes { enum class PortType { None, In, Out }; static const int INVALID = -1; using PortIndex = int; struct Port { PortType type; PortIndex index; Port() : type(PortType::None) , index(INVALID) {} Port(PortType t, PortIndex i) : type(t) , index(i) {} bool indexIsValid() { return index != INVALID; } bool portTypeIsValid() { return type != PortType::None; } }; //using PortAddress = std::pair; inline PortType oppositePort(PortType port) { PortType result = PortType::None; switch (port) { case PortType::In: result = PortType::Out; break; case PortType::Out: result = PortType::In; break; default: break; } return result; } } qnodeeditor-2.1.5+ds1/include/nodes/internal/QStringStdHash.hpp000066400000000000000000000005061361521262000244440ustar00rootroot00000000000000#pragma once #include #include #include namespace std { #if QT_VERSION < QT_VERSION_CHECK(5,14,0) template<> struct hash { inline std::size_t operator()(QString const &s) const { return qHash(s); } }; #endif } qnodeeditor-2.1.5+ds1/include/nodes/internal/QUuidStdHash.hpp000066400000000000000000000004341361521262000241040ustar00rootroot00000000000000#pragma once #include #include #include namespace std { template<> struct hash { inline std::size_t operator()(QUuid const &uid) const { return qHash(uid); } }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/Serializable.hpp000066400000000000000000000005131361521262000242020ustar00rootroot00000000000000#pragma once #include namespace QtNodes { class Serializable { public: virtual ~Serializable() = default; virtual QJsonObject save() const = 0; virtual void restore(QJsonObject const & /*p*/) {} }; } qnodeeditor-2.1.5+ds1/include/nodes/internal/Style.hpp000066400000000000000000000005331361521262000226760ustar00rootroot00000000000000#pragma once #include class Style { public: virtual ~Style() = default; private: virtual void loadJsonText(QString jsonText) = 0; virtual void loadJsonFile(QString fileName) = 0; virtual void loadJsonFromByteArray(QByteArray const &byteArray) = 0; }; qnodeeditor-2.1.5+ds1/include/nodes/internal/TypeConverter.hpp000066400000000000000000000006371361521262000244140ustar00rootroot00000000000000#pragma once #include "NodeData.hpp" #include "memory.hpp" #include namespace QtNodes { using SharedNodeData = std::shared_ptr; // a function taking in NodeData and returning NodeData using TypeConverter = std::function; // data-type-in, data-type-out using TypeConverterId = std::pair; } qnodeeditor-2.1.5+ds1/include/nodes/internal/memory.hpp000066400000000000000000000014131361521262000231040ustar00rootroot00000000000000#pragma once #include #include namespace QtNodes { namespace detail { #if (!defined(_MSC_VER) && (__cplusplus < 201300)) || \ ( defined(_MSC_VER) && (_MSC_VER < 1800)) //_MSC_VER == 1800 is Visual Studio 2013, which is already somewhat C++14 compilant, // and it has make_unique in it's standard library implementation template std::unique_ptr make_unique(Args &&... args) { return std::unique_ptr(new T(std::forward(args)...)); } #else template std::unique_ptr make_unique(Args &&... args) { return std::make_unique(std::forward(args)...); } #endif } } qnodeeditor-2.1.5+ds1/resources/000077500000000000000000000000001361521262000165275ustar00rootroot00000000000000qnodeeditor-2.1.5+ds1/resources/DefaultStyle.json000066400000000000000000000022431361521262000220300ustar00rootroot00000000000000{ "FlowViewStyle": { "BackgroundColor": [53, 53, 53], "FineGridColor": [60, 60, 60], "CoarseGridColor": [25, 25, 25] }, "NodeStyle": { "NormalBoundaryColor": [255, 255, 255], "SelectedBoundaryColor": [255, 165, 0], "GradientColor0": "gray", "GradientColor1": [80, 80, 80], "GradientColor2": [64, 64, 64], "GradientColor3": [58, 58, 58], "ShadowColor": [20, 20, 20], "FontColor" : "white", "FontColorFaded" : "gray", "ConnectionPointColor": [169, 169, 169], "FilledConnectionPointColor": "cyan", "ErrorColor": "red", "WarningColor": [128, 128, 0], "PenWidth": 1.0, "HoveredPenWidth": 1.5, "ConnectionPointDiameter": 8.0, "Opacity": 0.8, "PortTextCss": "p {margin : 0; padding : 0; font-size : 10px}", "NodeCaptionCss": "p {margin : 0; padding : 0; font-size : 10px}" }, "ConnectionStyle": { "ConstructionColor": "gray", "NormalColor": "darkcyan", "SelectedColor": [100, 100, 100], "SelectedHaloColor": "orange", "HoveredColor": "lightcyan", "LineWidth": 3.0, "ConstructionLineWidth": 2.0, "PointDiameter": 10.0, "UseDataDefinedColors": false } } qnodeeditor-2.1.5+ds1/resources/QNodeEditor_resources.qrc000066400000000000000000000002041361521262000235010ustar00rootroot00000000000000 DefaultStyle.json convert.png qnodeeditor-2.1.5+ds1/resources/convert.png000066400000000000000000000237331361521262000207250ustar00rootroot00000000000000‰PNG  IHDR\r¨fbKGDÿÿÿ ½§“ pHYs.v.vã*#tIMEâ 3,|»´ IDATxÚíy|UյǿûÜ)÷&!óH˜Aœ­Š8ê°ê«sëÔjí¬µvx¢«öÙÖg[ßÓmÕÖ*I: œÀyDÀÜÌ äN¹÷ž³ßÁªÈHî½çܬïçãG…äžsöÝëwÖ^{íµ@AAAAAAAAAAAAAAAAAAAAAAAAAAAAA„ýCÉ8”‡› ñåTcÕV5U*BëBh À îü¾]À¨=|jˆíüï0Є@oµE­ºAQ´¡u;†«Kµ³¦¬EÊ’/F@×û o–šŒ²&£Õd”ž ŒUømvÇq4[Pº e4¡iBÑ„¶6bñ «#ò¥Š»¢µbiûD,ó( uZ ú(``dÉSZÀ&o õ[(Þ@¹^'§ü-樤L€‘Ã’æÑ¸Ü'¢9Ôñ òGèhD@½Œf Š5¸Œ˜W¾A&‰@v°HLo9 ­fƒ:8 ¨‘Ù+ÀJ´zÅÓÔV¼RZ†EÀ, ŽÇR§> 8(•AAàI,µ”3+›dHDìÃZí¡£mZ‹ù(&Ë ¤”·Ñ4b¨FB]«8ï°¸ ‰@zy¤#Wr¨O¢t-P$ƒ’úP4`鉘K9oLT†D 5,oÊ!œ³¥/5ðÉ ØMƒz€ác‘68¾m–¾8‡¤Á žæA\ú™WµZ‚ˆ"ûGCË8”q%šË@–q´k°Í=xô½œ1ºYÆC`÷< ]Úê@Å<²' GÀ–‚¾ƒÚªe∠°¤¥—ú"š«=ú‘» þåù?jKvˆŒDkŸLÒüŠK€ØÄˆ܃‹Û˜_µI`d¼ñÁP_.Üb`¡ø71¯êM€l¤¾í$°nFqªÌwaÏq½ŒŸPWù¼@6ÐÐz<èï d~ ƒG?Åõ,¬~IÀ‘†¿õàú!P'“Y‚Gð\î³õ”bö ÀcU˜‰€ºÙʆ‡¨»IªùdE›€yàM/¹%_}3{.y%C! ÜŠ ý„Ú)ý"v¡>x.Š[±2G…4°ƒo0¿ªQ “<Ú:CߢVæ¤0¸ÆÉ9΀åÚM¤íj´þ!Š<™‡B‰ ¹™Hå­œ§L€T³4x޹'؈çÑês,¨|C eoýÖïßC2ø{~L òGN©vì ¨o›ˆÒ÷‚>Yæ˜à^Àr}š…åëí~£öÞ'×ZѼe½!Æ/8ˆéæ‹4/à@y¼»€xÿÝ Tâg¢yˆDìJΞÐ+0X·e< v…,a Ÿb~ÕYìÓøƒ— UbüB1‹•Ô·~V<€=1忸zHЦÀ’bO‚mÝê÷„º¾l—~öðk.&ÒúÏ¡¿ÇP,:DŽvŽ è«?É’–R(ÍeºWs†úQó*|\6>À1…™h‚½f`¨çYÒrðÈ€úàLk 0,qÑØ ¸áà|™d‚Ý™ˆ¡VÓÐ2kd @}Ë9(–‚.ŽpqR±€нÌ*•Æ=‚í)õOZ/ÊÔ d&¥¶1x š?çõ/ÀøPHó†ƒóXµº_‚{ûòä{ òÝŠϾß1SNjB¦fGÂ’| ÿLCku•¿Lÿj$Ý4´~ôçµ=†â…9e”ú>:‰¿üJ/ÿh‰°å%”ù ªý.ªs\TåŒö»¨ö»¨ô¹(ò*òÝFŸãÚWзèì·hë7iï·Ø6y/œä½°ÉÆp’˜)ê»_hndAÕÍÙ+­?Dëï÷ÇÖUæðÛc>¾’Ø1™½²“Dº†‚±~Só=LÉsqp¾‡Éy.¦ä¹ñÊs¶E?„u}I^ê³®/‰èÂ^î,¨ú¯ì€†àÀ¢T|ô}Ó‹™YêÝíßÝøÖîÞqü¼pqL‘—£ =Sèá <÷ßà™ ”Ô¼º=Á‹=q^êMðbO‚^YNìÊ-ÔU}+{ >x-Š[RñÑã.VžRÆž^z]q‹OwJ:çµãVpt¡—J¼SèáèB%Þì¬oª¡$+;ã<ÕÑÏs]qú%p°ˆºª›œ/Áo¡ùYª>þÛSó¹zRî^æöõ!n[²õ·=6àbf©%^f•zå™c¦fMO‚'Ûc,këgkÔ¹ ¹ŽU·:WZ?ú®T¾)_˜[N™oïÆ653žî ³ß>®¦ÇPœXìe^¥SË}Tç¸ä·ÞéKÒØã‘–ï…“#OWS[õç @}°Å?HáVãž‚»ãÞ;ûffÁæ¸3K¼ÔUåpz¹oľå”õ¡$õÁn‹Ò1ž\H]Õ΀†–ihµ<Õ;O/tÂORÜlJóÄq+˜SæãìÑ~N/÷92pg;‹Ðð|wœ‡[¢, Æß9Е–1—…ÏÚ_kŸŒi®ÊS9"c.Ví%ø·;–c|éåôÔe˜Väáìj? ªr(öÊ›>U„’š‡[¢üis„·û²z‰ÐŽ©NàÌÊ&û Àòö<Âæs(Kõh|kj>_ÞGðïã *8ë™.^êM¤äžòÝŠ3«ý\:.À!ùR·4ݼ¾=Áâæ(ß%šÉo£ã'³`\ý@kEcëC¤¡„—[ÁósË)÷íÿ›uUWœ ŸïÖû™^ìåÓcýÔVæà3ÄÅÏ4=q‹ÅÍQîÞ¦£?Ër Ëx¡²–Eʲ—4´|ÔÒ1µ•9üî˜?Côé5=¬èZk7¡8£ÂÇ•råø±MIXšGƒ1îØfC(«–7QWµÈ>М¦ž4.üóqEœRvà§ýÞØ‘ nu×Êw+.à²ñ¹äéÇÒ°¬-Æoß §lù—îGBëZT/˼<ÒQ;ù*– '5~«gï_ðowìïA¡ÁgǸb|`P'ç{²ª+Îmïö±¶ÇñBÐÁ±CíK8´™¼H¸’÷¦Ëø.ãg8–Ù×OÍÔ¡™BÁ7¦äñÌìR¾>%OŒßáÌ(ñòð‰%Ü7½˜O8{éVŒæ>–kwæ`Zëu(NM×»œ?&0lžÄ§Çú÷ø÷E^ƒë§æóìœ2¾>%O’v²Œ™¥^–œTÂÝÓŠ8|”C…@s"áàõ™YÔ·‹RÏÞt=ï¼Êî:¦pØ>¯;nqò.…Þ™ëÊËÚ8®v Á?^×çÄ Ã$è©«^›>à7½ îI§ñ\<Æ?¼>”×àó>È%8½ÜÇÓ§”ñÓÃG‰ñ ° *‡³J¹éÐQ仵•ëuË›rÒ'¹Å7¤#ÙgW—=uþ®œ˜Ë‰%^<¡˜»§1. ‡rF*CqÅøÿš]ÆgÆpPÖö¡Ds¥g °tÛT,ã 'OxÝAy|eržÌR!m¼µ#É÷ÞÜÁšž¸n7‰eLcaÅ«©óiÓøCºß­à¼¿ÌH!½¯ÕQnþvb1·Uð±z“¶\ (ë´V©€im—£˜‘î'›[î£RÎË Šüçh?+f•ò™}'l|³3h^œš%Àòö<"æ»@UºŸëžãŠ˜[&uþ…̳¦'ε¯í°sq’6ⱃÛŽ|ð@8yC&Œ¿:ÇÅ)ÒäC° ÇyY6£„/Mʵk°oÎw†×hÜZƒv­é~šoNÉãkS$ø'Ø{\ûúv;4Š¡Ì)ÔÖlÀrÿ4ÆïRp®ÿ›rl‘‡Çf”ð)yØ,u íTÿ}ßö’ö)æÛ@Ú£p§Wø¸ûØ"™i‚íy¹7Á—_ée³}2 (ãj+6îí‡ö}À0¿— ã‡~Bê%zýõ%-BIM_R³}—&.yîGÑc dO{ мRÞðŽ.ôðØŒRnxc»]ÚÑy°ôMÀ§Ü¨o›ˆ²Ö‘&¢Õ9.ž™S&“l´ÄLÞíKÒ5 FM¶Å,¶FMZ¢&­1“¡ÔÒT ˆA‘× Ôk01×Íä<å¹™œçf´änÛ>°5Ê÷ßÜA$óeÉL”1uo^ÀÞ [™ß•‘âvŒñ‹ñ’¤†·v$xm{‚·û’¬ëKòN_òcoóáD3Ðu©+n±x®û£ÙrynÅä<7Gx˜^äez±gÄärœWãçØ"_|©7Ó…J]hó+ÀW÷ßXÖZNRoÒ¾çRðìœ2ª$ùg®ûšž8k{¬í‰óJoÂo›}26àÚ)ݲÝKˆššo¼¶ú`F—aúÍqœSÓµ@BEF6àç–ùÄøwa}(Éãmý¬êêç¹î„#;o‰˜l‰Dyh[€ƒòÜœVáã´rÓŠ¼d›Ãçw);÷•ÀOï,×n"m›@ÎÄß=­ˆÓËGvòORêÎ~Zc<ÙÞŸ}Õmw¡Æïâ?*|œ]íwz¥žœè¶Ÿ{±×w¢¶¨Ï•œ4Ïþš‰[­Êqñì þ™;¾¾u 1fO|d¶Íž’çæÜ?çTû©Èqf]†í ‹¹+;i·p«…ÔUÖv ð…LÝæH þ½NòÀÖ׸-62~×åÎßéãgëú˜Uêã¢1~N¯ÈqÔ¼¸éí>?€¾¨ß·°lÛ’Æ&ÒTâûø<3§lDtÊMXš%Á‹›£¬éŽ£öƘ€‹ËǸ`LÀö{–wôsÉš»ÝVE µU{÷LãÂL?Àì²ìo“ÝÞoñ§-þ¼%b«våv§9bróÛ}üb}ˆójü|v|.cmX½)”Ô|ûõvB/Z]üjï Õ…dè}”Í™BIî|/ÌÃÛ¢$åu?$»{S„{7G8{´Ÿ¯LÎc¼„à‡ïôѳkaQ}É®ðQ_jIËÁêíLÜZåÎàŸ;ËÖÿoìHðë ak‹a‰á;ngUûùòä\&æf¶!ëê}'mý5Løp3c—Eø¹™º¯óküYeü›"&W¿ÒKíª.[ÅøSERÃCÛ¢Ì]Ùɵ¯mÏX5bj®{}»ýc9uÕƒ2?#¢¤¢ÿÙ@wÜâ'ëú8ue'¶Ä$¸—&L ÝeæŠ~²®ï#½ÒÁOÓS@¹û%@ýæ"”·ƒ œü›[æãžãœ}ì7jjîØæMaG¤åf;e>ƒkÊ゚©îؾ¦'ΧžëvŠ—— +¿d؇<ßdêØïXgÿžhïgîÊN~µ!$Æo:ú-®} žéâåvŽ[šo½¾ÃIK<^ß©_z^&î¦"Ç`®CÓ~ƒ1“+ÖöpùÚ¶FM±:òúög?ÛÅwßÜ‘’Ó‘·¼²cI°}1ëã ?øÃtr~MÀ‘Á?SÃ9Ïvóx{¿X™¾«{7G˜³²“¥­Ãw2ï•Þw5…7 JÍø¨,k-&¤û> :4øçRÎ_ºŒÄeÁU/õrÅÚž!ï$,͵¯oÇ‘+>ÍQ<Þ]ð$91÷qJ©Ÿ ÿìø€4,q ·÷súª¡y¿Üf]_Ò©Cà¢?v ­é™¸‹‹Ç:{ëÏïR|}J®X”é‰x_uû~nßÚ‘ä6†œ=†:éP¤]Ê|§–ç8~"_`Jž[,Ê¡<´-JÝê.ÞÜ1¸‚¤†k_ßîütnÍáuhº¯á˜@Vdþ¹Ô@çbÁ¹l%9ó™nîÜÞçvÞC¼¾=‘ }ØÀ»¿±k:Þ é«^d(X=»ÌÑëÿŠ)œõL/õ&ÄšÎéå>~ù‰ÂÝ9^J2oUñìÈë6 'ó T|j:`–û¢€ïœ/Ö“<ÞÞOÝêNÖï²·oi¸þõÙbü.rÕTKœî+_4&ûÚ}M/örj¹41ÍšÂ&g>ÓŲ¶v îj ³¦'ž]ªÜS P“ÒyÍ2ŸÁé9Y9q®ŸšŸò¼s!=„’š«^êåwï…i ›Üº>”}©­ÑJ¥µåw¶ûý0‡ä»9§Zš™f –(ðqö³]IJòŒ‡m ­Št®•ÏÏò~ß<(¯¸YEWöVgm€J›Ì*óÙª|S*¨ñ»¸tœ¤ NX0ÚÊÒu½3*FFìšI¹¶¯\+(J Á. 8]×ôŠªƒqcnÆú]Œ ¸pQ‘ã¢ÜgdÅØþzCˆŸ¿’I&Ø™-І`°MB»ÏPTìA &äºÉsÈ›5fjf­è$“:‚méT4ã€cš±xŒ ÂØ€k§@¸pQíwÙj‡áO["|ç2Í»V4³jcoQãw¥uŸÞÔpÚ¿:X1&c¸z :ãÒ4% 謀?Œ ¸(ð ü¡¡5Æ^ê•©6HÐpr SóÝ´Æ,6GL¶D’l‰šl‰˜lŽ˜l 'é“î*Ã4Þ#HöEŽKQîÛ½@LÌs“{€Ý)Ïz¶‹{ä Ð`™Uêcñô½W‰Þž°þ-íýmýÄÆPRгޤãbNŒ?<ß=P6Z<÷M/ff©÷€ÿñ%bîô ’lŽ˜l‹šˆ>ü;ÐÚºPÆbèkןA¹o÷ñ7w°¢C ˆ–CG¹YzriJb6I -Ñ÷½“¶˜õhŽ˜#¥¡K¢¡u+èÑ2åR‹¡ö`ûÉG²°*ýÇâ–)ñ‡VECË» ¦ÈtìÆ˜€‹³JñØìlÅÞâBI¢ÎY_¼ã:Áv4GLîoŽr‰ÍÎVx Ž(08¢Àãìøƒ¢ÕRÛ¤ƒ¥`Wþ{}ˆsFû“º/øpüaK$¹Óƒ°2д¹Ñº%ÍÁaÐtÆ-þÐækS²£ðª[1 ¸€îrÄLÍ´§:RÒÂl Ðf€Ú&ÓL°3¿}/Lgög. ÆÒhü€¦]@°=aSsÇÆì?Yys4ÍW46`½›mp©¬ª:,À½[¢4G²÷d廡dú‹Žªä;oYå_]?5ŸGN*áÈIpÌ–æ–,®¯pß–Hº/©q»×,¬Ž y/[òØ"—Ž Pî3øû‰Åœ[#E:³…G‚ÑléÊób¦æoÛbé¾l3gT†ßo öF6 ¤×PÜrDïŸÙñŠ_YÀO…Tèr>–&+«,Õ·ÆèM¤Ù W¼ ï÷„¬€kÊÛm£Î‹Ç¸÷¸bJ½†X‘Ãyº£Ÿgº²«AÇ}[¢™¸ì󀡟uú >ÊÕö\Ùlf©—Çg•2·Lº÷8½Ó—5¹kBIÖf¢ã°ùpûVŽ ±z ÅíGìÓÍ/õüßqEÜtè(Ûå— ƒçµí ‚±¬x–Å[¢™3Ë|á8½x;hÇ.¾:9—©ùîA.}àŠñ9±˜‰¹n±&‡òówûpú¡¼~KóжŒ¸ÿï0oL÷‡c Õ¿œ8ˆ‡Žrsõ¤ýO=¢ÀCãŒ.ã—DhÒ6¹¿9âèghf ø7`ë«ÞÿÏGÅV:mÝ n=¢à€#ü¹®]ƒO(ÎúŽEÙÈ/Þ r°°¸9š™ kkéÇÀçû'à¨ðê'æîñHæþp|±—Çf–r鸀t÷u½ ‹—{™°>”dMwFÌ-ANÎS€8À § à”<÷°žËu)~xØ(þqbɰˆŠZŽ,ððèI%Cª˜IîoŽff'C±zÀÖ?¾¥uÂà ~vĨ”tá=ºÐCýI%Ü~TE’7`;Fy n:tžä\¡Ž[š¿o‹fêòK?bKù+S? ößbýÜø\Ž+Jò þs´Ÿ§f–rј.Yd·‚O °bV)WŒwöwÒØÚŸ¹–ã¦ëá:»Ò\ k×ÁpñÄÌRiœBIn[¢>Köž„êªr¸î ¼¬Ù¶=÷¹nžËÌúÿyêªNس õŸíìúÿ÷‘i5~€Éyn~st!9¾XN¦‘Y¥>N.á7Gfño 'y¾;S±vußÇlj7 ”?aÓ݀ό p|qæ‚>'—x©?¹„ÿVÄQ")ã”2žPÌâéEY½oK4SklŒvça}œ†Ö‡AŸe§«ñ»xbVé·çJOuôsûúc·¢ì„KÁüʾ4L[»v$niŽ{ªƒî̬ÿ£®jþ®¸'¿êÿÛ€~~D­Œ`n™¹e>VuŹ»)Ì“ýÒüc?)ô\8ÆÏ%ãY_Åiik¦Œ”ºsw¼{¨¨h¤­5TÙaà.ã·õ~ïŒ/3J¼lŠ˜Ü½)̃[£ŽÎPKÇz¸`L€³ªsðm–Å™K]ÞD¨¢qO/×ÝÓØú´þQ¦­2ÇÅS³JÉwPE¾¤æ¡­QÜ–l”RŸÁŽös~·u²™÷ÂIf¯èÌTòÏõÔVý|ÿ ~sx· ÈhAö?[ÄT8÷ ÿúP’‡¶Ey`ktD”¶Þ•Áéå>êªr˜]汕™~øN¿{/œ‰K÷£CmUÇþ @}ð×(®ÉÔ 3ÚÏ/*ÈŠ Ô°¼£Ÿ¥­1žlÏàZ0 Œö»˜Sæc~¥“J|#¾[ÜÒLª#SÉ?wRWuõžƒ½ñhë\z=öèL©Ïà©™¥Y™ŽkjXÛçŸmýü³-Æ&‡—»öŠéÅf—ù˜Sæqîý¾x´%ÆÕ¯ôfâÒ b~Õ¦€Æàýh.H÷ç»·Uèh÷°l $‡¼Ð“à…î8[£ö„"¯Á´BÓŠ¼WìáÈ>9F¹G.x¾›Õ™¨c¨Õ],¨¼jïá}±¤} †ù{Þ2Laì¾8)—ëÊQndKÌäùî8/ö$x7”äݾdFÜGÅ@êõÁùn¦æ{8$ßÍ!ùn&å¹¥ˆÊ i ›œ²¢#Á¿¦šÊ™•MC€úÖߣô•™Ä£ =üÏ' 3‚‹vtÇ-Ö‡’¬%Ù2i‰™tÅ-ºâ=;ÿ½¿Æ=ÊcïV”ù ªs\Tû]Œö»¨ñüÿÄ<·ír/œÆÞéã· þé?PW}å`æÁ¾y¤£wr±.ùnÅÏŽ(`aUŽÌª=ĺãÑÍçw×dÒïRä» rÝÊQí¶Jbgð¯3ÝÞ›&„Û}óÊ‚Ã# ­¿ýõLêù5~:J&°`{–c|éå ÿÿEmÕ󣃱+Ï" ˜éAýëÖ(³Wvðx{¿Ì0ÁÖd ß ¶aêÛûÓƒ€Ú’hn°ÃÀ¶Å,®XÛÃ_îÍLUUAØ["&Ïd䨝u «#Ã/u•÷¶©XŒ1we'µJ¡Á^üyK$ÃVP[µ_õ<öO”Òhu `›÷Ž~‹+_êå‹/÷fuvà–æÁ­i¯ù×¥¿€R:u° ò àv» z}0Æ©ÿêäÑñ„̲¬­?ý‘Ô"V¿³¿¿u`y¶*ô}àu» |g¿ÅÕ¯ôrîsݬëKÊL2Â}é?öû·È/˜ÔNéólZ:ì¹î8óVuòƒ·ûä\¾°[ÌM‹Í“Ui5‹~0/ešJ¤Oêj^Aó#»~ÁI ¿o 3{eÝ•J=0pîâ‚绹wsjÞÒ÷7GÒ›ö«õ·©«yå€Cºørí&Òº 8Þî_üä<7ß;8ŸSË}b#Þ„ÅoÞ sWS„„¥)ñ¬š]6¬ eI Ç?ÕN{úê>Yíçós9$_ÊZ9ž¸Åâæ(Ü>ࢫ «r¸óèÂ!Ŧ?ÕNGÊ×ÿúUÜÆÉœQ>=nêÛ&¢¬ç€2'O¬ãм|iR.§É®-i ›Ü³9Ì}ÍÑ×@ŠÑŽ,ððù ¹Ì¯ôᑺw_ß?ÖÖÏ}["¬îŠë~ûÌR/÷M/> ßýôšVt¤4ø×‡²N¡vôËï+©¢!xp?ÙØƒOÎáŠñ¹Œ•€aZÙ1¹¿9ÂÍÑ”æØ/ž^ĬÒýóøÒü‹£:T<‘Ç"•4¶^ÖHùuÒˆ¡º_4&À¼Ê¤0QjØ‘°x¼½Ÿ¿m‹²ª3ž–ìºCG¹Y×_°è\IDATzr)ûãèý|]¿N]ðO£¹œU÷¤ne‘jêƒ×¢¸%'iUŽ‹³GçpVµ_‚†Ã@ÌÔ<ÑÞÏß[¢<ÝÿwÒN:¹ã…|²zpu'Sœù§Ñê«,¨üujC é !¸¸1›'ï”<7 «Ä`B®,Ëö„Å¿:ã<ÙÞÏcm±ŒÞªñ»XqJ)ÞA¸K[c\•ªàŸæÛ,¨úYªŸ7}lCðçÀuÙ>¡pL‘‡3«üœVî“xÁnh ›<Ý1Ðé¹î8v;°y󡣸|ü¾ãןYÓÃÓ© þ}‹ºª[Ò5_ÓGcð&4ÿ5’&ûØ€‹™¥>N+÷1«Ô;¨7K¶Ñ·x¦+Ϊ®8+:úmßù¨Øk°z…¶EMN~º#ÇŠÕw¨«üI:_X¤Y®Aó+yÍer]Š¥>f—y9¾ØËä,í°³%bòRo‚—zã¬éNðV_ÂqDZ¿6%oNÙscì[Þ ñ« ¡áuú×Q[u[:Ÿ33ó¯¾å*”ú Y²Ex x Ž)ôpl‘‡c ½]èq\¿ƒö~‹u}I^Ý>`ð/÷&²¢ úÞ %5œ°¼}¿RŽ÷‰RWQ[yw&–¬™¡¡õBÐÿ HªÝN\ Êssx‡I¹n&溘œçf|À•Ñ$$SC{¿É{a“õ¡$ëú’ÿîY˜ÍeÙ÷tPhY[ŒÏ½8lÁ¿(¨ó¨«¬ÏÄ3föuSß2C=Œ¦DÌϸÕ@tzRž›‰¹nªr J}U9.J¼ƒ<·"°}üb¦&”ÔìHZ„’ší M_Ò¢-fŒ ôl‰Zl‹š´÷›ŒÄÊj{:(téšžŽàŸ¢ ¥Ïb~õªL=cæýÍÆ¶Ih«˜*¦>t"ϽïUU8i!¥Ç®…¶FMf KðO¯ÇÐ ™?z]&Ÿ/ókðÚŠô›'£Y%Ómè®úö„µÏÄø÷ÃI Æx©÷ƒz›iŽÝøËðæ—iã·‡œSÓEnå?“)'Ø üä>` ø÷ס6üPê÷”W.äôâívx>û…œZ/ý{ W¦Ÿ`O/"fÂg_ì9Ј‚ú u•°ÓsÙsÏ©¾å”úpˆL=Á>ÊC™Ï`ùÿÖ:—ºJÛ5Ó±ï¦óÃM…ør~æ\™~‚ƒ÷pž9å!;Þý³Nꃗ¢ø5/“IpPð „¡¿Amõ]v¾Mg¤5´Œõ'`¦Ì,Á<‡åº„…åëí~£ÎHÅ­«ÞL r.š±i?BAúQ\O¸r†Œß9À‡y,xw¡9Qæ›`#^Ä0®`~ÅkNºigFÓZѼ¥n•Ø€a"hn&Ry+ç)Ói7ïìÓ¨KZÆâR¿Fs¦ÌC!o¢Gpë/sÆèf§>AvG¯o9¥n–I)¤&”þµÕÿpúƒdO=ŠåÚM8x†ú±œ.RóÂ'„â6±Ÿ2gB,)û Ò,k-'¡¡øà‘Y+ I4wap#µUÙô`Ù[–«¾m"Jßú"Fxå!aï|xÅ÷©­z+0ûëò5¡ù#°¡pÀ¶ÿpuÕk³ù)GŽA4´Lup–xÂ^XŠÁ̯Z3vä½Û&¡Í¯€ú7’|äºÄ -ãPê›h.C’‰Fê ?„ÒÄP·3¿jÓHY7v‚ø%h®AêŽ6úºÿw,×3’Bàßo­X<­®jéé•]˜ ëQêw¼P¹ŒEÊ’!Ø=KšGcx>ú2ñ O3p7Êüµ5[e8Döú¶“@_†Òç£d@AZ=„¡óBå¿äm/0t–7åñŸÖy "C»EÓ€¡Cx)µSúeHDR'Qß¼z…ª(AÉÈìíê1õ#À2VGdPDÒËZí!Øz2µhjQ&ƒ’RÖ¡ÕR´~„h応x_ ›Yf>šSY@™ Êhý$¨'p[O8ùì½ÀHCkŲÖC1™â4³€ ˜½²ô3`<,§¶â ”’ff"Yä!XLG3ÅqhŽA‘7BG£Ôk Ÿý Iϳ|²¬E&‰ÀÈáí"Ðvp8XG ŒCÑúpÈžd$MŠWѼ†Ö¯£\¯RWÞ$owa·ÂÐì'Ï8ŒIÀD4PL@3xíµÜ!„b+° ͔ڀÖpYèëmâ¼Ã¤œ»€0l±…GÛËqé2P•`V¢(Uº¥ €Qh*œ…Â}BàßåSû÷·Ñb ì«GQª­·£t/¨^´îEщ¢M3†nÃpmåŒÊ°|1‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ ‚ Œþ™P›…[³wIEND®B`‚qnodeeditor-2.1.5+ds1/src/000077500000000000000000000000001361521262000153045ustar00rootroot00000000000000qnodeeditor-2.1.5+ds1/src/Connection.cpp000066400000000000000000000213711361521262000201130ustar00rootroot00000000000000#include "Connection.hpp" #include #include #include #include #include "Node.hpp" #include "FlowScene.hpp" #include "FlowView.hpp" #include "NodeGeometry.hpp" #include "NodeGraphicsObject.hpp" #include "NodeDataModel.hpp" #include "ConnectionState.hpp" #include "ConnectionGeometry.hpp" #include "ConnectionGraphicsObject.hpp" using QtNodes::Connection; using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::ConnectionState; using QtNodes::Node; using QtNodes::NodeData; using QtNodes::NodeDataType; using QtNodes::ConnectionGraphicsObject; using QtNodes::ConnectionGeometry; using QtNodes::TypeConverter; Connection:: Connection(PortType portType, Node &node, PortIndex portIndex) : _uid(QUuid::createUuid()) , _outPortIndex(INVALID) , _inPortIndex(INVALID) , _connectionState() { setNodeToPort(node, portType, portIndex); setRequiredPort(oppositePort(portType)); } Connection:: Connection(Node &nodeIn, PortIndex portIndexIn, Node &nodeOut, PortIndex portIndexOut, TypeConverter typeConverter) : _uid(QUuid::createUuid()) , _outNode(&nodeOut) , _inNode(&nodeIn) , _outPortIndex(portIndexOut) , _inPortIndex(portIndexIn) , _connectionState() , _converter(std::move(typeConverter)) { setNodeToPort(nodeIn, PortType::In, portIndexIn); setNodeToPort(nodeOut, PortType::Out, portIndexOut); } Connection:: ~Connection() { if (complete()) connectionMadeIncomplete(*this); propagateEmptyData(); if (_inNode) { _inNode->nodeGraphicsObject().update(); } if (_outNode) { _outNode->nodeGraphicsObject().update(); } } QJsonObject Connection:: save() const { QJsonObject connectionJson; if (_inNode && _outNode) { connectionJson["in_id"] = _inNode->id().toString(); connectionJson["in_index"] = _inPortIndex; connectionJson["out_id"] = _outNode->id().toString(); connectionJson["out_index"] = _outPortIndex; if (_converter) { auto getTypeJson = [this](PortType type) { QJsonObject typeJson; NodeDataType nodeType = this->dataType(type); typeJson["id"] = nodeType.id; typeJson["name"] = nodeType.name; return typeJson; }; QJsonObject converterTypeJson; converterTypeJson["in"] = getTypeJson(PortType::In); converterTypeJson["out"] = getTypeJson(PortType::Out); connectionJson["converter"] = converterTypeJson; } } return connectionJson; } QUuid Connection:: id() const { return _uid; } bool Connection:: complete() const { return _inNode != nullptr && _outNode != nullptr; } void Connection:: setRequiredPort(PortType dragging) { _connectionState.setRequiredPort(dragging); switch (dragging) { case PortType::Out: _outNode = nullptr; _outPortIndex = INVALID; break; case PortType::In: _inNode = nullptr; _inPortIndex = INVALID; break; default: break; } } PortType Connection:: requiredPort() const { return _connectionState.requiredPort(); } void Connection:: setGraphicsObject(std::unique_ptr &&graphics) { _connectionGraphicsObject = std::move(graphics); // This function is only called when the ConnectionGraphicsObject // is newly created. At this moment both end coordinates are (0, 0) // in Connection G.O. coordinates. The position of the whole // Connection G. O. in scene coordinate system is also (0, 0). // By moving the whole object to the Node Port position // we position both connection ends correctly. if (requiredPort() != PortType::None) { PortType attachedPort = oppositePort(requiredPort()); PortIndex attachedPortIndex = getPortIndex(attachedPort); auto node = getNode(attachedPort); QTransform nodeSceneTransform = node->nodeGraphicsObject().sceneTransform(); QPointF pos = node->nodeGeometry().portScenePosition(attachedPortIndex, attachedPort, nodeSceneTransform); _connectionGraphicsObject->setPos(pos); } _connectionGraphicsObject->move(); } PortIndex Connection:: getPortIndex(PortType portType) const { PortIndex result = INVALID; switch (portType) { case PortType::In: result = _inPortIndex; break; case PortType::Out: result = _outPortIndex; break; default: break; } return result; } void Connection:: setNodeToPort(Node &node, PortType portType, PortIndex portIndex) { bool wasIncomplete = !complete(); auto &nodeWeak = getNode(portType); nodeWeak = &node; if (portType == PortType::Out) _outPortIndex = portIndex; else _inPortIndex = portIndex; _connectionState.setNoRequiredPort(); updated(*this); if (complete() && wasIncomplete) { connectionCompleted(*this); } } void Connection:: removeFromNodes() const { if (_inNode) _inNode->nodeState().eraseConnection(PortType::In, _inPortIndex, id()); if (_outNode) _outNode->nodeState().eraseConnection(PortType::Out, _outPortIndex, id()); } ConnectionGraphicsObject & Connection:: getConnectionGraphicsObject() const { return *_connectionGraphicsObject; } ConnectionState & Connection:: connectionState() { return _connectionState; } ConnectionState const & Connection:: connectionState() const { return _connectionState; } ConnectionGeometry & Connection:: connectionGeometry() { return _connectionGeometry; } ConnectionGeometry const & Connection:: connectionGeometry() const { return _connectionGeometry; } Node * Connection:: getNode(PortType portType) const { switch (portType) { case PortType::In: return _inNode; break; case PortType::Out: return _outNode; break; default: // not possible break; } return nullptr; } Node *& Connection:: getNode(PortType portType) { switch (portType) { case PortType::In: return _inNode; break; case PortType::Out: return _outNode; break; default: // not possible break; } Q_UNREACHABLE(); } void Connection:: clearNode(PortType portType) { if (complete()) { connectionMadeIncomplete(*this); } getNode(portType) = nullptr; if (portType == PortType::In) _inPortIndex = INVALID; else _outPortIndex = INVALID; } NodeDataType Connection:: dataType(PortType portType) const { if (_inNode && _outNode) { auto const &model = (portType == PortType::In) ? _inNode->nodeDataModel() : _outNode->nodeDataModel(); PortIndex index = (portType == PortType::In) ? _inPortIndex : _outPortIndex; return model->dataType(portType, index); } else { Node *validNode; PortIndex index = INVALID; if ((validNode = _inNode)) { index = _inPortIndex; portType = PortType::In; } else if ((validNode = _outNode)) { index = _outPortIndex; portType = PortType::Out; } if (validNode) { auto const &model = validNode->nodeDataModel(); return model->dataType(portType, index); } } Q_UNREACHABLE(); } TypeConverter Connection:: getTypeConverter() const { return _converter; } void Connection:: setTypeConverter(TypeConverter converter) { _converter = std::move(converter); } void Connection:: propagateData(std::shared_ptr nodeData) const { if (_inNode) { if (_converter) { nodeData = _converter(nodeData); } _inNode->propagateData(nodeData, _inPortIndex); } } void Node:: propagateData(std::shared_ptr nodeData, PortIndex inPortIndex) const { //Recalculate the nodes visuals. A data change can result in the node taking more space than before, so this forces a recalculate+repaint on the affected node _nodeGraphicsObject->setGeometryChanged(); _nodeGeometry.recalculateSize(); _nodeGraphicsObject->update(); _nodeGraphicsObject->moveConnections(); _nodeDataModel->setInData(std::move(nodeData), inPortIndex); recalculateVisuals(); } void Connection:: propagateEmptyData() const { std::shared_ptr emptyData; propagateData(emptyData); } qnodeeditor-2.1.5+ds1/src/ConnectionBlurEffect.cpp000066400000000000000000000010221361521262000220440ustar00rootroot00000000000000#include "ConnectionBlurEffect.hpp" #include "ConnectionGraphicsObject.hpp" #include "ConnectionPainter.hpp" using QtNodes::ConnectionBlurEffect; using QtNodes::ConnectionGraphicsObject; ConnectionBlurEffect:: ConnectionBlurEffect(ConnectionGraphicsObject *) { // } void ConnectionBlurEffect:: draw(QPainter *painter) { QGraphicsBlurEffect::draw(painter); //ConnectionPainter::paint(painter, //_object->connectionGeometry(), //_object->connectionState()); //_item->paint(painter, nullptr, nullptr); } qnodeeditor-2.1.5+ds1/src/ConnectionBlurEffect.hpp000066400000000000000000000005711361521262000220610ustar00rootroot00000000000000#include #include namespace QtNodes { class ConnectionGraphicsObject; class ConnectionBlurEffect : public QGraphicsBlurEffect { public: ConnectionBlurEffect(ConnectionGraphicsObject *item); void draw(QPainter *painter) override; private: }; } qnodeeditor-2.1.5+ds1/src/ConnectionGeometry.cpp000066400000000000000000000046351361521262000216330ustar00rootroot00000000000000#include "ConnectionGeometry.hpp" #include #include "StyleCollection.hpp" using QtNodes::ConnectionGeometry; using QtNodes::PortType; ConnectionGeometry:: ConnectionGeometry() : _in(0, 0) , _out(0, 0) //, _animationPhase(0) , _lineWidth(3.0) , _hovered(false) { } QPointF const & ConnectionGeometry:: getEndPoint(PortType portType) const { Q_ASSERT(portType != PortType::None); return (portType == PortType::Out ? _out : _in); } void ConnectionGeometry:: setEndPoint(PortType portType, QPointF const &point) { switch (portType) { case PortType::Out: _out = point; break; case PortType::In: _in = point; break; default: break; } } void ConnectionGeometry:: moveEndPoint(PortType portType, QPointF const &offset) { switch (portType) { case PortType::Out: _out += offset; break; case PortType::In: _in += offset; break; default: break; } } QRectF ConnectionGeometry:: boundingRect() const { auto points = pointsC1C2(); QRectF basicRect = QRectF(_out, _in).normalized(); QRectF c1c2Rect = QRectF(points.first, points.second).normalized(); auto const &connectionStyle = StyleCollection::connectionStyle(); float const diam = connectionStyle.pointDiameter(); QRectF commonRect = basicRect.united(c1c2Rect); QPointF const cornerOffset(diam, diam); commonRect.setTopLeft(commonRect.topLeft() - cornerOffset); commonRect.setBottomRight(commonRect.bottomRight() + 2 * cornerOffset); return commonRect; } std::pair ConnectionGeometry:: pointsC1C2() const { const double defaultOffset = 200; double xDistance = _in.x() - _out.x(); double horizontalOffset = qMin(defaultOffset, std::abs(xDistance)); double verticalOffset = 0; double ratioX = 0.5; if (xDistance <= 0) { double yDistance = _in.y() - _out.y() + 20; double vector = yDistance < 0 ? -1.0 : 1.0; verticalOffset = qMin(defaultOffset, std::abs(yDistance)) * vector; ratioX = 1.0; } horizontalOffset *= ratioX; QPointF c1(_out.x() + horizontalOffset, _out.y() + verticalOffset); QPointF c2(_in.x() - horizontalOffset, _in.y() - verticalOffset); return std::make_pair(c1, c2); } qnodeeditor-2.1.5+ds1/src/ConnectionGraphicsObject.cpp000066400000000000000000000132601361521262000227210ustar00rootroot00000000000000#include "ConnectionGraphicsObject.hpp" #include #include #include #include #include #include "FlowScene.hpp" #include "Connection.hpp" #include "ConnectionGeometry.hpp" #include "ConnectionPainter.hpp" #include "ConnectionState.hpp" #include "ConnectionBlurEffect.hpp" #include "NodeGraphicsObject.hpp" #include "NodeConnectionInteraction.hpp" #include "Node.hpp" using QtNodes::ConnectionGraphicsObject; using QtNodes::Connection; using QtNodes::FlowScene; ConnectionGraphicsObject:: ConnectionGraphicsObject(FlowScene &scene, Connection &connection) : _scene(scene) , _connection(connection) { _scene.addItem(this); setFlag(QGraphicsItem::ItemIsMovable, true); setFlag(QGraphicsItem::ItemIsFocusable, true); setFlag(QGraphicsItem::ItemIsSelectable, true); setAcceptHoverEvents(true); // addGraphicsEffect(); setZValue(-1.0); } ConnectionGraphicsObject:: ~ConnectionGraphicsObject() { _scene.removeItem(this); } QtNodes::Connection & ConnectionGraphicsObject:: connection() { return _connection; } QRectF ConnectionGraphicsObject:: boundingRect() const { return _connection.connectionGeometry().boundingRect(); } QPainterPath ConnectionGraphicsObject:: shape() const { #ifdef DEBUG_DRAWING //QPainterPath path; //path.addRect(boundingRect()); //return path; #else auto const &geom = _connection.connectionGeometry(); return ConnectionPainter::getPainterStroke(geom); #endif } void ConnectionGraphicsObject:: setGeometryChanged() { prepareGeometryChange(); } void ConnectionGraphicsObject:: move() { for (PortType portType : { PortType::In, PortType::Out }) { if (auto node = _connection.getNode(portType)) { auto const &nodeGraphics = node->nodeGraphicsObject(); auto const &nodeGeom = node->nodeGeometry(); QPointF scenePos = nodeGeom.portScenePosition(_connection.getPortIndex(portType), portType, nodeGraphics.sceneTransform()); QTransform sceneTransform = this->sceneTransform(); QPointF connectionPos = sceneTransform.inverted().map(scenePos); _connection.connectionGeometry().setEndPoint(portType, connectionPos); _connection.getConnectionGraphicsObject().setGeometryChanged(); _connection.getConnectionGraphicsObject().update(); } } } void ConnectionGraphicsObject:: lock(bool locked) { setFlag(QGraphicsItem::ItemIsMovable, !locked); setFlag(QGraphicsItem::ItemIsFocusable, !locked); setFlag(QGraphicsItem::ItemIsSelectable, !locked); } void ConnectionGraphicsObject:: paint(QPainter *painter, QStyleOptionGraphicsItem const *option, QWidget *) { painter->setClipRect(option->exposedRect); ConnectionPainter::paint(painter, _connection); } void ConnectionGraphicsObject:: mousePressEvent(QGraphicsSceneMouseEvent *event) { QGraphicsItem::mousePressEvent(event); //event->ignore(); } void ConnectionGraphicsObject:: mouseMoveEvent(QGraphicsSceneMouseEvent *event) { prepareGeometryChange(); auto view = static_cast(event->widget()); auto node = locateNodeAt(event->scenePos(), _scene, view->transform()); auto &connectionState = _connection.connectionState(); connectionState.interactWithNode(node); if (node) { auto oppPort = oppositePort(connectionState.requiredPort()); node->reactToPossibleConnection(connectionState.requiredPort(), _connection.dataType(oppPort), event->scenePos()); } //------------------- QPointF offset = event->pos() - event->lastPos(); auto requiredPort = _connection.requiredPort(); // This moves the loose end exactly to the mouse position if (requiredPort != PortType::None) { _connection.connectionGeometry().moveEndPoint(requiredPort, offset); } //------------------- update(); event->accept(); } void ConnectionGraphicsObject:: mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { ungrabMouse(); event->accept(); auto node = locateNodeAt(event->scenePos(), _scene, _scene.views()[0]->transform()); NodeConnectionInteraction interaction(*node, _connection, _scene); if (node && interaction.tryConnect()) { node->resetReactionToConnection(); } if (_connection.connectionState().requiresPort()) { _scene.deleteConnection(_connection); } else { _scene.connectionClicked(connection(), event->screenPos()); } } void ConnectionGraphicsObject:: hoverEnterEvent(QGraphicsSceneHoverEvent *event) { _connection.connectionGeometry().setHovered(true); update(); _scene.connectionHovered(connection(), event->screenPos()); event->accept(); } void ConnectionGraphicsObject:: hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { _connection.connectionGeometry().setHovered(false); update(); _scene.connectionHoverLeft(connection()); event->accept(); } void ConnectionGraphicsObject:: addGraphicsEffect() { auto effect = new QGraphicsBlurEffect; effect->setBlurRadius(5); setGraphicsEffect(effect); //auto effect = new QGraphicsDropShadowEffect; //auto effect = new ConnectionBlurEffect(this); //effect->setOffset(4, 4); //effect->setColor(QColor(Qt::gray).darker(800)); } qnodeeditor-2.1.5+ds1/src/ConnectionPainter.cpp000066400000000000000000000164431361521262000214420ustar00rootroot00000000000000#include "ConnectionPainter.hpp" #include #include "ConnectionGeometry.hpp" #include "ConnectionState.hpp" #include "ConnectionGraphicsObject.hpp" #include "Connection.hpp" #include "NodeData.hpp" #include "StyleCollection.hpp" using QtNodes::ConnectionPainter; using QtNodes::ConnectionGeometry; using QtNodes::Connection; static QPainterPath cubicPath(ConnectionGeometry const &geom) { QPointF const &source = geom.source(); QPointF const &sink = geom.sink(); auto c1c2 = geom.pointsC1C2(); // cubic spline QPainterPath cubic(source); cubic.cubicTo(c1c2.first, c1c2.second, sink); return cubic; } QPainterPath ConnectionPainter:: getPainterStroke(ConnectionGeometry const &geom) { auto cubic = cubicPath(geom); QPointF const &source = geom.source(); QPainterPath result(source); unsigned segments = 20; for (auto i = 0ul; i < segments; ++i) { double ratio = double(i + 1) / segments; result.lineTo(cubic.pointAtPercent(ratio)); } QPainterPathStroker stroker; stroker.setWidth(10.0); return stroker.createStroke(result); } #ifdef NODE_DEBUG_DRAWING static void debugDrawing(QPainter *painter, Connection const &connection) { Q_UNUSED(painter); Q_UNUSED(connection); ConnectionGeometry const &geom = connection.connectionGeometry(); { QPointF const &source = geom.source(); QPointF const &sink = geom.sink(); auto points = geom.pointsC1C2(); painter->setPen(Qt::red); painter->setBrush(Qt::red); painter->drawLine(QLineF(source, points.first)); painter->drawLine(QLineF(points.first, points.second)); painter->drawLine(QLineF(points.second, sink)); painter->drawEllipse(points.first, 3, 3); painter->drawEllipse(points.second, 3, 3); painter->setBrush(Qt::NoBrush); painter->drawPath(cubicPath(geom)); } { painter->setPen(Qt::yellow); painter->drawRect(geom.boundingRect()); } } #endif static void drawSketchLine(QPainter *painter, Connection const &connection) { using QtNodes::ConnectionState; ConnectionState const &state = connection.connectionState(); if (state.requiresPort()) { auto const &connectionStyle = QtNodes::StyleCollection::connectionStyle(); QPen p; p.setWidth(connectionStyle.constructionLineWidth()); p.setColor(connectionStyle.constructionColor()); p.setStyle(Qt::DashLine); painter->setPen(p); painter->setBrush(Qt::NoBrush); using QtNodes::ConnectionGeometry; ConnectionGeometry const &geom = connection.connectionGeometry(); auto cubic = cubicPath(geom); // cubic spline painter->drawPath(cubic); } } static void drawHoveredOrSelected(QPainter *painter, Connection const &connection) { using QtNodes::ConnectionGeometry; ConnectionGeometry const &geom = connection.connectionGeometry(); bool const hovered = geom.hovered(); auto const &graphicsObject = connection.getConnectionGraphicsObject(); bool const selected = graphicsObject.isSelected(); // drawn as a fat background if (hovered || selected) { QPen p; auto const &connectionStyle = QtNodes::StyleCollection::connectionStyle(); double const lineWidth = connectionStyle.lineWidth(); p.setWidth(2 * lineWidth); p.setColor(selected ? connectionStyle.selectedHaloColor() : connectionStyle.hoveredColor()); painter->setPen(p); painter->setBrush(Qt::NoBrush); // cubic spline auto cubic = cubicPath(geom); painter->drawPath(cubic); } } static void drawNormalLine(QPainter *painter, Connection const &connection) { using QtNodes::ConnectionState; ConnectionState const &state = connection.connectionState(); if (state.requiresPort()) return; // colors auto const &connectionStyle = QtNodes::StyleCollection::connectionStyle(); QColor normalColorOut = connectionStyle.normalColor(); QColor normalColorIn = connectionStyle.normalColor(); QColor selectedColor = connectionStyle.selectedColor(); bool gradientColor = false; if (connectionStyle.useDataDefinedColors()) { using QtNodes::PortType; auto dataTypeOut = connection.dataType(PortType::Out); auto dataTypeIn = connection.dataType(PortType::In); gradientColor = (dataTypeOut.id != dataTypeIn.id); normalColorOut = connectionStyle.normalColor(dataTypeOut.id); normalColorIn = connectionStyle.normalColor(dataTypeIn.id); selectedColor = normalColorOut.darker(200); } // geometry ConnectionGeometry const &geom = connection.connectionGeometry(); double const lineWidth = connectionStyle.lineWidth(); // draw normal line QPen p; p.setWidth(lineWidth); auto const &graphicsObject = connection.getConnectionGraphicsObject(); bool const selected = graphicsObject.isSelected(); auto cubic = cubicPath(geom); if (gradientColor) { painter->setBrush(Qt::NoBrush); QColor c = normalColorOut; if (selected) c = c.darker(200); p.setColor(c); painter->setPen(p); unsigned int const segments = 60; for (unsigned int i = 0ul; i < segments; ++i) { double ratioPrev = double(i) / segments; double ratio = double(i + 1) / segments; if (i == segments / 2) { QColor c = normalColorIn; if (selected) c = c.darker(200); p.setColor(c); painter->setPen(p); } painter->drawLine(cubic.pointAtPercent(ratioPrev), cubic.pointAtPercent(ratio)); } { QIcon icon(":convert.png"); QPixmap pixmap = icon.pixmap(QSize(22, 22)); painter->drawPixmap(cubic.pointAtPercent(0.50) - QPoint(pixmap.width() / 2, pixmap.height() / 2), pixmap); } } else { p.setColor(normalColorOut); if (selected) { p.setColor(selectedColor); } painter->setPen(p); painter->setBrush(Qt::NoBrush); painter->drawPath(cubic); } } void ConnectionPainter:: paint(QPainter *painter, Connection const &connection) { drawHoveredOrSelected(painter, connection); drawSketchLine(painter, connection); drawNormalLine(painter, connection); #ifdef NODE_DEBUG_DRAWING debugDrawing(painter, connection); #endif // draw end points ConnectionGeometry const &geom = connection.connectionGeometry(); QPointF const &source = geom.source(); QPointF const &sink = geom.sink(); auto const &connectionStyle = QtNodes::StyleCollection::connectionStyle(); double const pointDiameter = connectionStyle.pointDiameter(); painter->setPen(connectionStyle.constructionColor()); painter->setBrush(connectionStyle.constructionColor()); double const pointRadius = pointDiameter / 2.0; painter->drawEllipse(source, pointRadius, pointRadius); painter->drawEllipse(sink, pointRadius, pointRadius); } qnodeeditor-2.1.5+ds1/src/ConnectionPainter.hpp000066400000000000000000000006571361521262000214470ustar00rootroot00000000000000#pragma once #include namespace QtNodes { class ConnectionGeometry; class ConnectionState; class Connection; class ConnectionPainter { public: static void paint(QPainter *painter, Connection const &connection); static QPainterPath getPainterStroke(ConnectionGeometry const &geom); }; } qnodeeditor-2.1.5+ds1/src/ConnectionState.cpp000066400000000000000000000012261361521262000211110ustar00rootroot00000000000000#include "ConnectionState.hpp" #include #include #include "FlowScene.hpp" #include "Node.hpp" using QtNodes::ConnectionState; using QtNodes::Node; ConnectionState:: ~ConnectionState() { resetLastHoveredNode(); } void ConnectionState:: interactWithNode(Node *node) { if (node) { _lastHoveredNode = node; } else { resetLastHoveredNode(); } } void ConnectionState:: setLastHoveredNode(Node *node) { _lastHoveredNode = node; } void ConnectionState:: resetLastHoveredNode() { if (_lastHoveredNode) _lastHoveredNode->resetReactionToConnection(); _lastHoveredNode = nullptr; } qnodeeditor-2.1.5+ds1/src/ConnectionStyle.cpp000066400000000000000000000114771361521262000211420ustar00rootroot00000000000000#include "ConnectionStyle.hpp" #include #include #include #include #include #include #include #include "StyleCollection.hpp" using QtNodes::ConnectionStyle; inline void initResources() { Q_INIT_RESOURCE(QNodeEditor_resources); } ConnectionStyle:: ConnectionStyle() { // Explicit resources inialization for preventing the static initialization // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order initResources(); // This configuration is stored inside the compiled unit and is loaded statically loadJsonFile(":DefaultStyle.json"); } ConnectionStyle:: ConnectionStyle(QString jsonText) { loadJsonFile(":DefaultStyle.json"); loadJsonText(jsonText); } void ConnectionStyle:: setConnectionStyle(QString jsonText) { ConnectionStyle style(jsonText); StyleCollection::setConnectionStyle(style); } #ifdef STYLE_DEBUG #define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(v, variable) { \ if (v.type() == QJsonValue::Undefined || \ v.type() == QJsonValue::Null) \ qWarning() << "Undefined value for parameter:" << #variable; \ } #else #define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(v, variable) #endif #define CONNECTION_VALUE_EXISTS(v) \ (v.type() != QJsonValue::Undefined && \ v.type() != QJsonValue::Null) #define CONNECTION_STYLE_READ_COLOR(values, variable) { \ auto valueRef = values[#variable]; \ CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ if (CONNECTION_VALUE_EXISTS(valueRef)) {\ if (valueRef.isArray()) { \ auto colorArray = valueRef.toArray(); \ std::vector rgb; rgb.reserve(3); \ for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ rgb.push_back((*it).toInt()); \ } \ variable = QColor(rgb[0], rgb[1], rgb[2]); \ } else { \ variable = QColor(valueRef.toString()); \ } \ } \ } #define CONNECTION_STYLE_READ_FLOAT(values, variable) { \ auto valueRef = values[#variable]; \ CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ if (CONNECTION_VALUE_EXISTS(valueRef)) \ variable = valueRef.toDouble(); \ } #define CONNECTION_STYLE_READ_BOOL(values, variable) { \ auto valueRef = values[#variable]; \ CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ if (CONNECTION_VALUE_EXISTS(valueRef)) \ variable = valueRef.toBool(); \ } void ConnectionStyle:: loadJsonFile(QString styleFile) { QFile file(styleFile); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Couldn't open file " << styleFile; return; } loadJsonFromByteArray(file.readAll()); } void ConnectionStyle:: loadJsonText(QString jsonText) { loadJsonFromByteArray(jsonText.toUtf8()); } void ConnectionStyle:: loadJsonFromByteArray(QByteArray const &byteArray) { QJsonDocument json(QJsonDocument::fromJson(byteArray)); QJsonObject topLevelObject = json.object(); QJsonValueRef nodeStyleValues = topLevelObject["ConnectionStyle"]; QJsonObject obj = nodeStyleValues.toObject(); CONNECTION_STYLE_READ_COLOR(obj, ConstructionColor); CONNECTION_STYLE_READ_COLOR(obj, NormalColor); CONNECTION_STYLE_READ_COLOR(obj, SelectedColor); CONNECTION_STYLE_READ_COLOR(obj, SelectedHaloColor); CONNECTION_STYLE_READ_COLOR(obj, HoveredColor); CONNECTION_STYLE_READ_FLOAT(obj, LineWidth); CONNECTION_STYLE_READ_FLOAT(obj, ConstructionLineWidth); CONNECTION_STYLE_READ_FLOAT(obj, PointDiameter); CONNECTION_STYLE_READ_BOOL(obj, UseDataDefinedColors); } QColor ConnectionStyle:: constructionColor() const { return ConstructionColor; } QColor ConnectionStyle:: normalColor() const { return NormalColor; } QColor ConnectionStyle:: normalColor(QString typeId) const { std::size_t hash = qHash(typeId); std::size_t const hue_range = 0xFF; qsrand(hash); std::size_t hue = qrand() % hue_range; std::size_t sat = 120 + hash % 129; return QColor::fromHsl(hue, sat, 160); } QColor ConnectionStyle:: selectedColor() const { return SelectedColor; } QColor ConnectionStyle:: selectedHaloColor() const { return SelectedHaloColor; } QColor ConnectionStyle:: hoveredColor() const { return HoveredColor; } float ConnectionStyle:: lineWidth() const { return LineWidth; } float ConnectionStyle:: constructionLineWidth() const { return ConstructionLineWidth; } float ConnectionStyle:: pointDiameter() const { return PointDiameter; } bool ConnectionStyle:: useDataDefinedColors() const { return UseDataDefinedColors; } qnodeeditor-2.1.5+ds1/src/DataModelRegistry.cpp000066400000000000000000000023311361521262000213720ustar00rootroot00000000000000#include "DataModelRegistry.hpp" #include #include using QtNodes::DataModelRegistry; using QtNodes::NodeDataModel; using QtNodes::NodeDataType; using QtNodes::TypeConverter; std::unique_ptr DataModelRegistry:: create(QString const &modelName) { auto it = _registeredItemCreators.find(modelName); if (it != _registeredItemCreators.end()) { return it->second(); } return nullptr; } DataModelRegistry::RegisteredModelCreatorsMap const & DataModelRegistry:: registeredModelCreators() const { return _registeredItemCreators; } DataModelRegistry::RegisteredModelsCategoryMap const & DataModelRegistry:: registeredModelsCategoryAssociation() const { return _registeredModelsCategory; } DataModelRegistry::CategoriesSet const & DataModelRegistry:: categories() const { return _categories; } TypeConverter DataModelRegistry:: getTypeConverter(NodeDataType const &d1, NodeDataType const &d2) const { TypeConverterId converterId = std::make_pair(d1, d2); auto it = _registeredTypeConverters.find(converterId); if (it != _registeredTypeConverters.end()) { return it->second; } return TypeConverter{}; } qnodeeditor-2.1.5+ds1/src/FlowScene.cpp000066400000000000000000000366221361521262000177060ustar00rootroot00000000000000#include "FlowScene.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Node.hpp" #include "NodeGraphicsObject.hpp" #include "NodeGraphicsObject.hpp" #include "ConnectionGraphicsObject.hpp" #include "Connection.hpp" #include "FlowView.hpp" #include "DataModelRegistry.hpp" using QtNodes::FlowScene; using QtNodes::Node; using QtNodes::NodeGraphicsObject; using QtNodes::Connection; using QtNodes::DataModelRegistry; using QtNodes::NodeDataModel; using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::TypeConverter; FlowScene:: FlowScene(std::shared_ptr registry, QObject *parent) : QGraphicsScene(parent) , _registry(std::move(registry)) { setItemIndexMethod(QGraphicsScene::NoIndex); // This connection should come first connect(this, &FlowScene::connectionCreated, this, &FlowScene::setupConnectionSignals); connect(this, &FlowScene::connectionCreated, this, &FlowScene::sendConnectionCreatedToNodes); connect(this, &FlowScene::connectionDeleted, this, &FlowScene::sendConnectionDeletedToNodes); } FlowScene:: FlowScene(QObject *parent) : FlowScene(std::make_shared(), parent) {} FlowScene:: ~FlowScene() { clearScene(); } //------------------------------------------------------------------------------ std::shared_ptr FlowScene:: createConnection(PortType connectedPort, Node &node, PortIndex portIndex) { auto connection = std::make_shared(connectedPort, node, portIndex); auto cgo = detail::make_unique(*this, *connection); // after this function connection points are set to node port connection->setGraphicsObject(std::move(cgo)); _connections[connection->id()] = connection; // Note: this connection isn't truly created yet. It's only partially created. // Thus, don't send the connectionCreated(...) signal. connect(connection.get(), &Connection::connectionCompleted, this, [this](Connection const & c) { connectionCreated(c); }); return connection; } std::shared_ptr FlowScene:: createConnection(Node &nodeIn, PortIndex portIndexIn, Node &nodeOut, PortIndex portIndexOut, TypeConverter const &converter) { auto connection = std::make_shared(nodeIn, portIndexIn, nodeOut, portIndexOut, converter); auto cgo = detail::make_unique(*this, *connection); nodeIn.nodeState().setConnection(PortType::In, portIndexIn, *connection); nodeOut.nodeState().setConnection(PortType::Out, portIndexOut, *connection); // after this function connection points are set to node port connection->setGraphicsObject(std::move(cgo)); // trigger data propagation nodeOut.onDataUpdated(portIndexOut); _connections[connection->id()] = connection; connectionCreated(*connection); return connection; } std::shared_ptr FlowScene:: restoreConnection(QJsonObject const &connectionJson) { QUuid nodeInId = QUuid(connectionJson["in_id"].toString()); QUuid nodeOutId = QUuid(connectionJson["out_id"].toString()); PortIndex portIndexIn = connectionJson["in_index"].toInt(); PortIndex portIndexOut = connectionJson["out_index"].toInt(); auto nodeIn = _nodes[nodeInId].get(); auto nodeOut = _nodes[nodeOutId].get(); auto getConverter = [&]() { QJsonValue converterVal = connectionJson["converter"]; if (!converterVal.isUndefined()) { QJsonObject converterJson = converterVal.toObject(); NodeDataType inType { converterJson["in"].toObject()["id"].toString(), converterJson["in"].toObject()["name"].toString() }; NodeDataType outType { converterJson["out"].toObject()["id"].toString(), converterJson["out"].toObject()["name"].toString() }; auto converter = registry().getTypeConverter(outType, inType); if (converter) return converter; } return TypeConverter{}; }; std::shared_ptr connection = createConnection(*nodeIn, portIndexIn, *nodeOut, portIndexOut, getConverter()); // Note: the connectionCreated(...) signal has already been sent // by createConnection(...) return connection; } void FlowScene:: deleteConnection(Connection &connection) { auto it = _connections.find(connection.id()); if (it != _connections.end()) { connection.removeFromNodes(); _connections.erase(it); } } Node & FlowScene:: createNode(std::unique_ptr &&dataModel) { auto node = detail::make_unique(std::move(dataModel)); auto ngo = detail::make_unique(*this, *node); node->setGraphicsObject(std::move(ngo)); auto nodePtr = node.get(); connect(nodePtr, &Node::killConnection, this, &FlowScene::deleteConnection); _nodes[node->id()] = std::move(node); nodeCreated(*nodePtr); return *nodePtr; } Node & FlowScene:: restoreNode(QJsonObject const &nodeJson) { QString modelName = nodeJson["model"].toObject()["name"].toString(); auto dataModel = registry().create(modelName); if (!dataModel) throw std::logic_error(std::string("No registered model with name ") + modelName.toLocal8Bit().data()); auto node = detail::make_unique(std::move(dataModel)); auto ngo = detail::make_unique(*this, *node); node->setGraphicsObject(std::move(ngo)); node->restore(nodeJson); auto nodePtr = node.get(); connect(nodePtr, &Node::killConnection, this, &FlowScene::deleteConnection); _nodes[node->id()] = std::move(node); nodePlaced(*nodePtr); nodeCreated(*nodePtr); return *nodePtr; } void FlowScene:: removeNode(Node &node) { // call signal nodeDeleted(node); for (auto portType : { PortType::In, PortType::Out }) { auto nodeState = node.nodeState(); auto const &nodeEntries = nodeState.getEntries(portType); for (auto &connections : nodeEntries) { for (auto const &pair : connections) deleteConnection(*pair.second); } } _nodes.erase(node.id()); } DataModelRegistry & FlowScene:: registry() const { return *_registry; } void FlowScene:: setRegistry(std::shared_ptr registry) { _registry = std::move(registry); } void FlowScene:: iterateOverNodes(std::function const &visitor) { for (const auto &_node : _nodes) { visitor(_node.second.get()); } } void FlowScene:: iterateOverNodeData(std::function const &visitor) { for (const auto &_node : _nodes) { visitor(_node.second->nodeDataModel()); } } void FlowScene:: iterateOverNodeDataDependentOrder(std::function const &visitor) { std::set visitedNodesSet; //A leaf node is a node with no input ports, or all possible input ports empty auto isNodeLeaf = [](Node const & node, NodeDataModel const & model) { for (unsigned int i = 0; i < model.nPorts(PortType::In); ++i) { auto connections = node.nodeState().connections(PortType::In, i); if (!connections.empty()) { return false; } } return true; }; //Iterate over "leaf" nodes for (auto const &_node : _nodes) { auto const &node = _node.second; auto model = node->nodeDataModel(); if (isNodeLeaf(*node, *model)) { visitor(model); visitedNodesSet.insert(node->id()); } } auto areNodeInputsVisitedBefore = [&](Node const & node, NodeDataModel const & model) { for (size_t i = 0; i < model.nPorts(PortType::In); ++i) { auto connections = node.nodeState().connections(PortType::In, static_cast(i)); for (auto &conn : connections) { if (visitedNodesSet.find(conn.second->getNode(PortType::Out)->id()) == visitedNodesSet.end()) { return false; } } } return true; }; //Iterate over dependent nodes while (_nodes.size() != visitedNodesSet.size()) { for (auto const &_node : _nodes) { auto const &node = _node.second; if (visitedNodesSet.find(node->id()) != visitedNodesSet.end()) continue; auto model = node->nodeDataModel(); if (areNodeInputsVisitedBefore(*node, *model)) { visitor(model); visitedNodesSet.insert(node->id()); } } } } QPointF FlowScene:: getNodePosition(const Node &node) const { return node.nodeGraphicsObject().pos(); } void FlowScene:: setNodePosition(Node &node, const QPointF &pos) const { node.nodeGraphicsObject().setPos(pos); node.nodeGraphicsObject().moveConnections(); } QSizeF FlowScene:: getNodeSize(const Node &node) const { return QSizeF(node.nodeGeometry().width(), node.nodeGeometry().height()); } std::unordered_map > const & FlowScene:: nodes() const { return _nodes; } std::unordered_map > const & FlowScene:: connections() const { return _connections; } std::vector FlowScene:: allNodes() const { std::vector nodes; std::transform(_nodes.begin(), _nodes.end(), std::back_inserter(nodes), [](std::pair> const & p) { return p.second.get(); }); return nodes; } std::vector FlowScene:: selectedNodes() const { QList graphicsItems = selectedItems(); std::vector ret; ret.reserve(graphicsItems.size()); for (QGraphicsItem *item : graphicsItems) { auto ngo = qgraphicsitem_cast(item); if (ngo != nullptr) { ret.push_back(&ngo->node()); } } return ret; } //------------------------------------------------------------------------------ void FlowScene:: clearScene() { //Manual node cleanup. Simply clearing the holding datastructures doesn't work, the code crashes when // there are both nodes and connections in the scene. (The data propagation internal logic tries to propagate // data through already freed connections.) while (_connections.size() > 0) { deleteConnection(*_connections.begin()->second); } while (_nodes.size() > 0) { removeNode(*_nodes.begin()->second); } } void FlowScene:: save() const { QString fileName = QFileDialog::getSaveFileName(nullptr, tr("Open Flow Scene"), QDir::homePath(), tr("Flow Scene Files (*.flow)")); if (!fileName.isEmpty()) { if (!fileName.endsWith("flow", Qt::CaseInsensitive)) fileName += ".flow"; QFile file(fileName); if (file.open(QIODevice::WriteOnly)) { file.write(saveToMemory()); } } } void FlowScene:: load() { clearScene(); //------------- QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open Flow Scene"), QDir::homePath(), tr("Flow Scene Files (*.flow)")); if (!QFileInfo::exists(fileName)) return; QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) return; QByteArray wholeFile = file.readAll(); loadFromMemory(wholeFile); } QByteArray FlowScene:: saveToMemory() const { QJsonObject sceneJson; QJsonArray nodesJsonArray; for (auto const &pair : _nodes) { auto const &node = pair.second; nodesJsonArray.append(node->save()); } sceneJson["nodes"] = nodesJsonArray; QJsonArray connectionJsonArray; for (auto const &pair : _connections) { auto const &connection = pair.second; QJsonObject connectionJson = connection->save(); if (!connectionJson.isEmpty()) connectionJsonArray.append(connectionJson); } sceneJson["connections"] = connectionJsonArray; QJsonDocument document(sceneJson); return document.toJson(); } void FlowScene:: loadFromMemory(const QByteArray &data) { QJsonObject const jsonDocument = QJsonDocument::fromJson(data).object(); QJsonArray nodesJsonArray = jsonDocument["nodes"].toArray(); for (QJsonValueRef node : nodesJsonArray) { restoreNode(node.toObject()); } QJsonArray connectionJsonArray = jsonDocument["connections"].toArray(); for (QJsonValueRef connection : connectionJsonArray) { restoreConnection(connection.toObject()); } } void FlowScene:: setupConnectionSignals(Connection const &c) { connect(&c, &Connection::connectionMadeIncomplete, this, &FlowScene::connectionDeleted, Qt::UniqueConnection); } void FlowScene:: sendConnectionCreatedToNodes(Connection const &c) { Node *from = c.getNode(PortType::Out); Node *to = c.getNode(PortType::In); Q_ASSERT(from != nullptr); Q_ASSERT(to != nullptr); from->nodeDataModel()->outputConnectionCreated(c); to->nodeDataModel()->inputConnectionCreated(c); } void FlowScene:: sendConnectionDeletedToNodes(Connection const &c) { Node *from = c.getNode(PortType::Out); Node *to = c.getNode(PortType::In); Q_ASSERT(from != nullptr); Q_ASSERT(to != nullptr); from->nodeDataModel()->outputConnectionDeleted(c); to->nodeDataModel()->inputConnectionDeleted(c); } void FlowScene:: killConnection(Connection &connection) { deleteConnection(connection); } //------------------------------------------------------------------------------ namespace QtNodes { Node * locateNodeAt(QPointF scenePoint, FlowScene &scene, QTransform const &viewTransform) { // items under cursor QList items = scene.items(scenePoint, Qt::IntersectsItemShape, Qt::DescendingOrder, viewTransform); //// items convertable to NodeGraphicsObject std::vector filteredItems; std::copy_if(items.begin(), items.end(), std::back_inserter(filteredItems), [](QGraphicsItem * item) { return (dynamic_cast(item) != nullptr); }); Node *resultNode = nullptr; if (!filteredItems.empty()) { QGraphicsItem *graphicsItem = filteredItems.front(); auto ngo = dynamic_cast(graphicsItem); resultNode = &ngo->node(); } return resultNode; } } qnodeeditor-2.1.5+ds1/src/FlowView.cpp000066400000000000000000000222211361521262000175510ustar00rootroot00000000000000#include "FlowView.hpp" #include #include #include #include #include #include #include #include #include #include #include "FlowScene.hpp" #include "DataModelRegistry.hpp" #include "Node.hpp" #include "NodeGraphicsObject.hpp" #include "ConnectionGraphicsObject.hpp" #include "StyleCollection.hpp" using QtNodes::FlowView; using QtNodes::FlowScene; FlowView:: FlowView(QWidget *parent) : QGraphicsView(parent) , _clearSelectionAction(Q_NULLPTR) , _deleteSelectionAction(Q_NULLPTR) , _scene(Q_NULLPTR) { setDragMode(QGraphicsView::ScrollHandDrag); setRenderHint(QPainter::Antialiasing); auto const &flowViewStyle = StyleCollection::flowViewStyle(); setBackgroundBrush(flowViewStyle.BackgroundColor); //setViewportUpdateMode(QGraphicsView::FullViewportUpdate); //setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setCacheMode(QGraphicsView::CacheBackground); //setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); } FlowView:: FlowView(FlowScene *scene, QWidget *parent) : FlowView(parent) { setScene(scene); } QAction * FlowView:: clearSelectionAction() const { return _clearSelectionAction; } QAction * FlowView:: deleteSelectionAction() const { return _deleteSelectionAction; } void FlowView::setScene(FlowScene *scene) { _scene = scene; QGraphicsView::setScene(_scene); // setup actions delete _clearSelectionAction; _clearSelectionAction = new QAction(QStringLiteral("Clear Selection"), this); _clearSelectionAction->setShortcut(Qt::Key_Escape); connect(_clearSelectionAction, &QAction::triggered, _scene, &QGraphicsScene::clearSelection); addAction(_clearSelectionAction); delete _deleteSelectionAction; _deleteSelectionAction = new QAction(QStringLiteral("Delete Selection"), this); _deleteSelectionAction->setShortcut(Qt::Key_Delete); connect(_deleteSelectionAction, &QAction::triggered, this, &FlowView::deleteSelectedNodes); addAction(_deleteSelectionAction); } void FlowView:: contextMenuEvent(QContextMenuEvent *event) { if (itemAt(event->pos())) { QGraphicsView::contextMenuEvent(event); return; } QMenu modelMenu; auto skipText = QStringLiteral("skip me"); //Add filterbox to the context menu auto *txtBox = new QLineEdit(&modelMenu); txtBox->setPlaceholderText(QStringLiteral("Filter")); txtBox->setClearButtonEnabled(true); auto *txtBoxAction = new QWidgetAction(&modelMenu); txtBoxAction->setDefaultWidget(txtBox); modelMenu.addAction(txtBoxAction); //Add result treeview to the context menu auto *treeView = new QTreeWidget(&modelMenu); treeView->header()->close(); auto *treeViewAction = new QWidgetAction(&modelMenu); treeViewAction->setDefaultWidget(treeView); modelMenu.addAction(treeViewAction); QMap topLevelItems; for (auto const &cat : _scene->registry().categories()) { auto item = new QTreeWidgetItem(treeView); item->setText(0, cat); item->setData(0, Qt::UserRole, skipText); topLevelItems[cat] = item; } for (auto const &assoc : _scene->registry().registeredModelsCategoryAssociation()) { auto parent = topLevelItems[assoc.second]; auto item = new QTreeWidgetItem(parent); item->setText(0, assoc.first); item->setData(0, Qt::UserRole, assoc.first); } treeView->expandAll(); connect(treeView, &QTreeWidget::itemClicked, [&](QTreeWidgetItem * item, int) { QString modelName = item->data(0, Qt::UserRole).toString(); if (modelName == skipText) { return; } auto type = _scene->registry().create(modelName); if (type) { auto &node = _scene->createNode(std::move(type)); QPoint pos = event->pos(); QPointF posView = this->mapToScene(pos); node.nodeGraphicsObject().setPos(posView); _scene->nodePlaced(node); } else { qDebug() << "Model not found"; } modelMenu.close(); }); //Setup filtering connect(txtBox, &QLineEdit::textChanged, [&](const QString & text) { for (auto &topLvlItem : topLevelItems) { for (int i = 0; i < topLvlItem->childCount(); ++i) { auto child = topLvlItem->child(i); auto modelName = child->data(0, Qt::UserRole).toString(); const bool match = (modelName.contains(text, Qt::CaseInsensitive)); child->setHidden(!match); } } }); // make sure the text box gets focus so the user doesn't have to click on it txtBox->setFocus(); modelMenu.exec(event->globalPos()); } void FlowView:: wheelEvent(QWheelEvent *event) { QPoint delta = event->angleDelta(); if (delta.y() == 0) { event->ignore(); return; } double const d = delta.y() / std::abs(delta.y()); if (d > 0.0) scaleUp(); else scaleDown(); } void FlowView:: scaleUp() { double const step = 1.2; double const factor = std::pow(step, 1.0); QTransform t = transform(); if (t.m11() > 2.0) return; scale(factor, factor); } void FlowView:: scaleDown() { double const step = 1.2; double const factor = std::pow(step, -1.0); scale(factor, factor); } void FlowView:: deleteSelectedNodes() { // Delete the selected connections first, ensuring that they won't be // automatically deleted when selected nodes are deleted (deleting a node // deletes some connections as well) for (QGraphicsItem *item : _scene->selectedItems()) { if (auto c = qgraphicsitem_cast(item)) _scene->deleteConnection(c->connection()); } // Delete the nodes; this will delete many of the connections. // Selected connections were already deleted prior to this loop, otherwise // qgraphicsitem_cast(item) could be a use-after-free // when a selected connection is deleted by deleting the node. for (QGraphicsItem *item : _scene->selectedItems()) { if (auto n = qgraphicsitem_cast(item)) _scene->removeNode(n->node()); } } void FlowView:: keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Shift: setDragMode(QGraphicsView::RubberBandDrag); break; default: break; } QGraphicsView::keyPressEvent(event); } void FlowView:: keyReleaseEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Shift: setDragMode(QGraphicsView::ScrollHandDrag); break; default: break; } QGraphicsView::keyReleaseEvent(event); } void FlowView:: mousePressEvent(QMouseEvent *event) { QGraphicsView::mousePressEvent(event); if (event->button() == Qt::LeftButton) { _clickPos = mapToScene(event->pos()); } } void FlowView:: mouseMoveEvent(QMouseEvent *event) { QGraphicsView::mouseMoveEvent(event); if (scene()->mouseGrabberItem() == nullptr && event->buttons() == Qt::LeftButton) { // Make sure shift is not being pressed if ((event->modifiers() & Qt::ShiftModifier) == 0) { QPointF difference = _clickPos - mapToScene(event->pos()); setSceneRect(sceneRect().translated(difference.x(), difference.y())); } } } void FlowView:: drawBackground(QPainter *painter, const QRectF &r) { QGraphicsView::drawBackground(painter, r); auto drawGrid = [&](double gridStep) { QRect windowRect = rect(); QPointF tl = mapToScene(windowRect.topLeft()); QPointF br = mapToScene(windowRect.bottomRight()); double left = std::floor(tl.x() / gridStep - 0.5); double right = std::floor(br.x() / gridStep + 1.0); double bottom = std::floor(tl.y() / gridStep - 0.5); double top = std::floor(br.y() / gridStep + 1.0); // vertical lines for (int xi = int(left); xi <= int(right); ++xi) { QLineF line(xi * gridStep, bottom * gridStep, xi * gridStep, top * gridStep); painter->drawLine(line); } // horizontal lines for (int yi = int(bottom); yi <= int(top); ++yi) { QLineF line(left * gridStep, yi * gridStep, right * gridStep, yi * gridStep); painter->drawLine(line); } }; auto const &flowViewStyle = StyleCollection::flowViewStyle(); QBrush bBrush = backgroundBrush(); QPen pfine(flowViewStyle.FineGridColor, 1.0); painter->setPen(pfine); drawGrid(15); QPen p(flowViewStyle.CoarseGridColor, 1.0); painter->setPen(p); drawGrid(150); } void FlowView:: showEvent(QShowEvent *event) { _scene->setSceneRect(this->rect()); QGraphicsView::showEvent(event); } FlowScene * FlowView:: scene() { return _scene; } qnodeeditor-2.1.5+ds1/src/FlowViewStyle.cpp000066400000000000000000000051311361521262000205730ustar00rootroot00000000000000#include "FlowViewStyle.hpp" #include #include #include #include #include #include #include "StyleCollection.hpp" using QtNodes::FlowViewStyle; inline void initResources() { Q_INIT_RESOURCE(QNodeEditor_resources); } FlowViewStyle:: FlowViewStyle() { // Explicit resources inialization for preventing the static initialization // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order initResources(); // This configuration is stored inside the compiled unit and is loaded statically loadJsonFile(":DefaultStyle.json"); } FlowViewStyle:: FlowViewStyle(QString jsonText) { loadJsonText(jsonText); } void FlowViewStyle:: setStyle(QString jsonText) { FlowViewStyle style(jsonText); StyleCollection::setFlowViewStyle(style); } #ifdef STYLE_DEBUG #define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(v, variable) { \ if (v.type() == QJsonValue::Undefined || \ v.type() == QJsonValue::Null) \ qWarning() << "Undefined value for parameter:" << #variable; \ } #else #define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(v, variable) #endif #define FLOW_VIEW_STYLE_READ_COLOR(values, variable) { \ auto valueRef = values[#variable]; \ FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ if (valueRef.isArray()) { \ auto colorArray = valueRef.toArray(); \ std::vector rgb; rgb.reserve(3); \ for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ rgb.push_back((*it).toInt()); \ } \ variable = QColor(rgb[0], rgb[1], rgb[2]); \ } else { \ variable = QColor(valueRef.toString()); \ } \ } void FlowViewStyle:: loadJsonFile(QString styleFile) { QFile file(styleFile); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Couldn't open file " << styleFile; return; } loadJsonFromByteArray(file.readAll()); } void FlowViewStyle:: loadJsonText(QString jsonText) { loadJsonFromByteArray(jsonText.toUtf8()); } void FlowViewStyle:: loadJsonFromByteArray(QByteArray const &byteArray) { QJsonDocument json(QJsonDocument::fromJson(byteArray)); QJsonObject topLevelObject = json.object(); QJsonValueRef nodeStyleValues = topLevelObject["FlowViewStyle"]; QJsonObject obj = nodeStyleValues.toObject(); FLOW_VIEW_STYLE_READ_COLOR(obj, BackgroundColor); FLOW_VIEW_STYLE_READ_COLOR(obj, FineGridColor); FLOW_VIEW_STYLE_READ_COLOR(obj, CoarseGridColor); } qnodeeditor-2.1.5+ds1/src/Node.cpp000066400000000000000000000141601361521262000166770ustar00rootroot00000000000000#include "Node.hpp" #include #include #include #include "FlowScene.hpp" #include "NodeGraphicsObject.hpp" #include "NodeDataModel.hpp" #include "ConnectionGraphicsObject.hpp" #include "ConnectionState.hpp" using QtNodes::Node; using QtNodes::NodeGeometry; using QtNodes::NodeState; using QtNodes::NodeData; using QtNodes::NodeDataModel; using QtNodes::NodeDataType; using QtNodes::NodeGraphicsObject; using QtNodes::PortIndex; using QtNodes::PortType; Node:: Node(std::unique_ptr &&dataModel) : _uid(QUuid::createUuid()) , _nodeDataModel(std::move(dataModel)) , _nodeState(_nodeDataModel) , _nodeGeometry(_nodeDataModel) , _nodeGraphicsObject(nullptr) { _nodeGeometry.recalculateSize(); // propagate data: model => node connect(_nodeDataModel.get(), &NodeDataModel::dataUpdated, this, &Node::onDataUpdated); connect(_nodeDataModel.get(), &NodeDataModel::embeddedWidgetSizeUpdated, this, &Node::onNodeSizeUpdated); //connect(_nodeDataModel.get(), &NodeDataModel::portCountChanged, // this, &Node::onPortCountChanged); } Node:: ~Node() = default; QJsonObject Node:: save() const { QJsonObject nodeJson; nodeJson["id"] = _uid.toString(); nodeJson["model"] = _nodeDataModel->save(); QJsonObject obj; obj["x"] = _nodeGraphicsObject->pos().x(); obj["y"] = _nodeGraphicsObject->pos().y(); nodeJson["position"] = obj; return nodeJson; } void Node:: restore(QJsonObject const &json) { _uid = QUuid(json["id"].toString()); QJsonObject positionJson = json["position"].toObject(); QPointF point(positionJson["x"].toDouble(), positionJson["y"].toDouble()); _nodeGraphicsObject->setPos(point); _nodeDataModel->restore(json["model"].toObject()); } QUuid Node:: id() const { return _uid; } void Node:: reactToPossibleConnection(PortType reactingPortType, NodeDataType const &reactingDataType, QPointF const &scenePoint) { QTransform const t = _nodeGraphicsObject->sceneTransform(); // QPointF nodePoint = t.inverted().map(scenePoint); _nodeGeometry.setDraggingPosition(nodePoint); // _nodeGraphicsObject->update(); _nodeState.setReaction(NodeState::REACTING, reactingPortType, reactingDataType); } void Node:: resetReactionToConnection() { _nodeState.setReaction(NodeState::NOT_REACTING); _nodeGraphicsObject->update(); } NodeGraphicsObject const & Node:: nodeGraphicsObject() const { return *_nodeGraphicsObject.get(); } NodeGraphicsObject & Node:: nodeGraphicsObject() { return *_nodeGraphicsObject.get(); } void Node:: setGraphicsObject(std::unique_ptr &&graphics) { _nodeGraphicsObject = std::move(graphics); _nodeGeometry.recalculateSize(); } NodeGeometry & Node:: nodeGeometry() { return _nodeGeometry; } NodeGeometry const & Node:: nodeGeometry() const { return _nodeGeometry; } NodeState const & Node:: nodeState() const { return _nodeState; } NodeState & Node:: nodeState() { return _nodeState; } NodeDataModel * Node:: nodeDataModel() const { return _nodeDataModel.get(); } void Node:: propagateData(PortIndex inPortIndex) const { NodeState const &state = nodeState(); NodeState::ConnectionPtrSet connections = state.connections(PortType::In, inPortIndex); std::vector> nodeData; nodeData.reserve(connections.size()); for (const auto &connection : connections) { Connection *c = connection.second; Node *outNode = c->getNode(PortType::Out); PortIndex outNodeIndex = c->getPortIndex(PortType::Out); std::shared_ptr outData = outNode->nodeDataModel()->outData(outNodeIndex); TypeConverter converter = c->getTypeConverter(); if (converter != nullptr) { outData = converter(outData); } nodeData.push_back(outData); } _nodeDataModel->setInData(std::move(nodeData), inPortIndex); //Recalculate the nodes visuals. A data change can result in the node taking more space than before, so this forces a recalculate+repaint on the affected node _nodeGraphicsObject->setGeometryChanged(); _nodeGeometry.recalculateSize(); _nodeGraphicsObject->update(); _nodeGraphicsObject->moveConnections(); recalculateVisuals(); } void Node:: onDataUpdated(PortIndex index) { auto nodeData = _nodeDataModel->outData(index); auto connections = _nodeState.connections(PortType::Out, index); for (auto const &c : connections) c.second->propagateData(nodeData); } void Node:: onNodeSizeUpdated() { if (nodeDataModel()->embeddedWidget()) { nodeDataModel()->embeddedWidget()->adjustSize(); } nodeGeometry().recalculateSize(); for (PortType type : { PortType::In, PortType::Out }) { for (auto &conn_set : nodeState().getEntries(type)) { for (auto &pair : conn_set) { Connection *conn = pair.second; conn->getConnectionGraphicsObject().move(); } } } } void Node:: onPortCountChanged() { // Remove connections for (auto portType : { PortType::In, PortType::Out }) { auto &entries = _nodeState.getEntries(portType); int oldCount = static_cast(entries.size()); int newCount = _nodeDataModel->nPorts(portType); for (PortIndex index = newCount ; index < oldCount ; ++index) { auto connections = entries[index]; for (auto const &c : connections) { Q_EMIT killConnection(*c.second); } } } _nodeState.updatePortCount(_nodeDataModel->nPorts(PortType::In), _nodeDataModel->nPorts(PortType::Out)); nodeGeometry().updatePortCount(); recalculateVisuals(); } void Node:: recalculateVisuals() const { _nodeGraphicsObject->setGeometryChanged(); _nodeGeometry.recalculateSize(); _nodeGraphicsObject->update(); _nodeGraphicsObject->moveConnections(); } qnodeeditor-2.1.5+ds1/src/NodeConnectionInteraction.cpp000066400000000000000000000141441361521262000231210ustar00rootroot00000000000000#include "NodeConnectionInteraction.hpp" #include "ConnectionGraphicsObject.hpp" #include "NodeGraphicsObject.hpp" #include "NodeDataModel.hpp" #include "DataModelRegistry.hpp" #include "FlowScene.hpp" using QtNodes::NodeConnectionInteraction; using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::FlowScene; using QtNodes::Node; using QtNodes::Connection; using QtNodes::NodeDataModel; using QtNodes::TypeConverter; NodeConnectionInteraction:: NodeConnectionInteraction(Node &node, Connection &connection, FlowScene &scene) : _node(&node) , _connection(&connection) , _scene(&scene) {} bool NodeConnectionInteraction:: canConnect(PortIndex &portIndex, TypeConverter &converter) const { // 1) Connection requires a port PortType requiredPort = connectionRequiredPort(); if (requiredPort == PortType::None) { return false; } // 1.5) Forbid connecting the node to itself Node *node = _connection->getNode(oppositePort(requiredPort)); if (node == _node) return false; // 2) connection point is on top of the node port QPointF connectionPoint = connectionEndScenePosition(requiredPort); portIndex = nodePortIndexUnderScenePoint(requiredPort, connectionPoint); if (portIndex == INVALID) { return false; } // 3) Node port is vacant // port should be empty if (!nodePortIsEmpty(requiredPort, portIndex)) return false; // 4) Connection type equals node port type, or there is a registered type conversion that can translate between the two auto connectionDataType = _connection->dataType(oppositePort(requiredPort)); auto const &modelTarget = _node->nodeDataModel(); NodeDataType candidateNodeDataType = modelTarget->dataType(requiredPort, portIndex); if (connectionDataType.id != candidateNodeDataType.id) { if (requiredPort == PortType::In) { converter = _scene->registry().getTypeConverter(connectionDataType, candidateNodeDataType); } else if (requiredPort == PortType::Out) { converter = _scene->registry().getTypeConverter(candidateNodeDataType, connectionDataType); } return (converter != nullptr); } return true; } bool NodeConnectionInteraction:: tryConnect() const { // 1) Check conditions from 'canConnect' PortIndex portIndex = INVALID; TypeConverter converter; if (!canConnect(portIndex, converter)) { return false; } // 1.5) If the connection is possible but a type conversion is needed, // assign a convertor to connection if (converter) { _connection->setTypeConverter(converter); } // 2) Assign node to required port in Connection PortType requiredPort = connectionRequiredPort(); _node->nodeState().setConnection(requiredPort, portIndex, *_connection); // 3) Assign Connection to empty port in NodeState // The port is not longer required after this function _connection->setNodeToPort(*_node, requiredPort, portIndex); // 4) Adjust Connection geometry _node->nodeGraphicsObject().moveConnections(); // 5) Poke model to intiate data transfer auto outNode = _connection->getNode(PortType::Out); if (outNode) { PortIndex outPortIndex = _connection->getPortIndex(PortType::Out); outNode->onDataUpdated(outPortIndex); } return true; } /// 1) Node and Connection should be already connected /// 2) If so, clear Connection entry in the NodeState /// 3) Set Connection end to 'requiring a port' bool NodeConnectionInteraction:: disconnect(PortType portToDisconnect) const { PortIndex portIndex = _connection->getPortIndex(portToDisconnect); NodeState &state = _node->nodeState(); // clear pointer to Connection in the NodeState state.getEntries(portToDisconnect)[portIndex].erase(_connection->id()); //state.getEntries(portToDisconnect)[portIndex].clear(); // 4) Propagate invalid data to IN node _connection->propagateEmptyData(); // clear Connection side _connection->clearNode(portToDisconnect); _connection->setRequiredPort(portToDisconnect); _connection->getConnectionGraphicsObject().grabMouse(); return true; } // ------------------ util functions below PortType NodeConnectionInteraction:: connectionRequiredPort() const { auto const &state = _connection->connectionState(); return state.requiredPort(); } QPointF NodeConnectionInteraction:: connectionEndScenePosition(PortType portType) const { auto &go = _connection->getConnectionGraphicsObject(); ConnectionGeometry &geometry = _connection->connectionGeometry(); QPointF endPoint = geometry.getEndPoint(portType); return go.mapToScene(endPoint); } QPointF NodeConnectionInteraction:: nodePortScenePosition(PortType portType, PortIndex portIndex) const { NodeGeometry const &geom = _node->nodeGeometry(); QPointF p = geom.portScenePosition(portIndex, portType); NodeGraphicsObject &ngo = _node->nodeGraphicsObject(); return ngo.sceneTransform().map(p); } PortIndex NodeConnectionInteraction:: nodePortIndexUnderScenePoint(PortType portType, QPointF const &scenePoint) const { NodeGeometry const &nodeGeom = _node->nodeGeometry(); QTransform sceneTransform = _node->nodeGraphicsObject().sceneTransform(); PortIndex portIndex = nodeGeom.checkHitScenePoint(portType, scenePoint, sceneTransform); return portIndex; } bool NodeConnectionInteraction:: nodePortIsEmpty(PortType portType, PortIndex portIndex) const { if (portType == PortType::None) { return false; } NodeState const &nodeState = _node->nodeState(); auto const &entries = nodeState.getEntries(portType); if (entries[portIndex].empty()) return true; if (portType == PortType::In) return _node->nodeDataModel()->portInConnectionPolicy(portIndex) == NodeDataModel::ConnectionPolicy::Many; return _node->nodeDataModel()->portOutConnectionPolicy(portIndex) == NodeDataModel::ConnectionPolicy::Many; } qnodeeditor-2.1.5+ds1/src/NodeConnectionInteraction.hpp000066400000000000000000000045751361521262000231350ustar00rootroot00000000000000#pragma once #include "Node.hpp" #include "Connection.hpp" namespace QtNodes { class DataModelRegistry; class FlowScene; class NodeDataModel; /// Class performs various operations on the Node and Connection pair. /// An instance should be created on the stack and destroyed when /// the operation is completed class NodeConnectionInteraction { public: NodeConnectionInteraction(Node &node, Connection &connection, FlowScene &scene); /// Can connect when following conditions are met: /// 1) Connection 'requires' a port /// 2) Connection's vacant end is above the node port /// 3) Node port is vacant /// 4) Connection type equals node port type, or there is a registered type conversion that can translate between the two bool canConnect(PortIndex &portIndex, TypeConverter &converter) const; /// 1) Check conditions from 'canConnect' /// 1.5) If the connection is possible but a type conversion is needed, add a converter node to the scene, and connect it properly /// 2) Assign node to required port in Connection /// 3) Assign Connection to empty port in NodeState /// 4) Adjust Connection geometry /// 5) Poke model to initiate data transfer bool tryConnect() const; /// 1) Node and Connection should be already connected /// 2) If so, clear Connection entry in the NodeState /// 3) Propagate invalid data to IN node /// 4) Set Connection end to 'requiring a port' bool disconnect(PortType portToDisconnect) const; private: PortType connectionRequiredPort() const; QPointF connectionEndScenePosition(PortType) const; QPointF nodePortScenePosition(PortType portType, PortIndex portIndex) const; PortIndex nodePortIndexUnderScenePoint(PortType portType, QPointF const &p) const; bool nodePortIsEmpty(PortType portType, PortIndex portIndex) const; private: Node *_node; Connection *_connection; FlowScene *_scene; }; } qnodeeditor-2.1.5+ds1/src/NodeDataModel.cpp000066400000000000000000000026561361521262000204610ustar00rootroot00000000000000#include "NodeDataModel.hpp" #include "StyleCollection.hpp" using QtNodes::NodeDataModel; using QtNodes::NodeStyle; using QtNodes::NodeData; using QtNodes::PortIndex; using QtNodes::PortType; NodeDataModel::NodeDataModel() : _nodeStyle(StyleCollection::nodeStyle()) { // Derived classes can initialize specific style here } QJsonObject NodeDataModel:: save() const { QJsonObject modelJson; modelJson["name"] = name(); return modelJson; } NodeDataModel::ConnectionPolicy NodeDataModel:: portConnectionPolicy(PortType portType, PortIndex portIndex) const { ConnectionPolicy result = ConnectionPolicy::One; switch (portType) { case PortType::In: result = portInConnectionPolicy(portIndex); break; case PortType::Out: result = portOutConnectionPolicy(portIndex); break; default: break; } return result; } NodeStyle const & NodeDataModel:: nodeStyle() const { return _nodeStyle; } void NodeDataModel:: setNodeStyle(NodeStyle const &style) { _nodeStyle = style; } void NodeDataModel:: setInData(std::vector > nodeData, PortIndex port) { if (portInConnectionPolicy(port) == QtNodes::NodeDataModel::ConnectionPolicy::One) { if (nodeData.empty()) setInData(nullptr, port); else setInData(nodeData[0], port); } else { Q_ASSERT(false); } } qnodeeditor-2.1.5+ds1/src/NodeGeometry.cpp000066400000000000000000000253551361521262000204230ustar00rootroot00000000000000#include "NodeGeometry.hpp" #include #include #include "PortType.hpp" #include "NodeState.hpp" #include "NodeDataModel.hpp" #include "Node.hpp" #include "NodeGraphicsObject.hpp" #include "StyleCollection.hpp" #include #include #include using QtNodes::NodeGeometry; using QtNodes::NodeDataModel; using QtNodes::PortIndex; using QtNodes::PortType; using QtNodes::Node; NodeGeometry:: NodeGeometry(std::unique_ptr const &dataModel) : _width(100) , _height(150) , _inputPortWidth(70) , _outputPortWidth(70) , _entryHeight(20) , _spacing(20) , _hovered(false) , _nSources(dataModel->nPorts(PortType::Out)) , _nSinks(dataModel->nPorts(PortType::In)) , _draggingPos(-1000, -1000) , _dataModel(dataModel) , _fontMetrics(QFont()) , _boldFontMetrics(QFont()) , _portsXoffset(5) , _entryHeightCalculated(false) , _portsWidthCalculated(false) , _captionHeightCalculated(false) , _captionWidthCalculated(false) { QFont f; f.setBold(true); _boldFontMetrics = QFontMetrics(f); } unsigned int NodeGeometry::nSources() const { return _dataModel->nPorts(PortType::Out); } unsigned int NodeGeometry::nSinks() const { return _dataModel->nPorts(PortType::In); } void NodeGeometry::updatePortCount() { _nSources = _dataModel->nPorts(PortType::Out); _nSinks = _dataModel->nPorts(PortType::In); } QRectF NodeGeometry:: entryBoundingRect() const { double const addon = 0.0; return QRectF(0 - addon, 0 - addon, _entryWidth + 2 * addon, _entryHeight + 2 * addon); } QRectF NodeGeometry:: boundingRect() const { auto const &nodeStyle = StyleCollection::nodeStyle(); double addon = 4 * nodeStyle.ConnectionPointDiameter; return QRectF(0 - addon, 0 - addon, _width + 2 * addon, _height + 2 * addon); } void NodeGeometry:: recalculateSize() const { _entryHeight = _fontMetrics.height(); if (!_entryHeightCalculated) { QRectF td_rect; for (uint i = 0; i < _dataModel->nPorts(PortType::In); i++) td_rect |= portRect(PortType::In, i); for (uint i = 0; i < _dataModel->nPorts(PortType::Out); i++) td_rect |= portRect(PortType::Out, i); _entryHeight = td_rect.height(); _entryHeightCalculated = true; } { unsigned int maxNumOfEntries = std::max(_nSinks, _nSources); unsigned int step = _entryHeight + _spacing; _height = step * maxNumOfEntries; } if (auto w = _dataModel->embeddedWidget()) { _height = std::max(_height, static_cast(w->height())); } _height += captionHeight(); if (!_portsWidthCalculated) { _inputPortWidth = portWidth(PortType::In); _outputPortWidth = portWidth(PortType::Out); _portsWidthCalculated = true; } _width = _inputPortWidth + _outputPortWidth + 2 * _spacing + _portsXoffset * 3; if (auto w = _dataModel->embeddedWidget()) { _width += w->width(); } _width = std::max(_width, captionWidth()); if (_dataModel->validationState() != NodeValidationState::Valid) { _width = std::max(_width, validationWidth()); _height += validationHeight() + _spacing; } } void NodeGeometry:: recalculateSize(QFont const &font) const { QFontMetrics fontMetrics(font); QFont boldFont = font; boldFont.setBold(true); QFontMetrics boldFontMetrics(boldFont); if (_boldFontMetrics != boldFontMetrics) { _fontMetrics = fontMetrics; _boldFontMetrics = boldFontMetrics; recalculateSize(); } } QPointF NodeGeometry:: portScenePosition(PortIndex index, PortType portType, QTransform const &t) const { auto const &nodeStyle = StyleCollection::nodeStyle(); unsigned int step = _entryHeight + _spacing; QPointF result; double totalHeight = 0.0; totalHeight += captionHeight(); totalHeight += step * index; // TODO: why? totalHeight += step / 2.0; switch (portType) { case PortType::Out: { double x = _width + nodeStyle.ConnectionPointDiameter; result = QPointF(x, totalHeight); break; } case PortType::In: { double x = 0.0 - nodeStyle.ConnectionPointDiameter; result = QPointF(x, totalHeight); break; } default: break; } return t.map(result); } PortIndex NodeGeometry:: checkHitScenePoint(PortType portType, QPointF const scenePoint, QTransform const &sceneTransform) const { auto const &nodeStyle = StyleCollection::nodeStyle(); PortIndex result = INVALID; if (portType == PortType::None) return result; double const tolerance = 2.0 * nodeStyle.ConnectionPointDiameter; size_t const nItems = _dataModel->nPorts(portType); for (size_t i = 0; i < nItems; ++i) { auto pp = portScenePosition(i, portType, sceneTransform); QPointF p = pp - scenePoint; auto distance = std::sqrt(QPointF::dotProduct(p, p)); if (distance < tolerance) { result = PortIndex(i); break; } } return result; } QRect NodeGeometry:: resizeRect() const { unsigned int rectSize = 7; return QRect(_width - rectSize, _height - rectSize, rectSize, rectSize); } QPointF NodeGeometry:: widgetPosition() const { if (auto w = _dataModel->embeddedWidget()) { if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) { // If the widget wants to use as much vertical space as possible, place it immediately after the caption. return QPointF(_spacing + portWidth(PortType::In), captionHeight()); } else { if (_dataModel->validationState() != NodeValidationState::Valid) { return QPointF(_spacing + portWidth(PortType::In), (captionHeight() + _height - validationHeight() - _spacing - w->height()) / 2.0); } return QPointF(_spacing + portWidth(PortType::In), (captionHeight() + _height - w->height()) / 2.0); } } return QPointF(); } int NodeGeometry:: equivalentWidgetHeight() const { if (_dataModel->validationState() != NodeValidationState::Valid) { return height() - captionHeight() + validationHeight(); } return height() - captionHeight(); } unsigned int NodeGeometry:: captionHeight() const { if (!_dataModel->captionVisible()) return 0; if (!_captionHeightCalculated) { auto const &nodeStyle = _dataModel->nodeStyle(); QString name = _dataModel->caption(); QTextDocument td; td.setDefaultStyleSheet(nodeStyle.NodeCaptionCss); td.setHtml(name); auto rect = calculateDocRect(td); _captionHeight = rect.height(); _captionHeightCalculated = true; } return _captionHeight; } unsigned int NodeGeometry:: captionWidth() const { if (!_dataModel->captionVisible()) return 0; if (!_captionWidthCalculated) { auto const &nodeStyle = _dataModel->nodeStyle(); QString name = _dataModel->caption(); QTextDocument td; td.setDefaultStyleSheet(nodeStyle.NodeCaptionCss); td.setHtml(name); auto rect = calculateDocRect(td); _captionWidth = rect.width(); _captionWidthCalculated = true; } return _captionWidth; } unsigned int NodeGeometry:: validationHeight() const { QString msg = _dataModel->validationMessage(); return _boldFontMetrics.boundingRect(msg).height(); } unsigned int NodeGeometry:: validationWidth() const { QString msg = _dataModel->validationMessage(); return _boldFontMetrics.boundingRect(msg).width(); } QPointF NodeGeometry:: calculateNodePositionBetweenNodePorts(PortIndex targetPortIndex, PortType targetPort, Node *targetNode, PortIndex sourcePortIndex, PortType sourcePort, Node *sourceNode, Node &newNode) { //Calculating the nodes position in the scene. It'll be positioned half way between the two ports that it "connects". //The first line calculates the halfway point between the ports (node position + port position on the node for both nodes averaged). //The second line offsets this coordinate with the size of the new node, so that the new nodes center falls on the originally //calculated coordinate, instead of it's upper left corner. auto converterNodePos = (sourceNode->nodeGraphicsObject().pos() + sourceNode->nodeGeometry().portScenePosition(sourcePortIndex, sourcePort) + targetNode->nodeGraphicsObject().pos() + targetNode->nodeGeometry().portScenePosition(targetPortIndex, targetPort)) / 2.0f; converterNodePos.setX(converterNodePos.x() - newNode.nodeGeometry().width() / 2.0f); converterNodePos.setY(converterNodePos.y() - newNode.nodeGeometry().height() / 2.0f); return converterNodePos; } unsigned int NodeGeometry:: portWidth(PortType portType) const { unsigned width = 0; for (auto i = 0ul; i < _dataModel->nPorts(portType); ++i) { QString name; if (_dataModel->portCaptionVisible(portType, i)) { name = _dataModel->portCaption(portType, i); } else { name = _dataModel->dataType(portType, i).name; } width = std::max(unsigned(_fontMetrics.horizontalAdvance(name)), width); } return width; } QRectF NodeGeometry::calculateDocRect(const QTextDocument &td) { QRectF rect; auto layout = td.documentLayout(); QTextBlock currentBlock = td.begin(); while (currentBlock.isValid()) { rect |= layout->blockBoundingRect(currentBlock); currentBlock = currentBlock.next(); } return rect; } QRectF NodeGeometry::portRect(PortType portType, const PortIndex &index) const { if (int(portType) > 2) { return QRectF(); } auto &curr_map = _cachedPortRects[int(portType) - 1]; QRectF res; if (curr_map.contains(index)) { res = curr_map[index]; } else { auto const &nodeStyle = _dataModel->nodeStyle(); QString name; if (_dataModel->portCaptionVisible(portType, index)) { name = _dataModel->portCaption(portType, index); } else { name = _dataModel->dataType(portType, index).name; } QTextDocument td; td.setDefaultStyleSheet(nodeStyle.PortTextCss); td.setHtml(name); res = calculateDocRect(td); curr_map[index] = res; } return res; } qnodeeditor-2.1.5+ds1/src/NodeGraphicsObject.cpp000066400000000000000000000220231361521262000215040ustar00rootroot00000000000000#include "NodeGraphicsObject.hpp" #include #include #include #include #include "ConnectionGraphicsObject.hpp" #include "ConnectionState.hpp" #include "FlowScene.hpp" #include "NodePainter.hpp" #include "Node.hpp" #include "NodeDataModel.hpp" #include "NodeConnectionInteraction.hpp" #include "StyleCollection.hpp" using QtNodes::NodeGraphicsObject; using QtNodes::Node; using QtNodes::FlowScene; NodeGraphicsObject:: NodeGraphicsObject(FlowScene &scene, Node &node) : _scene(scene) , _node(node) , _locked(false) , _proxyWidget(nullptr) { _scene.addItem(this); setFlag(QGraphicsItem::ItemDoesntPropagateOpacityToChildren, true); setFlag(QGraphicsItem::ItemIsMovable, true); setFlag(QGraphicsItem::ItemIsFocusable, true); setFlag(QGraphicsItem::ItemIsSelectable, true); setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true); setCacheMode(QGraphicsItem::DeviceCoordinateCache); auto const &nodeStyle = node.nodeDataModel()->nodeStyle(); { auto effect = new QGraphicsDropShadowEffect; effect->setOffset(4, 4); effect->setBlurRadius(20); effect->setColor(nodeStyle.ShadowColor); setGraphicsEffect(effect); } setOpacity(nodeStyle.Opacity); setAcceptHoverEvents(true); setZValue(0); embedQWidget(); // connect to the move signals to emit the move signals in FlowScene auto onMoveSlot = [this] { _scene.nodeMoved(_node, pos()); }; connect(this, &QGraphicsObject::xChanged, this, onMoveSlot); connect(this, &QGraphicsObject::yChanged, this, onMoveSlot); } NodeGraphicsObject:: ~NodeGraphicsObject() { _scene.removeItem(this); } Node & NodeGraphicsObject:: node() { return _node; } Node const & NodeGraphicsObject:: node() const { return _node; } void NodeGraphicsObject:: embedQWidget() { NodeGeometry &geom = _node.nodeGeometry(); if (auto w = _node.nodeDataModel()->embeddedWidget()) { _proxyWidget = new QGraphicsProxyWidget(this); _proxyWidget->setWidget(w); _proxyWidget->setPreferredWidth(5); geom.recalculateSize(); if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) { // If the widget wants to use as much vertical space as possible, set it to have the geom's equivalentWidgetHeight. _proxyWidget->setMinimumHeight(geom.equivalentWidgetHeight()); } _proxyWidget->setPos(geom.widgetPosition()); update(); _proxyWidget->setOpacity(1.0); _proxyWidget->setFlag(QGraphicsItem::ItemIgnoresParentOpacity); } } QRectF NodeGraphicsObject:: boundingRect() const { return _node.nodeGeometry().boundingRect(); } void NodeGraphicsObject:: setGeometryChanged() { prepareGeometryChange(); } void NodeGraphicsObject:: moveConnections() const { NodeState const &nodeState = _node.nodeState(); for (PortType portType : { PortType::In, PortType::Out }) { auto const &connectionEntries = nodeState.getEntries(portType); for (auto const &connections : connectionEntries) { for (auto &con : connections) con.second->getConnectionGraphicsObject().move(); } } } void NodeGraphicsObject:: lock(bool locked) { _locked = locked; setFlag(QGraphicsItem::ItemIsMovable, !locked); setFlag(QGraphicsItem::ItemIsFocusable, !locked); setFlag(QGraphicsItem::ItemIsSelectable, !locked); } void NodeGraphicsObject:: paint(QPainter *painter, QStyleOptionGraphicsItem const *option, QWidget *) { painter->setClipRect(option->exposedRect); NodePainter::paint(painter, _node, _scene); } QVariant NodeGraphicsObject:: itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemPositionChange && scene()) { moveConnections(); } return QGraphicsItem::itemChange(change, value); } void NodeGraphicsObject:: mousePressEvent(QGraphicsSceneMouseEvent *event) { if (_locked) return; // deselect all other items after this one is selected if (!isSelected() && !(event->modifiers() & Qt::ControlModifier)) { _scene.clearSelection(); } for (PortType portToCheck : { PortType::In, PortType::Out }) { NodeGeometry const &nodeGeometry = _node.nodeGeometry(); // TODO do not pass sceneTransform int const portIndex = nodeGeometry.checkHitScenePoint(portToCheck, event->scenePos(), sceneTransform()); if (portIndex != INVALID) { NodeState const &nodeState = _node.nodeState(); std::unordered_map connections = nodeState.connections(portToCheck, portIndex); // start dragging existing connection if (!connections.empty() && portToCheck == PortType::In) { auto con = connections.begin()->second; NodeConnectionInteraction interaction(_node, *con, _scene); interaction.disconnect(portToCheck); } else { // initialize new Connection if (portToCheck == PortType::Out) { auto const outPolicy = _node.nodeDataModel()->portOutConnectionPolicy(portIndex); if (!connections.empty() && outPolicy == NodeDataModel::ConnectionPolicy::One) { _scene.deleteConnection(*connections.begin()->second); } } // todo add to FlowScene auto connection = _scene.createConnection(portToCheck, _node, portIndex); _node.nodeState().setConnection(portToCheck, portIndex, *connection); connection->getConnectionGraphicsObject().grabMouse(); } } } auto pos = event->pos(); auto &geom = _node.nodeGeometry(); auto &state = _node.nodeState(); if (_node.nodeDataModel()->resizable() && geom.resizeRect().contains(QPoint(pos.x(), pos.y()))) { state.setResizing(true); } } void NodeGraphicsObject:: mouseMoveEvent(QGraphicsSceneMouseEvent *event) { auto &geom = _node.nodeGeometry(); auto &state = _node.nodeState(); if (state.resizing()) { auto diff = event->pos() - event->lastPos(); if (auto w = _node.nodeDataModel()->embeddedWidget()) { prepareGeometryChange(); auto oldSize = w->size(); oldSize += QSize(diff.x(), diff.y()); w->setFixedSize(oldSize); _proxyWidget->setMinimumSize(oldSize); _proxyWidget->setMaximumSize(oldSize); _proxyWidget->setPos(geom.widgetPosition()); geom.recalculateSize(); update(); moveConnections(); event->accept(); } } else { QGraphicsObject::mouseMoveEvent(event); if (event->lastPos() != event->pos()) moveConnections(); event->ignore(); } QRectF r = scene()->sceneRect(); r = r.united(mapToScene(boundingRect()).boundingRect()); scene()->setSceneRect(r); } void NodeGraphicsObject:: mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { auto &state = _node.nodeState(); state.setResizing(false); QGraphicsObject::mouseReleaseEvent(event); // position connections precisely after fast node move moveConnections(); _scene.nodeClicked(node()); } void NodeGraphicsObject:: hoverEnterEvent(QGraphicsSceneHoverEvent *event) { // bring all the colliding nodes to background QList overlapItems = collidingItems(); for (QGraphicsItem *item : overlapItems) { if (item->zValue() > 0.0) { item->setZValue(0.0); } } // bring this node forward setZValue(1.0); _node.nodeGeometry().setHovered(true); update(); _scene.nodeHovered(node(), event->screenPos()); event->accept(); } void NodeGraphicsObject:: hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { _node.nodeGeometry().setHovered(false); update(); _scene.nodeHoverLeft(node()); event->accept(); } void NodeGraphicsObject:: hoverMoveEvent(QGraphicsSceneHoverEvent *event) { auto pos = event->pos(); auto &geom = _node.nodeGeometry(); if (_node.nodeDataModel()->resizable() && geom.resizeRect().contains(QPoint(pos.x(), pos.y()))) { setCursor(QCursor(Qt::SizeFDiagCursor)); } else { setCursor(QCursor()); } event->accept(); } void NodeGraphicsObject:: mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { QGraphicsItem::mouseDoubleClickEvent(event); _scene.nodeDoubleClicked(node()); } void NodeGraphicsObject:: contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { _scene.nodeContextMenu(node(), mapToScene(event->pos())); } qnodeeditor-2.1.5+ds1/src/NodePainter.cpp000066400000000000000000000255221361521262000202260ustar00rootroot00000000000000#include "NodePainter.hpp" #include #include #include #include #include #include "StyleCollection.hpp" #include "PortType.hpp" #include "NodeGraphicsObject.hpp" #include "NodeGeometry.hpp" #include "NodeState.hpp" #include "NodeDataModel.hpp" #include "Node.hpp" #include "FlowScene.hpp" using QtNodes::NodePainter; using QtNodes::NodeGeometry; using QtNodes::NodeGraphicsObject; using QtNodes::Node; using QtNodes::NodeState; using QtNodes::NodeDataModel; using QtNodes::FlowScene; void NodePainter:: paint(QPainter *painter, Node &node, FlowScene const &scene) { NodeGeometry const &geom = node.nodeGeometry(); NodeState const &state = node.nodeState(); NodeGraphicsObject const &graphicsObject = node.nodeGraphicsObject(); geom.recalculateSize(painter->font()); //-------------------------------------------- NodeDataModel const *model = node.nodeDataModel(); drawNodeRect(painter, geom, model, graphicsObject); drawConnectionPoints(painter, geom, state, model, scene); drawFilledConnectionPoints(painter, geom, state, model); drawModelName(painter, geom, state, model); drawEntryLabels(painter, geom, state, model); drawResizeRect(painter, geom, model); drawValidationRect(painter, geom, model, graphicsObject); /// call custom painter if (auto painterDelegate = model->painterDelegate()) { painterDelegate->paint(painter, geom, model); } } void NodePainter:: drawNodeRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model, NodeGraphicsObject const &graphicsObject) { NodeStyle const &nodeStyle = model->nodeStyle(); auto color = graphicsObject.isSelected() ? nodeStyle.SelectedBoundaryColor : nodeStyle.NormalBoundaryColor; if (geom.hovered()) { QPen p(color, nodeStyle.HoveredPenWidth); painter->setPen(p); } else { QPen p(color, nodeStyle.PenWidth); painter->setPen(p); } QLinearGradient gradient(QPointF(0.0, 0.0), QPointF(2.0, geom.height())); gradient.setColorAt(0.0, nodeStyle.GradientColor0); gradient.setColorAt(0.03, nodeStyle.GradientColor1); gradient.setColorAt(0.97, nodeStyle.GradientColor2); gradient.setColorAt(1.0, nodeStyle.GradientColor3); painter->setBrush(gradient); float diam = nodeStyle.ConnectionPointDiameter; QRectF boundary(-diam, -diam, 2.0 * diam + geom.width(), 2.0 * diam + geom.height()); double const radius = 3.0; painter->drawRoundedRect(boundary, radius, radius); } void NodePainter:: drawConnectionPoints(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model, FlowScene const &scene) { NodeStyle const &nodeStyle = model->nodeStyle(); auto const &connectionStyle = StyleCollection::connectionStyle(); float diameter = nodeStyle.ConnectionPointDiameter; auto reducedDiameter = diameter * 0.6; for (PortType portType : { PortType::Out, PortType::In }) { size_t n = state.getEntries(portType).size(); for (unsigned int i = 0; i < n; ++i) { QPointF p = geom.portScenePosition(i, portType); auto const &dataType = model->dataType(portType, i); bool canConnect = (state.getEntries(portType)[i].empty() || model->portConnectionPolicy(portType, i) == NodeDataModel::ConnectionPolicy::Many); double r = 1.0; if (state.isReacting() && canConnect && portType == state.reactingPortType()) { auto diff = geom.draggingPos() - p; double dist = std::sqrt(QPointF::dotProduct(diff, diff)); bool typeConvertable = false; { if (portType == PortType::In) { typeConvertable = scene.registry().getTypeConverter(state.reactingDataType(), dataType) != nullptr; } else { typeConvertable = scene.registry().getTypeConverter(dataType, state.reactingDataType()) != nullptr; } } if (state.reactingDataType().id == dataType.id || typeConvertable) { double const thres = 40.0; r = (dist < thres) ? (2.0 - dist / thres) : 1.0; } else { double const thres = 80.0; r = (dist < thres) ? (dist / thres) : 1.0; } } if (connectionStyle.useDataDefinedColors()) { painter->setBrush(connectionStyle.normalColor(dataType.id)); } else { painter->setBrush(nodeStyle.ConnectionPointColor); } painter->drawEllipse(p, reducedDiameter * r, reducedDiameter * r); } }; } void NodePainter:: drawFilledConnectionPoints(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model) { NodeStyle const &nodeStyle = model->nodeStyle(); auto const &connectionStyle = StyleCollection::connectionStyle(); auto diameter = nodeStyle.ConnectionPointDiameter; for (PortType portType : { PortType::Out, PortType::In }) { size_t n = state.getEntries(portType).size(); for (size_t i = 0; i < n; ++i) { QPointF p = geom.portScenePosition(i, portType); if (!state.getEntries(portType)[i].empty()) { auto const &dataType = model->dataType(portType, i); if (connectionStyle.useDataDefinedColors()) { QColor const c = connectionStyle.normalColor(dataType.id); painter->setPen(c); painter->setBrush(c); } else { painter->setPen(nodeStyle.FilledConnectionPointColor); painter->setBrush(nodeStyle.FilledConnectionPointColor); } painter->drawEllipse(p, diameter * 0.4, diameter * 0.4); } } } } void NodePainter:: drawModelName(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model) { NodeStyle const &nodeStyle = model->nodeStyle(); Q_UNUSED(state); if (!model->captionVisible()) return; QTextDocument td; td.setDefaultStyleSheet(nodeStyle.NodeCaptionCss); td.setHtml(model->name()); auto rect = NodeGeometry::calculateDocRect(td); QPointF position((geom.width() - rect.width()) / 2.0, 0); position.setY(position.y() - rect.top()); painter->setPen(QColor(0, 255, 0)); painter->translate(position); td.drawContents(painter); painter->translate(-position); } void NodePainter:: drawEntryLabels(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model) { QFontMetrics const &metrics = painter->fontMetrics(); for (PortType portType : { PortType::Out, PortType::In }) { auto const &nodeStyle = model->nodeStyle(); auto &entries = state.getEntries(portType); size_t n = entries.size(); for (size_t i = 0; i < n; ++i) { QPointF p = geom.portScenePosition(i, portType); if (entries[i].empty()) painter->setOpacity(0.3); else painter->setOpacity(1.0); QString s; QTextDocument td; td.setDefaultStyleSheet(nodeStyle.PortTextCss); td.setHtml(s); auto rect = NodeGeometry::calculateDocRect(td); p.setY(p.y() - rect.height() / 2.0); switch (portType) { case PortType::In: p.setX(qreal(geom.portsXoffset())); break; case PortType::Out: p.setX(geom.width() - qreal(geom.portsXoffset()) - rect.width()); break; default: break; } p = p - rect.topLeft(); painter->translate(p); td.drawContents(painter); painter->translate(-p); } } } void NodePainter:: drawResizeRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model) { if (model->resizable()) { painter->setBrush(Qt::gray); painter->drawEllipse(geom.resizeRect()); } } void NodePainter:: drawValidationRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model, NodeGraphicsObject const &graphicsObject) { auto modelValidationState = model->validationState(); if (modelValidationState != NodeValidationState::Valid) { NodeStyle const &nodeStyle = model->nodeStyle(); auto color = graphicsObject.isSelected() ? nodeStyle.SelectedBoundaryColor : nodeStyle.NormalBoundaryColor; if (geom.hovered()) { QPen p(color, nodeStyle.HoveredPenWidth); painter->setPen(p); } else { QPen p(color, nodeStyle.PenWidth); painter->setPen(p); } //Drawing the validation message background if (modelValidationState == NodeValidationState::Error) { painter->setBrush(nodeStyle.ErrorColor); } else { painter->setBrush(nodeStyle.WarningColor); } double const radius = 3.0; float diam = nodeStyle.ConnectionPointDiameter; QRectF boundary(-diam, -diam + geom.height() - geom.validationHeight(), 2.0 * diam + geom.width(), 2.0 * diam + geom.validationHeight()); painter->drawRoundedRect(boundary, radius, radius); painter->setBrush(Qt::gray); //Drawing the validation message itself QString const &errorMsg = model->validationMessage(); QFont f = painter->font(); QFontMetrics metrics(f); auto rect = metrics.boundingRect(errorMsg); QPointF position((geom.width() - rect.width()) / 2.0, geom.height() - (geom.validationHeight() - diam) / 2.0); painter->setFont(f); painter->setPen(nodeStyle.FontColor); painter->drawText(position, errorMsg); } } qnodeeditor-2.1.5+ds1/src/NodePainter.hpp000066400000000000000000000043651361521262000202350ustar00rootroot00000000000000#pragma once #include namespace QtNodes { class Node; class NodeState; class NodeGeometry; class NodeGraphicsObject; class NodeDataModel; class FlowItemEntry; class FlowScene; class NodePainter { public: NodePainter(); public: static void paint(QPainter *painter, Node &node, FlowScene const &scene); static void drawNodeRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model, NodeGraphicsObject const &graphicsObject); static void drawModelName(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model); static void drawEntryLabels(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model); static void drawConnectionPoints(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model, FlowScene const &scene); static void drawFilledConnectionPoints(QPainter *painter, NodeGeometry const &geom, NodeState const &state, NodeDataModel const *model); static void drawResizeRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model); static void drawValidationRect(QPainter *painter, NodeGeometry const &geom, NodeDataModel const *model, NodeGraphicsObject const &graphicsObject); }; } qnodeeditor-2.1.5+ds1/src/NodeState.cpp000066400000000000000000000046271361521262000177070ustar00rootroot00000000000000#include "NodeState.hpp" #include "NodeDataModel.hpp" #include "Connection.hpp" using QtNodes::NodeState; using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::Connection; NodeState:: NodeState(std::unique_ptr const &model) : _inConnections(model->nPorts(PortType::In)) , _outConnections(model->nPorts(PortType::Out)) , _reaction(NOT_REACTING) , _reactingPortType(PortType::None) , _resizing(false) {} std::vector const & NodeState:: getEntries(PortType portType) const { if (portType == PortType::In) return _inConnections; else return _outConnections; } std::vector & NodeState:: getEntries(PortType portType) { if (portType == PortType::In) return _inConnections; else return _outConnections; } NodeState::ConnectionPtrSet NodeState:: connections(PortType portType, PortIndex portIndex) const { auto const &connections = getEntries(portType); return connections[portIndex]; } void NodeState:: setConnection(PortType portType, PortIndex portIndex, Connection &connection) { auto &connections = getEntries(portType); connections.at(portIndex).insert(std::make_pair(connection.id(), &connection)); } void NodeState:: eraseConnection(PortType portType, PortIndex portIndex, QUuid id) { getEntries(portType)[portIndex].erase(id); } NodeState::ReactToConnectionState NodeState:: reaction() const { return _reaction; } PortType NodeState:: reactingPortType() const { return _reactingPortType; } NodeDataType NodeState:: reactingDataType() const { return _reactingDataType; } void NodeState:: setReaction(ReactToConnectionState reaction, PortType reactingPortType, NodeDataType reactingDataType) { _reaction = reaction; _reactingPortType = reactingPortType; _reactingDataType = std::move(reactingDataType); } bool NodeState:: isReacting() const { return _reaction == REACTING; } void NodeState:: setResizing(bool resizing) { _resizing = resizing; } bool NodeState:: resizing() const { return _resizing; } void NodeState:: updatePortCount(int inPorts, int outPorts) { _inConnections.resize(inPorts); _outConnections.resize(outPorts); } qnodeeditor-2.1.5+ds1/src/NodeStyle.cpp000066400000000000000000000073751361521262000177320ustar00rootroot00000000000000#include "NodeStyle.hpp" #include #include #include #include #include #include #include #include "StyleCollection.hpp" using QtNodes::NodeStyle; inline void initResources() { Q_INIT_RESOURCE(QNodeEditor_resources); } NodeStyle:: NodeStyle() { // Explicit resources inialization for preventing the static initialization // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order initResources(); // This configuration is stored inside the compiled unit and is loaded statically loadJsonFile(":DefaultStyle.json"); } NodeStyle:: NodeStyle(QString jsonText) { loadJsonText(jsonText); } void NodeStyle:: setNodeStyle(QString jsonText) { NodeStyle style(jsonText); StyleCollection::setNodeStyle(style); } #ifdef STYLE_DEBUG #define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable) { \ if (v.type() == QJsonValue::Undefined || \ v.type() == QJsonValue::Null) \ qWarning() << "Undefined value for parameter:" << #variable; \ } #else #define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable) #endif #define NODE_STYLE_READ_COLOR(values, variable) { \ auto valueRef = values[#variable]; \ NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ if (valueRef.isArray()) { \ auto colorArray = valueRef.toArray(); \ std::vector rgb; rgb.reserve(3); \ for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ rgb.push_back((*it).toInt()); \ } \ variable = QColor(rgb[0], rgb[1], rgb[2]); \ } else { \ variable = QColor(valueRef.toString()); \ } \ } #define NODE_STYLE_READ_FLOAT(values, variable) { \ auto valueRef = values[#variable]; \ NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ variable = valueRef.toDouble(); \ } #define NODE_STYLE_READ_STRING(values, variable) { \ auto valueRef = values[#variable]; \ NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ variable = valueRef.toString(); \ } void NodeStyle:: loadJsonFile(QString styleFile) { QFile file(styleFile); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Couldn't open file " << styleFile; return; } loadJsonFromByteArray(file.readAll()); } void NodeStyle:: loadJsonText(QString jsonText) { loadJsonFromByteArray(jsonText.toUtf8()); } void NodeStyle:: loadJsonFromByteArray(QByteArray const &byteArray) { QJsonDocument json(QJsonDocument::fromJson(byteArray)); QJsonObject topLevelObject = json.object(); QJsonValueRef nodeStyleValues = topLevelObject["NodeStyle"]; QJsonObject obj = nodeStyleValues.toObject(); NODE_STYLE_READ_COLOR(obj, NormalBoundaryColor); NODE_STYLE_READ_COLOR(obj, SelectedBoundaryColor); NODE_STYLE_READ_COLOR(obj, GradientColor0); NODE_STYLE_READ_COLOR(obj, GradientColor1); NODE_STYLE_READ_COLOR(obj, GradientColor2); NODE_STYLE_READ_COLOR(obj, GradientColor3); NODE_STYLE_READ_COLOR(obj, ShadowColor); NODE_STYLE_READ_COLOR(obj, FontColor); NODE_STYLE_READ_COLOR(obj, FontColorFaded); //NODE_STYLE_READ_COLOR(obj, FontColorFaded); NODE_STYLE_READ_COLOR(obj, ConnectionPointColor); NODE_STYLE_READ_COLOR(obj, FilledConnectionPointColor); NODE_STYLE_READ_COLOR(obj, WarningColor); NODE_STYLE_READ_COLOR(obj, ErrorColor); NODE_STYLE_READ_FLOAT(obj, PenWidth); NODE_STYLE_READ_FLOAT(obj, HoveredPenWidth); NODE_STYLE_READ_FLOAT(obj, ConnectionPointDiameter); NODE_STYLE_READ_FLOAT(obj, Opacity); NODE_STYLE_READ_STRING(obj, PortTextCss); NODE_STYLE_READ_STRING(obj, NodeCaptionCss); } qnodeeditor-2.1.5+ds1/src/Properties.cpp000066400000000000000000000002301361521262000201370ustar00rootroot00000000000000#include "Properties.hpp" using QtNodes::Properties; void Properties:: put(QString const &name, QVariant const &v) { _values.insert(name, v); } qnodeeditor-2.1.5+ds1/src/Properties.hpp000066400000000000000000000015751361521262000201610ustar00rootroot00000000000000#pragma once #include #include #include "Export.hpp" namespace QtNodes { class NODE_EDITOR_PUBLIC Properties { public: void put(QString const &name, QVariant const &v); template bool get(QString name, T *v) const { QVariant const &var = _values[name]; if (var.canConvert()) { *v = _values[name].value(); return true; } return false; } QVariantMap const & values() const { return _values; } QVariantMap & values() { return _values; } private: QVariantMap _values; }; } qnodeeditor-2.1.5+ds1/src/StyleCollection.cpp000066400000000000000000000016251361521262000211300ustar00rootroot00000000000000#include "StyleCollection.hpp" using QtNodes::StyleCollection; using QtNodes::NodeStyle; using QtNodes::ConnectionStyle; using QtNodes::FlowViewStyle; NodeStyle const & StyleCollection:: nodeStyle() { return instance()._nodeStyle; } ConnectionStyle const & StyleCollection:: connectionStyle() { return instance()._connectionStyle; } FlowViewStyle const & StyleCollection:: flowViewStyle() { return instance()._flowViewStyle; } void StyleCollection:: setNodeStyle(NodeStyle nodeStyle) { instance()._nodeStyle = nodeStyle; } void StyleCollection:: setConnectionStyle(ConnectionStyle connectionStyle) { instance()._connectionStyle = connectionStyle; } void StyleCollection:: setFlowViewStyle(FlowViewStyle flowViewStyle) { instance()._flowViewStyle = flowViewStyle; } StyleCollection & StyleCollection:: instance() { static StyleCollection collection; return collection; } qnodeeditor-2.1.5+ds1/src/StyleCollection.hpp000066400000000000000000000021271361521262000211330ustar00rootroot00000000000000#pragma once #include "NodeStyle.hpp" #include "ConnectionStyle.hpp" #include "FlowViewStyle.hpp" namespace QtNodes { class StyleCollection { public: static NodeStyle const & nodeStyle(); static ConnectionStyle const & connectionStyle(); static FlowViewStyle const & flowViewStyle(); public: static void setNodeStyle(NodeStyle); static void setConnectionStyle(ConnectionStyle); static void setFlowViewStyle(FlowViewStyle); private: StyleCollection() = default; StyleCollection(StyleCollection const &) = delete; StyleCollection & operator=(StyleCollection const &) = delete; static StyleCollection & instance(); private: NodeStyle _nodeStyle; ConnectionStyle _connectionStyle; FlowViewStyle _flowViewStyle; }; }