├── .appveyor.yml ├── .codeclimate.yml ├── .github └── workflows │ └── cmake_build.yml ├── .gitignore ├── .readthedocs.yml ├── .travis.yml ├── CMakeLists.txt ├── LICENSE.rst ├── README.rst ├── cmake ├── FindPySide2.cmake ├── FindShiboken2.cmake ├── FindSphinx.cmake ├── PySide2ModuleBuild.cmake └── QtNodesConfig.cmake.in ├── docs ├── CMakeLists.txt ├── Doxyfile.in ├── Makefile ├── _static │ ├── calculator.png │ ├── chigraph.png │ ├── flow.png │ ├── showcase_CANdevStudio.png │ ├── spkgen.png │ ├── style_example.png │ └── vid1.png ├── _templates │ └── layout.html ├── classes.rst ├── conf.py ├── css │ └── tablefix.css ├── development.rst ├── features.rst ├── index.rst ├── license_link.rst ├── make.bat ├── notes.rst ├── overview.rst ├── porting.rst └── requirements.txt ├── examples ├── CMakeLists.txt ├── calculator │ ├── AdditionModel.hpp │ ├── CMakeLists.txt │ ├── DecimalData.hpp │ ├── DivisionModel.hpp │ ├── MathOperationDataModel.cpp │ ├── MathOperationDataModel.hpp │ ├── MultiplicationModel.hpp │ ├── NumberDisplayDataModel.cpp │ ├── NumberDisplayDataModel.hpp │ ├── NumberSourceDataModel.cpp │ ├── NumberSourceDataModel.hpp │ ├── SubtractionModel.hpp │ ├── headless_main.cpp │ └── main.cpp ├── connection_colors │ ├── CMakeLists.txt │ ├── main.cpp │ ├── models.cpp │ └── models.hpp ├── dynamic_ports │ ├── CMakeLists.txt │ ├── DynamicPortsModel.cpp │ ├── DynamicPortsModel.hpp │ ├── PortAddRemoveWidget.cpp │ ├── PortAddRemoveWidget.hpp │ └── main.cpp ├── lock_nodes_and_connections │ ├── CMakeLists.txt │ ├── DataFlowModel.hpp │ ├── DelegateNodeModel.cpp │ ├── DelegateNodeModel.hpp │ └── main.cpp ├── resizable_images │ ├── CMakeLists.txt │ ├── ImageLoaderModel.cpp │ ├── ImageLoaderModel.hpp │ ├── ImageShowModel.cpp │ ├── ImageShowModel.hpp │ ├── PixmapData.hpp │ └── main.cpp ├── simple_graph_model │ ├── CMakeLists.txt │ ├── SimpleGraphModel.cpp │ ├── SimpleGraphModel.hpp │ └── main.cpp ├── styles │ ├── CMakeLists.txt │ ├── main.cpp │ ├── models.cpp │ └── models.hpp ├── text │ ├── CMakeLists.txt │ ├── TextData.hpp │ ├── TextDisplayDataModel.cpp │ ├── TextDisplayDataModel.hpp │ ├── TextSourceDataModel.cpp │ ├── TextSourceDataModel.hpp │ └── main.cpp └── vertical_layout │ ├── CMakeLists.txt │ ├── SimpleGraphModel.cpp │ ├── SimpleGraphModel.hpp │ └── main.cpp ├── external ├── CMakeLists.txt └── Catch2 │ └── CMakeLists.txt ├── include └── QtNodes │ ├── AbstractGraphModel │ ├── AbstractNodePainter │ ├── BasicGraphicsScene │ ├── ConnectionIdUtils │ ├── ConnectionStyle │ ├── DataFlowGraphModel │ ├── DataFlowGraphicsScene │ ├── DefaultNodePainter │ ├── Definitions │ ├── GraphicsView │ ├── GraphicsViewStyle │ ├── NodeData │ ├── NodeDelegateModel │ ├── NodeDelegateModelRegistry │ ├── NodeGeometry │ ├── NodeState │ ├── NodeStyle │ ├── StyleCollection │ └── internal │ ├── AbstractGraphModel.hpp │ ├── AbstractNodeGeometry.hpp │ ├── AbstractNodePainter.hpp │ ├── BasicGraphicsScene.hpp │ ├── Compiler.hpp │ ├── ConnectionGraphicsObject.hpp │ ├── ConnectionIdHash.hpp │ ├── ConnectionIdUtils.hpp │ ├── ConnectionState.hpp │ ├── ConnectionStyle.hpp │ ├── DataFlowGraphModel.hpp │ ├── DataFlowGraphicsScene.hpp │ ├── DefaultNodePainter.hpp │ ├── Definitions.hpp │ ├── Export.hpp │ ├── GraphicsView.hpp │ ├── GraphicsViewStyle.hpp │ ├── NodeData.hpp │ ├── NodeDelegateModel.hpp │ ├── NodeDelegateModelRegistry.hpp │ ├── NodeGraphicsObject.hpp │ ├── NodeState.hpp │ ├── NodeStyle.hpp │ ├── OperatingSystem.hpp │ ├── QStringStdHash.hpp │ ├── QUuidStdHash.hpp │ ├── Serializable.hpp │ ├── Style.hpp │ ├── StyleCollection.hpp │ └── locateNode.hpp ├── resources ├── DefaultStyle.json └── resources.qrc ├── src ├── AbstractGraphModel.cpp ├── AbstractNodeGeometry.cpp ├── BasicGraphicsScene.cpp ├── ConnectionGraphicsObject.cpp ├── ConnectionPainter.cpp ├── ConnectionPainter.hpp ├── ConnectionState.cpp ├── ConnectionStyle.cpp ├── DataFlowGraphModel.cpp ├── DataFlowGraphicsScene.cpp ├── DefaultHorizontalNodeGeometry.cpp ├── DefaultHorizontalNodeGeometry.hpp ├── DefaultNodePainter.cpp ├── DefaultVerticalNodeGeometry.cpp ├── DefaultVerticalNodeGeometry.hpp ├── Definitions.cpp ├── GraphicsView.cpp ├── GraphicsViewStyle.cpp ├── NodeConnectionInteraction.cpp ├── NodeConnectionInteraction.hpp ├── NodeDelegateModel.cpp ├── NodeDelegateModelRegistry.cpp ├── NodeGraphicsObject.cpp ├── NodeState.cpp ├── NodeStyle.cpp ├── StyleCollection.cpp ├── UndoCommands.cpp ├── UndoCommands.hpp └── locateNode.cpp └── test ├── CMakeLists.txt ├── include ├── ApplicationSetup.hpp ├── Stringify.hpp └── StubNodeDataModel.hpp ├── src ├── TestDataModelRegistry.cpp ├── TestDragging.cpp ├── TestFlowScene.cpp └── TestNodeGraphicsObject.cpp └── test_main.cpp /.appveyor.yml: -------------------------------------------------------------------------------- 1 | clone_depth: 5 2 | 3 | environment: 4 | matrix: 5 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 6 | GENERATOR : "Visual Studio 16 2019" 7 | ARCHITECTURE : "-A Win32" 8 | QTDIR: C:\Qt\5.15\msvc2019 9 | QT_MAJOR: 5 10 | PLATFORM: Win32 11 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 12 | GENERATOR : "Visual Studio 16 2019" 13 | ARCHITECTURE : "-A x64" 14 | QTDIR: C:\Qt\6.3\msvc2019_64 15 | QT_MAJOR: 6 16 | PLATFORM: x64 17 | 18 | configuration: 19 | - Release 20 | 21 | install: 22 | - set PATH=%QTDIR%\bin;%PATH% 23 | - set Qt%QT_MAJOR%_DIR=%QTDIR%\lib\cmake\Qt%QT_MAJOR% 24 | - set PATH=%PATH:C:\Program Files\Git\usr\bin=% # trick to remove sh.exe 25 | 26 | before_build: 27 | - mkdir build 28 | - cd build 29 | - mkdir bin 30 | - set OUTPUT_DIR=%cd%\bin 31 | - cmake "-G%GENERATOR%" %ARCHITECTURE% 32 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG="%OUTPUT_DIR%" 33 | -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE="%OUTPUT_DIR%" 34 | -DCMAKE_CXX_FLAGS_INIT="%CMAKE_CXX_FLAGS_INIT%" 35 | -DBUILD_DOCS=OFF 36 | .. 37 | 38 | 39 | build_script: 40 | - cmake --build . 41 | 42 | test_script: 43 | - ps: $env:isX86 = $env:PLATFORM.Contains("x86") 44 | - IF %isX86% == False ctest --output-on-failure -C Debug 45 | 46 | 47 | after_build: 48 | - 7z a examples.zip %APPVEYOR_BUILD_FOLDER%/build/bin 49 | - cmd: cd 50 | - cmd: dir \S \P "examples.zip" 51 | 52 | artifacts: 53 | - path: build\examples.zip 54 | name: ex 55 | 56 | #deploy: 57 | #release: $(APPVEYOR_REPO_TAG_NAME) 58 | #provider: GitHub 59 | #artifact: /.*\.exe/ 60 | #auth_token: 61 | #secure: j0nBV9xVItdG3j6d0gHoyvrzi7TOhAy9/QIeyCbFeP8PTqq7DPr1oYwL5WIkPaXe 62 | #draft: false 63 | #prerelease: false 64 | #on: 65 | #appveyor_repo_tag: true 66 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | fixme: 3 | enabled: true 4 | ratings: 5 | paths: [] 6 | exclude_paths: [] 7 | -------------------------------------------------------------------------------- /.github/workflows/cmake_build.yml: -------------------------------------------------------------------------------- 1 | name: build nodeeditor 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | tags: 9 | - "*" 10 | pull_request: 11 | 12 | jobs: 13 | build-and-test: 14 | 15 | name: ${{ matrix.toolchain }} 16 | runs-on: ${{ matrix.os }} 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | toolchain: 22 | - linux-gcc 23 | - macos-clang 24 | - windows-msvc 25 | 26 | configuration: 27 | - Release 28 | 29 | include: 30 | - toolchain: linux-gcc 31 | os: ubuntu-20.04 32 | compiler: gcc 33 | qt_version: "5.15.2" 34 | modules: "" 35 | 36 | - toolchain: macos-clang 37 | os: macos-latest 38 | compiler: clang 39 | qt_version: "5.15.2" 40 | modules: "" 41 | 42 | - toolchain: windows-msvc 43 | os: windows-latest 44 | compiler: msvc 45 | qt_version: "5.15.2" 46 | modules: "" 47 | 48 | - toolchain: windows-msvc 49 | os: windows-latest 50 | compiler: msvc 51 | qt_version: "6.3.0" 52 | modules: "qt5compat" 53 | 54 | steps: 55 | - name: Checkout Code 56 | uses: actions/checkout@v2 57 | with: 58 | submodules: true 59 | 60 | - name: Install Qt 61 | uses: jurplel/install-qt-action@v3 62 | with: 63 | version: ${{ matrix.qt_version }} 64 | modules: ${{ matrix.modules }} 65 | 66 | - name: Setup (Linux) 67 | if: startsWith (matrix.os, 'ubuntu') 68 | run: sudo apt-get install libxkbcommon-dev 69 | 70 | - name: Setup VS tools (Windows) 71 | if: startsWith (matrix.os, 'windows') 72 | uses: egor-tensin/vs-shell@v2 73 | with: 74 | arch: x64 75 | 76 | - name: Configure (${{ matrix.configuration }}) 77 | run: cmake -S . -Bbuild -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} -DBUILD_DOCS=OFF 78 | 79 | - name: Build with ${{ matrix.compiler }} 80 | run: cmake --build build --config ${{ matrix.configuration }} 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | CMakeLists.txt.user 3 | 4 | build/ 5 | .vscode/ 6 | 7 | tags 8 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | builder: html 11 | configuration: docs/conf.py 12 | 13 | 14 | # Optionally set the version of Python and requirements required to build your docs 15 | python: 16 | version: 3.7 17 | install: 18 | - requirements: docs/requirements.txt 19 | 20 | # Optionally build your docs in additional formats such as PDF 21 | #formats: 22 | #- pdf 23 | 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | matrix: 4 | include: 5 | - os: osx 6 | osx_image: xcode11.3 7 | compiler: clang 8 | env: Qt5_DIR=/usr/local/opt/qt5/lib/cmake/Qt5 9 | 10 | - os: linux 11 | dist: xenial 12 | sudo: false 13 | compiler: clang 14 | env: CXX=clang++-7 CC=clang-7 QT=512 15 | addons: 16 | apt: 17 | sources: 18 | - llvm-toolchain-xenial-7 19 | packages: 20 | - clang-7 21 | 22 | - os: linux 23 | dist: xenial 24 | sudo: false 25 | compiler: gcc 26 | env: 27 | - CXX=g++-7 CC=gcc-7 QT=512 28 | - CXXFLAGS="-fsanitize=address -fno-omit-frame-pointer" 29 | - LDFLAGS=-fsanitize=address 30 | # Too many false positive leaks: 31 | - ASAN_OPTIONS=detect_leaks=0 32 | addons: 33 | apt: 34 | sources: 35 | - ubuntu-toolchain-r-test 36 | packages: 37 | - g++-7 38 | 39 | git: 40 | depth: 10 41 | 42 | before_install: 43 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi 44 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install qt; fi 45 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq ; fi 46 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install build-essential libgl1-mesa-dev ; fi 47 | - if [[ "$QT" == "512" ]]; then sudo add-apt-repository ppa:beineri/opt-qt-5.12.1-xenial -y; fi 48 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq; fi 49 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -yqq install qt${QT}base; source /opt/qt${QT}/bin/qt${QT}-env.sh; fi 50 | 51 | script: 52 | - mkdir build 53 | - cd build 54 | - cmake -DCMAKE_VERBOSE_MAKEFILE=$VERBOSE_BUILD .. && make -j 55 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then xvfb-run --server-args="-screen 0 1024x768x24" ctest --output-on-failure; fi 56 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ctest --output-on-failure; fi 57 | 58 | notifications: 59 | email: false 60 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | BSD-3-Clause license 2 | ==================== 3 | 4 | Copyright (c) 2022, Dmitry Pinaev 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * Neither the name of copyright holder, nor the names of its contributors may 16 | be used to endorse or promote products derived from this software without 17 | specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 25 | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 28 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /cmake/FindSphinx.cmake: -------------------------------------------------------------------------------- 1 | #Look for an executable called sphinx-build 2 | find_program(SPHINX_EXECUTABLE 3 | NAMES sphinx-build 4 | DOC "Path to sphinx-build executable") 5 | 6 | include(FindPackageHandleStandardArgs) 7 | 8 | #Handle standard arguments to find_package like REQUIRED and QUIET 9 | find_package_handle_standard_args(Sphinx 10 | "Failed to find sphinx-build executable" 11 | SPHINX_EXECUTABLE) 12 | 13 | -------------------------------------------------------------------------------- /cmake/QtNodesConfig.cmake.in: -------------------------------------------------------------------------------- 1 | get_filename_component(QtNodes_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 2 | 3 | include(CMakeFindDependencyMacro) 4 | 5 | # NOTE Had to use find_package because find_dependency does not support COMPONENTS or MODULE until 3.8.0 6 | 7 | find_package(Qt6 REQUIRED COMPONENTS 8 | Core 9 | Widgets 10 | Gui 11 | OpenGL) 12 | 13 | if(NOT TARGET QtNodes::QtNodes) 14 | include("${QtNodes_CMAKE_DIR}/QtNodesTargets.cmake") 15 | endif() 16 | 17 | set(QtNodes_LIBRARIES QtNodes::QtNodes) 18 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Doxygen) 2 | 3 | if(DOXYGEN_FOUND) 4 | 5 | # Find all the public headers 6 | get_target_property(QT_NODES_PUBLIC_HEADER_DIR QtNodes INTERFACE_INCLUDE_DIRECTORIES) 7 | 8 | file(GLOB_RECURSE QT_NODES_PUBLIC_HEADERS ${QT_NODES_PUBLIC_HEADER_DIR}/*.hpp) 9 | 10 | #This will be the main output of our command 11 | set(DOXYGEN_INDEX_FILE ${CMAKE_CURRENT_BINARY_DIR}/html/index.html) 12 | 13 | set(DOXYGEN_INPUT_DIR ${PROJECT_SOURCE_DIR}/src 14 | ${PROJECT_SOURCE_DIR}/include 15 | ${PROJECT_SOURCE_DIR}/examples/calculator) 16 | # Making string joined with " " 17 | list(JOIN DOXYGEN_INPUT_DIR " " DOXYGEN_INPUT_DIR_JOINED) 18 | 19 | set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doxygen) 20 | 21 | set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/html/index.html) 22 | 23 | set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) 24 | set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) 25 | 26 | 27 | configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY) 28 | 29 | 30 | file(MAKE_DIRECTORY ${DOXYGEN_OUTPUT_DIR}) #Doxygen won't create this for us 31 | 32 | add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE} 33 | DEPENDS ${QT_NODES_PUBLIC_HEADERS} 34 | COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} 35 | MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN} 36 | COMMENT "Generating docs" 37 | VERBATIM) 38 | 39 | add_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE}) 40 | endif() 41 | 42 | 43 | ##################################################################################### 44 | 45 | 46 | find_package(Sphinx) 47 | 48 | if (Sphinx_FOUND) 49 | set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) 50 | set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/sphinx) 51 | set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html) 52 | 53 | # Only regenerate Sphinx when: 54 | # - Doxygen has rerun 55 | # - Our doc files have been updated 56 | # - The Sphinx config has been updated 57 | 58 | 59 | add_custom_command(OUTPUT ${SPHINX_INDEX_FILE} 60 | COMMAND ${SPHINX_EXECUTABLE} -b html 61 | # Tell Breathe where to find the Doxygen output 62 | -Dbreathe_projects.QtNodes=${DOXYGEN_OUTPUT_DIR}/xml 63 | ${SPHINX_SOURCE} ${SPHINX_BUILD} 64 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 65 | # Other docs files you want to track should go here (or in some variable) 66 | DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/index.rst 67 | ${DOXYGEN_INDEX_FILE} 68 | MAIN_DEPENDENCY ${SPHINX_SOURCE}/conf.py 69 | COMMENT "Generating documentation with Sphinx") 70 | 71 | # Nice named target so we can run the job easily 72 | add_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE}) 73 | 74 | # Add an install target to install the docs 75 | include(GNUInstallDirs) 76 | install(DIRECTORY ${SPHINX_BUILD} 77 | DESTINATION ${CMAKE_INSTALL_DOCDIR}) 78 | endif() 79 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/calculator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Daguerreo/NodeEditor/0d6c318b0396b4d83f7ad5867e4dd108811087be/docs/_static/calculator.png -------------------------------------------------------------------------------- /docs/_static/chigraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Daguerreo/NodeEditor/0d6c318b0396b4d83f7ad5867e4dd108811087be/docs/_static/chigraph.png -------------------------------------------------------------------------------- /docs/_static/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Daguerreo/NodeEditor/0d6c318b0396b4d83f7ad5867e4dd108811087be/docs/_static/flow.png -------------------------------------------------------------------------------- /docs/_static/showcase_CANdevStudio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Daguerreo/NodeEditor/0d6c318b0396b4d83f7ad5867e4dd108811087be/docs/_static/showcase_CANdevStudio.png -------------------------------------------------------------------------------- /docs/_static/spkgen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Daguerreo/NodeEditor/0d6c318b0396b4d83f7ad5867e4dd108811087be/docs/_static/spkgen.png -------------------------------------------------------------------------------- /docs/_static/style_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Daguerreo/NodeEditor/0d6c318b0396b4d83f7ad5867e4dd108811087be/docs/_static/style_example.png -------------------------------------------------------------------------------- /docs/_static/vid1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Daguerreo/NodeEditor/0d6c318b0396b4d83f7ad5867e4dd108811087be/docs/_static/vid1.png -------------------------------------------------------------------------------- /docs/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% set css_files = css_files + ["_static/tablefix.css"] %} 3 | 4 | {% block footer %} 5 | {{ super() }} 6 | @@ -14,4 +15,4 @@ 7 | var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); 8 | })(); 9 | 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /docs/classes.rst: -------------------------------------------------------------------------------- 1 | QtNodes Class Reference 2 | ======================= 3 | 4 | Basic Classes 5 | ------------- 6 | 7 | .. doxygenclass:: QtNodes::AbstractGraphModel 8 | :members: 9 | 10 | .. doxygenstruct:: QtNodes::NodeDataType 11 | :members: 12 | 13 | .. doxygenclass:: QtNodes::NodeData 14 | :members: 15 | 16 | .. doxygenstruct:: QtNodes::ConnectionId 17 | :members: 18 | 19 | .. doxygenclass:: QtNodes::BasicGraphicsScene 20 | :members: 21 | 22 | .. doxygenclass:: QtNodes::GraphicsView 23 | :members: 24 | 25 | .. doxygenclass:: QtNodes::GraphicsViewStyle 26 | :members: 27 | 28 | .. doxygenclass:: QtNodes::NodeGraphicsObject 29 | :members: 30 | 31 | .. doxygenclass:: QtNodes::AbstractNodePainter 32 | :members: 33 | 34 | .. doxygenclass:: QtNodes::DefaultNodePainter 35 | :members: 36 | 37 | .. doxygenclass:: QtNodes::AbstractNodeGeometry 38 | :members: 39 | 40 | .. doxygenclass:: QtNodes::DefaultHorizontalNodeGeometry 41 | :members: 42 | 43 | .. doxygenclass:: QtNodes::DefaultVerticalNodeGeometry 44 | :members: 45 | 46 | .. doxygenclass:: QtNodes::NodeState 47 | :members: 48 | 49 | .. doxygenclass:: QtNodes::NodeStyle 50 | :members: 51 | 52 | .. doxygenclass:: QtNodes::ConnectionGraphicsObject 53 | :members: 54 | 55 | .. doxygenclass:: QtNodes::ConnectionPainter 56 | :members: 57 | 58 | .. doxygenclass:: QtNodes::ConnectionStyle 59 | :members: 60 | 61 | .. doxygenclass:: QtNodes::NodeConnectionInteraction 62 | :members: 63 | 64 | Undo Redo 65 | --------- 66 | 67 | .. doxygenclass:: QtNodes::DeleteCommand 68 | :members: 69 | 70 | .. doxygenclass:: QtNodes::DuplicateCommand 71 | :members: 72 | 73 | .. doxygenclass:: QtNodes::DisconnectCommand 74 | :members: 75 | 76 | .. doxygenclass:: QtNodes::ConnectCommand 77 | :members: 78 | 79 | .. doxygenclass:: QtNodes::MoveNodeCommand 80 | :members: 81 | 82 | Dataflow Classes 83 | ---------------- 84 | 85 | .. doxygenclass:: QtNodes::DataFlowGraphicsScene 86 | :members: 87 | 88 | .. doxygenclass:: QtNodes::DataFlowGraphModel 89 | :members: 90 | 91 | .. doxygenclass:: QtNodes::NodeDelegateModel 92 | :members: 93 | 94 | .. doxygenclass:: QtNodes::NodeDelegateModelRegistry 95 | :members: 96 | 97 | Definitions 98 | ----------- 99 | 100 | .. doxygentypedef:: QtNodes::NodeId 101 | 102 | .. doxygentypedef:: QtNodes::PortIndex 103 | 104 | .. doxygenenum:: QtNodes::NodeRole 105 | 106 | .. doxygenenum:: QtNodes::NodeFlag 107 | 108 | .. doxygenenum:: QtNodes::PortRole 109 | 110 | .. doxygenenum:: QtNodes::ConnectionPolicy 111 | 112 | .. doxygenenum:: QtNodes::PortType 113 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import subprocess, os 2 | 3 | def configureDoxyfile(input_dir, output_dir): 4 | with open('Doxyfile.in', 'r') as file : 5 | filedata = file.read() 6 | 7 | filedata = filedata.replace('@DOXYGEN_INPUT_DIR_JOINED@', input_dir) 8 | filedata = filedata.replace('@DOXYGEN_OUTPUT_DIR@', output_dir) 9 | 10 | with open('Doxyfile', 'w') as file: 11 | file.write(filedata) 12 | 13 | # Check if we're running on Read the Docs' servers 14 | read_the_docs_build = (os.environ.get('READTHEDOCS', None) == 'True') 15 | 16 | breathe_projects = {} 17 | 18 | if read_the_docs_build: 19 | input_dir = "../src ../include ../include/QtNodes/internal" 20 | output_dir = '_build' 21 | configureDoxyfile(input_dir, output_dir) 22 | subprocess.call('doxygen', shell=True) 23 | breathe_projects['QtNodes'] = output_dir + '/xml/' 24 | 25 | 26 | # -- Project information ----------------------------------------------------- 27 | 28 | project = 'QtNodes' 29 | copyright = '2022, Dmitry Pinaev' 30 | author = 'Dmitry Pinaev' 31 | 32 | # The full version, including alpha/beta/rc tags 33 | release = '3.0' 34 | 35 | 36 | # -- General configuration --------------------------------------------------- 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ "breathe", "sphinx_rtd_theme" ] 42 | 43 | 44 | #Breathe configuration 45 | breathe_default_project = "QtNodes" 46 | breathe_default_members = ('members', 'undoc-members') 47 | 48 | # Add any paths that contain templates here, relative to this directory. 49 | templates_path = ['_templates'] 50 | 51 | # List of patterns, relative to source directory, that match files and 52 | # directories to ignore when looking for source files. 53 | # This pattern also affects html_static_path and html_extra_path. 54 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 55 | 56 | 57 | # -- Options for HTML output ------------------------------------------------- 58 | 59 | # The theme to use for HTML and HTML Help pages. See the documentation for 60 | # a list of builtin themes. 61 | # 62 | html_theme = 'sphinx_rtd_theme' 63 | 64 | # Add any paths that contain custom static files (such as style sheets) here, 65 | # relative to this directory. They are copied after the builtin static files, 66 | # so a file named "default.css" will overwrite the builtin "default.css". 67 | html_static_path = ['_static', 'css'] 68 | -------------------------------------------------------------------------------- /docs/css/tablefix.css: -------------------------------------------------------------------------------- 1 | /* override table width restrictions */ 2 | .wy-table-responsive table td, .wy-table-responsive table th { 3 | white-space: normal; 4 | } 5 | 6 | .wy-table-responsive { 7 | margin-bottom: 24px; 8 | max-width: 100%; 9 | overflow: visible; 10 | } 11 | -------------------------------------------------------------------------------- /docs/development.rst: -------------------------------------------------------------------------------- 1 | Development Progress 2 | ==================== 3 | 4 | 5 | - [✅ done] Save/restore to Json. Maybe inherit the GraphModel from Serializable 6 | - [✅ done] Vertical layout 7 | - [✅ done] Dynamic ports 8 | - [✅ done] ``AbstractNodeGeometry``, ``AbstractNodePainter`` 9 | - [✅ done] Website with documentation 10 | - [➡️ work in progress] Unit-Tests 11 | - [➡️ work in progress] Ctrl+D for copying and inserting a selection duplicate 12 | - [⏸ not started] Node groups 13 | - [⏸ not started] ``ConnectionPaintDelegate`` 14 | - [⏸ not started] Check how styles work and what needs to be done. See old pull-requests 15 | - [☝ help needed] Python bindings. Maybe a wrapper using Shiboken 16 | - Python examples 17 | - [☝ help needed] QML front-end 18 | 19 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | QtNodes Documentation 2 | ===================== 3 | 4 | .. image:: /_static/calculator.png 5 | 6 | .. toctree:: 7 | :maxdepth: 3 8 | 9 | overview 10 | features 11 | porting 12 | development 13 | classes 14 | notes 15 | license_link 16 | 17 | Index 18 | ================== 19 | 20 | * :ref:`genindex` 21 | -------------------------------------------------------------------------------- /docs/license_link.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../LICENSE.rst 2 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/notes.rst: -------------------------------------------------------------------------------- 1 | Random not Categorized Notes 2 | ============================ 3 | 4 | Node Geometry 5 | ------------- 6 | 7 | .. code-block:: 8 | 9 | spacing spacing 10 | spacing / \ spacing 11 | \ port port / 12 | | | width | | | | width | | 13 | 0 14 | 0_|_________________________________________ ___ 15 | / \ ___ spacing 16 | | | 17 | | Caption | caption height 18 | | ________________ | ___ 19 | | | | | ___ spacing 20 | | | | | 21 | O In Name | | Out Name O entry 22 | | | | | ___ 23 | | | | | ___ spacing 24 | | | | | 25 | O Another In | | Out Name O 26 | | | | | 27 | | | | | 28 | | | | | 29 | O | | O 30 | | | | | 31 | | |_______________| | 32 | | | 33 | O | 34 | | | 35 | \_________________________________________/ 36 | 37 | 38 | 39 | 40 | Node's size must be recalculated in following cases: 41 | 42 | #. After construction. 43 | #. Embedding the widget. 44 | #. After resizing. 45 | #. Before painting (conditional, depends on whether the font metrics was changed). 46 | #. When incoming data changed (could trigger size changes, maybe in captions). 47 | #. When embedded widget changes its size. 48 | 49 | 50 | -------------------------------------------------------------------------------- /docs/overview.rst: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | Intro 5 | ----- 6 | 7 | 8 | QtNodes is a Qt-based library designed for graphical representation of 9 | the node graphs and performing various operations on them. 10 | 11 | .. image:: /_static/calculator.png 12 | 13 | The project is built with help of the CMake configurator. Therefore it 14 | is quite easy to incorporate the library into any CMake-based Qt 15 | project. 16 | 17 | As of version `3.0` the library uses the Model-View approach. The 18 | central class :cpp:type:`AbstractGraphModel` is a starting point for user graph 19 | models. It wraps the data representing the graph and forwards it 20 | to the `BasicGraphicsScene` -- a class responsible for populating 21 | `QGraphicsObject` items and showing them on the `QGraphicsView` widget. 22 | 23 | The library could be used for two purposes: 24 | 25 | 1. General-purpose graph visulalization and editing. 26 | 2. Computing data in the nodes and propagating it through connections. 27 | 28 | The "headless" mode is also supported. It is possible to create, delete, connect 29 | and disconnect nodes, as well as propagate data, without assigning your 30 | :cpp:type:`AbstractGraphModel` derivative to a :cpp:type:`BasicGraphicsScene`. 31 | 32 | Examples Directory Layout 33 | ------------------------- 34 | 35 | The examples could be found in the directory ``examples``: 36 | 37 | - ``graph``. Demonstrates usage of AbstractGraphModel for general 38 | graph visulalization and editing. 39 | - ``dynamic_ports``. Shows what needs to be done to dynamically create and 40 | destroy node ports. 41 | - ``lock_nodes_and_connections``. Demonstrates two capabilities of 42 | "non-detachable" connectinos and "locked" nodes (non-movable, non-selectable). 43 | - legacy "data flow" examples from versions prior to ``3.0``: 44 | - ``text``. Text is propagated between the nodes. 45 | - ``calculator/main.cpp``. Dataflow-based implementation of the simplest 46 | calculator. We use an advanced model :cpp:type:`QtNodes::DataFlowGraphModel` 47 | capable of storing the registry of NodeDataModel and propagating user data 48 | beween the nodes. 49 | - ``calculator/headless_main.cpp``. The example loads a scene saved by a 50 | GUI-based ``calculator`` example and computes several results without 51 | creating GUI elements. 52 | - ``connection_colors``. Demonstrates the ability to color the 53 | connections in correspondence to the connected data types. 54 | - ``resizable_images``. The examples shows how to embed a widget into nodes and 55 | how to make the nodes resizable. 56 | - ``styles``. The example demonstrates graph style customization. 57 | 58 | 59 | 60 | Feedback Wanted 61 | --------------- 62 | 63 | Make a request on `Github `_ if 64 | something is unclear in the code or in the documentation. 65 | -------------------------------------------------------------------------------- /docs/porting.rst: -------------------------------------------------------------------------------- 1 | Porting Code from Version 2.x 2 | ============================= 3 | 4 | 5 | Renamed Classes 6 | --------------- 7 | 8 | The majority of classes work without significant changes, you might need to 9 | rename some base clasess you are inheriting from. 10 | 11 | 12 | .. table:: 13 | :widths: 10 10 30 14 | 15 | ==================== ========================== ============== 16 | Classes in v2 Classes in v3 Comment 17 | ==================== ========================== ============== 18 | Node -- The node is represented now just by its 19 | internal id which is stored in a central 20 | graph class. 21 | 22 | Connection -- Ditto 23 | 24 | NodeDataModel NodeDelegateModel In new terms a single model defines 25 | the whole graph structure. Hence 26 | smaller per-node models became 27 | delegates. 28 | 29 | DataModelRegistry NodeDelegateModelRegistry See comment above 30 | 31 | FlowView GraphicsView 32 | 33 | FlowScene DataFlowGraphicsScene The new class inherits from 34 | ``BasicGraphicsScene`` 35 | 36 | -- DataFlowGraphModel This is a new central class 37 | that defines the whole graph structure. 38 | The class takes 39 | ``NodeDelegatNodeDelegateModelRegistry`` 40 | in the constructur and 41 | populates delegate models 42 | internally. The graph model 43 | itself is then passed to the 44 | ``DataFlowGraphicsScene``. 45 | 46 | NodePainter DefaultNodePainter Previously a ``NodePainter`` 47 | was created dynamically on 48 | the stack right in the 49 | painting routine. Now 50 | painter is a class instance 51 | constanly living in the 52 | scene. It could be replaced 53 | by a user-defined clas 54 | inherited from 55 | ``AbstractNodePainter`` 56 | ==================== ========================== ============== 57 | 58 | 59 | Removed Features 60 | ---------------- 61 | 62 | 63 | Some minor capabilities were removed in version 3: 64 | 65 | - Warning messages at the bottom of the nodes. They were shown when the data was 66 | incosistent or upon any other error signalized by a node. 67 | The feature was useful in some cases but wasn't visually appealing and caused a 68 | node resize/repainting events. 69 | - Data Type Converters. Such classes were registered among Node Data Models and 70 | made ports of different types compatible. I prefer to leave it up to the 71 | ``AbstractGraphModel`` derivative to decide what could be attached and what 72 | not. See the function ``AbstractGraphModel::connectionPossible``. 73 | 74 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | breathe 3 | sphinx_rtd_theme 4 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(simple_graph_model) 2 | 3 | add_subdirectory(vertical_layout) 4 | 5 | add_subdirectory(calculator) 6 | 7 | add_subdirectory(text) 8 | 9 | add_subdirectory(resizable_images) 10 | 11 | add_subdirectory(styles) 12 | 13 | add_subdirectory(connection_colors) 14 | 15 | add_subdirectory(dynamic_ports) 16 | 17 | add_subdirectory(lock_nodes_and_connections) 18 | 19 | -------------------------------------------------------------------------------- /examples/calculator/AdditionModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MathOperationDataModel.hpp" 4 | #include "DecimalData.hpp" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | /// The model dictates the number of inputs and outputs for the Node. 12 | /// In this example it has no logic. 13 | class AdditionModel : public MathOperationDataModel 14 | { 15 | public: 16 | 17 | ~AdditionModel() = default; 18 | 19 | public: 20 | 21 | QString 22 | caption() const override 23 | { return QStringLiteral("Addition"); } 24 | 25 | QString 26 | name() const override 27 | { return QStringLiteral("Addition"); } 28 | 29 | private: 30 | 31 | void 32 | compute() override 33 | { 34 | PortIndex const outPortIndex = 0; 35 | 36 | auto n1 = _number1.lock(); 37 | auto n2 = _number2.lock(); 38 | 39 | if (n1 && n2) 40 | { 41 | _result = std::make_shared(n1->number() + 42 | n2->number()); 43 | } 44 | else 45 | { 46 | _result.reset(); 47 | } 48 | 49 | Q_EMIT dataUpdated(outPortIndex); 50 | } 51 | 52 | 53 | }; 54 | -------------------------------------------------------------------------------- /examples/calculator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CALC_SOURCE_FILES 2 | main.cpp 3 | MathOperationDataModel.cpp 4 | NumberDisplayDataModel.cpp 5 | NumberSourceDataModel.cpp 6 | ) 7 | 8 | set(CALC_HEADER_FILES 9 | AdditionModel.hpp 10 | DivisionModel.hpp 11 | DecimalData.hpp 12 | MathOperationDataModel.hpp 13 | NumberDisplayDataModel.hpp 14 | NumberSourceDataModel.hpp 15 | SubtractionModel.hpp 16 | ) 17 | 18 | add_executable(calculator 19 | ${CALC_SOURCE_FILES} 20 | ${CALC_HEAEDR_FILES} 21 | ) 22 | 23 | target_link_libraries(calculator QtNodes) 24 | 25 | 26 | 27 | set(HEADLESS_CALC_SOURCE_FILES 28 | headless_main.cpp 29 | MathOperationDataModel.cpp 30 | NumberDisplayDataModel.cpp 31 | NumberSourceDataModel.cpp 32 | ) 33 | 34 | add_executable(headless_calculator 35 | ${HEADLESS_CALC_SOURCE_FILES} 36 | ${CALC_HEAEDR_FILES} 37 | ) 38 | 39 | target_link_libraries(headless_calculator QtNodes) 40 | -------------------------------------------------------------------------------- /examples/calculator/DecimalData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using QtNodes::NodeDataType; 6 | using QtNodes::NodeData; 7 | 8 | /// The class can potentially incapsulate any user data which 9 | /// need to be transferred within the Node Editor graph 10 | class DecimalData : public NodeData 11 | { 12 | public: 13 | 14 | DecimalData() 15 | : _number(0.0) 16 | {} 17 | 18 | DecimalData(double const number) 19 | : _number(number) 20 | {} 21 | 22 | NodeDataType 23 | type() const override 24 | { 25 | return NodeDataType {"decimal", 26 | "Decimal"}; 27 | } 28 | 29 | 30 | double 31 | number() const 32 | { return _number; } 33 | 34 | QString 35 | numberAsText() const 36 | { return QString::number(_number, 'f'); } 37 | 38 | private: 39 | 40 | double _number; 41 | }; 42 | -------------------------------------------------------------------------------- /examples/calculator/DivisionModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MathOperationDataModel.hpp" 4 | #include "DecimalData.hpp" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | /// The model dictates the number of inputs and outputs for the Node. 12 | /// In this example it has no logic. 13 | class DivisionModel : public MathOperationDataModel 14 | { 15 | public: 16 | 17 | virtual 18 | ~DivisionModel() {} 19 | 20 | public: 21 | QString 22 | caption() const override 23 | { return QStringLiteral("Division"); } 24 | 25 | bool 26 | portCaptionVisible(PortType portType, PortIndex portIndex) const override 27 | { 28 | Q_UNUSED(portType); Q_UNUSED(portIndex); 29 | return true; 30 | } 31 | 32 | QString 33 | portCaption(PortType portType, PortIndex portIndex) const override 34 | { 35 | switch (portType) 36 | { 37 | case PortType::In: 38 | if (portIndex == 0) 39 | return QStringLiteral("Dividend"); 40 | else if (portIndex == 1) 41 | return QStringLiteral("Divisor"); 42 | 43 | break; 44 | 45 | case PortType::Out: 46 | return QStringLiteral("Result"); 47 | 48 | default: 49 | break; 50 | } 51 | return QString(); 52 | } 53 | 54 | QString 55 | name() const override 56 | { return QStringLiteral("Division"); } 57 | 58 | private: 59 | 60 | void 61 | compute() override 62 | { 63 | PortIndex const outPortIndex = 0; 64 | 65 | auto n1 = _number1.lock(); 66 | auto n2 = _number2.lock(); 67 | 68 | if (n2 && (n2->number() == 0.0)) 69 | { 70 | //modelValidationState = NodeValidationState::Error; 71 | //modelValidationError = QStringLiteral("Division by zero error"); 72 | _result.reset(); 73 | } 74 | else if (n1 && n2) 75 | { 76 | //modelValidationState = NodeValidationState::Valid; 77 | //modelValidationError = QString(); 78 | _result = std::make_shared(n1->number() / 79 | n2->number()); 80 | } 81 | else 82 | { 83 | //modelValidationState = NodeValidationState::Warning; 84 | //modelValidationError = QStringLiteral("Missing or incorrect inputs"); 85 | _result.reset(); 86 | } 87 | 88 | Q_EMIT dataUpdated(outPortIndex); 89 | } 90 | 91 | }; 92 | -------------------------------------------------------------------------------- /examples/calculator/MathOperationDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "MathOperationDataModel.hpp" 2 | 3 | #include "DecimalData.hpp" 4 | 5 | unsigned int 6 | MathOperationDataModel:: 7 | nPorts(PortType portType) const 8 | { 9 | unsigned int result; 10 | 11 | if (portType == PortType::In) 12 | result = 2; 13 | else 14 | result = 1; 15 | 16 | return result; 17 | } 18 | 19 | 20 | NodeDataType 21 | MathOperationDataModel:: 22 | dataType(PortType, PortIndex) const 23 | { 24 | return DecimalData().type(); 25 | } 26 | 27 | 28 | std::shared_ptr 29 | MathOperationDataModel:: 30 | outData(PortIndex) 31 | { 32 | return std::static_pointer_cast(_result); 33 | } 34 | 35 | 36 | void 37 | MathOperationDataModel:: 38 | setInData(std::shared_ptr data, PortIndex portIndex) 39 | { 40 | auto numberData = 41 | std::dynamic_pointer_cast(data); 42 | 43 | if (!data) 44 | { 45 | Q_EMIT dataInvalidated(0); 46 | } 47 | 48 | if (portIndex == 0) 49 | { 50 | _number1 = numberData; 51 | } 52 | else 53 | { 54 | _number2 = numberData; 55 | } 56 | 57 | compute(); 58 | } 59 | 60 | -------------------------------------------------------------------------------- /examples/calculator/MathOperationDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | class DecimalData; 12 | 13 | using QtNodes::NodeData; 14 | using QtNodes::NodeDelegateModel; 15 | using QtNodes::NodeDataType; 16 | using QtNodes::PortIndex; 17 | using QtNodes::PortType; 18 | 19 | /// The model dictates the number of inputs and outputs for the Node. 20 | /// In this example it has no logic. 21 | class MathOperationDataModel : public NodeDelegateModel 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | 27 | ~MathOperationDataModel() = default; 28 | 29 | public: 30 | 31 | unsigned int 32 | nPorts(PortType portType) const override; 33 | 34 | NodeDataType 35 | dataType(PortType portType, 36 | PortIndex portIndex) const override; 37 | 38 | std::shared_ptr 39 | outData(PortIndex port) override; 40 | 41 | void 42 | setInData(std::shared_ptr data, PortIndex portIndex) override; 43 | 44 | QWidget* 45 | embeddedWidget() override { return nullptr; } 46 | 47 | protected: 48 | 49 | virtual void 50 | compute() = 0; 51 | 52 | protected: 53 | 54 | std::weak_ptr _number1; 55 | std::weak_ptr _number2; 56 | 57 | std::shared_ptr _result; 58 | }; 59 | -------------------------------------------------------------------------------- /examples/calculator/MultiplicationModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "MathOperationDataModel.hpp" 9 | 10 | #include "DecimalData.hpp" 11 | 12 | /// The model dictates the number of inputs and outputs for the Node. 13 | /// In this example it has no logic. 14 | class MultiplicationModel : public MathOperationDataModel 15 | { 16 | public: 17 | 18 | virtual 19 | ~MultiplicationModel() {} 20 | 21 | public: 22 | 23 | QString 24 | caption() const override 25 | { return QStringLiteral("Multiplication"); } 26 | 27 | QString 28 | name() const override 29 | { return QStringLiteral("Multiplication"); } 30 | 31 | private: 32 | 33 | void 34 | compute() override 35 | { 36 | PortIndex const outPortIndex = 0; 37 | 38 | auto n1 = _number1.lock(); 39 | auto n2 = _number2.lock(); 40 | 41 | if (n1 && n2) 42 | { 43 | //modelValidationState = NodeValidationState::Valid; 44 | //modelValidationError = QString(); 45 | _result = std::make_shared(n1->number() * 46 | n2->number()); 47 | } 48 | else 49 | { 50 | //modelValidationState = NodeValidationState::Warning; 51 | //modelValidationError = QStringLiteral("Missing or incorrect inputs"); 52 | _result.reset(); 53 | } 54 | 55 | Q_EMIT dataUpdated(outPortIndex); 56 | } 57 | 58 | 59 | }; 60 | -------------------------------------------------------------------------------- /examples/calculator/NumberDisplayDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "NumberDisplayDataModel.hpp" 2 | 3 | #include 4 | 5 | NumberDisplayDataModel:: 6 | NumberDisplayDataModel() 7 | : _label{nullptr} 8 | { 9 | } 10 | 11 | 12 | unsigned int 13 | NumberDisplayDataModel:: 14 | nPorts(PortType portType) const 15 | { 16 | unsigned int result = 1; 17 | 18 | switch (portType) 19 | { 20 | case PortType::In: 21 | result = 1; 22 | break; 23 | 24 | case PortType::Out: 25 | result = 0; 26 | 27 | default: 28 | break; 29 | } 30 | 31 | return result; 32 | } 33 | 34 | 35 | NodeDataType 36 | NumberDisplayDataModel:: 37 | dataType(PortType, PortIndex) const 38 | { 39 | return DecimalData().type(); 40 | } 41 | 42 | 43 | std::shared_ptr 44 | NumberDisplayDataModel:: 45 | outData(PortIndex) 46 | { 47 | std::shared_ptr ptr; 48 | return ptr; 49 | } 50 | 51 | 52 | void 53 | NumberDisplayDataModel:: 54 | setInData(std::shared_ptr data, PortIndex portIndex) 55 | { 56 | _numberData = std::dynamic_pointer_cast(data); 57 | 58 | if (!_label) 59 | return; 60 | 61 | if (_numberData) 62 | { 63 | _label->setText(_numberData->numberAsText()); 64 | } 65 | else 66 | { 67 | _label->clear(); 68 | } 69 | 70 | _label->adjustSize(); 71 | } 72 | 73 | 74 | QWidget* 75 | NumberDisplayDataModel:: 76 | embeddedWidget() 77 | { 78 | if (!_label) 79 | { 80 | _label = new QLabel(); 81 | _label->setMargin(3); 82 | } 83 | 84 | return _label; 85 | } 86 | 87 | 88 | double 89 | NumberDisplayDataModel:: 90 | number() const 91 | { 92 | if (_numberData) 93 | return _numberData->number(); 94 | 95 | return 0.0; 96 | } 97 | -------------------------------------------------------------------------------- /examples/calculator/NumberDisplayDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include "DecimalData.hpp" 10 | 11 | 12 | using QtNodes::PortType; 13 | using QtNodes::PortIndex; 14 | using QtNodes::NodeData; 15 | using QtNodes::NodeDataType; 16 | using QtNodes::NodeDelegateModel; 17 | 18 | class QLabel; 19 | 20 | /// The model dictates the number of inputs and outputs for the Node. 21 | /// In this example it has no logic. 22 | class NumberDisplayDataModel : public NodeDelegateModel 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | NumberDisplayDataModel(); 28 | 29 | ~NumberDisplayDataModel() = default; 30 | 31 | public: 32 | 33 | QString 34 | caption() const override 35 | { return QStringLiteral("Result"); } 36 | 37 | bool 38 | captionVisible() const override 39 | { return false; } 40 | 41 | QString 42 | name() const override 43 | { return QStringLiteral("Result"); } 44 | 45 | public: 46 | 47 | unsigned int 48 | nPorts(PortType portType) const override; 49 | 50 | NodeDataType 51 | dataType(PortType portType, 52 | PortIndex portIndex) const override; 53 | 54 | std::shared_ptr 55 | outData(PortIndex port) override; 56 | 57 | void 58 | setInData(std::shared_ptr data, 59 | PortIndex portIndex) override; 60 | 61 | QWidget * 62 | embeddedWidget() override; 63 | 64 | double number() const; 65 | 66 | 67 | private: 68 | std::shared_ptr _numberData; 69 | 70 | QLabel* _label; 71 | }; 72 | -------------------------------------------------------------------------------- /examples/calculator/NumberSourceDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "NumberSourceDataModel.hpp" 2 | 3 | #include "DecimalData.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | NumberSourceDataModel:: 11 | NumberSourceDataModel() 12 | : _lineEdit{nullptr} 13 | , _number(std::make_shared(0.0)) 14 | { 15 | } 16 | 17 | 18 | QJsonObject 19 | NumberSourceDataModel:: 20 | save() const 21 | { 22 | QJsonObject modelJson = NodeDelegateModel::save(); 23 | 24 | modelJson["number"] = QString::number(_number->number()); 25 | 26 | return modelJson; 27 | } 28 | 29 | 30 | void 31 | NumberSourceDataModel:: 32 | load(QJsonObject const& p) 33 | { 34 | QJsonValue v = p["number"]; 35 | 36 | if (!v.isUndefined()) 37 | { 38 | QString strNum = v.toString(); 39 | 40 | bool ok; 41 | double d = strNum.toDouble(&ok); 42 | if (ok) 43 | { 44 | _number = std::make_shared(d); 45 | 46 | if (_lineEdit) 47 | _lineEdit->setText(strNum); 48 | } 49 | } 50 | } 51 | 52 | 53 | unsigned int 54 | NumberSourceDataModel:: 55 | nPorts(PortType portType) const 56 | { 57 | unsigned int result = 1; 58 | 59 | switch (portType) 60 | { 61 | case PortType::In: 62 | result = 0; 63 | break; 64 | 65 | case PortType::Out: 66 | result = 1; 67 | 68 | default: 69 | break; 70 | } 71 | 72 | return result; 73 | } 74 | 75 | 76 | void 77 | NumberSourceDataModel:: 78 | onTextEdited(QString const& str) 79 | { 80 | bool ok = false; 81 | 82 | double number = str.toDouble(&ok); 83 | 84 | if (ok) 85 | { 86 | _number = std::make_shared(number); 87 | 88 | Q_EMIT dataUpdated(0); 89 | 90 | } 91 | else 92 | { 93 | Q_EMIT dataInvalidated(0); 94 | } 95 | } 96 | 97 | 98 | NodeDataType 99 | NumberSourceDataModel:: 100 | dataType(PortType, PortIndex) const 101 | { 102 | return DecimalData().type(); 103 | } 104 | 105 | 106 | std::shared_ptr 107 | NumberSourceDataModel:: 108 | outData(PortIndex) 109 | { 110 | return _number; 111 | } 112 | 113 | 114 | QWidget * 115 | NumberSourceDataModel:: 116 | embeddedWidget() 117 | { 118 | if (!_lineEdit) 119 | { 120 | _lineEdit = new QLineEdit(); 121 | 122 | _lineEdit->setValidator(new QDoubleValidator()); 123 | _lineEdit->setMaximumSize(_lineEdit->sizeHint()); 124 | 125 | connect(_lineEdit, &QLineEdit::textChanged, 126 | this, &NumberSourceDataModel::onTextEdited); 127 | 128 | _lineEdit->setText(QString::number(_number->number())); 129 | } 130 | 131 | return _lineEdit; 132 | } 133 | 134 | 135 | void 136 | NumberSourceDataModel:: 137 | setNumber(double n) 138 | { 139 | _number = std::make_shared(n); 140 | 141 | Q_EMIT dataUpdated(0); 142 | 143 | if(_lineEdit) 144 | _lineEdit->setText(QString::number(_number->number())); 145 | } 146 | -------------------------------------------------------------------------------- /examples/calculator/NumberSourceDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | class DecimalData; 10 | 11 | using QtNodes::PortType; 12 | using QtNodes::PortIndex; 13 | using QtNodes::NodeData; 14 | using QtNodes::NodeDataType; 15 | using QtNodes::NodeDelegateModel; 16 | 17 | class QLineEdit; 18 | 19 | /// The model dictates the number of inputs and outputs for the Node. 20 | /// In this example it has no logic. 21 | class NumberSourceDataModel : public NodeDelegateModel 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | NumberSourceDataModel(); 27 | 28 | virtual 29 | ~NumberSourceDataModel() {} 30 | 31 | public: 32 | 33 | QString 34 | caption() const override 35 | { return QStringLiteral("Number Source"); } 36 | 37 | bool 38 | captionVisible() const override 39 | { return false; } 40 | 41 | QString 42 | name() const override 43 | { return QStringLiteral("NumberSource"); } 44 | 45 | public: 46 | 47 | QJsonObject 48 | save() const override; 49 | 50 | void 51 | load(QJsonObject const& p) override; 52 | 53 | public: 54 | 55 | unsigned int 56 | nPorts(PortType portType) const override; 57 | 58 | NodeDataType 59 | dataType(PortType portType, PortIndex portIndex) const override; 60 | 61 | std::shared_ptr 62 | outData(PortIndex port) override; 63 | 64 | void 65 | setInData(std::shared_ptr, PortIndex) override 66 | {} 67 | 68 | QWidget * 69 | embeddedWidget() override; 70 | 71 | public: 72 | void setNumber(double number); 73 | 74 | private Q_SLOTS: 75 | 76 | void 77 | onTextEdited(QString const& string); 78 | 79 | private: 80 | 81 | std::shared_ptr _number; 82 | 83 | QLineEdit* _lineEdit; 84 | }; 85 | -------------------------------------------------------------------------------- /examples/calculator/SubtractionModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "MathOperationDataModel.hpp" 9 | 10 | #include "DecimalData.hpp" 11 | 12 | /// The model dictates the number of inputs and outputs for the Node. 13 | /// In this example it has no logic. 14 | class SubtractionModel : public MathOperationDataModel 15 | { 16 | public: 17 | 18 | virtual 19 | ~SubtractionModel() {} 20 | 21 | public: 22 | 23 | QString 24 | caption() const override 25 | { return QStringLiteral("Subtraction"); } 26 | 27 | virtual bool 28 | portCaptionVisible(PortType portType, PortIndex portIndex) const override 29 | { 30 | Q_UNUSED(portType); Q_UNUSED(portIndex); 31 | return true; 32 | } 33 | 34 | 35 | virtual QString 36 | portCaption(PortType portType, PortIndex portIndex) const override 37 | { 38 | switch (portType) 39 | { 40 | case PortType::In: 41 | if (portIndex == 0) 42 | return QStringLiteral("Minuend"); 43 | else if (portIndex == 1) 44 | return QStringLiteral("Subtrahend"); 45 | 46 | break; 47 | 48 | case PortType::Out: 49 | return QStringLiteral("Result"); 50 | 51 | default: 52 | break; 53 | } 54 | return QString(); 55 | } 56 | 57 | 58 | QString 59 | name() const override 60 | { return QStringLiteral("Subtraction"); } 61 | 62 | private: 63 | 64 | void 65 | compute() override 66 | { 67 | PortIndex const outPortIndex = 0; 68 | 69 | auto n1 = _number1.lock(); 70 | auto n2 = _number2.lock(); 71 | 72 | if (n1 && n2) 73 | { 74 | _result = std::make_shared(n1->number() - 75 | n2->number()); 76 | } 77 | else 78 | { 79 | _result.reset(); 80 | } 81 | 82 | Q_EMIT dataUpdated(outPortIndex); 83 | } 84 | 85 | 86 | }; 87 | -------------------------------------------------------------------------------- /examples/calculator/headless_main.cpp: -------------------------------------------------------------------------------- 1 | #include "AdditionModel.hpp" 2 | #include "DivisionModel.hpp" 3 | #include "MultiplicationModel.hpp" 4 | #include "NumberDisplayDataModel.hpp" 5 | #include "NumberSourceDataModel.hpp" 6 | #include "SubtractionModel.hpp" 7 | 8 | #include 9 | #include 10 | 11 | 12 | using QtNodes::NodeId; 13 | using QtNodes::DataFlowGraphModel; 14 | using QtNodes::NodeDelegateModelRegistry; 15 | 16 | 17 | static std::shared_ptr 18 | registerDataModels() 19 | { 20 | auto ret = std::make_shared(); 21 | ret->registerModel("Sources"); 22 | 23 | ret->registerModel("Displays"); 24 | 25 | ret->registerModel("Operators"); 26 | 27 | ret->registerModel("Operators"); 28 | 29 | ret->registerModel("Operators"); 30 | 31 | ret->registerModel("Operators"); 32 | 33 | return ret; 34 | } 35 | 36 | 37 | /** 38 | * This scene JSON was saved by the normal `calculator` example. 39 | * It has one source number node connected to both inputs of an addition node, 40 | * the result is rendered in a displayer node. 41 | * 42 | * ____________ 43 | * / O[ ] 44 | * _____________ / [ addition ] ______________ 45 | * [ source node ]O [ node ]O- - - -O[ display node ] 46 | * ------------- \ [ ] -------------- 47 | * \ O[____________] 48 | */ 49 | static QString addingNumbersScene( 50 | R"( 51 | { 52 | "nodes": [ 53 | { 54 | "id": 0, 55 | "internal-data": { 56 | "model-name": "NumberSource", 57 | "number": "3" 58 | }, 59 | "position": { 60 | "x": -338, 61 | "y": -160 62 | } 63 | }, 64 | { 65 | "id": 1, 66 | "internal-data": { 67 | "model-name": "Addition" 68 | }, 69 | "position": { 70 | "x": -31, 71 | "y": -264 72 | } 73 | }, 74 | { 75 | "id": 2, 76 | "internal-data": { 77 | "model-name": "Result" 78 | }, 79 | "position": { 80 | "x": 201, 81 | "y": -129 82 | } 83 | } 84 | ], 85 | "connections": [ 86 | { 87 | "inPortIndex": 0, 88 | "intNodeId": 2, 89 | "outNodeId": 1, 90 | "outPortIndex": 0 91 | }, 92 | { 93 | "inPortIndex": 1, 94 | "intNodeId": 1, 95 | "outNodeId": 0, 96 | "outPortIndex": 0 97 | }, 98 | { 99 | "inPortIndex": 0, 100 | "intNodeId": 1, 101 | "outNodeId": 0, 102 | "outPortIndex": 0 103 | } 104 | ] 105 | } 106 | )"); 107 | 108 | 109 | int 110 | main(int argc, char* argv[]) 111 | { 112 | std::shared_ptr registry = registerDataModels(); 113 | 114 | // Here we create a graph model without attaching to any view or scene. 115 | DataFlowGraphModel dataFlowGraphModel(registry); 116 | 117 | // Alternatively you can create the graph by yourself with the functions 118 | // `DataFlowGraphModel::addNode` and `DataFlowGraphModel::addConnection` and 119 | // use the obtained `NodeId` to fetch the `NodeDelegateModel`s 120 | QJsonDocument sceneJson = QJsonDocument::fromJson(addingNumbersScene.toUtf8()); 121 | 122 | dataFlowGraphModel.load(sceneJson.object()); 123 | 124 | qInfo() << "Data Flow graph was created from a json-serialized graph"; 125 | 126 | NodeId const nodeSource = 0; 127 | NodeId const nodeResult = 2; 128 | 129 | 130 | qInfo() << "========================================"; 131 | qInfo() << "Entering the number " << 33.3 << "to the input node"; 132 | dataFlowGraphModel.delegateModel(nodeSource)->setNumber(33.3); 133 | 134 | qInfo() << "Result of the addiion operation: " 135 | << dataFlowGraphModel.delegateModel(nodeResult)->number(); 136 | 137 | qInfo() << "========================================"; 138 | qInfo() << "Entering the number " << -5. << "to the input node"; 139 | dataFlowGraphModel.delegateModel(nodeSource)->setNumber(-5); 140 | 141 | qInfo() << "Result of the addiion operation: " 142 | << dataFlowGraphModel.delegateModel(nodeResult)->number(); 143 | return 0; 144 | } 145 | 146 | -------------------------------------------------------------------------------- /examples/calculator/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include "AdditionModel.hpp" 16 | #include "DivisionModel.hpp" 17 | #include "MultiplicationModel.hpp" 18 | #include "NumberDisplayDataModel.hpp" 19 | #include "NumberSourceDataModel.hpp" 20 | #include "SubtractionModel.hpp" 21 | 22 | using QtNodes::ConnectionStyle; 23 | using QtNodes::DataFlowGraphModel; 24 | using QtNodes::DataFlowGraphicsScene; 25 | using QtNodes::NodeDelegateModelRegistry; 26 | using QtNodes::GraphicsView; 27 | 28 | 29 | static std::shared_ptr 30 | registerDataModels() 31 | { 32 | auto ret = std::make_shared(); 33 | ret->registerModel("Sources"); 34 | 35 | ret->registerModel("Displays"); 36 | 37 | ret->registerModel("Operators"); 38 | 39 | ret->registerModel("Operators"); 40 | 41 | ret->registerModel("Operators"); 42 | 43 | ret->registerModel("Operators"); 44 | 45 | return ret; 46 | } 47 | 48 | 49 | static 50 | void 51 | setStyle() 52 | { 53 | ConnectionStyle::setConnectionStyle( 54 | R"( 55 | { 56 | "ConnectionStyle": { 57 | "ConstructionColor": "gray", 58 | "NormalColor": "black", 59 | "SelectedColor": "gray", 60 | "SelectedHaloColor": "deepskyblue", 61 | "HoveredColor": "deepskyblue", 62 | 63 | "LineWidth": 3.0, 64 | "ConstructionLineWidth": 2.0, 65 | "PointDiameter": 10.0, 66 | 67 | "UseDataDefinedColors": true 68 | } 69 | } 70 | )"); 71 | } 72 | 73 | 74 | int 75 | main(int argc, char* argv[]) 76 | { 77 | QApplication app(argc, argv); 78 | 79 | setStyle(); 80 | 81 | std::shared_ptr registry = registerDataModels(); 82 | 83 | QWidget mainWidget; 84 | 85 | auto menuBar = new QMenuBar(); 86 | QMenu* menu = menuBar->addMenu("File"); 87 | auto saveAction = menu->addAction("Save Scene"); 88 | auto loadAction = menu->addAction("Load Scene"); 89 | 90 | QVBoxLayout* l = new QVBoxLayout(&mainWidget); 91 | 92 | DataFlowGraphModel dataFlowGraphModel(registry); 93 | 94 | l->addWidget(menuBar); 95 | auto scene = new DataFlowGraphicsScene(dataFlowGraphModel, 96 | &mainWidget); 97 | 98 | auto view = new GraphicsView(scene); 99 | l->addWidget(view); 100 | l->setContentsMargins(0, 0, 0, 0); 101 | l->setSpacing(0); 102 | 103 | QObject::connect(saveAction, &QAction::triggered, 104 | scene, &DataFlowGraphicsScene::save); 105 | 106 | QObject::connect(loadAction, &QAction::triggered, 107 | scene, &DataFlowGraphicsScene::load); 108 | 109 | QObject::connect(scene, &DataFlowGraphicsScene::sceneLoaded, 110 | view, &GraphicsView::centerScene); 111 | 112 | mainWidget.setWindowTitle("Data Flow: simplest calculator"); 113 | mainWidget.resize(800, 600); 114 | // Center window. 115 | mainWidget.move(QApplication::primaryScreen()->availableGeometry().center() - mainWidget.rect().center()); 116 | mainWidget.showNormal(); 117 | 118 | return app.exec(); 119 | } 120 | 121 | -------------------------------------------------------------------------------- /examples/connection_colors/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | file(GLOB_RECURSE HPPS ./*.hpp ) 3 | 4 | add_executable(connection_colors ${CPPS} ${HPPS}) 5 | 6 | target_link_libraries(connection_colors QtNodes) 7 | -------------------------------------------------------------------------------- /examples/connection_colors/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "models.hpp" 11 | 12 | using QtNodes::NodeDelegateModelRegistry; 13 | using QtNodes::DataFlowGraphModel; 14 | using QtNodes::DataFlowGraphicsScene; 15 | using QtNodes::GraphicsView; 16 | using QtNodes::ConnectionStyle; 17 | 18 | static std::shared_ptr 19 | registerDataModels() 20 | { 21 | auto ret = std::make_shared(); 22 | 23 | ret->registerModel(); 24 | 25 | /* 26 | We could have more models registered. 27 | All of them become items in the context meny of the scene. 28 | 29 | ret->registerModel(); 30 | ret->registerModel(); 31 | 32 | */ 33 | 34 | return ret; 35 | } 36 | 37 | 38 | static 39 | void 40 | setStyle() 41 | { 42 | ConnectionStyle::setConnectionStyle( 43 | R"( 44 | { 45 | "ConnectionStyle": { 46 | "UseDataDefinedColors": true 47 | } 48 | } 49 | )"); 50 | } 51 | 52 | 53 | //------------------------------------------------------------------------------ 54 | 55 | int 56 | main(int argc, char* argv[]) 57 | { 58 | QApplication app(argc, argv); 59 | 60 | setStyle(); 61 | 62 | std::shared_ptr registry = registerDataModels(); 63 | DataFlowGraphModel dataFlowGraphModel(registry); 64 | 65 | DataFlowGraphicsScene scene(dataFlowGraphModel); 66 | 67 | GraphicsView view(&scene); 68 | 69 | view.setWindowTitle("Node-based flow editor"); 70 | view.resize(800, 600); 71 | view.show(); 72 | 73 | return app.exec(); 74 | } 75 | 76 | -------------------------------------------------------------------------------- /examples/connection_colors/models.cpp: -------------------------------------------------------------------------------- 1 | #include "models.hpp" 2 | 3 | // For some reason CMake could not generate moc-files correctly 4 | // without having a cpp for an QObject from hpp. 5 | -------------------------------------------------------------------------------- /examples/connection_colors/models.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | using QtNodes::NodeData; 11 | using QtNodes::NodeDataType; 12 | using QtNodes::NodeDelegateModel; 13 | using QtNodes::PortType; 14 | using QtNodes::PortIndex; 15 | 16 | /// The class can potentially incapsulate any user data which 17 | /// need to be transferred within the Node Editor graph 18 | class MyNodeData : public NodeData 19 | { 20 | public: 21 | NodeDataType 22 | type() const override 23 | { 24 | return NodeDataType {"MyNodeData", 25 | "My Node Data"}; 26 | } 27 | 28 | }; 29 | 30 | 31 | class SimpleNodeData : public NodeData 32 | { 33 | public: 34 | NodeDataType 35 | type() const override 36 | { 37 | return NodeDataType {"SimpleData", 38 | "Simple Data"}; 39 | } 40 | 41 | }; 42 | 43 | //------------------------------------------------------------------------------ 44 | 45 | /// The model dictates the number of inputs and outputs for the Node. 46 | /// In this example it has no logic. 47 | class NaiveDataModel : public NodeDelegateModel 48 | { 49 | Q_OBJECT 50 | 51 | public: 52 | 53 | virtual 54 | ~NaiveDataModel() {} 55 | 56 | public: 57 | 58 | QString 59 | caption() const override 60 | { 61 | return QString("Naive Data Model"); 62 | } 63 | 64 | 65 | QString 66 | name() const override 67 | { return QString("NaiveDataModel"); } 68 | 69 | public: 70 | 71 | unsigned int 72 | nPorts(PortType const portType) const override 73 | { 74 | unsigned int result = 1; 75 | 76 | switch (portType) 77 | { 78 | case PortType::In: 79 | result = 2; 80 | break; 81 | 82 | case PortType::Out: 83 | result = 2; 84 | break; 85 | case PortType::None: 86 | break; 87 | } 88 | 89 | return result; 90 | } 91 | 92 | 93 | NodeDataType 94 | dataType(PortType const portType, 95 | PortIndex const portIndex) const override 96 | { 97 | switch (portType) 98 | { 99 | case PortType::In: 100 | switch (portIndex) 101 | { 102 | case 0: 103 | return MyNodeData().type(); 104 | case 1: 105 | return SimpleNodeData().type(); 106 | } 107 | break; 108 | 109 | case PortType::Out: 110 | switch (portIndex) 111 | { 112 | case 0: 113 | return MyNodeData().type(); 114 | case 1: 115 | return SimpleNodeData().type(); 116 | } 117 | break; 118 | 119 | case PortType::None: 120 | break; 121 | } 122 | // FIXME: control may reach end of non-void function [-Wreturn-type] 123 | return NodeDataType(); 124 | } 125 | 126 | 127 | std::shared_ptr 128 | outData(PortIndex const port) override 129 | { 130 | if (port < 1) 131 | return std::make_shared(); 132 | 133 | return std::make_shared(); 134 | } 135 | 136 | 137 | void 138 | setInData(std::shared_ptr, PortIndex const) override 139 | { 140 | // 141 | } 142 | 143 | 144 | QWidget* 145 | embeddedWidget() override { return nullptr; } 146 | }; 147 | -------------------------------------------------------------------------------- /examples/dynamic_ports/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | 3 | add_executable(dynamic_ports ${CPPS}) 4 | 5 | target_link_libraries(dynamic_ports QtNodes) 6 | -------------------------------------------------------------------------------- /examples/dynamic_ports/DynamicPortsModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | 11 | using ConnectionId = QtNodes::ConnectionId; 12 | using ConnectionPolicy = QtNodes::ConnectionPolicy; 13 | using NodeFlag = QtNodes::NodeFlag; 14 | using NodeId = QtNodes::NodeId; 15 | using NodeRole = QtNodes::NodeRole; 16 | using PortIndex = QtNodes::PortIndex; 17 | using PortRole = QtNodes::PortRole; 18 | using PortType = QtNodes::PortType; 19 | using StyleCollection = QtNodes::StyleCollection; 20 | using QtNodes::InvalidNodeId; 21 | 22 | class PortAddRemoveWidget; 23 | 24 | /** 25 | * The class implements a bare minimum required to demonstrate a model-based 26 | * graph. 27 | */ 28 | class DynamicPortsModel : public QtNodes::AbstractGraphModel 29 | { 30 | Q_OBJECT 31 | public: 32 | struct NodeGeometryData 33 | { 34 | QSize size; 35 | QPointF pos; 36 | }; 37 | 38 | public: 39 | DynamicPortsModel(); 40 | 41 | ~DynamicPortsModel() override; 42 | 43 | std::unordered_set 44 | allNodeIds() const override; 45 | 46 | std::unordered_set 47 | allConnectionIds(NodeId const nodeId) const override; 48 | 49 | std::unordered_set 50 | connections(NodeId nodeId, 51 | PortType portType, 52 | PortIndex portIndex) const override; 53 | 54 | bool 55 | connectionExists(ConnectionId const connectionId) const override; 56 | 57 | NodeId 58 | addNode(QString const nodeType = QString()) override; 59 | 60 | /** 61 | * Connection is possible when graph contains no connectivity data 62 | * in both directions `Out -> In` and `In -> Out`. 63 | */ 64 | bool 65 | connectionPossible(ConnectionId const connectionId) const override; 66 | 67 | void 68 | addConnection(ConnectionId const connectionId) override; 69 | 70 | bool 71 | nodeExists(NodeId const nodeId) const override; 72 | 73 | QVariant 74 | nodeData(NodeId nodeId, NodeRole role) const override; 75 | 76 | bool 77 | setNodeData(NodeId nodeId, 78 | NodeRole role, 79 | QVariant value) override; 80 | 81 | QVariant 82 | portData(NodeId nodeId, 83 | PortType portType, 84 | PortIndex portIndex, 85 | PortRole role) const override; 86 | 87 | bool 88 | setPortData(NodeId nodeId, 89 | PortType portType, 90 | PortIndex portIndex, 91 | QVariant const& value, 92 | PortRole role = PortRole::Data) override; 93 | 94 | bool 95 | deleteConnection(ConnectionId const connectionId) override; 96 | 97 | bool 98 | deleteNode(NodeId const nodeId) override; 99 | 100 | QJsonObject 101 | saveNode(NodeId const) const override; 102 | 103 | QJsonObject 104 | save() const; 105 | 106 | /// @brief Creates a new node based on the informatoin in `nodeJson`. 107 | /** 108 | * @param nodeJson conains a `NodeId`, node's position, internal node 109 | * information. 110 | */ 111 | void 112 | loadNode(QJsonObject const & nodeJson) override; 113 | 114 | void 115 | load(QJsonObject const &jsonDocument); 116 | 117 | void 118 | addPort(NodeId nodeId, 119 | PortType portType, 120 | PortIndex portIndex); 121 | 122 | void 123 | removePort(NodeId nodeId, 124 | PortType portType, 125 | PortIndex first); 126 | 127 | NodeId newNodeId() override { return _nextNodeId++; } 128 | 129 | private: 130 | std::unordered_set _nodeIds; 131 | 132 | /// [Important] This is a user defined data structure backing your model. 133 | /// In your case it could be anything else representing a graph, for example, a 134 | /// table. Or a collection of structs with pointers to each other. Or an 135 | /// abstract syntax tree, you name it. 136 | std::unordered_set _connectivity; 137 | 138 | mutable std::unordered_map 139 | _nodeGeometryData; 140 | 141 | struct NodePortCount 142 | { 143 | unsigned int in = 0; 144 | unsigned int out = 0; 145 | }; 146 | 147 | PortAddRemoveWidget* widget(NodeId) const; 148 | 149 | mutable std::unordered_map _nodePortCounts; 150 | mutable std::unordered_map _nodeWidgets; 151 | 152 | /// A convenience variable needed for generating unique node ids. 153 | unsigned int _nextNodeId; 154 | }; 155 | -------------------------------------------------------------------------------- /examples/dynamic_ports/PortAddRemoveWidget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | 12 | using QtNodes::PortType; 13 | using QtNodes::PortIndex; 14 | using QtNodes::NodeId; 15 | 16 | class DynamicPortsModel; 17 | 18 | 19 | /** 20 | * PortAddRemoveWidget 21 | * 22 | * ``` 23 | * _left _right 24 | * layout layout 25 | * ---------------------------------------- 26 | * | | | | 27 | * | [+] [-] | | [+] [-] | 28 | * | | | | 29 | * | [+] [-] | | [+] [-] | 30 | * | | | | 31 | * | [+] [-] | | [+] [-] | 32 | * | | | | 33 | * | [+] [-] | | | 34 | * | | | | 35 | * |_________|__________________|_________| 36 | * ``` 37 | * 38 | * The widget has two main vertical layouts containing groups of buttons for 39 | * adding and removing ports. Each such a `[+] [-]` group is contained in a 40 | * dedicated QHVBoxLayout. 41 | * 42 | */ 43 | class PortAddRemoveWidget : public QWidget 44 | { 45 | Q_OBJECT 46 | public: 47 | PortAddRemoveWidget(unsigned int nInPorts, 48 | unsigned int nOutPorts, 49 | NodeId nodeId, 50 | DynamicPortsModel & model, 51 | QWidget * parent = nullptr); 52 | 53 | ~PortAddRemoveWidget(); 54 | 55 | 56 | /** 57 | * Called from constructor, creates all button groups according to models'port 58 | * counts. 59 | */ 60 | void 61 | populateButtons(PortType portType, unsigned int nPorts); 62 | 63 | /** 64 | * Adds a single `[+][-]` button group to a given layout. 65 | */ 66 | QHBoxLayout* 67 | addButtonGroupToLayout(QVBoxLayout * vbl, unsigned int portIndex); 68 | 69 | /** 70 | * Removes a single `[+][-]` button group from a given layout. 71 | */ 72 | void 73 | removeButtonGroupFromLayout(QVBoxLayout * vbl, unsigned int portIndex); 74 | 75 | private Q_SLOTS: 76 | void onPlusClicked(); 77 | 78 | void onMinusClicked(); 79 | 80 | private: 81 | /** 82 | * @param buttonIndex is the index of a button in the layout. 83 | * Plus button has the index 0. 84 | * Minus button has the index 1. 85 | */ 86 | std::pair 87 | findWhichPortWasClicked(QObject* sender, int const buttonIndex); 88 | 89 | private: 90 | NodeId const _nodeId; 91 | DynamicPortsModel & _model; 92 | 93 | QVBoxLayout* _left; 94 | QVBoxLayout* _right; 95 | }; 96 | -------------------------------------------------------------------------------- /examples/lock_nodes_and_connections/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | 3 | add_executable(lock_nodes_and_connections ${CPPS}) 4 | 5 | target_link_libraries(lock_nodes_and_connections QtNodes) 6 | -------------------------------------------------------------------------------- /examples/lock_nodes_and_connections/DataFlowModel.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | using QtNodes::ConnectionId; 5 | using QtNodes::DataFlowGraphModel; 6 | using QtNodes::NodeDelegateModelRegistry; 7 | using QtNodes::NodeFlag; 8 | using QtNodes::NodeFlags; 9 | using QtNodes::NodeId; 10 | 11 | class DataFlowModel : public DataFlowGraphModel 12 | { 13 | public: 14 | DataFlowModel(std::shared_ptr registry) 15 | : DataFlowGraphModel(std::move(registry)) 16 | , _detachPossible{true} 17 | , _nodesLocked{false} 18 | {} 19 | 20 | bool detachPossible(ConnectionId const) const override 21 | { 22 | return _detachPossible; 23 | } 24 | 25 | void setDetachPossible(bool d = true) 26 | { 27 | _detachPossible = d; 28 | } 29 | 30 | 31 | //---- 32 | 33 | NodeFlags 34 | nodeFlags(NodeId nodeId) const override 35 | { 36 | auto basicFlags = DataFlowGraphModel::nodeFlags(nodeId); 37 | 38 | if (_nodesLocked) 39 | basicFlags |= NodeFlag::Locked; 40 | 41 | return basicFlags; 42 | } 43 | 44 | 45 | void setNodesLocked(bool b = true) 46 | { 47 | _nodesLocked = b; 48 | 49 | for (NodeId nodeId : allNodeIds()) 50 | { 51 | Q_EMIT nodeFlagsUpdated(nodeId); 52 | } 53 | } 54 | 55 | private: 56 | bool _detachPossible; 57 | 58 | bool _nodesLocked; 59 | }; 60 | -------------------------------------------------------------------------------- /examples/lock_nodes_and_connections/DelegateNodeModel.cpp: -------------------------------------------------------------------------------- 1 | #include "DelegateNodeModel.hpp" 2 | 3 | // For some reason CMake could not generate moc-files correctly 4 | // without having a cpp for an QObject from hpp. 5 | -------------------------------------------------------------------------------- /examples/lock_nodes_and_connections/DelegateNodeModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | using QtNodes::NodeData; 11 | using QtNodes::NodeDataType; 12 | using QtNodes::NodeDelegateModel; 13 | using QtNodes::PortType; 14 | using QtNodes::PortIndex; 15 | 16 | class SimpleNodeData : public NodeData 17 | { 18 | public: 19 | NodeDataType 20 | type() const override 21 | { 22 | return NodeDataType {"SimpleData", 23 | "Simple Data"}; 24 | } 25 | }; 26 | 27 | 28 | /// The model dictates the number of inputs and outputs for the Node. 29 | /// In this example it has no logic. 30 | class SimpleDataModel : public NodeDelegateModel 31 | { 32 | Q_OBJECT 33 | 34 | public: 35 | QString 36 | caption() const override 37 | { 38 | return QString("Simple Data Model"); 39 | } 40 | 41 | QString 42 | name() const override 43 | { return QString("SimpleDataModel"); } 44 | 45 | public: 46 | unsigned int 47 | nPorts(PortType const portType) const override 48 | { 49 | return 2; 50 | } 51 | 52 | NodeDataType 53 | dataType(PortType const portType, 54 | PortIndex const portIndex) const override 55 | { 56 | return SimpleNodeData().type(); 57 | } 58 | 59 | std::shared_ptr 60 | outData(PortIndex const port) override 61 | { 62 | return std::make_shared(); 63 | } 64 | 65 | void 66 | setInData(std::shared_ptr, PortIndex const) override 67 | {} 68 | 69 | QWidget* 70 | embeddedWidget() override { return nullptr; } 71 | }; 72 | -------------------------------------------------------------------------------- /examples/lock_nodes_and_connections/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "DataFlowModel.hpp" 13 | #include "DelegateNodeModel.hpp" 14 | 15 | 16 | using QtNodes::DataFlowGraphicsScene; 17 | using QtNodes::GraphicsView; 18 | using QtNodes::NodeDelegateModelRegistry; 19 | using QtNodes::NodeRole; 20 | 21 | 22 | static std::shared_ptr 23 | registerDataModels() 24 | { 25 | auto ret = std::make_shared(); 26 | 27 | ret->registerModel(); 28 | 29 | return ret; 30 | } 31 | 32 | int 33 | main(int argc, char *argv[]) 34 | { 35 | QApplication app(argc, argv); 36 | 37 | DataFlowModel graphModel(registerDataModels()); 38 | 39 | // Initialize and connect two nodes. 40 | { 41 | NodeId id1 = graphModel.addNode(SimpleNodeData().type().id); 42 | graphModel.setNodeData(id1, NodeRole::Position, QPointF(0, 0)); 43 | 44 | NodeId id2 = graphModel.addNode(SimpleNodeData().type().id); 45 | graphModel.setNodeData(id2, NodeRole::Position, QPointF(300, 300)); 46 | 47 | graphModel.addConnection(ConnectionId{id1, 0, id2, 0}); 48 | } 49 | 50 | auto scene = new DataFlowGraphicsScene(graphModel); 51 | 52 | QWidget window; 53 | 54 | QHBoxLayout * l = new QHBoxLayout(&window); 55 | 56 | GraphicsView view(scene); 57 | 58 | l->addWidget(&view); 59 | 60 | 61 | 62 | QGroupBox *groupBox = new QGroupBox("Options"); 63 | 64 | QCheckBox *cb1 = new QCheckBox("Nodes are locked"); 65 | QCheckBox *cb2 = new QCheckBox("Connections detachable"); 66 | cb2->setChecked(true); 67 | 68 | QVBoxLayout * vbl = new QVBoxLayout; 69 | vbl->addWidget(cb1); 70 | vbl->addWidget(cb2); 71 | vbl->addStretch(); 72 | groupBox->setLayout(vbl); 73 | 74 | QObject::connect(cb1, 75 | &QCheckBox::stateChanged, 76 | [&graphModel](int state) 77 | { 78 | graphModel.setNodesLocked(state == Qt::Checked); 79 | }); 80 | 81 | QObject::connect(cb2, 82 | &QCheckBox::stateChanged, 83 | [&graphModel](int state) 84 | { 85 | graphModel.setDetachPossible(state == Qt::Checked); 86 | }); 87 | 88 | l->addWidget(groupBox); 89 | 90 | 91 | window.setWindowTitle("Locked Nodes and Connections"); 92 | window.resize(800, 600); 93 | 94 | // Center window. 95 | window.move(QApplication::primaryScreen()->availableGeometry().center() - view.rect().center()); 96 | window.showNormal(); 97 | 98 | return app.exec(); 99 | } 100 | 101 | -------------------------------------------------------------------------------- /examples/resizable_images/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | file(GLOB_RECURSE HPPS ./*.hpp ) 3 | 4 | add_executable(resizable_images ${CPPS} ${HPPS}) 5 | 6 | target_link_libraries(resizable_images QtNodes) 7 | -------------------------------------------------------------------------------- /examples/resizable_images/ImageLoaderModel.cpp: -------------------------------------------------------------------------------- 1 | #include "ImageLoaderModel.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | ImageLoaderModel:: 9 | ImageLoaderModel() 10 | : _label(new QLabel("Double click to load image")) 11 | { 12 | _label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); 13 | 14 | QFont f = _label->font(); 15 | f.setBold(true); 16 | f.setItalic(true); 17 | 18 | _label->setFont(f); 19 | 20 | _label->setFixedSize(200, 200); 21 | 22 | _label->installEventFilter(this); 23 | } 24 | 25 | 26 | unsigned int 27 | ImageLoaderModel:: 28 | nPorts(PortType portType) const 29 | { 30 | unsigned int result = 1; 31 | 32 | switch (portType) 33 | { 34 | case PortType::In: 35 | result = 0; 36 | break; 37 | 38 | case PortType::Out: 39 | result = 1; 40 | 41 | default: 42 | break; 43 | } 44 | 45 | return result; 46 | } 47 | 48 | 49 | bool 50 | ImageLoaderModel:: 51 | eventFilter(QObject *object, QEvent *event) 52 | { 53 | if (object == _label) 54 | { 55 | int w = _label->width(); 56 | int h = _label->height(); 57 | 58 | if (event->type() == QEvent::MouseButtonPress) 59 | { 60 | 61 | QString fileName = 62 | QFileDialog::getOpenFileName(nullptr, 63 | tr("Open Image"), 64 | QDir::homePath(), 65 | tr("Image Files (*.png *.jpg *.bmp)")); 66 | 67 | _pixmap = QPixmap(fileName); 68 | 69 | _label->setPixmap(_pixmap.scaled(w, h, Qt::KeepAspectRatio)); 70 | 71 | Q_EMIT dataUpdated(0); 72 | 73 | return true; 74 | } 75 | else if (event->type() == QEvent::Resize) 76 | { 77 | if (!_pixmap.isNull()) 78 | _label->setPixmap(_pixmap.scaled(w, h, Qt::KeepAspectRatio)); 79 | } 80 | } 81 | 82 | return false; 83 | } 84 | 85 | 86 | NodeDataType 87 | ImageLoaderModel:: 88 | dataType(PortType const, PortIndex const) const 89 | { 90 | return PixmapData().type(); 91 | } 92 | 93 | 94 | std::shared_ptr 95 | ImageLoaderModel:: 96 | outData(PortIndex) 97 | { 98 | return std::make_shared(_pixmap); 99 | } 100 | -------------------------------------------------------------------------------- /examples/resizable_images/ImageLoaderModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "PixmapData.hpp" 12 | 13 | using QtNodes::PortType; 14 | using QtNodes::PortIndex; 15 | using QtNodes::NodeData; 16 | using QtNodes::NodeDataType; 17 | using QtNodes::NodeDelegateModel; 18 | 19 | /// The model dictates the number of inputs and outputs for the Node. 20 | /// In this example it has no logic. 21 | class ImageLoaderModel : public NodeDelegateModel 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | ImageLoaderModel(); 27 | 28 | ~ImageLoaderModel() = default; 29 | 30 | public: 31 | 32 | QString 33 | caption() const override { return QString("Image Source"); } 34 | 35 | QString 36 | name() const override { return QString("ImageLoaderModel"); } 37 | 38 | public: 39 | 40 | virtual QString 41 | modelName() const { return QString("Source Image"); } 42 | 43 | unsigned int 44 | nPorts(PortType const portType) const override; 45 | 46 | NodeDataType 47 | dataType(PortType const portType, 48 | PortIndex const portIndex) const override; 49 | 50 | std::shared_ptr 51 | outData(PortIndex const port) override; 52 | 53 | void 54 | setInData(std::shared_ptr, 55 | PortIndex const portIndex) override {} 56 | 57 | QWidget* 58 | embeddedWidget() override { return _label; } 59 | 60 | bool 61 | resizable() const override { return true; } 62 | 63 | protected: 64 | 65 | bool 66 | eventFilter(QObject* object, QEvent* event) override; 67 | 68 | private: 69 | 70 | QLabel* _label; 71 | 72 | QPixmap _pixmap; 73 | }; 74 | -------------------------------------------------------------------------------- /examples/resizable_images/ImageShowModel.cpp: -------------------------------------------------------------------------------- 1 | #include "ImageShowModel.hpp" 2 | 3 | #include "PixmapData.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | ImageShowModel:: 13 | ImageShowModel() 14 | : _label(new QLabel("Image will appear here")) 15 | { 16 | _label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); 17 | 18 | QFont f = _label->font(); 19 | f.setBold(true); 20 | f.setItalic(true); 21 | 22 | _label->setFont(f); 23 | 24 | _label->setFixedSize(200, 200); 25 | 26 | _label->installEventFilter(this); 27 | } 28 | 29 | 30 | unsigned int 31 | ImageShowModel:: 32 | nPorts(PortType portType) const 33 | { 34 | unsigned int result = 1; 35 | 36 | switch (portType) 37 | { 38 | case PortType::In: 39 | result = 1; 40 | break; 41 | 42 | case PortType::Out: 43 | result = 1; 44 | 45 | default: 46 | break; 47 | } 48 | 49 | return result; 50 | } 51 | 52 | 53 | bool 54 | ImageShowModel:: 55 | eventFilter(QObject* object, QEvent* event) 56 | { 57 | if (object == _label) 58 | { 59 | int w = _label->width(); 60 | int h = _label->height(); 61 | 62 | if (event->type() == QEvent::Resize) 63 | { 64 | auto d = std::dynamic_pointer_cast(_nodeData); 65 | if (d) 66 | { 67 | _label->setPixmap(d->pixmap().scaled(w, h, Qt::KeepAspectRatio)); 68 | } 69 | } 70 | } 71 | 72 | return false; 73 | } 74 | 75 | 76 | NodeDataType 77 | ImageShowModel:: 78 | dataType(PortType const, PortIndex const) const 79 | { 80 | return PixmapData().type(); 81 | } 82 | 83 | 84 | std::shared_ptr 85 | ImageShowModel:: 86 | outData(PortIndex) 87 | { 88 | return _nodeData; 89 | } 90 | 91 | 92 | void 93 | ImageShowModel:: 94 | setInData(std::shared_ptr nodeData, PortIndex const) 95 | { 96 | _nodeData = nodeData; 97 | 98 | if (_nodeData) 99 | { 100 | auto d = std::dynamic_pointer_cast(_nodeData); 101 | 102 | int w = _label->width(); 103 | int h = _label->height(); 104 | 105 | _label->setPixmap(d->pixmap().scaled(w, h, Qt::KeepAspectRatio)); 106 | } 107 | else 108 | { 109 | _label->setPixmap(QPixmap()); 110 | } 111 | 112 | Q_EMIT dataUpdated(0); 113 | } 114 | 115 | -------------------------------------------------------------------------------- /examples/resizable_images/ImageShowModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | using QtNodes::PortType; 12 | using QtNodes::PortIndex; 13 | using QtNodes::NodeData; 14 | using QtNodes::NodeDataType; 15 | using QtNodes::NodeDelegateModel; 16 | 17 | /// The model dictates the number of inputs and outputs for the Node. 18 | /// In this example it has no logic. 19 | class ImageShowModel : public NodeDelegateModel 20 | { 21 | Q_OBJECT 22 | 23 | public: 24 | ImageShowModel(); 25 | 26 | ~ImageShowModel() = default; 27 | 28 | public: 29 | 30 | QString 31 | caption() const override 32 | { return QString("Image Display"); } 33 | 34 | QString 35 | name() const override 36 | { return QString("ImageShowModel"); } 37 | 38 | public: 39 | 40 | virtual QString 41 | modelName() const 42 | { return QString("Resulting Image"); } 43 | 44 | unsigned int 45 | nPorts(PortType const portType) const override; 46 | 47 | NodeDataType 48 | dataType(PortType const portType, 49 | PortIndex const portIndex) const override; 50 | 51 | std::shared_ptr 52 | outData(PortIndex const port) override; 53 | 54 | void 55 | setInData(std::shared_ptr nodeData, 56 | PortIndex const port) override; 57 | 58 | QWidget* 59 | embeddedWidget() override { return _label; } 60 | 61 | bool 62 | resizable() const override { return true; } 63 | 64 | protected: 65 | 66 | bool 67 | eventFilter(QObject* object, QEvent* event) override; 68 | 69 | private: 70 | 71 | QLabel* _label; 72 | 73 | std::shared_ptr _nodeData; 74 | }; 75 | -------------------------------------------------------------------------------- /examples/resizable_images/PixmapData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | using QtNodes::NodeData; 8 | using QtNodes::NodeDataType; 9 | 10 | /// The class can potentially incapsulate any user data which 11 | /// need to be transferred within the Node Editor graph 12 | class PixmapData : public NodeData 13 | { 14 | public: 15 | 16 | PixmapData() {} 17 | 18 | PixmapData(QPixmap const &pixmap) 19 | : _pixmap(pixmap) 20 | {} 21 | 22 | NodeDataType 23 | type() const override 24 | { 25 | // id name 26 | return {"pixmap", "P"}; 27 | } 28 | 29 | QPixmap 30 | pixmap() const { return _pixmap; } 31 | 32 | private: 33 | 34 | QPixmap _pixmap; 35 | }; 36 | -------------------------------------------------------------------------------- /examples/resizable_images/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "ImageShowModel.hpp" 11 | #include "ImageLoaderModel.hpp" 12 | 13 | using QtNodes::ConnectionStyle; 14 | using QtNodes::DataFlowGraphModel; 15 | using QtNodes::DataFlowGraphicsScene; 16 | using QtNodes::NodeDelegateModelRegistry; 17 | using QtNodes::GraphicsView; 18 | 19 | 20 | static std::shared_ptr 21 | registerDataModels() 22 | { 23 | auto ret = std::make_shared(); 24 | ret->registerModel(); 25 | 26 | ret->registerModel(); 27 | 28 | return ret; 29 | } 30 | 31 | 32 | int 33 | main(int argc, char* argv[]) 34 | { 35 | QApplication app(argc, argv); 36 | 37 | std::shared_ptr registry = registerDataModels(); 38 | 39 | DataFlowGraphModel dataFlowGraphModel(registry); 40 | 41 | DataFlowGraphicsScene scene(dataFlowGraphModel); 42 | 43 | GraphicsView view(&scene); 44 | 45 | view.setWindowTitle("Data Flow: Resizable Images"); 46 | view.resize(800, 600); 47 | // Center window. 48 | view.move(QApplication::primaryScreen()->availableGeometry().center() - view.rect().center()); 49 | view.show(); 50 | 51 | return app.exec(); 52 | } 53 | 54 | -------------------------------------------------------------------------------- /examples/simple_graph_model/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | file(GLOB_RECURSE HPPS ./*.hpp ) 3 | 4 | add_executable(simple_graph_model ${CPPS} ${HPPS}) 5 | 6 | target_link_libraries(simple_graph_model QtNodes) 7 | -------------------------------------------------------------------------------- /examples/simple_graph_model/SimpleGraphModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | using ConnectionId = QtNodes::ConnectionId; 13 | using ConnectionPolicy = QtNodes::ConnectionPolicy; 14 | using NodeFlag = QtNodes::NodeFlag; 15 | using NodeId = QtNodes::NodeId; 16 | using NodeRole = QtNodes::NodeRole; 17 | using PortIndex = QtNodes::PortIndex; 18 | using PortRole = QtNodes::PortRole; 19 | using PortType = QtNodes::PortType; 20 | using StyleCollection = QtNodes::StyleCollection; 21 | using QtNodes::InvalidNodeId; 22 | 23 | /** 24 | * The class implements a bare minimum required to demonstrate a model-based 25 | * graph. 26 | */ 27 | class SimpleGraphModel : public QtNodes::AbstractGraphModel 28 | { 29 | Q_OBJECT 30 | public: 31 | struct NodeGeometryData 32 | { 33 | QSize size; 34 | QPointF pos; 35 | }; 36 | 37 | public: 38 | SimpleGraphModel(); 39 | 40 | ~SimpleGraphModel() override; 41 | 42 | std::unordered_set 43 | allNodeIds() const override; 44 | 45 | std::unordered_set 46 | allConnectionIds(NodeId const nodeId) const override; 47 | 48 | std::unordered_set 49 | connections(NodeId nodeId, 50 | PortType portType, 51 | PortIndex portIndex) const override; 52 | 53 | bool 54 | connectionExists(ConnectionId const connectionId) const override; 55 | 56 | NodeId 57 | addNode(QString const nodeType = QString()) override; 58 | 59 | /** 60 | * Connection is possible when graph contains no connectivity data 61 | * in both directions `Out -> In` and `In -> Out`. 62 | */ 63 | bool 64 | connectionPossible(ConnectionId const connectionId) const override; 65 | 66 | void 67 | addConnection(ConnectionId const connectionId) override; 68 | 69 | bool 70 | nodeExists(NodeId const nodeId) const override; 71 | 72 | QVariant 73 | nodeData(NodeId nodeId, NodeRole role) const override; 74 | 75 | bool 76 | setNodeData(NodeId nodeId, 77 | NodeRole role, 78 | QVariant value) override; 79 | 80 | QVariant 81 | portData(NodeId nodeId, 82 | PortType portType, 83 | PortIndex portIndex, 84 | PortRole role) const override; 85 | 86 | bool 87 | setPortData(NodeId nodeId, 88 | PortType portType, 89 | PortIndex portIndex, 90 | QVariant const& value, 91 | PortRole role = PortRole::Data) override; 92 | 93 | bool 94 | deleteConnection(ConnectionId const connectionId) override; 95 | 96 | bool 97 | deleteNode(NodeId const nodeId) override; 98 | 99 | QJsonObject 100 | saveNode(NodeId const) const override; 101 | 102 | /// @brief Creates a new node based on the informatoin in `nodeJson`. 103 | /** 104 | * @param nodeJson conains a `NodeId`, node's position, internal node 105 | * information. 106 | */ 107 | void 108 | loadNode(QJsonObject const & nodeJson) override; 109 | 110 | NodeId newNodeId() override { return _nextNodeId++; } 111 | 112 | private: 113 | std::unordered_set _nodeIds; 114 | 115 | /// [Important] This is a user defined data structure backing your model. 116 | /// In your case it could be anything else representing a graph, for example, a 117 | /// table. Or a collection of structs with pointers to each other. Or an 118 | /// abstract syntax tree, you name it. 119 | /// 120 | /// This data structure contains the graph connectivity information in both 121 | /// directions, i.e. from Node1 to Node2 and from Node2 to Node1. 122 | std::unordered_set _connectivity; 123 | 124 | mutable std::unordered_map 125 | _nodeGeometryData; 126 | 127 | /// A convenience variable needed for generating unique node ids. 128 | unsigned int _nextNodeId; 129 | }; 130 | -------------------------------------------------------------------------------- /examples/simple_graph_model/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "SimpleGraphModel.hpp" 11 | 12 | 13 | using QtNodes::ConnectionStyle; 14 | using QtNodes::GraphicsView; 15 | using QtNodes::BasicGraphicsScene; 16 | using QtNodes::NodeRole; 17 | using QtNodes::StyleCollection; 18 | 19 | int 20 | main(int argc, char *argv[]) 21 | { 22 | QApplication app(argc, argv); 23 | 24 | SimpleGraphModel graphModel; 25 | 26 | // Initialize and connect two nodes. 27 | { 28 | NodeId id1 = graphModel.addNode(); 29 | graphModel.setNodeData(id1, NodeRole::Position, QPointF(0, 0)); 30 | 31 | NodeId id2 = graphModel.addNode(); 32 | graphModel.setNodeData(id2, NodeRole::Position, QPointF(300, 300)); 33 | 34 | graphModel.addConnection(ConnectionId{id1, 0, id2, 0}); 35 | } 36 | 37 | auto scene = new BasicGraphicsScene(graphModel); 38 | 39 | GraphicsView view(scene); 40 | 41 | // Setup context menu for creating new nodes. 42 | view.setContextMenuPolicy(Qt::ActionsContextMenu); 43 | QAction createNodeAction(QStringLiteral("Create Node"), &view); 44 | QObject::connect(&createNodeAction, &QAction::triggered, 45 | [&]() 46 | { 47 | // Mouse position in scene coordinates. 48 | QPointF posView = 49 | view.mapToScene(view.mapFromGlobal(QCursor::pos())); 50 | 51 | 52 | NodeId const newId = graphModel.addNode(); 53 | graphModel.setNodeData(newId, 54 | NodeRole::Position, 55 | posView); 56 | }); 57 | view.insertAction(view.actions().front(), &createNodeAction); 58 | 59 | 60 | view.setWindowTitle("Simple Node Graph"); 61 | view.resize(800, 600); 62 | 63 | // Center window. 64 | view.move(QApplication::primaryScreen()->availableGeometry().center() - view.rect().center()); 65 | view.showNormal(); 66 | 67 | return app.exec(); 68 | } 69 | 70 | -------------------------------------------------------------------------------- /examples/styles/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | file(GLOB_RECURSE HPPS ./*.hpp ) 3 | 4 | add_executable(styles ${CPPS} ${HPPS}) 5 | 6 | target_link_libraries(styles QtNodes) 7 | -------------------------------------------------------------------------------- /examples/styles/main.cpp: -------------------------------------------------------------------------------- 1 | #include "models.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | using QtNodes::ConnectionStyle; 15 | using QtNodes::DataFlowGraphModel; 16 | using QtNodes::DataFlowGraphicsScene; 17 | using QtNodes::NodeDelegateModelRegistry; 18 | using QtNodes::GraphicsView; 19 | using QtNodes::GraphicsViewStyle; 20 | using QtNodes::NodeStyle; 21 | 22 | static std::shared_ptr 23 | registerDataModels() 24 | { 25 | auto ret = std::make_shared(); 26 | 27 | ret->registerModel(); 28 | 29 | return ret; 30 | } 31 | 32 | 33 | static 34 | void 35 | setStyle() 36 | { 37 | GraphicsViewStyle::setStyle( 38 | R"( 39 | { 40 | "GraphicsViewStyle": { 41 | "BackgroundColor": [255, 255, 240], 42 | "FineGridColor": [245, 245, 230], 43 | "CoarseGridColor": [235, 235, 220] 44 | } 45 | } 46 | )"); 47 | 48 | NodeStyle::setNodeStyle( 49 | R"( 50 | { 51 | "NodeStyle": { 52 | "NormalBoundaryColor": "darkgray", 53 | "SelectedBoundaryColor": "deepskyblue", 54 | "GradientColor0": "mintcream", 55 | "GradientColor1": "mintcream", 56 | "GradientColor2": "mintcream", 57 | "GradientColor3": "mintcream", 58 | "ShadowColor": [200, 200, 200], 59 | "FontColor": [10, 10, 10], 60 | "FontColorFaded": [100, 100, 100], 61 | "ConnectionPointColor": "white", 62 | "PenWidth": 2.0, 63 | "HoveredPenWidth": 2.5, 64 | "ConnectionPointDiameter": 10.0, 65 | "Opacity": 1.0 66 | } 67 | } 68 | )"); 69 | 70 | ConnectionStyle::setConnectionStyle( 71 | R"( 72 | { 73 | "ConnectionStyle": { 74 | "ConstructionColor": "gray", 75 | "NormalColor": "black", 76 | "SelectedColor": "gray", 77 | "SelectedHaloColor": "deepskyblue", 78 | "HoveredColor": "deepskyblue", 79 | 80 | "LineWidth": 3.0, 81 | "ConstructionLineWidth": 2.0, 82 | "PointDiameter": 10.0, 83 | 84 | "UseDataDefinedColors": false 85 | } 86 | } 87 | )"); 88 | } 89 | 90 | 91 | int 92 | main(int argc, char* argv[]) 93 | { 94 | QApplication app(argc, argv); 95 | 96 | setStyle(); 97 | 98 | std::shared_ptr registry = registerDataModels(); 99 | DataFlowGraphModel dataFlowGraphModel(registry); 100 | 101 | DataFlowGraphicsScene scene(dataFlowGraphModel); 102 | 103 | GraphicsView view(&scene); 104 | 105 | view.setWindowTitle("Style example"); 106 | view.resize(800, 600); 107 | view.show(); 108 | 109 | return app.exec(); 110 | } 111 | 112 | -------------------------------------------------------------------------------- /examples/styles/models.cpp: -------------------------------------------------------------------------------- 1 | #include "models.hpp" 2 | 3 | // For some reason CMake could not generate moc-files correctly 4 | // without having a cpp for an QObject from hpp. 5 | -------------------------------------------------------------------------------- /examples/styles/models.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | using QtNodes::PortType; 11 | using QtNodes::PortIndex; 12 | using QtNodes::NodeData; 13 | using QtNodes::NodeDataType; 14 | using QtNodes::NodeDelegateModel; 15 | 16 | /// The class can potentially incapsulate any user data which need to 17 | /// be transferred within the Node Editor graph 18 | class MyNodeData : public NodeData 19 | { 20 | public: 21 | 22 | NodeDataType 23 | type() const override 24 | { return NodeDataType {"MyNodeData", "My Node Data"}; } 25 | }; 26 | 27 | //------------------------------------------------------------------------------ 28 | 29 | /// The model dictates the number of inputs and outputs for the Node. 30 | /// In this example it has no logic. 31 | class MyDataModel : public NodeDelegateModel 32 | { 33 | Q_OBJECT 34 | public: 35 | 36 | ~MyDataModel() = default; 37 | 38 | public: 39 | 40 | QString 41 | caption() const override 42 | { 43 | return QString("My Data Model"); 44 | } 45 | 46 | 47 | QString 48 | name() const override 49 | { 50 | return QString("MyDataModel"); 51 | } 52 | 53 | 54 | public: 55 | 56 | QJsonObject 57 | save() const override 58 | { 59 | QJsonObject modelJson; 60 | 61 | modelJson["name"] = name(); 62 | 63 | return modelJson; 64 | } 65 | 66 | 67 | public: 68 | 69 | unsigned int 70 | nPorts(PortType const) const override 71 | { 72 | return 3; 73 | } 74 | 75 | 76 | NodeDataType 77 | dataType(PortType const, PortIndex const) const override 78 | { 79 | return MyNodeData().type(); 80 | } 81 | 82 | 83 | std::shared_ptr 84 | outData(PortIndex) override 85 | { 86 | return std::make_shared(); 87 | } 88 | 89 | 90 | void 91 | setInData(std::shared_ptr, PortIndex const) override 92 | {} 93 | 94 | QWidget* 95 | embeddedWidget() override { return nullptr; } 96 | }; 97 | -------------------------------------------------------------------------------- /examples/text/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | file(GLOB_RECURSE HPPS ./*.hpp ) 3 | 4 | add_executable(text ${CPPS} ${HPPS}) 5 | 6 | target_link_libraries(text QtNodes) 7 | -------------------------------------------------------------------------------- /examples/text/TextData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using QtNodes::NodeData; 6 | using QtNodes::NodeDataType; 7 | 8 | /// The class can potentially incapsulate any user data which 9 | /// need to be transferred within the Node Editor graph 10 | class TextData : public NodeData 11 | { 12 | public: 13 | 14 | TextData() {} 15 | 16 | TextData(QString const &text) 17 | : _text(text) 18 | {} 19 | 20 | NodeDataType type() const override 21 | { return NodeDataType {"text", "Text"}; } 22 | 23 | QString text() const { return _text; } 24 | 25 | private: 26 | 27 | QString _text; 28 | }; 29 | -------------------------------------------------------------------------------- /examples/text/TextDisplayDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "TextDisplayDataModel.hpp" 2 | 3 | TextDisplayDataModel:: 4 | TextDisplayDataModel() 5 | : _label(new QLabel("Resulting Text")) 6 | { 7 | _label->setMargin(3); 8 | } 9 | 10 | 11 | unsigned int 12 | TextDisplayDataModel:: 13 | nPorts(PortType portType) const 14 | { 15 | unsigned int result = 1; 16 | 17 | switch (portType) 18 | { 19 | case PortType::In: 20 | result = 1; 21 | break; 22 | 23 | case PortType::Out: 24 | result = 0; 25 | 26 | default: 27 | break; 28 | } 29 | 30 | return result; 31 | } 32 | 33 | 34 | NodeDataType 35 | TextDisplayDataModel:: 36 | dataType(PortType, PortIndex) const 37 | { 38 | return TextData().type(); 39 | } 40 | 41 | 42 | std::shared_ptr 43 | TextDisplayDataModel:: 44 | outData(PortIndex) 45 | { 46 | std::shared_ptr ptr; 47 | return ptr; 48 | } 49 | 50 | 51 | void 52 | TextDisplayDataModel:: 53 | setInData(std::shared_ptr data, PortIndex const) 54 | { 55 | auto textData = std::dynamic_pointer_cast(data); 56 | 57 | if (textData) 58 | { 59 | _inputText = textData->text(); 60 | } 61 | else 62 | { 63 | _inputText = ""; 64 | } 65 | 66 | _label->setText(_inputText); 67 | _label->adjustSize(); 68 | } 69 | 70 | -------------------------------------------------------------------------------- /examples/text/TextDisplayDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "TextData.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | using QtNodes::ConnectionPolicy; 14 | using QtNodes::NodeData; 15 | using QtNodes::NodeDelegateModel; 16 | using QtNodes::PortIndex; 17 | using QtNodes::PortType; 18 | 19 | /// The model dictates the number of inputs and outputs for the Node. 20 | /// In this example it has no logic. 21 | class TextDisplayDataModel : public NodeDelegateModel 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | TextDisplayDataModel(); 27 | 28 | virtual 29 | ~TextDisplayDataModel() {} 30 | 31 | public: 32 | 33 | QString 34 | caption() const override 35 | { return QString("Text Display"); } 36 | 37 | bool 38 | captionVisible() const override { return false; } 39 | 40 | static QString 41 | Name() 42 | { return QString("TextDisplayDataModel"); } 43 | 44 | QString 45 | name() const override 46 | { return TextDisplayDataModel::Name(); } 47 | 48 | public: 49 | 50 | unsigned int 51 | nPorts(PortType portType) const override; 52 | 53 | NodeDataType 54 | dataType(PortType portType, PortIndex portIndex) const override; 55 | 56 | std::shared_ptr 57 | outData(PortIndex const port) override; 58 | 59 | void 60 | setInData(std::shared_ptr data, 61 | PortIndex const portIndex) override; 62 | 63 | QWidget* 64 | embeddedWidget() override { return _label; } 65 | 66 | private: 67 | QLabel * _label; 68 | QString _inputText; 69 | }; 70 | -------------------------------------------------------------------------------- /examples/text/TextSourceDataModel.cpp: -------------------------------------------------------------------------------- 1 | #include "TextSourceDataModel.hpp" 2 | 3 | #include 4 | 5 | TextSourceDataModel:: 6 | TextSourceDataModel() 7 | : _lineEdit{nullptr} 8 | { 9 | // 10 | } 11 | 12 | 13 | unsigned int 14 | TextSourceDataModel:: 15 | nPorts(PortType portType) const 16 | { 17 | unsigned int result = 1; 18 | 19 | switch (portType) 20 | { 21 | case PortType::In: 22 | result = 0; 23 | break; 24 | 25 | case PortType::Out: 26 | result = 1; 27 | 28 | default: 29 | break; 30 | } 31 | 32 | return result; 33 | } 34 | 35 | 36 | void 37 | TextSourceDataModel:: 38 | onTextEdited(QString const & string) 39 | { 40 | Q_UNUSED(string); 41 | 42 | Q_EMIT dataUpdated(0); 43 | } 44 | 45 | 46 | NodeDataType 47 | TextSourceDataModel:: 48 | dataType(PortType, PortIndex) const 49 | { 50 | return TextData().type(); 51 | } 52 | 53 | 54 | std::shared_ptr 55 | TextSourceDataModel:: 56 | outData(PortIndex const portIndex) 57 | { 58 | Q_UNUSED(portIndex); 59 | return std::make_shared(_lineEdit->text()); 60 | } 61 | 62 | 63 | QWidget * 64 | TextSourceDataModel:: 65 | embeddedWidget() 66 | { 67 | if (!_lineEdit) 68 | { 69 | _lineEdit = new QLineEdit("Default Text"), 70 | 71 | connect(_lineEdit, &QLineEdit::textEdited, 72 | this, &TextSourceDataModel::onTextEdited); 73 | 74 | } 75 | 76 | return _lineEdit; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /examples/text/TextSourceDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "TextData.hpp" 6 | 7 | #include 8 | 9 | #include 10 | 11 | using QtNodes::PortType; 12 | using QtNodes::PortIndex; 13 | using QtNodes::NodeData; 14 | using QtNodes::NodeDelegateModel; 15 | 16 | class QLineEdit; 17 | 18 | /// The model dictates the number of inputs and outputs for the Node. 19 | /// In this example it has no logic. 20 | class TextSourceDataModel : public NodeDelegateModel 21 | { 22 | Q_OBJECT 23 | 24 | public: 25 | TextSourceDataModel(); 26 | 27 | public: 28 | QString 29 | caption() const override 30 | { return QString("Text Source"); } 31 | 32 | bool 33 | captionVisible() const override { return false; } 34 | 35 | static QString 36 | Name() 37 | { return QString("TextSourceDataModel"); } 38 | 39 | QString 40 | name() const override 41 | { return TextSourceDataModel::Name(); } 42 | 43 | public: 44 | unsigned int 45 | nPorts(PortType portType) const override; 46 | 47 | NodeDataType 48 | dataType(PortType portType, PortIndex portIndex) const override; 49 | 50 | std::shared_ptr 51 | outData(PortIndex const portIndex) override; 52 | 53 | void 54 | setInData(std::shared_ptr, PortIndex const) override { } 55 | 56 | QWidget * 57 | embeddedWidget() override; 58 | 59 | private Q_SLOTS: 60 | 61 | void 62 | onTextEdited(QString const & string); 63 | 64 | private: 65 | QLineEdit * _lineEdit; 66 | }; 67 | -------------------------------------------------------------------------------- /examples/text/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "TextSourceDataModel.hpp" 10 | #include "TextDisplayDataModel.hpp" 11 | 12 | using QtNodes::DataFlowGraphModel; 13 | using QtNodes::DataFlowGraphicsScene; 14 | using QtNodes::NodeDelegateModelRegistry; 15 | using QtNodes::GraphicsView; 16 | 17 | static std::shared_ptr 18 | registerDataModels() 19 | { 20 | auto ret = std::make_shared(); 21 | 22 | ret->registerModel(); 23 | ret->registerModel(); 24 | 25 | return ret; 26 | } 27 | 28 | 29 | int 30 | main(int argc, char* argv[]) 31 | { 32 | QApplication app(argc, argv); 33 | 34 | std::shared_ptr registry = registerDataModels(); 35 | DataFlowGraphModel dataFlowGraphModel(registry); 36 | 37 | DataFlowGraphicsScene scene(dataFlowGraphModel); 38 | 39 | GraphicsView view(&scene); 40 | 41 | view.setWindowTitle("Node-based flow editor"); 42 | view.resize(800, 600); 43 | view.show(); 44 | 45 | return app.exec(); 46 | } 47 | 48 | -------------------------------------------------------------------------------- /examples/vertical_layout/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE CPPS ./*.cpp ) 2 | 3 | add_executable(vertical_layout ${CPPS}) 4 | 5 | target_link_libraries(vertical_layout QtNodes) 6 | -------------------------------------------------------------------------------- /examples/vertical_layout/SimpleGraphModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | using ConnectionId = QtNodes::ConnectionId; 13 | using ConnectionPolicy = QtNodes::ConnectionPolicy; 14 | using NodeFlag = QtNodes::NodeFlag; 15 | using NodeId = QtNodes::NodeId; 16 | using NodeRole = QtNodes::NodeRole; 17 | using PortIndex = QtNodes::PortIndex; 18 | using PortRole = QtNodes::PortRole; 19 | using PortType = QtNodes::PortType; 20 | using StyleCollection = QtNodes::StyleCollection; 21 | using QtNodes::InvalidNodeId; 22 | 23 | /** 24 | * The class implements a bare minimum required to demonstrate a model-based 25 | * graph. 26 | */ 27 | class SimpleGraphModel : public QtNodes::AbstractGraphModel 28 | { 29 | Q_OBJECT 30 | public: 31 | struct NodeGeometryData 32 | { 33 | QSize size; 34 | QPointF pos; 35 | }; 36 | 37 | public: 38 | SimpleGraphModel(); 39 | 40 | ~SimpleGraphModel() override; 41 | 42 | std::unordered_set 43 | allNodeIds() const override; 44 | 45 | std::unordered_set 46 | allConnectionIds(NodeId const nodeId) const override; 47 | 48 | std::unordered_set 49 | connections(NodeId nodeId, 50 | PortType portType, 51 | PortIndex portIndex) const override; 52 | 53 | bool 54 | connectionExists(ConnectionId const connectionId) const override; 55 | 56 | NodeId 57 | addNode(QString const nodeType = QString()) override; 58 | 59 | /** 60 | * Connection is possible when graph contains no connectivity data 61 | * in both directions `Out -> In` and `In -> Out`. 62 | */ 63 | bool 64 | connectionPossible(ConnectionId const connectionId) const override; 65 | 66 | void 67 | addConnection(ConnectionId const connectionId) override; 68 | 69 | bool 70 | nodeExists(NodeId const nodeId) const override; 71 | 72 | QVariant 73 | nodeData(NodeId nodeId, NodeRole role) const override; 74 | 75 | bool 76 | setNodeData(NodeId nodeId, 77 | NodeRole role, 78 | QVariant value) override; 79 | 80 | QVariant 81 | portData(NodeId nodeId, 82 | PortType portType, 83 | PortIndex portIndex, 84 | PortRole role) const override; 85 | 86 | bool 87 | setPortData(NodeId nodeId, 88 | PortType portType, 89 | PortIndex portIndex, 90 | QVariant const& value, 91 | PortRole role = PortRole::Data) override; 92 | 93 | bool 94 | deleteConnection(ConnectionId const connectionId) override; 95 | 96 | bool 97 | deleteNode(NodeId const nodeId) override; 98 | 99 | QJsonObject 100 | saveNode(NodeId const) const override; 101 | 102 | /// @brief Creates a new node based on the informatoin in `nodeJson`. 103 | /** 104 | * @param nodeJson conains a `NodeId`, node's position, internal node 105 | * information. 106 | */ 107 | void 108 | loadNode(QJsonObject const & nodeJson) override; 109 | 110 | private: 111 | NodeId 112 | newNodeId() override { return _nextNodeId++; } 113 | 114 | private: 115 | std::unordered_set _nodeIds; 116 | 117 | /// [Important] This is a user defined data structure backing your model. 118 | /// In your case it could be anything else representing a graph, for example, a 119 | /// table. Or a collection of structs with pointers to each other. Or an 120 | /// abstract syntax tree, you name it. 121 | /// 122 | /// This data structure contains the graph connectivity information in both 123 | /// directions, i.e. from Node1 to Node2 and from Node2 to Node1. 124 | std::unordered_set _connectivity; 125 | 126 | mutable std::unordered_map 127 | _nodeGeometryData; 128 | 129 | /// A convenience variable needed for generating unique node ids. 130 | unsigned int _nextNodeId; 131 | }; 132 | -------------------------------------------------------------------------------- /examples/vertical_layout/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "SimpleGraphModel.hpp" 11 | 12 | 13 | using QtNodes::GraphicsView; 14 | using QtNodes::BasicGraphicsScene; 15 | using QtNodes::NodeRole; 16 | 17 | int 18 | main(int argc, char *argv[]) 19 | { 20 | QApplication app(argc, argv); 21 | 22 | SimpleGraphModel graphModel; 23 | 24 | // Initialize and connect two nodes. 25 | { 26 | NodeId id1 = graphModel.addNode(); 27 | graphModel.setNodeData(id1, NodeRole::Position, QPointF(0, 0)); 28 | 29 | NodeId id2 = graphModel.addNode(); 30 | graphModel.setNodeData(id2, NodeRole::Position, QPointF(300, 300)); 31 | 32 | graphModel.addConnection(ConnectionId{id1, 0, id2, 0}); 33 | } 34 | 35 | auto scene = new BasicGraphicsScene(graphModel); 36 | 37 | scene->setOrientation(Qt::Vertical); 38 | 39 | QWidget window; 40 | 41 | QHBoxLayout * l = new QHBoxLayout(&window); 42 | 43 | GraphicsView view(scene); 44 | 45 | l->addWidget(&view); 46 | 47 | 48 | 49 | QGroupBox *groupBox = new QGroupBox("Orientation"); 50 | 51 | QRadioButton *radio1 = new QRadioButton("Vertical"); 52 | QRadioButton *radio2 = new QRadioButton("Horizontal"); 53 | 54 | QVBoxLayout * vbl = new QVBoxLayout; 55 | vbl->addWidget(radio1); 56 | vbl->addWidget(radio2); 57 | vbl->addStretch(); 58 | groupBox->setLayout(vbl); 59 | 60 | QObject::connect(radio1, 61 | &QRadioButton::clicked, 62 | [&scene](){ scene->setOrientation(Qt::Vertical); }); 63 | 64 | QObject::connect(radio2, 65 | &QRadioButton::clicked, 66 | [&scene](){ scene->setOrientation(Qt::Horizontal); }); 67 | 68 | radio1->setChecked(true); 69 | 70 | l->addWidget(groupBox); 71 | 72 | 73 | 74 | // Setup context menu for creating new nodes. 75 | view.setContextMenuPolicy(Qt::ActionsContextMenu); 76 | QAction createNodeAction(QStringLiteral("Create Node"), &view); 77 | QObject::connect(&createNodeAction, &QAction::triggered, 78 | [&]() 79 | { 80 | // Mouse position in scene coordinates. 81 | QPointF posView = 82 | view.mapToScene(view.mapFromGlobal(QCursor::pos())); 83 | 84 | 85 | NodeId const newId = graphModel.addNode(); 86 | graphModel.setNodeData(newId, 87 | NodeRole::Position, 88 | posView); 89 | }); 90 | view.insertAction(view.actions().front(), &createNodeAction); 91 | 92 | 93 | window.setWindowTitle("Graph Orientation Demo"); 94 | window.resize(800, 600); 95 | 96 | // Center window. 97 | window.move(QApplication::primaryScreen()->availableGeometry().center() - view.rect().center()); 98 | window.showNormal(); 99 | 100 | return app.exec(); 101 | } 102 | 103 | -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(BUILD_TESTING) 2 | find_package(Catch2 QUIET) 3 | 4 | if(NOT Catch2_FOUND) 5 | add_subdirectory(Catch2) 6 | endif() 7 | endif() 8 | -------------------------------------------------------------------------------- /external/Catch2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/single_include/catch2/catch.hpp") 2 | file(DOWNLOAD https://raw.githubusercontent.com/catchorg/Catch2/v2.13.7/single_include/catch2/catch.hpp 3 | "${CMAKE_CURRENT_BINARY_DIR}/single_include/catch2/catch.hpp" 4 | EXPECTED_HASH SHA256=ea379c4a3cb5799027b1eb451163dff065a3d641aaba23bf4e24ee6b536bd9bc 5 | ) 6 | endif() 7 | 8 | add_library(Catch2 INTERFACE) 9 | add_library(Catch2::Catch2 ALIAS Catch2) 10 | target_include_directories(Catch2 11 | INTERFACE 12 | "${CMAKE_CURRENT_BINARY_DIR}/single_include" 13 | ) 14 | -------------------------------------------------------------------------------- /include/QtNodes/AbstractGraphModel: -------------------------------------------------------------------------------- 1 | #include "internal/AbstractGraphModel.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/AbstractNodePainter: -------------------------------------------------------------------------------- 1 | #include "internal/AbstractNodePainter.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/BasicGraphicsScene: -------------------------------------------------------------------------------- 1 | #include "internal/BasicGraphicsScene.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/ConnectionIdUtils: -------------------------------------------------------------------------------- 1 | #include "internal/ConnectionIdUtils.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/ConnectionStyle: -------------------------------------------------------------------------------- 1 | #include "internal/ConnectionStyle.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/DataFlowGraphModel: -------------------------------------------------------------------------------- 1 | #include "internal/DataFlowGraphModel.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/DataFlowGraphicsScene: -------------------------------------------------------------------------------- 1 | #include "internal/DataFlowGraphicsScene.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/DefaultNodePainter: -------------------------------------------------------------------------------- 1 | #include "internal/DefaultNodePainter.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/Definitions: -------------------------------------------------------------------------------- 1 | #include "internal/Definitions.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/GraphicsView: -------------------------------------------------------------------------------- 1 | #include "internal/GraphicsView.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/GraphicsViewStyle: -------------------------------------------------------------------------------- 1 | #include "internal/GraphicsViewStyle.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/NodeData: -------------------------------------------------------------------------------- 1 | #include "internal/NodeData.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/NodeDelegateModel: -------------------------------------------------------------------------------- 1 | #include "internal/NodeDelegateModel.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/NodeDelegateModelRegistry: -------------------------------------------------------------------------------- 1 | #include "internal/NodeDelegateModelRegistry.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/NodeGeometry: -------------------------------------------------------------------------------- 1 | #include "internal/NodeGeometry.hpp" 2 | 3 | -------------------------------------------------------------------------------- /include/QtNodes/NodeState: -------------------------------------------------------------------------------- 1 | #include "internal/NodeState.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/NodeStyle: -------------------------------------------------------------------------------- 1 | #include "internal/NodeStyle.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/StyleCollection: -------------------------------------------------------------------------------- 1 | #include "internal/StyleCollection.hpp" 2 | -------------------------------------------------------------------------------- /include/QtNodes/internal/AbstractNodeGeometry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Export.hpp" 4 | #include "Definitions.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace QtNodes 11 | { 12 | 13 | class AbstractGraphModel; 14 | 15 | class NODE_EDITOR_PUBLIC AbstractNodeGeometry 16 | { 17 | public: 18 | AbstractNodeGeometry(AbstractGraphModel &); 19 | virtual ~AbstractNodeGeometry() {} 20 | 21 | /** 22 | * The node's size plus some additional margin around it to account for drawing 23 | * effects (for example shadows) or node's parts outside the size rectangle 24 | * (for example port points). 25 | * 26 | * The default implementation returns QSize + 20 percent of width and heights 27 | * at each side of the rectangle. 28 | */ 29 | virtual QRectF boundingRect(NodeId const nodeId) const; 30 | 31 | /// A direct rectangle defining the borders of the node's rectangle. 32 | virtual QSize size(NodeId const nodeId) const = 0; 33 | 34 | /** 35 | * The function is triggeren when a nuber of ports is changed or when an 36 | * embedded widget needs an update. 37 | */ 38 | virtual void recomputeSize(NodeId const nodeId) const = 0; 39 | 40 | /// Port position in node's coordinate system. 41 | virtual 42 | QPointF 43 | portPosition(NodeId const nodeId, 44 | PortType const portType, 45 | PortIndex const index) const = 0; 46 | 47 | /// A convenience function using the `portPosition` and a given transformation. 48 | virtual 49 | QPointF 50 | portScenePosition(NodeId const nodeId, 51 | PortType const portType, 52 | PortIndex const index, 53 | QTransform const & t) const; 54 | 55 | /// Defines where to draw port label. The point corresponds to a font baseline. 56 | virtual 57 | QPointF 58 | portTextPosition(NodeId const nodeId, 59 | PortType const portType, 60 | PortIndex const portIndex) const = 0; 61 | 62 | /** 63 | * Defines where to start drawing the caption. The point corresponds to a font 64 | * baseline. 65 | */ 66 | virtual 67 | QPointF 68 | captionPosition(NodeId const nodeId) const = 0; 69 | 70 | 71 | /// Caption rect is needed for estimating the total node size. 72 | virtual 73 | QRectF 74 | captionRect(NodeId const nodeId) const = 0; 75 | 76 | /// Position for an embedded widget. Return any value if you don't embed. 77 | virtual 78 | QPointF 79 | widgetPosition(NodeId const nodeId) const = 0; 80 | 81 | virtual 82 | PortIndex 83 | checkPortHit(NodeId const nodeId, 84 | PortType const portType, 85 | QPointF const nodePoint) const; 86 | 87 | virtual 88 | QRect 89 | resizeHandleRect(NodeId const nodeId) const = 0; 90 | 91 | protected: 92 | AbstractGraphModel & _graphModel; 93 | }; 94 | 95 | } 96 | -------------------------------------------------------------------------------- /include/QtNodes/internal/AbstractNodePainter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Export.hpp" 6 | 7 | class QPainter; 8 | 9 | namespace QtNodes { 10 | 11 | class NodeGraphicsObject; 12 | class NodeDataModel; 13 | 14 | /// Class enables custom painting. 15 | class NODE_EDITOR_PUBLIC AbstractNodePainter 16 | { 17 | public: 18 | virtual ~AbstractNodePainter() = default; 19 | 20 | /** 21 | * Reimplement this function in order to have a custom painting. 22 | * 23 | * Useful functions: 24 | * `NodeGraphicsObject::nodeScene()->nodeGeometry()` 25 | * `NodeGraphicsObject::graphModel()` 26 | */ 27 | virtual 28 | void 29 | paint(QPainter* painter, 30 | NodeGraphicsObject & ngo) const = 0; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /include/QtNodes/internal/Compiler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if \ 4 | defined (__MINGW32__) || \ 5 | defined (__MINGW64__) 6 | # define NODE_EDITOR_COMPILER "MinGW" 7 | # define NODE_EDITOR_COMPILER_MINGW 8 | #elif \ 9 | defined (__clang__) 10 | # define NODE_EDITOR_COMPILER "Clang" 11 | # define NODE_EDITOR_COMPILER_CLANG 12 | #elif \ 13 | defined (_MSC_VER) 14 | # define NODE_EDITOR_COMPILER "Microsoft Visual C++" 15 | # define NODE_EDITOR_COMPILER_MICROSOFT 16 | #elif \ 17 | defined (__GNUC__) 18 | # define NODE_EDITOR_COMPILER "GNU" 19 | # define NODE_EDITOR_COMPILER_GNU 20 | # define NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR __GNUC__ 21 | # define NODE_EDITOR_COMPILER_GNU_VERSION_MINOR __GNUC_MINOR__ 22 | # define NODE_EDITOR_COMPILER_GNU_VERSION_PATCH __GNUC_PATCHLEVEL__ 23 | #elif \ 24 | defined (__BORLANDC__) 25 | # define NODE_EDITOR_COMPILER "Borland C++ Builder" 26 | # define NODE_EDITOR_COMPILER_BORLAND 27 | #elif \ 28 | defined (__CODEGEARC__) 29 | # define NODE_EDITOR_COMPILER "CodeGear C++ Builder" 30 | # define NODE_EDITOR_COMPILER_CODEGEAR 31 | #elif \ 32 | defined (__INTEL_COMPILER) || \ 33 | defined (__ICL) 34 | # define NODE_EDITOR_COMPILER "Intel C++" 35 | # define NODE_EDITOR_COMPILER_INTEL 36 | #elif \ 37 | defined (__xlC__) || \ 38 | defined (__IBMCPP__) 39 | # define NODE_EDITOR_COMPILER "IBM XL C++" 40 | # define NODE_EDITOR_COMPILER_IBM 41 | #elif \ 42 | defined (__HP_aCC) 43 | # define NODE_EDITOR_COMPILER "HP aC++" 44 | # define NODE_EDITOR_COMPILER_HP 45 | #elif \ 46 | defined (__WATCOMC__) 47 | # define NODE_EDITOR_COMPILER "Watcom C++" 48 | # define NODE_EDITOR_COMPILER_WATCOM 49 | #endif 50 | 51 | #ifndef NODE_EDITOR_COMPILER 52 | # error "Current compiler is not supported." 53 | #endif 54 | -------------------------------------------------------------------------------- /include/QtNodes/internal/ConnectionGraphicsObject.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "Definitions.hpp" 9 | #include "ConnectionState.hpp" 10 | 11 | class QGraphicsSceneMouseEvent; 12 | 13 | namespace QtNodes 14 | { 15 | 16 | class AbstractGraphModel; 17 | class BasicGraphicsScene; 18 | 19 | /// Graphic Object for connection. Adds itself to scene 20 | class ConnectionGraphicsObject : public QGraphicsObject 21 | { 22 | Q_OBJECT 23 | public: 24 | // Needed for qgraphicsitem_cast 25 | enum { Type = UserType + 2 }; 26 | 27 | int 28 | type() const override { return Type; } 29 | 30 | public: 31 | ConnectionGraphicsObject(BasicGraphicsScene &scene, 32 | ConnectionId const connectionId); 33 | 34 | ~ConnectionGraphicsObject() = default; 35 | 36 | public: 37 | AbstractGraphModel & 38 | graphModel() const; 39 | 40 | BasicGraphicsScene * 41 | nodeScene() const; 42 | 43 | ConnectionId const & 44 | connectionId() const; 45 | 46 | QRectF 47 | boundingRect() const override; 48 | 49 | QPainterPath 50 | shape() const override; 51 | 52 | QPointF const & 53 | endPoint(PortType portType) const; 54 | 55 | QPointF 56 | out() const { return _out; } 57 | 58 | QPointF 59 | in() const { return _in; } 60 | 61 | std::pair 62 | pointsC1C2() const; 63 | 64 | void 65 | setEndPoint(PortType portType, QPointF const &point); 66 | 67 | /// Updates the position of both ends 68 | void 69 | move(); 70 | 71 | ConnectionState const & 72 | connectionState() const; 73 | 74 | ConnectionState & 75 | connectionState(); 76 | 77 | protected: 78 | void 79 | paint(QPainter * painter, 80 | QStyleOptionGraphicsItem const * option, 81 | QWidget * widget = 0) override; 82 | 83 | void 84 | mousePressEvent(QGraphicsSceneMouseEvent * event) override; 85 | 86 | void 87 | mouseMoveEvent(QGraphicsSceneMouseEvent * event) override; 88 | 89 | void 90 | mouseReleaseEvent(QGraphicsSceneMouseEvent * event) override; 91 | 92 | void 93 | hoverEnterEvent(QGraphicsSceneHoverEvent * event) override; 94 | 95 | void 96 | hoverLeaveEvent(QGraphicsSceneHoverEvent * event) override; 97 | 98 | private: 99 | void 100 | initializePosition(); 101 | 102 | void 103 | addGraphicsEffect(); 104 | 105 | std::pair 106 | pointsC1C2Horizontal() const; 107 | 108 | std::pair 109 | pointsC1C2Vertical() const; 110 | 111 | private: 112 | ConnectionId _connectionId; 113 | 114 | AbstractGraphModel &_graphModel; 115 | 116 | ConnectionState _connectionState; 117 | 118 | mutable QPointF _out; 119 | mutable QPointF _in; 120 | }; 121 | 122 | } 123 | -------------------------------------------------------------------------------- /include/QtNodes/internal/ConnectionIdHash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Definitions.hpp" 6 | 7 | inline void hash_combine(std::size_t& seed) { Q_UNUSED(seed); } 8 | 9 | template 10 | inline void 11 | hash_combine(std::size_t& seed, const T& v, Rest... rest) 12 | { 13 | std::hash hasher; 14 | seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); 15 | hash_combine(seed, rest ...); 16 | } 17 | 18 | 19 | namespace std 20 | { 21 | template<> 22 | struct hash 23 | { 24 | inline 25 | std::size_t 26 | operator()(QtNodes::ConnectionId const& id) const 27 | { 28 | std::size_t h = 0; 29 | hash_combine(h, 30 | id.outNodeId, 31 | id.outPortIndex, 32 | id.inNodeId, 33 | id.inPortIndex); 34 | return h; 35 | } 36 | 37 | }; 38 | 39 | 40 | template<> 41 | struct hash> 42 | { 43 | inline 44 | std::size_t 45 | operator()(std::pair const & nodePort) const 46 | { 47 | std::size_t h = 0; 48 | hash_combine(h, 49 | nodePort.first, 50 | nodePort.second); 51 | return h; 52 | } 53 | 54 | }; 55 | 56 | template<> 57 | struct hash> 60 | { 61 | using Key = std::tuple; 64 | 65 | inline 66 | std::size_t 67 | operator()(Key const &key) const 68 | { 69 | std::size_t h = 0; 70 | hash_combine(h, 71 | std::get<0>(key), 72 | std::get<1>(key), 73 | std::get<2>(key)); 74 | return h; 75 | } 76 | 77 | }; 78 | } 79 | 80 | -------------------------------------------------------------------------------- /include/QtNodes/internal/ConnectionState.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Export.hpp" 6 | 7 | #include "Definitions.hpp" 8 | 9 | class QPointF; 10 | 11 | namespace QtNodes 12 | { 13 | 14 | class ConnectionGraphicsObject; 15 | 16 | /// Stores currently draggind end. 17 | /// Remembers last hovered Node. 18 | class NODE_EDITOR_PUBLIC ConnectionState 19 | { 20 | public: 21 | 22 | /// Defines whether we construct a new connection 23 | /// or it is already binding two nodes. 24 | enum LooseEnd 25 | { 26 | Pending = 0, 27 | Connected = 1 28 | }; 29 | 30 | public: 31 | 32 | ConnectionState(ConnectionGraphicsObject & cgo) 33 | : _cgo(cgo) 34 | , _hovered(false) 35 | {} 36 | 37 | ConnectionState(ConnectionState const&) = delete; 38 | ConnectionState(ConnectionState &&) = delete; 39 | 40 | ConnectionState& 41 | operator=(ConnectionState const&) = delete; 42 | ConnectionState& 43 | operator=(ConnectionState &&) = delete; 44 | 45 | ~ConnectionState(); 46 | 47 | public: 48 | 49 | PortType 50 | requiredPort() const; 51 | bool 52 | requiresPort() const; 53 | 54 | bool 55 | hovered() const; 56 | void 57 | setHovered(bool hovered); 58 | 59 | public: 60 | 61 | /// Caches NodeId for further interaction. 62 | void 63 | setLastHoveredNode(NodeId const nodeId); 64 | 65 | NodeId 66 | lastHoveredNode() const; 67 | 68 | void 69 | resetLastHoveredNode(); 70 | 71 | private: 72 | ConnectionGraphicsObject & _cgo; 73 | 74 | bool _hovered; 75 | 76 | NodeId _lastHoveredNode{InvalidNodeId}; 77 | 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /include/QtNodes/internal/ConnectionStyle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Export.hpp" 6 | #include "Style.hpp" 7 | 8 | namespace QtNodes 9 | { 10 | 11 | class NODE_EDITOR_PUBLIC ConnectionStyle : public Style 12 | { 13 | public: 14 | 15 | ConnectionStyle(); 16 | 17 | ConnectionStyle(QString jsonText); 18 | 19 | ~ConnectionStyle() = default; 20 | 21 | public: 22 | 23 | static void setConnectionStyle(QString jsonText); 24 | 25 | public: 26 | 27 | void loadJson(QJsonObject const & json) override; 28 | 29 | QJsonObject toJson() const override; 30 | 31 | public: 32 | 33 | QColor constructionColor() const; 34 | QColor normalColor() const; 35 | QColor normalColor(QString typeId) const; 36 | QColor selectedColor() const; 37 | QColor selectedHaloColor() const; 38 | QColor hoveredColor() const; 39 | 40 | float lineWidth() const; 41 | float constructionLineWidth() const; 42 | float pointDiameter() const; 43 | 44 | bool useDataDefinedColors() const; 45 | 46 | private: 47 | 48 | QColor ConstructionColor; 49 | QColor NormalColor; 50 | QColor SelectedColor; 51 | QColor SelectedHaloColor; 52 | QColor HoveredColor; 53 | 54 | float LineWidth; 55 | float ConstructionLineWidth; 56 | float PointDiameter; 57 | 58 | bool UseDataDefinedColors; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /include/QtNodes/internal/DataFlowGraphModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ConnectionIdUtils.hpp" 4 | #include "NodeDelegateModelRegistry.hpp" 5 | #include "AbstractGraphModel.hpp" 6 | #include "StyleCollection.hpp" 7 | #include "Serializable.hpp" 8 | 9 | #include "Export.hpp" 10 | 11 | #include 12 | 13 | #include 14 | 15 | namespace QtNodes 16 | { 17 | 18 | class NODE_EDITOR_PUBLIC DataFlowGraphModel 19 | : public AbstractGraphModel 20 | , public Serializable 21 | { 22 | Q_OBJECT 23 | 24 | public: 25 | struct NodeGeometryData 26 | { 27 | QSize size; 28 | QPointF pos; 29 | }; 30 | 31 | public: 32 | DataFlowGraphModel(std::shared_ptr registry); 33 | 34 | std::shared_ptr 35 | dataModelRegistry() { return _registry; } 36 | 37 | public: 38 | std::unordered_set 39 | allNodeIds() const override; 40 | 41 | std::unordered_set 42 | allConnectionIds(NodeId const nodeId) const override; 43 | 44 | std::unordered_set 45 | connections(NodeId nodeId, 46 | PortType portType, 47 | PortIndex portIndex) const override; 48 | 49 | bool 50 | connectionExists(ConnectionId const connectionId) const override; 51 | 52 | NodeId 53 | addNode(QString const nodeType) override; 54 | 55 | bool 56 | connectionPossible(ConnectionId const connectionId) const override; 57 | 58 | void 59 | addConnection(ConnectionId const connectionId) override; 60 | 61 | bool 62 | nodeExists(NodeId const nodeId) const override; 63 | 64 | QVariant 65 | nodeData(NodeId nodeId, NodeRole role) const override; 66 | 67 | 68 | NodeFlags 69 | nodeFlags(NodeId nodeId) const override; 70 | 71 | bool 72 | setNodeData(NodeId nodeId, 73 | NodeRole role, 74 | QVariant value) override; 75 | 76 | QVariant 77 | portData(NodeId nodeId, 78 | PortType portType, 79 | PortIndex portIndex, 80 | PortRole role) const override; 81 | 82 | bool 83 | setPortData(NodeId nodeId, 84 | PortType portType, 85 | PortIndex portIndex, 86 | QVariant const& value, 87 | PortRole role = PortRole::Data) override; 88 | 89 | bool 90 | deleteConnection(ConnectionId const connectionId) override; 91 | 92 | bool 93 | deleteNode(NodeId const nodeId) override; 94 | 95 | QJsonObject 96 | saveNode(NodeId const) const override; 97 | 98 | QJsonObject 99 | save() const override; 100 | 101 | void 102 | loadNode(QJsonObject const & nodeJson) override; 103 | 104 | void 105 | load(QJsonObject const &json) override; 106 | 107 | /** 108 | * Fetches the NodeDelegateModel for the given `nodeId` and tries to cast the 109 | * stored pointer to the given type 110 | */ 111 | template 112 | NodeDelegateModelType* 113 | delegateModel(NodeId const nodeId) 114 | { 115 | auto it = _models.find(nodeId); 116 | if (it == _models.end()) 117 | return nullptr; 118 | 119 | auto model = dynamic_cast(it->second.get()); 120 | 121 | return model; 122 | } 123 | 124 | Q_SIGNALS: 125 | void 126 | inPortDataWasSet(NodeId const, 127 | PortType const, 128 | PortIndex const); 129 | 130 | private: 131 | NodeId newNodeId() override { return _nextNodeId++; } 132 | 133 | private Q_SLOTS: 134 | /** 135 | * Fuction is called in three cases: 136 | * 137 | * - By underlying NodeDelegateModel when a node has new data to propagate. 138 | * @see DataFlowGraphModel::addNode 139 | * - When a new connection is created. 140 | * @see DataFlowGraphModel::addConnection 141 | * - When a node restored from JSON an needs to send data downstream. 142 | * @see DataFlowGraphModel::loadNode 143 | */ 144 | void 145 | onOutPortDataUpdated(NodeId const nodeId, 146 | PortIndex const portIndex); 147 | 148 | 149 | /// Function is called after detaching a connection. 150 | void 151 | propagateEmptyDataTo(NodeId const nodeId, 152 | PortIndex const portIndex); 153 | 154 | private: 155 | std::shared_ptr _registry; 156 | 157 | NodeId _nextNodeId; 158 | 159 | std::unordered_map> 161 | _models; 162 | 163 | std::unordered_set _connectivity; 164 | 165 | mutable std::unordered_map 166 | _nodeGeometryData; 167 | }; 168 | 169 | 170 | } 171 | -------------------------------------------------------------------------------- /include/QtNodes/internal/DataFlowGraphicsScene.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "BasicGraphicsScene.hpp" 4 | #include "DataFlowGraphModel.hpp" 5 | #include "Export.hpp" 6 | 7 | 8 | namespace QtNodes 9 | { 10 | 11 | 12 | /// @brief An advanced scene working with data-propagating graphs. 13 | /** 14 | * The class represents a scene that existed in v2.x but built wit the 15 | * new model-view approach in mind. 16 | */ 17 | class NODE_EDITOR_PUBLIC DataFlowGraphicsScene 18 | : public BasicGraphicsScene 19 | { 20 | Q_OBJECT 21 | public: 22 | 23 | DataFlowGraphicsScene(DataFlowGraphModel &graphModel, 24 | QObject * parent = nullptr); 25 | 26 | ~DataFlowGraphicsScene() = default; 27 | 28 | public: 29 | std::vector 30 | selectedNodes() const; 31 | 32 | public: 33 | QMenu * 34 | createSceneMenu(QPointF const scenePos) override; 35 | 36 | 37 | public Q_SLOTS: 38 | void 39 | save() const; 40 | 41 | void 42 | load(); 43 | 44 | Q_SIGNALS: 45 | void 46 | sceneLoaded(); 47 | 48 | private: 49 | DataFlowGraphModel &_graphModel; 50 | }; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /include/QtNodes/internal/DefaultNodePainter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Definitions.hpp" 6 | #include "AbstractNodePainter.hpp" 7 | 8 | namespace QtNodes 9 | { 10 | 11 | class BasicGraphicsScene; 12 | class GraphModel; 13 | class NodeGeometry; 14 | class NodeGraphicsObject; 15 | class NodeState; 16 | 17 | /// @ Lightweight class incapsulating paint code. 18 | class NODE_EDITOR_PUBLIC DefaultNodePainter : public AbstractNodePainter 19 | { 20 | public: 21 | void paint(QPainter * painter, 22 | NodeGraphicsObject & ngo) const override; 23 | 24 | void drawNodeRect(QPainter * painter, 25 | NodeGraphicsObject & ngo) const; 26 | 27 | void drawConnectionPoints(QPainter * painter, 28 | NodeGraphicsObject & ngo) const; 29 | 30 | void drawFilledConnectionPoints(QPainter * painter, 31 | NodeGraphicsObject & ngo) const; 32 | 33 | void drawNodeCaption(QPainter * painter, 34 | NodeGraphicsObject & ngo) const; 35 | 36 | void drawEntryLabels(QPainter * painter, 37 | NodeGraphicsObject & ngo) const; 38 | 39 | void drawResizeRect(QPainter * painter, 40 | NodeGraphicsObject & ngo) const; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /include/QtNodes/internal/Definitions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Export.hpp" 4 | 5 | #include 6 | 7 | #include 8 | 9 | /** 10 | * @file 11 | * Important definitions used throughout the library. 12 | */ 13 | 14 | namespace QtNodes 15 | { 16 | Q_NAMESPACE_EXPORT(NODE_EDITOR_PUBLIC) 17 | 18 | /** 19 | * Constants used for fetching QVariant data from GraphModel. 20 | */ 21 | enum class NodeRole 22 | { 23 | Type = 0, ///< Type of the current node, usually a string. 24 | Position = 1, ///< `QPointF` positon of the node on the scene. 25 | Size = 2, ///< `QSize` for resizable nodes. 26 | CaptionVisible = 3, ///< `bool` for caption visibility. 27 | Caption = 4, ///< `QString` for node caption. 28 | Style = 5, ///< Custom NodeStyle as QJsonDocument 29 | InternalData = 6, ///< Node-stecific user data as QJsonObject 30 | InPortCount = 7, ///< `unsigned int` 31 | OutPortCount = 9, ///< `unsigned int` 32 | Widget = 10, ///< Optional `QWidget*` or `nullptr` 33 | }; 34 | Q_ENUM_NS(NodeRole) 35 | 36 | 37 | /** 38 | * Specific flags regulating node features and appeaarence. 39 | */ 40 | enum NodeFlag 41 | { 42 | NoFlags = 0x0, ///< Default NodeFlag 43 | Resizable = 0x1, ///< Lets the node be resizable 44 | Locked = 0x2 45 | }; 46 | 47 | Q_DECLARE_FLAGS(NodeFlags, NodeFlag) 48 | Q_FLAG_NS(NodeFlags) 49 | Q_DECLARE_OPERATORS_FOR_FLAGS(NodeFlags) 50 | 51 | 52 | /** 53 | * Constants for fetching port-related information from the GraphModel. 54 | */ 55 | enum class PortRole 56 | { 57 | Data = 0, ///< `std::shared_ptr`. 58 | DataType = 1, ///< `QString` describing the port data type. 59 | ConnectionPolicyRole = 2, ///< `enum` ConnectionPolicyRole 60 | CaptionVisible = 3, ///< `bool` for caption visibility. 61 | Caption = 4, ///< `QString` for port caption. 62 | }; 63 | Q_ENUM_NS(PortRole) 64 | 65 | 66 | /** 67 | * Defines how many connections are possible to attach to ports. The 68 | * values are fetched using PortRole::ConnectionPolicy. 69 | */ 70 | enum class ConnectionPolicy 71 | { 72 | One, ///< Just one connection for each port. 73 | Many, ///< Any number of connections possible for the port. 74 | }; 75 | Q_ENUM_NS(ConnectionPolicy) 76 | 77 | 78 | /** 79 | * Used for distinguishing input and output node ports. 80 | */ 81 | enum class PortType 82 | { 83 | In = 0, ///< Input node port (from the left). 84 | Out = 1, ///< Output node port (from the right). 85 | None = 2 86 | }; 87 | Q_ENUM_NS(PortType) 88 | 89 | 90 | using PortCount = unsigned int; 91 | 92 | /// ports are consecutively numbered starting from zero. 93 | using PortIndex = unsigned int; 94 | 95 | static constexpr PortIndex InvalidPortIndex = 96 | std::numeric_limits::max(); 97 | 98 | /// Unique Id associated with each node in the GraphModel. 99 | using NodeId = unsigned int; 100 | 101 | static constexpr NodeId InvalidNodeId = 102 | std::numeric_limits::max(); 103 | 104 | /** 105 | * A unique connection identificator that stores 106 | * out `NodeId`, out `PortIndex`, in `NodeId`, in `PortIndex` 107 | */ 108 | struct ConnectionId 109 | { 110 | NodeId outNodeId; 111 | PortIndex outPortIndex; 112 | NodeId inNodeId; 113 | PortIndex inPortIndex; 114 | }; 115 | 116 | 117 | inline bool operator==(ConnectionId const& a, ConnectionId const& b) 118 | { 119 | return a.outNodeId == b.outNodeId && 120 | a.outPortIndex == b.outPortIndex && 121 | a.inNodeId == b.inNodeId && 122 | a.inPortIndex == b.inPortIndex; 123 | } 124 | 125 | inline bool operator!=(ConnectionId const& a, ConnectionId const& b) 126 | { 127 | return !(a == b); 128 | } 129 | 130 | 131 | inline void invertConnection(ConnectionId& id) 132 | { 133 | std::swap(id.outNodeId, id.inNodeId); 134 | std::swap(id.outPortIndex, id.inPortIndex); 135 | } 136 | 137 | 138 | } 139 | -------------------------------------------------------------------------------- /include/QtNodes/internal/Export.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Compiler.hpp" 4 | #include "OperatingSystem.hpp" 5 | 6 | #ifdef NODE_EDITOR_PLATFORM_WINDOWS 7 | # define NODE_EDITOR_EXPORT __declspec(dllexport) 8 | # define NODE_EDITOR_IMPORT __declspec(dllimport) 9 | # define NODE_EDITOR_LOCAL 10 | #elif \ 11 | NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR >= 4 || \ 12 | defined (NODE_EDITOR_COMPILER_CLANG) 13 | # define NODE_EDITOR_EXPORT __attribute__((visibility("default"))) 14 | # define NODE_EDITOR_IMPORT __attribute__((visibility("default"))) 15 | # define NODE_EDITOR_LOCAL __attribute__((visibility("hidden"))) 16 | #else 17 | # define NODE_EDITOR_EXPORT 18 | # define NODE_EDITOR_IMPORT 19 | # define NODE_EDITOR_LOCAL 20 | #endif 21 | 22 | #ifdef __cplusplus 23 | # define NODE_EDITOR_DEMANGLED extern "C" 24 | #else 25 | # define NODE_EDITOR_DEMANGLED 26 | #endif 27 | 28 | 29 | #if defined (NODE_EDITOR_SHARED) && !defined (NODE_EDITOR_STATIC) 30 | # ifdef NODE_EDITOR_EXPORTS 31 | # define NODE_EDITOR_PUBLIC NODE_EDITOR_EXPORT 32 | # else 33 | # define NODE_EDITOR_PUBLIC NODE_EDITOR_IMPORT 34 | # endif 35 | # define NODE_EDITOR_PRIVATE NODE_EDITOR_LOCAL 36 | #elif !defined (NODE_EDITOR_SHARED) && defined (NODE_EDITOR_STATIC) 37 | # define NODE_EDITOR_PUBLIC 38 | # define NODE_EDITOR_PRIVATE 39 | #elif defined (NODE_EDITOR_SHARED) && defined (NODE_EDITOR_STATIC) 40 | # ifdef NODE_EDITOR_EXPORTS 41 | # error "Cannot build as shared and static simultaneously." 42 | # else 43 | # error "Cannot link against shared and static simultaneously." 44 | # endif 45 | #else 46 | # ifdef NODE_EDITOR_EXPORTS 47 | # error "Choose whether to build as shared or static." 48 | # else 49 | # error "Choose whether to link against shared or static." 50 | # endif 51 | #endif 52 | -------------------------------------------------------------------------------- /include/QtNodes/internal/GraphicsView.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Export.hpp" 6 | 7 | namespace QtNodes 8 | { 9 | 10 | class BasicGraphicsScene; 11 | 12 | 13 | /** 14 | * @brief A central view able to render objects from `BasicGraphicsScene`. 15 | */ 16 | class NODE_EDITOR_PUBLIC GraphicsView 17 | : public QGraphicsView 18 | { 19 | Q_OBJECT 20 | public: 21 | struct ScaleRange 22 | { 23 | double minimum = 0; 24 | double maximum = 0; 25 | }; 26 | 27 | public: 28 | GraphicsView(QWidget* parent = Q_NULLPTR); 29 | GraphicsView(BasicGraphicsScene* scene, QWidget* parent = Q_NULLPTR); 30 | 31 | GraphicsView(const GraphicsView &) = delete; 32 | GraphicsView 33 | operator=(const GraphicsView &) = delete; 34 | 35 | QAction* 36 | clearSelectionAction() const; 37 | 38 | QAction* 39 | deleteSelectionAction() const; 40 | 41 | void 42 | setScene(BasicGraphicsScene* scene); 43 | 44 | void 45 | centerScene(); 46 | 47 | /// @brief max=0/min=0 indicates infinite zoom in/out 48 | void 49 | setScaleRange(double minimum = 0, double maximum = 0); 50 | 51 | void 52 | setScaleRange(ScaleRange range); 53 | 54 | double 55 | getScale() const; 56 | 57 | public Q_SLOTS: 58 | void 59 | scaleUp(); 60 | 61 | void 62 | scaleDown(); 63 | 64 | void 65 | setupScale(double scale); 66 | 67 | void 68 | onDeleteSelectedObjects(); 69 | 70 | void 71 | onDuplicateSelectedObjects(); 72 | 73 | Q_SIGNALS: 74 | void 75 | scaleChanged(double scale); 76 | 77 | protected: 78 | void 79 | contextMenuEvent(QContextMenuEvent* event) override; 80 | 81 | void 82 | wheelEvent(QWheelEvent* event) override; 83 | 84 | void 85 | keyPressEvent(QKeyEvent* event) override; 86 | 87 | void 88 | keyReleaseEvent(QKeyEvent* event) override; 89 | 90 | void 91 | mousePressEvent(QMouseEvent* event) override; 92 | 93 | void 94 | mouseMoveEvent(QMouseEvent* event) override; 95 | 96 | void 97 | drawBackground(QPainter* painter, const QRectF & r) override; 98 | 99 | void 100 | showEvent(QShowEvent* event) override; 101 | 102 | protected: 103 | BasicGraphicsScene* 104 | nodeScene(); 105 | 106 | private: 107 | QAction* _clearSelectionAction = nullptr; 108 | QAction* _deleteSelectionAction = nullptr; 109 | QAction* _duplicateSelectionAction = nullptr; 110 | 111 | QPointF _clickPos; 112 | ScaleRange _scaleRange; 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /include/QtNodes/internal/GraphicsViewStyle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Export.hpp" 6 | #include "Style.hpp" 7 | 8 | namespace QtNodes 9 | { 10 | 11 | class NODE_EDITOR_PUBLIC GraphicsViewStyle : public Style 12 | { 13 | public: 14 | 15 | GraphicsViewStyle(); 16 | 17 | GraphicsViewStyle(QString jsonText); 18 | 19 | ~GraphicsViewStyle() = default; 20 | 21 | public: 22 | 23 | static void setStyle(QString jsonText); 24 | 25 | private: 26 | 27 | void loadJson(QJsonObject const & json) override; 28 | 29 | QJsonObject toJson() const override; 30 | 31 | public: 32 | 33 | QColor BackgroundColor; 34 | QColor FineGridColor; 35 | QColor CoarseGridColor; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /include/QtNodes/internal/NodeData.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "Export.hpp" 9 | 10 | namespace QtNodes 11 | { 12 | 13 | /** 14 | * `id` represents an internal unique data type for the given port. 15 | * `name` is a normal text description. 16 | */ 17 | struct NODE_EDITOR_PUBLIC NodeDataType 18 | { 19 | QString id; 20 | QString name; 21 | }; 22 | 23 | /** 24 | * Class represents data transferred between nodes. 25 | * @param type is used for comparing the types 26 | * The actual data is stored in subtypes 27 | */ 28 | class NODE_EDITOR_PUBLIC NodeData 29 | { 30 | public: 31 | 32 | virtual 33 | ~NodeData() = default; 34 | 35 | virtual bool 36 | sameType(NodeData const &nodeData) const 37 | { 38 | return (this->type().id == nodeData.type().id); 39 | } 40 | 41 | /// Type for inner use 42 | virtual NodeDataType 43 | type() const = 0; 44 | }; 45 | 46 | } 47 | Q_DECLARE_METATYPE(QtNodes::NodeDataType) 48 | Q_DECLARE_METATYPE(std::shared_ptr) 49 | -------------------------------------------------------------------------------- /include/QtNodes/internal/NodeDelegateModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "Definitions.hpp" 8 | #include "Export.hpp" 9 | #include "NodeData.hpp" 10 | #include "NodeStyle.hpp" 11 | #include "Serializable.hpp" 12 | 13 | namespace QtNodes 14 | { 15 | 16 | class StyleCollection; 17 | 18 | /** 19 | * The class wraps Node-specific data operations and propagates it to 20 | * the nesting DataFlowGraphModel which is a subclass of 21 | * AbstractGrapModel. 22 | * This class is the same what has been called NodeDataModel before v3. 23 | */ 24 | class NODE_EDITOR_PUBLIC NodeDelegateModel 25 | : public QObject 26 | , public Serializable 27 | { 28 | Q_OBJECT 29 | 30 | public: 31 | NodeDelegateModel(); 32 | 33 | virtual 34 | ~NodeDelegateModel() = default; 35 | 36 | /// It is possible to hide caption in GUI 37 | virtual 38 | bool 39 | captionVisible() const { return true; } 40 | 41 | /// Caption is used in GUI 42 | virtual 43 | QString 44 | caption() const = 0; 45 | 46 | /// It is possible to hide port caption in GUI 47 | virtual 48 | bool 49 | portCaptionVisible(PortType, PortIndex) const { return false; } 50 | 51 | /// Port caption is used in GUI to label individual ports 52 | virtual 53 | QString 54 | portCaption(PortType, PortIndex) const { return QString(); } 55 | 56 | /// Name makes this model unique 57 | virtual 58 | QString 59 | name() const = 0; 60 | 61 | public: 62 | QJsonObject 63 | save() const override; 64 | 65 | void 66 | load(QJsonObject const &) override; 67 | 68 | public: 69 | virtual 70 | unsigned int 71 | nPorts(PortType portType) const = 0; 72 | 73 | virtual 74 | NodeDataType 75 | dataType(PortType portType, PortIndex portIndex) const = 0; 76 | 77 | public: 78 | virtual 79 | ConnectionPolicy 80 | portConnectionPolicy(PortType, PortIndex) const; 81 | 82 | NodeStyle const & 83 | nodeStyle() const; 84 | 85 | void 86 | setNodeStyle(NodeStyle const & style); 87 | 88 | public: 89 | virtual 90 | void 91 | setInData(std::shared_ptr nodeData, PortIndex const portIndex) = 0; 92 | 93 | virtual 94 | std::shared_ptr 95 | outData(PortIndex const port) = 0; 96 | 97 | /** 98 | * It is recommented to preform a lazy initialization for the 99 | * embedded widget and create it inside this function, not in the 100 | * constructor of the current model. 101 | * 102 | * Our Model Registry is able to shortly instantiate models in order 103 | * to call the non-static `Model::name()`. If the embedded widget is 104 | * allocated in the constructor but not actually embedded into some 105 | * QGraphicsProxyWidget, we'll gonna have a dangling pointer. 106 | */ 107 | virtual 108 | QWidget * 109 | embeddedWidget() = 0; 110 | 111 | virtual 112 | bool 113 | resizable() const { return false; } 114 | 115 | public Q_SLOTS: 116 | 117 | virtual 118 | void 119 | inputConnectionCreated(ConnectionId const &) {} 120 | 121 | virtual 122 | void 123 | inputConnectionDeleted(ConnectionId const &) {} 124 | 125 | virtual 126 | void 127 | outputConnectionCreated(ConnectionId const &) {} 128 | 129 | virtual 130 | void 131 | outputConnectionDeleted(ConnectionId const &) {} 132 | 133 | Q_SIGNALS: 134 | 135 | /// Triggers the updates in the nodes downstream. 136 | void 137 | dataUpdated(PortIndex const index); 138 | 139 | /// Triggers the propagation of the empty data downstream. 140 | void 141 | dataInvalidated(PortIndex const index); 142 | 143 | void 144 | computingStarted(); 145 | 146 | void 147 | computingFinished(); 148 | 149 | void 150 | embeddedWidgetSizeUpdated(); 151 | 152 | /// Call this function before deleting the data associated with ports. 153 | /** 154 | * The function notifies the Graph Model and makes it remove and recompute the 155 | * affected connection addresses. 156 | */ 157 | void 158 | portsAboutToBeDeleted(PortType const portType, 159 | PortIndex const first, 160 | PortIndex const last); 161 | 162 | /// Call this function when data and port moditications are finished. 163 | void 164 | portsDeleted(); 165 | 166 | /// Call this function before inserting the data associated with ports. 167 | /** 168 | * The function notifies the Graph Model and makes it recompute the affected 169 | * connection addresses. 170 | */ 171 | void 172 | portsAboutToBeInserted(PortType const portType, 173 | PortIndex const first, 174 | PortIndex const last); 175 | 176 | /// Call this function when data and port moditications are finished. 177 | void 178 | portsInserted(); 179 | 180 | private: 181 | NodeStyle _nodeStyle; 182 | }; 183 | 184 | } // namespace QtNodes 185 | -------------------------------------------------------------------------------- /include/QtNodes/internal/NodeGraphicsObject.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "NodeState.hpp" 7 | 8 | class QGraphicsProxyWidget; 9 | 10 | namespace QtNodes 11 | { 12 | 13 | class BasicGraphicsScene; 14 | class AbstractGraphModel; 15 | 16 | class NodeGraphicsObject : public QGraphicsObject 17 | { 18 | Q_OBJECT 19 | public: 20 | // Needed for qgraphicsitem_cast 21 | enum { Type = UserType + 1 }; 22 | 23 | int 24 | type() const override { return Type; } 25 | 26 | public: 27 | NodeGraphicsObject(BasicGraphicsScene &scene, 28 | NodeId node); 29 | 30 | ~NodeGraphicsObject() override = default; 31 | 32 | public: 33 | AbstractGraphModel & 34 | graphModel() const; 35 | 36 | BasicGraphicsScene * 37 | nodeScene() const; 38 | 39 | NodeId 40 | nodeId() { return _nodeId; } 41 | 42 | NodeId 43 | nodeId() const { return _nodeId; } 44 | 45 | NodeState & 46 | nodeState() { return _nodeState; } 47 | 48 | NodeState const & 49 | nodeState() const { return _nodeState; } 50 | 51 | QRectF 52 | boundingRect() const override; 53 | 54 | void 55 | setGeometryChanged(); 56 | 57 | /// Visits all attached connections and corrects 58 | /// their corresponding end points. 59 | void 60 | moveConnections() const; 61 | 62 | /// Repaints the node once with reacting ports. 63 | void 64 | reactToConnection(ConnectionGraphicsObject const * cgo); 65 | 66 | protected: 67 | void 68 | paint(QPainter* painter, 69 | QStyleOptionGraphicsItem const* option, 70 | QWidget* widget = 0) override; 71 | 72 | QVariant 73 | itemChange(GraphicsItemChange change, const QVariant &value) override; 74 | 75 | void 76 | mousePressEvent(QGraphicsSceneMouseEvent* event) override; 77 | 78 | void 79 | mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; 80 | 81 | void 82 | mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override; 83 | 84 | void 85 | hoverEnterEvent(QGraphicsSceneHoverEvent* event) override; 86 | 87 | void 88 | hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override; 89 | 90 | void 91 | hoverMoveEvent(QGraphicsSceneHoverEvent *) override; 92 | 93 | void 94 | mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) override; 95 | 96 | void 97 | contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override; 98 | 99 | private: 100 | void 101 | embedQWidget(); 102 | 103 | void 104 | setLockedState(); 105 | 106 | private: 107 | NodeId _nodeId; 108 | 109 | AbstractGraphModel &_graphModel; 110 | 111 | NodeState _nodeState; 112 | 113 | // either nullptr or owned by parent QGraphicsItem 114 | QGraphicsProxyWidget * _proxyWidget; 115 | }; 116 | } 117 | -------------------------------------------------------------------------------- /include/QtNodes/internal/NodeState.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "Export.hpp" 11 | 12 | #include "Definitions.hpp" 13 | #include "NodeData.hpp" 14 | 15 | namespace QtNodes 16 | { 17 | 18 | class ConnectionGraphicsObject; 19 | class NodeGraphicsObject; 20 | 21 | /// Stores bool for hovering connections and resizing flag. 22 | class NODE_EDITOR_PUBLIC NodeState 23 | { 24 | public: 25 | 26 | NodeState(NodeGraphicsObject & ngo); 27 | 28 | public: 29 | 30 | bool 31 | hovered() const 32 | { return _hovered; } 33 | 34 | void 35 | setHovered(bool hovered = true) 36 | { _hovered = hovered; } 37 | 38 | void 39 | setResizing(bool resizing); 40 | 41 | bool 42 | resizing() const; 43 | 44 | ConnectionGraphicsObject const * 45 | connectionForReaction() const; 46 | 47 | void 48 | storeConnectionForReaction(ConnectionGraphicsObject const * cgo); 49 | 50 | void 51 | resetConnectionForReaction(); 52 | 53 | private: 54 | 55 | NodeGraphicsObject & _ngo; 56 | 57 | bool _hovered; 58 | 59 | bool _resizing; 60 | 61 | // QPointer tracks the QObject inside and is automatically cleared 62 | // when the object is destroyed. 63 | QPointer _connectionForReaction; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /include/QtNodes/internal/NodeStyle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Export.hpp" 6 | #include "Style.hpp" 7 | 8 | namespace QtNodes 9 | { 10 | 11 | class NODE_EDITOR_PUBLIC NodeStyle : public Style 12 | { 13 | public: 14 | NodeStyle(); 15 | 16 | NodeStyle(QString jsonText); 17 | 18 | NodeStyle(QJsonObject const & json); 19 | 20 | virtual ~NodeStyle() = default; 21 | 22 | public: 23 | static void setNodeStyle(QString jsonText); 24 | 25 | public: 26 | void loadJson(QJsonObject const & json) override; 27 | 28 | QJsonObject toJson() const override; 29 | 30 | public: 31 | QColor NormalBoundaryColor; 32 | QColor SelectedBoundaryColor; 33 | QColor GradientColor0; 34 | QColor GradientColor1; 35 | QColor GradientColor2; 36 | QColor GradientColor3; 37 | QColor ShadowColor; 38 | QColor FontColor; 39 | QColor FontColorFaded; 40 | 41 | QColor ConnectionPointColor; 42 | QColor FilledConnectionPointColor; 43 | 44 | QColor WarningColor; 45 | QColor ErrorColor; 46 | 47 | float PenWidth; 48 | float HoveredPenWidth; 49 | 50 | float ConnectionPointDiameter; 51 | 52 | float Opacity; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /include/QtNodes/internal/OperatingSystem.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if \ 4 | defined (__CYGWIN__) || \ 5 | defined (__CYGWIN32__) 6 | # define NODE_EDITOR_PLATFORM "Cygwin" 7 | # define NODE_EDITOR_PLATFORM_CYGWIN 8 | # define NODE_EDITOR_PLATFORM_UNIX 9 | # define NODE_EDITOR_PLATFORM_WINDOWS 10 | #elif \ 11 | defined (_WIN16) || \ 12 | defined (_WIN32) || \ 13 | defined (_WIN64) || \ 14 | defined (__WIN32__) || \ 15 | defined (__TOS_WIN__) || \ 16 | defined (__WINDOWS__) 17 | # define NODE_EDITOR_PLATFORM "Windows" 18 | # define NODE_EDITOR_PLATFORM_WINDOWS 19 | #elif \ 20 | defined (macintosh) || \ 21 | defined (Macintosh) || \ 22 | defined (__TOS_MACOS__) || \ 23 | (defined (__APPLE__) && defined (__MACH__)) 24 | # define NODE_EDITOR_PLATFORM "Mac" 25 | # define NODE_EDITOR_PLATFORM_MAC 26 | # define NODE_EDITOR_PLATFORM_UNIX 27 | #elif \ 28 | defined (linux) || \ 29 | defined (__linux) || \ 30 | defined (__linux__) || \ 31 | defined (__TOS_LINUX__) 32 | # define NODE_EDITOR_PLATFORM "Linux" 33 | # define NODE_EDITOR_PLATFORM_LINUX 34 | # define NODE_EDITOR_PLATFORM_UNIX 35 | #elif \ 36 | defined (__FreeBSD__) || \ 37 | defined (__OpenBSD__) || \ 38 | defined (__NetBSD__) || \ 39 | defined (__bsdi__) || \ 40 | defined (__DragonFly__) 41 | # define NODE_EDITOR_PLATFORM "BSD" 42 | # define NODE_EDITOR_PLATFORM_BSD 43 | # define NODE_EDITOR_PLATFORM_UNIX 44 | #elif \ 45 | defined (sun) || \ 46 | defined (__sun) 47 | # define NODE_EDITOR_PLATFORM "Solaris" 48 | # define NODE_EDITOR_PLATFORM_SOLARIS 49 | # define NODE_EDITOR_PLATFORM_UNIX 50 | #elif \ 51 | defined (_AIX) || \ 52 | defined (__TOS_AIX__) 53 | # define NODE_EDITOR_PLATFORM "AIX" 54 | # define NODE_EDITOR_PLATFORM_AIX 55 | # define NODE_EDITOR_PLATFORM_UNIX 56 | #elif \ 57 | defined (hpux) || \ 58 | defined (_hpux) || \ 59 | defined (__hpux) 60 | # define NODE_EDITOR_PLATFORM "HPUX" 61 | # define NODE_EDITOR_PLATFORM_HPUX 62 | # define NODE_EDITOR_PLATFORM_UNIX 63 | #elif \ 64 | defined (__QNX__) 65 | # define NODE_EDITOR_PLATFORM "QNX" 66 | # define NODE_EDITOR_PLATFORM_QNX 67 | # define NODE_EDITOR_PLATFORM_UNIX 68 | #elif \ 69 | defined (unix) || \ 70 | defined (__unix) || \ 71 | defined (__unix__) 72 | # define NODE_EDITOR_PLATFORM "Unix" 73 | # define NODE_EDITOR_PLATFORM_UNIX 74 | #endif 75 | 76 | #ifndef NODE_EDITOR_PLATFORM 77 | # error "Current platform is not supported." 78 | #endif 79 | -------------------------------------------------------------------------------- /include/QtNodes/internal/QStringStdHash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) 6 | 7 | // As of 5.14 there is a specialization std::hash 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace std 15 | { 16 | template<> 17 | struct hash 18 | { 19 | inline std::size_t 20 | operator()(QString const &s) const 21 | { 22 | return qHash(s); 23 | } 24 | }; 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /include/QtNodes/internal/QUuidStdHash.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace std 9 | { 10 | template<> 11 | struct hash 12 | { 13 | inline 14 | std::size_t 15 | operator()(QUuid const& uid) const 16 | { 17 | return qHash(uid); 18 | } 19 | }; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /include/QtNodes/internal/Serializable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace QtNodes 6 | { 7 | 8 | class Serializable 9 | { 10 | public: 11 | 12 | virtual 13 | ~Serializable() = default; 14 | 15 | virtual 16 | QJsonObject 17 | save() const { return {}; } 18 | 19 | virtual void 20 | load(QJsonObject const & /*p*/) {} 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /include/QtNodes/internal/Style.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace QtNodes 11 | { 12 | 13 | class Style // : public QObject 14 | { 15 | //Q_OBJECT 16 | 17 | public: 18 | 19 | virtual ~Style() = default; 20 | 21 | public: 22 | 23 | virtual 24 | void loadJson(QJsonObject const & json) = 0; 25 | 26 | virtual 27 | QJsonObject toJson() const = 0; 28 | 29 | /// Loads from utf-8 byte array. 30 | virtual 31 | void loadJsonFromByteArray(QByteArray const & byteArray) 32 | { 33 | auto json = QJsonDocument::fromJson(byteArray).object(); 34 | 35 | loadJson(json); 36 | } 37 | 38 | virtual 39 | void loadJsonText(QString jsonText) 40 | { 41 | loadJsonFromByteArray(jsonText.toUtf8()); 42 | } 43 | 44 | virtual 45 | void loadJsonFile(QString fileName) 46 | { 47 | QFile file(fileName); 48 | 49 | if (!file.open(QIODevice::ReadOnly)) 50 | { 51 | qWarning() << "Couldn't open file " << fileName; 52 | 53 | return; 54 | } 55 | 56 | loadJsonFromByteArray(file.readAll()); 57 | } 58 | 59 | }; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /include/QtNodes/internal/StyleCollection.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Export.hpp" 4 | 5 | #include "ConnectionStyle.hpp" 6 | #include "GraphicsViewStyle.hpp" 7 | #include "NodeStyle.hpp" 8 | 9 | namespace QtNodes 10 | { 11 | 12 | class NODE_EDITOR_PUBLIC StyleCollection 13 | { 14 | public: 15 | 16 | static 17 | NodeStyle const & nodeStyle(); 18 | 19 | static 20 | ConnectionStyle const & connectionStyle(); 21 | 22 | static 23 | GraphicsViewStyle const & flowViewStyle(); 24 | 25 | public: 26 | 27 | static 28 | void setNodeStyle(NodeStyle); 29 | 30 | static 31 | void setConnectionStyle(ConnectionStyle); 32 | 33 | static 34 | void setGraphicsViewStyle(GraphicsViewStyle); 35 | 36 | private: 37 | 38 | StyleCollection() = default; 39 | 40 | StyleCollection(StyleCollection const &) = delete; 41 | 42 | StyleCollection & operator=(StyleCollection const &) = delete; 43 | 44 | static 45 | StyleCollection & instance(); 46 | 47 | private: 48 | 49 | NodeStyle _nodeStyle; 50 | 51 | ConnectionStyle _connectionStyle; 52 | 53 | GraphicsViewStyle _flowViewStyle; 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /include/QtNodes/internal/locateNode.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | 7 | class QGraphicsScene; 8 | 9 | namespace QtNodes 10 | { 11 | 12 | class NodeGraphicsObject; 13 | 14 | 15 | NodeGraphicsObject* 16 | locateNodeAt(QPointF scenePoint, 17 | QGraphicsScene &scene, 18 | QTransform const & viewTransform); 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /resources/DefaultStyle.json: -------------------------------------------------------------------------------- 1 | { 2 | "GraphicsViewStyle": { 3 | "BackgroundColor": [53, 53, 53], 4 | "FineGridColor": [60, 60, 60], 5 | "CoarseGridColor": [25, 25, 25] 6 | }, 7 | "NodeStyle": { 8 | "NormalBoundaryColor": [255, 255, 255], 9 | "SelectedBoundaryColor": [255, 165, 0], 10 | "GradientColor0": "gray", 11 | "GradientColor1": [80, 80, 80], 12 | "GradientColor2": [64, 64, 64], 13 | "GradientColor3": [58, 58, 58], 14 | "ShadowColor": [20, 20, 20], 15 | "FontColor" : "white", 16 | "FontColorFaded" : "gray", 17 | "ConnectionPointColor": [169, 169, 169], 18 | "FilledConnectionPointColor": "cyan", 19 | "ErrorColor": "red", 20 | "WarningColor": [128, 128, 0], 21 | 22 | "PenWidth": 1.0, 23 | "HoveredPenWidth": 1.5, 24 | 25 | "ConnectionPointDiameter": 8.0, 26 | 27 | "Opacity": 0.8 28 | }, 29 | "ConnectionStyle": { 30 | "ConstructionColor": "gray", 31 | "NormalColor": "darkcyan", 32 | "SelectedColor": [100, 100, 100], 33 | "SelectedHaloColor": "orange", 34 | "HoveredColor": "lightcyan", 35 | 36 | "LineWidth": 3.0, 37 | "ConstructionLineWidth": 2.0, 38 | "PointDiameter": 10.0, 39 | 40 | "UseDataDefinedColors": false 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /resources/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | DefaultStyle.json 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/AbstractGraphModel.cpp: -------------------------------------------------------------------------------- 1 | #include "AbstractGraphModel.hpp" 2 | 3 | #include 4 | 5 | 6 | namespace QtNodes 7 | { 8 | 9 | void 10 | AbstractGraphModel:: 11 | portsAboutToBeDeleted(NodeId const nodeId, 12 | PortType const portType, 13 | PortIndex const first, 14 | PortIndex const last) 15 | { 16 | _shiftedByDynamicPortsConnections.clear(); 17 | 18 | auto portCountRole = portType == PortType::In ? 19 | NodeRole::InPortCount : 20 | NodeRole::OutPortCount; 21 | 22 | unsigned int portCount = nodeData(nodeId, portCountRole).toUInt(); 23 | 24 | if (first > portCount - 1) 25 | return; 26 | 27 | if (last < first) 28 | return; 29 | 30 | auto clampedLast = std::min(last, portCount - 1); 31 | 32 | for (PortIndex portIndex = first; portIndex <= clampedLast; ++portIndex) 33 | { 34 | std::unordered_set conns = 35 | connections(nodeId, portType, portIndex); 36 | 37 | for (auto connectionId : conns) 38 | { 39 | deleteConnection(connectionId); 40 | } 41 | } 42 | 43 | 44 | std::size_t const nRemovedPorts = clampedLast - first + 1; 45 | 46 | for (PortIndex portIndex = clampedLast + 1; portIndex < portCount; ++portIndex) 47 | { 48 | std::unordered_set conns = 49 | connections(nodeId, portType, portIndex); 50 | 51 | for (auto connectionId : conns) 52 | { 53 | // Erases the information about the port on one side; 54 | auto c = makeIncompleteConnectionId(connectionId, portType); 55 | 56 | c = makeCompleteConnectionId(c, 57 | nodeId, 58 | portIndex - nRemovedPorts); 59 | 60 | _shiftedByDynamicPortsConnections.push_back(c); 61 | 62 | deleteConnection(connectionId); 63 | } 64 | } 65 | } 66 | 67 | 68 | void 69 | AbstractGraphModel:: 70 | portsDeleted() 71 | { 72 | for (auto const connectionId : _shiftedByDynamicPortsConnections) 73 | { 74 | addConnection(connectionId); 75 | } 76 | 77 | _shiftedByDynamicPortsConnections.clear(); 78 | } 79 | 80 | 81 | void 82 | AbstractGraphModel:: 83 | portsAboutToBeInserted(NodeId const nodeId, 84 | PortType const portType, 85 | PortIndex const first, 86 | PortIndex const last) 87 | { 88 | _shiftedByDynamicPortsConnections.clear(); 89 | 90 | auto portCountRole = portType == PortType::In ? 91 | NodeRole::InPortCount : 92 | NodeRole::OutPortCount; 93 | 94 | unsigned int portCount = nodeData(nodeId, portCountRole).toUInt(); 95 | 96 | if (first > portCount) 97 | return; 98 | 99 | if (last < first) 100 | return; 101 | 102 | std::size_t const nNewPorts = last - first + 1; 103 | 104 | for (PortIndex portIndex = first; portIndex < portCount; ++portIndex) 105 | { 106 | std::unordered_set conns = 107 | connections(nodeId, portType, portIndex); 108 | 109 | for( auto connectionId : conns ) 110 | { 111 | // Erases the information about the port on one side; 112 | auto c = makeIncompleteConnectionId(connectionId, 113 | portType); 114 | 115 | c = makeCompleteConnectionId(c, 116 | nodeId, 117 | portIndex + nNewPorts); 118 | 119 | _shiftedByDynamicPortsConnections.push_back(c); 120 | 121 | deleteConnection(connectionId); 122 | } 123 | } 124 | } 125 | 126 | 127 | void 128 | AbstractGraphModel:: 129 | portsInserted() 130 | { 131 | for (auto const connectionId : _shiftedByDynamicPortsConnections) 132 | { 133 | addConnection(connectionId); 134 | } 135 | 136 | _shiftedByDynamicPortsConnections.clear(); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /src/AbstractNodeGeometry.cpp: -------------------------------------------------------------------------------- 1 | #include "AbstractNodeGeometry.hpp" 2 | 3 | #include "AbstractGraphModel.hpp" 4 | #include "StyleCollection.hpp" 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace QtNodes 11 | { 12 | 13 | AbstractNodeGeometry:: 14 | AbstractNodeGeometry(AbstractGraphModel & graphModel) 15 | : _graphModel(graphModel) 16 | { 17 | // 18 | } 19 | 20 | 21 | QRectF 22 | AbstractNodeGeometry:: 23 | boundingRect(NodeId const nodeId) const 24 | { 25 | QSize s = size(nodeId); 26 | 27 | double ratio = 0.20; 28 | 29 | int widthMargin = s.width() * ratio; 30 | int heightMargin = s.height() * ratio; 31 | 32 | QMargins margins(widthMargin, heightMargin, widthMargin, heightMargin); 33 | 34 | QRectF r(QPointF(0, 0), s); 35 | 36 | return r.marginsAdded(margins); 37 | } 38 | 39 | 40 | QPointF 41 | AbstractNodeGeometry:: 42 | portScenePosition(NodeId const nodeId, 43 | PortType const portType, 44 | PortIndex const index, 45 | QTransform const & t) const 46 | { 47 | QPointF result = portPosition(nodeId, portType, index); 48 | 49 | return t.map(result); 50 | } 51 | 52 | 53 | PortIndex 54 | AbstractNodeGeometry:: 55 | checkPortHit(NodeId const nodeId, 56 | PortType const portType, 57 | QPointF const nodePoint) const 58 | { 59 | auto const & nodeStyle = StyleCollection::nodeStyle(); 60 | 61 | PortIndex result = InvalidPortIndex; 62 | 63 | if (portType == PortType::None) 64 | return result; 65 | 66 | double const tolerance = 2.0 * nodeStyle.ConnectionPointDiameter; 67 | 68 | size_t const n = 69 | _graphModel.nodeData(nodeId, 70 | (portType == PortType::Out) ? 71 | NodeRole::OutPortCount : 72 | NodeRole::InPortCount); 73 | 74 | for (unsigned int portIndex = 0; portIndex < n; ++portIndex) 75 | { 76 | auto pp = portPosition(nodeId, portType, portIndex); 77 | 78 | QPointF p = pp - nodePoint; 79 | auto distance = std::sqrt(QPointF::dotProduct(p, p)); 80 | 81 | if (distance < tolerance) 82 | { 83 | result = portIndex; 84 | break; 85 | } 86 | } 87 | 88 | return result; 89 | } 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/ConnectionPainter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Definitions.hpp" 7 | 8 | namespace QtNodes 9 | { 10 | 11 | class ConnectionGeometry; 12 | class ConnectionGraphicsObject; 13 | 14 | class ConnectionPainter 15 | { 16 | public: 17 | 18 | static 19 | void paint(QPainter * painter, 20 | ConnectionGraphicsObject const & cgo); 21 | 22 | static 23 | QPainterPath getPainterStroke(ConnectionGraphicsObject const & cgo); 24 | }; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/ConnectionState.cpp: -------------------------------------------------------------------------------- 1 | #include "ConnectionState.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "BasicGraphicsScene.hpp" 7 | #include "ConnectionGraphicsObject.hpp" 8 | #include "NodeGraphicsObject.hpp" 9 | 10 | namespace QtNodes 11 | { 12 | 13 | ConnectionState:: 14 | ~ConnectionState() 15 | { 16 | //resetLastHoveredNode(); 17 | } 18 | 19 | 20 | PortType 21 | ConnectionState:: 22 | requiredPort() const 23 | { 24 | PortType t = PortType::None; 25 | 26 | if (_cgo.connectionId().outNodeId == InvalidNodeId) 27 | { 28 | t = PortType::Out; 29 | } 30 | else if (_cgo.connectionId().inNodeId == InvalidNodeId) 31 | { 32 | t = PortType::In; 33 | } 34 | 35 | return t; 36 | } 37 | 38 | 39 | bool 40 | ConnectionState:: 41 | requiresPort() const 42 | { 43 | const ConnectionId& id = _cgo.connectionId(); 44 | return id.outNodeId == InvalidNodeId || 45 | id.inNodeId == InvalidNodeId; 46 | } 47 | 48 | 49 | bool 50 | ConnectionState:: 51 | hovered() const 52 | { 53 | return _hovered; 54 | } 55 | 56 | 57 | void 58 | ConnectionState:: 59 | setHovered(bool hovered) 60 | { 61 | _hovered = hovered; 62 | } 63 | 64 | 65 | void 66 | ConnectionState:: 67 | setLastHoveredNode(NodeId const nodeId) 68 | { 69 | _lastHoveredNode = nodeId; 70 | } 71 | 72 | 73 | NodeId 74 | ConnectionState:: 75 | lastHoveredNode() const 76 | { 77 | return _lastHoveredNode; 78 | } 79 | 80 | 81 | void 82 | ConnectionState:: 83 | resetLastHoveredNode() 84 | { 85 | if (_lastHoveredNode != InvalidNodeId) 86 | { 87 | auto ngo = 88 | _cgo.nodeScene()->nodeGraphicsObject(_lastHoveredNode); 89 | ngo->update(); 90 | } 91 | 92 | _lastHoveredNode = InvalidNodeId; 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/DefaultHorizontalNodeGeometry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AbstractNodeGeometry.hpp" 4 | 5 | #include 6 | 7 | 8 | namespace QtNodes 9 | { 10 | 11 | class AbstractGraphModel; 12 | class BasicGraphicsScene; 13 | 14 | 15 | class NODE_EDITOR_PUBLIC DefaultHorizontalNodeGeometry : public AbstractNodeGeometry 16 | { 17 | public: 18 | DefaultHorizontalNodeGeometry(AbstractGraphModel & graphModel); 19 | 20 | public: 21 | QSize size(NodeId const nodeId) const override; 22 | 23 | void recomputeSize(NodeId const nodeId) const override; 24 | 25 | QPointF portPosition(NodeId const nodeId, 26 | PortType const portType, 27 | PortIndex const index) const override; 28 | 29 | QPointF portTextPosition(NodeId const nodeId, 30 | PortType const portType, 31 | PortIndex const PortIndex) const override; 32 | QPointF captionPosition(NodeId const nodeId) const override; 33 | 34 | QRectF captionRect(NodeId const nodeId) const override; 35 | 36 | QPointF widgetPosition(NodeId const nodeId) const override; 37 | 38 | QRect resizeHandleRect(NodeId const nodeId) const override; 39 | private: 40 | 41 | QRectF 42 | portTextRect(NodeId const nodeId, 43 | PortType const portType, 44 | PortIndex const portIndex) const; 45 | 46 | /// Finds max number of ports and multiplies by (a port height + interval) 47 | unsigned int maxVerticalPortsExtent(NodeId const nodeId) const; 48 | 49 | unsigned int maxPortsTextAdvance(NodeId const nodeId, 50 | PortType const portType) const; 51 | private: 52 | // Some variables are mutable because we need to change drawing 53 | // metrics corresponding to fontMetrics but this doesn't change 54 | // constness of the Node. 55 | 56 | mutable unsigned int _portSize; 57 | unsigned int _portSpasing; 58 | mutable QFontMetrics _fontMetrics; 59 | mutable QFontMetrics _boldFontMetrics; 60 | }; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/DefaultVerticalNodeGeometry.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AbstractNodeGeometry.hpp" 4 | 5 | #include 6 | 7 | 8 | namespace QtNodes 9 | { 10 | 11 | class AbstractGraphModel; 12 | class BasicGraphicsScene; 13 | 14 | 15 | class NODE_EDITOR_PUBLIC DefaultVerticalNodeGeometry : public AbstractNodeGeometry 16 | { 17 | public: 18 | DefaultVerticalNodeGeometry(AbstractGraphModel & graphModel); 19 | 20 | public: 21 | QSize size(NodeId const nodeId) const override; 22 | 23 | void recomputeSize(NodeId const nodeId) const override; 24 | 25 | QPointF portPosition(NodeId const nodeId, 26 | PortType const portType, 27 | PortIndex const index) const override; 28 | 29 | QPointF portTextPosition(NodeId const nodeId, 30 | PortType const portType, 31 | PortIndex const PortIndex) const override; 32 | 33 | QPointF captionPosition(NodeId const nodeId) const override; 34 | 35 | QRectF captionRect(NodeId const nodeId) const override; 36 | 37 | QPointF widgetPosition(NodeId const nodeId) const override; 38 | 39 | QRect resizeHandleRect(NodeId const nodeId) const override; 40 | private: 41 | QRectF 42 | portTextRect(NodeId const nodeId, 43 | PortType const portType, 44 | PortIndex const portIndex) const; 45 | /// Finds 46 | unsigned int maxHorizontalPortsExtent(NodeId const nodeId) const; 47 | 48 | unsigned int maxPortsTextAdvance(NodeId const nodeId, 49 | PortType const portType) const; 50 | 51 | unsigned int portCaptionsHeight(NodeId const nodeId, 52 | PortType const portType) const; 53 | private: 54 | // Some variables are mutable because we need to change drawing 55 | // metrics corresponding to fontMetrics but this doesn't change 56 | // constness of the Node. 57 | 58 | mutable unsigned int _portSize; 59 | unsigned int _portSpasing; 60 | mutable QFontMetrics _fontMetrics; 61 | mutable QFontMetrics _boldFontMetrics; 62 | }; 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/Definitions.cpp: -------------------------------------------------------------------------------- 1 | #include "Definitions.hpp" 2 | -------------------------------------------------------------------------------- /src/GraphicsViewStyle.cpp: -------------------------------------------------------------------------------- 1 | #include "GraphicsViewStyle.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "StyleCollection.hpp" 9 | 10 | using QtNodes::GraphicsViewStyle; 11 | 12 | inline void 13 | initResources() { Q_INIT_RESOURCE(resources); } 14 | 15 | GraphicsViewStyle:: 16 | GraphicsViewStyle() 17 | { 18 | // Explicit resources inialization for preventing the static initialization 19 | // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order 20 | initResources(); 21 | 22 | // This configuration is stored inside the compiled unit and is loaded statically 23 | loadJsonFile(":DefaultStyle.json"); 24 | } 25 | 26 | 27 | GraphicsViewStyle:: 28 | GraphicsViewStyle(QString jsonText) 29 | { 30 | loadJsonText(jsonText); 31 | } 32 | 33 | 34 | void 35 | GraphicsViewStyle:: 36 | setStyle(QString jsonText) 37 | { 38 | GraphicsViewStyle style(jsonText); 39 | 40 | StyleCollection::setGraphicsViewStyle(style); 41 | } 42 | 43 | 44 | #ifdef STYLE_DEBUG 45 | #define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(v, variable) { \ 46 | if (v.type() == QJsonValue::Undefined || \ 47 | v.type() == QJsonValue::Null) \ 48 | qWarning () << "Undefined value for parameter:" << #variable; \ 49 | } 50 | #else 51 | #define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(v, variable) 52 | #endif 53 | 54 | #define FLOW_VIEW_STYLE_READ_COLOR(values, variable) { \ 55 | auto valueRef = values[#variable]; \ 56 | FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ 57 | if (valueRef.isArray()) { \ 58 | auto colorArray = valueRef.toArray(); \ 59 | std::vector rgb; rgb.reserve(3); \ 60 | for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ 61 | rgb.push_back((*it).toInt()); \ 62 | } \ 63 | variable = QColor(rgb[0], rgb[1], rgb[2]); \ 64 | } else { \ 65 | variable = QColor(valueRef.toString()); \ 66 | } \ 67 | } 68 | 69 | #define FLOW_VIEW_STYLE_WRITE_COLOR(values, variable) { \ 70 | values[#variable] = variable.name(); \ 71 | } 72 | 73 | 74 | void 75 | GraphicsViewStyle:: 76 | loadJson(QJsonObject const & json) 77 | { 78 | QJsonValue nodeStyleValues = json["GraphicsViewStyle"]; 79 | 80 | QJsonObject obj = nodeStyleValues.toObject(); 81 | 82 | FLOW_VIEW_STYLE_READ_COLOR(obj, BackgroundColor); 83 | FLOW_VIEW_STYLE_READ_COLOR(obj, FineGridColor); 84 | FLOW_VIEW_STYLE_READ_COLOR(obj, CoarseGridColor); 85 | } 86 | 87 | 88 | QJsonObject 89 | GraphicsViewStyle:: 90 | toJson() const 91 | { 92 | QJsonObject obj; 93 | 94 | FLOW_VIEW_STYLE_WRITE_COLOR(obj, BackgroundColor); 95 | FLOW_VIEW_STYLE_WRITE_COLOR(obj, FineGridColor); 96 | FLOW_VIEW_STYLE_WRITE_COLOR(obj, CoarseGridColor); 97 | 98 | QJsonObject root; 99 | root["GraphicsViewStyle"] = obj; 100 | 101 | return root; 102 | } 103 | -------------------------------------------------------------------------------- /src/NodeConnectionInteraction.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "Definitions.hpp" 8 | 9 | namespace QtNodes 10 | { 11 | 12 | class ConnectionGraphicsObject; 13 | class NodeGraphicsObject; 14 | class BasicGraphicsScene; 15 | 16 | /// Class wraps conecting and disconnecting checks. 17 | /** 18 | * An instance should be created on the stack and destroyed 19 | * automatically when the operation is completed 20 | */ 21 | class NodeConnectionInteraction 22 | { 23 | public: 24 | NodeConnectionInteraction(NodeGraphicsObject & ngo, 25 | ConnectionGraphicsObject & cgo, 26 | BasicGraphicsScene & scene); 27 | 28 | /** 29 | * Can connect when following conditions are met: 30 | * 1. Connection 'requires' a port. 31 | * 2. Connection loose end is above the node port. 32 | * 3. Source and target `nodeId`s are different. 33 | * 4. GraphModel permits connection. 34 | */ 35 | bool canConnect(PortIndex * portIndex) const; 36 | 37 | /// Creates a new connectino if possible. 38 | /** 39 | * 1. Check conditions from 'canConnect'. 40 | * 2. Creates new connection with `GraphModel::addConnection`. 41 | * 3. Adjust connection geometry. 42 | */ 43 | bool tryConnect() const; 44 | 45 | 46 | /** 47 | * 1. Delete connection with `GraphModel::deleteConnection`. 48 | * 2. Create a "draft" connection with incomplete `ConnectionId`. 49 | * 3. Repaint both previously connected nodes. 50 | */ 51 | bool disconnect(PortType portToDisconnect) const; 52 | 53 | private: 54 | 55 | PortType connectionRequiredPort() const; 56 | 57 | QPointF connectionEndScenePosition(PortType) const; 58 | 59 | QPointF nodePortScenePosition(PortType portType, 60 | PortIndex portIndex) const; 61 | 62 | PortIndex nodePortIndexUnderScenePoint(PortType portType, 63 | QPointF const &p) const; 64 | 65 | private: 66 | 67 | NodeGraphicsObject & _ngo; 68 | 69 | ConnectionGraphicsObject & _cgo; 70 | 71 | BasicGraphicsScene & _scene; 72 | }; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/NodeDelegateModel.cpp: -------------------------------------------------------------------------------- 1 | #include "NodeDelegateModel.hpp" 2 | 3 | #include "StyleCollection.hpp" 4 | 5 | namespace QtNodes 6 | { 7 | 8 | NodeDelegateModel:: 9 | NodeDelegateModel() 10 | : _nodeStyle(StyleCollection::nodeStyle()) 11 | { 12 | // Derived classes can initialize specific style here 13 | } 14 | 15 | 16 | QJsonObject 17 | NodeDelegateModel:: 18 | save() const 19 | { 20 | QJsonObject modelJson; 21 | 22 | modelJson["model-name"] = name(); 23 | 24 | return modelJson; 25 | } 26 | 27 | 28 | void 29 | NodeDelegateModel:: 30 | load(QJsonObject const &) 31 | { 32 | // 33 | } 34 | 35 | 36 | ConnectionPolicy 37 | NodeDelegateModel:: 38 | portConnectionPolicy(PortType portType, PortIndex) const 39 | { 40 | auto result = ConnectionPolicy::One; 41 | switch (portType) 42 | { 43 | case PortType::In: 44 | result = ConnectionPolicy::One; 45 | break; 46 | case PortType::Out: 47 | result = ConnectionPolicy::Many; 48 | break; 49 | case PortType::None: 50 | break; 51 | } 52 | 53 | return result; 54 | } 55 | 56 | 57 | NodeStyle const & 58 | NodeDelegateModel:: 59 | nodeStyle() const 60 | { 61 | return _nodeStyle; 62 | } 63 | 64 | 65 | void 66 | NodeDelegateModel:: 67 | setNodeStyle(NodeStyle const & style) 68 | { 69 | _nodeStyle = style; 70 | } 71 | 72 | 73 | } // namespace QtNodes 74 | -------------------------------------------------------------------------------- /src/NodeDelegateModelRegistry.cpp: -------------------------------------------------------------------------------- 1 | #include "NodeDelegateModelRegistry.hpp" 2 | 3 | #include 4 | #include 5 | 6 | using QtNodes::NodeDelegateModelRegistry; 7 | using QtNodes::NodeDelegateModel; 8 | using QtNodes::NodeDataType; 9 | 10 | std::unique_ptr 11 | NodeDelegateModelRegistry:: 12 | create(QString const& modelName) 13 | { 14 | auto it = _registeredItemCreators.find(modelName); 15 | 16 | if (it != _registeredItemCreators.end()) 17 | { 18 | return it->second(); 19 | } 20 | 21 | return nullptr; 22 | } 23 | 24 | 25 | NodeDelegateModelRegistry::RegisteredModelCreatorsMap const & 26 | NodeDelegateModelRegistry:: 27 | registeredModelCreators() const 28 | { 29 | return _registeredItemCreators; 30 | } 31 | 32 | 33 | NodeDelegateModelRegistry::RegisteredModelsCategoryMap const & 34 | NodeDelegateModelRegistry:: 35 | registeredModelsCategoryAssociation() const 36 | { 37 | return _registeredModelsCategory; 38 | } 39 | 40 | 41 | NodeDelegateModelRegistry::CategoriesSet const & 42 | NodeDelegateModelRegistry:: 43 | categories() const 44 | { 45 | return _categories; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/NodeState.cpp: -------------------------------------------------------------------------------- 1 | #include "NodeState.hpp" 2 | 3 | #include "ConnectionGraphicsObject.hpp" 4 | #include "NodeGraphicsObject.hpp" 5 | 6 | 7 | namespace QtNodes 8 | { 9 | 10 | NodeState:: 11 | NodeState(NodeGraphicsObject & ngo) 12 | : _ngo(ngo) 13 | , _hovered(false) 14 | , _resizing(false) 15 | , _connectionForReaction{nullptr} 16 | { 17 | Q_UNUSED(_ngo); 18 | } 19 | 20 | 21 | void 22 | NodeState:: 23 | setResizing(bool resizing) 24 | { 25 | _resizing = resizing; 26 | } 27 | 28 | 29 | bool 30 | NodeState:: 31 | resizing() const 32 | { 33 | return _resizing; 34 | } 35 | 36 | 37 | ConnectionGraphicsObject const * 38 | NodeState:: 39 | connectionForReaction() const 40 | { 41 | return _connectionForReaction.data(); 42 | } 43 | 44 | 45 | void 46 | NodeState:: 47 | storeConnectionForReaction(ConnectionGraphicsObject const * cgo) 48 | { 49 | _connectionForReaction = cgo; 50 | } 51 | 52 | 53 | void 54 | NodeState:: 55 | resetConnectionForReaction() 56 | { 57 | _connectionForReaction.clear(); 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/NodeStyle.cpp: -------------------------------------------------------------------------------- 1 | #include "NodeStyle.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "StyleCollection.hpp" 12 | 13 | using QtNodes::NodeStyle; 14 | 15 | inline void initResources() { Q_INIT_RESOURCE(resources); } 16 | 17 | NodeStyle:: 18 | NodeStyle() 19 | { 20 | // Explicit resources inialization for preventing the static initialization 21 | // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order 22 | initResources(); 23 | 24 | // This configuration is stored inside the compiled unit and is loaded statically 25 | loadJsonFile(":DefaultStyle.json"); 26 | } 27 | 28 | 29 | NodeStyle:: 30 | NodeStyle(QString jsonText) 31 | { 32 | loadJsonText(jsonText); 33 | } 34 | 35 | 36 | NodeStyle:: 37 | NodeStyle(QJsonObject const & json) 38 | { 39 | loadJson(json); 40 | } 41 | 42 | 43 | void 44 | NodeStyle:: 45 | setNodeStyle(QString jsonText) 46 | { 47 | NodeStyle style(jsonText); 48 | 49 | StyleCollection::setNodeStyle(style); 50 | } 51 | 52 | 53 | #ifdef STYLE_DEBUG 54 | #define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable) { \ 55 | if (v.type() == QJsonValue::Undefined || \ 56 | v.type() == QJsonValue::Null) \ 57 | qWarning() << "Undefined value for parameter:" << #variable; \ 58 | } 59 | #else 60 | #define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable) 61 | #endif 62 | 63 | #define NODE_STYLE_READ_COLOR(values, variable) { \ 64 | auto valueRef = values[#variable]; \ 65 | NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ 66 | if (valueRef.isArray()) { \ 67 | auto colorArray = valueRef.toArray(); \ 68 | std::vector rgb; rgb.reserve(3); \ 69 | for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ 70 | rgb.push_back((*it).toInt()); \ 71 | } \ 72 | variable = QColor(rgb[0], rgb[1], rgb[2]); \ 73 | } else { \ 74 | variable = QColor(valueRef.toString()); \ 75 | } \ 76 | } 77 | 78 | 79 | #define NODE_STYLE_WRITE_COLOR(values, variable) { \ 80 | values[#variable] = variable.name(); \ 81 | } 82 | 83 | #define NODE_STYLE_READ_FLOAT(values, variable) { \ 84 | auto valueRef = values[#variable]; \ 85 | NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ 86 | variable = valueRef.toDouble(); \ 87 | } 88 | 89 | #define NODE_STYLE_WRITE_FLOAT(values, variable) { \ 90 | values[#variable] = variable; \ 91 | } 92 | 93 | 94 | void 95 | NodeStyle:: 96 | loadJson(QJsonObject const & json) 97 | { 98 | QJsonValue nodeStyleValues = json["NodeStyle"]; 99 | 100 | QJsonObject obj = nodeStyleValues.toObject(); 101 | 102 | NODE_STYLE_READ_COLOR(obj, NormalBoundaryColor); 103 | NODE_STYLE_READ_COLOR(obj, SelectedBoundaryColor); 104 | NODE_STYLE_READ_COLOR(obj, GradientColor0); 105 | NODE_STYLE_READ_COLOR(obj, GradientColor1); 106 | NODE_STYLE_READ_COLOR(obj, GradientColor2); 107 | NODE_STYLE_READ_COLOR(obj, GradientColor3); 108 | NODE_STYLE_READ_COLOR(obj, ShadowColor); 109 | NODE_STYLE_READ_COLOR(obj, FontColor); 110 | NODE_STYLE_READ_COLOR(obj, FontColorFaded); 111 | NODE_STYLE_READ_COLOR(obj, ConnectionPointColor); 112 | NODE_STYLE_READ_COLOR(obj, FilledConnectionPointColor); 113 | NODE_STYLE_READ_COLOR(obj, WarningColor); 114 | NODE_STYLE_READ_COLOR(obj, ErrorColor); 115 | 116 | NODE_STYLE_READ_FLOAT(obj, PenWidth); 117 | NODE_STYLE_READ_FLOAT(obj, HoveredPenWidth); 118 | NODE_STYLE_READ_FLOAT(obj, ConnectionPointDiameter); 119 | 120 | NODE_STYLE_READ_FLOAT(obj, Opacity); 121 | } 122 | 123 | 124 | QJsonObject 125 | NodeStyle:: 126 | toJson() const 127 | { 128 | QJsonObject obj; 129 | 130 | NODE_STYLE_WRITE_COLOR(obj, NormalBoundaryColor); 131 | NODE_STYLE_WRITE_COLOR(obj, SelectedBoundaryColor); 132 | NODE_STYLE_WRITE_COLOR(obj, GradientColor0); 133 | NODE_STYLE_WRITE_COLOR(obj, GradientColor1); 134 | NODE_STYLE_WRITE_COLOR(obj, GradientColor2); 135 | NODE_STYLE_WRITE_COLOR(obj, GradientColor3); 136 | NODE_STYLE_WRITE_COLOR(obj, ShadowColor); 137 | NODE_STYLE_WRITE_COLOR(obj, FontColor); 138 | NODE_STYLE_WRITE_COLOR(obj, FontColorFaded); 139 | NODE_STYLE_WRITE_COLOR(obj, ConnectionPointColor); 140 | NODE_STYLE_WRITE_COLOR(obj, FilledConnectionPointColor); 141 | NODE_STYLE_WRITE_COLOR(obj, WarningColor); 142 | NODE_STYLE_WRITE_COLOR(obj, ErrorColor); 143 | 144 | NODE_STYLE_WRITE_FLOAT(obj, PenWidth); 145 | NODE_STYLE_WRITE_FLOAT(obj, HoveredPenWidth); 146 | NODE_STYLE_WRITE_FLOAT(obj, ConnectionPointDiameter); 147 | 148 | NODE_STYLE_WRITE_FLOAT(obj, Opacity); 149 | 150 | QJsonObject root; 151 | root["NodeStyle"] = obj; 152 | 153 | return root; 154 | } 155 | 156 | -------------------------------------------------------------------------------- /src/StyleCollection.cpp: -------------------------------------------------------------------------------- 1 | #include "StyleCollection.hpp" 2 | 3 | using QtNodes::StyleCollection; 4 | using QtNodes::NodeStyle; 5 | using QtNodes::ConnectionStyle; 6 | using QtNodes::GraphicsViewStyle; 7 | 8 | NodeStyle const & 9 | StyleCollection:: 10 | nodeStyle() 11 | { 12 | return instance()._nodeStyle; 13 | } 14 | 15 | 16 | ConnectionStyle const & 17 | StyleCollection:: 18 | connectionStyle() 19 | { 20 | return instance()._connectionStyle; 21 | } 22 | 23 | 24 | GraphicsViewStyle const & 25 | StyleCollection:: 26 | flowViewStyle() 27 | { 28 | return instance()._flowViewStyle; 29 | } 30 | 31 | 32 | void 33 | StyleCollection:: 34 | setNodeStyle(NodeStyle nodeStyle) 35 | { 36 | instance()._nodeStyle = nodeStyle; 37 | } 38 | 39 | 40 | void 41 | StyleCollection:: 42 | setConnectionStyle(ConnectionStyle connectionStyle) 43 | { 44 | instance()._connectionStyle = connectionStyle; 45 | } 46 | 47 | 48 | void 49 | StyleCollection:: 50 | setGraphicsViewStyle(GraphicsViewStyle flowViewStyle) 51 | { 52 | instance()._flowViewStyle = flowViewStyle; 53 | } 54 | 55 | 56 | StyleCollection & 57 | StyleCollection:: 58 | instance() 59 | { 60 | static StyleCollection collection; 61 | 62 | return collection; 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/UndoCommands.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Definitions.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace QtNodes 10 | { 11 | 12 | class BasicGraphicsScene; 13 | 14 | 15 | /** 16 | * Selected scene objects are serialized and then removed from the scene. 17 | * The deleted elements could be restored in `undo`. 18 | */ 19 | class DeleteCommand : public QUndoCommand 20 | { 21 | public: 22 | DeleteCommand(BasicGraphicsScene* scene); 23 | 24 | void undo() override; 25 | void redo() override; 26 | private: 27 | BasicGraphicsScene* _scene; 28 | QJsonObject _sceneJson; 29 | }; 30 | 31 | 32 | /** 33 | * A command used in `GraphicsView` when user duplicates the selected objects by 34 | * using Ctrl+D key combination. 35 | */ 36 | class DuplicateCommand : public QUndoCommand 37 | { 38 | public: 39 | DuplicateCommand(BasicGraphicsScene* scene, 40 | QPointF const & mouseScenePos); 41 | 42 | /** 43 | * Uses the stored `_newSceneJson` variable with the serialized duplicates to 44 | * delet nodes and connections. 45 | */ 46 | void undo() override; 47 | 48 | /** 49 | * Inserting duplicates is done via serializing the current scene selection and 50 | * then de-serirializing with on-the-fly substitution of newly generated 51 | * NodeIds for the inserted objects. 52 | * 53 | * A the same time all the new objects are stored in `_newSceneJson` in order 54 | * to be able to undo the duplication. 55 | */ 56 | void redo() override; 57 | private: 58 | BasicGraphicsScene* _scene; 59 | QPointF const & _mouseScenePos; 60 | QJsonObject _sceneJson; 61 | QJsonObject _newSceneJson; 62 | }; 63 | 64 | 65 | class DisconnectCommand : public QUndoCommand 66 | { 67 | public: 68 | DisconnectCommand(BasicGraphicsScene* scene, 69 | ConnectionId const); 70 | 71 | void undo() override; 72 | void redo() override; 73 | 74 | private: 75 | BasicGraphicsScene* _scene; 76 | 77 | ConnectionId _connId; 78 | }; 79 | 80 | 81 | class ConnectCommand : public QUndoCommand 82 | { 83 | public: 84 | ConnectCommand(BasicGraphicsScene* scene, 85 | ConnectionId const); 86 | 87 | void undo() override; 88 | void redo() override; 89 | 90 | private: 91 | BasicGraphicsScene* _scene; 92 | 93 | ConnectionId _connId; 94 | }; 95 | 96 | 97 | class MoveNodeCommand : public QUndoCommand 98 | { 99 | public: 100 | MoveNodeCommand(BasicGraphicsScene* scene, 101 | NodeId const nodeId, 102 | QPointF const &diff); 103 | 104 | void undo() override; 105 | void redo() override; 106 | 107 | /** 108 | * A command ID is used in command compression. It must be an integer unique to 109 | * this command's class, or -1 if the command doesn't support compression. 110 | */ 111 | int id() const override; 112 | 113 | /** 114 | * Several sequential movements could be merged into one command. 115 | */ 116 | bool mergeWith(QUndoCommand const *c) override; 117 | 118 | private: 119 | BasicGraphicsScene* _scene; 120 | NodeId _nodeId; 121 | QPointF _diff; 122 | }; 123 | 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/locateNode.cpp: -------------------------------------------------------------------------------- 1 | #include "locateNode.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "NodeGraphicsObject.hpp" 9 | 10 | 11 | namespace QtNodes 12 | { 13 | 14 | NodeGraphicsObject* 15 | locateNodeAt(QPointF scenePoint, 16 | QGraphicsScene &scene, 17 | QTransform const & viewTransform) 18 | { 19 | // items under cursor 20 | QList items = 21 | scene.items(scenePoint, 22 | Qt::IntersectsItemShape, 23 | Qt::DescendingOrder, 24 | viewTransform); 25 | 26 | // items convertable to NodeGraphicsObject 27 | std::vector filteredItems; 28 | 29 | std::copy_if(items.begin(), 30 | items.end(), 31 | std::back_inserter(filteredItems), 32 | [](QGraphicsItem * item) 33 | { 34 | return (qgraphicsitem_cast(item) != nullptr); 35 | }); 36 | 37 | NodeGraphicsObject * node = nullptr; 38 | 39 | if (!filteredItems.empty()) 40 | { 41 | QGraphicsItem* graphicsItem = filteredItems.front(); 42 | node = dynamic_cast(graphicsItem); 43 | } 44 | 45 | return node; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (Qt6_FOUND) 2 | find_package(Qt6 COMPONENTS Test) 3 | set(Qt Qt) 4 | else() 5 | find_package(Qt5 COMPONENTS Test) 6 | set(Qt Qt5) 7 | endif() 8 | 9 | add_executable(test_nodes 10 | test_main.cpp 11 | src/TestDragging.cpp 12 | src/TestDataModelRegistry.cpp 13 | src/TestFlowScene.cpp 14 | src/TestNodeGraphicsObject.cpp 15 | include/ApplicationSetup.hpp 16 | include/Stringify.hpp 17 | include/StubNodeDataModel.hpp 18 | ) 19 | 20 | target_include_directories(test_nodes 21 | PRIVATE 22 | ../src 23 | ../include/internal 24 | include 25 | ) 26 | 27 | target_link_libraries(test_nodes 28 | PRIVATE 29 | QtNodes::QtNodes 30 | Catch2::Catch2 31 | ${Qt}::Test 32 | ) 33 | 34 | add_test( 35 | NAME test_nodes 36 | COMMAND 37 | $ 38 | $<$:--use-colour=yes> 39 | ) 40 | -------------------------------------------------------------------------------- /test/include/ApplicationSetup.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | 8 | inline std::unique_ptr 9 | applicationSetup() 10 | { 11 | static int Argc = 0; 12 | static char ArgvVal = '\0'; 13 | static char* ArgvValPtr = &ArgvVal; 14 | static char** Argv = &ArgvValPtr; 15 | 16 | auto app = std::make_unique(Argc, Argv); 17 | app->setAttribute(Qt::AA_Use96Dpi, true); 18 | 19 | return app; 20 | } 21 | -------------------------------------------------------------------------------- /test/include/Stringify.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace Catch 11 | { 12 | template <> 13 | struct StringMaker 14 | { 15 | static std::string 16 | convert(QPointF const& p) 17 | { 18 | return std::string(QTest::toString(p)); 19 | } 20 | }; 21 | 22 | template <> 23 | struct StringMaker 24 | { 25 | static std::string 26 | convert(QPoint const& p) 27 | { 28 | return std::string(QTest::toString(p)); 29 | } 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /test/include/StubNodeDataModel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class StubNodeDataModel : public QtNodes::NodeDataModel 8 | { 9 | public: 10 | QString 11 | name() const override 12 | { 13 | return _name; 14 | } 15 | 16 | QString 17 | caption() const override 18 | { 19 | return _caption; 20 | } 21 | 22 | unsigned int nPorts(QtNodes::PortType) const override { return 0; } 23 | 24 | QWidget* 25 | embeddedWidget() override 26 | { 27 | return nullptr; 28 | } 29 | 30 | QtNodes::NodeDataType dataType(QtNodes::PortType, QtNodes::PortIndex) const override 31 | { 32 | return QtNodes::NodeDataType(); 33 | } 34 | 35 | std::shared_ptr outData(QtNodes::PortIndex) override 36 | { 37 | return nullptr; 38 | } 39 | 40 | void setInData(std::shared_ptr, QtNodes::PortIndex) override 41 | { 42 | } 43 | 44 | void 45 | name(QString name) 46 | { 47 | _name = std::move(name); 48 | } 49 | 50 | void 51 | caption(QString caption) 52 | { 53 | _caption = std::move(caption); 54 | } 55 | 56 | private: 57 | QString _name = "name"; 58 | QString _caption = "caption"; 59 | }; 60 | -------------------------------------------------------------------------------- /test/src/TestDataModelRegistry.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "StubNodeDataModel.hpp" 6 | 7 | using QtNodes::DataModelRegistry; 8 | using QtNodes::NodeData; 9 | using QtNodes::NodeDataModel; 10 | using QtNodes::NodeDataType; 11 | using QtNodes::PortIndex; 12 | using QtNodes::PortType; 13 | 14 | namespace 15 | { 16 | class StubModelStaticName : public StubNodeDataModel 17 | { 18 | public: 19 | static QString 20 | Name() 21 | { 22 | return "Name"; 23 | } 24 | }; 25 | } 26 | 27 | TEST_CASE("DataModelRegistry::registerModel", "[interface]") 28 | { 29 | DataModelRegistry registry; 30 | 31 | SECTION("stub model") 32 | { 33 | registry.registerModel(); 34 | auto model = registry.create("name"); 35 | 36 | CHECK(model->name() == "name"); 37 | } 38 | SECTION("stub model with static name") 39 | { 40 | registry.registerModel(); 41 | auto model = registry.create("Name"); 42 | 43 | CHECK(model->name() == "name"); 44 | } 45 | SECTION("From model creator function") 46 | { 47 | SECTION("non-static name()") 48 | { 49 | registry.registerModel([] { 50 | return std::make_unique(); 51 | }); 52 | 53 | auto model = registry.create("name"); 54 | 55 | REQUIRE(model != nullptr); 56 | CHECK(model->name() == "name"); 57 | CHECK(dynamic_cast(model.get())); 58 | } 59 | SECTION("static Name()") 60 | { 61 | registry.registerModel([] { 62 | return std::make_unique(); 63 | }); 64 | 65 | auto model = registry.create("Name"); 66 | 67 | REQUIRE(model != nullptr); 68 | CHECK(model->name() == "name"); 69 | CHECK(dynamic_cast(model.get())); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/src/TestDragging.cpp: -------------------------------------------------------------------------------- 1 | #include "ApplicationSetup.hpp" 2 | #include "Stringify.hpp" 3 | #include "StubNodeDataModel.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | using QtNodes::Connection; 16 | using QtNodes::DataModelRegistry; 17 | using QtNodes::FlowScene; 18 | using QtNodes::FlowView; 19 | using QtNodes::Node; 20 | using QtNodes::NodeData; 21 | using QtNodes::NodeDataModel; 22 | using QtNodes::NodeDataType; 23 | using QtNodes::PortIndex; 24 | using QtNodes::PortType; 25 | 26 | TEST_CASE("Dragging node changes position", "[gui]") 27 | { 28 | auto app = applicationSetup(); 29 | 30 | FlowScene scene; 31 | FlowView view(&scene); 32 | 33 | view.show(); 34 | REQUIRE(QTest::qWaitForWindowExposed(&view)); 35 | 36 | SECTION("just one node") 37 | { 38 | auto& node = scene.createNode(std::make_unique()); 39 | 40 | auto& ngo = node.nodeGraphicsObject(); 41 | 42 | QPointF scPosBefore = ngo.pos(); 43 | 44 | QPointF scClickPos = ngo.boundingRect().center(); 45 | scClickPos = QPointF(ngo.sceneTransform().map(scClickPos).toPoint()); 46 | 47 | QPoint vwClickPos = view.mapFromScene(scClickPos); 48 | QPoint vwDestPos = vwClickPos + QPoint(10, 20); 49 | 50 | QPointF scExpectedDelta = view.mapToScene(vwDestPos) - scClickPos; 51 | 52 | CAPTURE(scClickPos); 53 | CAPTURE(vwClickPos); 54 | CAPTURE(vwDestPos); 55 | CAPTURE(scExpectedDelta); 56 | 57 | QTest::mouseMove(view.windowHandle(), vwClickPos); 58 | QTest::mousePress(view.windowHandle(), Qt::LeftButton, Qt::NoModifier, vwClickPos); 59 | QTest::mouseMove(view.windowHandle(), vwDestPos); 60 | QTest::mouseRelease(view.windowHandle(), Qt::LeftButton, Qt::NoModifier, vwDestPos); 61 | 62 | QPointF scDelta = ngo.pos() - scPosBefore; 63 | QPoint roundDelta = scDelta.toPoint(); 64 | QPoint roundExpectedDelta = scExpectedDelta.toPoint(); 65 | 66 | CHECK(roundDelta == roundExpectedDelta); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/src/TestNodeGraphicsObject.cpp: -------------------------------------------------------------------------------- 1 | #include "ApplicationSetup.hpp" 2 | #include "StubNodeDataModel.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | 13 | 14 | using QtNodes::FlowScene; 15 | using QtNodes::FlowView; 16 | using QtNodes::Node; 17 | using QtNodes::NodeDataModel; 18 | using QtNodes::NodeGraphicsObject; 19 | using QtNodes::PortType; 20 | 21 | TEST_CASE("NodeDataModel::portOutConnectionPolicy(...) isn't called for input " 22 | "connections (issue #127)", 23 | "[gui]") 24 | { 25 | class MockModel : public StubNodeDataModel 26 | { 27 | public: 28 | unsigned int nPorts(PortType) const override { return 1; } 29 | 30 | NodeDataModel::ConnectionPolicy 31 | portOutConnectionPolicy(int index) const override 32 | { 33 | portOutConnectionPolicyCalledCount++; 34 | return NodeDataModel::ConnectionPolicy::One; 35 | } 36 | 37 | mutable int portOutConnectionPolicyCalledCount = 0; 38 | }; 39 | 40 | auto setup = applicationSetup(); 41 | 42 | FlowScene scene; 43 | FlowView view(&scene); 44 | 45 | // Ensure we have enough size to contain the node 46 | view.resize(640, 480); 47 | 48 | view.show(); 49 | REQUIRE(QTest::qWaitForWindowExposed(&view)); 50 | 51 | auto& node = scene.createNode(std::make_unique()); 52 | auto& model = dynamic_cast(*node.nodeDataModel()); 53 | auto& ngo = node.nodeGraphicsObject(); 54 | auto& ngeom = node.nodeGeometry(); 55 | 56 | // Move the node to somewhere in the middle of the screen 57 | ngo.setPos(QPointF(50, 50)); 58 | 59 | // Compute the on-screen position of the input port 60 | QPointF scInPortPos = ngeom.portScenePosition(0, PortType::In, ngo.sceneTransform()); 61 | QPoint vwInPortPos = view.mapFromScene(scInPortPos); 62 | 63 | // Create a partial connection by clicking on the input port of the node 64 | QTest::mousePress(view.windowHandle(), Qt::LeftButton, Qt::NoModifier, vwInPortPos); 65 | 66 | CHECK(model.portOutConnectionPolicyCalledCount == 0); 67 | } 68 | -------------------------------------------------------------------------------- /test/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | --------------------------------------------------------------------------------