From 99e1eb76775caf5871ee6190733d03a23479e6dd Mon Sep 17 00:00:00 2001 From: Tato Levicz Date: Mon, 12 Sep 2022 10:26:37 -0300 Subject: [PATCH 001/127] Add forward declaration in NodeDataModel.hpp (#323) * without this forward declaration it it wasn't compilling * Update NodeDataModel.hpp Co-authored-by: tatolevicz Co-authored-by: Dmitry Pinaev --- include/nodes/internal/NodeDataModel.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/nodes/internal/NodeDataModel.hpp b/include/nodes/internal/NodeDataModel.hpp index af7b06c3a..57428dba5 100644 --- a/include/nodes/internal/NodeDataModel.hpp +++ b/include/nodes/internal/NodeDataModel.hpp @@ -15,6 +15,8 @@ namespace QtNodes { +class NodePainterDelegate; + enum class NodeValidationState { Valid, From a6c403252edb3d05b0009eb2325e197496cc4647 Mon Sep 17 00:00:00 2001 From: Dmitry Pinaev Date: Tue, 15 Nov 2022 21:47:21 +0100 Subject: [PATCH 002/127] Fix redefined find_package macro for vcpkg (#328) (#329) (#324) The original idea was to be able to include the NodeEditor library to some super-project along with the included Catch2 (also as a subprobject), i.e.: ``` add_subdirectory(catch2) add_subdirectory(NodeEditor) ``` To do this we need to detect inside NodeEditor's CMake scripts whether the Catch2 target was already included. If not, we included our own Catch2 subdirectory. The chosen names of the macro caused recursion when called from within vcpkg. --- README.md | 14 ++++++++++++-- external/CMakeLists.txt | 8 +------- test/CMakeLists.txt | 2 -- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7971f2028..eca740043 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ connections updating the whole graph. - [Building](#building) - [Linux](#linux) - [Qt Creator](#qt-creator) +- [With Cmake using `vcpkg`](#with-cmake-using-vcpkg) - [>>> Version 3 Roadmap <<<](#-version-3-roadmap-) - [Citing](#citing) - [Youtube video:](#youtube-video) @@ -78,6 +79,15 @@ make -j && make install 4. `Build -> Build All` 5. Click the button `Run` +### With Cmake using [`vcpkg`](https://github.com/microsoft/vcpkg) + +1. Install `vcpkg` +2. Add the following flag in configuration step of `CMake` + ```bash + -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/scripts/buildsystems/vcpkg.cmake + ``` + + ## >>> Version 3 Roadmap <<< 1. Headless mode. [done] @@ -88,12 +98,12 @@ make -j && make install data propagation. 2. Build data propagation on top of the graph code [done]. - Fix old unit-tests. [in progress]. - - Fix save/restore. [in progress]. + - Fix save/restore. [done]. - Fix CI scriptst on travis and appveyor. [not started]. 3. Backward compatibility with Qt5 [not started/help needed]. 3. Write improved documentation based on Sphynx platform [done]. 4. Extend set of examples [partially done]. -5. Undo Redo [not started]. +5. Undo Redo [done]. 6. Python wrappring using PySide [HELP NEEDED]. 7. Implement grouping nodes [not started]. 8. GUI: fix scrolling for scene view window scrolling [need to check Qt6] diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 0159b3ad2..d7fbfcca7 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,13 +1,7 @@ if(BUILD_TESTING) - find_package(Catch2 2.13.7 QUIET) + find_package(Catch2 QUIET) if(NOT Catch2_FOUND) add_subdirectory(Catch2) endif() endif() - -macro(find_package pkg) - if(NOT TARGET "${pkg}") - _find_package(${ARGV}) - endif() -endmacro() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dffa83bfb..0c4a99a24 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,3 @@ -find_package(Catch2 2.3.0 REQUIRED) - if (Qt6_FOUND) find_package(Qt6 COMPONENTS Test) set(Qt Qt) From d09d173c1b9bce756dd9edc93b3a1232e446b639 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=87=E8=9B=BE?= <55644167+QianMoth@users.noreply.github.com> Date: Thu, 17 Nov 2022 19:18:15 +0800 Subject: [PATCH 003/127] Add cmake build action (#330) --- .github/workflows/cmake_build.yml | 80 +++++++++++++++++++++++++++++++ .gitignore | 3 ++ 2 files changed, 83 insertions(+) create mode 100644 .github/workflows/cmake_build.yml diff --git a/.github/workflows/cmake_build.yml b/.github/workflows/cmake_build.yml new file mode 100644 index 000000000..90882e2ef --- /dev/null +++ b/.github/workflows/cmake_build.yml @@ -0,0 +1,80 @@ +name: build nodeeditor + +on: + push: + branches: + - master + - main + tags: + - "*" + pull_request: + +jobs: + build-and-test: + + name: ${{ matrix.toolchain }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + toolchain: + - linux-gcc + - macos-clang + - windows-msvc + + configuration: + - Release + + include: + - toolchain: linux-gcc + os: ubuntu-20.04 + compiler: gcc + qt_version: "5.15.2" + modules: "" + + - toolchain: macos-clang + os: macos-latest + compiler: clang + qt_version: "5.15.2" + modules: "" + + - toolchain: windows-msvc + os: windows-latest + compiler: msvc + qt_version: "5.15.2" + modules: "" + + - toolchain: windows-msvc + os: windows-latest + compiler: msvc + qt_version: "6.3.0" + modules: "qt5compat" + + steps: + - name: Checkout Code + uses: actions/checkout@v2 + with: + submodules: true + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + version: ${{ matrix.qt_version }} + modules: ${{ matrix.modules }} + + - name: Setup (Linux) + if: startsWith (matrix.os, 'ubuntu') + run: sudo apt-get install libxkbcommon-dev + + - name: Setup VS tools (Windows) + if: startsWith (matrix.os, 'windows') + uses: egor-tensin/vs-shell@v2 + with: + arch: x64 + + - name: Configure (${{ matrix.configuration }}) + run: cmake -S . -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} + + - name: Build with ${{ matrix.compiler }} + run: cmake --build build --config ${{ matrix.configuration }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index c9eb5941f..d5272d93e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *.py *.pyc CMakeLists.txt.user + +build/ +.vscode/ From 8f9d655b50c8abad34a84a824bc9528ef6251e4c Mon Sep 17 00:00:00 2001 From: Romain Chardiny <38137329+romch007@users.noreply.github.com> Date: Thu, 24 Nov 2022 15:19:29 +0100 Subject: [PATCH 004/127] Add header files to CMake targets (#331) --- CMakeLists.txt | 32 +++++++++++++++++++++++ examples/calculator/CMakeLists.txt | 3 ++- examples/connection_colors/CMakeLists.txt | 3 ++- examples/example2/CMakeLists.txt | 3 ++- examples/images/CMakeLists.txt | 3 ++- examples/styles/CMakeLists.txt | 3 ++- test/CMakeLists.txt | 3 +++ 7 files changed, 45 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index feea6ad9f..ed54c86a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,10 +95,42 @@ set(CPP_SOURCE_FILES src/StyleCollection.cpp ) +set(HPP_HEADER_FILES + include/nodes/internal/Compiler.hpp + include/nodes/internal/Connection.hpp + include/nodes/internal/ConnectionGeometry.hpp + include/nodes/internal/ConnectionGraphicsObject.hpp + include/nodes/internal/ConnectionState.hpp + include/nodes/internal/ConnectionStyle.hpp + include/nodes/internal/DataModelRegistry.hpp + include/nodes/internal/Export.hpp + include/nodes/internal/FlowScene.hpp + include/nodes/internal/FlowView.hpp + include/nodes/internal/FlowViewStyle.hpp + include/nodes/internal/memory.hpp + include/nodes/internal/Node.hpp + include/nodes/internal/NodeData.hpp + include/nodes/internal/NodeDataModel.hpp + include/nodes/internal/NodeGeometry.hpp + include/nodes/internal/NodeGraphicsObject.hpp + include/nodes/internal/NodePainterDelegate.hpp + include/nodes/internal/NodeState.hpp + include/nodes/internal/NodeStyle.hpp + include/nodes/internal/OperatingSystem.hpp + include/nodes/internal/PortType.hpp + include/nodes/internal/QStringStdHash.hpp + include/nodes/internal/QUuidStdHash.hpp + include/nodes/internal/Serializable.hpp + include/nodes/internal/Style.hpp + include/nodes/internal/StyleCollection.hpp + include/nodes/internal/TypeConverter.hpp +) + # If we want to give the option to build a static library, # set BUILD_SHARED_LIBS option to OFF add_library(nodes ${CPP_SOURCE_FILES} + ${HPP_HEADER_FILES} ${RESOURCES} ) add_library(NodeEditor::nodes ALIAS nodes) diff --git a/examples/calculator/CMakeLists.txt b/examples/calculator/CMakeLists.txt index badf02f8d..b9ab36f31 100644 --- a/examples/calculator/CMakeLists.txt +++ b/examples/calculator/CMakeLists.txt @@ -1,5 +1,6 @@ file(GLOB_RECURSE CPPS ./*.cpp ) +file(GLOB_RECURSE HPPS ./*.hpp) -add_executable(calculator ${CPPS}) +add_executable(calculator ${CPPS} ${HPPS}) target_link_libraries(calculator nodes) diff --git a/examples/connection_colors/CMakeLists.txt b/examples/connection_colors/CMakeLists.txt index 784976743..1ef2d18e3 100644 --- a/examples/connection_colors/CMakeLists.txt +++ b/examples/connection_colors/CMakeLists.txt @@ -1,5 +1,6 @@ file(GLOB_RECURSE CPPS ./*.cpp ) +file(GLOB_RECURSE HPPS ./*.hpp ) -add_executable(connection_colors ${CPPS}) +add_executable(connection_colors ${CPPS} ${HPPS}) target_link_libraries(connection_colors nodes) diff --git a/examples/example2/CMakeLists.txt b/examples/example2/CMakeLists.txt index ae1f34dcf..2c8a980ac 100644 --- a/examples/example2/CMakeLists.txt +++ b/examples/example2/CMakeLists.txt @@ -1,5 +1,6 @@ file(GLOB_RECURSE CPPS ./*.cpp ) +file(GLOB_RECURSE HPPS ./*.hpp ) -add_executable(example2 ${CPPS}) +add_executable(example2 ${CPPS} ${HPPS}) target_link_libraries(example2 nodes) diff --git a/examples/images/CMakeLists.txt b/examples/images/CMakeLists.txt index c775b0986..cf2e8d4bc 100644 --- a/examples/images/CMakeLists.txt +++ b/examples/images/CMakeLists.txt @@ -1,5 +1,6 @@ file(GLOB_RECURSE CPPS ./*.cpp ) +file(GLOB_RECURSE HPPS ./*.hpp ) -add_executable(images ${CPPS}) +add_executable(images ${CPPS} ${HPPS}) target_link_libraries(images nodes) diff --git a/examples/styles/CMakeLists.txt b/examples/styles/CMakeLists.txt index 9c9e10c96..94a0b3965 100644 --- a/examples/styles/CMakeLists.txt +++ b/examples/styles/CMakeLists.txt @@ -1,5 +1,6 @@ file(GLOB_RECURSE CPPS ./*.cpp ) +file(GLOB_RECURSE HPPS ./*.hpp ) -add_executable(styles ${CPPS}) +add_executable(styles ${CPPS} ${HPPS}) target_link_libraries(styles nodes) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0c4a99a24..a225ef22e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,6 +12,9 @@ add_executable(test_nodes src/TestDataModelRegistry.cpp src/TestFlowScene.cpp src/TestNodeGraphicsObject.cpp + include/ApplicationSetup.hpp + include/Stringify.hpp + include/StubNodeDataModel.hpp ) target_include_directories(test_nodes From 82c4e754331a42456d1fc1f90589d622ad9c4448 Mon Sep 17 00:00:00 2001 From: Dmitry Pinaev Date: Tue, 2 Aug 2022 22:26:01 +0200 Subject: [PATCH 005/127] Refactor everything to use central Graph Model --- CMakeLists.txt | 40 +- examples/CMakeLists.txt | 9 +- examples/calculator/AdditionModel.hpp | 9 +- examples/calculator/Converters.cpp | 46 -- examples/calculator/Converters.hpp | 41 -- examples/calculator/DivisionModel.hpp | 13 +- examples/calculator/IntegerData.hpp | 37 - .../calculator/MathOperationDataModel.cpp | 20 +- .../calculator/MathOperationDataModel.hpp | 19 +- examples/calculator/ModuloModel.cpp | 118 ---- examples/calculator/ModuloModel.hpp | 107 --- examples/calculator/MultiplicationModel.hpp | 8 +- .../calculator/NumberDisplayDataModel.cpp | 22 +- .../calculator/NumberDisplayDataModel.hpp | 20 +- examples/calculator/NumberSourceDataModel.cpp | 2 +- examples/calculator/NumberSourceDataModel.hpp | 5 +- examples/calculator/SubtractionModel.hpp | 8 +- examples/calculator/main.cpp | 67 +- examples/connection_colors/main.cpp | 21 +- examples/connection_colors/models.hpp | 10 +- examples/example2/CMakeLists.txt | 6 - examples/graph/CMakeLists.txt | 5 + examples/graph/CustomGraphModel.hpp | 317 +++++++++ examples/graph/main.cpp | 67 ++ examples/images/ImageLoaderModel.cpp | 2 +- examples/images/ImageLoaderModel.hpp | 21 +- examples/images/ImageShowModel.cpp | 6 +- examples/images/ImageShowModel.hpp | 14 +- examples/images/main.cpp | 27 +- examples/styles/main.cpp | 44 +- examples/styles/models.hpp | 19 +- examples/text/CMakeLists.txt | 5 + examples/{example2 => text}/TextData.hpp | 0 .../TextDisplayDataModel.cpp | 0 .../TextDisplayDataModel.hpp | 13 +- .../TextSourceDataModel.cpp | 7 +- .../TextSourceDataModel.hpp | 7 +- examples/{example2 => text}/main.cpp | 19 +- include/nodes/BasicGraphicsScene | 1 + include/nodes/ConnectionIdUtils | 1 + include/nodes/{Connection => Connection_} | 0 include/nodes/DataFlowGraphModel | 1 + include/nodes/DataFlowGraphicsScene | 1 + include/nodes/Definitions | 1 + include/nodes/FlowScene | 1 - include/nodes/FlowView | 1 - include/nodes/FlowViewStyle | 1 - include/nodes/GraphModel | 1 + include/nodes/GraphicsView | 1 + include/nodes/GraphicsViewStyle | 1 + include/nodes/Node | 1 - include/nodes/TypeConverter | 1 - include/nodes/internal/BasicGraphicsScene.hpp | 200 ++++++ include/nodes/internal/Connection.hpp | 166 ----- include/nodes/internal/ConnectionGeometry.hpp | 60 -- .../internal/ConnectionGraphicsObject.hpp | 92 ++- include/nodes/internal/ConnectionIdHash.hpp | 79 +++ include/nodes/internal/ConnectionIdUtils.hpp | 160 +++++ include/nodes/internal/ConnectionState.hpp | 61 +- include/nodes/internal/ConnectionStyle.hpp | 10 +- include/nodes/internal/DataFlowGraphModel.hpp | 116 +++ .../nodes/internal/DataFlowGraphicsScene.hpp | 66 ++ include/nodes/internal/DataModelRegistry.hpp | 89 ++- include/nodes/internal/Definitions.hpp | 79 +++ include/nodes/internal/FlowScene.hpp | 165 ----- include/nodes/internal/FlowView.hpp | 69 -- include/nodes/internal/GraphModel.hpp | 183 +++++ include/nodes/internal/GraphicsView.hpp | 85 +++ ...lowViewStyle.hpp => GraphicsViewStyle.hpp} | 14 +- include/nodes/internal/Node.hpp | 124 ---- include/nodes/internal/NodeData.hpp | 17 +- include/nodes/internal/NodeDataModel.hpp | 108 ++- include/nodes/internal/NodeGeometry.hpp | 160 ++--- include/nodes/internal/NodeGraphicsObject.hpp | 64 +- include/nodes/internal/NodeState.hpp | 88 ++- include/nodes/internal/NodeStyle.hpp | 12 +- include/nodes/internal/PortType.hpp | 95 ++- include/nodes/internal/Style.hpp | 51 +- include/nodes/internal/StyleCollection.hpp | 35 +- include/nodes/internal/TypeConverter.hpp | 30 - include/nodes/internal/locateNode.hpp | 21 + include/nodes/internal/memory.hpp | 26 - resources/DefaultStyle.json | 2 +- src/BasicGraphicsScene.cpp | 418 +++++++++++ src/Connection.cpp | 450 ------------ src/ConnectionBlurEffect.cpp | 1 + src/ConnectionBlurEffect.hpp | 7 +- src/ConnectionGeometry.cpp | 131 ---- src/ConnectionGraphicsObject.cpp | 341 ++++++--- src/ConnectionPainter.cpp | 194 +++-- src/ConnectionPainter.hpp | 15 +- src/ConnectionState.cpp | 63 +- src/ConnectionStyle.cpp | 77 +- src/DataFlowGraphModel.cpp | 409 +++++++++++ src/DataFlowGraphicsScene.cpp | 389 ++++++++++ src/DataModelRegistry.cpp | 19 - src/FlowScene.cpp | 664 ------------------ src/GraphModel.cpp | 314 +++++++++ src/{FlowView.cpp => GraphicsView.cpp} | 229 +++--- ...lowViewStyle.cpp => GraphicsViewStyle.cpp} | 80 +-- src/Node.cpp | 251 ------- src/NodeConnectionInteraction.cpp | 253 +++---- src/NodeConnectionInteraction.hpp | 35 +- src/NodeDataModel.cpp | 11 +- src/NodeGeometry.cpp | 335 +++++---- src/NodeGraphicsObject.cpp | 319 +++++---- src/NodePainter.cpp | 395 +++++------ src/NodePainter.hpp | 67 +- src/NodeState.cpp | 148 ++-- src/NodeStyle.cpp | 88 ++- src/Properties.cpp | 12 - src/Properties.hpp | 47 -- src/StyleCollection.cpp | 14 +- src/locateNode.cpp | 48 ++ 114 files changed, 5113 insertions(+), 4452 deletions(-) delete mode 100644 examples/calculator/Converters.cpp delete mode 100644 examples/calculator/Converters.hpp delete mode 100644 examples/calculator/IntegerData.hpp delete mode 100644 examples/calculator/ModuloModel.cpp delete mode 100644 examples/calculator/ModuloModel.hpp delete mode 100644 examples/example2/CMakeLists.txt create mode 100644 examples/graph/CMakeLists.txt create mode 100644 examples/graph/CustomGraphModel.hpp create mode 100644 examples/graph/main.cpp create mode 100644 examples/text/CMakeLists.txt rename examples/{example2 => text}/TextData.hpp (100%) rename examples/{example2 => text}/TextDisplayDataModel.cpp (100%) rename examples/{example2 => text}/TextDisplayDataModel.hpp (91%) rename examples/{example2 => text}/TextSourceDataModel.cpp (85%) rename examples/{example2 => text}/TextSourceDataModel.hpp (89%) rename examples/{example2 => text}/main.cpp (58%) create mode 100644 include/nodes/BasicGraphicsScene create mode 100644 include/nodes/ConnectionIdUtils rename include/nodes/{Connection => Connection_} (100%) create mode 100644 include/nodes/DataFlowGraphModel create mode 100644 include/nodes/DataFlowGraphicsScene create mode 100644 include/nodes/Definitions delete mode 100644 include/nodes/FlowScene delete mode 100644 include/nodes/FlowView delete mode 100644 include/nodes/FlowViewStyle create mode 100644 include/nodes/GraphModel create mode 100644 include/nodes/GraphicsView create mode 100644 include/nodes/GraphicsViewStyle delete mode 100644 include/nodes/Node delete mode 100644 include/nodes/TypeConverter create mode 100644 include/nodes/internal/BasicGraphicsScene.hpp delete mode 100644 include/nodes/internal/Connection.hpp delete mode 100644 include/nodes/internal/ConnectionGeometry.hpp create mode 100644 include/nodes/internal/ConnectionIdHash.hpp create mode 100644 include/nodes/internal/ConnectionIdUtils.hpp create mode 100644 include/nodes/internal/DataFlowGraphModel.hpp create mode 100644 include/nodes/internal/DataFlowGraphicsScene.hpp create mode 100644 include/nodes/internal/Definitions.hpp delete mode 100644 include/nodes/internal/FlowScene.hpp delete mode 100644 include/nodes/internal/FlowView.hpp create mode 100644 include/nodes/internal/GraphModel.hpp create mode 100644 include/nodes/internal/GraphicsView.hpp rename include/nodes/internal/{FlowViewStyle.hpp => GraphicsViewStyle.hpp} (50%) delete mode 100644 include/nodes/internal/Node.hpp delete mode 100644 include/nodes/internal/TypeConverter.hpp create mode 100644 include/nodes/internal/locateNode.hpp delete mode 100644 include/nodes/internal/memory.hpp create mode 100644 src/BasicGraphicsScene.cpp delete mode 100644 src/Connection.cpp delete mode 100644 src/ConnectionGeometry.cpp create mode 100644 src/DataFlowGraphModel.cpp create mode 100644 src/DataFlowGraphicsScene.cpp delete mode 100644 src/FlowScene.cpp create mode 100644 src/GraphModel.cpp rename src/{FlowView.cpp => GraphicsView.cpp} (55%) rename src/{FlowViewStyle.cpp => GraphicsViewStyle.cpp} (59%) delete mode 100644 src/Node.cpp delete mode 100644 src/Properties.cpp delete mode 100644 src/Properties.hpp create mode 100644 src/locateNode.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ed54c86a7..92382e0bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ project(NodeEditor CXX) set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) set(CMAKE_DISABLE_SOURCE_CHANGES ON) set(OpenGL_GL_PREFERENCE LEGACY) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) get_directory_property(_has_parent PARENT_DIRECTORY) if(_has_parent) @@ -72,18 +73,18 @@ endif() set(CMAKE_AUTOMOC ON) set(CPP_SOURCE_FILES - src/Connection.cpp + src/BasicGraphicsScene.cpp src/ConnectionBlurEffect.cpp - src/ConnectionGeometry.cpp src/ConnectionGraphicsObject.cpp src/ConnectionPainter.cpp src/ConnectionState.cpp src/ConnectionStyle.cpp + src/DataFlowGraphModel.cpp + src/DataFlowGraphicsScene.cpp src/DataModelRegistry.cpp - src/FlowScene.cpp - src/FlowView.cpp - src/FlowViewStyle.cpp - src/Node.cpp + src/GraphModel.cpp + src/GraphicsView.cpp + src/GraphicsViewStyle.cpp src/NodeConnectionInteraction.cpp src/NodeDataModel.cpp src/NodeGeometry.cpp @@ -91,24 +92,27 @@ set(CPP_SOURCE_FILES src/NodePainter.cpp src/NodeState.cpp src/NodeStyle.cpp - src/Properties.cpp src/StyleCollection.cpp + src/locateNode.cpp ) set(HPP_HEADER_FILES + include/nodes/internal/BasicGraphicsScene.hpp include/nodes/internal/Compiler.hpp - include/nodes/internal/Connection.hpp - include/nodes/internal/ConnectionGeometry.hpp include/nodes/internal/ConnectionGraphicsObject.hpp + include/nodes/internal/ConnectionIdHash.hpp + include/nodes/internal/ConnectionIdUtils.hpp include/nodes/internal/ConnectionState.hpp include/nodes/internal/ConnectionStyle.hpp + include/nodes/internal/DataFlowGraphicsScene.hpp + include/nodes/internal/DataFlowGraphModel.hpp include/nodes/internal/DataModelRegistry.hpp + include/nodes/internal/Definitions.hpp include/nodes/internal/Export.hpp - include/nodes/internal/FlowScene.hpp - include/nodes/internal/FlowView.hpp - include/nodes/internal/FlowViewStyle.hpp - include/nodes/internal/memory.hpp - include/nodes/internal/Node.hpp + include/nodes/internal/GraphicsView.hpp + include/nodes/internal/GraphicsViewStyle.hpp + include/nodes/internal/GraphModel.hpp + include/nodes/internal/locateNode.hpp include/nodes/internal/NodeData.hpp include/nodes/internal/NodeDataModel.hpp include/nodes/internal/NodeGeometry.hpp @@ -123,7 +127,9 @@ set(HPP_HEADER_FILES include/nodes/internal/Serializable.hpp include/nodes/internal/Style.hpp include/nodes/internal/StyleCollection.hpp - include/nodes/internal/TypeConverter.hpp + src/ConnectionPainter.hpp + src/NodeConnectionInteraction.hpp + src/NodePainter.hpp ) # If we want to give the option to build a static library, @@ -133,8 +139,10 @@ add_library(nodes ${HPP_HEADER_FILES} ${RESOURCES} ) + add_library(NodeEditor::nodes ALIAS nodes) + target_include_directories(nodes PUBLIC $ @@ -221,7 +229,7 @@ endif() ## if(BUILD_TESTING) - add_subdirectory(test) + #add_subdirectory(test) endif() ############### diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 78178808e..d62e5e4a8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,9 +1,12 @@ -add_subdirectory(connection_colors) - -add_subdirectory(example2) +add_subdirectory(graph) add_subdirectory(calculator) +add_subdirectory(text) + add_subdirectory(images) add_subdirectory(styles) + +add_subdirectory(connection_colors) + diff --git a/examples/calculator/AdditionModel.hpp b/examples/calculator/AdditionModel.hpp index 672c468ba..719733f3e 100644 --- a/examples/calculator/AdditionModel.hpp +++ b/examples/calculator/AdditionModel.hpp @@ -15,8 +15,7 @@ class AdditionModel : public MathOperationDataModel { public: - virtual - ~AdditionModel() {} + ~AdditionModel() = default; public: @@ -40,18 +39,16 @@ class AdditionModel : public MathOperationDataModel if (n1 && n2) { - modelValidationState = NodeValidationState::Valid; - modelValidationError = QString(); _result = std::make_shared(n1->number() + n2->number()); } else { - modelValidationState = NodeValidationState::Warning; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); _result.reset(); } Q_EMIT dataUpdated(outPortIndex); } + + }; diff --git a/examples/calculator/Converters.cpp b/examples/calculator/Converters.cpp deleted file mode 100644 index 092e1cd67..000000000 --- a/examples/calculator/Converters.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "Converters.hpp" - -#include - -#include "DecimalData.hpp" -#include "IntegerData.hpp" - - -std::shared_ptr -DecimalToIntegerConverter:: -operator()(std::shared_ptr data) -{ - auto numberData = - std::dynamic_pointer_cast(data); - - if (numberData) - { - _integer = std::make_shared(numberData->number()); - } - else - { - _integer.reset(); - } - - return _integer; -} - - -std::shared_ptr -IntegerToDecimalConverter:: -operator()(std::shared_ptr data) -{ - auto numberData = - std::dynamic_pointer_cast(data); - - if (numberData) - { - _decimal = std::make_shared(numberData->number()); - } - else - { - _decimal.reset(); - } - - return _decimal; -} diff --git a/examples/calculator/Converters.hpp b/examples/calculator/Converters.hpp deleted file mode 100644 index ab6c0318b..000000000 --- a/examples/calculator/Converters.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include "DecimalData.hpp" -#include "IntegerData.hpp" - -using QtNodes::PortType; -using QtNodes::PortIndex; -using QtNodes::NodeData; -using QtNodes::NodeDataType; -using QtNodes::NodeDataModel; - -class DecimalData; -class IntegerData; - - -class DecimalToIntegerConverter -{ - -public: - - std::shared_ptr - operator()(std::shared_ptr data); - -private: - - std::shared_ptr _integer; -}; - - -class IntegerToDecimalConverter -{ - -public: - - std::shared_ptr - operator()(std::shared_ptr data); - -private: - - std::shared_ptr _decimal; -}; diff --git a/examples/calculator/DivisionModel.hpp b/examples/calculator/DivisionModel.hpp index cfbc6da68..5654f4ab7 100644 --- a/examples/calculator/DivisionModel.hpp +++ b/examples/calculator/DivisionModel.hpp @@ -68,24 +68,25 @@ class DivisionModel : public MathOperationDataModel if (n2 && (n2->number() == 0.0)) { - modelValidationState = NodeValidationState::Error; - modelValidationError = QStringLiteral("Division by zero error"); + //modelValidationState = NodeValidationState::Error; + //modelValidationError = QStringLiteral("Division by zero error"); _result.reset(); } else if (n1 && n2) { - modelValidationState = NodeValidationState::Valid; - modelValidationError = QString(); + //modelValidationState = NodeValidationState::Valid; + //modelValidationError = QString(); _result = std::make_shared(n1->number() / n2->number()); } else { - modelValidationState = NodeValidationState::Warning; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); + //modelValidationState = NodeValidationState::Warning; + //modelValidationError = QStringLiteral("Missing or incorrect inputs"); _result.reset(); } Q_EMIT dataUpdated(outPortIndex); } + }; diff --git a/examples/calculator/IntegerData.hpp b/examples/calculator/IntegerData.hpp deleted file mode 100644 index d66e742bf..000000000 --- a/examples/calculator/IntegerData.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include - -using QtNodes::NodeDataType; -using QtNodes::NodeData; - -/// The class can potentially incapsulate any user data which -/// need to be transferred within the Node Editor graph -class IntegerData : public NodeData -{ -public: - - IntegerData() - : _number(0.0) - {} - - IntegerData(int const number) - : _number(number) - {} - - NodeDataType type() const override - { - return NodeDataType {"integer", - "Integer"}; - } - - int number() const - { return _number; } - - QString numberAsText() const - { return QString::number(_number); } - -private: - - int _number; -}; diff --git a/examples/calculator/MathOperationDataModel.cpp b/examples/calculator/MathOperationDataModel.cpp index 3cfee4413..abfa4f6e9 100644 --- a/examples/calculator/MathOperationDataModel.cpp +++ b/examples/calculator/MathOperationDataModel.cpp @@ -40,6 +40,11 @@ setInData(std::shared_ptr data, PortIndex portIndex) auto numberData = std::dynamic_pointer_cast(data); + if (!data) + { + Q_EMIT dataInvalidated(0); + } + if (portIndex == 0) { _number1 = numberData; @@ -52,18 +57,3 @@ setInData(std::shared_ptr data, PortIndex portIndex) compute(); } - -NodeValidationState -MathOperationDataModel:: -validationState() const -{ - return modelValidationState; -} - - -QString -MathOperationDataModel:: -validationMessage() const -{ - return modelValidationError; -} diff --git a/examples/calculator/MathOperationDataModel.hpp b/examples/calculator/MathOperationDataModel.hpp index 34cc724f9..6023f1333 100644 --- a/examples/calculator/MathOperationDataModel.hpp +++ b/examples/calculator/MathOperationDataModel.hpp @@ -10,12 +10,11 @@ class DecimalData; -using QtNodes::PortType; -using QtNodes::PortIndex; using QtNodes::NodeData; -using QtNodes::NodeDataType; using QtNodes::NodeDataModel; -using QtNodes::NodeValidationState; +using QtNodes::NodeDataType; +using QtNodes::PortIndex; +using QtNodes::PortType; /// The model dictates the number of inputs and outputs for the Node. /// In this example it has no logic. @@ -25,8 +24,7 @@ class MathOperationDataModel : public NodeDataModel public: - virtual - ~MathOperationDataModel() {} + ~MathOperationDataModel() = default; public: @@ -46,12 +44,6 @@ class MathOperationDataModel : public NodeDataModel QWidget * embeddedWidget() override { return nullptr; } - NodeValidationState - validationState() const override; - - QString - validationMessage() const override; - protected: virtual void @@ -63,7 +55,4 @@ class MathOperationDataModel : public NodeDataModel std::weak_ptr _number2; std::shared_ptr _result; - - NodeValidationState modelValidationState = NodeValidationState::Warning; - QString modelValidationError = QString("Missing or incorrect inputs"); }; diff --git a/examples/calculator/ModuloModel.cpp b/examples/calculator/ModuloModel.cpp deleted file mode 100644 index 4639ec145..000000000 --- a/examples/calculator/ModuloModel.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "ModuloModel.hpp" - -#include - -#include "IntegerData.hpp" - -QJsonObject -ModuloModel:: -save() const -{ - QJsonObject modelJson; - - modelJson["name"] = name(); - - return modelJson; -} - - -unsigned int -ModuloModel:: -nPorts(PortType portType) const -{ - unsigned int result = 1; - - switch (portType) - { - case PortType::In: - result = 2; - break; - - case PortType::Out: - result = 1; - - default: - break; - } - - return result; -} - - -NodeDataType -ModuloModel:: -dataType(PortType, PortIndex) const -{ - return IntegerData().type(); -} - - -std::shared_ptr -ModuloModel:: -outData(PortIndex) -{ - return _result; -} - - -void -ModuloModel:: -setInData(std::shared_ptr data, PortIndex portIndex) -{ - auto numberData = - std::dynamic_pointer_cast(data); - - if (portIndex == 0) - { - _number1 = numberData; - } - else - { - _number2 = numberData; - } - - { - PortIndex const outPortIndex = 0; - - auto n1 = _number1.lock(); - auto n2 = _number2.lock(); - - if (n2 && (n2->number() == 0.0)) - { - modelValidationState = NodeValidationState::Error; - modelValidationError = QStringLiteral("Division by zero error"); - _result.reset(); - } - else if (n1 && n2) - { - modelValidationState = NodeValidationState::Valid; - modelValidationError = QString(); - _result = std::make_shared(n1->number() % - n2->number()); - } - else - { - modelValidationState = NodeValidationState::Warning; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); - _result.reset(); - } - - Q_EMIT dataUpdated(outPortIndex); - } -} - - -NodeValidationState -ModuloModel:: -validationState() const -{ - return modelValidationState; -} - - -QString -ModuloModel:: -validationMessage() const -{ - return modelValidationError; -} diff --git a/examples/calculator/ModuloModel.hpp b/examples/calculator/ModuloModel.hpp deleted file mode 100644 index 416598a67..000000000 --- a/examples/calculator/ModuloModel.hpp +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include - -using QtNodes::PortType; -using QtNodes::PortIndex; -using QtNodes::NodeData; -using QtNodes::NodeDataType; -using QtNodes::NodeDataModel; -using QtNodes::NodeValidationState; - -class IntegerData; - -class ModuloModel - : public NodeDataModel -{ - Q_OBJECT - -public: - ModuloModel() = default; - - virtual - ~ModuloModel() = default; - -public: - - QString - caption() const override - { return QStringLiteral("Modulo"); } - - bool - captionVisible() const override - { return true; } - - bool - portCaptionVisible(PortType, PortIndex ) const override - { return true; } - - QString - portCaption(PortType portType, PortIndex portIndex) const override - { - switch (portType) - { - case PortType::In: - if (portIndex == 0) - return QStringLiteral("Dividend"); - else if (portIndex == 1) - return QStringLiteral("Divisor"); - - break; - - case PortType::Out: - return QStringLiteral("Result"); - - default: - break; - } - return QString(); - } - - QString - name() const override - { return QStringLiteral("Modulo"); } - -public: - - QJsonObject - save() const override; - -public: - - unsigned int - nPorts(PortType portType) const override; - - NodeDataType - dataType(PortType portType, PortIndex portIndex) const override; - - std::shared_ptr - outData(PortIndex port) override; - - void - setInData(std::shared_ptr, int) override; - - QWidget * - embeddedWidget() override { return nullptr; } - - NodeValidationState - validationState() const override; - - QString - validationMessage() const override; - -private: - - std::weak_ptr _number1; - std::weak_ptr _number2; - - std::shared_ptr _result; - - NodeValidationState modelValidationState = NodeValidationState::Warning; - QString modelValidationError = QStringLiteral("Missing or incorrect inputs"); -}; diff --git a/examples/calculator/MultiplicationModel.hpp b/examples/calculator/MultiplicationModel.hpp index e998e734a..4ed8621d9 100644 --- a/examples/calculator/MultiplicationModel.hpp +++ b/examples/calculator/MultiplicationModel.hpp @@ -40,15 +40,15 @@ class MultiplicationModel : public MathOperationDataModel if (n1 && n2) { - modelValidationState = NodeValidationState::Valid; - modelValidationError = QString(); + //modelValidationState = NodeValidationState::Valid; + //modelValidationError = QString(); _result = std::make_shared(n1->number() * n2->number()); } else { - modelValidationState = NodeValidationState::Warning; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); + //modelValidationState = NodeValidationState::Warning; + //modelValidationError = QStringLiteral("Missing or incorrect inputs"); _result.reset(); } diff --git a/examples/calculator/NumberDisplayDataModel.cpp b/examples/calculator/NumberDisplayDataModel.cpp index 77cc7655e..2dc524216 100644 --- a/examples/calculator/NumberDisplayDataModel.cpp +++ b/examples/calculator/NumberDisplayDataModel.cpp @@ -53,26 +53,23 @@ outData(PortIndex) void NumberDisplayDataModel:: -setInData(std::shared_ptr data, int) +setInData(std::shared_ptr data, PortIndex portIndex) { auto numberData = std::dynamic_pointer_cast(data); if (numberData) { - modelValidationState = NodeValidationState::Valid; - modelValidationError = QString(); _label->setText(numberData->numberAsText()); } else { - modelValidationState = NodeValidationState::Warning; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); _label->clear(); } _label->adjustSize(); } + QWidget* NumberDisplayDataModel:: embeddedWidget() @@ -85,18 +82,3 @@ embeddedWidget() return _label; } - -NodeValidationState -NumberDisplayDataModel:: -validationState() const -{ - return modelValidationState; -} - - -QString -NumberDisplayDataModel:: -validationMessage() const -{ - return modelValidationError; -} diff --git a/examples/calculator/NumberDisplayDataModel.hpp b/examples/calculator/NumberDisplayDataModel.hpp index 7b2934de1..348673153 100644 --- a/examples/calculator/NumberDisplayDataModel.hpp +++ b/examples/calculator/NumberDisplayDataModel.hpp @@ -11,7 +11,6 @@ using QtNodes::PortIndex; using QtNodes::NodeData; using QtNodes::NodeDataType; using QtNodes::NodeDataModel; -using QtNodes::NodeValidationState; class QLabel; @@ -24,8 +23,7 @@ class NumberDisplayDataModel : public NodeDataModel public: NumberDisplayDataModel(); - virtual - ~NumberDisplayDataModel() {} + ~NumberDisplayDataModel() = default; public: @@ -47,28 +45,18 @@ class NumberDisplayDataModel : public NodeDataModel nPorts(PortType portType) const override; NodeDataType - dataType(PortType portType, + dataType(PortType portType, PortIndex portIndex) const override; std::shared_ptr outData(PortIndex port) override; void - setInData(std::shared_ptr data, int) override; + setInData(std::shared_ptr data, + PortIndex portIndex) override; QWidget * embeddedWidget() override; - NodeValidationState - validationState() const override; - - QString - validationMessage() const override; - -private: - - NodeValidationState modelValidationState = NodeValidationState::Warning; - QString modelValidationError = QStringLiteral("Missing or incorrect inputs"); - QLabel * _label; }; diff --git a/examples/calculator/NumberSourceDataModel.cpp b/examples/calculator/NumberSourceDataModel.cpp index 7b63fb67e..bc32b9aa1 100644 --- a/examples/calculator/NumberSourceDataModel.cpp +++ b/examples/calculator/NumberSourceDataModel.cpp @@ -36,7 +36,7 @@ restore(QJsonObject const &p) { QString strNum = v.toString(); - bool ok; + bool ok; double d = strNum.toDouble(&ok); if (ok) { diff --git a/examples/calculator/NumberSourceDataModel.hpp b/examples/calculator/NumberSourceDataModel.hpp index f3b3aabb7..d388abde4 100644 --- a/examples/calculator/NumberSourceDataModel.hpp +++ b/examples/calculator/NumberSourceDataModel.hpp @@ -13,7 +13,6 @@ using QtNodes::PortIndex; using QtNodes::NodeData; using QtNodes::NodeDataType; using QtNodes::NodeDataModel; -using QtNodes::NodeValidationState; class QLineEdit; @@ -64,8 +63,8 @@ class NumberSourceDataModel outData(PortIndex port) override; void - setInData(std::shared_ptr, int) override - { } + setInData(std::shared_ptr, PortIndex) override + {} QWidget * embeddedWidget() override; diff --git a/examples/calculator/SubtractionModel.hpp b/examples/calculator/SubtractionModel.hpp index fd071af2a..7aae8b97b 100644 --- a/examples/calculator/SubtractionModel.hpp +++ b/examples/calculator/SubtractionModel.hpp @@ -69,15 +69,15 @@ class SubtractionModel : public MathOperationDataModel if (n1 && n2) { - modelValidationState = NodeValidationState::Valid; - modelValidationError = QString(); + //modelValidationState = NodeValidationState::Valid; + //modelValidationError = QString(); _result = std::make_shared(n1->number() - n2->number()); } else { - modelValidationState = NodeValidationState::Warning; - modelValidationError = QStringLiteral("Missing or incorrect inputs"); + //modelValidationState = NodeValidationState::Warning; + //modelValidationError = QStringLiteral("Missing or incorrect inputs"); _result.reset(); } diff --git a/examples/calculator/main.cpp b/examples/calculator/main.cpp index 28708cd5e..bd5420c2d 100644 --- a/examples/calculator/main.cpp +++ b/examples/calculator/main.cpp @@ -1,31 +1,28 @@ -#include -#include -#include #include -#include +#include +#include +#include +#include +#include #include -#include #include +#include +#include -#include - -#include "NumberSourceDataModel.hpp" -#include "NumberDisplayDataModel.hpp" #include "AdditionModel.hpp" -#include "SubtractionModel.hpp" -#include "MultiplicationModel.hpp" #include "DivisionModel.hpp" -#include "ModuloModel.hpp" -#include "Converters.hpp" - +#include "MultiplicationModel.hpp" +#include "NumberDisplayDataModel.hpp" +#include "NumberSourceDataModel.hpp" +#include "SubtractionModel.hpp" -using QtNodes::DataModelRegistry; -using QtNodes::FlowScene; -using QtNodes::FlowView; using QtNodes::ConnectionStyle; -using QtNodes::TypeConverter; -using QtNodes::TypeConverterId; +using QtNodes::DataFlowGraphModel; +using QtNodes::DataFlowGraphicsScene; +using QtNodes::DataModelRegistry; +using QtNodes::GraphicsView; + static std::shared_ptr registerDataModels() @@ -43,18 +40,6 @@ registerDataModels() ret->registerModel("Operators"); - ret->registerModel("Operators"); - - ret->registerTypeConverter(std::make_pair(DecimalData().type(), - IntegerData().type()), - TypeConverter{DecimalToIntegerConverter()}); - - - - ret->registerTypeConverter(std::make_pair(IntegerData().type(), - DecimalData().type()), - TypeConverter{IntegerToDecimalConverter()}); - return ret; } @@ -64,7 +49,7 @@ void setStyle() { ConnectionStyle::setConnectionStyle( - R"( + R"( { "ConnectionStyle": { "ConstructionColor": "gray", @@ -91,6 +76,8 @@ main(int argc, char *argv[]) setStyle(); + std::shared_ptr registry = registerDataModels(); + QWidget mainWidget; auto menuBar = new QMenuBar(); @@ -99,21 +86,27 @@ main(int argc, char *argv[]) QVBoxLayout *l = new QVBoxLayout(&mainWidget); + DataFlowGraphModel dataFlowGraphModel(registry); + l->addWidget(menuBar); - auto scene = new FlowScene(registerDataModels(), &mainWidget); - l->addWidget(new FlowView(scene)); + auto scene = new DataFlowGraphicsScene(dataFlowGraphModel, + &mainWidget); + l->addWidget(new GraphicsView(scene)); l->setContentsMargins(0, 0, 0, 0); l->setSpacing(0); QObject::connect(saveAction, &QAction::triggered, - scene, &FlowScene::save); + scene, &DataFlowGraphicsScene::save); QObject::connect(loadAction, &QAction::triggered, - scene, &FlowScene::load); + scene, &DataFlowGraphicsScene::load); - mainWidget.setWindowTitle("Dataflow tools: simplest calculator"); + mainWidget.setWindowTitle("Data Flow: simplest calculator"); mainWidget.resize(800, 600); + // Center window. + mainWidget.move(QApplication::primaryScreen()->availableGeometry().center() - mainWidget.rect().center()); mainWidget.showNormal(); return app.exec(); } + diff --git a/examples/connection_colors/main.cpp b/examples/connection_colors/main.cpp index a9aeb8260..6d9641b2b 100644 --- a/examples/connection_colors/main.cpp +++ b/examples/connection_colors/main.cpp @@ -1,16 +1,18 @@ #include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #include "models.hpp" using QtNodes::DataModelRegistry; -using QtNodes::FlowScene; -using QtNodes::FlowView; +using QtNodes::DataFlowGraphModel; +using QtNodes::DataFlowGraphicsScene; +using QtNodes::GraphicsView; using QtNodes::ConnectionStyle; static std::shared_ptr @@ -57,9 +59,12 @@ main(int argc, char* argv[]) setStyle(); - FlowScene scene(registerDataModels()); + std::shared_ptr registry = registerDataModels(); + DataFlowGraphModel dataFlowGraphModel(registry); + + DataFlowGraphicsScene scene(dataFlowGraphModel); - FlowView view(&scene); + GraphicsView view(&scene); view.setWindowTitle("Node-based flow editor"); view.resize(800, 600); diff --git a/examples/connection_colors/models.hpp b/examples/connection_colors/models.hpp index 8757a903a..7b9b71a77 100644 --- a/examples/connection_colors/models.hpp +++ b/examples/connection_colors/models.hpp @@ -67,7 +67,7 @@ class NaiveDataModel : public NodeDataModel public: unsigned int - nPorts(PortType portType) const override + nPorts(PortType const portType) const override { unsigned int result = 1; @@ -88,8 +88,8 @@ class NaiveDataModel : public NodeDataModel } NodeDataType - dataType(PortType portType, - PortIndex portIndex) const override + dataType(PortType const portType, + PortIndex const portIndex) const override { switch (portType) { @@ -121,7 +121,7 @@ class NaiveDataModel : public NodeDataModel } std::shared_ptr - outData(PortIndex port) override + outData(PortIndex const port) override { if (port < 1) return std::make_shared(); @@ -130,7 +130,7 @@ class NaiveDataModel : public NodeDataModel } void - setInData(std::shared_ptr, int) override + setInData(std::shared_ptr, PortIndex const) override { // } diff --git a/examples/example2/CMakeLists.txt b/examples/example2/CMakeLists.txt deleted file mode 100644 index 2c8a980ac..000000000 --- a/examples/example2/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -file(GLOB_RECURSE CPPS ./*.cpp ) -file(GLOB_RECURSE HPPS ./*.hpp ) - -add_executable(example2 ${CPPS} ${HPPS}) - -target_link_libraries(example2 nodes) diff --git a/examples/graph/CMakeLists.txt b/examples/graph/CMakeLists.txt new file mode 100644 index 000000000..ae870067c --- /dev/null +++ b/examples/graph/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB_RECURSE CPPS ./*.cpp ) + +add_executable(graph ${CPPS}) + +target_link_libraries(graph nodes) diff --git a/examples/graph/CustomGraphModel.hpp b/examples/graph/CustomGraphModel.hpp new file mode 100644 index 000000000..d5cdc6375 --- /dev/null +++ b/examples/graph/CustomGraphModel.hpp @@ -0,0 +1,317 @@ +#pragma once + +#include +#include +#include + + +using ConnectionId = QtNodes::ConnectionId; +using ConnectionPolicy = QtNodes::ConnectionPolicy; +using NodeFlag = QtNodes::NodeFlag; +using NodeFlags = QtNodes::NodeFlags; +using NodeId = QtNodes::NodeId; +using NodeRole = QtNodes::NodeRole; +using PortIndex = QtNodes::PortIndex; +using PortRole = QtNodes::PortRole; +using PortType = QtNodes::PortType; +using StyleCollection = QtNodes::StyleCollection; +using QtNodes::InvalidNodeId; + + +class CustomGraphModel : public QtNodes::GraphModel +{ +public: + + struct NodeGeometryData + { + QSize size; + QPointF pos; + }; + + +public: + + CustomGraphModel() + : _lastNodeId{0} + {} + + + std::unordered_set + allNodeIds() const override + { + return _nodeIds; + } + + std::unordered_set> + connectedNodes(NodeId nodeId, + PortType portType, + PortIndex portIndex) const override + { + return _connectivity[std::make_tuple(nodeId, + portType, + portIndex)]; + } + + NodeId + addNode(QString const nodeType = QString()) override + { + NodeId newId = _lastNodeId++; + // Create new node. + _nodeIds.insert(newId); + + Q_EMIT nodeCreated(newId); + + return newId; + } + + void + addConnection(ConnectionId const connectionId) override + { + auto connect = + [&](PortType portType) + { + auto key = std::make_tuple(getNodeId(portType, connectionId), + portType, + getPortIndex(portType, connectionId)); + + PortType opposite = oppositePort(portType); + + _connectivity[key].insert(std::make_pair(getNodeId(opposite, connectionId), + getPortIndex(opposite, connectionId))); + }; + + connect(PortType::Out); + connect(PortType::In); + + Q_EMIT connectionCreated(connectionId); + } + + QVariant + nodeData(NodeId nodeId, NodeRole role) const override + { + Q_UNUSED(nodeId); + + QVariant result; + + switch (role) + { + case NodeRole::Type: + result = QString("Default Node Type"); + break; + + case NodeRole::Position: + result = _nodeGeometryData[nodeId].pos; + break; + + case NodeRole::Size: + result = _nodeGeometryData[nodeId].size; + break; + + case NodeRole::CaptionVisible: + result = true; + break; + + case NodeRole::Caption: + result = QString("Node"); + break; + + case NodeRole::Style: + { + auto style = StyleCollection::nodeStyle(); + result = style.toJson().toVariant(); + } + break; + + case NodeRole::NumberOfInPorts: + result = 1u; + break; + + case NodeRole::NumberOfOutPorts: + result = 1u; + break; + + case NodeRole::Widget: + result = QVariant(); + break; + } + + return result; + } + + bool + setNodeData(NodeId nodeId, + NodeRole role, + QVariant value) override + { + bool result = false; + + switch (role) + { + case NodeRole::Type: + break; + case NodeRole::Position: + { + _nodeGeometryData[nodeId].pos = value.value(); + + Q_EMIT nodePositonUpdated(nodeId); + + result = true; + } + break; + + case NodeRole::Size: + { + + _nodeGeometryData[nodeId].size = value.value(); + result = true; + } + break; + + case NodeRole::CaptionVisible: + break; + + case NodeRole::Caption: + break; + + case NodeRole::Style: + break; + + case NodeRole::NumberOfInPorts: + break; + + case NodeRole::NumberOfOutPorts: + break; + + case NodeRole::Widget: + break; + } + + return result; + } + + QVariant + portData(NodeId nodeId, + PortType portType, + PortIndex portIndex, + PortRole role) const override + { + switch (role) + { + case PortRole::Data: + return QVariant(); + break; + + case PortRole::DataType: + return QVariant(); + break; + + case PortRole::ConnectionPolicy: + return QVariant::fromValue(ConnectionPolicy::One); + break; + + case PortRole::CaptionVisible: + return true; + break; + + case PortRole::Caption: + if (portType == PortType::In) + return QString::fromUtf8("Port In"); + else + return QString::fromUtf8("Port Out"); + + break; + } + + return QVariant(); + } + + bool + setPortData(NodeId nodeId, + PortType portType, + PortIndex portIndex, + PortRole role) const override + { + Q_UNUSED(nodeId); + Q_UNUSED(portType); + Q_UNUSED(portIndex); + Q_UNUSED(role); + + return false; + } + + bool + deleteConnection(ConnectionId const connectionId) override + { + bool disconnected = false; + + auto disconnect = + [&](PortType portType) + { + auto key = std::make_tuple(getNodeId(portType, connectionId), + portType, + getPortIndex(portType, connectionId)); + auto it = _connectivity.find(key); + + if (it != _connectivity.end()) + { + disconnected = true; + + PortType opposite = oppositePort(portType); + + auto oppositePair = + std::make_pair(getNodeId(opposite, connectionId), + getPortIndex(opposite, connectionId)); + it->second.erase(oppositePair); + + if (it->second.empty()) + { + _connectivity.erase(it); + } + } + + }; + + disconnect(PortType::Out); + disconnect(PortType::In); + + if (disconnected) + Q_EMIT connectionDeleted(connectionId); + + return disconnected; + } + + bool + deleteNode(NodeId const nodeId) override + { + // Delete connections to this node first. + auto connectionIds = allConnectionIds(nodeId); + for (auto & cId : connectionIds) + { + deleteConnection(cId); + } + + _nodeIds.erase(nodeId); + _nodeGeometryData.erase(nodeId); + + Q_EMIT nodeDeleted(nodeId); + + return true; + } + +private: + + std::unordered_set _nodeIds; + + mutable + std::unordered_map, + std::unordered_set>> + _connectivity; + + mutable std::unordered_map + _nodeGeometryData; + + + unsigned int _lastNodeId; + +}; diff --git a/examples/graph/main.cpp b/examples/graph/main.cpp new file mode 100644 index 000000000..cebe35279 --- /dev/null +++ b/examples/graph/main.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "CustomGraphModel.hpp" + + +using QtNodes::ConnectionStyle; +using QtNodes::GraphicsView; +using QtNodes::BasicGraphicsScene; +using QtNodes::NodeRole; +using QtNodes::StyleCollection; + +int +main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + CustomGraphModel graphModel; + + // Initialize two nodes. + { + NodeId id1 = graphModel.addNode(); + graphModel.setNodeData(id1, NodeRole::Position, QPointF(0, 0)); + + NodeId id2 = graphModel.addNode(); + graphModel.setNodeData(id2, NodeRole::Position, QPointF(300, 300)); + } + + auto scene = new BasicGraphicsScene(graphModel); + + GraphicsView view(scene); + + // Setup context menu for creating new nodes. + view.setContextMenuPolicy(Qt::ActionsContextMenu); + QAction createNodeAction(QStringLiteral("Create Node"), &view); + QObject::connect(&createNodeAction, &QAction::triggered, + [&]() + { + // Mouse position in scene coordinates. + QPointF posView = + view.mapToScene(view.mapFromGlobal(QCursor::pos())); + + + NodeId const newId = graphModel.addNode(); + graphModel.setNodeData(newId, + NodeRole::Position, + posView); + }); + view.insertAction(view.actions().front(), &createNodeAction); + + + view.setWindowTitle("Simple Node Graph"); + view.resize(800, 600); + + // Center window. + view.move(QApplication::primaryScreen()->availableGeometry().center() - view.rect().center()); + view.showNormal(); + + return app.exec(); +} + diff --git a/examples/images/ImageLoaderModel.cpp b/examples/images/ImageLoaderModel.cpp index 4f90fb2cd..9266389d7 100644 --- a/examples/images/ImageLoaderModel.cpp +++ b/examples/images/ImageLoaderModel.cpp @@ -85,7 +85,7 @@ eventFilter(QObject *object, QEvent *event) NodeDataType ImageLoaderModel:: -dataType(PortType, PortIndex) const +dataType(PortType const, PortIndex const) const { return PixmapData().type(); } diff --git a/examples/images/ImageLoaderModel.hpp b/examples/images/ImageLoaderModel.hpp index 3e51e5c92..87e109248 100644 --- a/examples/images/ImageLoaderModel.hpp +++ b/examples/images/ImageLoaderModel.hpp @@ -15,7 +15,6 @@ using QtNodes::PortIndex; using QtNodes::NodeData; using QtNodes::NodeDataType; using QtNodes::NodeDataModel; -using QtNodes::NodeValidationState; /// The model dictates the number of inputs and outputs for the Node. /// In this example it has no logic. @@ -26,14 +25,12 @@ class ImageLoaderModel : public NodeDataModel public: ImageLoaderModel(); - virtual - ~ImageLoaderModel() {} + ~ImageLoaderModel() = default; public: QString - caption() const override - { return QString("Image Source"); } + caption() const override { return QString("Image Source"); } QString name() const override { return QString("ImageLoaderModel"); } @@ -41,21 +38,21 @@ class ImageLoaderModel : public NodeDataModel public: virtual QString - modelName() const - { return QString("Source Image"); } + modelName() const { return QString("Source Image"); } unsigned int - nPorts(PortType portType) const override; + nPorts(PortType const portType) const override; NodeDataType - dataType(PortType portType, PortIndex portIndex) const override; + dataType(PortType const portType, + PortIndex const portIndex) const override; std::shared_ptr - outData(PortIndex port) override; + outData(PortIndex const port) override; void - setInData(std::shared_ptr, int) override - { } + setInData(std::shared_ptr, + PortIndex const portIndex) override {} QWidget * embeddedWidget() override { return _label; } diff --git a/examples/images/ImageShowModel.cpp b/examples/images/ImageShowModel.cpp index 6c01b2f70..0500583d0 100644 --- a/examples/images/ImageShowModel.cpp +++ b/examples/images/ImageShowModel.cpp @@ -26,6 +26,7 @@ ImageShowModel() _label->installEventFilter(this); } + unsigned int ImageShowModel:: nPorts(PortType portType) const @@ -74,7 +75,7 @@ eventFilter(QObject *object, QEvent *event) NodeDataType ImageShowModel:: -dataType(PortType, PortIndex) const +dataType(PortType const, PortIndex const) const { return PixmapData().type(); } @@ -90,7 +91,7 @@ outData(PortIndex) void ImageShowModel:: -setInData(std::shared_ptr nodeData, PortIndex) +setInData(std::shared_ptr nodeData, PortIndex const) { _nodeData = nodeData; @@ -110,3 +111,4 @@ setInData(std::shared_ptr nodeData, PortIndex) Q_EMIT dataUpdated(0); } + diff --git a/examples/images/ImageShowModel.hpp b/examples/images/ImageShowModel.hpp index fb1ace35b..af2fa8854 100644 --- a/examples/images/ImageShowModel.hpp +++ b/examples/images/ImageShowModel.hpp @@ -13,7 +13,6 @@ using QtNodes::PortIndex; using QtNodes::NodeData; using QtNodes::NodeDataType; using QtNodes::NodeDataModel; -using QtNodes::NodeValidationState; /// The model dictates the number of inputs and outputs for the Node. /// In this example it has no logic. @@ -24,8 +23,7 @@ class ImageShowModel : public NodeDataModel public: ImageShowModel(); - virtual - ~ImageShowModel() {} + ~ImageShowModel() = default; public: @@ -44,16 +42,18 @@ class ImageShowModel : public NodeDataModel { return QString("Resulting Image"); } unsigned int - nPorts(PortType portType) const override; + nPorts(PortType const portType) const override; NodeDataType - dataType(PortType portType, PortIndex portIndex) const override; + dataType(PortType const portType, + PortIndex const portIndex) const override; std::shared_ptr - outData(PortIndex port) override; + outData(PortIndex const port) override; void - setInData(std::shared_ptr nodeData, PortIndex port) override; + setInData(std::shared_ptr nodeData, + PortIndex const port) override; QWidget * embeddedWidget() override { return _label; } diff --git a/examples/images/main.cpp b/examples/images/main.cpp index 61ff948d5..04bb0794f 100644 --- a/examples/images/main.cpp +++ b/examples/images/main.cpp @@ -1,15 +1,21 @@ +#include +#include +#include +#include #include -#include -#include #include +#include #include "ImageShowModel.hpp" #include "ImageLoaderModel.hpp" +using QtNodes::ConnectionStyle; +using QtNodes::DataFlowGraphModel; +using QtNodes::DataFlowGraphicsScene; using QtNodes::DataModelRegistry; -using QtNodes::FlowScene; -using QtNodes::FlowView; +using QtNodes::GraphicsView; + static std::shared_ptr registerDataModels() @@ -28,13 +34,20 @@ main(int argc, char *argv[]) { QApplication app(argc, argv); - FlowScene scene(registerDataModels()); + std::shared_ptr registry = registerDataModels(); + + DataFlowGraphModel dataFlowGraphModel(registry); - FlowView view(&scene); + DataFlowGraphicsScene scene(dataFlowGraphModel); - view.setWindowTitle("Node-based flow editor"); + GraphicsView view(&scene); + + view.setWindowTitle("Data Flow: Resizable Images"); view.resize(800, 600); + // Center window. + view.move(QApplication::primaryScreen()->availableGeometry().center() - view.rect().center()); view.show(); return app.exec(); } + diff --git a/examples/styles/main.cpp b/examples/styles/main.cpp index 1b1e3c8d3..798920f7e 100644 --- a/examples/styles/main.cpp +++ b/examples/styles/main.cpp @@ -1,21 +1,23 @@ -#include - -#include -#include -#include +#include +#include +#include #include +#include +#include +#include #include -#include -#include + +#include #include "models.hpp" +using QtNodes::ConnectionStyle; +using QtNodes::DataFlowGraphModel; +using QtNodes::DataFlowGraphicsScene; using QtNodes::DataModelRegistry; -using QtNodes::FlowScene; -using QtNodes::FlowView; -using QtNodes::FlowViewStyle; +using QtNodes::GraphicsView; +using QtNodes::GraphicsViewStyle; using QtNodes::NodeStyle; -using QtNodes::ConnectionStyle; static std::shared_ptr registerDataModels() @@ -32,10 +34,10 @@ static void setStyle() { - FlowViewStyle::setStyle( - R"( + GraphicsViewStyle::setStyle( + R"( { - "FlowViewStyle": { + "GraphicsViewStyle": { "BackgroundColor": [255, 255, 240], "FineGridColor": [245, 245, 230], "CoarseGridColor": [235, 235, 220] @@ -44,7 +46,7 @@ setStyle() )"); NodeStyle::setNodeStyle( - R"( + R"( { "NodeStyle": { "NormalBoundaryColor": "darkgray", @@ -66,7 +68,7 @@ setStyle() )"); ConnectionStyle::setConnectionStyle( - R"( + R"( { "ConnectionStyle": { "ConstructionColor": "gray", @@ -86,8 +88,6 @@ setStyle() } -//------------------------------------------------------------------------------ - int main(int argc, char* argv[]) { @@ -95,9 +95,12 @@ main(int argc, char* argv[]) setStyle(); - FlowScene scene(registerDataModels()); + std::shared_ptr registry = registerDataModels(); + DataFlowGraphModel dataFlowGraphModel(registry); + + DataFlowGraphicsScene scene(dataFlowGraphModel); - FlowView view(&scene); + GraphicsView view(&scene); view.setWindowTitle("Style example"); view.resize(800, 600); @@ -105,3 +108,4 @@ main(int argc, char* argv[]) return app.exec(); } + diff --git a/examples/styles/models.hpp b/examples/styles/models.hpp index 18b158795..753eb5477 100644 --- a/examples/styles/models.hpp +++ b/examples/styles/models.hpp @@ -12,10 +12,9 @@ using QtNodes::PortIndex; using QtNodes::NodeData; using QtNodes::NodeDataType; using QtNodes::NodeDataModel; -using QtNodes::NodeValidationState; -/// The class can potentially incapsulate any user data which -/// need to be transferred within the Node Editor graph +/// The class can potentially incapsulate any user data which need to +/// be transferred within the Node Editor graph class MyNodeData : public NodeData { public: @@ -32,11 +31,9 @@ class MyNodeData : public NodeData class MyDataModel : public NodeDataModel { Q_OBJECT - public: - virtual - ~MyDataModel() {} + ~MyDataModel() = default; public: @@ -67,13 +64,13 @@ class MyDataModel : public NodeDataModel public: unsigned int - nPorts(PortType) const override + nPorts(PortType const) const override { return 3; } NodeDataType - dataType(PortType, PortIndex) const override + dataType(PortType const, PortIndex const) const override { return MyNodeData().type(); } @@ -85,10 +82,8 @@ class MyDataModel : public NodeDataModel } void - setInData(std::shared_ptr, int) override - { - // - } + setInData(std::shared_ptr, PortIndex const) override + {} QWidget * embeddedWidget() override { return nullptr; } diff --git a/examples/text/CMakeLists.txt b/examples/text/CMakeLists.txt new file mode 100644 index 000000000..369dccbd8 --- /dev/null +++ b/examples/text/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB_RECURSE CPPS ./*.cpp ) + +add_executable(text ${CPPS}) + +target_link_libraries(text nodes) diff --git a/examples/example2/TextData.hpp b/examples/text/TextData.hpp similarity index 100% rename from examples/example2/TextData.hpp rename to examples/text/TextData.hpp diff --git a/examples/example2/TextDisplayDataModel.cpp b/examples/text/TextDisplayDataModel.cpp similarity index 100% rename from examples/example2/TextDisplayDataModel.cpp rename to examples/text/TextDisplayDataModel.cpp diff --git a/examples/example2/TextDisplayDataModel.hpp b/examples/text/TextDisplayDataModel.hpp similarity index 91% rename from examples/example2/TextDisplayDataModel.hpp rename to examples/text/TextDisplayDataModel.hpp index 8b997f3ef..c8c18bec4 100644 --- a/examples/example2/TextDisplayDataModel.hpp +++ b/examples/text/TextDisplayDataModel.hpp @@ -6,14 +6,16 @@ #include "TextData.hpp" #include +#include #include #include -using QtNodes::PortType; -using QtNodes::PortIndex; +using QtNodes::ConnectionPolicy; using QtNodes::NodeData; using QtNodes::NodeDataModel; +using QtNodes::PortIndex; +using QtNodes::PortType; /// The model dictates the number of inputs and outputs for the Node. /// In this example it has no logic. @@ -53,7 +55,7 @@ class TextDisplayDataModel : public NodeDataModel dataType(PortType portType, PortIndex portIndex) const override; std::shared_ptr - outData(PortIndex port) override; + outData(PortIndex const port) override; ConnectionPolicy portInConnectionPolicy(PortIndex) const override @@ -62,12 +64,12 @@ class TextDisplayDataModel : public NodeDataModel } void - setInData(std::shared_ptr data, int) override + setInData(std::shared_ptr data, PortIndex const portIndex) override { } void - setInData(std::shared_ptr data, int, const QUuid& connectionId) override + setInData(std::shared_ptr data, int, const QUuid& connectionId) { auto textData = std::dynamic_pointer_cast(data); @@ -97,6 +99,7 @@ class TextDisplayDataModel : public NodeDataModel _label->adjustSize(); } + QWidget * embeddedWidget() override { return _label; } diff --git a/examples/example2/TextSourceDataModel.cpp b/examples/text/TextSourceDataModel.cpp similarity index 85% rename from examples/example2/TextSourceDataModel.cpp rename to examples/text/TextSourceDataModel.cpp index 2486ee780..c0969e98a 100644 --- a/examples/example2/TextSourceDataModel.cpp +++ b/examples/text/TextSourceDataModel.cpp @@ -3,7 +3,6 @@ TextSourceDataModel:: TextSourceDataModel() : _lineEdit(new QLineEdit("Default Text")), - _textData(std::make_shared()) { connect(_lineEdit, &QLineEdit::textEdited, this, &TextSourceDataModel::onTextEdited); @@ -53,8 +52,8 @@ dataType(PortType, PortIndex) const std::shared_ptr TextSourceDataModel:: -outData(PortIndex) +outData(PortIndex const portIndex) { - *_textData = TextData(_lineEdit->text()); - return _textData; + Q_UNUSED(portIndex); + return std::make_shared(_lineEdit->text()); } diff --git a/examples/example2/TextSourceDataModel.hpp b/examples/text/TextSourceDataModel.hpp similarity index 89% rename from examples/example2/TextSourceDataModel.hpp rename to examples/text/TextSourceDataModel.hpp index 8968801cd..d8991863a 100644 --- a/examples/example2/TextSourceDataModel.hpp +++ b/examples/text/TextSourceDataModel.hpp @@ -52,11 +52,10 @@ class TextSourceDataModel : public NodeDataModel dataType(PortType portType, PortIndex portIndex) const override; std::shared_ptr - outData(PortIndex port) override; + outData(PortIndex const portIndex) override; void - setInData(std::shared_ptr, int) override - { } + setInData(std::shared_ptr, PortIndex const) override { } QWidget * embeddedWidget() override { return _lineEdit; } @@ -67,7 +66,5 @@ private Q_SLOTS: onTextEdited(QString const &string); private: - QLineEdit * _lineEdit; - std::shared_ptr _textData; }; diff --git a/examples/example2/main.cpp b/examples/text/main.cpp similarity index 58% rename from examples/example2/main.cpp rename to examples/text/main.cpp index f3073f9bf..ac56f30e2 100644 --- a/examples/example2/main.cpp +++ b/examples/text/main.cpp @@ -1,6 +1,8 @@ +#include +#include +#include +#include #include -#include -#include #include @@ -9,9 +11,10 @@ #include "TextSourceDataModel.hpp" #include "TextDisplayDataModel.hpp" +using QtNodes::DataFlowGraphModel; +using QtNodes::DataFlowGraphicsScene; using QtNodes::DataModelRegistry; -using QtNodes::FlowView; -using QtNodes::FlowScene; +using QtNodes::GraphicsView; static std::shared_ptr registerDataModels() @@ -19,7 +22,6 @@ registerDataModels() auto ret = std::make_shared(); ret->registerModel(); - ret->registerModel(); return ret; @@ -31,9 +33,12 @@ main(int argc, char *argv[]) { QApplication app(argc, argv); - FlowScene scene(registerDataModels()); + std::shared_ptr registry = registerDataModels(); + DataFlowGraphModel dataFlowGraphModel(registry); + + DataFlowGraphicsScene scene(dataFlowGraphModel); - FlowView view(&scene); + GraphicsView view(&scene); view.setWindowTitle("Node-based flow editor"); view.resize(800, 600); diff --git a/include/nodes/BasicGraphicsScene b/include/nodes/BasicGraphicsScene new file mode 100644 index 000000000..7d17e8dd6 --- /dev/null +++ b/include/nodes/BasicGraphicsScene @@ -0,0 +1 @@ +#include "internal/BasicGraphicsScene.hpp" diff --git a/include/nodes/ConnectionIdUtils b/include/nodes/ConnectionIdUtils new file mode 100644 index 000000000..4c97c07f3 --- /dev/null +++ b/include/nodes/ConnectionIdUtils @@ -0,0 +1 @@ +#include "internal/ConnectionIdUtils.hpp" diff --git a/include/nodes/Connection b/include/nodes/Connection_ similarity index 100% rename from include/nodes/Connection rename to include/nodes/Connection_ diff --git a/include/nodes/DataFlowGraphModel b/include/nodes/DataFlowGraphModel new file mode 100644 index 000000000..28e8cbe30 --- /dev/null +++ b/include/nodes/DataFlowGraphModel @@ -0,0 +1 @@ +#include "internal/DataFlowGraphModel.hpp" diff --git a/include/nodes/DataFlowGraphicsScene b/include/nodes/DataFlowGraphicsScene new file mode 100644 index 000000000..3585527f3 --- /dev/null +++ b/include/nodes/DataFlowGraphicsScene @@ -0,0 +1 @@ +#include "internal/DataFlowGraphicsScene.hpp" diff --git a/include/nodes/Definitions b/include/nodes/Definitions new file mode 100644 index 000000000..2ac05a0d9 --- /dev/null +++ b/include/nodes/Definitions @@ -0,0 +1 @@ +#include "internal/Definitions.hpp" diff --git a/include/nodes/FlowScene b/include/nodes/FlowScene deleted file mode 100644 index b4e0745b6..000000000 --- a/include/nodes/FlowScene +++ /dev/null @@ -1 +0,0 @@ -#include "internal/FlowScene.hpp" diff --git a/include/nodes/FlowView b/include/nodes/FlowView deleted file mode 100644 index ca35f5763..000000000 --- a/include/nodes/FlowView +++ /dev/null @@ -1 +0,0 @@ -#include "internal/FlowView.hpp" diff --git a/include/nodes/FlowViewStyle b/include/nodes/FlowViewStyle deleted file mode 100644 index e66055b1b..000000000 --- a/include/nodes/FlowViewStyle +++ /dev/null @@ -1 +0,0 @@ -#include "internal/FlowViewStyle.hpp" diff --git a/include/nodes/GraphModel b/include/nodes/GraphModel new file mode 100644 index 000000000..ba4c7b751 --- /dev/null +++ b/include/nodes/GraphModel @@ -0,0 +1 @@ +#include "internal/GraphModel.hpp" diff --git a/include/nodes/GraphicsView b/include/nodes/GraphicsView new file mode 100644 index 000000000..da4e21a8b --- /dev/null +++ b/include/nodes/GraphicsView @@ -0,0 +1 @@ +#include "internal/GraphicsView.hpp" diff --git a/include/nodes/GraphicsViewStyle b/include/nodes/GraphicsViewStyle new file mode 100644 index 000000000..8a985415b --- /dev/null +++ b/include/nodes/GraphicsViewStyle @@ -0,0 +1 @@ +#include "internal/GraphicsViewStyle.hpp" diff --git a/include/nodes/Node b/include/nodes/Node deleted file mode 100644 index 9270781ad..000000000 --- a/include/nodes/Node +++ /dev/null @@ -1 +0,0 @@ -#include "internal/Node.hpp" diff --git a/include/nodes/TypeConverter b/include/nodes/TypeConverter deleted file mode 100644 index 9bcdbbd23..000000000 --- a/include/nodes/TypeConverter +++ /dev/null @@ -1 +0,0 @@ -#include "internal/TypeConverter.hpp" diff --git a/include/nodes/internal/BasicGraphicsScene.hpp b/include/nodes/internal/BasicGraphicsScene.hpp new file mode 100644 index 000000000..33ea11bed --- /dev/null +++ b/include/nodes/internal/BasicGraphicsScene.hpp @@ -0,0 +1,200 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +//#include "DataModelRegistry.hpp" +#include "Definitions.hpp" +#include "Export.hpp" +#include "GraphModel.hpp" +#include "ConnectionIdHash.hpp" +//#include "TypeConverter.hpp" + +#include "QUuidStdHash.hpp" + +namespace QtNodes +{ + +class ConnectionGraphicsObject; +class GraphModel; +class NodeGraphicsObject; +class NodeStyle; + +/// Scene holds connections and nodes. +class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene +{ + Q_OBJECT +public: + + BasicGraphicsScene(GraphModel & graphModel, + QObject * parent = nullptr); + + // Scenes without models are not supported + BasicGraphicsScene() = delete; + + ~BasicGraphicsScene(); + +public: + + GraphModel const & + graphModel() const; + + GraphModel & + graphModel(); + +public: + + //ConnectionGraphicsObject * + //draftConnection() const; + + /// Creates instance of "disconnected" ConnectionGraphicsObject. + /** + * We store a "draft" connection which has one loose end. + * After attachment the "draft" instance is removed and instead a + * normal "full" connection is created. + * Function @returns the "draft" instance for further geometry + * manipulations. + */ + std::unique_ptr const & + makeDraftConnection(ConnectionId const newConnectionId); + + void + resetDraftConnection(); + + void + clearScene(); + +public: + + NodeGraphicsObject * + nodeGraphicsObject(NodeId nodeId); + + ConnectionGraphicsObject * + connectionGraphicsObject(ConnectionId connectionId); + +public: + + virtual + QMenu * + createSceneMenu(QPointF const scenePos); + +Q_SIGNALS: + + void + connectionCreated(ConnectionId const connectionId); + + //void nodeMoved(Node& n, const QPointF& newLocation); + + void + nodeClicked(NodeId const nodeId); + + void + nodeDoubleClicked(NodeId const nodeId); + + void + connectionHovered(ConnectionId const connectionId, QPoint const screenPos); + + void + nodeHovered(NodeId const nodeId, QPoint const screenPos); + + void + connectionHoverLeft(ConnectionId const connectionId); + + void + nodeHoverLeft(NodeId const nodeId); + + void + nodeContextMenu(NodeId const nodeId, QPointF const pos); + +private: + + /// @brief Creates Node and Connection graphics objects. + /** We perform depth-first graph traversal. The connections are + * created by checking non-empyt node's Out ports. + */ + void + traverseGraphAndPopulateGraphicsObjects(); + + void + updateAttachedNodes(ConnectionId const connectionId, + PortType const portType); + +private Q_SLOTS: + + + /// Deletes the object from the main connection object set. + /** + * The function returns a unique pointer to the graphics object. If + * the pointer is not stored somewhere, the object is automatically + * destroyed and removed from the scene. + */ + void + onConnectionDeleted(ConnectionId const connectionId); + + void + onConnectionCreated(ConnectionId const connectionId); + + void + onNodeDeleted(NodeId const nodeId); + + void + onNodeCreated(NodeId const nodeId); + + void + onNodePositionUpdated(NodeId const nodeId); + + void + onPortsAboutToBeDeleted(NodeId const nodeId, + PortType const portType, + std::unordered_set const & portIndexSet); + + void + onPortsDeleted(NodeId const nodeId, + PortType const portType, + std::unordered_set const & portIndexSet); + + void + onPortsAboutToBeInserted(NodeId const nodeId, + PortType const portType, + std::unordered_set const & portIndexSet); + + void + onPortsInserted(NodeId const nodeId, + PortType const portType, + std::unordered_set const & portIndexSet); + + +private: + + // TODO shared pointer? + GraphModel & _graphModel; + + + using UniqueNodeGraphicsObject = + std::unique_ptr; + + using UniqueConnectionGraphicsObject = + std::unique_ptr; + + std::unordered_map + _nodeGraphicsObjects; + + std::unordered_map + _connectionGraphicsObjects; + + + //std::shared_ptr _registry; + + std::unique_ptr _draftConnection; + +}; + + +} diff --git a/include/nodes/internal/Connection.hpp b/include/nodes/internal/Connection.hpp deleted file mode 100644 index 5f79b38ea..000000000 --- a/include/nodes/internal/Connection.hpp +++ /dev/null @@ -1,166 +0,0 @@ -#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; - - 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; -}; -} diff --git a/include/nodes/internal/ConnectionGeometry.hpp b/include/nodes/internal/ConnectionGeometry.hpp deleted file mode 100644 index 870c3559d..000000000 --- a/include/nodes/internal/ConnectionGeometry.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#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; -}; -} diff --git a/include/nodes/internal/ConnectionGraphicsObject.hpp b/include/nodes/internal/ConnectionGraphicsObject.hpp index 7db498e48..af46fe890 100644 --- a/include/nodes/internal/ConnectionGraphicsObject.hpp +++ b/include/nodes/internal/ConnectionGraphicsObject.hpp @@ -1,41 +1,52 @@ #pragma once -#include +#include +#include #include +#include "Definitions.hpp" +#include "ConnectionState.hpp" + class QGraphicsSceneMouseEvent; namespace QtNodes { -class FlowScene; -class Connection; -class ConnectionGeometry; -class Node; +class BasicGraphicsScene; +class GraphModel; /// Graphic Object for connection. Adds itself to scene -class ConnectionGraphicsObject - : public QGraphicsObject +class ConnectionGraphicsObject : public QGraphicsObject { Q_OBJECT - public: - - ConnectionGraphicsObject(FlowScene &scene, - Connection &connection); - - virtual - ~ConnectionGraphicsObject(); - + // Needed for qgraphicsitem_cast enum { Type = UserType + 2 }; + int type() const override { return Type; } public: - Connection& - connection(); + ConnectionGraphicsObject(BasicGraphicsScene &scene, + ConnectionId const connectionId); + + ~ConnectionGraphicsObject() = default; + +public: + + void + initializePosition(); + + GraphModel & + graphModel() const; + + BasicGraphicsScene * + nodeScene() const; + + ConnectionId + connectionId() const; QRectF boundingRect() const override; @@ -43,6 +54,21 @@ class ConnectionGraphicsObject QPainterPath shape() const override; + QPointF const & + endPoint(PortType portType) const; + + QPointF + out() const { return _out; } + + QPointF + in() const { return _in; } + + std::pair + pointsC1C2() const; + + void + setEndPoint(PortType portType, QPointF const &point); + void setGeometryChanged(); @@ -50,30 +76,32 @@ class ConnectionGraphicsObject void move(); - void - lock(bool locked); + ConnectionState const & + connectionState() const; + ConnectionState & + connectionState(); protected: void - paint(QPainter* painter, - QStyleOptionGraphicsItem const* option, - QWidget* widget = 0) override; + paint(QPainter * painter, + QStyleOptionGraphicsItem const * option, + QWidget * widget = 0) override; void - mousePressEvent(QGraphicsSceneMouseEvent* event) override; + mousePressEvent(QGraphicsSceneMouseEvent * event) override; void - mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; + mouseMoveEvent(QGraphicsSceneMouseEvent * event) override; void - mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override; + mouseReleaseEvent(QGraphicsSceneMouseEvent * event) override; void - hoverEnterEvent(QGraphicsSceneHoverEvent* event) override; + hoverEnterEvent(QGraphicsSceneHoverEvent * event) override; void - hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override; + hoverLeaveEvent(QGraphicsSceneHoverEvent * event) override; private: @@ -82,8 +110,14 @@ class ConnectionGraphicsObject private: - FlowScene & _scene; + ConnectionId _connectionId; - Connection& _connection; + GraphModel &_graphModel; + + ConnectionState _connectionState; + + QPointF _out; + QPointF _in; }; + } diff --git a/include/nodes/internal/ConnectionIdHash.hpp b/include/nodes/internal/ConnectionIdHash.hpp new file mode 100644 index 000000000..77ff0a721 --- /dev/null +++ b/include/nodes/internal/ConnectionIdHash.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include "Definitions.hpp" + +inline void hash_combine(std::size_t& seed) { Q_UNUSED(seed); } + +template +inline void +hash_combine(std::size_t& seed, const T& v, Rest... rest) +{ + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + hash_combine(seed, rest ...); +} + + +namespace std +{ +template<> +struct hash +{ + inline + std::size_t + operator()(QtNodes::ConnectionId const& id) const + { + std::size_t h = 0; + hash_combine(h, + std::get<0>(id), + std::get<1>(id), + std::get<2>(id), + std::get<3>(id)); + return h; + } + +}; + + +template<> +struct hash> +{ + inline + std::size_t + operator()(std::pair const & nodePort) const + { + std::size_t h = 0; + hash_combine(h, + nodePort.first, + nodePort.second); + return h; + } + +}; + +template<> +struct hash> +{ + using Key = std::tuple; + + inline + std::size_t + operator()(Key const &key) const + { + std::size_t h = 0; + hash_combine(h, + std::get<0>(key), + std::get<1>(key), + std::get<2>(key)); + return h; + } + +}; +} + diff --git a/include/nodes/internal/ConnectionIdUtils.hpp b/include/nodes/internal/ConnectionIdUtils.hpp new file mode 100644 index 000000000..13e1407b1 --- /dev/null +++ b/include/nodes/internal/ConnectionIdUtils.hpp @@ -0,0 +1,160 @@ +#pragma once + +#include + +#include "Definitions.hpp" + +namespace QtNodes +{ + +inline +PortIndex +getNodeId(PortType portType, ConnectionId connectionId) +{ + NodeId id = InvalidNodeId; + + if (portType == PortType::Out) + { + id = std::get<0>(connectionId); + } + else if (portType == PortType::In) + { + id = std::get<2>(connectionId); + } + + return id; +} + + +inline +PortIndex +getPortIndex(PortType portType, ConnectionId connectionId) +{ + PortIndex index = InvalidPortIndex; + + if (portType == PortType::Out) + { + index = std::get<1>(connectionId); + } + else if (portType == PortType::In) + { + index = std::get<3>(connectionId); + } + + return index; +} + + +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; + + case PortType::None: + result = PortType::None; + break; + + default: + break; + } + return result; +} + + +inline +bool +isPortIndexValid(PortIndex index) +{ + return index != InvalidPortIndex; +} + + +inline +bool +isPortTypeValid(PortType portType) +{ + return portType != PortType::None; +} + + +inline +ConnectionId +makeIncompleteConnectionId(PortType const connectedPort, + NodeId const connectedNodeId, + PortIndex const connectedPortIndex) +{ + return (connectedPort == PortType::In) ? + std::make_tuple(InvalidNodeId, InvalidPortIndex, + connectedNodeId, connectedPortIndex) : + std::make_tuple(connectedNodeId, connectedPortIndex, + InvalidNodeId, InvalidPortIndex); +} + + +inline +ConnectionId +makeIncompleteConnectionId(ConnectionId connectionId, + PortType const portToDisconnect) +{ + if (portToDisconnect == PortType::Out) + { + std::get<0>(connectionId) = InvalidNodeId; + std::get<1>(connectionId) = InvalidPortIndex; + } + else + { + std::get<2>(connectionId) = InvalidNodeId; + std::get<3>(connectionId) = InvalidPortIndex; + } + + return connectionId; +} + + +inline +ConnectionId +makeCompleteConnectionId(ConnectionId incompleteConnectionId, + NodeId const nodeId, + PortIndex const portIndex) +{ + if (std::get<0>(incompleteConnectionId) == InvalidNodeId) + { + std::get<0>(incompleteConnectionId) = nodeId; + std::get<1>(incompleteConnectionId) = portIndex; + } + else + { + std::get<2>(incompleteConnectionId) = nodeId; + std::get<3>(incompleteConnectionId) = portIndex; + } + + return incompleteConnectionId; +} + + +inline +std::ostream & +operator<<(std::ostream & ostr, ConnectionId const connectionId) +{ + ostr << "(" << std::get<0>(connectionId) + << ", " << (isPortIndexValid(std::get<1>(connectionId)) ? std::to_string(std::get<1>(connectionId)) : "INVALID") + << ", " << std::get<2>(connectionId) + << ", " << (isPortIndexValid(std::get<3>(connectionId)) ? std::to_string(std::get<3>(connectionId)) : "INVALID") + << ")" << std::endl; + + return ostr; +} + + +} diff --git a/include/nodes/internal/ConnectionState.hpp b/include/nodes/internal/ConnectionState.hpp index 9191267c5..d0baef4f9 100644 --- a/include/nodes/internal/ConnectionState.hpp +++ b/include/nodes/internal/ConnectionState.hpp @@ -2,51 +2,63 @@ #include -#include "PortType.hpp" +#include "Export.hpp" + +#include "Definitions.hpp" class QPointF; namespace QtNodes { -class Node; +class ConnectionGraphicsObject; /// Stores currently draggind end. /// Remembers last hovered Node. -class ConnectionState +class NODE_EDITOR_PUBLIC ConnectionState { public: - ConnectionState(PortType port = PortType::None) - : _requiredPort(port) + /// Defines whether we construct a new connection + /// or it is already binding two nodes. + enum LooseEnd + { + Pending = 0, + Connected = 1 + }; + +public: + + ConnectionState(ConnectionGraphicsObject & cgo) + : _cgo(cgo) + , _connectedState(LooseEnd::Pending) + , _hovered(false) {} - ConnectionState(const ConnectionState&) = delete; - ConnectionState operator=(const ConnectionState&) = delete; + ConnectionState(ConnectionState const&) = delete; + ConnectionState(ConnectionState &&) = delete; + + ConnectionState& operator=(ConnectionState const&) = delete; + ConnectionState& operator=(ConnectionState &&) = delete; ~ConnectionState(); public: - void setRequiredPort(PortType end) - { _requiredPort = end; } + PortType requiredPort() const; + bool requiresPort() const; - PortType requiredPort() const - { return _requiredPort; } - - bool requiresPort() const - { return _requiredPort != PortType::None; } - - void setNoRequiredPort() - { _requiredPort = PortType::None; } + bool hovered() const { return _hovered; } + void setHovered(bool hovered) { _hovered = hovered; } public: - void interactWithNode(Node* node); + /// Caches NodeId for further interaction. + void interactWithNode(NodeId const nodeId); - void setLastHoveredNode(Node* node); + void setLastHoveredNode(NodeId const nodeId); - Node* + NodeId lastHoveredNode() const { return _lastHoveredNode; } @@ -54,8 +66,13 @@ class ConnectionState private: - PortType _requiredPort; + ConnectionGraphicsObject & _cgo; + + LooseEnd _connectedState; + + bool _hovered; + + NodeId _lastHoveredNode{InvalidNodeId}; - Node* _lastHoveredNode{nullptr}; }; } diff --git a/include/nodes/internal/ConnectionStyle.hpp b/include/nodes/internal/ConnectionStyle.hpp index e37feb037..4329394ea 100644 --- a/include/nodes/internal/ConnectionStyle.hpp +++ b/include/nodes/internal/ConnectionStyle.hpp @@ -16,17 +16,17 @@ class NODE_EDITOR_PUBLIC ConnectionStyle : public Style ConnectionStyle(QString jsonText); + ~ConnectionStyle() = default; + public: static void setConnectionStyle(QString jsonText); -private: - - void loadJsonText(QString jsonText) override; +public: - void loadJsonFile(QString fileName) override; + void loadJson(QJsonDocument const & json) override; - void loadJsonFromByteArray(QByteArray const &byteArray) override; + QJsonDocument toJson() const override; public: diff --git a/include/nodes/internal/DataFlowGraphModel.hpp b/include/nodes/internal/DataFlowGraphModel.hpp new file mode 100644 index 000000000..64bf6d67b --- /dev/null +++ b/include/nodes/internal/DataFlowGraphModel.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include + +#include "ConnectionIdUtils.hpp" +#include "DataModelRegistry.hpp" +#include "Export.hpp" +#include "GraphModel.hpp" +#include "StyleCollection.hpp" + + +namespace QtNodes +{ + +class NODE_EDITOR_PUBLIC DataFlowGraphModel : public GraphModel +{ + Q_OBJECT + +public: + + struct NodeGeometryData + { + QSize size; + QPointF pos; + }; + +public: + + DataFlowGraphModel(std::shared_ptr registry); + + std::shared_ptr + dataModelRegistry() { return _registry; } + +public: + + std::unordered_set + allNodeIds() const override; + + std::unordered_set> + connectedNodes(NodeId nodeId, + PortType portType, + PortIndex portIndex) const override; + + NodeId + addNode(QString const nodeType) override; + + void + addConnection(ConnectionId const connectionId) override; + + QVariant + nodeData(NodeId nodeId, NodeRole role) const override; + + + NodeFlags + nodeFlags(NodeId nodeId) const override; + + bool + setNodeData(NodeId nodeId, + NodeRole role, + QVariant value) override; + + QVariant + portData(NodeId nodeId, + PortType portType, + PortIndex portIndex, + PortRole role) const override; + + bool + setPortData(NodeId nodeId, + PortType portType, + PortIndex portIndex, + PortRole role) const override; + + bool + deleteConnection(ConnectionId const connectionId) override; + + bool + deleteNode(NodeId const nodeId) override; + +private: + + NodeId + newNodeId() { return _nextNodeId++; } + +private Q_SLOTS: + + void + onNodeDataUpdated(NodeId const nodeId, + PortIndex const portIndex); + + + void + propagateEmptyDataTo(NodeId const nodeId, + PortIndex const portIndex); + +private: + + std::shared_ptr _registry; + + NodeId _nextNodeId; + + std::unordered_map> + _models; + + mutable + std::unordered_map, + std::unordered_set>> + _connectivity; + + mutable std::unordered_map + _nodeGeometryData; +}; + + +} diff --git a/include/nodes/internal/DataFlowGraphicsScene.hpp b/include/nodes/internal/DataFlowGraphicsScene.hpp new file mode 100644 index 000000000..d4b606e4e --- /dev/null +++ b/include/nodes/internal/DataFlowGraphicsScene.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "BasicGraphicsScene.hpp" +#include "DataFlowGraphModel.hpp" +#include "Export.hpp" + + +namespace QtNodes +{ + +class NODE_EDITOR_PUBLIC DataFlowGraphicsScene + : public BasicGraphicsScene +{ + Q_OBJECT +public: + + DataFlowGraphicsScene(DataFlowGraphModel &graphModel, + QObject * parent = nullptr); + + ~DataFlowGraphicsScene() = default; + +public: + + std::vector + selectedNodes() const; + +public: + + + QMenu * + createSceneMenu(QPointF const scenePos) override; + + +public Q_SLOTS: + + void + save() const; + + void + load(); + + void + onPortDataSet(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex); + + + //std::shared_ptr restoreConnection(QJsonObject const & connectionJson); + + //Node & restoreNode(QJsonObject const & nodeJson) + + + //void save() const; + + //void load(); + + //QByteArray saveToMemory() const; + + //void loadFromMemory(const QByteArray & data); + +private: + + DataFlowGraphModel &_graphModel; +}; + +} diff --git a/include/nodes/internal/DataModelRegistry.hpp b/include/nodes/internal/DataModelRegistry.hpp index 7638ef1cd..46eeee985 100644 --- a/include/nodes/internal/DataModelRegistry.hpp +++ b/include/nodes/internal/DataModelRegistry.hpp @@ -10,15 +10,15 @@ #include -#include "NodeDataModel.hpp" -#include "TypeConverter.hpp" #include "Export.hpp" +#include "NodeData.hpp" +#include "NodeDataModel.hpp" #include "QStringStdHash.hpp" -#include "memory.hpp" namespace QtNodes { + /// Class uses map for storing models (name, model) class NODE_EDITOR_PUBLIC DataModelRegistry { @@ -27,12 +27,11 @@ class NODE_EDITOR_PUBLIC DataModelRegistry using RegistryItemPtr = std::unique_ptr; using RegistryItemCreator = std::function; - using RegisteredModelCreatorsMap = std::unordered_map; + using RegisteredModelCreatorsMap = std::unordered_map; using RegisteredModelsCategoryMap = std::unordered_map; using CategoriesSet = std::set; - using RegisteredTypeConvertersMap = std::unordered_map< - TypeConverterId, TypeConverter, TypeConverterIdHash>; + //using RegisteredTypeConvertersMap = std::map; DataModelRegistry() = default; ~DataModelRegistry() = default; @@ -40,16 +39,20 @@ class NODE_EDITOR_PUBLIC DataModelRegistry DataModelRegistry(DataModelRegistry const &) = delete; DataModelRegistry(DataModelRegistry &&) = default; - DataModelRegistry&operator=(DataModelRegistry const &) = delete; - DataModelRegistry&operator=(DataModelRegistry &&) = default; + DataModelRegistry & + operator=(DataModelRegistry const &) = delete; + + DataModelRegistry & + operator=(DataModelRegistry &&) = default; public: template - void registerModel(RegistryItemCreator creator, - QString const &category = "Nodes") + void + registerModel(RegistryItemCreator creator, + QString const &category = "Nodes") { - const QString name = computeName(HasStaticMethodName{}, creator); + QString const name = computeName(HasStaticMethodName{}, creator); if (!_registeredItemCreators.count(name)) { _registeredItemCreators[name] = std::move(creator); @@ -59,48 +62,64 @@ class NODE_EDITOR_PUBLIC DataModelRegistry } template - void registerModel(QString const &category = "Nodes") + void + registerModel(QString const &category = "Nodes") { RegistryItemCreator creator = [](){ return std::make_unique(); }; registerModel(std::move(creator), category); } +#if 0 template - void registerModel(QString const &category, - RegistryItemCreator creator) + void + registerModel(RegistryItemCreator creator, + QString const &category = "Nodes") { registerModel(std::move(creator), category); } template - void registerModel(ModelCreator&& creator, QString const& category = "Nodes") + void + registerModel(ModelCreator&& creator, QString const& category = "Nodes") { using ModelType = compute_model_type_t; registerModel(std::forward(creator), category); } template - void registerModel(QString const& category, ModelCreator&& creator) + void + registerModel(QString const& category, ModelCreator&& creator) { registerModel(std::forward(creator), category); } - void registerTypeConverter(TypeConverterId const & id, - TypeConverter typeConverter) + void + registerTypeConverter(TypeConverterId const &id, + TypeConverter typeConverter) { _registeredTypeConverters[id] = std::move(typeConverter); } - std::unique_ptrcreate(QString const &modelName); +#endif - RegisteredModelCreatorsMap const ®isteredModelCreators() const; - RegisteredModelsCategoryMap const ®isteredModelsCategoryAssociation() const; + std::unique_ptr + create(QString const &modelName); - CategoriesSet const &categories() const; + RegisteredModelCreatorsMap const & + registeredModelCreators() const; - TypeConverter getTypeConverter(NodeDataType const & d1, - NodeDataType const & d2) const; + RegisteredModelsCategoryMap const & + registeredModelsCategoryAssociation() const; + + CategoriesSet const & + categories() const; + +#if 0 + TypeConverter + getTypeConverter(NodeDataType const &d1, + NodeDataType const &d2) const; +#endif private: @@ -110,27 +129,24 @@ class NODE_EDITOR_PUBLIC DataModelRegistry RegisteredModelCreatorsMap _registeredItemCreators; +#if 0 RegisteredTypeConvertersMap _registeredTypeConverters; +#endif 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 + // `static QString Name();`, use it. Otherwise use the non-static + // method: `virtual QString name() const;` + template struct HasStaticMethodName - : std::false_type + : std::false_type {}; template - struct HasStaticMethodName::value>::type> - : std::true_type + struct HasStaticMethodName::value>::type> + : std::true_type {}; template @@ -170,5 +186,4 @@ class NODE_EDITOR_PUBLIC DataModelRegistry }; - } diff --git a/include/nodes/internal/Definitions.hpp b/include/nodes/internal/Definitions.hpp new file mode 100644 index 000000000..8f376e9c0 --- /dev/null +++ b/include/nodes/internal/Definitions.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include + +namespace QtNodes +{ +Q_NAMESPACE + + +enum class NodeRole +{ + Type = 0, + Position = 1, + Size = 2, + CaptionVisible = 3, + Caption = 4, + Style = 5, + NumberOfInPorts = 6, + NumberOfOutPorts = 7, + Widget = 8, +}; +Q_ENUM_NS(NodeRole) + + +enum NodeFlag +{ + NoFlags = 0x0, + Resizable = 0x1, +}; + +Q_DECLARE_FLAGS(NodeFlags, NodeFlag) +Q_FLAG_NS(NodeFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(NodeFlags) + + +enum class PortRole +{ + Data = 0, + DataType = 1, + ConnectionPolicy = 2, + CaptionVisible = 3, + Caption = 4, +}; +Q_ENUM_NS(PortRole) + + +enum class ConnectionPolicy +{ + One, + Many, +}; +Q_ENUM_NS(ConnectionPolicy) + + +enum class PortType +{ + In = 0, + Out = 1, + None = 2 +}; +Q_ENUM_NS(PortType) + + +using PortIndex = unsigned int; + +static constexpr PortIndex InvalidPortIndex = + std::numeric_limits::max(); + +using NodeId = unsigned int; + +static constexpr NodeId InvalidNodeId = + std::numeric_limits::max(); + +using ConnectionId = std::tuple; // Port In + +} diff --git a/include/nodes/internal/FlowScene.hpp b/include/nodes/internal/FlowScene.hpp deleted file mode 100644 index 9303c442a..000000000 --- a/include/nodes/internal/FlowScene.hpp +++ /dev/null @@ -1,165 +0,0 @@ -#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 const& 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 nodeClicked(Node& n); - - void connectionHovered(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; - - // DO NOT reorder this member to go after the others. - // This should outlive all the connections and nodes of - // the graph, so that nodes can potentially have pointers into it, - // which is why it comes first in the class. - std::shared_ptr _registry; - - std::unordered_map _connections; - std::unordered_map _nodes; - -private Q_SLOTS: - - void setupConnectionSignals(Connection const& c); - - void sendConnectionCreatedToNodes(Connection const& c); - void sendConnectionDeletedToNodes(Connection const& c); - -}; - -Node* -locateNodeAt(QPointF scenePoint, FlowScene &scene, - QTransform const & viewTransform); -} diff --git a/include/nodes/internal/FlowView.hpp b/include/nodes/internal/FlowView.hpp deleted file mode 100644 index 473af4d15..000000000 --- a/include/nodes/internal/FlowView.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#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); - -public Q_SLOTS: - - void scaleUp(); - - void scaleDown(); - - 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; - -protected: - - FlowScene * scene(); - -private: - - QAction* _clearSelectionAction; - QAction* _deleteSelectionAction; - - QPointF _clickPos; - - FlowScene* _scene; -}; -} diff --git a/include/nodes/internal/GraphModel.hpp b/include/nodes/internal/GraphModel.hpp new file mode 100644 index 000000000..668dea69f --- /dev/null +++ b/include/nodes/internal/GraphModel.hpp @@ -0,0 +1,183 @@ +#pragma once + +#include "Export.hpp" + +#include +#include + +#include +#include + +#include "Definitions.hpp" +#include "ConnectionIdHash.hpp" + + +namespace QtNodes +{ + +class NODE_EDITOR_PUBLIC GraphModel : public QObject +{ + Q_OBJECT +public: + /// @brief Returns the full set of unique Node Ids. + /** + * Users are responsible for generating unique unsigned int Ids for + * all the nodes in the graph. From an Id it should be possible to + * trace back to the model's internal representation of the node. + */ + virtual + std::unordered_set + allNodeIds() const; + + virtual + std::unordered_set + allConnectionIds(NodeId const nodeId) const; + + /// @brief Returns all connected Node Ids for given port. + /** + * The returned set of nodes and port indices correspond to the type + * opposite to the given `portType`. + */ + virtual + std::unordered_set> + connectedNodes(NodeId nodeId, + PortType portType, + PortIndex index) const; + + + virtual + bool + connectionExists(ConnectionId const connectionId) const; + + + virtual + NodeId + addNode(QString const nodeType = QString()); + + /// Model decides if a conection with given connection Id possible. + /** + * The default implementation compares corresponding data types. + */ + virtual + bool + connectionPossible(ConnectionId const connectionId); + + virtual + void + addConnection(ConnectionId const connectionId); + + virtual + bool + nodeExists(NodeId const nodeId) const; + + + /// @brief Returns node-related data for requested NodeRole. + /** + * Returns: Node Caption, Node Caption Visibility, + * Node Position etc. + */ + virtual + QVariant + nodeData(NodeId nodeId, NodeRole role) const; + + virtual + NodeFlags + nodeFlags(NodeId nodeId) const; + + /// @brief Sets node properties. + /** + * Sets: Node Caption, Node Caption Visibility, + * Shyle, State, Node Position etc. + */ + virtual + bool + setNodeData(NodeId nodeId, NodeRole role, QVariant value); + + /// @brief Returns port-related data for requested NodeRole. + /** + * Returns: Port Data Type, Port Data, Connection Policy, Port + * Caption. + */ + virtual + QVariant + portData(NodeId nodeId, + PortType portType, + PortIndex index, + PortRole role) const; + + virtual + bool + setPortData(NodeId nodeId, + PortType portType, + PortIndex index, + PortRole role) const; + + virtual + bool + deleteConnection(ConnectionId const connectionId); + + virtual + bool + deleteNode(NodeId const nodeId); + +Q_SIGNALS: + + void + connectionCreated(ConnectionId const connectionId); + + void + connectionDeleted(ConnectionId const connectionId); + + void + nodeCreated(NodeId const nodeId); + + void + nodeDeleted(NodeId const nodeId); + + void + nodePositonUpdated(NodeId const nodeId); + + void + portDataSet(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex); + + /** + * Signal emitted when model is about to remove port-related data. + * Clients must destroy existing connections to these ports. + */ + void + portsAboutToBeDeleted(NodeId const nodeId, + PortType const portType, + std::unordered_set const &portIndexSet); + + /** + * Signal emitted when model no longer has the old data associated + * with the given port indices. + */ + void + portsDeleted(NodeId const nodeId, + PortType const portType, + std::unordered_set const &portIndexSet); + + /** + * Signal emitted when model is about to create new port-related + * data. + */ + void + portsAboutToBeInserted(NodeId const nodeId, + PortType const portType, + std::unordered_set const &portIndexSet); + + /** + * Signal emitted when model is ready to provide the new data for + * just creted ports. Clients must re-draw the nodes, move existing + * conection ends to their new positions. + */ + void + portsInserted(NodeId const nodeId, + PortType const portType, + std::unordered_set const &portIndexSet); +}; + +} diff --git a/include/nodes/internal/GraphicsView.hpp b/include/nodes/internal/GraphicsView.hpp new file mode 100644 index 000000000..780997bdb --- /dev/null +++ b/include/nodes/internal/GraphicsView.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include + +#include "Export.hpp" + +namespace QtNodes +{ + +class BasicGraphicsScene; + +class NODE_EDITOR_PUBLIC GraphicsView + : public QGraphicsView +{ + Q_OBJECT +public: + GraphicsView(QWidget *parent = Q_NULLPTR); + GraphicsView(BasicGraphicsScene *scene, QWidget *parent = Q_NULLPTR); + + GraphicsView(const GraphicsView &) = delete; + GraphicsView + operator=(const GraphicsView &) = delete; + + QAction* + clearSelectionAction() const; + + QAction* + deleteSelectionAction() const; + + void + setScene(BasicGraphicsScene *scene); + + void + centerScene(); + +public Q_SLOTS: + + void + scaleUp(); + + void + scaleDown(); + + void + deleteSelectedObjects(); + +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; + +protected: + + BasicGraphicsScene * + nodeScene(); + +private: + + QAction* _clearSelectionAction; + QAction* _deleteSelectionAction; + + QPointF _clickPos; +}; +} diff --git a/include/nodes/internal/FlowViewStyle.hpp b/include/nodes/internal/GraphicsViewStyle.hpp similarity index 50% rename from include/nodes/internal/FlowViewStyle.hpp rename to include/nodes/internal/GraphicsViewStyle.hpp index aebdb4b4e..678b1dcd4 100644 --- a/include/nodes/internal/FlowViewStyle.hpp +++ b/include/nodes/internal/GraphicsViewStyle.hpp @@ -8,13 +8,15 @@ namespace QtNodes { -class NODE_EDITOR_PUBLIC FlowViewStyle : public Style +class NODE_EDITOR_PUBLIC GraphicsViewStyle : public Style { public: - FlowViewStyle(); + GraphicsViewStyle(); - FlowViewStyle(QString jsonText); + GraphicsViewStyle(QString jsonText); + + ~GraphicsViewStyle() = default; public: @@ -22,11 +24,9 @@ class NODE_EDITOR_PUBLIC FlowViewStyle : public Style private: - void loadJsonText(QString jsonText) override; - - void loadJsonFile(QString fileName) override; + void loadJson(QJsonDocument const & json) override; - void loadJsonFromByteArray(QByteArray const &byteArray) override; + QJsonDocument toJson() const override; public: diff --git a/include/nodes/internal/Node.hpp b/include/nodes/internal/Node.hpp deleted file mode 100644 index 9047690a9..000000000 --- a/include/nodes/internal/Node.hpp +++ /dev/null @@ -1,124 +0,0 @@ -#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(std::shared_ptr nodeData, - PortIndex inPortIndex, - const QUuid& connectionId) const; - - /// Fetches data from model's OUT #index port - /// and propagates it to the connection - void - onDataUpdated(PortIndex index); - - /// Propagates empty data to the attached connection. - void - onDataInvalidated(PortIndex index); - - /// update the graphic part if the size of the embeddedwidget changes - void - onNodeSizeUpdated(); - -private: - - // addressing - QUuid _uid; - - // data - std::unique_ptr _nodeDataModel; - - NodeState _nodeState; - - // painting - NodeGeometry _nodeGeometry; - - std::unique_ptr _nodeGraphicsObject; -}; -} diff --git a/include/nodes/internal/NodeData.hpp b/include/nodes/internal/NodeData.hpp index 44e6d4f3c..ffed37e91 100644 --- a/include/nodes/internal/NodeData.hpp +++ b/include/nodes/internal/NodeData.hpp @@ -1,5 +1,8 @@ #pragma once +#include + +#include #include #include "Export.hpp" @@ -7,7 +10,7 @@ namespace QtNodes { -struct NodeDataType +struct NODE_EDITOR_PUBLIC NodeDataType { QString id; QString name; @@ -32,14 +35,20 @@ class NODE_EDITOR_PUBLIC NodeData { public: - virtual ~NodeData() = default; + virtual + ~NodeData() = default; - virtual bool sameType(NodeData const &nodeData) const + virtual bool + sameType(NodeData const &nodeData) const { return (this->type().id == nodeData.type().id); } /// Type for inner use - virtual NodeDataType type() const = 0; + virtual NodeDataType + type() const = 0; }; + } +Q_DECLARE_METATYPE(QtNodes::NodeDataType) +Q_DECLARE_METATYPE(std::shared_ptr) diff --git a/include/nodes/internal/NodeDataModel.hpp b/include/nodes/internal/NodeDataModel.hpp index 57428dba5..8513115a1 100644 --- a/include/nodes/internal/NodeDataModel.hpp +++ b/include/nodes/internal/NodeDataModel.hpp @@ -1,31 +1,18 @@ #pragma once +#include #include -#include "PortType.hpp" +#include "Definitions.hpp" +#include "Export.hpp" #include "NodeData.hpp" -#include "Serializable.hpp" -#include "NodeGeometry.hpp" #include "NodeStyle.hpp" -#include "NodePainterDelegate.hpp" -#include "Export.hpp" -#include "memory.hpp" +#include "Serializable.hpp" namespace QtNodes { -class NodePainterDelegate; - -enum class NodeValidationState -{ - Valid, - Warning, - Error -}; - -class Connection; - class StyleCollection; class NODE_EDITOR_PUBLIC NodeDataModel @@ -41,24 +28,29 @@ class NODE_EDITOR_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 + virtual + bool captionVisible() const { return true; } - /// Port caption is used in GUI to label individual ports - virtual QString - portCaption(PortType, PortIndex) const { return QString(); } + /// Caption is used in GUI + virtual + QString + caption() const = 0; /// It is possible to hide port caption in GUI - virtual bool + virtual + bool portCaptionVisible(PortType, PortIndex) const { return false; } + /// Port caption is used in GUI to label individual ports + virtual + QString + portCaption(PortType, PortIndex) const { return QString(); } + /// Name makes this model unique - virtual QString + virtual + QString name() const = 0; public: @@ -69,19 +61,15 @@ class NODE_EDITOR_PUBLIC NodeDataModel public: virtual - unsigned int nPorts(PortType portType) const = 0; + unsigned int + nPorts(PortType portType) const = 0; virtual - NodeDataType dataType(PortType portType, PortIndex portIndex) const = 0; + NodeDataType + dataType(PortType portType, PortIndex portIndex) const = 0; public: - enum class ConnectionPolicy - { - One, - Many, - }; - virtual ConnectionPolicy portOutConnectionPolicy(PortIndex) const @@ -100,7 +88,7 @@ class NODE_EDITOR_PUBLIC NodeDataModel nodeStyle() const; void - setNodeStyle(NodeStyle const& style); + setNodeStyle(NodeStyle const &style); public: @@ -108,7 +96,7 @@ class NODE_EDITOR_PUBLIC NodeDataModel virtual void setInData(std::shared_ptr nodeData, - PortIndex port) = 0; + PortIndex const port) = 0; // Use this if portInConnectionPolicy returns ConnectionPolicy::Many virtual @@ -123,7 +111,7 @@ class NODE_EDITOR_PUBLIC NodeDataModel virtual std::shared_ptr - outData(PortIndex port) = 0; + outData(PortIndex const port) = 0; /** * It is recommented to preform a lazy initialization for the @@ -143,48 +131,37 @@ class NODE_EDITOR_PUBLIC NodeDataModel 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&) - { - } + inputConnectionCreated(ConnectionId const &) + {} + virtual void - inputConnectionDeleted(Connection const&) - { - } + inputConnectionDeleted(ConnectionId const &) + {} + virtual void - outputConnectionCreated(Connection const&) - { - } + outputConnectionCreated(ConnectionId const &) + {} + virtual void - outputConnectionDeleted(Connection const&) - { - } + outputConnectionDeleted(ConnectionId const &) + {} + Q_SIGNALS: /// Triggers the updates in the nodes downstream. void - dataUpdated(PortIndex index); + dataUpdated(PortIndex const index); /// Triggers the propagation of the empty data downstream. void - dataInvalidated(PortIndex index); + dataInvalidated(PortIndex const index); void computingStarted(); @@ -192,10 +169,13 @@ public Q_SLOTS: void computingFinished(); - void embeddedWidgetSizeUpdated(); + void + embeddedWidgetSizeUpdated(); private: NodeStyle _nodeStyle; }; + + } diff --git a/include/nodes/internal/NodeGeometry.hpp b/include/nodes/internal/NodeGeometry.hpp index 0a8ea4e23..471644c5e 100644 --- a/include/nodes/internal/NodeGeometry.hpp +++ b/include/nodes/internal/NodeGeometry.hpp @@ -5,158 +5,102 @@ #include #include -#include "PortType.hpp" #include "Export.hpp" -#include "memory.hpp" +#include "Definitions.hpp" namespace QtNodes { -class NodeState; -class NodeDataModel; -class Node; +class GraphModel; +class NodeGraphicsObject; +/** + * A helper-class for manipulating the node's geometry. + * It is designed to be constructed on stack and used in-place. + * The class is in essense a wrapper around the GraphModel. + */ class NODE_EDITOR_PUBLIC NodeGeometry { public: - - NodeGeometry(std::unique_ptr const &dataModel); + NodeGeometry(NodeGraphicsObject const & ngo); 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; + unsigned int entryHeight() const; - QPointF const& - draggingPos() const - { return _draggingPos; } - - void - setDraggingPosition(QPointF const& pos) - { _draggingPos = pos; } + unsigned int verticalSpacing() const; public: + QRectF boundingRect() const; - QRectF - entryBoundingRect() const; - - QRectF - boundingRect() const; + QSize size() const; /// Updates size unconditionally - void - recalculateSize() const; + QSize recalculateSize() const; /// Updates size if the QFontMetrics is changed - void - recalculateSize(QFont const &font) const; + QSize recalculateSizeIfFontChanged(QFont const & font) const; + + QPointF portNodePosition(PortType const portType, + PortIndex const index) const; - // TODO removed default QTransform() - QPointF - portScenePosition(PortIndex index, - PortType portType, - QTransform const & t = QTransform()) const; + QPointF portScenePosition(PortType const portType, + PortIndex const index, + QTransform const & t) const; - PortIndex - checkHitScenePoint(PortType portType, - QPointF point, - QTransform const & t = QTransform()) const; + PortIndex checkHitScenePoint(PortType portType, + QPointF point, + QTransform const & t = QTransform()) const; - QRect - resizeRect() const; + QRect resizeRect() const; /// Returns the position of a widget on the Node surface - QPointF - widgetPosition() const; + 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); + 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); private: - unsigned int - captionHeight() const; + unsigned int captionHeight() const; - unsigned int - captionWidth() const; + unsigned int captionWidth() const; - unsigned int - portWidth(PortType portType) const; + unsigned int portWidth(PortType portType) const; private: + NodeGraphicsObject const & _ngo; + GraphModel & _graphModel; + // 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 _defaultInPortWidth; + mutable unsigned int _defaultOutPortWidth; mutable unsigned int _entryHeight; - unsigned int _spacing; + unsigned int _verticalSpacing; + mutable QFontMetrics _fontMetrics; + mutable QFontMetrics _boldFontMetrics; - bool _hovered; + //unsigned int _entryWidth; // TODO: where was is used? - unsigned int _nSources; - unsigned int _nSinks; + //unsigned int _nSources; // TODO: where was it used? + //unsigned int _nSinks; - QPointF _draggingPos; + //QPointF _draggingPos; - std::unique_ptr const &_dataModel; + //std::unique_ptr const & _dataModel; - mutable QFontMetrics _fontMetrics; - mutable QFontMetrics _boldFontMetrics; }; } diff --git a/include/nodes/internal/NodeGraphicsObject.hpp b/include/nodes/internal/NodeGraphicsObject.hpp index ee717b5d5..c2bab604b 100644 --- a/include/nodes/internal/NodeGraphicsObject.hpp +++ b/include/nodes/internal/NodeGraphicsObject.hpp @@ -3,9 +3,6 @@ #include #include -#include "Connection.hpp" - -#include "NodeGeometry.hpp" #include "NodeState.hpp" class QGraphicsProxyWidget; @@ -13,27 +10,42 @@ class QGraphicsProxyWidget; namespace QtNodes { -class FlowScene; -class FlowItemEntry; +class BasicGraphicsScene; +class GraphModel; -/// Class reacts on GUI events, mouse clicks and -/// forwards painting operation. class NodeGraphicsObject : public QGraphicsObject { Q_OBJECT +public: + // Needed for qgraphicsitem_cast + enum { Type = UserType + 1 }; + + int + type() const override { return Type; } public: - NodeGraphicsObject(FlowScene &scene, - Node& node); + NodeGraphicsObject(BasicGraphicsScene &scene, + NodeId node); + + ~NodeGraphicsObject() override = default; + +public: + + GraphModel & + graphModel() const; - virtual - ~NodeGraphicsObject(); + BasicGraphicsScene * + nodeScene() const; - Node& - node(); + NodeId + nodeId() { return _nodeId; } + NodeId + nodeId() const { return _nodeId; } - Node const& - node() const; + NodeState & + nodeState() { return _nodeState; } + NodeState const & + nodeState() const { return _nodeState; } QRectF boundingRect() const override; @@ -46,19 +58,11 @@ class NodeGraphicsObject : public QGraphicsObject void moveConnections() const; - enum { Type = UserType + 1 }; - - int - type() const override { return Type; } - - void - lock(bool locked); - protected: void - paint(QPainter* painter, + paint(QPainter* painter, QStyleOptionGraphicsItem const* option, - QWidget* widget = 0) override; + QWidget* widget = 0) override; QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; @@ -92,13 +96,17 @@ class NodeGraphicsObject : public QGraphicsObject void embedQWidget(); +//private Q_SLOTS: + + //void onNodeSizeUpdated(); + private: - FlowScene & _scene; + NodeId _nodeId; - Node& _node; + GraphModel &_graphModel; - bool _locked; + NodeState _nodeState; // either nullptr or owned by parent QGraphicsItem QGraphicsProxyWidget * _proxyWidget; diff --git a/include/nodes/internal/NodeState.hpp b/include/nodes/internal/NodeState.hpp index 966bd46aa..4027d8448 100644 --- a/include/nodes/internal/NodeState.hpp +++ b/include/nodes/internal/NodeState.hpp @@ -3,60 +3,80 @@ #include #include +#include #include #include "Export.hpp" -#include "PortType.hpp" +#include "Definitions.hpp" #include "NodeData.hpp" -#include "memory.hpp" namespace QtNodes { -class Connection; -class NodeDataModel; +class NodeGraphicsObject; /// 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 }; + //using ConnectionPtrSet = + //std::unordered_map; + public: - NodeState(std::unique_ptr const &model); + NodeState(NodeGraphicsObject & ngo); 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 const& + //getEntries(PortType) const; - std::vector & - getEntries(PortType); + //std::vector & + //getEntries(PortType); - ConnectionPtrSet - connections(PortType portType, PortIndex portIndex) const; + //ConnectionPtrSet + //connections(PortType portType, PortIndex portIndex) const; + + //void + //setConnection(PortType portType, + //PortIndex portIndex, + //ConnectionId & connection); + + //void + //eraseConnection(PortType portType, + //PortIndex portIndex, + //QUuid id); + +public: + + bool hovered() const { return _hovered; } + + void setHovered(bool hovered = true) { _hovered = hovered; } - void - setConnection(PortType portType, - PortIndex portIndex, - Connection& connection); void - eraseConnection(PortType portType, - PortIndex portIndex, - QUuid id); + setResizing(bool resizing); + + bool + resizing() const; + + //void lock(bool locked = true); + + //void unlock() { lock(false); } + +public: ReactToConnectionState reaction() const; @@ -67,31 +87,39 @@ class NODE_EDITOR_PUBLIC NodeState NodeDataType reactingDataType() const; + QPointF draggingPos() const { return _draggingPos; } + void setReaction(ReactToConnectionState reaction, PortType reactingPortType = PortType::None, - - NodeDataType reactingDataType = - NodeDataType()); + NodeDataType reactingDataType = NodeDataType()); bool isReacting() const; - void - setResizing(bool resizing); + void reactToPossibleConnection(PortType, + NodeDataType const &, + QPointF const & scenePoint); - bool - resizing() const; + void resetReactionToConnection(); private: - std::vector _inConnections; - std::vector _outConnections; + NodeGraphicsObject & _ngo; + + bool _hovered; + + //std::vector _inConnections; + //std::vector _outConnections; ReactToConnectionState _reaction; - PortType _reactingPortType; + PortType _reactingPortType; NodeDataType _reactingDataType; + QPointF _draggingPos; + + bool _locked; // TODO: WTF? + bool _resizing; }; } diff --git a/include/nodes/internal/NodeStyle.hpp b/include/nodes/internal/NodeStyle.hpp index 5f0518880..9fc395ae9 100644 --- a/include/nodes/internal/NodeStyle.hpp +++ b/include/nodes/internal/NodeStyle.hpp @@ -16,17 +16,19 @@ class NODE_EDITOR_PUBLIC NodeStyle : public Style NodeStyle(QString jsonText); + NodeStyle(QJsonDocument const & json); + + virtual ~NodeStyle() = default; + public: static void setNodeStyle(QString jsonText); -private: - - void loadJsonText(QString jsonText) override; +public: - void loadJsonFile(QString fileName) override; + void loadJson(QJsonDocument const & json) override; - void loadJsonFromByteArray(QByteArray const &byteArray) override; + QJsonDocument toJson() const override; public: diff --git a/include/nodes/internal/PortType.hpp b/include/nodes/internal/PortType.hpp index cd76769ff..b2e1fe49e 100644 --- a/include/nodes/internal/PortType.hpp +++ b/include/nodes/internal/PortType.hpp @@ -3,65 +3,56 @@ #include #include -namespace QtNodes -{ +//namespace QtNodes +//{ +//Q_NAMESPACE -enum class PortType -{ - None, - In, - Out -}; -static const int INVALID = -1; +//struct Port +//{ + //PortType type; -using PortIndex = int; + //PortIndex index; -struct Port -{ - PortType type; + //Port() + //: type(PortType::None) + //, index(INVALID) + //{} - PortIndex index; + //Port(PortType t, PortIndex i) + //: type(t) + //, index(i) + //{} - Port() - : type(PortType::None) - , index(INVALID) - {} + //bool + //indexIsValid() { return index != INVALID; } - Port(PortType t, PortIndex i) - : type(t) - , index(i) - {} - - bool - indexIsValid() { return index != INVALID; } - - bool - portTypeIsValid() { return type != PortType::None; } -}; + //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; -} -} +//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; +//} +//} diff --git a/include/nodes/internal/Style.hpp b/include/nodes/internal/Style.hpp index a45d17940..b5d053e72 100644 --- a/include/nodes/internal/Style.hpp +++ b/include/nodes/internal/Style.hpp @@ -1,27 +1,60 @@ #pragma once +#include +#include +#include +#include #include namespace QtNodes { -class Style +class Style // : public QObject { + //Q_OBJECT + +public: + + virtual ~Style() = default; + public: virtual - ~Style() = default; + void loadJson(QJsonDocument const & json) = 0; + + virtual + QJsonDocument toJson() const = 0; + + /// Loads from utf-8 byte array. + virtual + void loadJsonFromByteArray(QByteArray const & byteArray) + { + QJsonDocument json(QJsonDocument::fromJson(byteArray)); + + loadJson(json); + } + + virtual + void loadJsonText(QString jsonText) + { + loadJsonFromByteArray(jsonText.toUtf8()); + } + + virtual + void loadJsonFile(QString fileName) + { + QFile file(fileName); -private: + if (!file.open(QIODevice::ReadOnly)) + { + qWarning() << "Couldn't open file " << fileName; - virtual void - loadJsonText(QString jsonText) = 0; + return; + } - virtual void - loadJsonFile(QString fileName) = 0; + loadJsonFromByteArray(file.readAll()); + } - virtual void - loadJsonFromByteArray(QByteArray const &byteArray) = 0; }; } diff --git a/include/nodes/internal/StyleCollection.hpp b/include/nodes/internal/StyleCollection.hpp index 3965a3602..ef5d482c6 100644 --- a/include/nodes/internal/StyleCollection.hpp +++ b/include/nodes/internal/StyleCollection.hpp @@ -1,10 +1,11 @@ #pragma once -#include "NodeStyle.hpp" -#include "ConnectionStyle.hpp" -#include "FlowViewStyle.hpp" #include "Export.hpp" +#include "ConnectionStyle.hpp" +#include "GraphicsViewStyle.hpp" +#include "NodeStyle.hpp" + namespace QtNodes { @@ -13,43 +14,35 @@ class NODE_EDITOR_PUBLIC StyleCollection public: static - NodeStyle const& - nodeStyle(); + NodeStyle const & nodeStyle(); static - ConnectionStyle const& - connectionStyle(); + ConnectionStyle const & connectionStyle(); static - FlowViewStyle const& - flowViewStyle(); + GraphicsViewStyle const & flowViewStyle(); public: static - void - setNodeStyle(NodeStyle); + void setNodeStyle(NodeStyle); static - void - setConnectionStyle(ConnectionStyle); + void setConnectionStyle(ConnectionStyle); static - void - setFlowViewStyle(FlowViewStyle); + void setGraphicsViewStyle(GraphicsViewStyle); private: StyleCollection() = default; - StyleCollection(StyleCollection const&) = delete; + StyleCollection(StyleCollection const &) = delete; - StyleCollection& - operator=(StyleCollection const&) = delete; + StyleCollection & operator=(StyleCollection const &) = delete; static - StyleCollection& - instance(); + StyleCollection & instance(); private: @@ -57,6 +50,6 @@ class NODE_EDITOR_PUBLIC StyleCollection ConnectionStyle _connectionStyle; - FlowViewStyle _flowViewStyle; + GraphicsViewStyle _flowViewStyle; }; } diff --git a/include/nodes/internal/TypeConverter.hpp b/include/nodes/internal/TypeConverter.hpp deleted file mode 100644 index 58a62e86d..000000000 --- a/include/nodes/internal/TypeConverter.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#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; - -struct TypeConverterIdHash -{ - std::size_t operator()(const QtNodes::TypeConverterId& converter) const noexcept - { - return qHash(converter.first.id) - ^ qHash(converter.second.id); - } -}; - -} diff --git a/include/nodes/internal/locateNode.hpp b/include/nodes/internal/locateNode.hpp new file mode 100644 index 000000000..fb2729131 --- /dev/null +++ b/include/nodes/internal/locateNode.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + + +class QGraphicsScene; + +namespace QtNodes +{ + +class NodeGraphicsObject; + + +NodeGraphicsObject* +locateNodeAt(QPointF scenePoint, + QGraphicsScene &scene, + QTransform const & viewTransform); + + +} diff --git a/include/nodes/internal/memory.hpp b/include/nodes/internal/memory.hpp deleted file mode 100644 index 241d6e4de..000000000 --- a/include/nodes/internal/memory.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#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 - } -} diff --git a/resources/DefaultStyle.json b/resources/DefaultStyle.json index 8375b4a15..da8dfe84c 100644 --- a/resources/DefaultStyle.json +++ b/resources/DefaultStyle.json @@ -1,5 +1,5 @@ { - "FlowViewStyle": { + "GraphicsViewStyle": { "BackgroundColor": [53, 53, 53], "FineGridColor": [60, 60, 60], "CoarseGridColor": [25, 25, 25] diff --git a/src/BasicGraphicsScene.cpp b/src/BasicGraphicsScene.cpp new file mode 100644 index 000000000..26fcf0a7d --- /dev/null +++ b/src/BasicGraphicsScene.cpp @@ -0,0 +1,418 @@ +#include "BasicGraphicsScene.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ConnectionGraphicsObject.hpp" +#include "ConnectionIdUtils.hpp" +#include "GraphicsView.hpp" +#include "NodeGraphicsObject.hpp" + + +namespace QtNodes +{ + +BasicGraphicsScene:: +BasicGraphicsScene(GraphModel &graphModel, + QObject * parent) + : QGraphicsScene(parent) + , _graphModel(graphModel) +{ + setItemIndexMethod(QGraphicsScene::NoIndex); + + + connect(&_graphModel, &GraphModel::connectionCreated, + this, &BasicGraphicsScene::onConnectionCreated); + + connect(&_graphModel, &GraphModel::connectionDeleted, + this, &BasicGraphicsScene::onConnectionDeleted); + + connect(&_graphModel, &GraphModel::nodeCreated, + this, &BasicGraphicsScene::onNodeCreated); + + connect(&_graphModel, &GraphModel::nodeDeleted, + this, &BasicGraphicsScene::onNodeDeleted); + + connect(&_graphModel, &GraphModel::nodePositonUpdated, + this, &BasicGraphicsScene::onNodePositionUpdated); + + connect(&_graphModel, &GraphModel::portsAboutToBeDeleted, + this, &BasicGraphicsScene::onPortsAboutToBeDeleted); + + connect(&_graphModel, &GraphModel::portsDeleted, + this, &BasicGraphicsScene::onPortsDeleted); + + connect(&_graphModel, &GraphModel::portsAboutToBeInserted, + this, &BasicGraphicsScene::onPortsAboutToBeInserted); + + connect(&_graphModel, &GraphModel::portsInserted, + this, &BasicGraphicsScene::onPortsInserted); + + traverseGraphAndPopulateGraphicsObjects(); +} + + +BasicGraphicsScene:: +~BasicGraphicsScene() +{} + + +GraphModel const & +BasicGraphicsScene:: +graphModel() const +{ + return _graphModel; +} + + +GraphModel & +BasicGraphicsScene:: +graphModel() +{ + return _graphModel; +} + + +std::unique_ptr const & +BasicGraphicsScene:: +makeDraftConnection(ConnectionId const incompleteConnectionId) +{ + _draftConnection = + std::make_unique(*this, + incompleteConnectionId); + + _draftConnection->grabMouse(); + + return _draftConnection; +} + + +void +BasicGraphicsScene:: +resetDraftConnection() +{ + _draftConnection.reset(); +} + + +void +BasicGraphicsScene:: +clearScene() +{ + auto const &allNodeIds = + graphModel().allNodeIds(); + + for ( auto nodeId : allNodeIds) + { + graphModel().deleteNode(nodeId); + } +} + + +NodeGraphicsObject* +BasicGraphicsScene:: +nodeGraphicsObject(NodeId nodeId) +{ + NodeGraphicsObject * ngo = nullptr; + auto it = _nodeGraphicsObjects.find(nodeId); + if (it != _nodeGraphicsObjects.end()) + { + ngo = it->second.get(); + } + + return ngo; +} + + +ConnectionGraphicsObject* +BasicGraphicsScene:: +connectionGraphicsObject(ConnectionId connectionId) +{ + ConnectionGraphicsObject * cgo = nullptr; + auto it = _connectionGraphicsObjects.find(connectionId); + if (it != _connectionGraphicsObjects.end()) + { + cgo = it->second.get(); + } + + return cgo; +} + + +QMenu * +BasicGraphicsScene:: +createSceneMenu(QPointF const scenePos) +{ + Q_UNUSED(scenePos); + return nullptr; +} + + +void +BasicGraphicsScene:: +traverseGraphAndPopulateGraphicsObjects() +{ + auto allNodeIds = _graphModel.allNodeIds(); + + while (!allNodeIds.empty()) + { + std::queue fifo; + + auto firstId = *allNodeIds.begin(); + allNodeIds.erase(firstId); + + fifo.push(firstId); + + while (!fifo.empty()) + { + auto nodeId = fifo.front(); + fifo.pop(); + + _nodeGraphicsObjects[nodeId] = + std::make_unique(*this, nodeId); + + unsigned int nOutPorts = + _graphModel.nodeData(nodeId, NodeRole::NumberOfOutPorts).toUInt(); + + for (PortIndex index = 0; index < nOutPorts; ++index) + { + auto connectedNodes = + _graphModel.connectedNodes(nodeId, + PortType::Out, + index); + + for (auto cn : connectedNodes) + { + fifo.push(cn.second); + allNodeIds.erase(cn.second); + + auto connectionId = std::make_tuple(nodeId, index, cn.first, cn.second); + + _connectionGraphicsObjects[connectionId] = + std::make_unique(*this, + connectionId); + } + } + } // while + } +} + + +void +BasicGraphicsScene:: +updateAttachedNodes(ConnectionId const connectionId, + PortType const portType) +{ + auto node = + nodeGraphicsObject(getNodeId(portType, connectionId)); + + if (node) + { + node->update(); + } +} + + +void +BasicGraphicsScene:: +onConnectionDeleted(ConnectionId const connectionId) +{ + auto it = _connectionGraphicsObjects.find(connectionId); + if (it != _connectionGraphicsObjects.end()) + { + _connectionGraphicsObjects.erase(it); + } + + // TODO: do we need it? + if (_draftConnection && + _draftConnection->connectionId() == connectionId) + { + _draftConnection.reset(); + } + + updateAttachedNodes(connectionId, PortType::Out); + updateAttachedNodes(connectionId, PortType::In); +} + + +void +BasicGraphicsScene:: +onConnectionCreated(ConnectionId const connectionId) +{ + _connectionGraphicsObjects[connectionId] = + std::make_unique(*this, + connectionId); + + updateAttachedNodes(connectionId, PortType::Out); + updateAttachedNodes(connectionId, PortType::In); +} + + +void +BasicGraphicsScene:: +onNodeDeleted(NodeId const nodeId) +{ + auto it = _nodeGraphicsObjects.find(nodeId); + if (it != _nodeGraphicsObjects.end()) + { + _nodeGraphicsObjects.erase(it); + } +} + + +void +BasicGraphicsScene:: +onNodeCreated(NodeId const nodeId) +{ + _nodeGraphicsObjects[nodeId] = + std::make_unique(*this, nodeId); +} + + +void +BasicGraphicsScene:: +onNodePositionUpdated(NodeId const nodeId) +{ + auto node = nodeGraphicsObject(nodeId); + if (node) + { + node->setPos(_graphModel.nodeData(nodeId, + NodeRole::Position).value()); + node->update(); + } +} + + +void +BasicGraphicsScene:: +onPortsAboutToBeDeleted(NodeId const nodeId, + PortType const portType, + std::unordered_set const &portIndexSet) +{ + //NodeGraphicsObject * node = nodeGraphicsObject(nodeId); + + //if (node) + //{ + //for (auto portIndex : portIndexSet) + //{ + //auto const connectedNodes = + //_graphModel.connectedNodes(nodeId, portType, portIndex); + + //for (auto cn : connectedNodes) + //{ + //ConnectionId connectionId = + //(portType == PortType::In) ? + //std::make_tuple(cn.first, cn.second, nodeId, portIndex) : + //std::make_tuple(nodeId, portIndex, cn.first, cn.second); + + //deleteConnection(connectionId); + //} + //} + //} +} + + +void +BasicGraphicsScene:: +onPortsDeleted(NodeId const nodeId, + PortType const portType, + std::unordered_set const &portIndexSet) +{ + NodeGraphicsObject * node = nodeGraphicsObject(nodeId); + + if (node) + { + node->update(); + } +} + + +void +BasicGraphicsScene:: +onPortsAboutToBeInserted(NodeId const nodeId, + PortType const portType, + std::unordered_set const &portIndexSet) +{ + // TODO +} + + +void +BasicGraphicsScene:: +onPortsInserted(NodeId const nodeId, + PortType const portType, + std::unordered_set const &portIndexSet) +{ + // TODO +} + + +#if 0 +ConnectionGraphicsObject & +BasicGraphicsScene:: +createConnection(NodeId const nodeId, + PortType const connectedPort, + PortIndex const portIndex) +{ + // Construct an incomplete ConnectionId with one dangling end. + ConnectionId const connectionId = + (connectedPort == PortType::In) ? + std::make_tuple(InvalidNodeId, InvalidPortIndex, nodeId, portIndex) : + std::make_tuple(nodeId, portIndex, InvalidNodeId, InvalidPortIndex); + + auto cgo = std::make_unique(*this, connectionId); + + _connectionGraphicsObjects[connectionId] = std::move(cgo); + + // Note: this connection isn't truly created yet. + // It has just one valid attached end. + // Thus, don't send the connectionCreated(...) signal. + + //QObject::connect(connection.get(), + //&Connection::connectionCompleted, + //this, + //[this](Connection const & c) + //{ connectionCreated(c); }); + + return *_connectionGraphicsObjects[connectionId]; +} + + +#endif + + +//Node & +//BasicGraphicsScene:: +//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(); +//_nodes[node->id()] = std::move(node); + +//nodeCreated(*nodePtr); +//return *nodePtr; +//} + + +} diff --git a/src/Connection.cpp b/src/Connection.cpp deleted file mode 100644 index c1dbd6cdf..000000000 --- a/src/Connection.cpp +++ /dev/null @@ -1,450 +0,0 @@ -#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); - } - - if (_inNode) - { - _inNode->nodeGraphicsObject().update(); - } - - if (_outNode) - { - propagateEmptyData(); - _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(); -} - - -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, id()); - } -} - - -void -Connection:: -propagateEmptyData() const -{ - std::shared_ptr emptyData; - - propagateData(emptyData); -} diff --git a/src/ConnectionBlurEffect.cpp b/src/ConnectionBlurEffect.cpp index ca80609b6..4a6cd8456 100644 --- a/src/ConnectionBlurEffect.cpp +++ b/src/ConnectionBlurEffect.cpp @@ -25,3 +25,4 @@ draw(QPainter* painter) //_item->paint(painter, nullptr, nullptr); } + diff --git a/src/ConnectionBlurEffect.hpp b/src/ConnectionBlurEffect.hpp index abea71cdf..6b8ea8986 100644 --- a/src/ConnectionBlurEffect.hpp +++ b/src/ConnectionBlurEffect.hpp @@ -1,5 +1,4 @@ #include - #include namespace QtNodes @@ -11,12 +10,8 @@ class ConnectionBlurEffect : public QGraphicsBlurEffect { public: - ConnectionBlurEffect(ConnectionGraphicsObject* item); - void - draw(QPainter* painter) override; - -private: + void draw(QPainter* painter) override; }; } diff --git a/src/ConnectionGeometry.cpp b/src/ConnectionGeometry.cpp deleted file mode 100644 index 084b4c936..000000000 --- a/src/ConnectionGeometry.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#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); -} diff --git a/src/ConnectionGraphicsObject.cpp b/src/ConnectionGraphicsObject.cpp index 8fff26459..751f502f9 100644 --- a/src/ConnectionGraphicsObject.cpp +++ b/src/ConnectionGraphicsObject.cpp @@ -6,64 +6,134 @@ #include #include -#include "FlowScene.hpp" +#include -#include "Connection.hpp" -#include "ConnectionGeometry.hpp" +#include "ConnectionIdUtils.hpp" #include "ConnectionPainter.hpp" #include "ConnectionState.hpp" -#include "ConnectionBlurEffect.hpp" - -#include "NodeGraphicsObject.hpp" - +#include "ConnectionStyle.hpp" +#include "GraphModel.hpp" #include "NodeConnectionInteraction.hpp" +#include "NodeGeometry.hpp" +#include "NodeGraphicsObject.hpp" +#include "BasicGraphicsScene.hpp" +#include "StyleCollection.hpp" +#include "locateNode.hpp" -#include "Node.hpp" -using QtNodes::ConnectionGraphicsObject; -using QtNodes::Connection; -using QtNodes::FlowScene; +namespace QtNodes +{ ConnectionGraphicsObject:: -ConnectionGraphicsObject(FlowScene &scene, - Connection &connection) - : _scene(scene) - , _connection(connection) +ConnectionGraphicsObject(BasicGraphicsScene &scene, + ConnectionId const connectionId) + : _connectionId(connectionId) + , _graphModel(scene.graphModel()) + , _connectionState(*this) + , _out{0, 0} + , _in{0, 0} { - _scene.addItem(this); + scene.addItem(this); - setFlag(QGraphicsItem::ItemIsMovable, true); - setFlag(QGraphicsItem::ItemIsFocusable, true); + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsFocusable, true); setFlag(QGraphicsItem::ItemIsSelectable, true); setAcceptHoverEvents(true); - // addGraphicsEffect(); + //addGraphicsEffect(); setZValue(-1.0); + + initializePosition(); +} + + +void +ConnectionGraphicsObject:: +initializePosition() +{ + // 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 (_connectionState.requiredPort() != PortType::None) + { + PortType attachedPort = oppositePort(_connectionState.requiredPort()); + + PortIndex portIndex = getPortIndex(attachedPort, _connectionId); + NodeId nodeId = getNodeId(attachedPort, _connectionId); + + NodeGraphicsObject * ngo = + nodeScene()->nodeGraphicsObject(nodeId); + + QTransform nodeSceneTransform = + ngo->sceneTransform(); + + NodeGeometry geometry(*ngo); + + QPointF pos = + geometry.portScenePosition(attachedPort, + portIndex, + nodeSceneTransform); + + this->setPos(pos); + } + + move(); } +GraphModel & ConnectionGraphicsObject:: -~ConnectionGraphicsObject() +graphModel() const { - _scene.removeItem(this); + return _graphModel; } -QtNodes::Connection& +BasicGraphicsScene * ConnectionGraphicsObject:: -connection() +nodeScene() const { - return _connection; + return dynamic_cast(scene()); } +ConnectionId +ConnectionGraphicsObject:: +connectionId() const +{ + return _connectionId; +} + + + QRectF ConnectionGraphicsObject:: boundingRect() const { - return _connection.connectionGeometry().boundingRect(); + auto points = pointsC1C2(); + + // `normalized()` fixes inverted rects. + QRectF basicRect = QRectF(_out, _in).normalized(); + + QRectF c1c2Rect = QRectF(points.first, points.second).normalized(); + + QRectF commonRect = basicRect.united(c1c2Rect); + + auto const &connectionStyle = StyleCollection::connectionStyle(); + float const diam = connectionStyle.pointDiameter(); + QPointF const cornerOffset(diam, diam); + + // Expand rect by port circle diameter + commonRect.setTopLeft(commonRect.topLeft() - cornerOffset); + commonRect.setBottomRight(commonRect.bottomRight() + 2 * cornerOffset); + + return commonRect; } @@ -79,12 +149,31 @@ shape() const //return path; #else - auto const &geom = - _connection.connectionGeometry(); + return ConnectionPainter::getPainterStroke(*this); +#endif +} - return ConnectionPainter::getPainterStroke(geom); -#endif +QPointF const & +ConnectionGraphicsObject:: +endPoint(PortType portType) const +{ + Q_ASSERT(portType != PortType::None); + + return (portType == PortType::Out ? + _out : + _in); +} + + +void +ConnectionGraphicsObject:: +setEndPoint(PortType portType, QPointF const &point) +{ + if (portType == PortType::In) + _in = point; + else + _out = point; } @@ -100,93 +189,120 @@ void ConnectionGraphicsObject:: move() { - for(PortType portType: { PortType::In, PortType::Out } ) - { - if (auto node = _connection.getNode(portType)) + auto moveEnd = + [this](ConnectionId cId, PortType portType) { - auto const &nodeGraphics = node->nodeGraphicsObject(); + NodeId nodeId = getNodeId(portType, cId); + + if (nodeId == InvalidNodeId) + return; - auto const &nodeGeom = node->nodeGeometry(); + NodeGraphicsObject * ngo = + nodeScene()->nodeGraphicsObject(nodeId); + + NodeGeometry nodeGeometry(*ngo); QPointF scenePos = - nodeGeom.portScenePosition(_connection.getPortIndex(portType), - portType, - nodeGraphics.sceneTransform()); + nodeGeometry.portScenePosition(portType, + getPortIndex(portType, cId), + ngo->sceneTransform()); - QTransform sceneTransform = this->sceneTransform(); + QPointF connectionPos = sceneTransform().inverted().map(scenePos); - QPointF connectionPos = sceneTransform.inverted().map(scenePos); + setEndPoint(portType, connectionPos); + }; - _connection.connectionGeometry().setEndPoint(portType, - connectionPos); + moveEnd(_connectionId, PortType::Out); + moveEnd(_connectionId, PortType::In); + + setGeometryChanged(); + + update(); +} - _connection.getConnectionGraphicsObject().setGeometryChanged(); - _connection.getConnectionGraphicsObject().update(); - } - } +ConnectionState const & +ConnectionGraphicsObject:: +connectionState() const +{ + return _connectionState; } -void ConnectionGraphicsObject::lock(bool locked) + +ConnectionState & +ConnectionGraphicsObject:: +connectionState() { - setFlag(QGraphicsItem::ItemIsMovable, !locked); - setFlag(QGraphicsItem::ItemIsFocusable, !locked); - setFlag(QGraphicsItem::ItemIsSelectable, !locked); + return _connectionState; } void ConnectionGraphicsObject:: -paint(QPainter* painter, - QStyleOptionGraphicsItem const* option, - QWidget*) +paint(QPainter * painter, + QStyleOptionGraphicsItem const * option, + QWidget *) { + if (!scene()) + return; + painter->setClipRect(option->exposedRect); - ConnectionPainter::paint(painter, - _connection); + ConnectionPainter::paint(painter, *this); } void ConnectionGraphicsObject:: -mousePressEvent(QGraphicsSceneMouseEvent* event) +mousePressEvent(QGraphicsSceneMouseEvent * event) { QGraphicsItem::mousePressEvent(event); - //event->ignore(); } void ConnectionGraphicsObject:: -mouseMoveEvent(QGraphicsSceneMouseEvent* event) +mouseMoveEvent(QGraphicsSceneMouseEvent * event) { prepareGeometryChange(); - auto view = static_cast(event->widget()); - auto node = locateNodeAt(event->scenePos(), - _scene, + auto view = static_cast(event->widget()); + auto ngo = locateNodeAt(event->scenePos(), + *nodeScene(), view->transform()); + if (ngo) + { + _connectionState.interactWithNode(ngo->nodeId()); - auto &state = _connection.connectionState(); + PortType knownPortType = oppositePort(_connectionState.requiredPort()); - state.interactWithNode(node); - if (node) - { - node->reactToPossibleConnection(state.requiredPort(), - _connection.dataType(oppositePort(state.requiredPort())), - event->scenePos()); + NodeId knownNodeId = (knownPortType == PortType::Out) ? + std::get<0>(_connectionId) : + std::get<2>(_connectionId); + + PortIndex knownPortIndex = (knownPortType == PortType::Out) ? + std::get<1>(_connectionId) : + std::get<3>(_connectionId); + + NodeDataType knownDataType = + _graphModel.portData(knownNodeId, + knownPortType, + knownPortIndex, + PortRole::DataType).value(); + + // Sets node's mouse dragging position in nodes coordinates. + ngo->nodeState().reactToPossibleConnection(_connectionState.requiredPort(), + knownDataType, + event->scenePos()); } //------------------- - QPointF offset = event->pos() - event->lastPos(); - - auto requiredPort = _connection.requiredPort(); + auto requiredPort = _connectionState.requiredPort(); if (requiredPort != PortType::None) { - _connection.connectionGeometry().moveEndPoint(requiredPort, offset); + setEndPoint(requiredPort, event->pos()); } //------------------- @@ -199,52 +315,104 @@ mouseMoveEvent(QGraphicsSceneMouseEvent* event) void ConnectionGraphicsObject:: -mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +mouseReleaseEvent(QGraphicsSceneMouseEvent * event) { + QGraphicsItem::mouseReleaseEvent(event); + ungrabMouse(); event->accept(); - auto node = locateNodeAt(event->scenePos(), _scene, - _scene.views()[0]->transform()); + auto view = static_cast(event->widget()); - NodeConnectionInteraction interaction(*node, _connection, _scene); + Q_ASSERT(view); - if (node && interaction.tryConnect()) + auto ngo = locateNodeAt(event->scenePos(), + *nodeScene(), + view->transform()); + + if (ngo) { - node->resetReactionToConnection(); + NodeConnectionInteraction interaction(*ngo, *this, *nodeScene()); + + interaction.tryConnect(); } - if (_connection.connectionState().requiresPort()) + // If connection attempt was unsuccessful + if (_connectionState.requiresPort()) { - _scene.deleteConnection(_connection); + // Resulting unique_ptr is not used and automatically deleted. + nodeScene()->resetDraftConnection(); } } void ConnectionGraphicsObject:: -hoverEnterEvent(QGraphicsSceneHoverEvent* event) +hoverEnterEvent(QGraphicsSceneHoverEvent * event) { - _connection.connectionGeometry().setHovered(true); + _connectionState.setHovered(true); update(); - _scene.connectionHovered(connection(), event->screenPos()); + + // Signal + nodeScene()->connectionHovered(connectionId(), event->screenPos()); + event->accept(); } void ConnectionGraphicsObject:: -hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +hoverLeaveEvent(QGraphicsSceneHoverEvent * event) { - _connection.connectionGeometry().setHovered(false); + _connectionState.setHovered(false); update(); - _scene.connectionHoverLeft(connection()); + + // Signal + nodeScene()->connectionHoverLeft(connectionId()); + event->accept(); } +std::pair +ConnectionGraphicsObject:: +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); +} + + void ConnectionGraphicsObject:: addGraphicsEffect() @@ -259,3 +427,6 @@ addGraphicsEffect() //effect->setOffset(4, 4); //effect->setColor(QColor(Qt::gray).darker(800)); } + + +} diff --git a/src/ConnectionPainter.cpp b/src/ConnectionPainter.cpp index c6da391be..5f482a875 100644 --- a/src/ConnectionPainter.cpp +++ b/src/ConnectionPainter.cpp @@ -2,34 +2,30 @@ #include -#include "ConnectionGeometry.hpp" -#include "ConnectionState.hpp" #include "ConnectionGraphicsObject.hpp" -#include "Connection.hpp" - +#include "ConnectionState.hpp" +#include "Definitions.hpp" +#include "GraphModel.hpp" #include "NodeData.hpp" - #include "StyleCollection.hpp" -using QtNodes::ConnectionPainter; -using QtNodes::ConnectionGeometry; -using QtNodes::Connection; - +namespace QtNodes +{ static QPainterPath -cubicPath(ConnectionGeometry const& geom) +cubicPath(ConnectionGraphicsObject const &connection) { - QPointF const& source = geom.source(); - QPointF const& sink = geom.sink(); + QPointF const &in = connection.endPoint(PortType::In); + QPointF const &out = connection.endPoint(PortType::Out); - auto c1c2 = geom.pointsC1C2(); + auto const c1c2 = connection.pointsC1C2(); // cubic spline - QPainterPath cubic(source); + QPainterPath cubic(out); - cubic.cubicTo(c1c2.first, c1c2.second, sink); + cubic.cubicTo(c1c2.first, c1c2.second, in); return cubic; } @@ -37,12 +33,12 @@ cubicPath(ConnectionGeometry const& geom) QPainterPath ConnectionPainter:: -getPainterStroke(ConnectionGeometry const& geom) +getPainterStroke(ConnectionGraphicsObject const &connection) { - auto cubic = cubicPath(geom); + auto cubic = cubicPath(connection); - QPointF const& source = geom.source(); - QPainterPath result(source); + QPointF const &out = connection.endPoint(PortType::Out); + QPainterPath result(out); unsigned segments = 20; @@ -62,107 +58,93 @@ getPainterStroke(ConnectionGeometry const& geom) static void debugDrawing(QPainter * painter, - Connection const & connection) + ConnectionGraphicsObject const &cgo) { Q_UNUSED(painter); - Q_UNUSED(connection); - ConnectionGeometry const& geom = - connection.connectionGeometry(); { - QPointF const& source = geom.source(); - QPointF const& sink = geom.sink(); + QPointF const &out = geom.out(); + QPointF const &in = geom.in(); - auto points = geom.pointsC1C2(); + auto const points = cgo.pointsC1C2(); painter->setPen(Qt::red); painter->setBrush(Qt::red); - painter->drawLine(QLineF(source, points.first)); + painter->drawLine(QLineF(out, points.first)); painter->drawLine(QLineF(points.first, points.second)); - painter->drawLine(QLineF(points.second, sink)); - painter->drawEllipse(points.first, 3, 3); + painter->drawLine(QLineF(points.second, in)); + painter->drawEllipse(points.first, 3, 3); painter->drawEllipse(points.second, 3, 3); painter->setBrush(Qt::NoBrush); - - painter->drawPath(cubicPath(geom)); + painter->drawPath(cubicPath(cgo)); } { painter->setPen(Qt::yellow); - painter->drawRect(geom.boundingRect()); } } + + #endif static void drawSketchLine(QPainter * painter, - Connection const & connection) + ConnectionGraphicsObject const &cgo) { - using QtNodes::ConnectionState; - - ConnectionState const& state = - connection.connectionState(); + ConnectionState const &state = cgo.connectionState(); if (state.requiresPort()) { - auto const & connectionStyle = + auto const &connectionStyle = QtNodes::StyleCollection::connectionStyle(); - QPen p; - p.setWidth(connectionStyle.constructionLineWidth()); - p.setColor(connectionStyle.constructionColor()); - p.setStyle(Qt::DashLine); + QPen pen; + pen.setWidth(connectionStyle.constructionLineWidth()); + pen.setColor(connectionStyle.constructionColor()); + pen.setStyle(Qt::DashLine); - painter->setPen(p); + painter->setPen(pen); painter->setBrush(Qt::NoBrush); - using QtNodes::ConnectionGeometry; - ConnectionGeometry const& geom = connection.connectionGeometry(); + auto cubic = cubicPath(cgo); - auto cubic = cubicPath(geom); // cubic spline painter->drawPath(cubic); } } + static void drawHoveredOrSelected(QPainter * painter, - Connection const & connection) + ConnectionGraphicsObject const &cgo) { - using QtNodes::ConnectionGeometry; - - ConnectionGeometry const& geom = connection.connectionGeometry(); - bool const hovered = geom.hovered(); - - auto const& graphicsObject = - connection.getConnectionGraphicsObject(); - - bool const selected = graphicsObject.isSelected(); + bool const hovered = cgo.connectionState().hovered(); + bool const selected = cgo.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()); + double const lineWidth = connectionStyle.lineWidth(); - painter->setPen(p); + QPen pen; + pen.setWidth(2 * lineWidth); + pen.setColor(selected ? + connectionStyle.selectedHaloColor() : + connectionStyle.hoveredColor()); + + painter->setPen(pen); painter->setBrush(Qt::NoBrush); // cubic spline - auto cubic = cubicPath(geom); + auto const cubic = cubicPath(cgo); painter->drawPath(cubic); } } @@ -171,12 +153,9 @@ drawHoveredOrSelected(QPainter * painter, static void drawNormalLine(QPainter * painter, - Connection const & connection) + ConnectionGraphicsObject const &cgo) { - using QtNodes::ConnectionState; - - ConnectionState const& state = - connection.connectionState(); + ConnectionState const &state = cgo.connectionState(); if (state.requiresPort()) return; @@ -186,30 +165,41 @@ drawNormalLine(QPainter * painter, auto const &connectionStyle = QtNodes::StyleCollection::connectionStyle(); - QColor normalColorOut = connectionStyle.normalColor(); - QColor normalColorIn = connectionStyle.normalColor(); - QColor selectedColor = connectionStyle.selectedColor(); + QColor normalColorOut = connectionStyle.normalColor(); + QColor normalColorIn = connectionStyle.normalColor(); + QColor selectedColor = connectionStyle.selectedColor(); + + bool useGradientColor = false; - bool gradientColor = false; + GraphModel const &graphModel = cgo.graphModel(); if (connectionStyle.useDataDefinedColors()) { using QtNodes::PortType; - auto dataTypeOut = connection.dataType(PortType::Out); - auto dataTypeIn = connection.dataType(PortType::In); + auto const cId = cgo.connectionId(); + + auto dataTypeOut = + graphModel.portData(std::get<0>(cId), + PortType::Out, + std::get<1>(cId), + PortRole::DataType).value(); + + auto dataTypeIn = + graphModel.portData(std::get<2>(cId), + PortType::In, + std::get<3>(cId), + PortRole::DataType).value(); - gradientColor = (dataTypeOut.id != dataTypeIn.id); + useGradientColor = (dataTypeOut.id != dataTypeIn.id); - normalColorOut = connectionStyle.normalColor(dataTypeOut.id); - normalColorIn = connectionStyle.normalColor(dataTypeIn.id); - selectedColor = normalColorOut.darker(200); + 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 @@ -217,12 +207,10 @@ drawNormalLine(QPainter * painter, p.setWidth(lineWidth); - auto const& graphicsObject = connection.getConnectionGraphicsObject(); - bool const selected = graphicsObject.isSelected(); - + bool const selected = cgo.isSelected(); - auto cubic = cubicPath(geom); - if (gradientColor) + auto cubic = cubicPath(cgo); + if (useGradientColor) { painter->setBrush(Qt::NoBrush); @@ -237,7 +225,7 @@ drawNormalLine(QPainter * painter, for (unsigned int i = 0ul; i < segments; ++i) { double ratioPrev = double(i) / segments; - double ratio = double(i + 1) / segments; + double ratio = double(i + 1) / segments; if (i == segments / 2) { @@ -256,8 +244,8 @@ drawNormalLine(QPainter * painter, QIcon icon(":convert.png"); QPixmap pixmap = icon.pixmap(QSize(22, 22)); - painter->drawPixmap(cubic.pointAtPercent(0.50) - QPoint(pixmap.width()/2, - pixmap.height()/2), + painter->drawPixmap(cubic.pointAtPercent(0.50) - QPoint(pixmap.width() / 2, + pixmap.height() / 2), pixmap); } @@ -281,26 +269,21 @@ drawNormalLine(QPainter * painter, void ConnectionPainter:: -paint(QPainter* painter, - Connection const &connection) +paint(QPainter * painter, + ConnectionGraphicsObject const &cgo) { - drawHoveredOrSelected(painter, connection); + drawHoveredOrSelected(painter, cgo); - drawSketchLine(painter, connection); + drawSketchLine(painter, cgo); - drawNormalLine(painter, connection); + drawNormalLine(painter, cgo); #ifdef NODE_DEBUG_DRAWING - debugDrawing(painter, connection); + debugDrawing(painter, cgo); #endif // draw end points - ConnectionGeometry const& geom = connection.connectionGeometry(); - - QPointF const & source = geom.source(); - QPointF const & sink = geom.sink(); - - auto const & connectionStyle = + auto const &connectionStyle = QtNodes::StyleCollection::connectionStyle(); double const pointDiameter = connectionStyle.pointDiameter(); @@ -308,6 +291,9 @@ paint(QPainter* painter, painter->setPen(connectionStyle.constructionColor()); painter->setBrush(connectionStyle.constructionColor()); double const pointRadius = pointDiameter / 2.0; - painter->drawEllipse(source, pointRadius, pointRadius); - painter->drawEllipse(sink, pointRadius, pointRadius); + painter->drawEllipse(cgo.out(), pointRadius, pointRadius); + painter->drawEllipse(cgo.in(), pointRadius, pointRadius); +} + + } diff --git a/src/ConnectionPainter.hpp b/src/ConnectionPainter.hpp index b91b508f2..606ca7144 100644 --- a/src/ConnectionPainter.hpp +++ b/src/ConnectionPainter.hpp @@ -1,25 +1,26 @@ #pragma once #include +#include + +#include "Definitions.hpp" namespace QtNodes { class ConnectionGeometry; -class ConnectionState; -class Connection; +class ConnectionGraphicsObject; class ConnectionPainter { public: static - void - paint(QPainter* painter, - Connection const& connection); + void paint(QPainter * painter, + ConnectionGraphicsObject const & cgo); static - QPainterPath - getPainterStroke(ConnectionGeometry const& geom); + QPainterPath getPainterStroke(ConnectionGraphicsObject const & cgo); }; + } diff --git a/src/ConnectionState.cpp b/src/ConnectionState.cpp index 5d8d38dfa..7e970e726 100644 --- a/src/ConnectionState.cpp +++ b/src/ConnectionState.cpp @@ -1,14 +1,14 @@ #include "ConnectionState.hpp" -#include - +#include #include -#include "FlowScene.hpp" -#include "Node.hpp" +#include "BasicGraphicsScene.hpp" +#include "ConnectionGraphicsObject.hpp" +#include "NodeGraphicsObject.hpp" -using QtNodes::ConnectionState; -using QtNodes::Node; +namespace QtNodes +{ ConnectionState:: ~ConnectionState() @@ -17,13 +17,42 @@ ConnectionState:: } +PortType +ConnectionState:: +requiredPort() const +{ + PortType t = PortType::None; + + if (std::get<0>(_cgo.connectionId()) == InvalidNodeId) + { + t = PortType::Out; + } + else + { + t = PortType::In; + } + + return t; +} + + +bool +ConnectionState:: +requiresPort() const +{ + ConnectionId id = _cgo.connectionId(); + return std::get<0>(id) == InvalidNodeId || + std::get<2>(id) == InvalidNodeId; +} + + void ConnectionState:: -interactWithNode(Node* node) +interactWithNode(NodeId const nodeId) { - if (node) + if (nodeId != InvalidNodeId) { - _lastHoveredNode = node; + _lastHoveredNode = nodeId; } else { @@ -34,9 +63,9 @@ interactWithNode(Node* node) void ConnectionState:: -setLastHoveredNode(Node* node) +setLastHoveredNode(NodeId const nodeId) { - _lastHoveredNode = node; + _lastHoveredNode = nodeId; } @@ -44,8 +73,14 @@ void ConnectionState:: resetLastHoveredNode() { - if (_lastHoveredNode) - _lastHoveredNode->resetReactionToConnection(); + if (_lastHoveredNode != InvalidNodeId) + { + auto & ngo = *_cgo.nodeScene()->nodeGraphicsObject(_lastHoveredNode); + ngo.nodeState().resetReactionToConnection(); + } + + _lastHoveredNode = InvalidNodeId; +} + - _lastHoveredNode = nullptr; } diff --git a/src/ConnectionStyle.cpp b/src/ConnectionStyle.cpp index 91dc4d705..b0f45ab66 100644 --- a/src/ConnectionStyle.cpp +++ b/src/ConnectionStyle.cpp @@ -2,7 +2,6 @@ #include "StyleCollection.hpp" -#include #include #include #include @@ -45,12 +44,13 @@ setConnectionStyle(QString 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; \ - } + 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 @@ -63,7 +63,7 @@ setConnectionStyle(QString jsonText) #define CONNECTION_STYLE_READ_COLOR(values, variable) { \ auto valueRef = values[#variable]; \ CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ - if (CONNECTION_VALUE_EXISTS(valueRef)) {\ + if (CONNECTION_VALUE_EXISTS(valueRef)) { \ if (valueRef.isArray()) { \ auto colorArray = valueRef.toArray(); \ std::vector rgb; rgb.reserve(3); \ @@ -77,51 +77,37 @@ setConnectionStyle(QString jsonText) } \ } +#define CONNECTION_STYLE_WRITE_COLOR(values, variable) { \ + values[#variable] = variable.name(); \ +} + #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(); \ + variable = valueRef.toDouble(); \ +} + +#define CONNECTION_STYLE_WRITE_FLOAT(values, variable) { \ + values[#variable] = variable; \ } #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(); \ + 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()); +#define CONNECTION_STYLE_WRITE_BOOL(values, variable) { \ + values[#variable] = variable; \ } void ConnectionStyle:: -loadJsonText(QString jsonText) +loadJson(QJsonDocument const & json) { - loadJsonFromByteArray(jsonText.toUtf8()); -} - - -void -ConnectionStyle:: -loadJsonFromByteArray(QByteArray const &byteArray) -{ - QJsonDocument json(QJsonDocument::fromJson(byteArray)); - QJsonObject topLevelObject = json.object(); QJsonValueRef nodeStyleValues = topLevelObject["ConnectionStyle"]; @@ -142,6 +128,31 @@ loadJsonFromByteArray(QByteArray const &byteArray) } +QJsonDocument +ConnectionStyle:: +toJson() const +{ + QJsonObject obj; + + CONNECTION_STYLE_WRITE_COLOR(obj, ConstructionColor); + CONNECTION_STYLE_WRITE_COLOR(obj, NormalColor); + CONNECTION_STYLE_WRITE_COLOR(obj, SelectedColor); + CONNECTION_STYLE_WRITE_COLOR(obj, SelectedHaloColor); + CONNECTION_STYLE_WRITE_COLOR(obj, HoveredColor); + + CONNECTION_STYLE_WRITE_FLOAT(obj, LineWidth); + CONNECTION_STYLE_WRITE_FLOAT(obj, ConstructionLineWidth); + CONNECTION_STYLE_WRITE_FLOAT(obj, PointDiameter); + + CONNECTION_STYLE_WRITE_BOOL(obj, UseDataDefinedColors); + + QJsonObject root; + root["ConnectionStyle"] = obj; + + return QJsonDocument(root); +} + + QColor ConnectionStyle:: constructionColor() const diff --git a/src/DataFlowGraphModel.cpp b/src/DataFlowGraphModel.cpp new file mode 100644 index 000000000..44ec97c2a --- /dev/null +++ b/src/DataFlowGraphModel.cpp @@ -0,0 +1,409 @@ +#include "DataFlowGraphModel.hpp" + +namespace QtNodes +{ + + +DataFlowGraphModel:: +DataFlowGraphModel(std::shared_ptr registry) + : _registry(registry) + , _nextNodeId{0} +{} + + +std::unordered_set +DataFlowGraphModel:: +allNodeIds() const +{ + std::unordered_set nodeIds; + for_each(_models.begin(), _models.end(), + [&nodeIds](auto const &p) + { + nodeIds.insert(p.first); + + }); + + return nodeIds; +} + + +std::unordered_set> +DataFlowGraphModel:: +connectedNodes(NodeId nodeId, + PortType portType, + PortIndex portIndex) const +{ + std::unordered_set> result; + + auto const key = std::make_tuple(nodeId, portType, portIndex); + + auto it = _connectivity.find(key); + + if (it != _connectivity.end()) + result = it->second; + + return result; +} + + +NodeId +DataFlowGraphModel:: +addNode(QString const nodeType) +{ + std::unique_ptr model = + _registry->create(nodeType); + + if (model) + { + NodeId newId = newNodeId(); + + connect(model.get(), &NodeDataModel::dataUpdated, + [newId, this](PortIndex const portIndex) + { onNodeDataUpdated(newId, portIndex); }); + + _models[newId] = std::move(model); + + Q_EMIT nodeCreated(newId); + + return newId; + } + + return InvalidNodeId; +} + + +void +DataFlowGraphModel:: +addConnection(ConnectionId const connectionId) +{ + auto connect = + [&](PortType portType) + { + auto key = std::make_tuple(getNodeId(portType, connectionId), + portType, + getPortIndex(portType, connectionId)); + + PortType opposite = oppositePort(portType); + + _connectivity[key].insert(std::make_pair(getNodeId(opposite, connectionId), + getPortIndex(opposite, connectionId))); + }; + + connect(PortType::Out); + connect(PortType::In); + + Q_EMIT connectionCreated(connectionId); + + onNodeDataUpdated(getNodeId(PortType::Out, connectionId), + getPortIndex(PortType::Out, connectionId)); +} + + +QVariant +DataFlowGraphModel:: +nodeData(NodeId nodeId, NodeRole role) const +{ + QVariant result; + + auto it = _models.find(nodeId); + if (it == _models.end()) + return result; + + auto &model = it->second; + + switch (role) + { + case NodeRole::Type: + result = model->name(); + break; + + case NodeRole::Position: + result = _nodeGeometryData[nodeId].pos; + break; + + case NodeRole::Size: + result = _nodeGeometryData[nodeId].size; + break; + + case NodeRole::CaptionVisible: + result = model->captionVisible(); + break; + + case NodeRole::Caption: + result = model->caption(); + break; + + case NodeRole::Style: + { + auto style = StyleCollection::nodeStyle(); + result = style.toJson().toVariant(); + } + break; + + case NodeRole::NumberOfInPorts: + result = model->nPorts(PortType::In); + break; + + case NodeRole::NumberOfOutPorts: + result = model->nPorts(PortType::Out); + break; + + case NodeRole::Widget: + { + auto w = model->embeddedWidget(); + result = QVariant::fromValue(w); + } + break; + } + + return result; +} + + +NodeFlags +DataFlowGraphModel:: +nodeFlags(NodeId nodeId) const +{ + auto it = _models.find(nodeId); + + if (it != _models.end() && it->second->resizable()) + return NodeFlag::Resizable; + + return GraphModel::nodeFlags(nodeId); +} + + +bool +DataFlowGraphModel:: +setNodeData(NodeId nodeId, + NodeRole role, + QVariant value) +{ + Q_UNUSED(nodeId); + Q_UNUSED(role); + Q_UNUSED(value); + + bool result = false; + + switch (role) + { + case NodeRole::Type: + break; + case NodeRole::Position: + { + _nodeGeometryData[nodeId].pos = value.value(); + + Q_EMIT nodePositonUpdated(nodeId); + + result = true; + } + break; + + case NodeRole::Size: + { + _nodeGeometryData[nodeId].size = value.value(); + result = true; + } + break; + + case NodeRole::CaptionVisible: + break; + + case NodeRole::Caption: + break; + + case NodeRole::Style: + break; + + case NodeRole::NumberOfInPorts: + break; + + case NodeRole::NumberOfOutPorts: + break; + + case NodeRole::Widget: + break; + } + + return result; +} + + +QVariant +DataFlowGraphModel:: +portData(NodeId nodeId, + PortType portType, + PortIndex portIndex, + PortRole role) const +{ + QVariant result; + + auto it = _models.find(nodeId); + if (it == _models.end()) + return result; + + auto &model = it->second; + + switch (role) + { + case PortRole::Data: + if (portType == PortType::Out) + result = QVariant::fromValue(model->outData(portIndex)); + break; + + case PortRole::DataType: + result = QVariant::fromValue(model->dataType(portType, portIndex)); + break; + + case PortRole::ConnectionPolicy: + if (portType == PortType::Out) + result = QVariant::fromValue(model->portOutConnectionPolicy(portIndex)); + else + result = QVariant::fromValue(ConnectionPolicy::One); + break; + + case PortRole::CaptionVisible: + result = model->portCaptionVisible(portType, portIndex); + break; + + case PortRole::Caption: + result = model->portCaption(portType, portIndex); + + break; + } + + return result; +} + + +bool +DataFlowGraphModel:: +setPortData(NodeId nodeId, + PortType portType, + PortIndex portIndex, + PortRole role) const +{ + Q_UNUSED(nodeId); + Q_UNUSED(portType); + Q_UNUSED(portIndex); + Q_UNUSED(role); + + return false; +} + + +bool +DataFlowGraphModel:: +deleteConnection(ConnectionId const connectionId) +{ + bool disconnected = false; + + auto disconnect = + [&](PortType portType) + { + auto key = std::make_tuple(getNodeId(portType, connectionId), + portType, + getPortIndex(portType, connectionId)); + auto it = _connectivity.find(key); + + if (it != _connectivity.end()) + { + disconnected = true; + + PortType opposite = oppositePort(portType); + + auto oppositePair = + std::make_pair(getNodeId(opposite, connectionId), + getPortIndex(opposite, connectionId)); + it->second.erase(oppositePair); + + if (it->second.empty()) + { + _connectivity.erase(it); + } + } + + }; + + disconnect(PortType::Out); + disconnect(PortType::In); + + if (disconnected) + { + Q_EMIT connectionDeleted(connectionId); + + propagateEmptyDataTo(getNodeId(PortType::In, connectionId), + getPortIndex(PortType::In, connectionId)); + + } + + return disconnected; +} + + +bool +DataFlowGraphModel:: +deleteNode(NodeId const nodeId) +{ + // Delete connections to this node first. + auto connectionIds = allConnectionIds(nodeId); + for (auto &cId : connectionIds) + { + deleteConnection(cId); + } + + _nodeGeometryData.erase(nodeId); + _models.erase(nodeId); + + Q_EMIT nodeDeleted(nodeId); + + return true; +} + + +void +DataFlowGraphModel:: +onNodeDataUpdated(NodeId const nodeId, + PortIndex const portIndex) +{ + + auto const &connected = + connectedNodes(nodeId, PortType::Out, portIndex); + + // TODO: Should we pull the data through the model? + /* + auto outPortData = + portData(nodeId, + PortType::Out, + portIndex, + PortRole::Data).value>(); + */ + + auto const outPortData = + _models[nodeId]->outData(portIndex); + + for (auto const &cn : connected) + { + _models[cn.first]->setInData(outPortData, cn.second); + + Q_EMIT portDataSet(cn.first, PortType::In, cn.second); + } +} + + +void + +DataFlowGraphModel:: +propagateEmptyDataTo(NodeId const nodeId, + PortIndex const portIndex) +{ + auto const emptyData = std::shared_ptr(); + + _models[nodeId]->setInData(emptyData, portIndex); + + Q_EMIT portDataSet(nodeId, PortType::In, portIndex); +} + + +} + diff --git a/src/DataFlowGraphicsScene.cpp b/src/DataFlowGraphicsScene.cpp new file mode 100644 index 000000000..32d2d079b --- /dev/null +++ b/src/DataFlowGraphicsScene.cpp @@ -0,0 +1,389 @@ +#include "DataFlowGraphicsScene.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ConnectionGraphicsObject.hpp" +#include "DataModelRegistry.hpp" +#include "GraphicsView.hpp" +#include "NodeGeometry.hpp" +#include "NodeGraphicsObject.hpp" + +namespace QtNodes +{ + + +DataFlowGraphicsScene:: +DataFlowGraphicsScene(DataFlowGraphModel &graphModel, + QObject * parent) + : BasicGraphicsScene(graphModel, parent) + , _graphModel(graphModel) +{ + connect(&_graphModel, &GraphModel::portDataSet, + this, &DataFlowGraphicsScene::onPortDataSet); +} + + +// TODO constructor for an empyt scene? +// TODO constructor for an empyt scene? +// TODO constructor for an empyt scene? +// TODO constructor for an empyt scene? + + +//--------------------------------------------------------------------- + +std::vector +DataFlowGraphicsScene:: +selectedNodes() const +{ + QList graphicsItems = selectedItems(); + + std::vector result; + result.reserve(graphicsItems.size()); + + for (QGraphicsItem* item : graphicsItems) + { + auto ngo = qgraphicsitem_cast(item); + + if (ngo != nullptr) + { + result.push_back(ngo->nodeId()); + } + } + + return result; +} + + +QMenu * +DataFlowGraphicsScene:: +createSceneMenu(QPointF const scenePos) +{ + QMenu * modelMenu = new QMenu(); + + 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); + + // 1. + modelMenu->addAction(txtBoxAction); + + // Add result treeview to the context menu + QTreeWidget *treeView = new QTreeWidget(modelMenu); + treeView->header()->close(); + + auto *treeViewAction = new QWidgetAction(modelMenu); + treeViewAction->setDefaultWidget(treeView); + + // 2. + modelMenu->addAction(treeViewAction); + + auto registry = _graphModel.dataModelRegistry(); + + QMap topLevelItems; + for (auto const &cat : registry->categories()) + { + auto item = new QTreeWidgetItem(treeView); + item->setText(0, cat); + item->setData(0, Qt::UserRole, skipText); + topLevelItems[cat] = item; + } + + for (auto const &assoc : 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, + [this, + modelMenu, + skipText, + scenePos](QTreeWidgetItem *item, int) + { + QString modelName = item->data(0, Qt::UserRole).toString(); + + if (modelName == skipText) + { + return; + } + + NodeId nodeId = this->_graphModel.addNode(modelName); + + if (nodeId != InvalidNodeId) + { + _graphModel.setNodeData(nodeId, + NodeRole::Position, + scenePos); + } + + 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(); + + // QMenu's instance auto-destruction + modelMenu->setAttribute(Qt::WA_DeleteOnClose); + + return modelMenu; +} + + +#if 0 +std::shared_ptr +DataFlowGraphicsScene:: +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; +} + + +Node & +DataFlowGraphicsScene:: +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(); + _nodes[node->id()] = std::move(node); + + nodePlaced(*nodePtr); + nodeCreated(*nodePtr); + return *nodePtr; +} + + +#endif + +void +DataFlowGraphicsScene:: +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 +DataFlowGraphicsScene:: +load() +{ + //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; + + //clearScene(); + + //QByteArray wholeFile = file.readAll(); + + //loadFromMemory(wholeFile); +} + + +#if 0 + + +QByteArray +DataFlowGraphicsScene:: +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 +DataFlowGraphicsScene:: +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()); + } +} + + +#endif + +void +DataFlowGraphicsScene:: +onPortDataSet(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex) +{ + auto node = nodeGraphicsObject(nodeId); + + if (node) + { + node->setGeometryChanged(); + + NodeGeometry geometry(*node); + geometry.recalculateSize(); + + node->update(); + node->moveConnections(); + } +} + + +} diff --git a/src/DataModelRegistry.cpp b/src/DataModelRegistry.cpp index 4a8b202d8..e66fc33ea 100644 --- a/src/DataModelRegistry.cpp +++ b/src/DataModelRegistry.cpp @@ -6,7 +6,6 @@ using QtNodes::DataModelRegistry; using QtNodes::NodeDataModel; using QtNodes::NodeDataType; -using QtNodes::TypeConverter; std::unique_ptr DataModelRegistry:: @@ -45,21 +44,3 @@ 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{}; -} diff --git a/src/FlowScene.cpp b/src/FlowScene.cpp deleted file mode 100644 index dd4306df1..000000000 --- a/src/FlowScene.cpp +++ /dev/null @@ -1,664 +0,0 @@ -#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(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 const& 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(); - _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(); - _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() -{ - 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; - - clearScene(); - - 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); -} - - -//------------------------------------------------------------------------------ -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; -} -} diff --git a/src/GraphModel.cpp b/src/GraphModel.cpp new file mode 100644 index 000000000..33bf1bb9b --- /dev/null +++ b/src/GraphModel.cpp @@ -0,0 +1,314 @@ +#include "GraphModel.hpp" + +#include +#include +#include +#include + +#include "ConnectionIdHash.hpp" +#include "ConnectionIdUtils.hpp" +#include "NodeData.hpp" +#include "StyleCollection.hpp" + + +Q_DECLARE_METATYPE(QWidget*) + +namespace QtNodes +{ + +std::unordered_set +GraphModel:: +allNodeIds() const +{ + std::unordered_set r = {1u, }; + + return r; +} + + +std::unordered_set +GraphModel:: +allConnectionIds(NodeId const nodeId) const +{ + std::unordered_set result; + + unsigned int nIn = + nodeData(nodeId, NodeRole::NumberOfInPorts).toUInt(); + + for (PortIndex i = 0; i < nIn; ++i) + { + auto const & connections = + connectedNodes(nodeId, PortType::In, i); + + for (auto & cn : connections) + { + result.insert(std::make_tuple(cn.first, cn.second, nodeId, i)); + } + + } + + unsigned int nOut = + nodeData(nodeId, NodeRole::NumberOfOutPorts).toUInt(); + + for (PortIndex i = 0; i < nOut; ++i) + { + auto const & connections = + connectedNodes(nodeId, PortType::Out, i); + + for (auto & cn : connections) + { + result.insert(std::make_tuple(nodeId, i, cn.first, cn.second)); + } + } + + return result; +} + + +std::unordered_set> +GraphModel:: +connectedNodes(NodeId nodeId, + PortType portType, + PortIndex portIndex) const +{ + Q_UNUSED(nodeId); + Q_UNUSED(portType); + Q_UNUSED(portIndex); + + // No connected nodes in the default implementation. + return std::unordered_set>(); +} + + +bool +GraphModel:: +connectionExists(ConnectionId const connectionId) const +{ + Q_UNUSED(connectionId); + + return false; +} + + +NodeId +GraphModel:: +addNode(QString const nodeType) +{ + return InvalidNodeId; +} + + +bool +GraphModel:: +connectionPossible(ConnectionId const connectionId) +{ + NodeId nodeIdIn = getNodeId(PortType::In, connectionId); + PortIndex portIndexIn = getPortIndex(PortType::In, connectionId); + + NodeDataType typeIn = + portData(nodeIdIn, + PortType::In, + portIndexIn, + PortRole::DataType).value(); + + NodeId nodeIdOut = getNodeId(PortType::Out, connectionId); + PortIndex portIndexOut = getPortIndex(PortType::Out, connectionId); + + NodeDataType typeOut = + portData(nodeIdOut, + PortType::Out, + portIndexOut, + PortRole::DataType).value(); + + return (typeIn.id == typeOut.id); +} + + +void +GraphModel:: +addConnection(ConnectionId const connectionId) +{ + Q_UNUSED(connectionId); + + Q_EMIT connectionCreated(connectionId); +} + + +bool +GraphModel:: +nodeExists(NodeId const nodeId) const +{ + Q_UNUSED(nodeId); + + return false; +} + + +QVariant +GraphModel:: +nodeData(NodeId nodeId, NodeRole role) const +{ + Q_UNUSED(nodeId); + + QVariant result; + + switch (role) + { + case NodeRole::Type: + result = QString("Default Node Type"); + break; + + case NodeRole::Position: + result = QPointF(0, 0); // _position; + break; + + case NodeRole::Size: + result = QSize(100, 100); + break; + + case NodeRole::CaptionVisible: + result = true; + break; + + case NodeRole::Caption: + result = QString("Node"); + break; + + case NodeRole::Style: + { + auto style = StyleCollection::nodeStyle(); + result = style.toJson().toVariant(); + } + break; + + case NodeRole::NumberOfInPorts: + result = 1u; + break; + + case NodeRole::NumberOfOutPorts: + result = 1u; + break; + + case NodeRole::Widget: + result = QVariant(); + break; + } + + return result; +} + + +NodeFlags +GraphModel:: +nodeFlags(NodeId nodeId) const +{ + Q_UNUSED(nodeId); + + return NodeFlag::NoFlags; +} + + +bool +GraphModel:: +setNodeData(NodeId nodeId, NodeRole role, QVariant value) +{ + Q_UNUSED(nodeId); + Q_UNUSED(role); + Q_UNUSED(value); + + return false; +} + + +QVariant +GraphModel:: +portData(NodeId nodeId, + PortType portType, + PortIndex portIndex, + PortRole role) const +{ + Q_UNUSED(nodeId); + Q_UNUSED(portIndex); + + switch (role) + { + case PortRole::Data: + return QVariant(); + break; + + case PortRole::DataType: + return QVariant(); + break; + + case PortRole::ConnectionPolicy: + return QVariant::fromValue(ConnectionPolicy::One); + break; + + case PortRole::CaptionVisible: + return true; + break; + + case PortRole::Caption: + if (portType == PortType::In) + return QString::fromUtf8("Port In"); + else + return QString::fromUtf8("Port Out"); + + break; + } + + return QVariant(); +} + + +bool +GraphModel:: +setPortData(NodeId nodeId, + PortType portType, + PortIndex portIndex, + PortRole role) const +{ + Q_UNUSED(nodeId); + Q_UNUSED(portType); + Q_UNUSED(portIndex); + Q_UNUSED(role); + + return false; +} + + +bool +GraphModel:: +deleteConnection(ConnectionId const connectionId) +{ + Q_UNUSED(connectionId); + + if (connectionExists(connectionId)) + { + Q_EMIT connectionDeleted(connectionId); + + return true; + } + + return false; +} + + +bool +GraphModel:: +deleteNode(NodeId const nodeId) +{ + Q_UNUSED(nodeId); + + if (nodeExists(nodeId)) + { + Q_EMIT nodeDeleted(nodeId); + + return true; + } + + return false; +} + + +} diff --git a/src/FlowView.cpp b/src/GraphicsView.cpp similarity index 55% rename from src/FlowView.cpp rename to src/GraphicsView.cpp index d7f768361..d13720248 100644 --- a/src/FlowView.cpp +++ b/src/GraphicsView.cpp @@ -1,4 +1,4 @@ -#include "FlowView.hpp" +#include "GraphicsView.hpp" #include @@ -8,30 +8,27 @@ #include #include +#include #include #include -#include #include #include -#include "FlowScene.hpp" -#include "DataModelRegistry.hpp" -#include "Node.hpp" -#include "NodeGraphicsObject.hpp" #include "ConnectionGraphicsObject.hpp" +#include "NodeGraphicsObject.hpp" +#include "BasicGraphicsScene.hpp" #include "StyleCollection.hpp" -using QtNodes::FlowView; -using QtNodes::FlowScene; +using QtNodes::GraphicsView; +using QtNodes::BasicGraphicsScene; -FlowView:: -FlowView(QWidget *parent) +GraphicsView:: +GraphicsView(QWidget *parent) : QGraphicsView(parent) , _clearSelectionAction(Q_NULLPTR) , _deleteSelectionAction(Q_NULLPTR) - , _scene(Q_NULLPTR) { setDragMode(QGraphicsView::ScrollHandDrag); setRenderHint(QPainter::Antialiasing); @@ -52,16 +49,16 @@ FlowView(QWidget *parent) } -FlowView:: -FlowView(FlowScene *scene, QWidget *parent) - : FlowView(parent) +GraphicsView:: +GraphicsView(BasicGraphicsScene *scene, QWidget *parent) + : GraphicsView(parent) { setScene(scene); } QAction* -FlowView:: +GraphicsView:: clearSelectionAction() const { return _clearSelectionAction; @@ -69,7 +66,7 @@ clearSelectionAction() const QAction* -FlowView:: +GraphicsView:: deleteSelectionAction() const { return _deleteSelectionAction; @@ -77,134 +74,72 @@ deleteSelectionAction() const void -FlowView::setScene(FlowScene *scene) +GraphicsView:: +setScene(BasicGraphicsScene *scene) { - _scene = scene; - QGraphicsView::setScene(_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); + 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); + connect(_deleteSelectionAction, &QAction::triggered, + this, &GraphicsView::deleteSelectedObjects); addAction(_deleteSelectionAction); } void -FlowView:: -contextMenuEvent(QContextMenuEvent *event) +GraphicsView:: +centerScene() { - if (itemAt(event->pos())) + if (scene()) { - 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(); + scene()->setSceneRect(QRectF()); - auto *treeViewAction = new QWidgetAction(&modelMenu); - treeViewAction->setDefaultWidget(treeView); + QRectF sceneRect = scene()->sceneRect(); - 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) + if (sceneRect.width() > this->rect().width() || + sceneRect.height() > this->rect().height()) { - return; + fitInView(sceneRect, Qt::KeepAspectRatio); } - auto type = _scene->registry().create(modelName); - - if (type) - { - auto& node = _scene->createNode(std::move(type)); - - QPoint pos = event->pos(); + centerOn(sceneRect.center()); + } +} - QPointF posView = this->mapToScene(pos); - node.nodeGraphicsObject().setPos(posView); +void +GraphicsView:: +contextMenuEvent(QContextMenuEvent *event) +{ + if (itemAt(event->pos())) + { + QGraphicsView::contextMenuEvent(event); + return; + } - _scene->nodePlaced(node); - } - else - { - qDebug() << "Model not found"; - } + auto const scenePos = mapToScene(event->pos()); - modelMenu.close(); - }); + QMenu * menu = nodeScene()->createSceneMenu(scenePos); - //Setup filtering - connect(txtBox, &QLineEdit::textChanged, [&](const QString &text) + if (menu) { - 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()); + menu->exec(event->globalPos()); + } } void -FlowView:: +GraphicsView:: wheelEvent(QWheelEvent *event) { QPoint delta = event->angleDelta(); @@ -225,7 +160,7 @@ wheelEvent(QWheelEvent *event) void -FlowView:: +GraphicsView:: scaleUp() { double const step = 1.2; @@ -241,7 +176,7 @@ scaleUp() void -FlowView:: +GraphicsView:: scaleDown() { double const step = 1.2; @@ -252,32 +187,37 @@ scaleDown() void -FlowView:: -deleteSelectedNodes() +GraphicsView:: +deleteSelectedObjects() { // 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()) + // 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()); + { + nodeScene()->graphModel().deleteConnection(c->connectionId()); + } } // 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()) + // 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()); + { + nodeScene()->graphModel().deleteNode(n->nodeId()); + } } } void -FlowView:: +GraphicsView:: keyPressEvent(QKeyEvent *event) { switch (event->key()) @@ -295,7 +235,7 @@ keyPressEvent(QKeyEvent *event) void -FlowView:: +GraphicsView:: keyReleaseEvent(QKeyEvent *event) { switch (event->key()) @@ -312,7 +252,7 @@ keyReleaseEvent(QKeyEvent *event) void -FlowView:: +GraphicsView:: mousePressEvent(QMouseEvent *event) { QGraphicsView::mousePressEvent(event); @@ -324,7 +264,7 @@ mousePressEvent(QMouseEvent *event) void -FlowView:: +GraphicsView:: mouseMoveEvent(QMouseEvent *event) { QGraphicsView::mouseMoveEvent(event); @@ -341,28 +281,28 @@ mouseMoveEvent(QMouseEvent *event) void -FlowView:: -drawBackground(QPainter* painter, const QRectF& r) +GraphicsView:: +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()); + 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); + 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 ); + xi * gridStep, top * gridStep); painter->drawLine(line); } @@ -371,15 +311,13 @@ drawBackground(QPainter* painter, const QRectF& r) for (int yi = int(bottom); yi <= int(top); ++yi) { QLineF line(left * gridStep, yi * gridStep, - right * 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); @@ -393,17 +331,20 @@ drawBackground(QPainter* painter, const QRectF& r) void -FlowView:: +GraphicsView:: showEvent(QShowEvent *event) { - _scene->setSceneRect(this->rect()); QGraphicsView::showEvent(event); + + scene()->setSceneRect(this->rect()); + centerScene(); } -FlowScene * -FlowView:: -scene() +BasicGraphicsScene * +GraphicsView:: +nodeScene() { - return _scene; + return dynamic_cast(scene()); } + diff --git a/src/FlowViewStyle.cpp b/src/GraphicsViewStyle.cpp similarity index 59% rename from src/FlowViewStyle.cpp rename to src/GraphicsViewStyle.cpp index e08cf4fcf..d15f9cb16 100644 --- a/src/FlowViewStyle.cpp +++ b/src/GraphicsViewStyle.cpp @@ -1,4 +1,4 @@ -#include "FlowViewStyle.hpp" +#include "GraphicsViewStyle.hpp" #include #include @@ -6,16 +6,15 @@ #include #include -#include - #include "StyleCollection.hpp" -using QtNodes::FlowViewStyle; +using QtNodes::GraphicsViewStyle; -inline void initResources() { Q_INIT_RESOURCE(resources); } +inline void +initResources() { Q_INIT_RESOURCE(resources); } -FlowViewStyle:: -FlowViewStyle() +GraphicsViewStyle:: +GraphicsViewStyle() { // Explicit resources inialization for preventing the static initialization // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order @@ -26,29 +25,29 @@ FlowViewStyle() } -FlowViewStyle:: -FlowViewStyle(QString jsonText) +GraphicsViewStyle:: +GraphicsViewStyle(QString jsonText) { loadJsonText(jsonText); } void -FlowViewStyle:: +GraphicsViewStyle:: setStyle(QString jsonText) { - FlowViewStyle style(jsonText); + GraphicsViewStyle style(jsonText); - StyleCollection::setFlowViewStyle(style); + StyleCollection::setGraphicsViewStyle(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; \ - } + 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 @@ -68,40 +67,18 @@ setStyle(QString jsonText) } \ } -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()); +#define FLOW_VIEW_STYLE_WRITE_COLOR(values, variable) { \ + values[#variable] = variable.name(); \ } void -FlowViewStyle:: -loadJsonFromByteArray(QByteArray const &byteArray) +GraphicsViewStyle:: +loadJson(QJsonDocument const & json) { - QJsonDocument json(QJsonDocument::fromJson(byteArray)); - QJsonObject topLevelObject = json.object(); - QJsonValueRef nodeStyleValues = topLevelObject["FlowViewStyle"]; + QJsonValueRef nodeStyleValues = topLevelObject["GraphicsViewStyle"]; QJsonObject obj = nodeStyleValues.toObject(); @@ -109,3 +86,20 @@ loadJsonFromByteArray(QByteArray const &byteArray) FLOW_VIEW_STYLE_READ_COLOR(obj, FineGridColor); FLOW_VIEW_STYLE_READ_COLOR(obj, CoarseGridColor); } + + +QJsonDocument +GraphicsViewStyle:: +toJson() const +{ + QJsonObject obj; + + FLOW_VIEW_STYLE_WRITE_COLOR(obj, BackgroundColor); + FLOW_VIEW_STYLE_WRITE_COLOR(obj, FineGridColor); + FLOW_VIEW_STYLE_WRITE_COLOR(obj, CoarseGridColor); + + QJsonObject root; + root["GraphicsViewStyle"] = obj; + + return QJsonDocument(root); +} diff --git a/src/Node.cpp b/src/Node.cpp deleted file mode 100644 index 05571c40d..000000000 --- a/src/Node.cpp +++ /dev/null @@ -1,251 +0,0 @@ -#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::NodeDataType; -using QtNodes::NodeDataModel; -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::dataInvalidated, - this, &Node::onDataInvalidated); - - connect(_nodeDataModel.get(), &NodeDataModel::embeddedWidgetSizeUpdated, - this, &Node::onNodeSizeUpdated ); -} - - -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 p = t.inverted().map(scenePoint); - - _nodeGeometry.setDraggingPosition(p); - - _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(std::shared_ptr nodeData, - PortIndex inPortIndex, - const QUuid& connectionId) const -{ - _nodeDataModel->setInData(std::move(nodeData), inPortIndex, connectionId); - - // 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(); -} - - -void -Node:: -onDataUpdated(PortIndex index) -{ - auto nodeData = _nodeDataModel->outData(index); - - auto const &connections = - _nodeState.connections(PortType::Out, index); - - for (auto const & c : connections) - c.second->propagateData(nodeData); -} - - -void -Node:: -onDataInvalidated(PortIndex index) -{ - auto const &connections = - _nodeState.connections(PortType::Out, index); - - for (auto const & c : connections) - c.second->propagateEmptyData(); -} - -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(); - } - } - } -} diff --git a/src/NodeConnectionInteraction.cpp b/src/NodeConnectionInteraction.cpp index 82689f4dc..7c3dcab66 100644 --- a/src/NodeConnectionInteraction.cpp +++ b/src/NodeConnectionInteraction.cpp @@ -1,57 +1,59 @@ #include "NodeConnectionInteraction.hpp" +#include + +#include + #include "ConnectionGraphicsObject.hpp" +#include "ConnectionIdUtils.hpp" +#include "NodeGeometry.hpp" #include "NodeGraphicsObject.hpp" -#include "NodeDataModel.hpp" -#include "DataModelRegistry.hpp" -#include "FlowScene.hpp" +#include "BasicGraphicsScene.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; +namespace QtNodes +{ NodeConnectionInteraction:: -NodeConnectionInteraction(Node& node, Connection& connection, FlowScene& scene) - : _node(&node) - , _connection(&connection) - , _scene(&scene) +NodeConnectionInteraction(NodeGraphicsObject & ngo, + ConnectionGraphicsObject & cgo, + BasicGraphicsScene & scene) + : _ngo(ngo) + , _cgo(cgo) + , _scene(scene) {} bool NodeConnectionInteraction:: -canConnect(PortIndex &portIndex, TypeConverter & converter) const +canConnect(PortIndex * portIndex) const { // 1) Connection requires a port - PortType requiredPort = connectionRequiredPort(); - + PortType requiredPort = _cgo.connectionState().requiredPort(); if (requiredPort == PortType::None) { return false; } + NodeId connectedNodeId = + getNodeId(oppositePort(requiredPort), _cgo.connectionId()); + // 1.5) Forbid connecting the node to itself - Node* node = _connection->getNode(oppositePort(requiredPort)); - if (node == _node) + if (_ngo.nodeId() == connectedNodeId) return false; // 2) connection point is on top of the node port - QPointF connectionPoint = connectionEndScenePosition(requiredPort); + QPointF connectionPoint = + _cgo.sceneTransform().map(_cgo.endPoint(requiredPort)); - portIndex = nodePortIndexUnderScenePoint(requiredPort, - connectionPoint); + *portIndex = nodePortIndexUnderScenePoint(requiredPort, + connectionPoint); - if (portIndex == INVALID) + if (*portIndex == InvalidPortIndex) { return false; } @@ -59,32 +61,19 @@ canConnect(PortIndex &portIndex, TypeConverter & converter) const // 3) Node port is vacant // port should be empty - if (!nodePortIsEmpty(requiredPort, portIndex)) + 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 + // 4) Connection type equals node port type. - auto connectionDataType = - _connection->dataType(oppositePort(requiredPort)); + GraphModel & model = _ngo.nodeScene()->graphModel(); - auto const &modelTarget = _node->nodeDataModel(); - NodeDataType candidateNodeDataType = modelTarget->dataType(requiredPort, portIndex); + ConnectionId connectionId = + makeCompleteConnectionId(_cgo.connectionId(), // incomplete + _ngo.nodeId(), // missing node id + *portIndex); // missing port index - 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; + return model.connectionPossible(connectionId); } @@ -93,73 +82,83 @@ NodeConnectionInteraction:: tryConnect() const { // 1) Check conditions from 'canConnect' - PortIndex portIndex = INVALID; - TypeConverter converter; + //TypeConverter converter; - if (!canConnect(portIndex, converter)) + PortIndex targetPortIndex = InvalidPortIndex; + if (!canConnect(&targetPortIndex)) { 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); - } + // 1.5) If the connection is possible but a type conversion is + // needed, assign a convertor to connection + //if (converter) + //{ + //_connectionId->setTypeConverter(converter); + //} + + // 2) Create new connection + + ConnectionId incompleteConnectionId = _cgo.connectionId(); - // 2) Assign node to required port in Connection - PortType requiredPort = connectionRequiredPort(); - _node->nodeState().setConnection(requiredPort, - portIndex, - *_connection); + PortType requiredPort = _cgo.connectionState().requiredPort(); - // 3) Assign Connection to empty port in NodeState - // The port is not longer required after this function - _connection->setNodeToPort(*_node, requiredPort, portIndex); + ConnectionId newConnectionId = + makeCompleteConnectionId(incompleteConnectionId, + _ngo.nodeId(), + targetPortIndex); + + _ngo.nodeScene()->resetDraftConnection(); // 4) Adjust Connection geometry - _node->nodeGraphicsObject().moveConnections(); + //_ngo.moveConnections(); + _ngo.nodeState().resetReactionToConnection(); - // 5) Poke model to intiate data transfer + GraphModel & model = _ngo.nodeScene()->graphModel(); - auto outNode = _connection->getNode(PortType::Out); - if (outNode) - { - PortIndex outPortIndex = _connection->getPortIndex(PortType::Out); - outNode->onDataUpdated(outPortIndex); - } + model.addConnection(newConnectionId); 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); + ConnectionId connectionId = _cgo.connectionId(); + + _scene.graphModel().deleteConnection(connectionId); + + NodeGeometry nodeGeometry(_ngo); + + QPointF scenePos = + nodeGeometry.portScenePosition(portToDisconnect, + getPortIndex(portToDisconnect, + connectionId), + _ngo.sceneTransform()); - NodeState &state = _node->nodeState(); + // Converted to "draft" connection with the new incomplete id. + ConnectionId incompleteConnectionId = + makeIncompleteConnectionId(connectionId, portToDisconnect); - // clear pointer to Connection in the NodeState - state.getEntries(portToDisconnect)[portIndex].erase(_connection->id()); + auto const & draftConnection = + _scene.makeDraftConnection(incompleteConnectionId); - // 4) Propagate invalid data to IN node - _connection->propagateEmptyData(); + QPointF looseEndPos = draftConnection->mapFromScene(scenePos); - // clear Connection side - _connection->clearNode(portToDisconnect); + draftConnection->setEndPoint(portToDisconnect, looseEndPos); - _connection->setRequiredPort(portToDisconnect); + // Repaint connection points. + NodeId connectedNodeId = + getNodeId(oppositePort(portToDisconnect), connectionId); + _scene.nodeGraphicsObject(connectedNodeId)->update(); - _connection->getConnectionGraphicsObject().grabMouse(); + NodeId disconnectedNodeId = + getNodeId(portToDisconnect, connectionId); + _scene.nodeGraphicsObject(disconnectedNodeId)->update(); return true; } @@ -171,38 +170,22 @@ PortType NodeConnectionInteraction:: connectionRequiredPort() const { - auto const &state = _connection->connectionState(); + auto const &state = _cgo.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); + NodeGeometry geometry(_ngo); - NodeGraphicsObject& ngo = _node->nodeGraphicsObject(); + QPointF p = + geometry.portScenePosition(portType, portIndex, _ngo.sceneTransform()); - return ngo.sceneTransform().map(p); + return p; } @@ -211,12 +194,11 @@ NodeConnectionInteraction:: nodePortIndexUnderScenePoint(PortType portType, QPointF const & scenePoint) const { - NodeGeometry const &nodeGeom = _node->nodeGeometry(); + NodeGeometry geometry(_ngo); - QTransform sceneTransform = - _node->nodeGraphicsObject().sceneTransform(); + QTransform sceneTransform = _ngo.sceneTransform(); - PortIndex portIndex = nodeGeom.checkHitScenePoint(portType, + PortIndex portIndex = geometry.checkHitScenePoint(portType, scenePoint, sceneTransform); return portIndex; @@ -227,40 +209,23 @@ bool NodeConnectionInteraction:: nodePortIsEmpty(PortType portType, PortIndex portIndex) const { - NodeState const & nodeState = _node->nodeState(); - - auto const & entries = nodeState.getEntries(portType); - auto const & connections = entries[portIndex]; - if (connections.empty()) return true; - - // Check if the connection already exists connected to the respective - // input and output ports - auto sourcePortType = oppositePort(portType); - auto it = std::find_if(connections.begin(), connections.end(), - [this, sourcePortType](const auto& connection) - { - const auto* const currentConn = connection.second; - - assert(_connection->getNode(sourcePortType)); - assert(currentConn->getNode(sourcePortType)); - return _connection->getNode(sourcePortType) == currentConn->getNode(sourcePortType) && - _connection->getPortIndex(sourcePortType) == currentConn->getPortIndex(sourcePortType); - }); - if (it != connections.end()) - return false; + GraphModel const & model = _ngo.nodeScene()->graphModel(); + + auto const & connectedNodes = + model.connectedNodes(_ngo.nodeId(), portType, portIndex); + + if (connectedNodes.empty()) + return true; + + ConnectionPolicy const outPolicy = + model.portData(_ngo.nodeId(), + portType, + portIndex, + PortRole::ConnectionPolicy).value(); + + return (portType == PortType::Out && + outPolicy == ConnectionPolicy::Many); + +} - switch (portType) - { - case PortType::In: - { - const auto policy = _node->nodeDataModel()->portInConnectionPolicy(portIndex); - return policy == NodeDataModel::ConnectionPolicy::Many; - } - case PortType::Out: - { - const auto policy = _node->nodeDataModel()->portOutConnectionPolicy(portIndex); - return policy == NodeDataModel::ConnectionPolicy::Many; - } - default: return false; - } } diff --git a/src/NodeConnectionInteraction.hpp b/src/NodeConnectionInteraction.hpp index fca4e13e8..c5d966fff 100644 --- a/src/NodeConnectionInteraction.hpp +++ b/src/NodeConnectionInteraction.hpp @@ -1,14 +1,17 @@ #pragma once -#include "Node.hpp" -#include "Connection.hpp" +#include + +#include + +#include "Definitions.hpp" namespace QtNodes { -class DataModelRegistry; -class FlowScene; -class NodeDataModel; +class ConnectionGraphicsObject; +class NodeGraphicsObject; +class BasicGraphicsScene; /// Class performs various operations on the Node and Connection pair. /// An instance should be created on the stack and destroyed when @@ -16,20 +19,21 @@ class NodeDataModel; class NodeConnectionInteraction { public: - NodeConnectionInteraction(Node& node, - Connection& connection, - FlowScene& scene); + NodeConnectionInteraction(NodeGraphicsObject & ngo, + ConnectionGraphicsObject & cgo, + BasicGraphicsScene & 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; + //bool canConnect(PortIndex & portIndex, + //TypeConverter & converter) const; + + bool canConnect(PortIndex * portIndex) 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 @@ -49,7 +53,7 @@ class NodeConnectionInteraction QPointF connectionEndScenePosition(PortType) const; - QPointF nodePortScenePosition(PortType portType, + QPointF nodePortScenePosition(PortType portType, PortIndex portIndex) const; PortIndex nodePortIndexUnderScenePoint(PortType portType, @@ -59,10 +63,11 @@ class NodeConnectionInteraction private: - Node* _node; + NodeGraphicsObject & _ngo; - Connection* _connection; + ConnectionGraphicsObject & _cgo; - FlowScene* _scene; + BasicGraphicsScene & _scene; }; + } diff --git a/src/NodeDataModel.cpp b/src/NodeDataModel.cpp index 9737b345d..892a0a4b4 100644 --- a/src/NodeDataModel.cpp +++ b/src/NodeDataModel.cpp @@ -2,8 +2,8 @@ #include "StyleCollection.hpp" -using QtNodes::NodeDataModel; -using QtNodes::NodeStyle; +namespace QtNodes +{ NodeDataModel:: NodeDataModel() @@ -25,7 +25,7 @@ save() const } -NodeStyle const& +NodeStyle const & NodeDataModel:: nodeStyle() const { @@ -35,7 +35,10 @@ nodeStyle() const void NodeDataModel:: -setNodeStyle(NodeStyle const& style) +setNodeStyle(NodeStyle const &style) { _nodeStyle = style; } + + +} diff --git a/src/NodeGeometry.cpp b/src/NodeGeometry.cpp index 91d90dc66..bd5e436a2 100644 --- a/src/NodeGeometry.cpp +++ b/src/NodeGeometry.cpp @@ -1,128 +1,149 @@ #include "NodeGeometry.hpp" -#include "PortType.hpp" -#include "NodeState.hpp" -#include "NodeDataModel.hpp" -#include "Node.hpp" +#include "Definitions.hpp" +#include "GraphModel.hpp" #include "NodeGraphicsObject.hpp" #include "StyleCollection.hpp" -#include +#include #include #include -using QtNodes::NodeGeometry; -using QtNodes::NodeDataModel; -using QtNodes::PortIndex; -using QtNodes::PortType; -using QtNodes::Node; +namespace QtNodes +{ NodeGeometry:: -NodeGeometry(std::unique_ptr const &dataModel) - : _width(100) - , _height(150) - , _inputPortWidth(70) - , _outputPortWidth(70) +NodeGeometry(NodeGraphicsObject const &ngo) + : _ngo(ngo) + , _graphModel(ngo.graphModel()) + , _defaultInPortWidth(70) + , _defaultOutPortWidth(70) , _entryHeight(20) - , _spacing(20) - , _hovered(false) - , _nSources(dataModel->nPorts(PortType::Out)) - , _nSinks(dataModel->nPorts(PortType::In)) - , _draggingPos(-1000, -1000) - , _dataModel(dataModel) + , _verticalSpacing(20) , _fontMetrics(QFont()) , _boldFontMetrics(QFont()) { QFont f; f.setBold(true); _boldFontMetrics = QFontMetrics(f); + + _entryHeight = _fontMetrics.height(); } + unsigned int -NodeGeometry::nSources() const +NodeGeometry:: +entryHeight() const { - return _dataModel->nPorts(PortType::Out); + return _entryHeight; } + unsigned int -NodeGeometry::nSinks() const +NodeGeometry:: +verticalSpacing() const { - return _dataModel->nPorts(PortType::In); + return _verticalSpacing; } -QRectF -NodeGeometry:: -entryBoundingRect() const -{ - double const addon = 0.0; - return QRectF(0 - addon, - 0 - addon, - _entryWidth + 2 * addon, - _entryHeight + 2 * addon); -} +//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 { + NodeId nodeId = _ngo.nodeId(); + + auto const &style = _graphModel.nodeData(nodeId, NodeRole::Style); auto const &nodeStyle = StyleCollection::nodeStyle(); double addon = 4 * nodeStyle.ConnectionPointDiameter; + QSize size = + _graphModel.nodeData(_ngo.nodeId(), NodeRole::Size).value(); + return QRectF(0 - addon, 0 - addon, - _width + 2 * addon, - _height + 2 * addon); + size.width() + 2 * addon, + size.height() + 2 * addon); +} + + +QSize +NodeGeometry:: +size() const +{ + QSize size = + _graphModel.nodeData(_ngo.nodeId(), NodeRole::Size).value(); + + return size; } -void +QSize NodeGeometry:: recalculateSize() const { - _entryHeight = _fontMetrics.height(); + NodeId nodeId = _ngo.nodeId(); + unsigned int height = 0; { - unsigned int maxNumOfEntries = std::max(_nSinks, _nSources); - unsigned int step = _entryHeight + _spacing; - _height = step * maxNumOfEntries; + unsigned int nInPorts = + _graphModel.nodeData(nodeId, + NodeRole::NumberOfInPorts).toUInt(); + + unsigned int nOutPorts = + _graphModel.nodeData(nodeId, + NodeRole::NumberOfOutPorts).toUInt(); + + unsigned int maxNumOfEntries = std::max(nInPorts, nOutPorts); + unsigned int step = _entryHeight + _verticalSpacing; + height = step * maxNumOfEntries; } - if (auto w = _dataModel->embeddedWidget()) + if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget).value()) { - _height = std::max(_height, static_cast(w->height())); + height = std::max(height, static_cast(w->height())); } - _height += captionHeight(); + height += captionHeight(); - _inputPortWidth = portWidth(PortType::In); - _outputPortWidth = portWidth(PortType::Out); + unsigned int inPortWidth = portWidth(PortType::In); + unsigned int outPortWidth = portWidth(PortType::Out); - _width = _inputPortWidth + - _outputPortWidth + - 2 * _spacing; + unsigned int width = inPortWidth + outPortWidth + 2 * _verticalSpacing; - if (auto w = _dataModel->embeddedWidget()) + if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget).value()) { - _width += w->width(); + width += w->width(); } - _width = std::max(_width, captionWidth()); + width = std::max(width, captionWidth()); - if (_dataModel->validationState() != NodeValidationState::Valid) - { - _width = std::max(_width, validationWidth()); - _height += validationHeight() + _spacing; - } + QSize size(width, height); + + _graphModel.setNodeData(_ngo.nodeId(), NodeRole::Size, size); + + return size; } -void +QSize NodeGeometry:: -recalculateSize(QFont const & font) const +recalculateSizeIfFontChanged(QFont const &font) const { QFontMetrics fontMetrics(font); QFont boldFont = font; @@ -136,20 +157,20 @@ recalculateSize(QFont const & font) const _fontMetrics = fontMetrics; _boldFontMetrics = boldFontMetrics; - recalculateSize(); } + + return recalculateSize(); } QPointF NodeGeometry:: -portScenePosition(PortIndex index, - PortType portType, - QTransform const & t) const +portNodePosition(PortType const portType, + PortIndex const index) const { auto const &nodeStyle = StyleCollection::nodeStyle(); - unsigned int step = _entryHeight + _spacing; + unsigned int step = _entryHeight + _verticalSpacing; QPointF result; @@ -162,11 +183,14 @@ portScenePosition(PortIndex index, // TODO: why? totalHeight += step / 2.0; + QSize size = + _graphModel.nodeData(_ngo.nodeId(), NodeRole::Size).value(); + switch (portType) { case PortType::Out: { - double x = _width + nodeStyle.ConnectionPointDiameter; + double x = size.width() + nodeStyle.ConnectionPointDiameter; result = QPointF(x, totalHeight); break; @@ -184,37 +208,56 @@ portScenePosition(PortIndex index, break; } + return result; +} + + +QPointF +NodeGeometry:: +portScenePosition(PortType const portType, + PortIndex const index, + QTransform const &t) const +{ + QPointF result = portNodePosition(portType, index); + return t.map(result); } +// TODO check implementation PortIndex NodeGeometry:: checkHitScenePoint(PortType portType, QPointF const scenePoint, - QTransform const & sceneTransform) const + QTransform const &sceneTransform) const { auto const &nodeStyle = StyleCollection::nodeStyle(); - PortIndex result = INVALID; + PortIndex result = InvalidPortIndex; if (portType == PortType::None) return result; double const tolerance = 2.0 * nodeStyle.ConnectionPointDiameter; - unsigned int const nItems = _dataModel->nPorts(portType); + NodeId nodeId = _ngo.nodeId(); - for (unsigned int i = 0; i < nItems; ++i) + size_t const n = + _graphModel.nodeData(nodeId, + (portType == PortType::Out) ? + NodeRole::NumberOfOutPorts : + NodeRole::NumberOfInPorts).toUInt(); + + for (unsigned int portIndex = 0; portIndex < n; ++portIndex) { - auto pp = portScenePosition(i, portType, sceneTransform); + auto pp = portScenePosition(portType, portIndex, sceneTransform); - QPointF p = pp - scenePoint; - auto distance = std::sqrt(QPointF::dotProduct(p, p)); + QPointF p = pp - scenePoint; + auto distance = std::sqrt(QPointF::dotProduct(p, p)); if (distance < tolerance) { - result = PortIndex(i); + result = portIndex; break; } } @@ -227,10 +270,13 @@ QRect NodeGeometry:: resizeRect() const { + QSize size = + _graphModel.nodeData(_ngo.nodeId(), NodeRole::Size).value(); + unsigned int rectSize = 7; - return QRect(_width - rectSize, - _height - rectSize, + return QRect(size.width() - rectSize, + size.height() - rectSize, rectSize, rectSize); } @@ -240,48 +286,52 @@ QPointF NodeGeometry:: widgetPosition() const { - if (auto w = _dataModel->embeddedWidget()) + QSize size = + _graphModel.nodeData(_ngo.nodeId(), NodeRole::Size).value(); + + NodeId const nodeId = _ngo.nodeId(); + if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget).value()) { + // If the widget wants to use as much vertical space as possible, + // place it immediately after the caption. 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()); + return QPointF(_verticalSpacing + 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(_verticalSpacing + portWidth(PortType::In), + (captionHeight() + size.height() - w->height()) / 2.0); } } return QPointF(); } + int NodeGeometry:: equivalentWidgetHeight() const { - if (_dataModel->validationState() != NodeValidationState::Valid) - { - return height() - captionHeight() + validationHeight(); - } - - return height() - captionHeight(); + QSize size = + _graphModel.nodeData(_ngo.nodeId(), NodeRole::Size).value(); + //if (_dataModel->validationState() != NodeValidationState::Valid) + //{ + //return height() - captionHeight() + validationHeight(); + //} + return size.height() - captionHeight(); } + unsigned int NodeGeometry:: captionHeight() const { - if (!_dataModel->captionVisible()) + NodeId nodeId = _ngo.nodeId(); + + if (!_graphModel.nodeData(nodeId, NodeRole::CaptionVisible).toBool()) return 0; - QString name = _dataModel->caption(); + QString name = _graphModel.nodeData(nodeId, NodeRole::Caption).toString(); return _boldFontMetrics.boundingRect(name).height(); } @@ -291,51 +341,55 @@ unsigned int NodeGeometry:: captionWidth() const { - if (!_dataModel->captionVisible()) + NodeId nodeId = _ngo.nodeId(); + + if (!_graphModel.nodeData(nodeId, NodeRole::CaptionVisible).toBool()) return 0; - QString name = _dataModel->caption(); + QString name = _graphModel.nodeData(nodeId, NodeRole::Caption).toString(); return _boldFontMetrics.boundingRect(name).width(); } -unsigned int -NodeGeometry:: -validationHeight() const -{ - QString msg = _dataModel->validationMessage(); +//unsigned int +//NodeGeometry:: +//validationHeight() const +//{ +//QString msg = _dataModel->validationMessage(); - return _boldFontMetrics.boundingRect(msg).height(); -} +//return _boldFontMetrics.boundingRect(msg).height(); +//} -unsigned int -NodeGeometry:: -validationWidth() const -{ - QString msg = _dataModel->validationMessage(); +//unsigned int +//NodeGeometry:: +//validationWidth() const +//{ +//QString msg = _dataModel->validationMessage(); - return _boldFontMetrics.boundingRect(msg).width(); -} +//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; -} +//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 @@ -344,17 +398,33 @@ portWidth(PortType portType) const { unsigned width = 0; - for (auto i = 0ul; i < _dataModel->nPorts(portType); ++i) + NodeId nodeId = _ngo.nodeId(); + + size_t const n = + _graphModel.nodeData(nodeId, + (portType == PortType::Out) ? + NodeRole::NumberOfOutPorts : + NodeRole::NumberOfInPorts).toUInt(); + + for (PortIndex portIndex = 0ul; portIndex < n; ++portIndex) { QString name; - if (_dataModel->portCaptionVisible(portType, i)) + if (_graphModel.portData(nodeId, + portType, + portIndex, + PortRole::CaptionVisible).toBool()) { - name = _dataModel->portCaption(portType, i); + QVariant portData = + _graphModel.portData(nodeId, portType, portIndex, PortRole::Caption); + + name = portData.toString(); } else { - name = _dataModel->dataType(portType, i).name; + QVariant portData = + _graphModel.portData(nodeId, portType, portIndex, PortRole::DataType); + name = portData.value().name; } #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) @@ -368,3 +438,6 @@ portWidth(PortType portType) const return width; } + + +} diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index 7516d0bbd..f714eb6f6 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -6,41 +6,39 @@ #include #include +#include "BasicGraphicsScene.hpp" #include "ConnectionGraphicsObject.hpp" -#include "ConnectionState.hpp" - -#include "FlowScene.hpp" -#include "NodePainter.hpp" - -#include "Node.hpp" -#include "NodeDataModel.hpp" +#include "ConnectionIdUtils.hpp" +#include "GraphModel.hpp" #include "NodeConnectionInteraction.hpp" - +#include "NodeGeometry.hpp" +#include "NodePainter.hpp" #include "StyleCollection.hpp" -using QtNodes::NodeGraphicsObject; -using QtNodes::Node; -using QtNodes::FlowScene; + +namespace QtNodes +{ NodeGraphicsObject:: -NodeGraphicsObject(FlowScene &scene, - Node& node) - : _scene(scene) - , _node(node) - , _locked(false) +NodeGraphicsObject(BasicGraphicsScene &scene, + NodeId nodeId) + : _nodeId(nodeId) + , _graphModel(scene.graphModel()) + , _nodeState(*this) , _proxyWidget(nullptr) { - _scene.addItem(this); + scene.addItem(this); setFlag(QGraphicsItem::ItemDoesntPropagateOpacityToChildren, true); - setFlag(QGraphicsItem::ItemIsMovable, true); - setFlag(QGraphicsItem::ItemIsFocusable, true); - setFlag(QGraphicsItem::ItemIsSelectable, true); - setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true); + setFlag(QGraphicsItem::ItemIsFocusable, true); + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsSelectable, true); + setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true); - setCacheMode( QGraphicsItem::DeviceCoordinateCache ); + setCacheMode(QGraphicsItem::DeviceCoordinateCache); - auto const &nodeStyle = node.nodeDataModel()->nodeStyle(); + // TODO: Take style from model. + auto const &nodeStyle = StyleCollection::nodeStyle(); { auto effect = new QGraphicsDropShadowEffect; @@ -59,35 +57,30 @@ NodeGraphicsObject(FlowScene &scene, 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); -} + NodeGeometry geometry(*this); + geometry.recalculateSize(); -NodeGraphicsObject:: -~NodeGraphicsObject() -{ - _scene.removeItem(this); + QPointF const pos = + _graphModel.nodeData(_nodeId, NodeRole::Position).value(); + + setPos(pos); } -Node& +GraphModel & NodeGraphicsObject:: -node() +graphModel() const { - return _node; + return _graphModel; } -Node const& +BasicGraphicsScene * NodeGraphicsObject:: -node() const +nodeScene() const { - return _node; + return dynamic_cast(scene()); } @@ -95,9 +88,10 @@ void NodeGraphicsObject:: embedQWidget() { - NodeGeometry & geom = _node.nodeGeometry(); + NodeGeometry geom(*this); - if (auto w = _node.nodeDataModel()->embeddedWidget()) + if (auto w = _graphModel.nodeData(_nodeId, + NodeRole::Widget).value()) { _proxyWidget = new QGraphicsProxyWidget(this); @@ -105,17 +99,18 @@ embedQWidget() _proxyWidget->setPreferredWidth(5); - geom.recalculateSize(); + NodeGeometry(*this).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. + // 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(); + //update(); _proxyWidget->setOpacity(1.0); _proxyWidget->setFlag(QGraphicsItem::ItemIgnoresParentOpacity); @@ -123,11 +118,38 @@ embedQWidget() } +#if 0 +void +NodeGraphicsObject:: +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(); + } + } + } +} + + +#endif + + QRectF NodeGraphicsObject:: boundingRect() const { - return _node.nodeGeometry().boundingRect(); + return NodeGeometry(*this).boundingRect(); } @@ -143,43 +165,47 @@ void NodeGraphicsObject:: moveConnections() const { - NodeState const & nodeState = _node.nodeState(); + auto moveConns = + [this](PortType portType, NodeRole nodeRole) + { + size_t const n = + _graphModel.nodeData(_nodeId, nodeRole).toUInt(); - for (PortType portType: {PortType::In, PortType::Out}) - { - auto const & connectionEntries = - nodeState.getEntries(portType); + for (PortIndex portIndex = 0; portIndex < n; ++portIndex) + { + auto const &connectedNodes = + _graphModel.connectedNodes(_nodeId, portType, portIndex); - for (auto const & connections : connectionEntries) - { - for (auto & con : connections) - con.second->getConnectionGraphicsObject().move(); - } - } -} + for (auto &cn: connectedNodes) + { + // out node id, out port index, in node id, in port index. + ConnectionId connectionId = + (portType == PortType::In) ? + std::make_tuple(cn.first, cn.second, _nodeId, portIndex) : + std::make_tuple(_nodeId, portIndex, cn.first, cn.second); + auto cgo = nodeScene()->connectionGraphicsObject(connectionId); -void -NodeGraphicsObject:: -lock(bool locked) -{ - _locked = locked; + // TODO: Directly move the connection's end? + cgo->move(); + } + } + }; - setFlag(QGraphicsItem::ItemIsMovable, !locked); - setFlag(QGraphicsItem::ItemIsFocusable, !locked); - setFlag(QGraphicsItem::ItemIsSelectable, !locked); + moveConns(PortType::In, NodeRole::NumberOfInPorts); + moveConns(PortType::Out, NodeRole::NumberOfOutPorts); } void NodeGraphicsObject:: paint(QPainter * painter, - QStyleOptionGraphicsItem const* option, - QWidget* ) + QStyleOptionGraphicsItem const * option, + QWidget *) { painter->setClipRect(option->exposedRect); - NodePainter::paint(painter, _node, _scene); + NodePainter::paint(painter, *this); } @@ -200,31 +226,40 @@ void NodeGraphicsObject:: mousePressEvent(QGraphicsSceneMouseEvent * event) { - if (_locked) - return; + //if (_nodeState.locked()) + //return; + + BasicGraphicsScene * nodeScene = this->nodeScene(); for (PortType portToCheck: {PortType::In, PortType::Out}) { - NodeGeometry const & nodeGeometry = _node.nodeGeometry(); + NodeGeometry nodeGeometry(*this); - // TODO do not pass sceneTransform - int const portIndex = nodeGeometry.checkHitScenePoint(portToCheck, - event->scenePos(), - sceneTransform()); + PortIndex const portIndex = + nodeGeometry.checkHitScenePoint(portToCheck, + event->scenePos(), + sceneTransform()); - if (portIndex != INVALID) + if (portIndex != InvalidPortIndex) { - NodeState const & nodeState = _node.nodeState(); - - std::unordered_map connections = - nodeState.connections(portToCheck, portIndex); + auto const &connectedNodes = + _graphModel.connectedNodes(_nodeId, portToCheck, portIndex); - // start dragging existing connection - if (!connections.empty() && portToCheck == PortType::In) + // Start dragging existing connection. + if (!connectedNodes.empty() && portToCheck == PortType::In) { - auto con = connections.begin()->second; + auto const &cn = *connectedNodes.begin(); + + // Need "reversed" connectin id if enabled for both port types. + ConnectionId connectionId = + std::make_tuple(cn.first, cn.second, _nodeId, portIndex); + + // Need ConnectionGraphicsObject - NodeConnectionInteraction interaction(_node, *con, _scene); + NodeConnectionInteraction + interaction(*this, + *nodeScene->connectionGraphicsObject(connectionId), + *nodeScene); interaction.disconnect(portToCheck); } @@ -232,37 +267,40 @@ mousePressEvent(QGraphicsSceneMouseEvent * event) { if (portToCheck == PortType::Out) { - auto const outPolicy = _node.nodeDataModel()->portOutConnectionPolicy(portIndex); - if (!connections.empty() && - outPolicy == NodeDataModel::ConnectionPolicy::One) + auto const outPolicy = + _graphModel.portData(_nodeId, + portToCheck, + portIndex, + PortRole::ConnectionPolicy).value(); + + if (!connectedNodes.empty() && + outPolicy == ConnectionPolicy::One) { - _scene.deleteConnection( *connections.begin()->second ); - } - } + for (auto &cn : connectedNodes) + { + ConnectionId connectionId = + std::make_tuple(_nodeId, portIndex, cn.first, cn.second); - // todo add to FlowScene - auto connection = _scene.createConnection(portToCheck, - _node, - portIndex); + _graphModel.deleteConnection(connectionId); + } + } + } // if port == out - _node.nodeState().setConnection(portToCheck, - portIndex, - *connection); + ConnectionId const incompleteConnectionId = + makeIncompleteConnectionId(portToCheck, _nodeId, portIndex); - connection->getConnectionGraphicsObject().grabMouse(); + nodeScene->makeDraftConnection(incompleteConnectionId); } } } - auto pos = event->pos(); - auto & geom = _node.nodeGeometry(); - auto & state = _node.nodeState(); - - if (_node.nodeDataModel()->resizable() && - geom.resizeRect().contains(QPoint(pos.x(), - pos.y()))) + if (_graphModel.nodeFlags(_nodeId) & NodeFlag::Resizable) { - state.setResizing(true); + NodeGeometry geometry(*this); + + auto pos = event->pos(); + bool const hit = geometry.resizeRect().contains(QPoint(pos.x(), pos.y())); + _nodeState.setResizing(hit); } } @@ -271,21 +309,19 @@ void NodeGraphicsObject:: mouseMoveEvent(QGraphicsSceneMouseEvent * event) { - auto & geom = _node.nodeGeometry(); - auto & state = _node.nodeState(); - // deselect all other items after this one is selected if (!isSelected()) { - _scene.clearSelection(); + nodeScene()->clearSelection(); setSelected(true); } - if (state.resizing()) + if (_nodeState.resizing()) { auto diff = event->pos() - event->lastPos(); - if (auto w = _node.nodeDataModel()->embeddedWidget()) + if (auto w = _graphModel.nodeData(_nodeId, + NodeRole::Widget).value()) { prepareGeometryChange(); @@ -295,11 +331,14 @@ mouseMoveEvent(QGraphicsSceneMouseEvent * event) w->setFixedSize(oldSize); + NodeGeometry geometry(*this); + _proxyWidget->setMinimumSize(oldSize); _proxyWidget->setMaximumSize(oldSize); - _proxyWidget->setPos(geom.widgetPosition()); + _proxyWidget->setPos(geometry.widgetPosition()); + + geometry.recalculateSize(); - geom.recalculateSize(); update(); moveConnections(); @@ -317,28 +356,26 @@ mouseMoveEvent(QGraphicsSceneMouseEvent * event) event->ignore(); } - QRectF r = scene()->sceneRect(); + QRectF r = nodeScene()->sceneRect(); r = r.united(mapToScene(boundingRect()).boundingRect()); - scene()->setSceneRect(r); + nodeScene()->setSceneRect(r); } void NodeGraphicsObject:: -mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +mouseReleaseEvent(QGraphicsSceneMouseEvent * event) { - auto & state = _node.nodeState(); - - state.setResizing(false); + _nodeState.setResizing(false); QGraphicsObject::mouseReleaseEvent(event); // position connections precisely after fast node move moveConnections(); - _scene.nodeClicked(node()); + nodeScene()->nodeClicked(_nodeId); } @@ -349,7 +386,7 @@ hoverEnterEvent(QGraphicsSceneHoverEvent * event) // bring all the colliding nodes to background QList overlapItems = collidingItems(); - for (QGraphicsItem *item : overlapItems) + for (QGraphicsItem * item : overlapItems) { if (item->zValue() > 0.0) { @@ -360,9 +397,13 @@ hoverEnterEvent(QGraphicsSceneHoverEvent * event) // bring this node forward setZValue(1.0); - _node.nodeGeometry().setHovered(true); + _nodeState.setHovered(true); + update(); - _scene.nodeHovered(node(), event->screenPos()); + + // Signal + nodeScene()->nodeHovered(_nodeId, event->screenPos()); + event->accept(); } @@ -371,9 +412,13 @@ void NodeGraphicsObject:: hoverLeaveEvent(QGraphicsSceneHoverEvent * event) { - _node.nodeGeometry().setHovered(false); + _nodeState.setHovered(false); + update(); - _scene.nodeHoverLeft(node()); + + // Signal + nodeScene()->nodeHoverLeft(_nodeId); + event->accept(); } @@ -382,11 +427,12 @@ void NodeGraphicsObject:: hoverMoveEvent(QGraphicsSceneHoverEvent * event) { - auto pos = event->pos(); - auto & geom = _node.nodeGeometry(); + auto pos = event->pos(); + + NodeGeometry geometry(*this); - if (_node.nodeDataModel()->resizable() && - geom.resizeRect().contains(QPoint(pos.x(), pos.y()))) + if ((_graphModel.nodeFlags(_nodeId) | NodeFlag::Resizable) && + geometry.resizeRect().contains(QPoint(pos.x(), pos.y()))) { setCursor(QCursor(Qt::SizeFDiagCursor)); } @@ -401,17 +447,20 @@ hoverMoveEvent(QGraphicsSceneHoverEvent * event) void NodeGraphicsObject:: -mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) +mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event) { QGraphicsItem::mouseDoubleClickEvent(event); - _scene.nodeDoubleClicked(node()); + nodeScene()->nodeDoubleClicked(_nodeId); } void NodeGraphicsObject:: -contextMenuEvent(QGraphicsSceneContextMenuEvent* event) +contextMenuEvent(QGraphicsSceneContextMenuEvent * event) { - _scene.nodeContextMenu(node(), mapToScene(event->pos())); + nodeScene()->nodeContextMenu(_nodeId, mapToScene(event->pos())); +} + + } diff --git a/src/NodePainter.cpp b/src/NodePainter.cpp index aa96d15a6..61960de69 100644 --- a/src/NodePainter.cpp +++ b/src/NodePainter.cpp @@ -4,76 +4,68 @@ #include -#include "StyleCollection.hpp" -#include "PortType.hpp" -#include "NodeGraphicsObject.hpp" +#include "GraphModel.hpp" #include "NodeGeometry.hpp" +#include "NodeGraphicsObject.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; +#include "PortType.hpp" +#include "StyleCollection.hpp" + + +namespace QtNodes +{ void NodePainter:: -paint(QPainter* painter, - Node & node, - FlowScene const& scene) +paint(QPainter * painter, + NodeGraphicsObject const &ngo) { - NodeGeometry const& geom = node.nodeGeometry(); + NodeGeometry geometry(ngo); + geometry.recalculateSizeIfFontChanged(painter->font()); - NodeState const& state = node.nodeState(); + drawNodeRect(painter, ngo); - NodeGraphicsObject const & graphicsObject = node.nodeGraphicsObject(); + drawConnectionPoints(painter, ngo); - geom.recalculateSize(painter->font()); + drawFilledConnectionPoints(painter, ngo); - //-------------------------------------------- - NodeDataModel const * model = node.nodeDataModel(); + drawNodeCaption(painter, ngo); - drawNodeRect(painter, geom, model, graphicsObject); + drawEntryLabels(painter, ngo); - 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); + drawResizeRect(painter, ngo); /// call custom painter - if (auto painterDelegate = model->painterDelegate()) - { - painterDelegate->paint(painter, geom, model); - } + // TODO: think about and implement custom painter delegate + //if (auto painterDelegate = model->painterDelegate()) + //{ + //painterDelegate->paint(painter, geom, model); + //} } void NodePainter:: -drawNodeRect(QPainter* painter, - NodeGeometry const& geom, - NodeDataModel const* model, - NodeGraphicsObject const & graphicsObject) +drawNodeRect(QPainter * painter, + NodeGraphicsObject const &ngo) { - NodeStyle const& nodeStyle = model->nodeStyle(); + GraphModel const &model = ngo.graphModel(); + + NodeId const nodeId = ngo.nodeId(); - auto color = graphicsObject.isSelected() - ? nodeStyle.SelectedBoundaryColor - : nodeStyle.NormalBoundaryColor; + NodeGeometry geom(ngo); + QSize size = geom.size(); - if (geom.hovered()) + QJsonDocument json = + QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); + + NodeStyle nodeStyle(json); + + auto color = ngo.isSelected() ? + nodeStyle.SelectedBoundaryColor : + nodeStyle.NormalBoundaryColor; + + if (ngo.nodeState().hovered()) { QPen p(color, nodeStyle.HoveredPenWidth); painter->setPen(p); @@ -85,18 +77,20 @@ drawNodeRect(QPainter* painter, } QLinearGradient gradient(QPointF(0.0, 0.0), - QPointF(2.0, geom.height())); + QPointF(2.0, size.height())); - gradient.setColorAt(0.0, nodeStyle.GradientColor0); + gradient.setColorAt(0.0, nodeStyle.GradientColor0); gradient.setColorAt(0.03, nodeStyle.GradientColor1); gradient.setColorAt(0.97, nodeStyle.GradientColor2); - gradient.setColorAt(1.0, nodeStyle.GradientColor3); + 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()); + QRectF boundary(-diam, -diam, + 2.0 * diam + size.width(), + 2.0 * diam + size.height()); double const radius = 3.0; @@ -106,67 +100,86 @@ drawNodeRect(QPainter* painter, void NodePainter:: -drawConnectionPoints(QPainter* painter, - NodeGeometry const& geom, - NodeState const& state, - NodeDataModel const * model, - FlowScene const & scene) +drawConnectionPoints(QPainter * painter, + NodeGraphicsObject const &ngo) { - NodeStyle const& nodeStyle = model->nodeStyle(); - auto const &connectionStyle = StyleCollection::connectionStyle(); + GraphModel const &model = ngo.graphModel(); + NodeId const nodeId = ngo.nodeId(); + NodeGeometry geom(ngo); + + QJsonDocument json = + QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); + NodeStyle nodeStyle(json); + + auto const &connectionStyle = StyleCollection::connectionStyle(); - float diameter = nodeStyle.ConnectionPointDiameter; - auto reducedDiameter = diameter * 0.6; + float diameter = nodeStyle.ConnectionPointDiameter; + auto reducedDiameter = diameter * 0.6; - for(PortType portType: {PortType::Out, PortType::In}) + for (PortType portType: {PortType::Out, PortType::In}) { - size_t n = state.getEntries(portType).size(); + size_t const n = + model.nodeData(nodeId, + (portType == PortType::Out) ? + NodeRole::NumberOfOutPorts : + NodeRole::NumberOfInPorts).toUInt(); - for (unsigned int i = 0; i < n; ++i) + for (PortIndex portIndex = 0; portIndex < n; ++portIndex) { - QPointF p = geom.portScenePosition(i, portType); + QPointF p = geom.portNodePosition(portType, portIndex); - auto const & dataType = - model->dataType(portType, static_cast(i)); + auto const &dataType = + model.portData(nodeId, + portType, + portIndex, + PortRole::DataType).value(); - bool canConnect = (state.getEntries(portType)[i].empty() || - (portType == PortType::Out && - model->portOutConnectionPolicy(i) == NodeDataModel::ConnectionPolicy::Many) ); + auto const &connectedNodes = + model.connectedNodes(nodeId, portType, portIndex); + + bool canConnect = + (connectedNodes.empty() || + model.portData(nodeId, + portType, + portIndex, + PortRole::ConnectionPolicy).value() == ConnectionPolicy::Many); double r = 1.0; + + NodeState const &state = ngo.nodeState(); + if (state.isReacting() && canConnect && portType == state.reactingPortType()) { - - auto diff = geom.draggingPos() - p; + auto diff = state.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; - } - } + 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; + (2.0 - dist / thres ) : + 1.0; } else { double const thres = 80.0; r = (dist < thres) ? - (dist / thres) : - 1.0; + (dist / thres) : + 1.0; } } @@ -183,37 +196,50 @@ drawConnectionPoints(QPainter* painter, reducedDiameter * r, reducedDiameter * r); } - }; + } + ; } void NodePainter:: drawFilledConnectionPoints(QPainter * painter, - NodeGeometry const & geom, - NodeState const & state, - NodeDataModel const * model) + NodeGraphicsObject const &ngo) { - NodeStyle const& nodeStyle = model->nodeStyle(); - auto const & connectionStyle = StyleCollection::connectionStyle(); + GraphModel const &model = ngo.graphModel(); + NodeId const nodeId = ngo.nodeId(); + NodeGeometry geom(ngo); + + QJsonDocument json = + QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); + NodeStyle nodeStyle(json); auto diameter = nodeStyle.ConnectionPointDiameter; - for(PortType portType: {PortType::Out, PortType::In}) + for (PortType portType: {PortType::Out, PortType::In}) { - size_t n = state.getEntries(portType).size(); + size_t const n = + model.nodeData(nodeId, + (portType == PortType::Out) ? + NodeRole::NumberOfOutPorts : + NodeRole::NumberOfInPorts).toUInt(); - for (size_t i = 0; i < n; ++i) + for (PortIndex portIndex = 0; portIndex < n; ++portIndex) { - QPointF p = geom.portScenePosition( - static_cast(i), - static_cast(portType)); + QPointF p = geom.portNodePosition(portType, portIndex); + + auto const &connectedNodes = + model.connectedNodes(nodeId, portType, portIndex); - if (!state.getEntries(portType)[i].empty()) + if (!connectedNodes.empty()) { - auto const & dataType = - model->dataType(portType, static_cast(i)); + auto const &dataType = + model.portData(nodeId, + portType, + portIndex, + PortRole::DataType).value(); + auto const &connectionStyle = StyleCollection::connectionStyle(); if (connectionStyle.useDataDefinedColors()) { QColor const c = connectionStyle.normalColor(dataType.id); @@ -237,30 +263,31 @@ drawFilledConnectionPoints(QPainter * painter, void NodePainter:: -drawModelName(QPainter * painter, - NodeGeometry const & geom, - NodeState const & state, - NodeDataModel const * model) +drawNodeCaption(QPainter * painter, + NodeGraphicsObject const &ngo) { - NodeStyle const& nodeStyle = model->nodeStyle(); + GraphModel const &model = ngo.graphModel(); + NodeId const nodeId = ngo.nodeId(); + NodeGeometry geom(ngo); - Q_UNUSED(state); - - if (!model->captionVisible()) + if (!model.nodeData(nodeId, NodeRole::CaptionVisible).toBool()) return; - QString const &name = model->caption(); + QString const name = model.nodeData(nodeId, NodeRole::Caption).toString(); QFont f = painter->font(); - f.setBold(true); QFontMetrics metrics(f); + auto rect = metrics.boundingRect(name); + QSize size = geom.size(); - auto rect = metrics.boundingRect(name); + QPointF position((size.width() - rect.width()) / 2.0, + (geom.verticalSpacing() + geom.entryHeight()) / 3.0); - QPointF position((geom.width() - rect.width()) / 2.0, - (geom.spacing() + geom.entryHeight()) / 3.0); + QJsonDocument json = + QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); + NodeStyle nodeStyle(json); painter->setFont(f); painter->setPen(nodeStyle.FontColor); @@ -274,57 +301,69 @@ drawModelName(QPainter * painter, void NodePainter:: drawEntryLabels(QPainter * painter, - NodeGeometry const & geom, - NodeState const & state, - NodeDataModel const * model) + NodeGraphicsObject const &ngo) { - QFontMetrics const & metrics = - painter->fontMetrics(); + GraphModel const &model = ngo.graphModel(); + NodeId const nodeId = ngo.nodeId(); + NodeGeometry geom(ngo); - for(PortType portType: {PortType::Out, PortType::In}) - { - auto const &nodeStyle = model->nodeStyle(); + QJsonDocument json = + QJsonDocument::fromVariant(model.nodeData(nodeId, NodeRole::Style)); + NodeStyle nodeStyle(json); - auto& entries = state.getEntries(portType); + QSize size = geom.size(); - size_t n = entries.size(); + for (PortType portType: {PortType::Out, PortType::In}) + { + size_t const n = + model.nodeData(nodeId, + (portType == PortType::Out) ? + NodeRole::NumberOfOutPorts : + NodeRole::NumberOfInPorts).toUInt(); - for (size_t i = 0; i < n; ++i) + for (PortIndex portIndex = 0; portIndex < n; ++portIndex) { - QPointF p = geom.portScenePosition(static_cast(i), portType); + auto const &connectedNodes = + model.connectedNodes(nodeId, portType, portIndex); + + QPointF p = geom.portNodePosition(portType, portIndex); - if (entries[i].empty()) + if (connectedNodes.empty()) painter->setPen(nodeStyle.FontColorFaded); else painter->setPen(nodeStyle.FontColor); QString s; - if (model->portCaptionVisible(portType, static_cast(i))) + if (model.portData(nodeId, portType, portIndex, PortRole::CaptionVisible).toBool()) { - s = model->portCaption(portType, static_cast(i)); + s = model.portData(nodeId, portType, portIndex, PortRole::Caption).toString(); } else { - s = model->dataType(portType, static_cast(i)).name; + auto portData = + model.portData(nodeId, portType, portIndex, PortRole::DataType); + + s = portData.value().name; } + QFontMetrics const &metrics = painter->fontMetrics(); auto rect = metrics.boundingRect(s); p.setY(p.y() + rect.height() / 4.0); switch (portType) { - case PortType::In: - p.setX(5.0); - break; + case PortType::In: + p.setX(5.0); + break; - case PortType::Out: - p.setX(geom.width() - 5.0 - rect.width()); - break; + case PortType::Out: + p.setX(size.width() - 5.0 - rect.width()); + break; - default: - break; + default: + break; } painter->drawText(p, s); @@ -336,10 +375,13 @@ drawEntryLabels(QPainter * painter, void NodePainter:: drawResizeRect(QPainter * painter, - NodeGeometry const & geom, - NodeDataModel const * model) + NodeGraphicsObject const &ngo) { - if (model->resizable()) + GraphModel const &model = ngo.graphModel(); + NodeId const nodeId = ngo.nodeId(); + NodeGeometry geom(ngo); + + if (model.nodeFlags(nodeId) & NodeFlag::Resizable) { painter->setBrush(Qt::gray); @@ -348,71 +390,4 @@ drawResizeRect(QPainter * painter, } -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); - } } diff --git a/src/NodePainter.hpp b/src/NodePainter.hpp index ba8c3c71b..b8a8d740e 100644 --- a/src/NodePainter.hpp +++ b/src/NodePainter.hpp @@ -2,17 +2,18 @@ #include +#include "Definitions.hpp" + namespace QtNodes { -class Node; -class NodeState; +class BasicGraphicsScene; +class GraphModel; class NodeGeometry; class NodeGraphicsObject; -class NodeDataModel; -class FlowItemEntry; -class FlowScene; +class NodeState; +/// @ Lightweight class incapsulating paint code. class NodePainter { public: @@ -22,58 +23,30 @@ class NodePainter public: static - void - paint(QPainter* painter, - Node& node, - FlowScene const& scene); + void paint(QPainter * painter, + NodeGraphicsObject const & ngo); static - void - drawNodeRect(QPainter* painter, - NodeGeometry const& geom, - NodeDataModel const* model, - NodeGraphicsObject const & graphicsObject); + void drawNodeRect(QPainter * painter, + NodeGraphicsObject const & ngo); 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); - + void drawConnectionPoints(QPainter * painter, + NodeGraphicsObject const & ngo); static - void - drawConnectionPoints(QPainter* painter, - NodeGeometry const& geom, - NodeState const& state, - NodeDataModel const * model, - FlowScene const & scene); + void drawFilledConnectionPoints(QPainter * painter, + NodeGraphicsObject const & ngo); static - void - drawFilledConnectionPoints(QPainter* painter, - NodeGeometry const& geom, - NodeState const& state, - NodeDataModel const * model); + void drawNodeCaption(QPainter * painter, + NodeGraphicsObject const & ngo); static - void - drawResizeRect(QPainter* painter, - NodeGeometry const& geom, - NodeDataModel const * model); + void drawEntryLabels(QPainter * painter, + NodeGraphicsObject const & ngo); static - void - drawValidationRect(QPainter * painter, - NodeGeometry const & geom, - NodeDataModel const * model, - NodeGraphicsObject const & graphicsObject); + void drawResizeRect(QPainter * painter, + NodeGraphicsObject const & ngo); }; } diff --git a/src/NodeState.cpp b/src/NodeState.cpp index 226d5cfed..589598533 100644 --- a/src/NodeState.cpp +++ b/src/NodeState.cpp @@ -1,81 +1,105 @@ #include "NodeState.hpp" -#include "NodeDataModel.hpp" +#include "NodeGraphicsObject.hpp" -#include "Connection.hpp" -using QtNodes::NodeState; -using QtNodes::NodeDataType; -using QtNodes::NodeDataModel; -using QtNodes::PortType; -using QtNodes::PortIndex; -using QtNodes::Connection; +namespace QtNodes +{ NodeState:: -NodeState(std::unique_ptr const &model) - : _inConnections(model->nPorts(PortType::In)) - , _outConnections(model->nPorts(PortType::Out)) +NodeState(NodeGraphicsObject & ngo) + : _ngo(ngo) + , _hovered(false) , _reaction(NOT_REACTING) , _reactingPortType(PortType::None) + //, _locked(false) , _resizing(false) {} -std::vector const & -NodeState:: -getEntries(PortType portType) const -{ - if (portType == PortType::In) - return _inConnections; - else - return _outConnections; -} +//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; -} +//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); +//NodeState::ConnectionPtrSet +//NodeState:: +//connections(PortType portType, PortIndex portIndex) const +//{ +//auto const &connections = getEntries(portType); - return connections[portIndex]; -} +//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); +//} void NodeState:: -setConnection(PortType portType, - PortIndex portIndex, - Connection& connection) +setResizing(bool resizing) { - auto &connections = getEntries(portType); - - connections.at(portIndex).insert(std::make_pair(connection.id(), - &connection)); + _resizing = resizing; } -void +bool NodeState:: -eraseConnection(PortType portType, - PortIndex portIndex, - QUuid id) +resizing() const { - getEntries(portType)[portIndex].erase(id); + return _resizing; } +//void +//NodeState:: +//lock(bool locked) +//{ +//_locked = locked; + +//setFlag(QGraphicsItem::ItemIsMovable, !locked); +//setFlag(QGraphicsItem::ItemIsFocusable, !locked); +//setFlag(QGraphicsItem::ItemIsSelectable, !locked); +//} + + NodeState::ReactToConnectionState NodeState:: reaction() const @@ -124,15 +148,33 @@ isReacting() const void NodeState:: -setResizing(bool resizing) +reactToPossibleConnection(PortType reactingPortType, + NodeDataType const & reactingDataType, + QPointF const & scenePoint) { - _resizing = resizing; + QTransform const t = _ngo.sceneTransform(); + + QPointF p = t.inverted().map(scenePoint); + + _draggingPos = p; + + setReaction(NodeState::REACTING, + reactingPortType, + reactingDataType); + + _ngo.update(); + } -bool +void NodeState:: -resizing() const +resetReactionToConnection() { - return _resizing; + setReaction(NodeState::NOT_REACTING); + + _ngo.update(); +} + + } diff --git a/src/NodeStyle.cpp b/src/NodeStyle.cpp index c62e0ac9e..caa7fc43f 100644 --- a/src/NodeStyle.cpp +++ b/src/NodeStyle.cpp @@ -2,13 +2,12 @@ #include -#include +#include #include #include #include -#include -#include +#include #include "StyleCollection.hpp" @@ -35,23 +34,29 @@ NodeStyle(QString jsonText) } +NodeStyle:: +NodeStyle(QJsonDocument const & json) +{ + loadJson(json); +} + + 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; \ - } + 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 @@ -71,43 +76,26 @@ setNodeStyle(QString jsonText) } \ } + +#define NODE_STYLE_WRITE_COLOR(values, variable) { \ + values[#variable] = variable.name(); \ +} + #define NODE_STYLE_READ_FLOAT(values, variable) { \ auto valueRef = values[#variable]; \ NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ variable = valueRef.toDouble(); \ } -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()); +#define NODE_STYLE_WRITE_FLOAT(values, variable) { \ + values[#variable] = variable; \ } void NodeStyle:: -loadJsonFromByteArray(QByteArray const &byteArray) +loadJson(QJsonDocument const & json) { - QJsonDocument json(QJsonDocument::fromJson(byteArray)); - QJsonObject topLevelObject = json.object(); QJsonValueRef nodeStyleValues = topLevelObject["NodeStyle"]; @@ -134,3 +122,37 @@ loadJsonFromByteArray(QByteArray const &byteArray) NODE_STYLE_READ_FLOAT(obj, Opacity); } + + +QJsonDocument +NodeStyle:: +toJson() const +{ + QJsonObject obj; + + NODE_STYLE_WRITE_COLOR(obj, NormalBoundaryColor); + NODE_STYLE_WRITE_COLOR(obj, SelectedBoundaryColor); + NODE_STYLE_WRITE_COLOR(obj, GradientColor0); + NODE_STYLE_WRITE_COLOR(obj, GradientColor1); + NODE_STYLE_WRITE_COLOR(obj, GradientColor2); + NODE_STYLE_WRITE_COLOR(obj, GradientColor3); + NODE_STYLE_WRITE_COLOR(obj, ShadowColor); + NODE_STYLE_WRITE_COLOR(obj, FontColor); + NODE_STYLE_WRITE_COLOR(obj, FontColorFaded); + NODE_STYLE_WRITE_COLOR(obj, ConnectionPointColor); + NODE_STYLE_WRITE_COLOR(obj, FilledConnectionPointColor); + NODE_STYLE_WRITE_COLOR(obj, WarningColor); + NODE_STYLE_WRITE_COLOR(obj, ErrorColor); + + NODE_STYLE_WRITE_FLOAT(obj, PenWidth); + NODE_STYLE_WRITE_FLOAT(obj, HoveredPenWidth); + NODE_STYLE_WRITE_FLOAT(obj, ConnectionPointDiameter); + + NODE_STYLE_WRITE_FLOAT(obj, Opacity); + + QJsonObject root; + root["NodeStyle"] = obj; + + return QJsonDocument(root); +} + diff --git a/src/Properties.cpp b/src/Properties.cpp deleted file mode 100644 index 422e6aa10..000000000 --- a/src/Properties.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "Properties.hpp" - -using QtNodes::Properties; - -void -Properties:: -put(QString const &name, QVariant const &v) -{ - _values.insert(name, v); -} - - diff --git a/src/Properties.hpp b/src/Properties.hpp deleted file mode 100644 index 35bb95e27..000000000 --- a/src/Properties.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#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; -}; -} diff --git a/src/StyleCollection.cpp b/src/StyleCollection.cpp index 3f1d3d628..5343fab1b 100644 --- a/src/StyleCollection.cpp +++ b/src/StyleCollection.cpp @@ -3,9 +3,9 @@ using QtNodes::StyleCollection; using QtNodes::NodeStyle; using QtNodes::ConnectionStyle; -using QtNodes::FlowViewStyle; +using QtNodes::GraphicsViewStyle; -NodeStyle const& +NodeStyle const & StyleCollection:: nodeStyle() { @@ -13,7 +13,7 @@ nodeStyle() } -ConnectionStyle const& +ConnectionStyle const & StyleCollection:: connectionStyle() { @@ -21,7 +21,7 @@ connectionStyle() } -FlowViewStyle const& +GraphicsViewStyle const & StyleCollection:: flowViewStyle() { @@ -47,14 +47,13 @@ setConnectionStyle(ConnectionStyle connectionStyle) void StyleCollection:: -setFlowViewStyle(FlowViewStyle flowViewStyle) +setGraphicsViewStyle(GraphicsViewStyle flowViewStyle) { instance()._flowViewStyle = flowViewStyle; } - -StyleCollection& +StyleCollection & StyleCollection:: instance() { @@ -62,3 +61,4 @@ instance() return collection; } + diff --git a/src/locateNode.cpp b/src/locateNode.cpp new file mode 100644 index 000000000..f288b3a99 --- /dev/null +++ b/src/locateNode.cpp @@ -0,0 +1,48 @@ +#include "locateNode.hpp" + +#include + +#include +#include + +#include "NodeGraphicsObject.hpp" + + +namespace QtNodes +{ + +NodeGraphicsObject* +locateNodeAt(QPointF scenePoint, + QGraphicsScene &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); + }); + + NodeGraphicsObject * node = nullptr; + + if (!filteredItems.empty()) + { + QGraphicsItem* graphicsItem = filteredItems.front(); + node = dynamic_cast(graphicsItem); + } + + return node; +} + +} From d59be84c74c47fdcd49ca850c4bc22ee5d3a2842 Mon Sep 17 00:00:00 2001 From: Dmitry Pinaev Date: Sun, 31 Jan 2021 13:57:17 +0100 Subject: [PATCH 006/127] Update project names, generate documentation --- CMakeLists.txt | 37 +++++++++++-------- README.md | 4 +- ...Config.cmake.in => QtNodesConfig.cmake.in} | 8 ++-- test/CMakeLists.txt | 2 +- 4 files changed, 28 insertions(+), 23 deletions(-) rename cmake/{NodeEditorConfig.cmake.in => QtNodesConfig.cmake.in} (54%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92382e0bb..ce72aa967 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.8) -project(NodeEditor CXX) +project(QtNodes CXX) set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) set(CMAKE_DISABLE_SOURCE_CHANGES ON) @@ -14,17 +14,18 @@ else() set(is_root_project ON) endif() -set(NE_DEVELOPER_DEFAULTS "${is_root_project}" CACHE BOOL "Turns on default settings for development of NodeEditor") +set(NODES_DEVELOPER_DEFAULTS "${is_root_project}" CACHE BOOL "Turns on default settings for development of QtNodes") -option(BUILD_TESTING "Build tests" "${NE_DEVELOPER_DEFAULTS}") -option(BUILD_EXAMPLES "Build Examples" "${NE_DEVELOPER_DEFAULTS}") +option(BUILD_TESTING "Build tests" "${NODES_DEVELOPER_DEFAULTS}") +option(BUILD_EXAMPLES "Build Examples" "${NODES_DEVELOPER_DEFAULTS}") +option(BUILD_DOCS "Build Documentation" "${NODES_DEVELOPER_DEFAULTS}") option(BUILD_SHARED_LIBS "Build as shared library" ON) option(BUILD_DEBUG_POSTFIX_D "Append d suffix to debug libraries" OFF) -option(NE_FORCE_TEST_COLOR "Force colorized unit test output" OFF) +option(NODES_FORCE_TEST_COLOR "Force colorized unit test output" OFF) enable_testing() -if(NE_DEVELOPER_DEFAULTS) +if(NODES_DEVELOPER_DEFAULTS) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") @@ -140,7 +141,7 @@ add_library(nodes ${RESOURCES} ) -add_library(NodeEditor::nodes ALIAS nodes) +add_library(QtNodes::nodes ALIAS nodes) target_include_directories(nodes @@ -182,7 +183,7 @@ if(NOT "${CMAKE_CXX_SIMULATE_ID}" STREQUAL "MSVC") ) endif() -if(NE_DEVELOPER_DEFAULTS) +if(NODES_DEVELOPER_DEFAULTS) target_compile_features(nodes PUBLIC cxx_std_14) set_target_properties(nodes PROPERTIES CXX_EXTENSIONS OFF) endif() @@ -224,6 +225,10 @@ if(BUILD_EXAMPLES) add_subdirectory(examples) endif() +if(BUILD_DOCS) + add_subdirectory(docs) +endif() + ################## # Automated Tests ## @@ -238,10 +243,10 @@ endif() include(GNUInstallDirs) -set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/NodeEditor) +set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/QtNodes) install(TARGETS nodes - EXPORT NodeEditorTargets + EXPORT QtNodesTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} @@ -249,20 +254,20 @@ install(TARGETS nodes install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(EXPORT NodeEditorTargets - FILE NodeEditorTargets.cmake - NAMESPACE NodeEditor:: +install(EXPORT QtNodesTargets + FILE QtNodesTargets.cmake + NAMESPACE QtNodes:: DESTINATION ${INSTALL_CONFIGDIR} ) include(CMakePackageConfigHelpers) -configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/cmake/NodeEditorConfig.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake +configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/cmake/QtNodesConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/QtNodesConfig.cmake INSTALL_DESTINATION ${INSTALL_CONFIGDIR} ) install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/QtNodesConfig.cmake DESTINATION ${INSTALL_CONFIGDIR} ) diff --git a/README.md b/README.md index eca740043..d7eb103e8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Qt NodeEditor -**NodeEditor** is conceived as a general-purpose Qt-based library aimed at +**QtNodes** 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 +**QtNodes** 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 diff --git a/cmake/NodeEditorConfig.cmake.in b/cmake/QtNodesConfig.cmake.in similarity index 54% rename from cmake/NodeEditorConfig.cmake.in rename to cmake/QtNodesConfig.cmake.in index fa3f4ae57..4dce03880 100644 --- a/cmake/NodeEditorConfig.cmake.in +++ b/cmake/QtNodesConfig.cmake.in @@ -1,4 +1,4 @@ -get_filename_component(NodeEditor_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(QtNodes_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) include(CMakeFindDependencyMacro) @@ -10,8 +10,8 @@ find_package(Qt5 REQUIRED COMPONENTS Gui OpenGL) -if(NOT TARGET NodeEditor::nodes) - include("${NodeEditor_CMAKE_DIR}/NodeEditorTargets.cmake") +if(NOT TARGET QtNodes::nodes) + include("${QtNodes_CMAKE_DIR}/QtNodesTargets.cmake") endif() -set(NodeEditor_LIBRARIES NodeEditor::nodes) +set(QtNodes_LIBRARIES QtNodes::nodes) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a225ef22e..6d3e88668 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -26,7 +26,7 @@ target_include_directories(test_nodes target_link_libraries(test_nodes PRIVATE - NodeEditor::nodes + QtNodes::nodes Catch2::Catch2 ${Qt}::Test ) From 88ad0966224d28e3b9fa324d655adba138f78133 Mon Sep 17 00:00:00 2001 From: Dmitry Pinaev Date: Sun, 31 Jan 2021 17:29:36 +0100 Subject: [PATCH 007/127] Build sphinx documentation --- CMakeLists.txt | 5 ++++- cmake/FindSphinx.cmake | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 cmake/FindSphinx.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index ce72aa967..ee21c6820 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.8) project(QtNodes CXX) +set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) + set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) set(CMAKE_DISABLE_SOURCE_CHANGES ON) set(OpenGL_GL_PREFERENCE LEGACY) @@ -252,7 +254,8 @@ install(TARGETS nodes RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) -install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +install(DIRECTORY include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(EXPORT QtNodesTargets FILE QtNodesTargets.cmake diff --git a/cmake/FindSphinx.cmake b/cmake/FindSphinx.cmake new file mode 100644 index 000000000..990f979cb --- /dev/null +++ b/cmake/FindSphinx.cmake @@ -0,0 +1,11 @@ +#Look for an executable called sphinx-build +find_program(SPHINX_EXECUTABLE + NAMES sphinx-build + DOC "Path to sphinx-build executable") + +include(FindPackageHandleStandardArgs) + +#Handle standard arguments to find_package like REQUIRED and QUIET +find_package_handle_standard_args(Sphinx + "Failed to find sphinx-build executable" + SPHINX_EXECUTABLE) From de5da15631848cf0cc38629a12659ac77c2e7389 Mon Sep 17 00:00:00 2001 From: Dmitry Pinaev Date: Sun, 31 Jan 2021 22:30:11 +0100 Subject: [PATCH 008/127] Add read the docs config --- .gitignore | 1 - .readthedocs.yml | 23 + docs/CMakeLists.txt | 72 ++ docs/Doxyfile.in | 2618 +++++++++++++++++++++++++++++++++++++++++ docs/Makefile | 20 + docs/conf.py | 66 ++ docs/index.rst | 5 + docs/make.bat | 35 + docs/requirements.txt | 1 + 9 files changed, 2840 insertions(+), 1 deletion(-) create mode 100644 .readthedocs.yml create mode 100644 docs/CMakeLists.txt create mode 100644 docs/Doxyfile.in create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/requirements.txt diff --git a/.gitignore b/.gitignore index d5272d93e..23a711b3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -*.py *.pyc CMakeLists.txt.user diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..05b217a3f --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,23 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + builder: html + configuration: docs/conf.py + + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: docs/requirements.txt + +# Optionally build your docs in additional formats such as PDF +#formats: + #- pdf + diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 000000000..95b04c1b2 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,72 @@ +find_package(Doxygen REQUIRED) + +# Find all the public headers +get_target_property(QT_NODES_PUBLIC_HEADER_DIR nodes INTERFACE_INCLUDE_DIRECTORIES) + +file(GLOB_RECURSE QT_NODES_PUBLIC_HEADERS ${QT_NODES_PUBLIC_HEADER_DIR}/*.hpp) + +#This will be the main output of our command +set(DOXYGEN_INDEX_FILE ${CMAKE_CURRENT_BINARY_DIR}/html/index.html) + +set(DOXYGEN_INPUT_DIR ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/include) +# Making string joined with " " +list(JOIN DOXYGEN_INPUT_DIR " " DOXYGEN_INPUT_DIR_JOINED) + +set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doxygen) + +set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/html/index.html) + +set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) +set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + +configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY) + + +file(MAKE_DIRECTORY ${DOXYGEN_OUTPUT_DIR}) #Doxygen won't create this for us + +add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE} + DEPENDS ${QT_NODES_PUBLIC_HEADERS} + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} + MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN} + COMMENT "Generating docs" + VERBATIM) + +add_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE}) + + +##################################################################################### + + +find_package(Sphinx REQUIRED) + +set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) +set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/sphinx) +set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html) + +# Only regenerate Sphinx when: +# - Doxygen has rerun +# - Our doc files have been updated +# - The Sphinx config has been updated + + +add_custom_command(OUTPUT ${SPHINX_INDEX_FILE} + COMMAND ${SPHINX_EXECUTABLE} -b html + # Tell Breathe where to find the Doxygen output + -Dbreathe_projects.QtNodes=${DOXYGEN_OUTPUT_DIR}/xml + ${SPHINX_SOURCE} ${SPHINX_BUILD} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + # Other docs files you want to track should go here (or in some variable) + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/index.rst + ${DOXYGEN_INDEX_FILE} + MAIN_DEPENDENCY ${SPHINX_SOURCE}/conf.py + COMMENT "Generating documentation with Sphinx") + +# Nice named target so we can run the job easily +add_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE}) + +# Add an install target to install the docs +include(GNUInstallDirs) +install(DIRECTORY ${SPHINX_BUILD} + DESTINATION ${CMAKE_INSTALL_DOCDIR}) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 000000000..c08a171dc --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,2618 @@ +# Doxyfile 1.9.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "QtNodes" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = 1 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @DOXYGEN_INPUT_DIR_JOINED@ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /