From f45d3ebcd59aba2b6095be804167b48fbc76013f Mon Sep 17 00:00:00 2001 From: baiYue Date: Tue, 3 Dec 2024 20:07:25 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=AC=A1=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 105 ++++ CMakeLists.txt | 207 +++++++ README.md | 4 + include/abstractGraphModel.h | 245 ++++++++ include/abstractNodeGeometry.h | 76 +++ include/abstractNodePainter.h | 27 + include/basicGraphicsScene.h | 168 ++++++ include/connectionGraphicsObject.h | 93 +++ include/connectionIdUtils.h | 149 +++++ include/connectionPainter.h | 17 + include/connectionState.h | 55 ++ include/connectionStyle.h | 51 ++ include/dataFlowGraphModel.h | 131 +++++ include/defaultHorizontalNodeGeometry.h | 54 ++ include/defaultNodePainter.h | 32 ++ include/defaultVerticalNodeGeometry.h | 57 ++ include/designerScene.h | 63 +++ include/designerView.h | 40 ++ include/diagramCavas.h | 33 ++ include/drawingPanel.h | 51 ++ include/electricElementsBox.h | 31 + include/electricElementsListwidget.h | 18 + include/electricElementsPanel.h | 31 + include/global.h | 142 +++++ include/graphicElementsPanel.h | 30 + include/graphicsItem/electricSvgItem.h | 28 + include/graphicsItem/electricSvgItemBus.h | 18 + include/graphicsItem/electricSvgItemRect.h | 20 + .../graphicsItem/electricSvgItemTriangle.h | 20 + include/graphicsItem/graphicsBaseItem.h | 471 ++++++++++++++++ include/graphicsItem/graphicsItemGroup.h | 38 ++ include/graphicsItem/graphicsPolygonItem.h | 33 ++ include/graphicsItem/graphicsRectItem.h | 30 + include/graphicsItem/itemControlHandle.h | 64 +++ include/graphicsViewStyle.h | 29 + include/locateNode.h | 12 + include/mainwindow.h | 70 +++ include/nodeConnectionInteraction.h | 63 +++ include/nodeData.h | 35 ++ include/nodeDelegateModel.h | 128 +++++ include/nodeDelegateModelRegistry.h | 158 ++++++ include/nodeGraphicsObject.h | 88 +++ include/nodeState.h | 45 ++ include/nodeStyle.h | 50 ++ include/operationCommand.h | 81 +++ include/serializable.h | 13 + include/style.h | 45 ++ include/styleCollection.h | 38 ++ include/toolBox.h | 20 + include/toolPage.h | 30 + include/undoCommands.h | 118 ++++ include/util/baseSelector.h | 74 +++ include/util/connectingSelector.h | 33 ++ include/util/creatingSelector.h | 47 ++ include/util/editingSelector.h | 34 ++ include/util/movingSelector.h | 29 + include/util/rotationSelector.h | 28 + include/util/scalingSelector.h | 30 + include/util/selectorManager.h | 39 ++ resource/DiagramDesigner.qrc | 17 + resource/images/checkerboard.png | Bin 0 -> 137 bytes resource/images/element/icons_triangle.png | Bin 0 -> 657 bytes resource/images/element/svg_bus.svg | 7 + resource/images/element/svg_rect.svg | 9 + resource/images/element/svg_triangle.svg | 9 + resource/images/icon_down_arrow.png | Bin 0 -> 272 bytes resource/images/icon_left_arrow.png | Bin 0 -> 248 bytes resource/images/icon_rotate.png | Bin 0 -> 373 bytes resource/images/icon_rotate_lb.png | Bin 0 -> 865 bytes resource/images/icon_rotate_lt.png | Bin 0 -> 864 bytes resource/images/icon_rotate_rb.png | Bin 0 -> 872 bytes resource/images/icon_rotate_rt.png | Bin 0 -> 280 bytes resource/images/icon_up_arrow.png | Bin 0 -> 268 bytes source/abstractGraphModel.cpp | 102 ++++ source/abstractNodeGeometry.cpp | 75 +++ source/basicGraphicsScene.cpp | 298 ++++++++++ source/connectionGraphicsObject.cpp | 378 +++++++++++++ source/connectionPainter.cpp | 251 +++++++++ source/connectionState.cpp | 63 +++ source/connectionStyle.cpp | 209 +++++++ source/dataFlowGraphModel.cpp | 532 ++++++++++++++++++ source/defaultHorizontalNodeGeometry.cpp | 236 ++++++++ source/defaultVerticalNodeGeometry.cpp | 296 ++++++++++ source/designerScene.cpp | 275 +++++++++ source/designerView.cpp | 180 ++++++ source/diagramCavas.cpp | 41 ++ source/drawingPanel.cpp | 94 ++++ source/electricElementsBox.cpp | 53 ++ source/electricElementsListwidget.cpp | 23 + source/electricElementsPanel.cpp | 53 ++ source/graphicElementsPanel.cpp | 28 + source/graphicsItem/electricSvgItem.cpp | 151 +++++ source/graphicsItem/electricSvgItemBus.cpp | 28 + source/graphicsItem/electricSvgItemRect.cpp | 55 ++ .../graphicsItem/electricSvgItemTriangle.cpp | 64 +++ source/graphicsItem/graphicsBaseItem.cpp | 116 ++++ source/graphicsItem/graphicsItemGroup.cpp | 286 ++++++++++ source/graphicsItem/graphicsPolygonItem.cpp | 155 +++++ source/graphicsItem/graphicsRectItem.cpp | 230 ++++++++ source/graphicsItem/itemControlHandle.cpp | 77 +++ source/graphicsViewStyle.cpp | 93 +++ source/locateNode.cpp | 36 ++ source/main.cpp | 10 + source/mainwindow.cpp | 194 +++++++ source/nodeConnectionInteraction.cpp | 149 +++++ source/nodeDelegateModel.cpp | 52 ++ source/nodeDelegateModelRegistry.cpp | 35 ++ source/nodeGraphicsObject.cpp | 360 ++++++++++++ source/nodeState.cpp | 38 ++ source/nodeStyle.cpp | 145 +++++ source/operationCommand.cpp | 141 +++++ source/styleCollection.cpp | 39 ++ source/toolBox.cpp | 58 ++ source/toolPage.cpp | 77 +++ source/undoCommands.cpp | 458 +++++++++++++++ source/util/baseSelector.cpp | 313 +++++++++++ source/util/connectingSelector.cpp | 66 +++ source/util/creatingSelector.cpp | 188 +++++++ source/util/editingSelector.cpp | 54 ++ source/util/movingSelector.cpp | 48 ++ source/util/rotationSelector.cpp | 69 +++ source/util/scalingSelector.cpp | 70 +++ source/util/selectorManager.cpp | 76 +++ ui/drawingPanel.ui | 41 ++ ui/graphicElementsPanel.ui | 80 +++ ui/mainwindow.ui | 267 +++++++++ 126 files changed, 11517 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 include/abstractGraphModel.h create mode 100644 include/abstractNodeGeometry.h create mode 100644 include/abstractNodePainter.h create mode 100644 include/basicGraphicsScene.h create mode 100644 include/connectionGraphicsObject.h create mode 100644 include/connectionIdUtils.h create mode 100644 include/connectionPainter.h create mode 100644 include/connectionState.h create mode 100644 include/connectionStyle.h create mode 100644 include/dataFlowGraphModel.h create mode 100644 include/defaultHorizontalNodeGeometry.h create mode 100644 include/defaultNodePainter.h create mode 100644 include/defaultVerticalNodeGeometry.h create mode 100644 include/designerScene.h create mode 100644 include/designerView.h create mode 100644 include/diagramCavas.h create mode 100644 include/drawingPanel.h create mode 100644 include/electricElementsBox.h create mode 100644 include/electricElementsListwidget.h create mode 100644 include/electricElementsPanel.h create mode 100644 include/global.h create mode 100644 include/graphicElementsPanel.h create mode 100644 include/graphicsItem/electricSvgItem.h create mode 100644 include/graphicsItem/electricSvgItemBus.h create mode 100644 include/graphicsItem/electricSvgItemRect.h create mode 100644 include/graphicsItem/electricSvgItemTriangle.h create mode 100644 include/graphicsItem/graphicsBaseItem.h create mode 100644 include/graphicsItem/graphicsItemGroup.h create mode 100644 include/graphicsItem/graphicsPolygonItem.h create mode 100644 include/graphicsItem/graphicsRectItem.h create mode 100644 include/graphicsItem/itemControlHandle.h create mode 100644 include/graphicsViewStyle.h create mode 100644 include/locateNode.h create mode 100644 include/mainwindow.h create mode 100644 include/nodeConnectionInteraction.h create mode 100644 include/nodeData.h create mode 100644 include/nodeDelegateModel.h create mode 100644 include/nodeDelegateModelRegistry.h create mode 100644 include/nodeGraphicsObject.h create mode 100644 include/nodeState.h create mode 100644 include/nodeStyle.h create mode 100644 include/operationCommand.h create mode 100644 include/serializable.h create mode 100644 include/style.h create mode 100644 include/styleCollection.h create mode 100644 include/toolBox.h create mode 100644 include/toolPage.h create mode 100644 include/undoCommands.h create mode 100644 include/util/baseSelector.h create mode 100644 include/util/connectingSelector.h create mode 100644 include/util/creatingSelector.h create mode 100644 include/util/editingSelector.h create mode 100644 include/util/movingSelector.h create mode 100644 include/util/rotationSelector.h create mode 100644 include/util/scalingSelector.h create mode 100644 include/util/selectorManager.h create mode 100644 resource/DiagramDesigner.qrc create mode 100644 resource/images/checkerboard.png create mode 100644 resource/images/element/icons_triangle.png create mode 100644 resource/images/element/svg_bus.svg create mode 100644 resource/images/element/svg_rect.svg create mode 100644 resource/images/element/svg_triangle.svg create mode 100644 resource/images/icon_down_arrow.png create mode 100644 resource/images/icon_left_arrow.png create mode 100644 resource/images/icon_rotate.png create mode 100644 resource/images/icon_rotate_lb.png create mode 100644 resource/images/icon_rotate_lt.png create mode 100644 resource/images/icon_rotate_rb.png create mode 100644 resource/images/icon_rotate_rt.png create mode 100644 resource/images/icon_up_arrow.png create mode 100644 source/abstractGraphModel.cpp create mode 100644 source/abstractNodeGeometry.cpp create mode 100644 source/basicGraphicsScene.cpp create mode 100644 source/connectionGraphicsObject.cpp create mode 100644 source/connectionPainter.cpp create mode 100644 source/connectionState.cpp create mode 100644 source/connectionStyle.cpp create mode 100644 source/dataFlowGraphModel.cpp create mode 100644 source/defaultHorizontalNodeGeometry.cpp create mode 100644 source/defaultVerticalNodeGeometry.cpp create mode 100644 source/designerScene.cpp create mode 100644 source/designerView.cpp create mode 100644 source/diagramCavas.cpp create mode 100644 source/drawingPanel.cpp create mode 100644 source/electricElementsBox.cpp create mode 100644 source/electricElementsListwidget.cpp create mode 100644 source/electricElementsPanel.cpp create mode 100644 source/graphicElementsPanel.cpp create mode 100644 source/graphicsItem/electricSvgItem.cpp create mode 100644 source/graphicsItem/electricSvgItemBus.cpp create mode 100644 source/graphicsItem/electricSvgItemRect.cpp create mode 100644 source/graphicsItem/electricSvgItemTriangle.cpp create mode 100644 source/graphicsItem/graphicsBaseItem.cpp create mode 100644 source/graphicsItem/graphicsItemGroup.cpp create mode 100644 source/graphicsItem/graphicsPolygonItem.cpp create mode 100644 source/graphicsItem/graphicsRectItem.cpp create mode 100644 source/graphicsItem/itemControlHandle.cpp create mode 100644 source/graphicsViewStyle.cpp create mode 100644 source/locateNode.cpp create mode 100644 source/main.cpp create mode 100644 source/mainwindow.cpp create mode 100644 source/nodeConnectionInteraction.cpp create mode 100644 source/nodeDelegateModel.cpp create mode 100644 source/nodeDelegateModelRegistry.cpp create mode 100644 source/nodeGraphicsObject.cpp create mode 100644 source/nodeState.cpp create mode 100644 source/nodeStyle.cpp create mode 100644 source/operationCommand.cpp create mode 100644 source/styleCollection.cpp create mode 100644 source/toolBox.cpp create mode 100644 source/toolPage.cpp create mode 100644 source/undoCommands.cpp create mode 100644 source/util/baseSelector.cpp create mode 100644 source/util/connectingSelector.cpp create mode 100644 source/util/creatingSelector.cpp create mode 100644 source/util/editingSelector.cpp create mode 100644 source/util/movingSelector.cpp create mode 100644 source/util/rotationSelector.cpp create mode 100644 source/util/scalingSelector.cpp create mode 100644 source/util/selectorManager.cpp create mode 100644 ui/drawingPanel.ui create mode 100644 ui/graphicElementsPanel.ui create mode 100644 ui/mainwindow.ui diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8b4fd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,105 @@ +build/ +.vscode/ + +# ---> CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +# ---> C++ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# ---> C +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4eb6fe7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,207 @@ +cmake_minimum_required(VERSION 3.5) + +project(DiagramDesigner LANGUAGES CXX VERSION 1.0) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +#set(ADS_VERSION 4.3.1) +#add_subdirectory(QtADS) + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widgets REQUIRED) +find_package(Qt6 REQUIRED COMPONENTS SvgWidgets) + + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +# 默认 ui 文件要和 .h 头文件在一个目录,若不在一个目录,需要指定其所在目录 +set(CMAKE_AUTOUIC_SEARCH_PATHS "ui") + +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") + set(dd_PlatformDir "x86") +else() + if(DEFINED CMAKE_SYSTEM_PROCESSOR) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") + set(dd_PlatformDir "x64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64") + set(dd_PlatformDir "aarch64") + else() + set(dd_PlatformDir "x64") + endif() + else() + set(dd_PlatformDir "x64") + endif() +endif() + +set(H_HEADER_FILES + include/global.h + include/mainwindow.h + include/graphicElementsPanel.h + include/electricElementsPanel.h + include/electricElementsBox.h + include/electricElementsListwidget.h + include/drawingPanel.h + include/diagramCavas.h + include/designerScene.h + include/designerView.h + include/operationCommand.h + include/toolPage.h + include/toolBox.h + + include/util/baseSelector.h + include/util/creatingSelector.h + include/util/movingSelector.h + include/util/rotationSelector.h + include/util/scalingSelector.h + include/util/editingSelector.h + include/util/connectingSelector.h + include/util/selectorManager.h + + include/graphicsItem/itemControlHandle.h + include/graphicsItem/graphicsBaseItem.h + include/graphicsItem/graphicsRectItem.h + include/graphicsItem/graphicsPolygonItem.h + include/graphicsItem/graphicsItemGroup.h + include/graphicsItem/electricSvgItem.h + include/graphicsItem/electricSvgItemBus.h + include/graphicsItem/electricSvgItemRect.h + include/graphicsItem/electricSvgItemTriangle.h + include/abstractGraphModel.h + include/connectionIdUtils.h + include/serializable.h + include/dataFlowGraphModel.h + include/nodeData.h + include/nodeDelegateModel.h + include/nodeDelegateModelRegistry.h + include/style.h + include/nodeStyle.h + include/connectionStyle.h + include/graphicsViewStyle.h + include/styleCollection.h + include/abstractNodeGeometry.h + include/basicGraphicsScene.h + include/connectionState.h + include/connectionGraphicsObject.h + include/nodeGraphicsObject.h + include/nodeState.h + include/connectionPainter.h + include/nodeConnectionInteraction.h + include/undoCommands.h + include/locateNode.h + include/defaultNodePainter.h + include/abstractNodePainter.h + include/defaultHorizontalNodeGeometry.h + include/defaultVerticalNodeGeometry.h +) +set(CPP_SOURCE_FILES + source/main.cpp + source/mainwindow.cpp + source/graphicElementsPanel.cpp + source/electricElementsPanel.cpp + source/electricElementsBox.cpp + source/electricElementsListwidget.cpp + source/drawingPanel.cpp + source/diagramCavas.cpp + source/designerScene.cpp + source/designerView.cpp + source/operationCommand.cpp + source/toolPage.cpp + source/toolBox.cpp + + source/util/baseSelector.cpp + source/util/creatingSelector.cpp + source/util/movingSelector.cpp + source/util/rotationSelector.cpp + source/util/scalingSelector.cpp + source/util/editingSelector.cpp + source/util/connectingSelector.cpp + source/util/selectorManager.cpp + + source/graphicsItem/itemControlHandle.cpp + source/graphicsItem/graphicsBaseItem.cpp + source/graphicsItem/graphicsRectItem.cpp + source/graphicsItem/graphicsPolygonItem.cpp + source/graphicsItem/graphicsItemGroup.cpp + source/graphicsItem/electricSvgItem.cpp + source/graphicsItem/electricSvgItemBus.cpp + source/graphicsItem/electricSvgItemRect.cpp + source/graphicsItem/electricSvgItemTriangle.cpp + source/abstractGraphModel.cpp + source/dataFlowGraphModel.cpp + source/nodeDelegateModel.cpp + source/nodeDelegateModelRegistry.cpp + source/nodeStyle.cpp + source/styleCollection.cpp + source/connectionStyle.cpp + source/graphicsViewStyle.cpp + source/abstractNodeGeometry.cpp + source/basicGraphicsScene.cpp + source/connectionState.cpp + source/nodeState.cpp + source/connectionPainter.cpp + source/nodeConnectionInteraction.cpp + source/undoCommands.cpp + source/locateNode.cpp + source/connectionGraphicsObject.cpp + source/defaultVerticalNodeGeometry.cpp + source/defaultHorizontalNodeGeometry.cpp + source/nodeGraphicsObject.cpp +) +set(UI_FILES + ui/mainwindow.ui + ui/graphicElementsPanel.ui + ui/drawingPanel.ui +) + +# 包含源文件目录 +INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(DiagramDesigner + MANUAL_FINALIZATION + ${H_HEADER_FILES} + ${CPP_SOURCE_FILES} + ${UI_FILES} + resource/DiagramDesigner.qrc + ) +else() + if(ANDROID) + add_library(DiagramDesigner SHARED + ${H_HEADER_FILES} + ${CPP_SOURCE_FILES} + ${UI_FILES} + resource/DiagramDesigner.qrc + ) + else() + add_executable(DiagramDesigner WIN32 + ${H_HEADER_FILES} + ${CPP_SOURCE_FILES} + ${UI_FILES} + resource/DiagramDesigner.qrc + ) + endif() +endif() + +include_directories(include) + +target_include_directories(DiagramDesigner PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") +#target_link_libraries(DiagramDesigner PRIVATE qt${QT_VERSION_MAJOR}advanceddocking) +target_link_libraries(DiagramDesigner PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets) +target_link_libraries(DiagramDesigner PRIVATE Qt6::SvgWidgets) +set_target_properties(DiagramDesigner PROPERTIES + AUTOMOC ON + AUTORCC ON + AUTOUIC ON + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + VERSION 1.0 + EXPORT_NAME "DiagramDesigner with Qt Advanced Docking System" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${dd_PlatformDir}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${dd_PlatformDir}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${dd_PlatformDir}/bin" +) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..78ac8ac --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# DiagramDesigner + +桌面端单线图设计时(Desktop one-line diagram DesignTime) + diff --git a/include/abstractGraphModel.h b/include/abstractGraphModel.h new file mode 100644 index 0000000..dd67b29 --- /dev/null +++ b/include/abstractGraphModel.h @@ -0,0 +1,245 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include "global.h" + + +/** + * The central class in the Model-View approach. It delivers all kinds + * of information from the backing user data structures that represent + * the graph. The class allows to modify the graph structure: create + * and remove nodes and connections. + * + * We use two types of the unique ids for graph manipulations: + * - NodeId + * - ConnectionId + */ +class AbstractGraphModel : public QObject +{ + Q_OBJECT +public: + /// Generates a new unique NodeId. + virtual NodeId newNodeId() = 0; + + /// @brief Returns the full set of unique Node Ids. + /** + * Model creator is responsible for generating unique `unsigned int` + * Ids for all the nodes in the graph. From an Id it should be + * possible to trace back to the model's internal representation of + * the node. + */ + virtual QSet allNodeIds() const = 0; + + /** + * A collection of all input and output connections for the given `nodeId`. + */ + virtual QSet allConnectionIds(NodeId const nodeId) const = 0; + + /// @brief Returns all connected Node Ids for given port. + /** + * The returned set of nodes and port indices correspond to the type + * opposite to the given `portType`. + */ + virtual QSet connections(NodeId nodeId, + PortType portType, + PortIndex index) const + = 0; + + /// Checks if two nodes with the given `connectionId` are connected. + virtual bool connectionExists(ConnectionId const connectionId) const = 0; + + /// Creates a new node instance in the derived class. + /** + * The model is responsible for generating a unique `NodeId`. + * @param[in] nodeType is free to be used and interpreted by the + * model on its own, it helps to distinguish between possible node + * types and create a correct instance inside. + */ + virtual NodeId addNode(QString const nodeType = QString()) = 0; + + /// Model decides if a conection with a given connection Id possible. + /** + * The default implementation compares corresponding data types. + * + * It is possible to override the function and connect non-equal + * data types. + */ + virtual bool connectionPossible(ConnectionId const connectionId) const = 0; + + /// Defines if detaching the connection is possible. + virtual bool detachPossible(ConnectionId const) const { return true; } + + /// Creates a new connection between two nodes. + /** + * Default implementation emits signal + * `connectionCreated(connectionId)` + * + * In the derived classes user must emite the signal to notify the + * scene about the changes. + */ + virtual void addConnection(ConnectionId const connectionId) = 0; + + /** + * @returns `true` if there is data in the model associated with the + * given `nodeId`. + */ + virtual bool nodeExists(NodeId const nodeId) const = 0; + + /// @brief Returns node-related data for requested NodeRole. + /** + * @returns Node Caption, Node Caption Visibility, Node Position etc. + */ + virtual QVariant nodeData(NodeId nodeId, NodeRole role) const = 0; + + /** + * A utility function that unwraps the `QVariant` value returned from the + * standard `QVariant AbstractGraphModel::nodeData(NodeId, NodeRole)` function. + */ + template + T nodeData(NodeId nodeId, NodeRole role) const + { + return nodeData(nodeId, role).value(); + } + + virtual NodeFlags nodeFlags(NodeId nodeId) const + { + Q_UNUSED(nodeId); + return NodeFlag::NoFlags; + } + + /// @brief Sets node properties. + /** + * Sets: Node Caption, Node Caption Visibility, + * Shyle, State, Node Position etc. + * @see NodeRole. + */ + virtual bool setNodeData(NodeId nodeId, NodeRole role, QVariant value) = 0; + + /// @brief Returns port-related data for requested NodeRole. + /** + * @returns Port Data Type, Port Data, Connection Policy, Port + * Caption. + */ + virtual QVariant portData(NodeId nodeId, PortType portType, PortIndex index, PortRole role) const + = 0; + + /** + * A utility function that unwraps the `QVariant` value returned from the + * standard `QVariant AbstractGraphModel::portData(...)` function. + */ + template + T portData(NodeId nodeId, PortType portType, PortIndex index, PortRole role) const + { + return portData(nodeId, portType, index, role).value(); + } + + virtual bool setPortData(NodeId nodeId, + PortType portType, + PortIndex index, + QVariant const &value, + PortRole role = PortRole::Data) + = 0; + + virtual bool deleteConnection(ConnectionId const connectionId) = 0; + + virtual bool deleteNode(NodeId const nodeId) = 0; + + /** + * Reimplement the function if you want to store/restore the node's + * inner state during undo/redo node deletion operations. + */ + virtual QJsonObject saveNode(NodeId const) const { return {}; } + + /** + * Reimplement the function if you want to support: + * + * - graph save/restore operations, + * - undo/redo operations after deleting the node. + * + * QJsonObject must contain following fields: + * + * + * ``` + * { + * id : 5, + * position : { x : 100, y : 200 }, + * internal-data { + * "your model specific data here" + * } + * } + * ``` + * + * The function must do almost exacly the same thing as the normal addNode(). + * The main difference is in a model-specific `inner-data` processing. + */ + virtual void loadNode(QJsonObject const &) {} + +public: + /** + * Function clears connections attached to the ports that are scheduled to be + * deleted. It must be called right before the model removes its old port data. + * + * @param nodeId Defines the node to be modified + * @param portType Is either PortType::In or PortType::Out + * @param first Index of the first port to be removed + * @param last Index of the last port to be removed + */ + void portsAboutToBeDeleted(NodeId const nodeId, + PortType const portType, + PortIndex const first, + PortIndex const last); + + /** + * Signal emitted when model no longer has the old data associated with the + * given port indices and when the node must be repainted. + */ + void portsDeleted(); + + /** + * Signal emitted when model is about to create new ports on the given node. + * @param first Is the first index of the new port after insertion. + * @param last Is the last index of the new port after insertion. + * + * Function caches existing connections that are located after the `last` port + * index. For such connections the new "post-insertion" addresses are computed + * and stored until the function AbstractGraphModel::portsInserted is called. + */ + void portsAboutToBeInserted(NodeId const nodeId, + PortType const portType, + PortIndex const first, + PortIndex const last); + + /** + * Function re-creates the connections that were shifted during the port + * insertion. After that the node is updated. + */ + void portsInserted(); + +Q_SIGNALS: + void connectionCreated(ConnectionId const connectionId); + + void connectionDeleted(ConnectionId const connectionId); + + void nodeCreated(NodeId const nodeId); + + void nodeDeleted(NodeId const nodeId); + + void nodeUpdated(NodeId const nodeId); + + void nodeFlagsUpdated(NodeId const nodeId); + + void nodePositionUpdated(NodeId const nodeId); + + void modelReset(); + +private: + QVector _shiftedByDynamicPortsConnections; +}; + diff --git a/include/abstractNodeGeometry.h b/include/abstractNodeGeometry.h new file mode 100644 index 0000000..e7ae2c4 --- /dev/null +++ b/include/abstractNodeGeometry.h @@ -0,0 +1,76 @@ +#pragma once + +#include "global.h" + +#include +#include +#include + + +class AbstractGraphModel; + +class AbstractNodeGeometry +{ +public: + AbstractNodeGeometry(AbstractGraphModel &); + virtual ~AbstractNodeGeometry() {} + + /** + * The node's size plus some additional margin around it to account for drawing + * effects (for example shadows) or node's parts outside the size rectangle + * (for example port points). + * + * The default implementation returns QSize + 20 percent of width and heights + * at each side of the rectangle. + */ + virtual QRectF boundingRect(NodeId const nodeId) const; + + /// A direct rectangle defining the borders of the node's rectangle. + virtual QSize size(NodeId const nodeId) const = 0; + + /** + * The function is triggeren when a nuber of ports is changed or when an + * embedded widget needs an update. + */ + virtual void recomputeSize(NodeId const nodeId) const = 0; + + /// Port position in node's coordinate system. + virtual QPointF portPosition(NodeId const nodeId, + PortType const portType, + PortIndex const index) const + = 0; + + /// A convenience function using the `portPosition` and a given transformation. + virtual QPointF portScenePosition(NodeId const nodeId, + PortType const portType, + PortIndex const index, + QTransform const &t) const; + + /// Defines where to draw port label. The point corresponds to a font baseline. + virtual QPointF portTextPosition(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex) const + = 0; + + /** + * Defines where to start drawing the caption. The point corresponds to a font + * baseline. + */ + virtual QPointF captionPosition(NodeId const nodeId) const = 0; + + /// Caption rect is needed for estimating the total node size. + virtual QRectF captionRect(NodeId const nodeId) const = 0; + + /// Position for an embedded widget. Return any value if you don't embed. + virtual QPointF widgetPosition(NodeId const nodeId) const = 0; + + virtual PortIndex checkPortHit(NodeId const nodeId, + PortType const portType, + QPointF const nodePoint) const; + + virtual QRect resizeHandleRect(NodeId const nodeId) const = 0; + +protected: + AbstractGraphModel &_graphModel; +}; + diff --git a/include/abstractNodePainter.h b/include/abstractNodePainter.h new file mode 100644 index 0000000..b745ed6 --- /dev/null +++ b/include/abstractNodePainter.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "global.h" + +class QPainter; + + +class NodeGraphicsObject; +class NodeDataModel; + +/// Class enables custom painting. +class AbstractNodePainter +{ +public: + virtual ~AbstractNodePainter() = default; + + /** + * Reimplement this function in order to have a custom painting. + * + * Useful functions: + * `NodeGraphicsObject::nodeScene()->nodeGeometry()` + * `NodeGraphicsObject::graphModel()` + */ + virtual void paint(QPainter *painter, NodeGraphicsObject &ngo) const = 0; +}; diff --git a/include/basicGraphicsScene.h b/include/basicGraphicsScene.h new file mode 100644 index 0000000..de3998c --- /dev/null +++ b/include/basicGraphicsScene.h @@ -0,0 +1,168 @@ +#pragma once + +#include +#include +#include + +#include "abstractGraphModel.h" +#include "abstractNodeGeometry.h" +#include "global.h" + +class QUndoStack; + +class AbstractGraphModel; +class AbstractNodePainter; +class ConnectionGraphicsObject; +class NodeGraphicsObject; +class NodeStyle; + +/// An instance of QGraphicsScene, holds connections and nodes. +class BasicGraphicsScene : public QGraphicsScene +{ + Q_OBJECT +public: + BasicGraphicsScene(AbstractGraphModel &graphModel, QObject *parent = nullptr); + + // Scenes without models are not supported + BasicGraphicsScene() = delete; + + ~BasicGraphicsScene(); + +public: + /// @returns associated AbstractGraphModel. + AbstractGraphModel const &graphModel() const; + + AbstractGraphModel &graphModel(); + + AbstractNodeGeometry &nodeGeometry(); + + AbstractNodePainter &nodePainter(); + + void setNodePainter(QSharedPointer newPainter); + + QUndoStack &undoStack(); + +public: + /// Creates a "draft" instance of ConnectionGraphicsObject. + /** + * The scene caches a "draft" connection which has one loose end. + * After attachment the "draft" instance is deleted and instead a + * normal "full" connection is created. + * Function @returns the "draft" instance for further geometry + * manipulations. + */ + QSharedPointer const &makeDraftConnection( + ConnectionId const newConnectionId); + + /// Deletes "draft" connection. + /** + * The function is called when user releases the mouse button during + * the construction of the new connection without attaching it to any + * node. + */ + void resetDraftConnection(); + + /// Deletes all the nodes. Connections are removed automatically. + void clearScene(); + +public: + /// @returns NodeGraphicsObject associated with the given nodeId. + /** + * @returns nullptr when the object is not found. + */ + NodeGraphicsObject *nodeGraphicsObject(NodeId nodeId); + + /// @returns ConnectionGraphicsObject corresponding to `connectionId`. + /** + * @returns `nullptr` when the object is not found. + */ + ConnectionGraphicsObject *connectionGraphicsObject(ConnectionId connectionId); + + Qt::Orientation orientation() const { return _orientation; } + + void setOrientation(Qt::Orientation const orientation); + +public: + /// Can @return an instance of the scene context menu in subclass. + /** + * Default implementation returns `nullptr`. + */ + virtual QMenu *createSceneMenu(QPointF const scenePos); + +Q_SIGNALS: + void modified(BasicGraphicsScene *); + + void nodeMoved(NodeId const nodeId, QPointF const &newLocation); + + void nodeClicked(NodeId const nodeId); + + void nodeSelected(NodeId const nodeId); + + void nodeDoubleClicked(NodeId const nodeId); + + void nodeHovered(NodeId const nodeId, QPoint const screenPos); + + void nodeHoverLeft(NodeId const nodeId); + + void connectionHovered(ConnectionId const connectionId, QPoint const screenPos); + + void connectionHoverLeft(ConnectionId const connectionId); + + /// Signal allows showing custom context menu upon clicking a node. + void nodeContextMenu(NodeId const nodeId, QPointF const pos); + +private: + /// @brief Creates Node and Connection graphics objects. + /** + * Function is used to populate an empty scene in the constructor. We + * perform depth-first AbstractGraphModel traversal. The connections are + * created by checking non-empty node `Out` ports. + */ + void traverseGraphAndPopulateGraphicsObjects(); + + /// Redraws adjacent nodes for given `connectionId` + void updateAttachedNodes(ConnectionId const connectionId, PortType const portType); + +public Q_SLOTS: + /// Slot called when the `connectionId` is erased form the AbstractGraphModel. + void onConnectionDeleted(ConnectionId const connectionId); + + /// Slot called when the `connectionId` is created in the AbstractGraphModel. + void onConnectionCreated(ConnectionId const connectionId); + + void onNodeDeleted(NodeId const nodeId); + + void onNodeCreated(NodeId const nodeId); + + void onNodePositionUpdated(NodeId const nodeId); + + void onNodeUpdated(NodeId const nodeId); + + void onNodeClicked(NodeId const nodeId); + + void onModelReset(); + +private: + AbstractGraphModel &_graphModel; + + using UniqueNodeGraphicsObject = QSharedPointer; + + using UniqueConnectionGraphicsObject = QSharedPointer; + + QMap _nodeGraphicsObjects; + + QMap _connectionGraphicsObjects; + + QSharedPointer _draftConnection; + + QSharedPointer _nodeGeometry; + + QSharedPointer _nodePainter; + + bool _nodeDrag; + + QUndoStack *_undoStack; + + Qt::Orientation _orientation; +}; + diff --git a/include/connectionGraphicsObject.h b/include/connectionGraphicsObject.h new file mode 100644 index 0000000..31ee70b --- /dev/null +++ b/include/connectionGraphicsObject.h @@ -0,0 +1,93 @@ +#pragma once + +#include + +#include +#include + +#include "connectionState.h" +#include "global.h" + +class QGraphicsSceneMouseEvent; + + +class AbstractGraphModel; +class BasicGraphicsScene; + +/// Graphic Object for connection. Adds itself to scene +class ConnectionGraphicsObject : public QGraphicsObject +{ + Q_OBJECT +public: + // Needed for qgraphicsitem_cast + enum { Type = UserType + 2 }; + + int type() const override { return Type; } + +public: + ConnectionGraphicsObject(BasicGraphicsScene &scene, ConnectionId const connectionId); + + ~ConnectionGraphicsObject() = default; + +public: + AbstractGraphModel &graphModel() const; + + BasicGraphicsScene *nodeScene() const; + + ConnectionId const &connectionId() const; + + QRectF boundingRect() const override; + + QPainterPath shape() const override; + + QPointF const &endPoint(PortType portType) const; + + QPointF out() const { return _out; } + + QPointF in() const { return _in; } + + std::pair pointsC1C2() const; + + void setEndPoint(PortType portType, QPointF const &point); + + /// Updates the position of both ends + void move(); + + ConnectionState const &connectionState() const; + + ConnectionState &connectionState(); + +protected: + void paint(QPainter *painter, + QStyleOptionGraphicsItem const *option, + QWidget *widget = 0) override; + + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; + + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + +private: + void initializePosition(); + + void addGraphicsEffect(); + + std::pair pointsC1C2Horizontal() const; + + std::pair pointsC1C2Vertical() const; + +private: + ConnectionId _connectionId; + + AbstractGraphModel &_graphModel; + + ConnectionState _connectionState; + + mutable QPointF _out; + mutable QPointF _in; +}; diff --git a/include/connectionIdUtils.h b/include/connectionIdUtils.h new file mode 100644 index 0000000..793f4fe --- /dev/null +++ b/include/connectionIdUtils.h @@ -0,0 +1,149 @@ +#pragma once + +#include "global.h" + +#include + +#include +#include + + +inline PortIndex getNodeId(PortType portType, ConnectionId connectionId) +{ + NodeId id = InvalidNodeId; + + if (portType == PortType::Out) { + id = connectionId.outNodeId; + } else if (portType == PortType::In) { + id = connectionId.inNodeId; + } + + return id; +} + +inline PortIndex getPortIndex(PortType portType, ConnectionId connectionId) +{ + PortIndex index = InvalidPortIndex; + + if (portType == PortType::Out) { + index = connectionId.outPortIndex; + } else if (portType == PortType::In) { + index = connectionId.inPortIndex; + } + + return index; +} + +inline PortType oppositePort(PortType port) +{ + PortType result = PortType::None; + + switch (port) { + case PortType::In: + result = PortType::Out; + break; + + case PortType::Out: + result = PortType::In; + break; + + case PortType::None: + result = PortType::None; + break; + + default: + break; + } + return result; +} + +inline bool isPortIndexValid(PortIndex index) +{ + return index != InvalidPortIndex; +} + +inline bool isPortTypeValid(PortType portType) +{ + return portType != PortType::None; +} + +/** + * Creates a connection Id instance filled just on one side. + */ +inline ConnectionId makeIncompleteConnectionId(NodeId const connectedNodeId, + PortType const connectedPort, + PortIndex const connectedPortIndex) +{ + return (connectedPort == PortType::In) + ? ConnectionId{InvalidNodeId, InvalidPortIndex, connectedNodeId, connectedPortIndex} + : ConnectionId{connectedNodeId, connectedPortIndex, InvalidNodeId, InvalidPortIndex}; +} + +/** + * Turns a full connection Id into an incomplete one by removing the + * data on the given side + */ +inline ConnectionId makeIncompleteConnectionId(ConnectionId connectionId, + PortType const portToDisconnect) +{ + if (portToDisconnect == PortType::Out) { + connectionId.outNodeId = InvalidNodeId; + connectionId.outPortIndex = InvalidPortIndex; + } else { + connectionId.inNodeId = InvalidNodeId; + connectionId.inPortIndex = InvalidPortIndex; + } + + return connectionId; +} + +inline ConnectionId makeCompleteConnectionId(ConnectionId incompleteConnectionId, + NodeId const nodeId, + PortIndex const portIndex) +{ + if (incompleteConnectionId.outNodeId == InvalidNodeId) { + incompleteConnectionId.outNodeId = nodeId; + incompleteConnectionId.outPortIndex = portIndex; + } else { + incompleteConnectionId.inNodeId = nodeId; + incompleteConnectionId.inPortIndex = portIndex; + } + + return incompleteConnectionId; +} + +inline std::ostream &operator<<(std::ostream &ostr, ConnectionId const connectionId) +{ + ostr << "(" << connectionId.outNodeId << ", " + << (isPortIndexValid(connectionId.outPortIndex) ? std::to_string(connectionId.outPortIndex) + : "INVALID") + << ", " << connectionId.inNodeId << ", " + << (isPortIndexValid(connectionId.inPortIndex) ? std::to_string(connectionId.inPortIndex) + : "INVALID") + << ")" << std::endl; + + return ostr; +} + +inline QJsonObject toJson(ConnectionId const &connId) +{ + QJsonObject connJson; + + connJson["outNodeId"] = static_cast(connId.outNodeId); + connJson["outPortIndex"] = static_cast(connId.outPortIndex); + connJson["intNodeId"] = static_cast(connId.inNodeId); + connJson["inPortIndex"] = static_cast(connId.inPortIndex); + + return connJson; +} + +inline ConnectionId fromJson(QJsonObject const &connJson) +{ + ConnectionId connId{static_cast(connJson["outNodeId"].toInt(InvalidNodeId)), + static_cast(connJson["outPortIndex"].toInt(InvalidPortIndex)), + static_cast(connJson["intNodeId"].toInt(InvalidNodeId)), + static_cast(connJson["inPortIndex"].toInt(InvalidPortIndex))}; + + return connId; +} + diff --git a/include/connectionPainter.h b/include/connectionPainter.h new file mode 100644 index 0000000..1e6f46c --- /dev/null +++ b/include/connectionPainter.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +#include "global.h" + +class ConnectionGeometry; +class ConnectionGraphicsObject; + +class ConnectionPainter +{ +public: + static void paint(QPainter *painter, ConnectionGraphicsObject const &cgo); + + static QPainterPath getPainterStroke(ConnectionGraphicsObject const &cgo); +}; diff --git a/include/connectionState.h b/include/connectionState.h new file mode 100644 index 0000000..15ecabe --- /dev/null +++ b/include/connectionState.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include "global.h" + +class QPointF; + +class ConnectionGraphicsObject; + +/// Stores currently draggind end. +/// Remembers last hovered Node. +class ConnectionState +{ +public: + /// Defines whether we construct a new connection + /// or it is already binding two nodes. + enum LooseEnd { Pending = 0, Connected = 1 }; + +public: + ConnectionState(ConnectionGraphicsObject &cgo) + : _cgo(cgo) + , _hovered(false) + {} + + ConnectionState(ConnectionState const &) = delete; + ConnectionState(ConnectionState &&) = delete; + + ConnectionState &operator=(ConnectionState const &) = delete; + ConnectionState &operator=(ConnectionState &&) = delete; + + ~ConnectionState(); + +public: + PortType requiredPort() const; + bool requiresPort() const; + + bool hovered() const; + void setHovered(bool hovered); + +public: + /// Caches NodeId for further interaction. + void setLastHoveredNode(NodeId const nodeId); + + NodeId lastHoveredNode() const; + + void resetLastHoveredNode(); + +private: + ConnectionGraphicsObject &_cgo; + + bool _hovered; + + NodeId _lastHoveredNode{InvalidNodeId}; +}; diff --git a/include/connectionStyle.h b/include/connectionStyle.h new file mode 100644 index 0000000..191a85a --- /dev/null +++ b/include/connectionStyle.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "style.h" + +class ConnectionStyle : public Style +{ +public: + ConnectionStyle(); + + ConnectionStyle(QString jsonText); + + ~ConnectionStyle() = default; + +public: + static void setConnectionStyle(QString jsonText); + +public: + void loadJson(QJsonObject const &json) override; + + QJsonObject toJson() const override; + +public: + QColor constructionColor() const; + QColor normalColor() const; + QColor normalColor(QString typeId) const; + QColor selectedColor() const; + QColor selectedHaloColor() const; + QColor hoveredColor() const; + + float lineWidth() const; + float constructionLineWidth() const; + float pointDiameter() const; + + bool useDataDefinedColors() const; + +private: + QColor ConstructionColor; + QColor NormalColor; + QColor SelectedColor; + QColor SelectedHaloColor; + QColor HoveredColor; + + float LineWidth; + float ConstructionLineWidth; + float PointDiameter; + + bool UseDataDefinedColors; +}; + diff --git a/include/dataFlowGraphModel.h b/include/dataFlowGraphModel.h new file mode 100644 index 0000000..410c7c8 --- /dev/null +++ b/include/dataFlowGraphModel.h @@ -0,0 +1,131 @@ +#pragma once + +#include "abstractGraphModel.h" +#include "connectionIdUtils.h" +#include "nodeDelegateModelRegistry.h" +#include "serializable.h" +#include "styleCollection.h" + +#include +#include + + +class DataFlowGraphModel : public AbstractGraphModel, public Serializable +{ + Q_OBJECT + +public: + struct NodeGeometryData + { + QSize size; + QPointF pos; + }; + +public: + DataFlowGraphModel(QSharedPointer registry); + + QSharedPointer dataModelRegistry() { return _registry; } + +public: + QSet allNodeIds() const override; + + QSet allConnectionIds(NodeId const nodeId) const override; + + QSet connections(NodeId nodeId, + PortType portType, + PortIndex portIndex) const override; + + bool connectionExists(ConnectionId const connectionId) const override; + + NodeId addNode(QString const nodeType) override; + + bool connectionPossible(ConnectionId const connectionId) const override; + + void addConnection(ConnectionId const connectionId) override; + + bool nodeExists(NodeId const nodeId) const override; + + QVariant nodeData(NodeId nodeId, NodeRole role) const override; + + NodeFlags nodeFlags(NodeId nodeId) const override; + + bool setNodeData(NodeId nodeId, NodeRole role, QVariant value) override; + + QVariant portData(NodeId nodeId, + PortType portType, + PortIndex portIndex, + PortRole role) const override; + + bool setPortData(NodeId nodeId, + PortType portType, + PortIndex portIndex, + QVariant const &value, + PortRole role = PortRole::Data) override; + + bool deleteConnection(ConnectionId const connectionId) override; + + bool deleteNode(NodeId const nodeId) override; + + QJsonObject saveNode(NodeId const) const override; + + QJsonObject save() const override; + + void loadNode(QJsonObject const &nodeJson) override; + + void load(QJsonObject const &json) override; + + /** + * Fetches the NodeDelegateModel for the given `nodeId` and tries to cast the + * stored pointer to the given type + */ + template + NodeDelegateModelType *delegateModel(NodeId const nodeId) + { + auto it = _models.find(nodeId); + if (it == _models.end()) + return nullptr; + + //auto model = dynamic_cast(it->second.get()); + auto model = dynamic_cast(*it); + + return model; + } + +Q_SIGNALS: + void inPortDataWasSet(NodeId const, PortType const, PortIndex const); + +private: + NodeId newNodeId() override { return _nextNodeId++; } + + void sendConnectionCreation(ConnectionId const connectionId); + + void sendConnectionDeletion(ConnectionId const connectionId); + +private Q_SLOTS: + /** + * Fuction is called in three cases: + * + * - By underlying NodeDelegateModel when a node has new data to propagate. + * @see DataFlowGraphModel::addNode + * - When a new connection is created. + * @see DataFlowGraphModel::addConnection + * - When a node restored from JSON an needs to send data downstream. + * @see DataFlowGraphModel::loadNode + */ + void onOutPortDataUpdated(NodeId const nodeId, PortIndex const portIndex); + + /// Function is called after detaching a connection. + void propagateEmptyDataTo(NodeId const nodeId, PortIndex const portIndex); + +private: + QSharedPointer _registry; + + NodeId _nextNodeId; + + QMap> _models; + + QSet _connectivity; + + mutable QMap _nodeGeometryData; +}; + diff --git a/include/defaultHorizontalNodeGeometry.h b/include/defaultHorizontalNodeGeometry.h new file mode 100644 index 0000000..94d13f6 --- /dev/null +++ b/include/defaultHorizontalNodeGeometry.h @@ -0,0 +1,54 @@ +#pragma once + +#include "abstractNodeGeometry.h" + +#include + +class AbstractGraphModel; +class BasicGraphicsScene; + +class DefaultHorizontalNodeGeometry : public AbstractNodeGeometry +{ +public: + DefaultHorizontalNodeGeometry(AbstractGraphModel &graphModel); + +public: + QSize size(NodeId const nodeId) const override; + + void recomputeSize(NodeId const nodeId) const override; + + QPointF portPosition(NodeId const nodeId, + PortType const portType, + PortIndex const index) const override; + + QPointF portTextPosition(NodeId const nodeId, + PortType const portType, + PortIndex const PortIndex) const override; + QPointF captionPosition(NodeId const nodeId) const override; + + QRectF captionRect(NodeId const nodeId) const override; + + QPointF widgetPosition(NodeId const nodeId) const override; + + QRect resizeHandleRect(NodeId const nodeId) const override; + +private: + QRectF portTextRect(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex) const; + + /// Finds max number of ports and multiplies by (a port height + interval) + unsigned int maxVerticalPortsExtent(NodeId const nodeId) const; + + unsigned int maxPortsTextAdvance(NodeId const nodeId, PortType const portType) const; + +private: + // Some variables are mutable because we need to change drawing + // metrics corresponding to fontMetrics but this doesn't change + // constness of the Node. + + mutable unsigned int _portSize; + unsigned int _portSpasing; + mutable QFontMetrics _fontMetrics; + mutable QFontMetrics _boldFontMetrics; +}; diff --git a/include/defaultNodePainter.h b/include/defaultNodePainter.h new file mode 100644 index 0000000..240e06b --- /dev/null +++ b/include/defaultNodePainter.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "abstractNodePainter.h" +#include "global.h" + + +class BasicGraphicsScene; +class GraphModel; +class NodeGeometry; +class NodeGraphicsObject; +class NodeState; + +/// @ Lightweight class incapsulating paint code. +class DefaultNodePainter : public AbstractNodePainter +{ +public: + void paint(QPainter *painter, NodeGraphicsObject &ngo) const override; + + void drawNodeRect(QPainter *painter, NodeGraphicsObject &ngo) const; + + void drawConnectionPoints(QPainter *painter, NodeGraphicsObject &ngo) const; + + void drawFilledConnectionPoints(QPainter *painter, NodeGraphicsObject &ngo) const; + + void drawNodeCaption(QPainter *painter, NodeGraphicsObject &ngo) const; + + void drawEntryLabels(QPainter *painter, NodeGraphicsObject &ngo) const; + + void drawResizeRect(QPainter *painter, NodeGraphicsObject &ngo) const; +}; diff --git a/include/defaultVerticalNodeGeometry.h b/include/defaultVerticalNodeGeometry.h new file mode 100644 index 0000000..fdafde5 --- /dev/null +++ b/include/defaultVerticalNodeGeometry.h @@ -0,0 +1,57 @@ +#pragma once + +#include "abstractNodeGeometry.h" + +#include + +class AbstractGraphModel; +class BasicGraphicsScene; + +class DefaultVerticalNodeGeometry : public AbstractNodeGeometry +{ +public: + DefaultVerticalNodeGeometry(AbstractGraphModel &graphModel); + +public: + QSize size(NodeId const nodeId) const override; + + void recomputeSize(NodeId const nodeId) const override; + + QPointF portPosition(NodeId const nodeId, + PortType const portType, + PortIndex const index) const override; + + QPointF portTextPosition(NodeId const nodeId, + PortType const portType, + PortIndex const PortIndex) const override; + + QPointF captionPosition(NodeId const nodeId) const override; + + QRectF captionRect(NodeId const nodeId) const override; + + QPointF widgetPosition(NodeId const nodeId) const override; + + QRect resizeHandleRect(NodeId const nodeId) const override; + +private: + QRectF portTextRect(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex) const; + /// Finds + unsigned int maxHorizontalPortsExtent(NodeId const nodeId) const; + + unsigned int maxPortsTextAdvance(NodeId const nodeId, PortType const portType) const; + + unsigned int portCaptionsHeight(NodeId const nodeId, PortType const portType) const; + +private: + // Some variables are mutable because we need to change drawing + // metrics corresponding to fontMetrics but this doesn't change + // constness of the Node. + + mutable unsigned int _portSize; + unsigned int _portSpasing; + mutable QFontMetrics _fontMetrics; + mutable QFontMetrics _boldFontMetrics; +}; + diff --git a/include/designerScene.h b/include/designerScene.h new file mode 100644 index 0000000..29a700d --- /dev/null +++ b/include/designerScene.h @@ -0,0 +1,63 @@ +#ifndef DESIGNER_SCENE_H +#define DESIGNER_SCENE_H + +#include +#include "basicGraphicsScene.h" +#include "dataFlowGraphModel.h" + +class GraphicsItemGroup; +class DrawingPanel; + + +class DesignerScene : public BasicGraphicsScene +{ + Q_OBJECT + +public: + DesignerScene(DataFlowGraphModel &graphModel,QObject *parent = 0); + virtual ~DesignerScene(); + + void setGridVisible(bool); + void setView(QGraphicsView* view) { m_pView = view; } + QGraphicsView* getView() { return m_pView; } + void callParentEvent(QGraphicsSceneMouseEvent*); + + GraphicsItemGroup* createGroup(); + void destroyGroup(); + +signals: + void signalAddItem(QGraphicsItem*); + +protected: + void drawBackground(QPainter*, const QRectF&) override; + void mousePressEvent(QGraphicsSceneMouseEvent*) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; + void keyPressEvent(QKeyEvent*) override; + void keyReleaseEvent(QKeyEvent*) override; + +private: + bool m_bGridVisible; + QGraphicsView* m_pView; + DrawingPanel* m_pDrawingPanel; //保存父指针 + +public: + std::vector selectedNodes() const; + +public: + QMenu *createSceneMenu(QPointF const scenePos) override; + +public Q_SLOTS: + bool save() const; + + bool load(); + +Q_SIGNALS: + void sceneLoaded(); + +private: + DataFlowGraphModel &_graphModel; +}; + +#endif diff --git a/include/designerView.h b/include/designerView.h new file mode 100644 index 0000000..4f124a0 --- /dev/null +++ b/include/designerView.h @@ -0,0 +1,40 @@ +#ifndef DESIGNER_VIEW_H +#define DESIGNER_VIEW_H + +#include + +class DesignerView : public QGraphicsView +{ + Q_OBJECT + +public: + explicit DesignerView(QWidget *parent = 0); + virtual ~DesignerView(); + + //视图操作-外部调用 + void zoomIn(); + void zoomOut(); + void zoomFit(); + +protected: + virtual void contextMenuEvent(QContextMenuEvent*) override; + virtual void mousePressEvent(QMouseEvent*) override; + virtual void mouseMoveEvent(QMouseEvent*) override; + virtual void mouseReleaseEvent(QMouseEvent*) override; + virtual void wheelEvent(QWheelEvent*) override; + +private: + void initialize(); + //视图操作相关 + void zoom(const QPointF&, double); + bool zoomLimit(double&); + double getScaleFactor(); + void translate(const QPointF&); + +private: + bool m_bMousePress; + double m_dScale; + QPointF m_ptLatstMouse_view; //鼠标最后按下在view中的位置 +}; + +#endif diff --git a/include/diagramCavas.h b/include/diagramCavas.h new file mode 100644 index 0000000..c696ccf --- /dev/null +++ b/include/diagramCavas.h @@ -0,0 +1,33 @@ +#ifndef DIAGRAMCAVAS_H +#define DIAGRAMCAVAS_H + +#include +#include "global.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class diagramCavas; } +QT_END_NAMESPACE + +class DrawingPanel; + +class DiagramCavas : public QMdiArea +{ + Q_OBJECT + +public: + DiagramCavas(QWidget *parent = nullptr); + ~DiagramCavas(); + +public: + void initial(); + +public slots: + void onSignal_addDrawingPanel(const QString& sTitile); + void onSignal_addGraphicsItem(GraphicsItemType&); + +private: + QMap m_mapDrawPanel; + +}; + +#endif diff --git a/include/drawingPanel.h b/include/drawingPanel.h new file mode 100644 index 0000000..8097630 --- /dev/null +++ b/include/drawingPanel.h @@ -0,0 +1,51 @@ +#ifndef DRAWINGPANEL_H +#define DRAWINGPANEL_H + +#include +#include "global.h" +#include "designerScene.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class drawingPanel; } +QT_END_NAMESPACE + +class DesignerView; +class DesignerScene; +class SelectorManager; +class GraphicsItemGroup; +//class NodeDelegateModelRegistry; + + +class DrawingPanel : public QWidget +{ + Q_OBJECT + +public: + DrawingPanel(QWidget *parent = nullptr); + ~DrawingPanel(); + + QGraphicsScene* getQGraphicsScene(); + DesignerScene* getDesignerScene(); + + void grahpicsViewZoomIn(); + void grahpicsViewZoomOut(); + void grahpicsViewZoomFit(); + + GraphicsItemGroup* createItemGroup(); + void destroyItemGroup(); + + SelectorManager* selectorManager() const; //返回manager指针 + +public slots: + void onSignal_addGraphicsItem(GraphicsItemType&); +private: + QSharedPointer registerDataModels(); +private: + Ui::drawingPanel *ui; + DesignerView* m_pGraphicsView; + DesignerScene* m_pGraphicsScene; + SelectorManager* m_pSelectorManager; + +}; + +#endif diff --git a/include/electricElementsBox.h b/include/electricElementsBox.h new file mode 100644 index 0000000..177c11f --- /dev/null +++ b/include/electricElementsBox.h @@ -0,0 +1,31 @@ +#ifndef ELETRICELEMENTSPANELCONTAINER_H +#define ELETRICELEMENTSPANELCONTAINER_H + +#include +#include "global.h" + +class ToolBox; +class ElectricElementsPanel; + +//电力图元面板 +class ElectricElementsBox : public QObject +{ + Q_OBJECT + +public: + ElectricElementsBox(QObject *parent = nullptr); + ~ElectricElementsBox(); + +public: + void initial(); + ToolBox* getToolBox() const; +signals: + void addEletricItem(GraphicsItemType&); +public slots: + void onSignal_addEletricItem(GraphicsItemType&); +private: + ToolBox* m_pToolBox; + QMap m_mapPanels; +}; + +#endif diff --git a/include/electricElementsListwidget.h b/include/electricElementsListwidget.h new file mode 100644 index 0000000..6d13bcd --- /dev/null +++ b/include/electricElementsListwidget.h @@ -0,0 +1,18 @@ +#ifndef ELETRICELEMENTLISTWIDGET_H +#define ELETRICELEMENTLISTWIDGET_H + +#include +#include + +class ElectricElementsListwidget : public QListWidget +{ + Q_OBJECT + +public: + ElectricElementsListwidget(QListWidget *parent = nullptr); + ~ElectricElementsListwidget(); +protected: + void mousePressEvent(QMouseEvent *event); +}; + +#endif diff --git a/include/electricElementsPanel.h b/include/electricElementsPanel.h new file mode 100644 index 0000000..6c88aa3 --- /dev/null +++ b/include/electricElementsPanel.h @@ -0,0 +1,31 @@ +#ifndef ELETRICELEMENTSPANEL_H +#define ELETRICELEMENTSPANEL_H + +#include +#include "global.h" + +class ElectricElementsListwidget; +class QListWidgetItem; + +class ElectricElementsPanel : public QWidget +{ + Q_OBJECT + +public: + ElectricElementsPanel(QWidget *parent = nullptr); + ~ElectricElementsPanel(); + +signals: + void addGraphicsItem(GraphicsItemType&); +public: + void setData(const QMap&); +private: + void initial(); +public slots: + void onItemClicked(QListWidgetItem*); +private: + ElectricElementsListwidget* m_pListWidget; + QMap m_mapEleData; +}; + +#endif diff --git a/include/global.h b/include/global.h new file mode 100644 index 0000000..0928643 --- /dev/null +++ b/include/global.h @@ -0,0 +1,142 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#include +#include +#include +#include + +const double g_dGriaphicsScene_Width = 600; +const double g_dGriaphicsScene_Height = 450; + +//Q_NAMESPACE +enum GraphicsItemType +{ + GIT_base = QGraphicsItem::UserType + 1, + GIT_line = QGraphicsItem::UserType + 2, + GIT_rect = QGraphicsItem::UserType + 3, + GIT_roundRect = QGraphicsItem::UserType + 4, + GIT_ellipse = QGraphicsItem::UserType + 5, + GIT_polygon = QGraphicsItem::UserType + 6, + //====================================== + GIT_bus = QGraphicsItem::UserType + 50, + GIT_itemRect = QGraphicsItem::UserType + 51, + GIT_itemTri = QGraphicsItem::UserType + 52 +}; +//Q_ENUM_NS(GraphicsItemType) + +/** + * Constants used for fetching QVariant data from GraphModel. + */ +enum class NodeRole { + Type = 0, ///< Type of the current node, usually a string. + Position = 1, ///< `QPointF` positon of the node on the scene. + Size = 2, ///< `QSize` for resizable nodes. + CaptionVisible = 3, ///< `bool` for caption visibility. + Caption = 4, ///< `QString` for node caption. + Style = 5, ///< Custom NodeStyle as QJsonDocument + InternalData = 6, ///< Node-stecific user data as QJsonObject + InPortCount = 7, ///< `unsigned int` + OutPortCount = 9, ///< `unsigned int` + Widget = 10, ///< Optional `QWidget*` or `nullptr` +}; +//Q_ENUM_NS(NodeRole) + +/** + * Specific flags regulating node features and appeaarence. + */ +enum NodeFlag { + NoFlags = 0x0, ///< Default NodeFlag + Resizable = 0x1, ///< Lets the node be resizable + Locked = 0x2 +}; + +Q_DECLARE_FLAGS(NodeFlags, NodeFlag) +//Q_FLAG_NS(NodeFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(NodeFlags) + +/** + * Constants for fetching port-related information from the GraphModel. + */ +enum class PortRole { + Data = 0, ///< `std::shared_ptr`. + DataType = 1, ///< `QString` describing the port data type. + ConnectionPolicyRole = 2, ///< `enum` ConnectionPolicyRole + CaptionVisible = 3, ///< `bool` for caption visibility. + Caption = 4, ///< `QString` for port caption. +}; +//Q_ENUM_NS(PortRole) + +/** + * Defines how many connections are possible to attach to ports. The + * values are fetched using PortRole::ConnectionPolicy. + */ +enum class ConnectionPolicy { + One, ///< Just one connection for each port. + Many, ///< Any number of connections possible for the port. +}; +//Q_ENUM_NS(ConnectionPolicy) + +/** + * Used for distinguishing input and output node ports. + */ +enum class PortType { + In = 0, ///< Input node port (from the left). + Out = 1, ///< Output node port (from the right). + None = 2 +}; +//Q_ENUM_NS(PortType) + +using PortCount = int; + +/// ports are consecutively numbered starting from zero. +using PortIndex = int; + +const PortIndex InvalidPortIndex = -1; + +/// Unique Id associated with each node in the GraphModel. +using NodeId = int; + +const NodeId InvalidNodeId = -1; + +/** + * A unique connection identificator that stores + * out `NodeId`, out `PortIndex`, in `NodeId`, in `PortIndex` + */ +struct ConnectionId +{ + int conId; + NodeId outNodeId; + PortIndex outPortIndex; + NodeId inNodeId; + PortIndex inPortIndex; +}; + +inline uint qHash(const ConnectionId &data, uint seed){ + + return data.conId; +} + +inline bool operator<(ConnectionId const &a, ConnectionId const &b) +{ + return a.conId +#include "global.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class graphicElementsPanel; } +QT_END_NAMESPACE + +class GraphicElementsPanel : public QWidget +{ + Q_OBJECT + +public: + GraphicElementsPanel(QWidget *parent = nullptr); + ~GraphicElementsPanel(); + +signals: + void addGraphicsItem(GraphicsItemType&); + +public slots: + void onBtnClicked_GraphicsItem(); + +private: + Ui::graphicElementsPanel *ui; + +}; + +#endif diff --git a/include/graphicsItem/electricSvgItem.h b/include/graphicsItem/electricSvgItem.h new file mode 100644 index 0000000..2cf0c4d --- /dev/null +++ b/include/graphicsItem/electricSvgItem.h @@ -0,0 +1,28 @@ +#ifndef ELECTRICSVGITEM_H +#define ELECTRICSVGITEM_H + +#include "graphicsBaseItem.h" +#include + +class ElectricSvgItem :public GraphicsBaseItem +{ + Q_OBJECT +public: + ElectricSvgItem(const QRect &rect, QGraphicsItem *parent = 0); + virtual ~ElectricSvgItem(); + void resize(int,double, double, const QPointF&); + void updateCoordinate(); + void move(const QPointF&); + +protected: + virtual QPainterPath shape(); + virtual void editShape(int, const QPointF&); + virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); + virtual void loadSvg(const QString&); +protected: + QRectF m_lastBoudingRect; //记录上一时刻的boundingRect + QSvgRenderer* m_pRender; + +}; + +#endif diff --git a/include/graphicsItem/electricSvgItemBus.h b/include/graphicsItem/electricSvgItemBus.h new file mode 100644 index 0000000..f00bf31 --- /dev/null +++ b/include/graphicsItem/electricSvgItemBus.h @@ -0,0 +1,18 @@ +#ifndef ELECTRICSVGITEMBUS_H +#define ELECTRICSVGITEMBUS_H + +#include "electricSvgItem.h" + +class ElectricSvgItemBus :public ElectricSvgItem +{ + Q_OBJECT +public: + ElectricSvgItemBus(const QRect &rect, QGraphicsItem *parent = 0); + virtual ~ElectricSvgItemBus(); +protected: + virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); +private: + virtual void updateHandles(); +}; + +#endif diff --git a/include/graphicsItem/electricSvgItemRect.h b/include/graphicsItem/electricSvgItemRect.h new file mode 100644 index 0000000..d7601b0 --- /dev/null +++ b/include/graphicsItem/electricSvgItemRect.h @@ -0,0 +1,20 @@ +#ifndef ELECTRICSVGITEMRECT_H +#define ELECTRICSVGITEMRECT_H + +#include "electricSvgItem.h" + +class ElectricSvgItemRect :public ElectricSvgItem +{ + Q_OBJECT +public: + ElectricSvgItemRect(const QRect &rect, QGraphicsItem *parent = 0); + virtual ~ElectricSvgItemRect(); +protected: + virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); +private: + virtual void updateHandles(); + + double m_dRatioX; +}; + +#endif diff --git a/include/graphicsItem/electricSvgItemTriangle.h b/include/graphicsItem/electricSvgItemTriangle.h new file mode 100644 index 0000000..a258f12 --- /dev/null +++ b/include/graphicsItem/electricSvgItemTriangle.h @@ -0,0 +1,20 @@ +#ifndef ELECTRICSVGITEMTRIANGLE_H +#define ELECTRICSVGITEMTRIANGLE_H + +#include "electricSvgItem.h" + +class ElectricSvgItemTriangle :public ElectricSvgItem +{ + Q_OBJECT +public: + ElectricSvgItemTriangle(const QRect &rect, QGraphicsItem *parent = 0); + virtual ~ElectricSvgItemTriangle(); +protected: + virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); +private: + virtual void updateHandles(); + double m_dTopRatioX; + double m_dBottomRatioX; +}; + +#endif diff --git a/include/graphicsItem/graphicsBaseItem.h b/include/graphicsItem/graphicsBaseItem.h new file mode 100644 index 0000000..2f61ae2 --- /dev/null +++ b/include/graphicsItem/graphicsBaseItem.h @@ -0,0 +1,471 @@ +#ifndef GRAPHICSBASEITEM_H +#define GRAPHICSBASEITEM_H + +#include "itemControlHandle.h" + +#include +#include +#include +#include +#include + + +enum ShapeType +{ + T_undefined, + T_item, + T_group +}; + +enum ItemState +{ + S_normal = 0, + S_lineOut, + s_lineIn +}; + +//基类采用模板形式,QGraphicsItem是默认值,也可以是别的类型,比如QGraphicsItemGroup,这样不同的基类继承可以共用一些高层的行为定义 +template +class AbstractShapeType : public BaseType +{ +public: + explicit AbstractShapeType(QGraphicsItem *parent = 0) + : BaseType(parent) + { + m_type = T_undefined; + m_pen = QPen(Qt::NoPen); + m_brush = QBrush(QColor(rand() % 32 * 8, rand() % 32 * 8, rand() % 32 * 8)); + m_dWidth = m_dHeight = 0; + m_pOperationCopy = nullptr; + m_dSyncRotationByParent = 0.0; + } + + virtual ~AbstractShapeType() + { + /*for (size_t i = 0; i < m_vecHanle.size(); i++) + { + ItemControlHandle* pHandle = m_vecHanle[i]; + if (pHandle) + { + delete pHandle; + pHandle = nullptr; + } + }*/ + foreach (int key, m_vecHanle.keys()) + { + ItemControlHandle* pHandle = m_vecHanle.value(key); + if (pHandle) + { + delete pHandle; + pHandle = nullptr; + } + } + } + +public: + virtual ShapeType getType() {return m_type;} + + QPen pen() { return m_pen; } + void setPen(const QPen &pen) { m_pen = pen; } + QColor penColor() { return m_pen.color(); } + void setPenColor(const QColor &color) { m_pen.setColor(color); } + + QBrush brush() { return m_brush; } + void setBrush(const QBrush &brush) { m_brush = brush; } + QColor brushColor() { return m_brush.color(); } + void setBrushColor(const QColor &color) { m_brush.setColor(color); } + + double width() { return m_dWidth; } + void setWidth(double width) + { + m_dWidth = width; + updateCoordinate(); + } + + double height() { return m_dHeight; } + void setHeight(double height) + { + m_dHeight = height; + updateCoordinate(); + } + + int collidesWithHandle(const QPointF& point) + { + /*for(auto it = m_vecHanle.begin(); it != m_vecHanle.end(); it++) + { + QPointF pt = (*it)->mapFromScene(point); + if((*it)->contains(pt)) + return (*it)->getTag(); + }*/ + + foreach (int key, m_vecHanle.keys()) + { + ItemControlHandle* pHandle = m_vecHanle.value(key); + if (pHandle) + { + QPointF pt = pHandle->mapFromScene(point); + if(pHandle->contains(pt)) + return pHandle->getTag(); + } + } + return HandleTag::H_none; + } + + //操作副本相关 + virtual void createOperationCopy() {} + virtual void removeOperationCopy() {} + virtual void moveOperationCopy(const QPointF&) {} + virtual void rotateOperationCopy(const double&) {} + + virtual void resize(int,double, double, const QPointF&) {} + virtual void move(const QPointF&) {} + virtual void editShape(int, const QPointF&) {} + + virtual void updateCoordinate() {} + + //handle相关 + virtual int handleCount() { return m_vecHanle.count(); } + virtual ItemControlHandle* getHandle(int nHandle) + { + ItemControlHandle* handle = nullptr; + /*for(auto it = m_vecHanle.begin(); it != m_vecHanle.end(); it++) + { + if((*it)->getTag() == nHandle) + { + handle = (*it); + return handle; + } + }*/ + + foreach (int key, m_vecHanle.keys()) + { + ItemControlHandle* pHandle = m_vecHanle.value(key); + if (pHandle) + { + if(pHandle->getTag() == nHandle) + { + handle = pHandle; + return handle; + } + } + } + return handle; + } + virtual void setHandleVisible(bool bVisible) + { + /*for(auto it = m_vecHanle.begin(); it != m_vecHanle.end(); it++) + { + if(bVisible) + (*it)->show(); + else + (*it)->hide(); + }*/ + + foreach (int key, m_vecHanle.keys()) + { + ItemControlHandle* pHandle = m_vecHanle.value(key); + if(pHandle) + { + if(bVisible) + pHandle->show(); + else + pHandle->hide(); + } + } + } + virtual QPointF getSymmetricPointPos(int nHandle) //获取对称点的坐标位置,缩放的时候需要以对称点为锚点 + { + QPointF pt; + //handle的位置相对boundingRect会有一个向外的错位,因此直接采用bounddingRect的相应位置会更精准 + switch (nHandle) + { + case H_leftTop: + //pt = m_vecHanle.at(H_rightBottom - 1)->pos(); + pt = m_boundingRect.bottomRight(); + break; + case H_top: + //pt = m_vecHanle.at(H_bottom - 1)->pos(); + pt = QPointF(m_boundingRect.width() * 0.5, m_boundingRect.bottom()); + break; + case H_rightTop: + //pt = m_vecHanle.at(H_leftBottom - 1)->pos(); + pt = m_boundingRect.bottomLeft(); + break; + case H_right: + //pt = m_vecHanle.at(H_left - 1)->pos(); + pt = QPointF(m_boundingRect.left(), m_boundingRect.height() * 0.5); + break; + case H_rightBottom: + //pt = m_vecHanle.at(H_leftTop - 1)->pos(); + pt = m_boundingRect.topLeft(); + break; + case H_bottom: + //pt = m_vecHanle.at(H_top - 1)->pos(); + pt = QPointF(m_boundingRect.width() * 0.5, m_boundingRect.top()); + break; + case H_leftBottom: + //pt = m_vecHanle.at(H_rightTop - 1)->pos(); + pt = m_boundingRect.topRight(); + break; + case H_left: + //pt = m_vecHanle.at(H_right - 1)->pos(); + pt = QPointF(m_boundingRect.right(), m_boundingRect.height() * 0.5); + break; + default: + break; + } + + return pt; + } + virtual void updateHandles() + { + int nMargin = 5; + const QRectF& boundRect = this->boundingRect(); + /*for(auto it = m_vecHanle.begin(); it != m_vecHanle.end(); it++) + { + switch ((*it)->getTag()) { + case H_leftTop: + (*it)->move(boundRect.x() - nMargin, boundRect.y() - nMargin); + break; + case H_top: + (*it)->move(boundRect.x() + boundRect.width() * 0.5, boundRect.y() - nMargin); + break; + case H_rightTop: + (*it)->move(boundRect.x() + boundRect.width() + nMargin, boundRect.y() - nMargin); + break; + case H_right: + (*it)->move(boundRect.x() + boundRect.width() + nMargin, boundRect.y() + boundRect.height() * 0.5 + nMargin); + break; + case H_rightBottom: + (*it)->move(boundRect.x() + boundRect.width() + nMargin, boundRect.y() + boundRect.height() + nMargin); + break; + case H_bottom: + (*it)->move(boundRect.x() + boundRect.width() * 0.5, boundRect.y() + boundRect.height()+ nMargin); + break; + case H_leftBottom: + (*it)->move(boundRect.x() - nMargin, boundRect.y() + boundRect.height() + nMargin); + break; + case H_left: + (*it)->move(boundRect.x() - nMargin, boundRect.y() + boundRect.height() * 0.5); + break; + case H_rotate_leftTop: + { + ItemControlHandle* handle = getHandle(H_leftTop); + if(handle) + { + int nSize = handle->getSize(); + QPointF pt = handle->pos(); + (*it)->move(pt.x() - nSize - 1, pt.y() - nSize - 1); + } + else + (*it)->setVisible(false); + + break; + } + case H_rotate_rightTop: + { + ItemControlHandle* handle = getHandle(H_rightTop); + if(handle) + { + int nSize = handle->getSize(); + QPointF pt = handle->pos(); + (*it)->move(pt.x() + nSize + 1, pt.y() - nSize - 1); + } + else + (*it)->setVisible(false); + + break; + } + case H_rotate_rightBottom: + { + ItemControlHandle* handle = getHandle(H_rightBottom); + if(handle) + { + int nSize = handle->getSize(); + QPointF pt = handle->pos(); + (*it)->move(pt.x() + nSize + 1, pt.y() + nSize + 1); + } + else + (*it)->setVisible(false); + + break; + } + case H_rotate_leftBottom: + { + ItemControlHandle* handle = getHandle(H_leftBottom); + if(handle) + { + int nSize = handle->getSize(); + QPointF pt = handle->pos(); + (*it)->move(pt.x() - nSize - 1, pt.y() + nSize + 1); + } + else + (*it)->setVisible(false); + + break; + } + default: + break; + } + }*/ + + foreach (int key, m_vecHanle.keys()) + { + ItemControlHandle* pHandle = m_vecHanle.value(key); + if(pHandle) + { + switch (pHandle->getTag()) { + case H_leftTop: + pHandle->move(boundRect.x() - nMargin, boundRect.y() - nMargin); + break; + case H_top: + pHandle->move(boundRect.x() + boundRect.width() * 0.5, boundRect.y() - nMargin); + break; + case H_rightTop: + pHandle->move(boundRect.x() + boundRect.width() + nMargin, boundRect.y() - nMargin); + break; + case H_right: + pHandle->move(boundRect.x() + boundRect.width() + nMargin, boundRect.y() + boundRect.height() * 0.5 + nMargin); + break; + case H_rightBottom: + pHandle->move(boundRect.x() + boundRect.width() + nMargin, boundRect.y() + boundRect.height() + nMargin); + break; + case H_bottom: + pHandle->move(boundRect.x() + boundRect.width() * 0.5, boundRect.y() + boundRect.height()+ nMargin); + break; + case H_leftBottom: + pHandle->move(boundRect.x() - nMargin, boundRect.y() + boundRect.height() + nMargin); + break; + case H_left: + pHandle->move(boundRect.x() - nMargin, boundRect.y() + boundRect.height() * 0.5); + break; + case H_rotate_leftTop: + { + ItemControlHandle* handle = getHandle(H_leftTop); + if(handle) + { + int nSize = handle->getSize(); + QPointF pt = handle->pos(); + pHandle->move(pt.x() - nSize - 1, pt.y() - nSize - 1); + } + else + pHandle->setVisible(false); + + break; + } + case H_rotate_rightTop: + { + ItemControlHandle* handle = getHandle(H_rightTop); + if(handle) + { + int nSize = handle->getSize(); + QPointF pt = handle->pos(); + pHandle->move(pt.x() + nSize + 1, pt.y() - nSize - 1); + } + else + pHandle->setVisible(false); + + break; + } + case H_rotate_rightBottom: + { + ItemControlHandle* handle = getHandle(H_rightBottom); + if(handle) + { + int nSize = handle->getSize(); + QPointF pt = handle->pos(); + pHandle->move(pt.x() + nSize + 1, pt.y() + nSize + 1); + } + else + pHandle->setVisible(false); + + break; + } + case H_rotate_leftBottom: + { + ItemControlHandle* handle = getHandle(H_leftBottom); + if(handle) + { + int nSize = handle->getSize(); + QPointF pt = handle->pos(); + pHandle->move(pt.x() - nSize - 1, pt.y() + nSize + 1); + } + else + pHandle->setVisible(false); + + break; + } + default: + break; + } + } + } + + double dWidth = boundRect.width(); + double dHeight = boundRect.height(); + double dDiagonal = sqrt((dWidth* 0.5)*(dWidth * 0.5)+(dHeight*0.5)*(dHeight*0.5)); + double dParam = dWidth > dHeight ? (dDiagonal-dWidth*0.5):(dDiagonal-dHeight*0.5); + m_boundingRect_selected = boundRect.adjusted(-dParam-nMargin, -dParam-nMargin, dParam+nMargin, dParam+nMargin); + } + + virtual QRectF boundingRect() const { return m_boundingRect; } + virtual QPainterPath shape() + { + QPainterPath path; + return path; + } + + virtual void syncRotationDataFromParent(const double&) {} + virtual double getSyncRotationDataFromParent() {return m_dSyncRotationByParent;} + +protected: + ShapeType m_type; + QPen m_pen; + QBrush m_brush; + double m_dWidth; + double m_dHeight; + QRectF m_boundingRect; + QRectF m_boundingRect_selected; //选中矩形框 + + double m_dSyncRotationByParent; //父项(被加入到某一组)的旋转数据,因为加入某一组后,对该组进行旋转,自身的旋转数据不会同步更新 + + QGraphicsPathItem* m_pOperationCopy; //图元移动和旋转时的操作副本 + QPointF m_movingIniPos; //移动副本开始移动初始 + + + QMap m_vecHanle; +}; + +typedef AbstractShapeType AbstractShape; + +class GraphicsBaseItem : public QObject, public AbstractShapeType +{ + Q_OBJECT + +public: + GraphicsBaseItem(QGraphicsItem *parent); + virtual ~GraphicsBaseItem(); + + virtual void createOperationCopy(); + virtual void removeOperationCopy(); + virtual void moveOperationCopy(const QPointF&); + virtual void rotateOperationCopy(const double&); + virtual void syncRotationDataFromParent(const double&); + //多边形、线段等点选创建的对象需要的函数 + virtual void addPoint(const QPointF&) {} + virtual bool endDrawing() { return true; } + virtual void setState(ItemState s){m_state = s;} + virtual void setBeginConnectPos(QPointF p){m_beginConnectPoint = p;} + virtual void setEndConnectPos(QPointF p){m_endConnectPoint = p;} +protected: + virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange, const QVariant&); + virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent*); + +protected: + + ItemState m_state; + QPointF m_beginConnectPoint; + QPointF m_endConnectPoint; +}; + +#endif diff --git a/include/graphicsItem/graphicsItemGroup.h b/include/graphicsItem/graphicsItemGroup.h new file mode 100644 index 0000000..85ecdfd --- /dev/null +++ b/include/graphicsItem/graphicsItemGroup.h @@ -0,0 +1,38 @@ +#ifndef GRAPHICSITEMGROUP_H +#define GRAPHICSITEMGROUP_H + +#include "graphicsBaseItem.h" + + +class GraphicsItemGroup : public QObject, public AbstractShapeType +{ + Q_OBJECT + +public: + GraphicsItemGroup(QGraphicsItem *parent = 0); + virtual ~GraphicsItemGroup(); + + void resize(int,double, double, const QPointF&); + void updateCoordinate(); + + void createOperationCopy(); + void removeOperationCopy(); + void moveOperationCopy(const QPointF&); + void rotateOperationCopy(const double&); + + void addItems(const QList&); + QList getItems() {return m_listItem;} + +protected: + virtual QPainterPath shape(); + virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); + virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent*); + virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange, const QVariant&); + virtual void syncRotationDataFromParent(const double&); + +private: + QRectF m_lastBoudingRect; //记录上一时刻的boundingRect + QList m_listItem; +}; + +#endif diff --git a/include/graphicsItem/graphicsPolygonItem.h b/include/graphicsItem/graphicsPolygonItem.h new file mode 100644 index 0000000..a76f04e --- /dev/null +++ b/include/graphicsItem/graphicsPolygonItem.h @@ -0,0 +1,33 @@ +#ifndef GRAPHICSPOLYGONITEM_H +#define GRAPHICSPOLYGONITEM_H + +#include "graphicsBaseItem.h" + +class GraphicPolygonItem : public GraphicsBaseItem +{ +public: + GraphicPolygonItem(QGraphicsItem *parent = 0); + virtual ~GraphicPolygonItem(); + + void resize(int,double, double, const QPointF&); + void updateCoordinate(); + void move(const QPointF&); + void editShape(int, const QPointF&); + + void addPoint(const QPointF&); + bool endDrawing(); + QPolygonF getPoints(void) { return m_points; } + +protected: + virtual QPainterPath shape(); + virtual QRectF boundingRect(); + virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); + +private: + virtual void updateHandles(); + + QPolygonF m_lastPoints; //记录上一时刻的点集数据,用于缩放等操作 + QPolygonF m_points; +}; + +#endif diff --git a/include/graphicsItem/graphicsRectItem.h b/include/graphicsItem/graphicsRectItem.h new file mode 100644 index 0000000..0b0ee6f --- /dev/null +++ b/include/graphicsItem/graphicsRectItem.h @@ -0,0 +1,30 @@ +#ifndef GRAPHICSRECTITEM_H +#define GRAPHICSRECTITEM_H + +#include "graphicsBaseItem.h" + +class GraphicsRectItem : public GraphicsBaseItem +{ +public: + GraphicsRectItem(const QRect &rect, bool isRound = false, QGraphicsItem *parent = 0); + virtual ~GraphicsRectItem(); + + void resize(int,double, double, const QPointF&); + void updateCoordinate(); + void move(const QPointF&); + void editShape(int, const QPointF&); + +protected: + virtual QPainterPath shape(); + virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); + +private: + virtual void updateHandles(); + + QRectF m_lastBoudingRect; //记录上一时刻的boundingRect + bool m_bIsRound; //是否为圆角矩形 + double m_dRatioX; + double m_dRatioY; +}; + +#endif diff --git a/include/graphicsItem/itemControlHandle.h b/include/graphicsItem/itemControlHandle.h new file mode 100644 index 0000000..1cadd4a --- /dev/null +++ b/include/graphicsItem/itemControlHandle.h @@ -0,0 +1,64 @@ +#ifndef ITEMCONTROLHANDLE_H +#define ITEMCONTROLHANDLE_H + +#include + +enum HandleType +{ + T_resize, //调整大小 + T_rotate, //旋转 + T_editShape, //编辑形状 + T_lineIn, //入线口 + T_lineOut //出线口 +}; + +enum HandleTag +{ + H_none = 0, + H_leftTop, + H_top, + H_rightTop, + H_right, + H_rightBottom, + H_bottom, + H_leftBottom, + H_left, //8 + H_rotate_leftTop, + H_rotate_rightTop, + H_rotate_rightBottom, + H_rotate_leftBottom, //12 + H_edit, + H_connect = 50 //连接操作点从50开始,前面预留 +}; + + +class ItemControlHandle : public QObject,public QGraphicsRectItem +{ + Q_OBJECT + +public: + ItemControlHandle(QGraphicsItem *parent); + virtual ~ItemControlHandle(); + +public: + void setType(HandleType ht) { m_type = ht; } + HandleType getType() { return m_type; } + + void setTag(int ht) { m_tag = ht; } + int getTag() { return m_tag; } + + int getSize(); + void move(double, double); + +protected: + virtual void hoverEnterEvent(QGraphicsSceneHoverEvent*) override; + virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; + virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override; + +private: + HandleType m_type; + int m_tag; + +}; + +#endif diff --git a/include/graphicsViewStyle.h b/include/graphicsViewStyle.h new file mode 100644 index 0000000..035147e --- /dev/null +++ b/include/graphicsViewStyle.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "style.h" + + +class GraphicsViewStyle : public Style +{ +public: + GraphicsViewStyle(); + + GraphicsViewStyle(QString jsonText); + + ~GraphicsViewStyle() = default; + +public: + static void setStyle(QString jsonText); + +private: + void loadJson(QJsonObject const &json) override; + + QJsonObject toJson() const override; + +public: + QColor BackgroundColor; + QColor FineGridColor; + QColor CoarseGridColor; +}; diff --git a/include/locateNode.h b/include/locateNode.h new file mode 100644 index 0000000..d204372 --- /dev/null +++ b/include/locateNode.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +class QGraphicsScene; + +class NodeGraphicsObject; + +NodeGraphicsObject *locateNodeAt(QPointF scenePoint, + QGraphicsScene &scene, + QTransform const &viewTransform); diff --git a/include/mainwindow.h b/include/mainwindow.h new file mode 100644 index 0000000..7d98d7f --- /dev/null +++ b/include/mainwindow.h @@ -0,0 +1,70 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +//#include "DockManager.h" +//#include "DockAreaWidget.h" +//#include "DockWidget.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class CMainWindow; } +QT_END_NAMESPACE + +class QGraphicsItem; +class QUndoStack; +class DrawingPanel; +class GraphicElementsPanel; +class DesignerScene; +class DiagramCavas; +class ElectricElementsBox; + +class CMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + CMainWindow(QWidget *parent = nullptr); + ~CMainWindow(); + +protected: + virtual void closeEvent(QCloseEvent* event) override; + virtual void changeEvent(QEvent* event) override; + +private: + void initializeDockUi(); + void initializeAction(); + +private slots: + void onAction_zoomIn(); + void onAction_zoomOut(); + void onAction_zoomFit(); + void onAction_createGroup(); + void onAction_destroyGroup(); + void onSignal_addItem(QGraphicsItem*); + void onSignal_deleteItem(); + +public: + GraphicElementsPanel* graphicsElementsPanel() const; + +private: + QAction* SavePerspectiveAction = nullptr; + QWidgetAction* PerspectiveListAction = nullptr; + QComboBox* PerspectiveComboBox = nullptr; + + QUndoStack* m_pUndoStack; + + Ui::CMainWindow *ui; + + //ads::CDockManager* DockManager; + //ads::CDockAreaWidget* StatusDockArea; + //ads::CDockWidget* TimelineDockWidget; + + DiagramCavas* m_pDiagramCavas; + DrawingPanel* m_pDrawingPanel; + ElectricElementsBox* m_pElectricElementsBox; + GraphicElementsPanel* m_pGraphicElementsPanel; +}; +#endif // MAINWINDOW_H diff --git a/include/nodeConnectionInteraction.h b/include/nodeConnectionInteraction.h new file mode 100644 index 0000000..126550c --- /dev/null +++ b/include/nodeConnectionInteraction.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include "global.h" + +class ConnectionGraphicsObject; +class NodeGraphicsObject; +class BasicGraphicsScene; + +/// Class wraps conecting and disconnecting checks. +/** + * An instance should be created on the stack and destroyed + * automatically when the operation is completed + */ +class NodeConnectionInteraction +{ +public: + NodeConnectionInteraction(NodeGraphicsObject &ngo, + ConnectionGraphicsObject &cgo, + BasicGraphicsScene &scene); + + /** + * Can connect when following conditions are met: + * 1. Connection 'requires' a port. + * 2. Connection loose end is above the node port. + * 3. Source and target `nodeId`s are different. + * 4. GraphModel permits connection. + */ + bool canConnect(PortIndex *portIndex) const; + + /// Creates a new connectino if possible. + /** + * 1. Check conditions from 'canConnect'. + * 2. Creates new connection with `GraphModel::addConnection`. + * 3. Adjust connection geometry. + */ + bool tryConnect() const; + + /** + * 1. Delete connection with `GraphModel::deleteConnection`. + * 2. Create a "draft" connection with incomplete `ConnectionId`. + * 3. Repaint both previously connected nodes. + */ + bool disconnect(PortType portToDisconnect) const; + +private: + PortType connectionRequiredPort() const; + + QPointF connectionEndScenePosition(PortType) const; + + QPointF nodePortScenePosition(PortType portType, PortIndex portIndex) const; + + PortIndex nodePortIndexUnderScenePoint(PortType portType, QPointF const &p) const; + +private: + NodeGraphicsObject &_ngo; + + ConnectionGraphicsObject &_cgo; + + BasicGraphicsScene &_scene; +}; + diff --git a/include/nodeData.h b/include/nodeData.h new file mode 100644 index 0000000..858b733 --- /dev/null +++ b/include/nodeData.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +/** + * `id` represents an internal unique data type for the given port. + * `name` is a normal text description. + */ +struct NodeDataType +{ + QString id; + QString name; +}; + +/** + * Class represents data transferred between nodes. + * @param type is used for comparing the types + * The actual data is stored in subtypes + */ +class NodeData +{ +public: + virtual ~NodeData() = default; + + virtual bool sameType(NodeData const &nodeData) const + { + return (this->type().id == nodeData.type().id); + } + + /// Type for inner use + virtual NodeDataType type() const = 0; +}; + +Q_DECLARE_METATYPE(NodeDataType); diff --git a/include/nodeDelegateModel.h b/include/nodeDelegateModel.h new file mode 100644 index 0000000..9134581 --- /dev/null +++ b/include/nodeDelegateModel.h @@ -0,0 +1,128 @@ +#pragma once + +#include + +#include "global.h" +#include "nodeData.h" +#include "nodeStyle.h" +#include "serializable.h" + + +class StyleCollection; + +/** + * The class wraps Node-specific data operations and propagates it to + * the nesting DataFlowGraphModel which is a subclass of + * AbstractGraphModel. + * This class is the same what has been called NodeDataModel before v3. + */ +class NodeDelegateModel : public QObject, public Serializable +{ + Q_OBJECT + +public: + NodeDelegateModel(); + + virtual ~NodeDelegateModel() = default; + + /// It is possible to hide caption in GUI + virtual bool captionVisible() const { return true; } + + /// Caption is used in GUI + virtual QString caption() const = 0; + + /// It is possible to hide port caption in GUI + virtual bool portCaptionVisible(PortType, PortIndex) const { return false; } + + /// Port caption is used in GUI to label individual ports + virtual QString portCaption(PortType, PortIndex) const { return QString(); } + + /// Name makes this model unique + virtual QString name() const = 0; + +public: + QJsonObject save() const override; + + void load(QJsonObject const &) override; + +public: + virtual unsigned int nPorts(PortType portType) const = 0; + + virtual NodeDataType dataType(PortType portType, PortIndex portIndex) const = 0; + +public: + virtual ConnectionPolicy portConnectionPolicy(PortType, PortIndex) const; + + NodeStyle const &nodeStyle() const; + + void setNodeStyle(NodeStyle const &style); + +public: + virtual void setInData(std::shared_ptr nodeData, PortIndex const portIndex) = 0; + + virtual std::shared_ptr outData(PortIndex const port) = 0; + + /** + * It is recommented to preform a lazy initialization for the + * embedded widget and create it inside this function, not in the + * constructor of the current model. + * + * Our Model Registry is able to shortly instantiate models in order + * to call the non-static `Model::name()`. If the embedded widget is + * allocated in the constructor but not actually embedded into some + * QGraphicsProxyWidget, we'll gonna have a dangling pointer. + */ + virtual QWidget *embeddedWidget() = 0; + + virtual bool resizable() const { return false; } + +public Q_SLOTS: + + virtual void inputConnectionCreated(ConnectionId const &) {} + + virtual void inputConnectionDeleted(ConnectionId const &) {} + + virtual void outputConnectionCreated(ConnectionId const &) {} + + virtual void outputConnectionDeleted(ConnectionId const &) {} + +Q_SIGNALS: + + /// Triggers the updates in the nodes downstream. + void dataUpdated(PortIndex const index); + + /// Triggers the propagation of the empty data downstream. + void dataInvalidated(PortIndex const index); + + void computingStarted(); + + void computingFinished(); + + void embeddedWidgetSizeUpdated(); + + /// Call this function before deleting the data associated with ports. + /** + * The function notifies the Graph Model and makes it remove and recompute the + * affected connection addresses. + */ + void portsAboutToBeDeleted(PortType const portType, PortIndex const first, PortIndex const last); + + /// Call this function when data and port moditications are finished. + void portsDeleted(); + + /// Call this function before inserting the data associated with ports. + /** + * The function notifies the Graph Model and makes it recompute the affected + * connection addresses. + */ + void portsAboutToBeInserted(PortType const portType, + PortIndex const first, + PortIndex const last); + + /// Call this function when data and port moditications are finished. + void portsInserted(); + +private: + NodeStyle _nodeStyle; +}; + diff --git a/include/nodeDelegateModelRegistry.h b/include/nodeDelegateModelRegistry.h new file mode 100644 index 0000000..ffc4ffb --- /dev/null +++ b/include/nodeDelegateModelRegistry.h @@ -0,0 +1,158 @@ +#pragma once + +#include "nodeData.h" +#include "nodeDelegateModel.h" + +#include + +/// Class uses map for storing models (name, model) +class NodeDelegateModelRegistry +{ +public: + using RegistryItemPtr = QSharedPointer; + using RegistryItemCreator = RegistryItemPtr; + using RegisteredModelCreatorsMap = QMap; + using RegisteredModelsCategoryMap = QMap; + using CategoriesSet = QSet; + + //using RegisteredTypeConvertersMap = std::map; + + NodeDelegateModelRegistry() = default; + ~NodeDelegateModelRegistry() = default; + + NodeDelegateModelRegistry(NodeDelegateModelRegistry const &) = delete; + NodeDelegateModelRegistry(NodeDelegateModelRegistry &&) = default; + + NodeDelegateModelRegistry &operator=(NodeDelegateModelRegistry const &) = delete; + + NodeDelegateModelRegistry &operator=(NodeDelegateModelRegistry &&) = default; + +public: + template + void registerModel(RegistryItemCreator creator, QString const &category = "Nodes") + { + QString const name = computeName(HasStaticMethodName{}, creator); + if (!_registeredItemCreators.count(name)) { + _registeredItemCreators[name] = std::move(creator); + _categories.insert(category); + _registeredModelsCategory[name] = category; + } + } + + template + void registerModel(QString const &category = "Nodes") + { + RegistryItemCreator creator = []() { return std::make_unique(); }; + registerModel(std::move(creator), category); + } + +#if 0 + template + void + registerModel(RegistryItemCreator creator, + QString const& category = "Nodes") + { + registerModel(std::move(creator), category); + } + + + template + void + registerModel(ModelCreator&& creator, QString const& category = "Nodes") + { + using ModelType = compute_model_type_t; + registerModel(std::forward(creator), category); + } + + + template + void + registerModel(QString const& category, ModelCreator&& creator) + { + registerModel(std::forward(creator), category); + } + + + void + registerTypeConverter(TypeConverterId const& id, + TypeConverter typeConverter) + { + _registeredTypeConverters[id] = std::move(typeConverter); + } + +#endif + + QSharedPointer create(QString const &modelName); + + RegisteredModelCreatorsMap const ®isteredModelCreators() const; + + RegisteredModelsCategoryMap const ®isteredModelsCategoryAssociation() const; + + CategoriesSet const &categories() const; + +#if 0 + TypeConverter + getTypeConverter(NodeDataType const& d1, + NodeDataType const& d2) const; +#endif + +private: + RegisteredModelsCategoryMap _registeredModelsCategory; + + CategoriesSet _categories; + + RegisteredModelCreatorsMap _registeredItemCreators; + +#if 0 + RegisteredTypeConvertersMap _registeredTypeConverters; +#endif + +private: + // If the registered ModelType class has the static member method + // `static QString Name();`, use it. Otherwise use the non-static + // method: `virtual QString name() const;` + template + struct HasStaticMethodName : std::false_type + {}; + + template + struct HasStaticMethodName< + T, + typename std::enable_if::value>::type> + : std::true_type + {}; + + template + static QString computeName(std::true_type, RegistryItemCreator const &) + { + return ModelType::Name(); + } + + template + static QString computeName(std::false_type, RegistryItemCreator const &creator) + { + return creator->name(); + } + + template + struct UnwrapUniquePtr + { + // Assert always fires, but the compiler doesn't know this: + static_assert(!std::is_same::value, + "The ModelCreator must return a std::unique_ptr, where T " + "inherits from NodeDelegateModel"); + }; + + template + struct UnwrapUniquePtr> + { + static_assert(std::is_base_of::value, + "The ModelCreator must return a std::unique_ptr, where T " + "inherits from NodeDelegateModel"); + using type = T; + }; + + template + using compute_model_type_t = typename UnwrapUniquePtr::type; +}; + diff --git a/include/nodeGraphicsObject.h b/include/nodeGraphicsObject.h new file mode 100644 index 0000000..4c24ab8 --- /dev/null +++ b/include/nodeGraphicsObject.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +#include "nodeState.h" + +class QGraphicsProxyWidget; + +class BasicGraphicsScene; +class AbstractGraphModel; + +class NodeGraphicsObject : public QGraphicsObject +{ + Q_OBJECT +public: + // Needed for qgraphicsitem_cast + enum { Type = UserType + 1 }; + + int type() const override { return Type; } + +public: + NodeGraphicsObject(BasicGraphicsScene &scene, NodeId node); + + ~NodeGraphicsObject() override = default; + +public: + AbstractGraphModel &graphModel() const; + + BasicGraphicsScene *nodeScene() const; + + NodeId nodeId() { return _nodeId; } + + NodeId nodeId() const { return _nodeId; } + + NodeState &nodeState() { return _nodeState; } + + NodeState const &nodeState() const { return _nodeState; } + + QRectF boundingRect() const override; + + void setGeometryChanged(); + + /// Visits all attached connections and corrects + /// their corresponding end points. + void moveConnections() const; + + /// Repaints the node once with reacting ports. + void reactToConnection(ConnectionGraphicsObject const *cgo); + +protected: + void paint(QPainter *painter, + QStyleOptionGraphicsItem const *option, + QWidget *widget = 0) override; + + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; + + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + + void hoverMoveEvent(QGraphicsSceneHoverEvent *) override; + + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + + void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; + +private: + void embedQWidget(); + + void setLockedState(); + +private: + NodeId _nodeId; + + AbstractGraphModel &_graphModel; + + NodeState _nodeState; + + // either nullptr or owned by parent QGraphicsItem + QGraphicsProxyWidget *_proxyWidget; +}; diff --git a/include/nodeState.h b/include/nodeState.h new file mode 100644 index 0000000..abb43f3 --- /dev/null +++ b/include/nodeState.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#include "global.h" +#include "nodeData.h" + + +class ConnectionGraphicsObject; +class NodeGraphicsObject; + +/// Stores bool for hovering connections and resizing flag. +class NodeState +{ +public: + NodeState(NodeGraphicsObject &ngo); + +public: + bool hovered() const { return _hovered; } + + void setHovered(bool hovered = true) { _hovered = hovered; } + + void setResizing(bool resizing); + + bool resizing() const; + + ConnectionGraphicsObject const *connectionForReaction() const; + + void storeConnectionForReaction(ConnectionGraphicsObject const *cgo); + + void resetConnectionForReaction(); + +private: + NodeGraphicsObject &_ngo; + + bool _hovered; + + bool _resizing; + + // QPointer tracks the QObject inside and is automatically cleared + // when the object is destroyed. + QPointer _connectionForReaction; +}; diff --git a/include/nodeStyle.h b/include/nodeStyle.h new file mode 100644 index 0000000..7db8c81 --- /dev/null +++ b/include/nodeStyle.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#include "style.h" + +class NodeStyle : public Style +{ +public: + NodeStyle(); + + NodeStyle(QString jsonText); + + NodeStyle(QJsonObject const &json); + + virtual ~NodeStyle() = default; + +public: + static void setNodeStyle(QString jsonText); + +public: + void loadJson(QJsonObject const &json) override; + + QJsonObject toJson() const override; + +public: + QColor NormalBoundaryColor; + QColor SelectedBoundaryColor; + QColor GradientColor0; + QColor GradientColor1; + QColor GradientColor2; + QColor GradientColor3; + QColor ShadowColor; + QColor FontColor; + QColor FontColorFaded; + + QColor ConnectionPointColor; + QColor FilledConnectionPointColor; + + QColor WarningColor; + QColor ErrorColor; + + float PenWidth; + float HoveredPenWidth; + + float ConnectionPointDiameter; + + float Opacity; +}; diff --git a/include/operationCommand.h b/include/operationCommand.h new file mode 100644 index 0000000..2178549 --- /dev/null +++ b/include/operationCommand.h @@ -0,0 +1,81 @@ +/** + *\file operationCommand.h + * + *\brief 用来实现“撤销/重做”的操作指令,继承自QUndoCommand + * + *\author dsc + */ + +#ifndef OPERATIONCOMMAND_H +#define OPERATIONCOMMAND_H + +#include +#include + +class GraphicsItemGroup; + + +class AddItemCommand : public QUndoCommand +{ +public: + explicit AddItemCommand(QGraphicsItem* item, QGraphicsScene* graphicsScene, QUndoCommand* parent = 0); + ~AddItemCommand(); + +public: + void undo() override; + void redo() override; + +private: + QGraphicsItem* m_pItem; + QPointF m_itemPos; + QGraphicsScene* m_pGraphicsScene; +}; + +class DeleteItemCommand : public QUndoCommand +{ +public: + explicit DeleteItemCommand(QGraphicsScene* graphicsScene, QUndoCommand* parent = 0); + ~DeleteItemCommand(); + +public: + void undo() override; + void redo() override; + +private: + QList m_listItem; + QGraphicsScene* m_pGraphicsScene; +}; + +class CreateItemGoupCommand : public QUndoCommand +{ +public: + explicit CreateItemGoupCommand(GraphicsItemGroup* group, QGraphicsScene* graphicsScene, QUndoCommand* parent = 0); + ~CreateItemGoupCommand(); + +public: + void undo() override; + void redo() override; + +private: + QGraphicsItemGroup* m_pGroup; + QGraphicsScene* m_pGraphicsScene; + QList m_listItem; +}; + +class DestroyItemGoupCommand : public QUndoCommand +{ +public: + explicit DestroyItemGoupCommand(GraphicsItemGroup* group, QGraphicsScene* graphicsScene, QUndoCommand* parent = 0); + ~DestroyItemGoupCommand(); + +public: + void undo() override; + void redo() override; + +private: + QGraphicsItemGroup* m_pGroup; + QGraphicsScene* m_pGraphicsScene; + QList m_listItem; +}; + +#endif diff --git a/include/serializable.h b/include/serializable.h new file mode 100644 index 0000000..935c8e0 --- /dev/null +++ b/include/serializable.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class Serializable +{ +public: + virtual ~Serializable() = default; + + virtual QJsonObject save() const { return {}; } + + virtual void load(QJsonObject const & /*p*/) {} +}; diff --git a/include/style.h b/include/style.h new file mode 100644 index 0000000..c6e467c --- /dev/null +++ b/include/style.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class Style // : public QObject +{ + //Q_OBJECT + +public: + virtual ~Style() = default; + +public: + virtual void loadJson(QJsonObject const &json) = 0; + + virtual QJsonObject toJson() const = 0; + + /// Loads from utf-8 byte array. + virtual void loadJsonFromByteArray(QByteArray const &byteArray) + { + auto json = QJsonDocument::fromJson(byteArray).object(); + + loadJson(json); + } + + virtual void loadJsonText(QString jsonText) { loadJsonFromByteArray(jsonText.toUtf8()); } + + virtual void loadJsonFile(QString fileName) + { + QFile file(fileName); + + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "Couldn't open file " << fileName; + + return; + } + + loadJsonFromByteArray(file.readAll()); + } +}; + diff --git a/include/styleCollection.h b/include/styleCollection.h new file mode 100644 index 0000000..5a094b8 --- /dev/null +++ b/include/styleCollection.h @@ -0,0 +1,38 @@ +#pragma once + +#include "connectionStyle.h" +#include "graphicsViewStyle.h" +#include "nodeStyle.h" + +class StyleCollection +{ +public: + static NodeStyle const &nodeStyle(); + + static ConnectionStyle const &connectionStyle(); + + static GraphicsViewStyle const &flowViewStyle(); + +public: + static void setNodeStyle(NodeStyle); + + static void setConnectionStyle(ConnectionStyle); + + static void setGraphicsViewStyle(GraphicsViewStyle); + +private: + StyleCollection() = default; + + StyleCollection(StyleCollection const &) = delete; + + StyleCollection &operator=(StyleCollection const &) = delete; + + static StyleCollection &instance(); + +private: + NodeStyle _nodeStyle; + + ConnectionStyle _connectionStyle; + + GraphicsViewStyle _flowViewStyle; +}; diff --git a/include/toolBox.h b/include/toolBox.h new file mode 100644 index 0000000..5645ec0 --- /dev/null +++ b/include/toolBox.h @@ -0,0 +1,20 @@ +#ifndef TOOLBOX_H +#define TOOLBOX_H + +#include + +class QVBoxLayout; +class ToolBox : public QWidget +{ + Q_OBJECT + +public: + explicit ToolBox(QWidget *parent = nullptr); + ~ToolBox(); + void addWidget(const QString &title, QWidget *widget); + void removeWidget(const QString &title); +private: + QVBoxLayout *m_pContentVBoxLayout; + QMap m_mapWidget; +}; +#endif // TOOLBOX_H diff --git a/include/toolPage.h b/include/toolPage.h new file mode 100644 index 0000000..dcd9be8 --- /dev/null +++ b/include/toolPage.h @@ -0,0 +1,30 @@ +#ifndef TOOLPAGE_H +#define TOOLPAGE_H + +#include + +class QFormLayout; +class QLabel; +class QPushButton; + + +class ToolPage : public QWidget +{ + Q_OBJECT +public: + explicit ToolPage(QWidget *parent = nullptr); + ~ToolPage(); +public slots: + void addWidget(const QString &title, QWidget *widget); + void expand(); + void collapse(); +private slots: + void onPushButtonFoldClicked(); +private: + + bool m_bIsExpanded; + QLabel *m_pLabel; + QPushButton *m_pPushButtonFold; + QWidget *m_pContent; +}; +#endif // TOOLPAGE_H diff --git a/include/undoCommands.h b/include/undoCommands.h new file mode 100644 index 0000000..70eb2a8 --- /dev/null +++ b/include/undoCommands.h @@ -0,0 +1,118 @@ +#pragma once + +#include "global.h" + +#include +#include +#include + +class BasicGraphicsScene; + +class CreateCommand : public QUndoCommand +{ +public: + CreateCommand(BasicGraphicsScene *scene, QString const name, QPointF const &mouseScenePos); + + void undo() override; + void redo() override; + +private: + BasicGraphicsScene *_scene; + NodeId _nodeId; + QJsonObject _sceneJson; +}; + +/** + * Selected scene objects are serialized and then removed from the scene. + * The deleted elements could be restored in `undo`. + */ +class DeleteCommand : public QUndoCommand +{ +public: + DeleteCommand(BasicGraphicsScene *scene); + + void undo() override; + void redo() override; + +private: + BasicGraphicsScene *_scene; + QJsonObject _sceneJson; +}; + +class CopyCommand : public QUndoCommand +{ +public: + CopyCommand(BasicGraphicsScene *scene); +}; + +class PasteCommand : public QUndoCommand +{ +public: + PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos); + + void undo() override; + void redo() override; + +private: + QJsonObject takeSceneJsonFromClipboard(); + QJsonObject makeNewNodeIdsInScene(QJsonObject const &sceneJson); + +private: + BasicGraphicsScene *_scene; + QPointF const &_mouseScenePos; + QJsonObject _newSceneJson; +}; + +class DisconnectCommand : public QUndoCommand +{ +public: + DisconnectCommand(BasicGraphicsScene *scene, ConnectionId const); + + void undo() override; + void redo() override; + +private: + BasicGraphicsScene *_scene; + + ConnectionId _connId; +}; + +class ConnectCommand : public QUndoCommand +{ +public: + ConnectCommand(BasicGraphicsScene *scene, ConnectionId const); + + void undo() override; + void redo() override; + +private: + BasicGraphicsScene *_scene; + + ConnectionId _connId; +}; + +class MoveNodeCommand : public QUndoCommand +{ +public: + MoveNodeCommand(BasicGraphicsScene *scene, QPointF const &diff); + + void undo() override; + void redo() override; + + /** + * A command ID is used in command compression. It must be an integer unique to + * this command's class, or -1 if the command doesn't support compression. + */ + int id() const override; + + /** + * Several sequential movements could be merged into one command. + */ + bool mergeWith(QUndoCommand const *c) override; + +private: + BasicGraphicsScene *_scene; + QSet _selectedNodes; + QPointF _diff; +}; + diff --git a/include/util/baseSelector.h b/include/util/baseSelector.h new file mode 100644 index 0000000..8179601 --- /dev/null +++ b/include/util/baseSelector.h @@ -0,0 +1,74 @@ +/** + *\file baseSelector.h + * + *\brief selector是用来实现对图元进行具体操作的类,例如创建、编辑、旋转、移动等,通过对鼠标的行为动作进行具体的逻辑编写实现操作表达 + * + *\author dsc + */ + +#ifndef BASESELECTOR_H +#define BASESELECTOR_H + +#include +#include "designerScene.h" + +enum SelectorType +{ + ST_base = 0, //基本 + ST_cerating, //创建 + ST_moving, //移动 + ST_editing, //顶点编辑 + ST_scaling, //缩放 + ST_rotation, //旋转 + ST_connecting, //连接 +}; + +enum OperationMode +{ + OM_none = 0, + OM_move, //移动 + OM_create, //创建 + OM_edit, //顶点编辑 + OM_scale, //缩放 + OM_rotate, //旋转 + OM_connect, //连接 +}; + +class BaseSelector : public QObject +{ + Q_OBJECT + +public: + explicit BaseSelector(QObject *parent = 0); + virtual ~BaseSelector(); + +public: + virtual void mousePressEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + + SelectorType getSelectorType() { return m_type; } + //void setOperationMode(OperationMode m) { m_opMode = m; } + OperationMode getOperationMode() { return ms_opMode; } + + void setCursor(DesignerScene*, const QCursor&); + +signals: + void setWorkingSelector(SelectorType); + +protected: + //静态变量,用于不同类型selector间的成员共享 + static OperationMode ms_opMode; + static QPointF ms_ptMouseDown; + static QPointF ms_ptMouseLast; + static double ms_dAngleMouseDownToItem; //鼠标按下时其位置和item中心点形成的夹角 + static int ms_nDragHandle; //当前抓取的控制点 + SelectorType m_type; + +private: + bool m_bHoverOnHandel; //鼠标是否悬停在handel + OperationMode m_opMode; +}; + +#endif diff --git a/include/util/connectingSelector.h b/include/util/connectingSelector.h new file mode 100644 index 0000000..355ea06 --- /dev/null +++ b/include/util/connectingSelector.h @@ -0,0 +1,33 @@ +/** + *\file connectingSelector.h + * + *\brief 用来实现图元连接的selector + * + *\author by + */ + +#ifndef CONNECTINGSELECTOR_H +#define CONNECTINGSELECTOR_H + +#include "baseSelector.h" + +class GraphicsBaseItem; + + +class ConnectingSelector : public BaseSelector +{ + Q_OBJECT + +public: + explicit ConnectingSelector(QObject *parent = 0); + virtual ~ConnectingSelector(); + +public: + void mousePressEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + void mouseMoveEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + void mouseReleaseEvent(QGraphicsSceneMouseEvent*, DesignerScene*); +private: + GraphicsBaseItem* m_pConnectingItem; +}; + +#endif diff --git a/include/util/creatingSelector.h b/include/util/creatingSelector.h new file mode 100644 index 0000000..ae0fa15 --- /dev/null +++ b/include/util/creatingSelector.h @@ -0,0 +1,47 @@ +/** + *\file creatingSelector.h + * + *\brief 用来实现图元创建的selector + * + *\author dsc + */ + +#ifndef CREATINGSELECTOR_H +#define CREATINGSELECTOR_H + +#include "baseSelector.h" +#include "global.h" + + +enum CreatingMethod +{ + CM_drag, //多拽,默认的创建方式 + CM_click //单击点选,如多边形、线段等 +}; + +class GraphicsBaseItem; +class DesignerScene; + +class CreatingSelector : public BaseSelector +{ + Q_OBJECT + +public: + explicit CreatingSelector(QObject *parent = 0); + virtual ~CreatingSelector(); + +public: + virtual void mousePressEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + + void setCreatingItem(GraphicsItemType& type) { m_creatingItemType=type; } + +private: + CreatingMethod m_creatingMethod; + GraphicsItemType m_creatingItemType; + GraphicsBaseItem* m_pCreatingItem; + QPointF m_scalBasePoint; +}; + +#endif diff --git a/include/util/editingSelector.h b/include/util/editingSelector.h new file mode 100644 index 0000000..c7f4ca4 --- /dev/null +++ b/include/util/editingSelector.h @@ -0,0 +1,34 @@ +/** + *\file editingSelector.h + * + *\brief 用来实现图元编辑的selector + * + *\author dsc + */ + +#ifndef EDITINGSELECTOR_H +#define EDITINGSELECTOR_H + +#include "baseSelector.h" +#include "global.h" +#include + + +class EditingSelector : public BaseSelector +{ + Q_OBJECT + +public: + explicit EditingSelector(QObject *parent = 0); + virtual ~EditingSelector(); + +public: + void mousePressEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + void mouseMoveEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + void mouseReleaseEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + +private: + GraphicsBaseItem* m_pEditingItem; +}; + +#endif diff --git a/include/util/movingSelector.h b/include/util/movingSelector.h new file mode 100644 index 0000000..93aefe8 --- /dev/null +++ b/include/util/movingSelector.h @@ -0,0 +1,29 @@ +/** + *\file movingSelector.h + * + *\brief 用来实现图元移动的selector + * + *\author dsc + */ + +#ifndef MOVINGSELECTOR_H +#define MOVINGSELECTOR_H + +#include "baseSelector.h" + +class MovingSelector : public BaseSelector +{ + Q_OBJECT + +public: + explicit MovingSelector(QObject *parent = 0); + virtual ~MovingSelector(); + +public: + void mousePressEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + void mouseMoveEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + void mouseReleaseEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + +}; + +#endif diff --git a/include/util/rotationSelector.h b/include/util/rotationSelector.h new file mode 100644 index 0000000..22c4610 --- /dev/null +++ b/include/util/rotationSelector.h @@ -0,0 +1,28 @@ +/** + *\file rotationSelector.h + * + *\brief 用来实现图元旋转的selector + * + *\author dsc + */ + +#ifndef ROTATIONSELECTOR_H +#define ROTATIONSELECTOR_H + +#include "baseSelector.h" + +class RotationSelector : public BaseSelector +{ + Q_OBJECT + +public: + explicit RotationSelector(QObject *parent = 0); + virtual ~RotationSelector(); + +public: + void mousePressEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + void mouseMoveEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + void mouseReleaseEvent(QGraphicsSceneMouseEvent*, DesignerScene*); +}; + +#endif diff --git a/include/util/scalingSelector.h b/include/util/scalingSelector.h new file mode 100644 index 0000000..e12333f --- /dev/null +++ b/include/util/scalingSelector.h @@ -0,0 +1,30 @@ +/** + *\file scalingSelector.h + * + *\brief 用来实现图元缩放的selector + *\author dsc + */ + +#ifndef SCALINGSELECTOR_H +#define SCALINGSELECTOR_H + +#include "baseSelector.h" + +class ScalingSelector : public BaseSelector +{ + Q_OBJECT + +public: + explicit ScalingSelector(QObject *parent = 0); + virtual ~ScalingSelector(); + +public: + void mousePressEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + void mouseMoveEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + void mouseReleaseEvent(QGraphicsSceneMouseEvent*, DesignerScene*); + +private: + QPointF m_scalBasePoint; +}; + +#endif diff --git a/include/util/selectorManager.h b/include/util/selectorManager.h new file mode 100644 index 0000000..4e43a57 --- /dev/null +++ b/include/util/selectorManager.h @@ -0,0 +1,39 @@ +/** + *\file selectorManager.h + * + *\brief 所有的selector管理类,采用单例模式 + * 每个cavas实例一个selector,根据具体要实现的内容进行创建和选择 + *\author by 20241113 + */ + +#ifndef SELECTORMANAGER_H +#define SELECTORMANAGER_H + +#include +#include "baseSelector.h" +#include "global.h" + + +class SelectorManager : public QObject +{ + Q_OBJECT + +public: + SelectorManager(QObject *parent = 0); + ~SelectorManager(); + +public: + void setWorkingSelector(SelectorType s) { m_curSelector=s; } + BaseSelector* getWorkingSelector(); //根据操作方式获取selector + + void setDrawGraphicsItem(GraphicsItemType); + +public slots: + void onSignal_setWorkingSelector(SelectorType); + +private: + SelectorType m_curSelector; + QVector m_vecSelectors; +}; + +#endif diff --git a/resource/DiagramDesigner.qrc b/resource/DiagramDesigner.qrc new file mode 100644 index 0000000..fda9ea1 --- /dev/null +++ b/resource/DiagramDesigner.qrc @@ -0,0 +1,17 @@ + + + images/checkerboard.png + images/icon_rotate_lb.png + images/icon_rotate_lt.png + images/icon_rotate_rb.png + images/icon_rotate_rt.png + images/icon_rotate.png + images/icon_up_arrow.png + images/icon_down_arrow.png + images/icon_left_arrow.png + images/element/icons_triangle.png + images/element/svg_bus.svg + images/element/svg_rect.svg + images/element/svg_triangle.svg + + diff --git a/resource/images/checkerboard.png b/resource/images/checkerboard.png new file mode 100644 index 0000000000000000000000000000000000000000..466985575f65ed0cc2e746841b94e8d9771ef920 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|oCO|{#S9GG!XV7ZFl&wkP>{XE z)7O>#KDV5x7<<9)5DuV_hNp{Th{pNkKmY&RGn*BBe;2#vkq?hbQy}jV2CRXal7t9@0$Ui=q97NR?y_A` zt87uE?K0dIuC!K0t<+jnL@HEZ5JHB6K|$c6NCPP%qQWfA7#Qhtc(u9SGc)JHdEkS~ z%=f+LJ3q|4cfjNEcs&281MY?q{LV|x1G7e)BhM%UCdEXifO7JPCt?zF@f27>9&mht zNz6q(j=2Gn{a^JMq__~{R`w* zg*llu-K5MRaa1}Y)ZGwu)uhBS;EmDDXJAc)+G60NQ9BAOCj~BxRXveK{Yg>Zj02|d zS(Y9u$n7^YekN@y9~%w)1}YYzvjg~*mZ_{4N8(BjdRK+sF=A#F@WB}LJ+Lwd%@x4g zq)lbJSiK__bdL(%HezD8FonGq^&bdRSxF40@VPLBTP=>U8Tf9DF=X3RHi)fn!af(p zIA`KCg|Fu^g}G7)ycDK#qYd#cVG0irANz!6swrGc{FDHn zY?;btu_~uXj?-fNOWBHS6Q*!5d<-W=0Wcs;<@RhudW0$5Me^(vrt(qL;##q#t)xe* z=qKp$zx0X6ZAGpN9Y>-&>?!EY + + Layer 1 + + + + \ No newline at end of file diff --git a/resource/images/element/svg_rect.svg b/resource/images/element/svg_rect.svg new file mode 100644 index 0000000..2adf44f --- /dev/null +++ b/resource/images/element/svg_rect.svg @@ -0,0 +1,9 @@ + + + Layer 1 + + + + + + \ No newline at end of file diff --git a/resource/images/element/svg_triangle.svg b/resource/images/element/svg_triangle.svg new file mode 100644 index 0000000..9e43441 --- /dev/null +++ b/resource/images/element/svg_triangle.svg @@ -0,0 +1,9 @@ + + + Layer 1 + + + + + + \ No newline at end of file diff --git a/resource/images/icon_down_arrow.png b/resource/images/icon_down_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..20a2bd1b56fccaaf50946713da0ef6befa164410 GIT binary patch literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9GG!XV7ZFl&wkQ1Fna zi(^Q|oVPa|`I-zwTrchxooSi)=v>LxS4=)Ch1?R8CrPk1ap~s1_q6&~+ds*{agvHB z7@gh1yzYQxM(1F&CiuWtGMhl4!e52IV}Qlgy*CcSA^SJnonGP=5GM>4TGnv KpUXO@geCyBVQAL? literal 0 HcmV?d00001 diff --git a/resource/images/icon_left_arrow.png b/resource/images/icon_left_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..391592bd69f6469687eb28b89aea7b6a6cdc8c2a GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9GG!XV7ZFl&wkNY2y6 zF{EP7+gr9=Ook$^4>wOem^JJA4x_tk>bV7YcC>ml9bK?h;m+r&FW;R#-Q39NIZ4HH z(iiu#1A-A8Ar8z|1!@*G{@M%d4sHJ)newCkq4N~;{^a|$pLe`fp2E#99#{Tw;_eSo zK|dZ&lBwIVR6O9Ne?mm#?`19O8`RHaaYh_^ppw8jd2)lQj~~-C152R5lkLp+Rbx0q pW()bXL^U|S;uiwhsPfTTfAj9KP)RCt`VltE6zFc3u_Ra(x|Lm*f}5eO~;rCG4ek`1Ej9YBH` zQIHDU0=fvY^dk+C-Ppl-Qda)+XA);Vq;bc<)VZ5XfJ@*7cvl1WR4q*}PJu6DS_c|@ zNE^)vSQ)cdU=B<@<`~^lwVgEPKfo1Gn6tLIqm94 literal 0 HcmV?d00001 diff --git a/resource/images/icon_rotate_lb.png b/resource/images/icon_rotate_lb.png new file mode 100644 index 0000000000000000000000000000000000000000..4438b6c95b9d382e5553bfc071f4f577edc4f31f GIT binary patch literal 865 zcmV-n1D^beP)1^@s6$I7^K00009a7bBm000XU z000XU0RWnu7ytkR6G=otRCt{2oB2x>Q545NE{WM*-{NJ&Xa zNr@F3fTeT&5OH7~@EhpHI4gl~Kn*a0VXA@ez%tom7-bRg8Cc<6djzb;7z=@~z?$HC z8_?~kxGjNRrGYsr0 z(s79-1Dq)Go;+DGwi(5lizC3nV6`#JsX%j>cE$y%%m~xY zI0v+NwrOW{>4V zFrSp+LT|~GN{}Ym0o?a&mlc18aS_(w**(B{j8X}F351tR8nSl^I)gFPvEq18c|&`d=ei!H0A|8)o-gbIcqi~u&bbZL5feIqk2$LL>y-l; zQbTRA90Lv%Fs?x<%5iC*_PI_`Rs$1xx`*rJ5Z5PmMNTRN7d#&p`5gT-xaYrdMswO_ z*K@H=JKwBwtQiH}Ud4ePIm}IwR}y-KG8jh@SfO8NA}B=VW3BqgcI#-Az$=teInJ;1 z#EvR8!A(k+xc>9mrcx<}STDP|f?U)lm61ki)smSDw?nR19(H4%G0W8-GOt@O%hc@B zr@)%gsuLZfs^AT%>CQFEgw%Wgaxg_Uu$#4%yR4fz#ys87sid^olEqD6iyCO$9>p}R rz%S_!^ORdJUZtd@q@<*z5L^BLeSl*Z$)eJ^00000NkvXXu0mjf?74#e literal 0 HcmV?d00001 diff --git a/resource/images/icon_rotate_lt.png b/resource/images/icon_rotate_lt.png new file mode 100644 index 0000000000000000000000000000000000000000..c4c15c118373f1ea080a7a20e64a57927ea0a0af GIT binary patch literal 864 zcmV-m1E2hfP)1^@s6$I7^K00009a7bBm000XU z000XU0RWnu7ytkR5=lfsRCt{2nrll`K^%u4Ej3d!%}BB$DeY=hcE3YvM%J5d1f?a> z4T++%Br-E|Ev2?HE2Zxc^uEs)1HS{qVRze=v)7pi4m;=UFlWxp{I4_M@pwEQkH_Qj zcs!A@B)X=GpK0J=zB^F}R09*hS70rUGYISf>VO5Hk|Jdta1Iy&-dWd_ekXN}0at*n z3G_qUDsKnIDH1AyUVW^Q^C94vW;GSdfW5#?AO_3;*MU>Otx(;Iz%>e(4Vp1C%cp^* z5wvLr=QU81;J;B|7X`)^t$=61$`tj?;B1yfFe~RWa0WOGYy&DXe6JCuA3y^IKo#&@ zgJ86Mm>PZXl$QOtMBoB&9Owj20Ox@L;H~PK>k?3#Mjx7hIiQ?8azveIPw-#2`9%JieOVP~!Aq8Z4HJLU$fEOXr_zpDU7}dbfkW2m1)$sXA$f4?&>+Bp&3atZLfj2nH z1hB@s|CA(ZEO{D2@xVUdHjXl+FgqX~QC^}biHCHBaeSO)x6*6@CWKyvqdW~MPUGi0 z%Ll96?y};nkVy@=FP-5eZQ7^_Fj1njoK|eB4_f8MSgq1s}(guyQ#H?~{ zD39SBQ&xF}HiqonhRI{>1Xfc*lq-qbtAwh_C%y#!hE~QeYj=E9I^_?rkrHD;+jkv0 zE|5-mURA0dX}wOsWWu0!xJr_4Z3m{iU0_GECCPLsFiS?C3;EA_U^b0vZGk1 qnGYRKahl3}cfjNEcsw2th0H(n!DAAJL11^@s6$I7^K00009a7bBm000XU z000XU0RWnu7ytkR8c9S!RCt{2n(1p3K^TXhG}hLj)@rTyK|S$kp{Q6D!J?vg)eG@x z(UyveqAgWh6-67F^rOscPL zB$or~^crvwgDeH!d*@EAe!?J0;0w^?U2}n)H5g=6r3YC$tj0U<2MnrJz~^eH$EYpi zSPA7qoHv@qr5C?@W_ajczEjKiPYH?P*{D-G(RayfBrw+zBAS%Sn^bVcWykA*=LHH$ zN@2Gu`C0 zxa}Py(n=ZKcZKl#t36EDB4a4Hl=~(0vNWg$%N;AdTxcX?3Nrzdc%mAdrnw@e71CBF y%d_f9dVYyBjAHng>-1xi=BY7112Hi%Sl}-j8)AqG*9dF?0000FSZ?E`!A955p_VK*qL6+BIt&&QCYN`J`dAvVd(6##Y@4lB#j{;lU59t>V#M#oy z+beH8z4`D+{ZpOIKbiM!p2c6#Y^Np|++*POdB(HzJMKlrKjL)0vsmuX`V9G7e^|Tc z?)|=Yk7UwiftM{89Jf4@H2b)9`MQqygqIxW{#*R-n6=Yo+72gk?*)QtGdxqo)@hl0 zE|i_7cg!PMq)_O`QLA~+_~w87sg=5EbKJu4r3VDIp6H$|7wV(CtUyvS)2y~zbM~>f b`!36G-^w$Ot$cw$&{qteu6{1-oD!Ma#`5y&?tuq7sPrVmb5dY}E(EWo? z%?rUlX*DMV|0LF|5d4!`(;@gLx#psB#ljy-?jH_)bnSgG^-;IPLG9!JzjFNUtv~I* z*W2cu;_Toy@tGev+*7wqzLNGM`0&eFvAft|E?5v*o_MtRCD*OqPaq*rS3j3^P6 portCount - 1) + return; + + if (last < first) + return; + + auto clampedLast = last > portCount - 1?portCount - 1:last; //qMin(last, portCount - 1); + + for (PortIndex portIndex = first; portIndex <= clampedLast; ++portIndex) { + QSet conns = connections(nodeId, portType, portIndex); + + for (auto connectionId : conns) { + deleteConnection(connectionId); + } + } + + std::size_t const nRemovedPorts = clampedLast - first + 1; + + for (PortIndex portIndex = clampedLast + 1; portIndex < portCount; ++portIndex) { + QSet conns = connections(nodeId, portType, portIndex); + + for (auto connectionId : conns) { + // Erases the information about the port on one side; + auto c = makeIncompleteConnectionId(connectionId, portType); + + c = makeCompleteConnectionId(c, nodeId, portIndex - nRemovedPorts); + + _shiftedByDynamicPortsConnections.push_back(c); + + deleteConnection(connectionId); + } + } +} + +void AbstractGraphModel::portsDeleted() +{ + for (auto const connectionId : _shiftedByDynamicPortsConnections) { + addConnection(connectionId); + } + + _shiftedByDynamicPortsConnections.clear(); +} + +void AbstractGraphModel::portsAboutToBeInserted(NodeId const nodeId, + PortType const portType, + PortIndex const first, + PortIndex const last) +{ + _shiftedByDynamicPortsConnections.clear(); + + auto portCountRole = portType == PortType::In ? NodeRole::InPortCount : NodeRole::OutPortCount; + + unsigned int portCount = nodeData(nodeId, portCountRole).toUInt(); + + if (first > portCount) + return; + + if (last < first) + return; + + std::size_t const nNewPorts = last - first + 1; + + for (PortIndex portIndex = first; portIndex < portCount; ++portIndex) { + QSet conns = connections(nodeId, portType, portIndex); + + for (auto connectionId : conns) { + // Erases the information about the port on one side; + auto c = makeIncompleteConnectionId(connectionId, portType); + + c = makeCompleteConnectionId(c, nodeId, portIndex + nNewPorts); + + _shiftedByDynamicPortsConnections.push_back(c); + + deleteConnection(connectionId); + } + } +} + +void AbstractGraphModel::portsInserted() +{ + for (auto const connectionId : _shiftedByDynamicPortsConnections) { + addConnection(connectionId); + } + + _shiftedByDynamicPortsConnections.clear(); +} diff --git a/source/abstractNodeGeometry.cpp b/source/abstractNodeGeometry.cpp new file mode 100644 index 0000000..007f13e --- /dev/null +++ b/source/abstractNodeGeometry.cpp @@ -0,0 +1,75 @@ +#include "abstractNodeGeometry.h" + +#include "abstractGraphModel.h" +#include "styleCollection.h" + +#include + +#include + + +AbstractNodeGeometry::AbstractNodeGeometry(AbstractGraphModel &graphModel) + : _graphModel(graphModel) +{ + // +} + +QRectF AbstractNodeGeometry::boundingRect(NodeId const nodeId) const +{ + QSize s = size(nodeId); + + double ratio = 0.20; + + int widthMargin = s.width() * ratio; + int heightMargin = s.height() * ratio; + + QMargins margins(widthMargin, heightMargin, widthMargin, heightMargin); + + QRectF r(QPointF(0, 0), s); + + return r.marginsAdded(margins); +} + +QPointF AbstractNodeGeometry::portScenePosition(NodeId const nodeId, + PortType const portType, + PortIndex const index, + QTransform const &t) const +{ + QPointF result = portPosition(nodeId, portType, index); + + return t.map(result); +} + +PortIndex AbstractNodeGeometry::checkPortHit(NodeId const nodeId, + PortType const portType, + QPointF const nodePoint) const +{ + auto const &nodeStyle = StyleCollection::nodeStyle(); + + PortIndex result = InvalidPortIndex; + + if (portType == PortType::None) + return result; + + double const tolerance = 2.0 * nodeStyle.ConnectionPointDiameter; + + size_t const n = _graphModel.nodeData(nodeId, + (portType == PortType::Out) + ? NodeRole::OutPortCount + : NodeRole::InPortCount); + + for (unsigned int portIndex = 0; portIndex < n; ++portIndex) { + auto pp = portPosition(nodeId, portType, portIndex); + + QPointF p = pp - nodePoint; + auto distance = std::sqrt(QPointF::dotProduct(p, p)); + + if (distance < tolerance) { + result = portIndex; + break; + } + } + + return result; +} + diff --git a/source/basicGraphicsScene.cpp b/source/basicGraphicsScene.cpp new file mode 100644 index 0000000..d52ce0f --- /dev/null +++ b/source/basicGraphicsScene.cpp @@ -0,0 +1,298 @@ +#include "basicGraphicsScene.h" + +#include "abstractNodeGeometry.h" +#include "connectionGraphicsObject.h" +#include "connectionIdUtils.h" +#include "defaultHorizontalNodeGeometry.h" +#include "defaultNodePainter.h" +#include "defaultVerticalNodeGeometry.h" +//#include "GraphicsView.hpp" +#include "nodeGraphicsObject.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +BasicGraphicsScene::BasicGraphicsScene(AbstractGraphModel &graphModel, QObject *parent) + : QGraphicsScene(parent) + , _graphModel(graphModel) + , _nodeGeometry(QSharedPointer(new DefaultHorizontalNodeGeometry(_graphModel))) + , _nodePainter(QSharedPointer()) + , _nodeDrag(false) + , _undoStack(new QUndoStack(this)) + , _orientation(Qt::Horizontal) +{ + setItemIndexMethod(QGraphicsScene::NoIndex); + + connect(&_graphModel, + &AbstractGraphModel::connectionCreated, + this, + &BasicGraphicsScene::onConnectionCreated); + + connect(&_graphModel, + &AbstractGraphModel::connectionDeleted, + this, + &BasicGraphicsScene::onConnectionDeleted); + + connect(&_graphModel, + &AbstractGraphModel::nodeCreated, + this, + &BasicGraphicsScene::onNodeCreated); + + connect(&_graphModel, + &AbstractGraphModel::nodeDeleted, + this, + &BasicGraphicsScene::onNodeDeleted); + + connect(&_graphModel, + &AbstractGraphModel::nodePositionUpdated, + this, + &BasicGraphicsScene::onNodePositionUpdated); + + connect(&_graphModel, + &AbstractGraphModel::nodeUpdated, + this, + &BasicGraphicsScene::onNodeUpdated); + + connect(this, &BasicGraphicsScene::nodeClicked, this, &BasicGraphicsScene::onNodeClicked); + + connect(&_graphModel, &AbstractGraphModel::modelReset, this, &BasicGraphicsScene::onModelReset); + + traverseGraphAndPopulateGraphicsObjects(); +} + +BasicGraphicsScene::~BasicGraphicsScene() = default; + +AbstractGraphModel const &BasicGraphicsScene::graphModel() const +{ + return _graphModel; +} + +AbstractGraphModel &BasicGraphicsScene::graphModel() +{ + return _graphModel; +} + +AbstractNodeGeometry &BasicGraphicsScene::nodeGeometry() +{ + return *_nodeGeometry; +} + +AbstractNodePainter &BasicGraphicsScene::nodePainter() +{ + return *_nodePainter; +} + +void BasicGraphicsScene::setNodePainter(QSharedPointer newPainter) +{ + _nodePainter = std::move(newPainter); +} + +QUndoStack &BasicGraphicsScene::undoStack() +{ + return *_undoStack; +} + +QSharedPointer const &BasicGraphicsScene::makeDraftConnection( + ConnectionId const incompleteConnectionId) +{ + _draftConnection = QSharedPointer(new ConnectionGraphicsObject(*this, incompleteConnectionId)); + + _draftConnection->grabMouse(); + + return _draftConnection; +} + +void BasicGraphicsScene::resetDraftConnection() +{ + _draftConnection.reset(); +} + +void BasicGraphicsScene::clearScene() +{ + auto const &allNodeIds = graphModel().allNodeIds(); + + for (auto nodeId : allNodeIds) { + graphModel().deleteNode(nodeId); + } +} + +NodeGraphicsObject *BasicGraphicsScene::nodeGraphicsObject(NodeId nodeId) +{ + NodeGraphicsObject *ngo = nullptr; + auto it = _nodeGraphicsObjects.find(nodeId); + if (it != _nodeGraphicsObjects.end()) { + ngo = it->data(); + } + + return ngo; +} + +ConnectionGraphicsObject *BasicGraphicsScene::connectionGraphicsObject(ConnectionId connectionId) +{ + ConnectionGraphicsObject *cgo = nullptr; + auto it = _connectionGraphicsObjects.find(connectionId); + if (it != _connectionGraphicsObjects.end()) { + cgo = it->data(); + } + + return cgo; +} + +void BasicGraphicsScene::setOrientation(Qt::Orientation const orientation) +{ + if (_orientation != orientation) { + _orientation = orientation; + + switch (_orientation) { + case Qt::Horizontal: + _nodeGeometry = QSharedPointer(new DefaultHorizontalNodeGeometry(_graphModel)); + break; + + case Qt::Vertical: + _nodeGeometry = QSharedPointer(new DefaultVerticalNodeGeometry(_graphModel)); + break; + } + + onModelReset(); + } +} + +QMenu *BasicGraphicsScene::createSceneMenu(QPointF const scenePos) +{ + Q_UNUSED(scenePos); + return nullptr; +} + +void BasicGraphicsScene::traverseGraphAndPopulateGraphicsObjects() +{ + auto allNodeIds = _graphModel.allNodeIds(); + + // First create all the nodes. + for (NodeId const nodeId : allNodeIds) { + _nodeGraphicsObjects[nodeId] = QSharedPointer(new NodeGraphicsObject(*this, nodeId)); + } + + // Then for each node check output connections and insert them. + for (NodeId const nodeId : allNodeIds) { + auto nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); + + for (PortIndex index = 0; index < nOutPorts; ++index) { + auto const &outConnectionIds = _graphModel.connections(nodeId, PortType::Out, index); + + for (auto cid : outConnectionIds) { + _connectionGraphicsObjects[cid] = QSharedPointer(new ConnectionGraphicsObject(*this,cid)); + } + } + } +} + +void BasicGraphicsScene::updateAttachedNodes(ConnectionId const connectionId, + PortType const portType) +{ + auto node = nodeGraphicsObject(getNodeId(portType, connectionId)); + + if (node) { + node->update(); + } +} + +void BasicGraphicsScene::onConnectionDeleted(ConnectionId const connectionId) +{ + auto it = _connectionGraphicsObjects.find(connectionId); + if (it != _connectionGraphicsObjects.end()) { + _connectionGraphicsObjects.erase(it); + } + + // TODO: do we need it? + if (_draftConnection && _draftConnection->connectionId() == connectionId) { + _draftConnection.reset(); + } + + updateAttachedNodes(connectionId, PortType::Out); + updateAttachedNodes(connectionId, PortType::In); + + Q_EMIT modified(this); +} + +void BasicGraphicsScene::onConnectionCreated(ConnectionId const connectionId) +{ + _connectionGraphicsObjects[connectionId] + = QSharedPointer(new ConnectionGraphicsObject(*this, connectionId)); + + updateAttachedNodes(connectionId, PortType::Out); + updateAttachedNodes(connectionId, PortType::In); + + Q_EMIT modified(this); +} + +void BasicGraphicsScene::onNodeDeleted(NodeId const nodeId) +{ + auto it = _nodeGraphicsObjects.find(nodeId); + if (it != _nodeGraphicsObjects.end()) { + _nodeGraphicsObjects.erase(it); + + Q_EMIT modified(this); + } +} + +void BasicGraphicsScene::onNodeCreated(NodeId const nodeId) +{ + _nodeGraphicsObjects[nodeId] = QSharedPointer(new NodeGraphicsObject(*this, nodeId)); + + Q_EMIT modified(this); +} + +void BasicGraphicsScene::onNodePositionUpdated(NodeId const nodeId) +{ + auto node = nodeGraphicsObject(nodeId); + if (node) { + node->setPos(_graphModel.nodeData(nodeId, NodeRole::Position).value()); + node->update(); + _nodeDrag = true; + } +} + +void BasicGraphicsScene::onNodeUpdated(NodeId const nodeId) +{ + auto node = nodeGraphicsObject(nodeId); + + if (node) { + node->setGeometryChanged(); + + _nodeGeometry->recomputeSize(nodeId); + + node->update(); + node->moveConnections(); + } +} + +void BasicGraphicsScene::onNodeClicked(NodeId const nodeId) +{ + if (_nodeDrag) { + Q_EMIT nodeMoved(nodeId, _graphModel.nodeData(nodeId, NodeRole::Position).value()); + Q_EMIT modified(this); + } + _nodeDrag = false; +} + +void BasicGraphicsScene::onModelReset() +{ + _connectionGraphicsObjects.clear(); + _nodeGraphicsObjects.clear(); + + clear(); + + traverseGraphAndPopulateGraphicsObjects(); +} diff --git a/source/connectionGraphicsObject.cpp b/source/connectionGraphicsObject.cpp new file mode 100644 index 0000000..36830a3 --- /dev/null +++ b/source/connectionGraphicsObject.cpp @@ -0,0 +1,378 @@ +#include "connectionGraphicsObject.h" + +#include "abstractGraphModel.h" +#include "abstractNodeGeometry.h" +#include "basicGraphicsScene.h" +#include "connectionIdUtils.h" +#include "connectionPainter.h" +#include "connectionState.h" +#include "connectionStyle.h" +#include "nodeConnectionInteraction.h" +#include "nodeGraphicsObject.h" +#include "styleCollection.h" +#include "locateNode.h" + +#include +#include +#include +#include +#include + +#include + +#include + + +ConnectionGraphicsObject::ConnectionGraphicsObject(BasicGraphicsScene &scene, + ConnectionId const connectionId) + : _connectionId(connectionId) + , _graphModel(scene.graphModel()) + , _connectionState(*this) + , _out{0, 0} + , _in{0, 0} +{ + scene.addItem(this); + + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsFocusable, true); + setFlag(QGraphicsItem::ItemIsSelectable, true); + + setAcceptHoverEvents(true); + + //addGraphicsEffect(); + + setZValue(-1.0); + + initializePosition(); +} + +void ConnectionGraphicsObject::initializePosition() +{ + // This function is only called when the ConnectionGraphicsObject + // is newly created. At this moment both end coordinates are (0, 0) + // in Connection G.O. coordinates. The position of the whole + // Connection G. O. in scene coordinate system is also (0, 0). + // By moving the whole object to the Node Port position + // we position both connection ends correctly. + + if (_connectionState.requiredPort() != PortType::None) { + PortType attachedPort = oppositePort(_connectionState.requiredPort()); + + PortIndex portIndex = getPortIndex(attachedPort, _connectionId); + NodeId nodeId = getNodeId(attachedPort, _connectionId); + + NodeGraphicsObject *ngo = nodeScene()->nodeGraphicsObject(nodeId); + + if (ngo) { + QTransform nodeSceneTransform = ngo->sceneTransform(); + + AbstractNodeGeometry &geometry = nodeScene()->nodeGeometry(); + + QPointF pos = geometry.portScenePosition(nodeId, + attachedPort, + portIndex, + nodeSceneTransform); + + this->setPos(pos); + } + } + + move(); +} + +AbstractGraphModel &ConnectionGraphicsObject::graphModel() const +{ + return _graphModel; +} + +BasicGraphicsScene *ConnectionGraphicsObject::nodeScene() const +{ + return dynamic_cast(scene()); +} + +ConnectionId const &ConnectionGraphicsObject::connectionId() const +{ + return _connectionId; +} + +QRectF ConnectionGraphicsObject::boundingRect() const +{ + auto points = pointsC1C2(); + + // `normalized()` fixes inverted rects. + QRectF basicRect = QRectF(_out, _in).normalized(); + + QRectF c1c2Rect = QRectF(points.first, points.second).normalized(); + + QRectF commonRect = basicRect.united(c1c2Rect); + + auto const &connectionStyle = StyleCollection::connectionStyle(); + float const diam = connectionStyle.pointDiameter(); + QPointF const cornerOffset(diam, diam); + + // Expand rect by port circle diameter + commonRect.setTopLeft(commonRect.topLeft() - cornerOffset); + commonRect.setBottomRight(commonRect.bottomRight() + 2 * cornerOffset); + + return commonRect; +} + +QPainterPath ConnectionGraphicsObject::shape() const +{ +#ifdef DEBUG_DRAWING + + //QPainterPath path; + + //path.addRect(boundingRect()); + //return path; + +#else + return ConnectionPainter::getPainterStroke(*this); +#endif +} + +QPointF const &ConnectionGraphicsObject::endPoint(PortType portType) const +{ + Q_ASSERT(portType != PortType::None); + + return (portType == PortType::Out ? _out : _in); +} + +void ConnectionGraphicsObject::setEndPoint(PortType portType, QPointF const &point) +{ + if (portType == PortType::In) + _in = point; + else + _out = point; +} + +void ConnectionGraphicsObject::move() +{ + auto moveEnd = [this](ConnectionId cId, PortType portType) { + NodeId nodeId = getNodeId(portType, cId); + + if (nodeId == InvalidNodeId) + return; + + NodeGraphicsObject *ngo = nodeScene()->nodeGraphicsObject(nodeId); + + if (ngo) { + AbstractNodeGeometry &geometry = nodeScene()->nodeGeometry(); + + QPointF scenePos = geometry.portScenePosition(nodeId, + portType, + getPortIndex(portType, cId), + ngo->sceneTransform()); + + QPointF connectionPos = sceneTransform().inverted().map(scenePos); + + setEndPoint(portType, connectionPos); + } + }; + + moveEnd(_connectionId, PortType::Out); + moveEnd(_connectionId, PortType::In); + + prepareGeometryChange(); + + update(); +} + +ConnectionState const &ConnectionGraphicsObject::connectionState() const +{ + return _connectionState; +} + +ConnectionState &ConnectionGraphicsObject::connectionState() +{ + return _connectionState; +} + +void ConnectionGraphicsObject::paint(QPainter *painter, + QStyleOptionGraphicsItem const *option, + QWidget *) +{ + if (!scene()) + return; + + painter->setClipRect(option->exposedRect); + + ConnectionPainter::paint(painter, *this); +} + +void ConnectionGraphicsObject::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem::mousePressEvent(event); +} + +void ConnectionGraphicsObject::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + prepareGeometryChange(); + + auto view = static_cast(event->widget()); + auto ngo = locateNodeAt(event->scenePos(), *nodeScene(), view->transform()); + if (ngo) { + ngo->reactToConnection(this); + + _connectionState.setLastHoveredNode(ngo->nodeId()); + } else { + _connectionState.resetLastHoveredNode(); + } + + //------------------- + + auto requiredPort = _connectionState.requiredPort(); + + if (requiredPort != PortType::None) { + setEndPoint(requiredPort, event->pos()); + } + + //------------------- + + update(); + + event->accept(); +} + +void ConnectionGraphicsObject::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem::mouseReleaseEvent(event); + + ungrabMouse(); + event->accept(); + + auto view = static_cast(event->widget()); + + Q_ASSERT(view); + + auto ngo = locateNodeAt(event->scenePos(), *nodeScene(), view->transform()); + + bool wasConnected = false; + + if (ngo) { + NodeConnectionInteraction interaction(*ngo, *this, *nodeScene()); + + wasConnected = interaction.tryConnect(); + } + + // If connection attempt was unsuccessful + if (!wasConnected) { + // Resulting unique_ptr is not used and automatically deleted. + nodeScene()->resetDraftConnection(); + } +} + +void ConnectionGraphicsObject::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + _connectionState.setHovered(true); + + update(); + + // Signal + nodeScene()->connectionHovered(connectionId(), event->screenPos()); + + event->accept(); +} + +void ConnectionGraphicsObject::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + _connectionState.setHovered(false); + + update(); + + // Signal + nodeScene()->connectionHoverLeft(connectionId()); + + event->accept(); +} + +std::pair ConnectionGraphicsObject::pointsC1C2() const +{ + switch (nodeScene()->orientation()) { + case Qt::Horizontal: + return pointsC1C2Horizontal(); + break; + + case Qt::Vertical: + return pointsC1C2Vertical(); + break; + } + + throw std::logic_error("Unreachable code after switch statement"); +} + +void ConnectionGraphicsObject::addGraphicsEffect() +{ + auto effect = new QGraphicsBlurEffect; + + effect->setBlurRadius(5); + setGraphicsEffect(effect); + + //auto effect = new QGraphicsDropShadowEffect; + //auto effect = new ConnectionBlurEffect(this); + //effect->setOffset(4, 4); + //effect->setColor(QColor(Qt::gray).darker(800)); +} + +std::pair ConnectionGraphicsObject::pointsC1C2Horizontal() const +{ + double const defaultOffset = 200; + + double xDistance = _in.x() - _out.x(); + + double horizontalOffset = qMin(defaultOffset, std::abs(xDistance)); + + double verticalOffset = 0; + + double ratioX = 0.5; + + if (xDistance <= 0) { + double yDistance = _in.y() - _out.y() + 20; + + double vector = yDistance < 0 ? -1.0 : 1.0; + + verticalOffset = qMin(defaultOffset, std::abs(yDistance)) * vector; + + ratioX = 1.0; + } + + horizontalOffset *= ratioX; + + QPointF c1(_out.x() + horizontalOffset, _out.y() + verticalOffset); + + QPointF c2(_in.x() - horizontalOffset, _in.y() - verticalOffset); + + return std::make_pair(c1, c2); +} + +std::pair ConnectionGraphicsObject::pointsC1C2Vertical() const +{ + double const defaultOffset = 200; + + double yDistance = _in.y() - _out.y(); + + double verticalOffset = qMin(defaultOffset, std::abs(yDistance)); + + double horizontalOffset = 0; + + double ratioY = 0.5; + + if (yDistance <= 0) { + double xDistance = _in.x() - _out.x() + 20; + + double vector = xDistance < 0 ? -1.0 : 1.0; + + horizontalOffset = qMin(defaultOffset, std::abs(xDistance)) * vector; + + ratioY = 1.0; + } + + verticalOffset *= ratioY; + + QPointF c1(_out.x() + horizontalOffset, _out.y() + verticalOffset); + + QPointF c2(_in.x() - horizontalOffset, _in.y() - verticalOffset); + + return std::make_pair(c1, c2); +} + diff --git a/source/connectionPainter.cpp b/source/connectionPainter.cpp new file mode 100644 index 0000000..8e29411 --- /dev/null +++ b/source/connectionPainter.cpp @@ -0,0 +1,251 @@ +#include "connectionPainter.h" + +#include + +#include "abstractGraphModel.h" +#include "connectionGraphicsObject.h" +#include "connectionState.h" +#include "global.h" +#include "nodeData.h" +#include "styleCollection.h" + + +static QPainterPath cubicPath(ConnectionGraphicsObject const &connection) +{ + QPointF const &in = connection.endPoint(PortType::In); + QPointF const &out = connection.endPoint(PortType::Out); + + auto const c1c2 = connection.pointsC1C2(); + + // cubic spline + QPainterPath cubic(out); + + cubic.cubicTo(c1c2.first, c1c2.second, in); + + return cubic; +} + +QPainterPath ConnectionPainter::getPainterStroke(ConnectionGraphicsObject const &connection) +{ + auto cubic = cubicPath(connection); + + QPointF const &out = connection.endPoint(PortType::Out); + QPainterPath result(out); + + unsigned segments = 20; + + for (auto i = 0ul; i < segments; ++i) { + double ratio = double(i + 1) / segments; + result.lineTo(cubic.pointAtPercent(ratio)); + } + + QPainterPathStroker stroker; + stroker.setWidth(10.0); + + return stroker.createStroke(result); +} + +#ifdef NODE_DEBUG_DRAWING +static void debugDrawing(QPainter *painter, ConnectionGraphicsObject const &cgo) +{ + Q_UNUSED(painter); + + { + QPointF const &in = cgo.endPoint(PortType::In); + QPointF const &out = cgo.endPoint(PortType::Out); + + auto const points = cgo.pointsC1C2(); + + painter->setPen(Qt::red); + painter->setBrush(Qt::red); + + painter->drawLine(QLineF(out, points.first)); + painter->drawLine(QLineF(points.first, points.second)); + painter->drawLine(QLineF(points.second, in)); + painter->drawEllipse(points.first, 3, 3); + painter->drawEllipse(points.second, 3, 3); + + painter->setBrush(Qt::NoBrush); + painter->drawPath(cubicPath(cgo)); + } + + { + painter->setPen(Qt::yellow); + painter->drawRect(cgo.boundingRect()); + } +} + +#endif + +static void drawSketchLine(QPainter *painter, ConnectionGraphicsObject const &cgo) +{ + ConnectionState const &state = cgo.connectionState(); + + if (state.requiresPort()) { + auto const &connectionStyle = StyleCollection::connectionStyle(); + + QPen pen; + pen.setWidth(connectionStyle.constructionLineWidth()); + pen.setColor(connectionStyle.constructionColor()); + pen.setStyle(Qt::DashLine); + + painter->setPen(pen); + painter->setBrush(Qt::NoBrush); + + auto cubic = cubicPath(cgo); + + // cubic spline + painter->drawPath(cubic); + } +} + +static void drawHoveredOrSelected(QPainter *painter, ConnectionGraphicsObject const &cgo) +{ + bool const hovered = cgo.connectionState().hovered(); + bool const selected = cgo.isSelected(); + + // drawn as a fat background + if (hovered || selected) { + auto const &connectionStyle = StyleCollection::connectionStyle(); + + double const lineWidth = connectionStyle.lineWidth(); + + QPen pen; + pen.setWidth(2 * lineWidth); + pen.setColor(selected ? connectionStyle.selectedHaloColor() + : connectionStyle.hoveredColor()); + + painter->setPen(pen); + painter->setBrush(Qt::NoBrush); + + // cubic spline + auto const cubic = cubicPath(cgo); + painter->drawPath(cubic); + } +} + +static void drawNormalLine(QPainter *painter, ConnectionGraphicsObject const &cgo) +{ + ConnectionState const &state = cgo.connectionState(); + + if (state.requiresPort()) + return; + + // colors + + auto const &connectionStyle = StyleCollection::connectionStyle(); + + QColor normalColorOut = connectionStyle.normalColor(); + QColor normalColorIn = connectionStyle.normalColor(); + QColor selectedColor = connectionStyle.selectedColor(); + + bool useGradientColor = false; + + AbstractGraphModel const &graphModel = cgo.graphModel(); + + if (connectionStyle.useDataDefinedColors()) { + + auto const cId = cgo.connectionId(); + + auto dataTypeOut = graphModel + .portData(cId.outNodeId, + PortType::Out, + cId.outPortIndex, + PortRole::DataType) + .value(); + + auto dataTypeIn + = graphModel.portData(cId.inNodeId, PortType::In, cId.inPortIndex, PortRole::DataType) + .value(); + + useGradientColor = (dataTypeOut.id != dataTypeIn.id); + + normalColorOut = connectionStyle.normalColor(dataTypeOut.id); + normalColorIn = connectionStyle.normalColor(dataTypeIn.id); + selectedColor = normalColorOut.darker(200); + } + + // geometry + + double const lineWidth = connectionStyle.lineWidth(); + + // draw normal line + QPen p; + + p.setWidth(lineWidth); + + bool const selected = cgo.isSelected(); + + auto cubic = cubicPath(cgo); + if (useGradientColor) { + painter->setBrush(Qt::NoBrush); + + QColor cOut = normalColorOut; + if (selected) + cOut = cOut.darker(200); + p.setColor(cOut); + painter->setPen(p); + + unsigned int const segments = 60; + + for (unsigned int i = 0ul; i < segments; ++i) { + double ratioPrev = double(i) / segments; + double ratio = double(i + 1) / segments; + + if (i == segments / 2) { + QColor cIn = normalColorIn; + if (selected) + cIn = cIn.darker(200); + + p.setColor(cIn); + painter->setPen(p); + } + painter->drawLine(cubic.pointAtPercent(ratioPrev), cubic.pointAtPercent(ratio)); + } + + { + QIcon icon(":convert.png"); + + QPixmap pixmap = icon.pixmap(QSize(22, 22)); + painter->drawPixmap(cubic.pointAtPercent(0.50) + - QPoint(pixmap.width() / 2, pixmap.height() / 2), + pixmap); + } + } else { + p.setColor(normalColorOut); + + if (selected) { + p.setColor(selectedColor); + } + + painter->setPen(p); + painter->setBrush(Qt::NoBrush); + + painter->drawPath(cubic); + } +} + +void ConnectionPainter::paint(QPainter *painter, ConnectionGraphicsObject const &cgo) +{ + drawHoveredOrSelected(painter, cgo); + + drawSketchLine(painter, cgo); + + drawNormalLine(painter, cgo); + +#ifdef NODE_DEBUG_DRAWING + debugDrawing(painter, cgo); +#endif + + // draw end points + auto const &connectionStyle = StyleCollection::connectionStyle(); + + double const pointDiameter = connectionStyle.pointDiameter(); + + painter->setPen(connectionStyle.constructionColor()); + painter->setBrush(connectionStyle.constructionColor()); + double const pointRadius = pointDiameter / 2.0; + painter->drawEllipse(cgo.out(), pointRadius, pointRadius); + painter->drawEllipse(cgo.in(), pointRadius, pointRadius); +} + diff --git a/source/connectionState.cpp b/source/connectionState.cpp new file mode 100644 index 0000000..b04cb92 --- /dev/null +++ b/source/connectionState.cpp @@ -0,0 +1,63 @@ +#include "connectionState.h" + +#include +#include + +#include "basicGraphicsScene.h" +#include "connectionGraphicsObject.h" +#include "nodeGraphicsObject.h" + + +ConnectionState::~ConnectionState() +{ + //resetLastHoveredNode(); +} + +PortType ConnectionState::requiredPort() const +{ + PortType t = PortType::None; + + if (_cgo.connectionId().outNodeId == InvalidNodeId) { + t = PortType::Out; + } else if (_cgo.connectionId().inNodeId == InvalidNodeId) { + t = PortType::In; + } + + return t; +} + +bool ConnectionState::requiresPort() const +{ + const ConnectionId &id = _cgo.connectionId(); + return id.outNodeId == InvalidNodeId || id.inNodeId == InvalidNodeId; +} + +bool ConnectionState::hovered() const +{ + return _hovered; +} + +void ConnectionState::setHovered(bool hovered) +{ + _hovered = hovered; +} + +void ConnectionState::setLastHoveredNode(NodeId const nodeId) +{ + _lastHoveredNode = nodeId; +} + +NodeId ConnectionState::lastHoveredNode() const +{ + return _lastHoveredNode; +} + +void ConnectionState::resetLastHoveredNode() +{ + if (_lastHoveredNode != InvalidNodeId) { + auto ngo = _cgo.nodeScene()->nodeGraphicsObject(_lastHoveredNode); + ngo->update(); + } + + _lastHoveredNode = InvalidNodeId; +} diff --git a/source/connectionStyle.cpp b/source/connectionStyle.cpp new file mode 100644 index 0000000..b9cbc23 --- /dev/null +++ b/source/connectionStyle.cpp @@ -0,0 +1,209 @@ +#include "connectionStyle.h" + +#include "styleCollection.h" + +#include +#include +#include +#include + +#include + +//#include + + +inline void initResources() +{ + Q_INIT_RESOURCE(DiagramDesigner); +} + +ConnectionStyle::ConnectionStyle() +{ + // Explicit resources inialization for preventing the static initialization + // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order + initResources(); + + // This configuration is stored inside the compiled unit and is loaded statically + loadJsonFile(":DefaultStyle.json"); +} + +ConnectionStyle::ConnectionStyle(QString jsonText) +{ + loadJsonFile(":DefaultStyle.json"); + loadJsonText(jsonText); +} + +void ConnectionStyle::setConnectionStyle(QString jsonText) +{ + ConnectionStyle style(jsonText); + + StyleCollection::setConnectionStyle(style); +} + +#ifdef STYLE_DEBUG +#define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(v, variable) \ + { \ + if (v.type() == QJsonValue::Undefined || v.type() == QJsonValue::Null) \ + qWarning() << "Undefined value for parameter:" << #variable; \ + } +#else +#define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(v, variable) +#endif + +#define CONNECTION_VALUE_EXISTS(v) \ + (v.type() != QJsonValue::Undefined && v.type() != QJsonValue::Null) + +#define CONNECTION_STYLE_READ_COLOR(values, variable) \ + { \ + auto valueRef = values[#variable]; \ + CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ + if (CONNECTION_VALUE_EXISTS(valueRef)) { \ + if (valueRef.isArray()) { \ + auto colorArray = valueRef.toArray(); \ + std::vector rgb; \ + rgb.reserve(3); \ + for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ + rgb.push_back((*it).toInt()); \ + } \ + variable = QColor(rgb[0], rgb[1], rgb[2]); \ + } else { \ + variable = QColor(valueRef.toString()); \ + } \ + } \ + } + +#define CONNECTION_STYLE_WRITE_COLOR(values, variable) \ + { \ + values[#variable] = variable.name(); \ + } + +#define CONNECTION_STYLE_READ_FLOAT(values, variable) \ + { \ + auto valueRef = values[#variable]; \ + CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ + if (CONNECTION_VALUE_EXISTS(valueRef)) \ + variable = valueRef.toDouble(); \ + } + +#define CONNECTION_STYLE_WRITE_FLOAT(values, variable) \ + { \ + values[#variable] = variable; \ + } + +#define CONNECTION_STYLE_READ_BOOL(values, variable) \ + { \ + auto valueRef = values[#variable]; \ + CONNECTION_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ + if (CONNECTION_VALUE_EXISTS(valueRef)) \ + variable = valueRef.toBool(); \ + } + +#define CONNECTION_STYLE_WRITE_BOOL(values, variable) \ + { \ + values[#variable] = variable; \ + } + +void ConnectionStyle::loadJson(QJsonObject const &json) +{ + QJsonValue nodeStyleValues = json["ConnectionStyle"]; + + QJsonObject obj = nodeStyleValues.toObject(); + + CONNECTION_STYLE_READ_COLOR(obj, ConstructionColor); + CONNECTION_STYLE_READ_COLOR(obj, NormalColor); + CONNECTION_STYLE_READ_COLOR(obj, SelectedColor); + CONNECTION_STYLE_READ_COLOR(obj, SelectedHaloColor); + CONNECTION_STYLE_READ_COLOR(obj, HoveredColor); + + CONNECTION_STYLE_READ_FLOAT(obj, LineWidth); + CONNECTION_STYLE_READ_FLOAT(obj, ConstructionLineWidth); + CONNECTION_STYLE_READ_FLOAT(obj, PointDiameter); + + CONNECTION_STYLE_READ_BOOL(obj, UseDataDefinedColors); +} + +QJsonObject ConnectionStyle::toJson() const +{ + QJsonObject obj; + + CONNECTION_STYLE_WRITE_COLOR(obj, ConstructionColor); + CONNECTION_STYLE_WRITE_COLOR(obj, NormalColor); + CONNECTION_STYLE_WRITE_COLOR(obj, SelectedColor); + CONNECTION_STYLE_WRITE_COLOR(obj, SelectedHaloColor); + CONNECTION_STYLE_WRITE_COLOR(obj, HoveredColor); + + CONNECTION_STYLE_WRITE_FLOAT(obj, LineWidth); + CONNECTION_STYLE_WRITE_FLOAT(obj, ConstructionLineWidth); + CONNECTION_STYLE_WRITE_FLOAT(obj, PointDiameter); + + CONNECTION_STYLE_WRITE_BOOL(obj, UseDataDefinedColors); + + QJsonObject root; + root["ConnectionStyle"] = obj; + + return root; +} + +QColor ConnectionStyle::constructionColor() const +{ + return ConstructionColor; +} + +QColor ConnectionStyle::normalColor() const +{ + return NormalColor; +} + +QColor ConnectionStyle::normalColor(QString typeId) const +{ + std::size_t hash = qHash(typeId); + + /*std::size_t const hue_range = 0xFF; + + std::mt19937 gen(static_cast(hash)); + std::uniform_int_distribution distrib(0, hue_range); + + int hue = distrib(gen); + int sat = 120 + hash % 129;*/ + + QRandomGenerator ran(hash); + int hue = ran.bounded(0,255); + int sat = ran.bounded(120,249); + + return QColor::fromHsl(hue, sat, 160); +} + +QColor ConnectionStyle::selectedColor() const +{ + return SelectedColor; +} + +QColor ConnectionStyle::selectedHaloColor() const +{ + return SelectedHaloColor; +} + +QColor ConnectionStyle::hoveredColor() const +{ + return HoveredColor; +} + +float ConnectionStyle::lineWidth() const +{ + return LineWidth; +} + +float ConnectionStyle::constructionLineWidth() const +{ + return ConstructionLineWidth; +} + +float ConnectionStyle::pointDiameter() const +{ + return PointDiameter; +} + +bool ConnectionStyle::useDataDefinedColors() const +{ + return UseDataDefinedColors; +} diff --git a/source/dataFlowGraphModel.cpp b/source/dataFlowGraphModel.cpp new file mode 100644 index 0000000..13c4baf --- /dev/null +++ b/source/dataFlowGraphModel.cpp @@ -0,0 +1,532 @@ +#include "dataFlowGraphModel.h" + +#include + +//#include + + +DataFlowGraphModel::DataFlowGraphModel(QSharedPointer registry) + : _registry(registry) + , _nextNodeId{0} +{} + +QSet DataFlowGraphModel::allNodeIds() const +{ + QSet nodeIds; + //for_each(_models.begin(), _models.end(), [&nodeIds](auto const &p) { nodeIds.insert(p.first); }); + + for(auto iter = _models.begin();iter != _models.end();++iter) + { + nodeIds.insert(iter.key()); + } + return nodeIds; +} + +QSet DataFlowGraphModel::allConnectionIds(NodeId const nodeId) const +{ + QSet result; + + std::copy_if(_connectivity.begin(), + _connectivity.end(), + std::inserter(result, std::end(result)), + [&nodeId](ConnectionId const &cid) { + return cid.inNodeId == nodeId || cid.outNodeId == nodeId; + }); + + return result; +} + +QSet DataFlowGraphModel::connections(NodeId nodeId, + PortType portType, + PortIndex portIndex) const +{ + QSet result; + + std::copy_if(_connectivity.begin(), + _connectivity.end(), + std::inserter(result, std::end(result)), + [&portType, &portIndex, &nodeId](ConnectionId const &cid) { + return (getNodeId(portType, cid) == nodeId + && getPortIndex(portType, cid) == portIndex); + }); + + return result; +} + +bool DataFlowGraphModel::connectionExists(ConnectionId const connectionId) const +{ + return (_connectivity.find(connectionId) != _connectivity.end()); +} + +NodeId DataFlowGraphModel::addNode(QString const nodeType) +{ + QSharedPointer model = _registry->create(nodeType); + + if (model) { + NodeId newId = newNodeId(); + + connect(model.get(), + &NodeDelegateModel::dataUpdated, + [newId, this](PortIndex const portIndex) { + onOutPortDataUpdated(newId, portIndex); + }); + + connect(model.get(), + &NodeDelegateModel::portsAboutToBeDeleted, + this, + [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { + portsAboutToBeDeleted(newId, portType, first, last); + }); + + connect(model.get(), + &NodeDelegateModel::portsDeleted, + this, + &DataFlowGraphModel::portsDeleted); + + connect(model.get(), + &NodeDelegateModel::portsAboutToBeInserted, + this, + [newId, this](PortType const portType, PortIndex const first, PortIndex const last) { + portsAboutToBeInserted(newId, portType, first, last); + }); + + connect(model.get(), + &NodeDelegateModel::portsInserted, + this, + &DataFlowGraphModel::portsInserted); + + _models[newId] = model; + + Q_EMIT nodeCreated(newId); + + return newId; + } + + return InvalidNodeId; +} + +bool DataFlowGraphModel::connectionPossible(ConnectionId const connectionId) const +{ + auto getDataType = [&](PortType const portType) { + return portData(getNodeId(portType, connectionId), + portType, + getPortIndex(portType, connectionId), + PortRole::DataType) + .value(); + }; + + auto portVacant = [&](PortType const portType) { + NodeId const nodeId = getNodeId(portType, connectionId); + PortIndex const portIndex = getPortIndex(portType, connectionId); + auto const connected = connections(nodeId, portType, portIndex); + + auto policy = portData(nodeId, portType, portIndex, PortRole::ConnectionPolicyRole) + .value(); + + return connected.empty() || (policy == ConnectionPolicy::Many); + }; + + return getDataType(PortType::Out).id == getDataType(PortType::In).id + && portVacant(PortType::Out) && portVacant(PortType::In); +} + +void DataFlowGraphModel::addConnection(ConnectionId const connectionId) +{ + _connectivity.insert(connectionId); + + sendConnectionCreation(connectionId); + + QVariant const portDataToPropagate = portData(connectionId.outNodeId, + PortType::Out, + connectionId.outPortIndex, + PortRole::Data); + + setPortData(connectionId.inNodeId, + PortType::In, + connectionId.inPortIndex, + portDataToPropagate, + PortRole::Data); +} + +void DataFlowGraphModel::sendConnectionCreation(ConnectionId const connectionId) +{ + Q_EMIT connectionCreated(connectionId); + + auto iti = _models.find(connectionId.inNodeId); + auto ito = _models.find(connectionId.outNodeId); + if (iti != _models.end() && ito != _models.end()) { + auto &modeli = *iti; + auto &modelo = *ito; + modeli->inputConnectionCreated(connectionId); + modelo->outputConnectionCreated(connectionId); + } +} + +void DataFlowGraphModel::sendConnectionDeletion(ConnectionId const connectionId) +{ + Q_EMIT connectionDeleted(connectionId); + + auto iti = _models.find(connectionId.inNodeId); + auto ito = _models.find(connectionId.outNodeId); + if (iti != _models.end() && ito != _models.end()) { + auto &modeli = *iti; + auto &modelo = *ito; + modeli->inputConnectionDeleted(connectionId); + modelo->outputConnectionDeleted(connectionId); + } +} + +bool DataFlowGraphModel::nodeExists(NodeId const nodeId) const +{ + return (_models.find(nodeId) != _models.end()); +} + +QVariant DataFlowGraphModel::nodeData(NodeId nodeId, NodeRole role) const +{ + QVariant result; + + auto it = _models.find(nodeId); + if (it == _models.end()) + return result; + + auto &model = *it; + + switch (role) { + case NodeRole::Type: + result = model->name(); + break; + + case NodeRole::Position: + result = _nodeGeometryData[nodeId].pos; + break; + + case NodeRole::Size: + result = _nodeGeometryData[nodeId].size; + break; + + case NodeRole::CaptionVisible: + result = model->captionVisible(); + break; + + case NodeRole::Caption: + result = model->caption(); + break; + + case NodeRole::Style: { + auto style = StyleCollection::nodeStyle(); + result = style.toJson().toVariantMap(); + } break; + + case NodeRole::InternalData: { + QJsonObject nodeJson; + + nodeJson["internal-data"] = _models[nodeId]->save(); + + result = nodeJson.toVariantMap(); + break; + } + + case NodeRole::InPortCount: + result = model->nPorts(PortType::In); + break; + + case NodeRole::OutPortCount: + result = model->nPorts(PortType::Out); + break; + + case NodeRole::Widget: { + auto w = model->embeddedWidget(); + result = QVariant::fromValue(w); + } break; + } + + return result; +} + +NodeFlags DataFlowGraphModel::nodeFlags(NodeId nodeId) const +{ + auto it = _models.find(nodeId); + + if (it != _models.end() && it->data()->resizable()) + return NodeFlag::Resizable; + + return NodeFlag::NoFlags; +} + +bool DataFlowGraphModel::setNodeData(NodeId nodeId, NodeRole role, QVariant value) +{ + Q_UNUSED(nodeId); + Q_UNUSED(role); + Q_UNUSED(value); + + bool result = false; + + switch (role) { + case NodeRole::Type: + break; + case NodeRole::Position: { + _nodeGeometryData[nodeId].pos = value.value(); + + Q_EMIT nodePositionUpdated(nodeId); + + result = true; + } break; + + case NodeRole::Size: { + _nodeGeometryData[nodeId].size = value.value(); + result = true; + } break; + + case NodeRole::CaptionVisible: + break; + + case NodeRole::Caption: + break; + + case NodeRole::Style: + break; + + case NodeRole::InternalData: + break; + + case NodeRole::InPortCount: + break; + + case NodeRole::OutPortCount: + break; + + case NodeRole::Widget: + break; + } + + return result; +} + +QVariant DataFlowGraphModel::portData(NodeId nodeId, + PortType portType, + PortIndex portIndex, + PortRole role) const +{ + QVariant result; + + auto it = _models.find(nodeId); + if (it == _models.end()) + return result; + + auto &model = *it; + + switch (role) { + case PortRole::Data: + if (portType == PortType::Out) + result = QVariant::fromValue(model->outData(portIndex)); + break; + + case PortRole::DataType: + result = QVariant::fromValue(model->dataType(portType, portIndex)); + break; + + case PortRole::ConnectionPolicyRole: + result = QVariant::fromValue(model->portConnectionPolicy(portType, portIndex)); + break; + + case PortRole::CaptionVisible: + result = model->portCaptionVisible(portType, portIndex); + break; + + case PortRole::Caption: + result = model->portCaption(portType, portIndex); + + break; + } + + return result; +} + +bool DataFlowGraphModel::setPortData( + NodeId nodeId, PortType portType, PortIndex portIndex, QVariant const &value, PortRole role) +{ + Q_UNUSED(nodeId); + + QVariant result; + + auto it = _models.find(nodeId); + if (it == _models.end()) + return false; + + auto &model = *it; + + switch (role) { + case PortRole::Data: + if (portType == PortType::In) { + model->setInData(value.value>(), portIndex); + + // Triggers repainting on the scene. + Q_EMIT inPortDataWasSet(nodeId, portType, portIndex); + } + break; + + default: + break; + } + + return false; +} + +bool DataFlowGraphModel::deleteConnection(ConnectionId const connectionId) +{ + bool disconnected = false; + + auto it = _connectivity.find(connectionId); + + if (it != _connectivity.end()) { + disconnected = true; + + _connectivity.erase(it); + } + + if (disconnected) { + sendConnectionDeletion(connectionId); + + propagateEmptyDataTo(getNodeId(PortType::In, connectionId), + getPortIndex(PortType::In, connectionId)); + } + + return disconnected; +} + +bool DataFlowGraphModel::deleteNode(NodeId const nodeId) +{ + // Delete connections to this node first. + auto connectionIds = allConnectionIds(nodeId); + for (auto &cId : connectionIds) { + deleteConnection(cId); + } + + _nodeGeometryData.take(nodeId); + _models.take(nodeId); + + Q_EMIT nodeDeleted(nodeId); + + return true; +} + +QJsonObject DataFlowGraphModel::saveNode(NodeId const nodeId) const +{ + QJsonObject nodeJson; + + nodeJson["id"] = static_cast(nodeId); + + nodeJson["internal-data"] = _models[nodeId]->save(); + + { + QPointF const pos = nodeData(nodeId, NodeRole::Position).value(); + + QJsonObject posJson; + posJson["x"] = pos.x(); + posJson["y"] = pos.y(); + nodeJson["position"] = posJson; + } + + return nodeJson; +} + +QJsonObject DataFlowGraphModel::save() const +{ + QJsonObject sceneJson; + + QJsonArray nodesJsonArray; + for (auto const nodeId : allNodeIds()) { + nodesJsonArray.append(saveNode(nodeId)); + } + sceneJson["nodes"] = nodesJsonArray; + + QJsonArray connJsonArray; + for (auto const &cid : _connectivity) { + connJsonArray.append(toJson(cid)); + } + sceneJson["connections"] = connJsonArray; + + return sceneJson; +} + +void DataFlowGraphModel::loadNode(QJsonObject const &nodeJson) +{ + // Possibility of the id clash when reading it from json and not generating a + // new value. + // 1. When restoring a scene from a file. + // Conflict is not possible because the scene must be cleared by the time of + // loading. + // 2. When undoing the deletion command. Conflict is not possible + // because all the new ids were created past the removed nodes. + NodeId restoredNodeId = nodeJson["id"].toInt(); + + _nextNodeId = std::max(_nextNodeId, restoredNodeId + 1); + + QJsonObject const internalDataJson = nodeJson["internal-data"].toObject(); + + QString delegateModelName = internalDataJson["model-name"].toString(); + + QSharedPointer model = _registry->create(delegateModelName); + + if (model) { + connect(model.get(), + &NodeDelegateModel::dataUpdated, + [restoredNodeId, this](PortIndex const portIndex) { + onOutPortDataUpdated(restoredNodeId, portIndex); + }); + + _models[restoredNodeId] = std::move(model); + + Q_EMIT nodeCreated(restoredNodeId); + + QJsonObject posJson = nodeJson["position"].toObject(); + QPointF const pos(posJson["x"].toDouble(), posJson["y"].toDouble()); + + setNodeData(restoredNodeId, NodeRole::Position, pos); + + _models[restoredNodeId]->load(internalDataJson); + } else { + throw std::logic_error(std::string("No registered model with name ") + + delegateModelName.toLocal8Bit().data()); + } +} + +void DataFlowGraphModel::load(QJsonObject const &jsonDocument) +{ + QJsonArray nodesJsonArray = jsonDocument["nodes"].toArray(); + + for (QJsonValueRef nodeJson : nodesJsonArray) { + loadNode(nodeJson.toObject()); + } + + QJsonArray connectionJsonArray = jsonDocument["connections"].toArray(); + + for (QJsonValueRef connection : connectionJsonArray) { + QJsonObject connJson = connection.toObject(); + + ConnectionId connId = fromJson(connJson); + + // Restore the connection + addConnection(connId); + } +} + +void DataFlowGraphModel::onOutPortDataUpdated(NodeId const nodeId, PortIndex const portIndex) +{ + QSet const &connected = connections(nodeId, + PortType::Out, + portIndex); + + QVariant const portDataToPropagate = portData(nodeId, PortType::Out, portIndex, PortRole::Data); + + for (auto const &cn : connected) { + setPortData(cn.inNodeId, PortType::In, cn.inPortIndex, portDataToPropagate, PortRole::Data); + } +} + +void DataFlowGraphModel::propagateEmptyDataTo(NodeId const nodeId, PortIndex const portIndex) +{ + QVariant emptyData{}; + + setPortData(nodeId, PortType::In, portIndex, emptyData, PortRole::Data); +} diff --git a/source/defaultHorizontalNodeGeometry.cpp b/source/defaultHorizontalNodeGeometry.cpp new file mode 100644 index 0000000..63150a6 --- /dev/null +++ b/source/defaultHorizontalNodeGeometry.cpp @@ -0,0 +1,236 @@ +#include "defaultHorizontalNodeGeometry.h" + +#include "abstractGraphModel.h" +#include "nodeData.h" + +#include +#include +#include + +DefaultHorizontalNodeGeometry::DefaultHorizontalNodeGeometry(AbstractGraphModel &graphModel) + : AbstractNodeGeometry(graphModel) + , _portSize(20) + , _portSpasing(10) + , _fontMetrics(QFont()) + , _boldFontMetrics(QFont()) +{ + QFont f; + f.setBold(true); + _boldFontMetrics = QFontMetrics(f); + + _portSize = _fontMetrics.height(); +} + +QSize DefaultHorizontalNodeGeometry::size(NodeId const nodeId) const +{ + return _graphModel.nodeData(nodeId, NodeRole::Size); +} + +void DefaultHorizontalNodeGeometry::recomputeSize(NodeId const nodeId) const +{ + unsigned int height = maxVerticalPortsExtent(nodeId); + + if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { + height = std::max(height, static_cast(w->height())); + } + + QRectF const capRect = captionRect(nodeId); + + height += capRect.height(); + + height += _portSpasing; // space above caption + height += _portSpasing; // space below caption + + unsigned int inPortWidth = maxPortsTextAdvance(nodeId, PortType::In); + unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out); + + unsigned int width = inPortWidth + outPortWidth + 4 * _portSpasing; + + if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { + width += w->width(); + } + + width = std::max(width, static_cast(capRect.width()) + 2 * _portSpasing); + + QSize size(width, height); + + _graphModel.setNodeData(nodeId, NodeRole::Size, size); +} + +QPointF DefaultHorizontalNodeGeometry::portPosition(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex) const +{ + unsigned int const step = _portSize + _portSpasing; + + QPointF result; + + double totalHeight = 0.0; + + totalHeight += captionRect(nodeId).height(); + totalHeight += _portSpasing; + + totalHeight += step * portIndex; + totalHeight += step / 2.0; + + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + + switch (portType) { + case PortType::In: { + double x = 0.0; + + result = QPointF(x, totalHeight); + break; + } + + case PortType::Out: { + double x = size.width(); + + result = QPointF(x, totalHeight); + break; + } + + default: + break; + } + + return result; +} + +QPointF DefaultHorizontalNodeGeometry::portTextPosition(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex) const +{ + QPointF p = portPosition(nodeId, portType, portIndex); + + QRectF rect = portTextRect(nodeId, portType, portIndex); + + p.setY(p.y() + rect.height() / 4.0); + + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + + switch (portType) { + case PortType::In: + p.setX(_portSpasing); + break; + + case PortType::Out: + p.setX(size.width() - _portSpasing - rect.width()); + break; + + default: + break; + } + + return p; +} + +QRectF DefaultHorizontalNodeGeometry::captionRect(NodeId const nodeId) const +{ + if (!_graphModel.nodeData(nodeId, NodeRole::CaptionVisible)) + return QRect(); + + QString name = _graphModel.nodeData(nodeId, NodeRole::Caption); + + return _boldFontMetrics.boundingRect(name); +} + +QPointF DefaultHorizontalNodeGeometry::captionPosition(NodeId const nodeId) const +{ + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + return QPointF(0.5 * (size.width() - captionRect(nodeId).width()), + 0.5 * _portSpasing + captionRect(nodeId).height()); +} + +QPointF DefaultHorizontalNodeGeometry::widgetPosition(NodeId const nodeId) const +{ + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + + unsigned int captionHeight = captionRect(nodeId).height(); + + if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { + // If the widget wants to use as much vertical space as possible, + // place it immediately after the caption. + if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) { + return QPointF(2.0 * _portSpasing + maxPortsTextAdvance(nodeId, PortType::In), + captionHeight); + } else { + return QPointF(2.0 * _portSpasing + maxPortsTextAdvance(nodeId, PortType::In), + (captionHeight + size.height() - w->height()) / 2.0); + } + } + return QPointF(); +} + +QRect DefaultHorizontalNodeGeometry::resizeHandleRect(NodeId const nodeId) const +{ + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + + unsigned int rectSize = 7; + + return QRect(size.width() - _portSpasing, size.height() - _portSpasing, rectSize, rectSize); +} + +QRectF DefaultHorizontalNodeGeometry::portTextRect(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex) const +{ + QString s; + if (_graphModel.portData(nodeId, portType, portIndex, PortRole::CaptionVisible)) { + s = _graphModel.portData(nodeId, portType, portIndex, PortRole::Caption); + } else { + auto portData = _graphModel.portData(nodeId, portType, portIndex, PortRole::DataType); + + s = portData.value().name; + } + + return _fontMetrics.boundingRect(s); +} + +unsigned int DefaultHorizontalNodeGeometry::maxVerticalPortsExtent(NodeId const nodeId) const +{ + PortCount nInPorts = _graphModel.nodeData(nodeId, NodeRole::InPortCount); + + PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); + + unsigned int maxNumOfEntries = std::max(nInPorts, nOutPorts); + unsigned int step = _portSize + _portSpasing; + + return step * maxNumOfEntries; +} + +unsigned int DefaultHorizontalNodeGeometry::maxPortsTextAdvance(NodeId const nodeId, + PortType const portType) const +{ + unsigned int width = 0; + + size_t const n = _graphModel + .nodeData(nodeId, + (portType == PortType::Out) ? NodeRole::OutPortCount + : NodeRole::InPortCount) + .toUInt(); + + for (PortIndex portIndex = 0ul; portIndex < n; ++portIndex) { + QString name; + + if (_graphModel.portData(nodeId, portType, portIndex, PortRole::CaptionVisible)) { + name = _graphModel.portData(nodeId, portType, portIndex, PortRole::Caption); + } else { + NodeDataType portData = _graphModel.portData(nodeId, + portType, + portIndex, + PortRole::DataType); + + name = portData.name; + } + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + width = std::max(unsigned(_fontMetrics.horizontalAdvance(name)), width); +#else + width = std::max(unsigned(_fontMetrics.width(name)), width); +#endif + } + + return width; +} + diff --git a/source/defaultVerticalNodeGeometry.cpp b/source/defaultVerticalNodeGeometry.cpp new file mode 100644 index 0000000..2244f84 --- /dev/null +++ b/source/defaultVerticalNodeGeometry.cpp @@ -0,0 +1,296 @@ +#include "defaultVerticalNodeGeometry.h" + +#include "abstractGraphModel.h" +#include "nodeData.h" + +#include +#include +#include + +DefaultVerticalNodeGeometry::DefaultVerticalNodeGeometry(AbstractGraphModel &graphModel) + : AbstractNodeGeometry(graphModel) + , _portSize(20) + , _portSpasing(10) + , _fontMetrics(QFont()) + , _boldFontMetrics(QFont()) +{ + QFont f; + f.setBold(true); + _boldFontMetrics = QFontMetrics(f); + + _portSize = _fontMetrics.height(); +} + +QSize DefaultVerticalNodeGeometry::size(NodeId const nodeId) const +{ + return _graphModel.nodeData(nodeId, NodeRole::Size); +} + +void DefaultVerticalNodeGeometry::recomputeSize(NodeId const nodeId) const +{ + unsigned int height = _portSpasing; // maxHorizontalPortsExtent(nodeId); + + if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { + height = std::max(height, static_cast(w->height())); + } + + QRectF const capRect = captionRect(nodeId); + + height += capRect.height(); + + height += _portSpasing; + height += _portSpasing; + + PortCount nInPorts = _graphModel.nodeData(nodeId, NodeRole::InPortCount); + PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); + + // Adding double step (top and bottom) to reserve space for port captions. + + height += portCaptionsHeight(nodeId, PortType::In); + height += portCaptionsHeight(nodeId, PortType::Out); + + unsigned int inPortWidth = maxPortsTextAdvance(nodeId, PortType::In); + unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out); + + unsigned int totalInPortsWidth = nInPorts > 0 + ? inPortWidth * nInPorts + _portSpasing * (nInPorts - 1) + : 0; + + unsigned int totalOutPortsWidth = nOutPorts > 0 ? outPortWidth * nOutPorts + + _portSpasing * (nOutPorts - 1) + : 0; + + unsigned int width = std::max(totalInPortsWidth, totalOutPortsWidth); + + if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { + width = std::max(width, static_cast(w->width())); + } + + width = std::max(width, static_cast(capRect.width())); + + width += _portSpasing; + width += _portSpasing; + + QSize size(width, height); + + _graphModel.setNodeData(nodeId, NodeRole::Size, size); +} + +QPointF DefaultVerticalNodeGeometry::portPosition(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex) const +{ + QPointF result; + + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + + switch (portType) { + case PortType::In: { + unsigned int inPortWidth = maxPortsTextAdvance(nodeId, PortType::In) + _portSpasing; + + PortCount nInPorts = _graphModel.nodeData(nodeId, NodeRole::InPortCount); + + double x = (size.width() - (nInPorts - 1) * inPortWidth) / 2.0 + portIndex * inPortWidth; + + double y = 0.0; + + result = QPointF(x, y); + + break; + } + + case PortType::Out: { + unsigned int outPortWidth = maxPortsTextAdvance(nodeId, PortType::Out) + _portSpasing; + PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); + + double x = (size.width() - (nOutPorts - 1) * outPortWidth) / 2.0 + portIndex * outPortWidth; + + double y = size.height(); + + result = QPointF(x, y); + + break; + } + + default: + break; + } + + return result; +} + +QPointF DefaultVerticalNodeGeometry::portTextPosition(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex) const +{ + QPointF p = portPosition(nodeId, portType, portIndex); + + QRectF rect = portTextRect(nodeId, portType, portIndex); + + p.setX(p.x() - rect.width() / 2.0); + + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + + switch (portType) { + case PortType::In: + p.setY(5.0 + rect.height()); + break; + + case PortType::Out: + p.setY(size.height() - 5.0); + break; + + default: + break; + } + + return p; +} + +QRectF DefaultVerticalNodeGeometry::captionRect(NodeId const nodeId) const +{ + if (!_graphModel.nodeData(nodeId, NodeRole::CaptionVisible)) + return QRect(); + + QString name = _graphModel.nodeData(nodeId, NodeRole::Caption); + + return _boldFontMetrics.boundingRect(name); +} + +QPointF DefaultVerticalNodeGeometry::captionPosition(NodeId const nodeId) const +{ + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + + unsigned int step = portCaptionsHeight(nodeId, PortType::In); + step += _portSpasing; + + auto rect = captionRect(nodeId); + + return QPointF(0.5 * (size.width() - rect.width()), step + rect.height()); +} + +QPointF DefaultVerticalNodeGeometry::widgetPosition(NodeId const nodeId) const +{ + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + + unsigned int captionHeight = captionRect(nodeId).height(); + + if (auto w = _graphModel.nodeData(nodeId, NodeRole::Widget)) { + // If the widget wants to use as much vertical space as possible, + // place it immediately after the caption. + if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) { + return QPointF(_portSpasing + maxPortsTextAdvance(nodeId, PortType::In), captionHeight); + } else { + return QPointF(_portSpasing + maxPortsTextAdvance(nodeId, PortType::In), + (captionHeight + size.height() - w->height()) / 2.0); + } + } + return QPointF(); +} + +QRect DefaultVerticalNodeGeometry::resizeHandleRect(NodeId const nodeId) const +{ + QSize size = _graphModel.nodeData(nodeId, NodeRole::Size); + + unsigned int rectSize = 7; + + return QRect(size.width() - rectSize, size.height() - rectSize, rectSize, rectSize); +} + +QRectF DefaultVerticalNodeGeometry::portTextRect(NodeId const nodeId, + PortType const portType, + PortIndex const portIndex) const +{ + QString s; + if (_graphModel.portData(nodeId, portType, portIndex, PortRole::CaptionVisible)) { + s = _graphModel.portData(nodeId, portType, portIndex, PortRole::Caption); + } else { + auto portData = _graphModel.portData(nodeId, portType, portIndex, PortRole::DataType); + + s = portData.value().name; + } + + return _fontMetrics.boundingRect(s); +} + +unsigned int DefaultVerticalNodeGeometry::maxHorizontalPortsExtent(NodeId const nodeId) const +{ + PortCount nInPorts = _graphModel.nodeData(nodeId, NodeRole::InPortCount); + + PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); + + unsigned int maxNumOfEntries = std::max(nInPorts, nOutPorts); + unsigned int step = _portSize + _portSpasing; + + return step * maxNumOfEntries; +} + +unsigned int DefaultVerticalNodeGeometry::maxPortsTextAdvance(NodeId const nodeId, + PortType const portType) const +{ + unsigned int width = 0; + + size_t const n = _graphModel + .nodeData(nodeId, + (portType == PortType::Out) ? NodeRole::OutPortCount + : NodeRole::InPortCount) + .toUInt(); + + for (PortIndex portIndex = 0ul; portIndex < n; ++portIndex) { + QString name; + + if (_graphModel.portData(nodeId, portType, portIndex, PortRole::CaptionVisible)) { + name = _graphModel.portData(nodeId, portType, portIndex, PortRole::Caption); + } else { + NodeDataType portData = _graphModel.portData(nodeId, + portType, + portIndex, + PortRole::DataType); + + name = portData.name; + } + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + width = std::max(unsigned(_fontMetrics.horizontalAdvance(name)), width); +#else + width = std::max(unsigned(_fontMetrics.width(name)), width); +#endif + } + + return width; +} + +unsigned int DefaultVerticalNodeGeometry::portCaptionsHeight(NodeId const nodeId, + PortType const portType) const +{ + unsigned int h = 0; + + switch (portType) { + case PortType::In: { + PortCount nInPorts = _graphModel.nodeData(nodeId, NodeRole::InPortCount); + for (PortIndex i = 0; i < nInPorts; ++i) { + if (_graphModel.portData(nodeId, PortType::In, i, PortRole::CaptionVisible)) { + h += _portSpasing; + break; + } + } + break; + } + + case PortType::Out: { + PortCount nOutPorts = _graphModel.nodeData(nodeId, NodeRole::OutPortCount); + for (PortIndex i = 0; i < nOutPorts; ++i) { + if (_graphModel.portData(nodeId, PortType::Out, i, PortRole::CaptionVisible)) { + h += _portSpasing; + break; + } + } + break; + } + + default: + break; + } + + return h; +} diff --git a/source/designerScene.cpp b/source/designerScene.cpp new file mode 100644 index 0000000..21cc664 --- /dev/null +++ b/source/designerScene.cpp @@ -0,0 +1,275 @@ +#include "designerScene.h" +#include "util/selectorManager.h" +#include "graphicsItem/graphicsItemGroup.h" +#include "drawingPanel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +DesignerScene::DesignerScene(DataFlowGraphModel &graphModel, QObject *parent) + : BasicGraphicsScene(graphModel,parent), + m_pDrawingPanel(NULL), + _graphModel(graphModel) +{ + m_bGridVisible = true; + m_pView = nullptr; + m_pDrawingPanel = dynamic_cast(parent); + + connect(&_graphModel, + &DataFlowGraphModel::inPortDataWasSet, + [this](NodeId const nodeId, PortType const, PortIndex const) { onNodeUpdated(nodeId); }); +} +DesignerScene::~DesignerScene() +{ +} + +void DesignerScene::drawBackground(QPainter* painter, const QRectF& rect) +{ + QGraphicsScene::drawBackground(painter, rect); + painter->fillRect(sceneRect(), Qt::white); + if(!m_bGridVisible) + return; + + QRectF sceneRect = this->sceneRect(); + QPen pen; + pen.setBrush(Qt::darkCyan);//藏青色 + pen.setStyle(Qt::DashLine); + pen.setWidthF(0.2); + painter->setPen(pen); + int nGridSpace = 20; //暂时写死 + //竖线 + for(int nX = sceneRect.left(); nX < sceneRect.right(); nX += nGridSpace) + painter->drawLine(nX,sceneRect.top(),nX,sceneRect.bottom()); + //横线 + for(int nY = sceneRect.top(); nY < sceneRect.bottom(); nY += nGridSpace) + painter->drawLine(sceneRect.left(),nY,sceneRect.right(),nY); + //补齐最后两条 + painter->drawLine(sceneRect.right(), sceneRect.top(), sceneRect.right(), sceneRect.bottom()); + painter->drawLine(sceneRect.left(), sceneRect.bottom(), sceneRect.right(), sceneRect.bottom()); + + //原点 + // painter->setPen(Qt::red); + // painter->setBrush(Qt::red); + // painter->drawEllipse(0, 0, 50, 50); +} + +void DesignerScene::mousePressEvent(QGraphicsSceneMouseEvent* mouseEvent) +{ + if(m_pDrawingPanel) + { + m_pDrawingPanel->selectorManager()->getWorkingSelector()->mousePressEvent(mouseEvent, this); + update(); + } + else + QGraphicsScene::mousePressEvent(mouseEvent); +} + +void DesignerScene::mouseMoveEvent(QGraphicsSceneMouseEvent* mouseEvent) +{ + if(m_pDrawingPanel) + { + m_pDrawingPanel->selectorManager()->getWorkingSelector()->mouseMoveEvent(mouseEvent, this); + update(); + } + else + QGraphicsScene::mouseMoveEvent(mouseEvent); +} + +void DesignerScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* mouseEvent) +{ + if(m_pDrawingPanel) + { + m_pDrawingPanel->selectorManager()->getWorkingSelector()->mouseReleaseEvent(mouseEvent, this); + update(); + } + else + QGraphicsScene::mouseReleaseEvent(mouseEvent); +} + +void DesignerScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* mouseEvent) +{ + if(m_pDrawingPanel) + { + m_pDrawingPanel->selectorManager()->getWorkingSelector()->mouseDoubleClickEvent(mouseEvent, this); + update(); + } + else + QGraphicsScene::mouseReleaseEvent(mouseEvent); +} + +void DesignerScene::keyPressEvent(QKeyEvent* event) +{ + QGraphicsScene::keyPressEvent(event); +} + +void DesignerScene::keyReleaseEvent(QKeyEvent* event) +{ + QGraphicsScene::keyReleaseEvent(event); +} + +void DesignerScene::setGridVisible(bool bVisible) +{ + m_bGridVisible = bVisible; + update(); +} + +void DesignerScene::callParentEvent(QGraphicsSceneMouseEvent* mouseEvent) +{ + //调用QGraphicsScene的函数,会直接触发一些操作,比如取消item的selected状态,从而触发item的一些函数(itemChange),然后在item的相应函数中书写执行一些操作 + switch (mouseEvent->type()) + { + case QEvent::GraphicsSceneMousePress: + QGraphicsScene::mousePressEvent(mouseEvent); + break; + + case QEvent::GraphicsSceneMouseMove: + QGraphicsScene::mouseMoveEvent(mouseEvent); + break; + + case QEvent::GraphicsSceneMouseRelease: + QGraphicsScene::mouseReleaseEvent(mouseEvent); + break; + + default: + break; + } +} + +GraphicsItemGroup* DesignerScene::createGroup() +{ + QList listItem = selectedItems(); + if(listItem.isEmpty()) + return nullptr; + else if(listItem.count() == 1) //判断只选中了一个时是不是已经打组,如果是不做操作,防止循环打组 + { + AbstractShape* item = qgraphicsitem_cast(listItem.first()); + if(item && item->getType()==T_group) + return nullptr; + } + else //如果选择的有组群,则拆散该组群,并和其它单独的itme组合成新组群,防止多层组群出现,方便管理和计算 + { + for(int n=0; n(listItem[n]); + if(shape && shape->getType()==T_group) + { + GraphicsItemGroup* group = qgraphicsitem_cast(listItem[n]); + QList childItems = group->childItems(); + foreach (QGraphicsItem* child, childItems) + { + if(qgraphicsitem_cast(child)) + continue; + + group->removeFromGroup(child); + listItem.push_back(child); + } + + listItem.takeAt(n); + removeItem(group); + delete group; + n--; + } + } + } + + GraphicsItemGroup* group = new GraphicsItemGroup(); + group->addItems(listItem); + addItem(group); + return group; +} + +void DesignerScene::destroyGroup() +{ + +} + +std::vector DesignerScene::selectedNodes() const +{ + QList graphicsItems = selectedItems(); + + std::vector result; + result.reserve(graphicsItems.size()); + + /*for (QGraphicsItem *obj : graphicsItems) { + auto ngo = qgraphicsitem_cast(obj); + + if (ngo != nullptr) { + result.push_back(ngo->nodeId()); + } + }*/ + + return result; +} + +QMenu *DesignerScene::createSceneMenu(QPointF const scenePos) +{ + QMenu *modelMenu = new QMenu(); + + // QMenu's instance auto-destruction + modelMenu->setAttribute(Qt::WA_DeleteOnClose); + + return modelMenu; +} + +bool DesignerScene::save() const +{ + QString fileName = QFileDialog::getSaveFileName(nullptr, + tr("Open Flow Scene"), + QDir::homePath(), + tr("Flow Scene Files (*.flow)")); + + if (!fileName.isEmpty()) { + if (!fileName.endsWith("flow", Qt::CaseInsensitive)) + fileName += ".flow"; + + QFile file(fileName); + if (file.open(QIODevice::WriteOnly)) { + file.write(QJsonDocument(_graphModel.save()).toJson()); + return true; + } + } + return false; +} + +bool DesignerScene::load() +{ + QString fileName = QFileDialog::getOpenFileName(nullptr, + tr("Open Flow Scene"), + QDir::homePath(), + tr("Flow Scene Files (*.flow)")); + + if (!QFileInfo::exists(fileName)) + return false; + + QFile file(fileName); + + if (!file.open(QIODevice::ReadOnly)) + return false; + + clearScene(); + + QByteArray const wholeFile = file.readAll(); + + _graphModel.load(QJsonDocument::fromJson(wholeFile).object()); + + Q_EMIT sceneLoaded(); + + return true; +} + diff --git a/source/designerView.cpp b/source/designerView.cpp new file mode 100644 index 0000000..7b4d97d --- /dev/null +++ b/source/designerView.cpp @@ -0,0 +1,180 @@ +#include "designerView.h" +#include + +#define MAX_ZoomValue 50.0 +#define MIN_ZoomValue 0.02 + +DesignerView::DesignerView(QWidget *parent) + : QGraphicsView(parent) +{ + m_bMousePress = false; + m_dScale = 1.0; + initialize(); +} +DesignerView::~DesignerView() +{ + +} + +void DesignerView::initialize() +{ + //去掉QGraphicsView自带滚动条 + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + //设置背景 + setStyleSheet("background-image: url(:/images/checkerboard.png);\ + padding: 0px; \ + border: 0px;"); + //打开反锯齿 + setRenderHint(QPainter::Antialiasing); + setMouseTracking(true); + //setDragMode(QGraphicsView::RubberBandDrag); //将控制交给selector + + centerOn(0, 0); +} + +void DesignerView::contextMenuEvent(QContextMenuEvent* event) +{ + Q_UNUSED(event); + m_bMousePress = false; +} + +void DesignerView::mousePressEvent(QMouseEvent* event) +{ + QGraphicsItem* item = scene()->itemAt(mapToScene(event->pos()), transform()); + if (event->button() == Qt::MiddleButton /*&& !item*/) //在空白处按住中键进行拖动 + { + m_bMousePress = true; + setCursor(Qt::ClosedHandCursor); + m_ptLatstMouse_view = event->pos(); + } + else + QGraphicsView::mousePressEvent(event); +} + +void DesignerView::mouseMoveEvent(QMouseEvent* event) +{ + if(m_bMousePress) + { + QPointF mouseMoveLength = event->pos() - m_ptLatstMouse_view.toPoint(); + translate(mouseMoveLength); + m_ptLatstMouse_view = event->pos(); + } + else + QGraphicsView::mouseMoveEvent(event); +} + +void DesignerView::mouseReleaseEvent(QMouseEvent* event) +{ + if (event->button() == Qt::MiddleButton) //按住中键进行拖动 + { + m_bMousePress = false; + setCursor(Qt::ArrowCursor); + } + else + QGraphicsView::mouseReleaseEvent(event); +} + +void DesignerView::wheelEvent(QWheelEvent* event) //滚轮进行放大缩小 +{ + QPointF mousePos = event->position(); + double angleDeltaY = event->angleDelta().y(); + double zoomFactor = qPow(1.0015, angleDeltaY); //可以实现更平滑的缩放 + zoom(mousePos, zoomFactor); + + //注意不要调用基类的滚轮函数,否则滚轮事件会映射到滚动条操作 + //QGraphicsView::wheelEvent(event); +} + +double DesignerView::getScaleFactor() +{ + //QTransform为一个3*3的矩阵,m11和m22元素分别为水平和垂直的缩放值 + return this->transform().m11(); +} + +bool DesignerView::zoomLimit(double& value) +{ + m_dScale *= value; + if(m_dScale >= MAX_ZoomValue) + { + m_dScale = MAX_ZoomValue; + value = m_dScale / getScaleFactor(); + } + else if(m_dScale <= MIN_ZoomValue) + { + m_dScale = MIN_ZoomValue; + value = m_dScale / getScaleFactor(); + } + + if(qFabs(value - 1) < 0.01) //缩放因子近似为1 + return true; + + return false; +} + +void DesignerView::zoom(const QPointF& mousePos, double zoomFactor) +{ + if(zoomLimit(zoomFactor)) + return; + + /* + * QGraphicsView的默认transformationAnchor(变换锚点)是AnchorViewCenter,也就是中心,此处希望以鼠标指向为变换中心, + * AnchorUnderMouse可以实现,但经尝试没有自己进行移动(translate)效果好,所以先设置成NoAnchor,变换后再设置回来 + */ + ViewportAnchor lastAnchor = this->transformationAnchor(); + setTransformationAnchor(QGraphicsView::NoAnchor); + QPointF targetScenePos = mapToScene(mousePos.toPoint()); + QTransform trans = transform(); + trans.translate(targetScenePos.x(), targetScenePos.y()); + trans.scale(zoomFactor, zoomFactor); + trans.translate(-targetScenePos.x(), -targetScenePos.y()); + setTransform(trans); + setTransformationAnchor(lastAnchor); +} + +void DesignerView::zoomIn() +{ + //以view的中心点作为放大中心 + double dWidth_View = viewport()->width(); + double dHeight_View = viewport()->height(); + QPoint pt(dWidth_View * 0.5, dHeight_View * 0.5); + zoom(pt, 1.1); +} + +void DesignerView::zoomOut() +{ + //以view的中心点作为缩小中心 + double dWidth_View = viewport()->width(); + double dHeight_View = viewport()->height(); + QPoint pt(dWidth_View * 0.5, dHeight_View * 0.5); + zoom(pt, 0.9); +} + +void DesignerView::zoomFit() +{ + resetTransform(); + + double dWidth_View = viewport()->width(); + double dHeight_View = viewport()->height(); + double dWidth_Scene = scene()->sceneRect().width(); + double dHeight_Scene = scene()->sceneRect().height(); + + double dWidthScale = dWidth_View / dWidth_Scene; + double dHeightScale = dHeight_View / dHeight_Scene; + m_dScale = qMin(dWidthScale, dHeightScale); + + QTransform trans = transform(); + trans.scale(m_dScale, m_dScale); + setTransform(trans); + centerOn(0, 0); +} + +void DesignerView::translate(const QPointF& delta) +{ + ViewportAnchor lastAnchor = this->transformationAnchor(); + setTransformationAnchor(QGraphicsView::NoAnchor); + QTransform trans = transform(); + trans.translate(delta.x(), delta.y()); + setTransform(trans); + setTransformationAnchor(lastAnchor); +} diff --git a/source/diagramCavas.cpp b/source/diagramCavas.cpp new file mode 100644 index 0000000..b0f790e --- /dev/null +++ b/source/diagramCavas.cpp @@ -0,0 +1,41 @@ +#include "drawingPanel.h" +#include "diagramCavas.h" +#include "mainwindow.h" +#include + +DiagramCavas::DiagramCavas(QWidget *parent) + : QMdiArea(parent) +{ + + +} + +DiagramCavas::~DiagramCavas() +{ + +} + +void DiagramCavas::initial() +{ + //todo:读取数据并初始化 + onSignal_addDrawingPanel(QString("electricElements")); + onSignal_addDrawingPanel(QString("baseElements")); +} + + + +void DiagramCavas::onSignal_addDrawingPanel(const QString& sTitile) +{ + DrawingPanel* pPanel = new DrawingPanel(this); + m_mapDrawPanel.insert(sTitile,pPanel); + pPanel->setWindowTitle(sTitile); + this->addSubWindow(pPanel); +} + +void DiagramCavas::onSignal_addGraphicsItem(GraphicsItemType& type) +{ + QWidget* pWindow= currentSubWindow()->widget(); + DrawingPanel* pPanel = dynamic_cast(pWindow); + if(pPanel) + pPanel->onSignal_addGraphicsItem(type); +} diff --git a/source/drawingPanel.cpp b/source/drawingPanel.cpp new file mode 100644 index 0000000..eab82cf --- /dev/null +++ b/source/drawingPanel.cpp @@ -0,0 +1,94 @@ +#include "drawingPanel.h" +#include "ui_drawingPanel.h" +#include "designerView.h" +#include "dataFlowGraphModel.h" +#include "nodeDelegateModelRegistry.h" +#include "util/selectorManager.h" + + +DrawingPanel::DrawingPanel(QWidget *parent) + : QWidget(parent) + , ui(new Ui::drawingPanel) +{ + ui->setupUi(this); + + QSharedPointer registry = registerDataModels(); + DataFlowGraphModel dataFlowGraphModel(registry); + + m_pSelectorManager = new SelectorManager(this); + m_pGraphicsScene = new DesignerScene(dataFlowGraphModel,this); + //设置场景大小.前两个参数为scene的坐标远点,设置到view的中心点后,无论view如何缩放,secne的坐标原点都不会动,方便后续的位置计算 + m_pGraphicsScene->setSceneRect(-g_dGriaphicsScene_Width / 2, -g_dGriaphicsScene_Height / 2, g_dGriaphicsScene_Width, g_dGriaphicsScene_Height); + m_pGraphicsScene->setGridVisible(true); + + m_pGraphicsView = new DesignerView(this); + m_pGraphicsView->setScene(m_pGraphicsScene); + m_pGraphicsScene->setView(m_pGraphicsView); + ui->mainLayout->addWidget(m_pGraphicsView); + + +} + +DrawingPanel::~DrawingPanel() +{ + delete ui; +} + +QGraphicsScene* DrawingPanel::getQGraphicsScene() +{ + return m_pGraphicsView->scene(); +} + +DesignerScene* DrawingPanel::getDesignerScene() +{ + return m_pGraphicsScene; +} + +void DrawingPanel::grahpicsViewZoomIn() +{ + m_pGraphicsView->zoomIn(); +} + +void DrawingPanel::grahpicsViewZoomOut() +{ + m_pGraphicsView->zoomOut(); +} + +void DrawingPanel::grahpicsViewZoomFit() +{ + m_pGraphicsView->zoomFit(); +} + +GraphicsItemGroup* DrawingPanel::createItemGroup() +{ + return m_pGraphicsScene->createGroup(); +} + +void DrawingPanel::destroyItemGroup() +{ + m_pGraphicsScene->destroyGroup(); +} + +SelectorManager* DrawingPanel::selectorManager() const +{ + if(m_pSelectorManager) + return m_pSelectorManager; + else + return NULL; +} + +void DrawingPanel::onSignal_addGraphicsItem(GraphicsItemType& itemType) +{ + if(m_pSelectorManager) + { + m_pSelectorManager->setWorkingSelector(ST_cerating); + m_pSelectorManager->setDrawGraphicsItem(itemType); + } +} + +QSharedPointer DrawingPanel::registerDataModels() +{ + auto ret = QSharedPointer(new NodeDelegateModelRegistry()); + return ret; +} + diff --git a/source/electricElementsBox.cpp b/source/electricElementsBox.cpp new file mode 100644 index 0000000..8afb742 --- /dev/null +++ b/source/electricElementsBox.cpp @@ -0,0 +1,53 @@ +#include + +#include "electricElementsPanel.h" +#include "electricElementsBox.h" +#include "toolBox.h" +#include "util/baseSelector.h" + +ElectricElementsBox::ElectricElementsBox(QObject *parent) + : QObject(parent), + m_pToolBox(nullptr) +{ + m_pToolBox = new ToolBox(); + +} + +ElectricElementsBox::~ElectricElementsBox() +{ + if(m_pToolBox) + delete m_pToolBox; +} + +void ElectricElementsBox::initial() +{ + ElectricElementsPanel* pPanel1 = new ElectricElementsPanel(); + QMap map1; + map1.insert(QString::fromWCharArray(L"三角"),GIT_rect); + map1.insert(QString::fromWCharArray(L"四边"),GIT_roundRect); + pPanel1->setData(map1); + m_mapPanels.insert("baseElements",pPanel1); + m_pToolBox->addWidget("baseElements",pPanel1); + connect(pPanel1,&ElectricElementsPanel::addGraphicsItem,this,&ElectricElementsBox::onSignal_addEletricItem); + + ElectricElementsPanel* pPanel2 = new ElectricElementsPanel(); + QMap map2; + map2.insert(QString::fromWCharArray(L"总线"),GIT_bus); + map2.insert(QString::fromWCharArray(L"单线"),GIT_itemRect); + map2.insert(QString::fromWCharArray(L"双线"),GIT_itemTri); + pPanel2->setData(map2); + m_mapPanels.insert("eletricElements",pPanel2); + m_pToolBox->addWidget("eletricElements",pPanel2); + connect(pPanel2,&ElectricElementsPanel::addGraphicsItem,this,&ElectricElementsBox::onSignal_addEletricItem); +} + +ToolBox* ElectricElementsBox::getToolBox() const +{ + return m_pToolBox; +} + +void ElectricElementsBox::onSignal_addEletricItem(GraphicsItemType& type) +{ + emit addEletricItem(type); + +} diff --git a/source/electricElementsListwidget.cpp b/source/electricElementsListwidget.cpp new file mode 100644 index 0000000..bf7eb42 --- /dev/null +++ b/source/electricElementsListwidget.cpp @@ -0,0 +1,23 @@ +#include "electricElementsListwidget.h" + +ElectricElementsListwidget::ElectricElementsListwidget(QListWidget *parent) + : QListWidget(parent) +{ + +} + +ElectricElementsListwidget::~ElectricElementsListwidget() +{ + +} + +void ElectricElementsListwidget::mousePressEvent(QMouseEvent *event) +{ + int n = count(); //手动取消着色,处理qt6listwidget选中颜色 + for(int i = 0;i < n;++i) + { + QListWidgetItem* p = item(i); + p->setBackground(QBrush(QColor(0,0,0,0))); + } + QListWidget::mousePressEvent(event); +} diff --git a/source/electricElementsPanel.cpp b/source/electricElementsPanel.cpp new file mode 100644 index 0000000..456ad78 --- /dev/null +++ b/source/electricElementsPanel.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include "electricElementsPanel.h" +#include "electricElementsListwidget.h" + +ElectricElementsPanel::ElectricElementsPanel(QWidget *parent) + : QWidget(parent) +{ + m_pListWidget = new ElectricElementsListwidget(); + QVBoxLayout *vBoxLayout = new QVBoxLayout(this); + vBoxLayout->setContentsMargins(0, 0, 0, 0); + vBoxLayout->addWidget(m_pListWidget); + m_pListWidget->setViewMode(QListView::IconMode); + m_pListWidget->setResizeMode(QListView::Adjust); + m_pListWidget->setMovement(QListView::Static); + + connect(m_pListWidget,&QListWidget::itemClicked,this,&ElectricElementsPanel::onItemClicked); +} + +ElectricElementsPanel::~ElectricElementsPanel() +{ + if(m_pListWidget) + delete m_pListWidget; +} + +void ElectricElementsPanel::initial() +{ + for(auto iter = m_mapEleData.begin();iter != m_mapEleData.end();++iter) + { + QIcon icon(":/images/element/icons_triangle.png"); + QListWidgetItem* pItem = new QListWidgetItem(icon,iter.key()); + pItem->setSizeHint(QSize(50,50)); + pItem->setData(Qt::UserRole,iter.value()); + m_pListWidget->addItem(pItem); + } +} + +void ElectricElementsPanel::setData(const QMap& map) +{ + m_mapEleData = map; + initial(); +} + + +void ElectricElementsPanel::onItemClicked(QListWidgetItem* item) +{ + item->setBackground(QBrush(QColor(135,206,235,220))); + GraphicsItemType itetType = (GraphicsItemType)item->data(Qt::UserRole).toInt(); + emit addGraphicsItem(itetType); +} diff --git a/source/graphicElementsPanel.cpp b/source/graphicElementsPanel.cpp new file mode 100644 index 0000000..2213ca5 --- /dev/null +++ b/source/graphicElementsPanel.cpp @@ -0,0 +1,28 @@ +#include "graphicElementsPanel.h" +#include "ui_graphicElementsPanel.h" + +GraphicElementsPanel::GraphicElementsPanel(QWidget *parent) + : QWidget(parent) + , ui(new Ui::graphicElementsPanel) +{ + ui->setupUi(this); + + ui->pushBtn_rect->setProperty("shap",GIT_rect); + connect(ui->pushBtn_rect, SIGNAL(clicked()), this, SLOT(onBtnClicked_GraphicsItem())); + ui->pushBtn_roundRect->setProperty("shap",GIT_roundRect); + connect(ui->pushBtn_roundRect, SIGNAL(clicked()), this, SLOT(onBtnClicked_GraphicsItem())); + ui->pushBtn_polygon->setProperty("shap",GIT_polygon); + connect(ui->pushBtn_polygon, SIGNAL(clicked()), this, SLOT(onBtnClicked_GraphicsItem())); +} + +GraphicElementsPanel::~GraphicElementsPanel() +{ + delete ui; +} + +void GraphicElementsPanel::onBtnClicked_GraphicsItem() +{ + QObject* pObject = QObject::sender(); + GraphicsItemType itetType = (GraphicsItemType)pObject->property("shap").toInt(); + emit addGraphicsItem(itetType); +} diff --git a/source/graphicsItem/electricSvgItem.cpp b/source/graphicsItem/electricSvgItem.cpp new file mode 100644 index 0000000..e25251c --- /dev/null +++ b/source/graphicsItem/electricSvgItem.cpp @@ -0,0 +1,151 @@ +#include "graphicsItem/electricSvgItem.h" +#include "graphicsItem/itemControlHandle.h" + +#include +#include +#include +#include + +ElectricSvgItem::ElectricSvgItem(const QRect &rect, QGraphicsItem *parent) + : GraphicsBaseItem(parent),m_pRender(nullptr) +{ + m_lastBoudingRect = rect; + m_boundingRect = rect; + m_dWidth = rect.width(); + m_dHeight = rect.height(); +} + +ElectricSvgItem::~ElectricSvgItem() +{ + +} + +QPainterPath ElectricSvgItem::shape() +{ + QPainterPath path; + double dHandleX = 0.0; + double dHandleY = 0.0; + path.addRect(m_boundingRect); + return path; +} + +void ElectricSvgItem::updateCoordinate() //当执行了resie和editShape函数后,boundingRect发生了变换,需要将item的原点(以中心点为原点)校准至boundingRect.center() +{ + if (!parentItem()) + { + QPointF pt1, pt2, delta; + pt1 = mapToScene(QPointF(0, 0)); + pt2 = mapToScene(m_boundingRect.center()); + delta = pt1 - pt2; + + prepareGeometryChange(); + //将boundingRect设置成中心点和原点(也是默认变换原点),这样三点重合,有助于简化计算 + m_boundingRect = QRectF(-m_dWidth / 2, -m_dHeight / 2, m_dWidth, m_dHeight); + //setTransformOriginPoint(m_boundingRect.center()); //变换中心默认为item的(0,0)点,所以不执行这句话也没有问题 + //更新bouondingRect后重回会显示位置会有变化,需要做对应的移动 + moveBy(-delta.x(), -delta.y()); + updateHandles(); + } + + m_lastBoudingRect = m_boundingRect; +} + +void ElectricSvgItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + if(m_pRender) + { + m_pRender->render(painter,m_boundingRect); + } + + painter->setPen(m_pen); + painter->setBrush(m_brush); + if(m_state == S_lineOut) + { + painter->setPen(QPen(QColor(255,51,153,180))); + painter->drawLine(m_beginConnectPoint,m_endConnectPoint); + } + + if (option->state & QStyle::State_Selected) //是选中状态,绘制选中框 + { + int nPenWidth = 1; + painter->setPen(QPen(QColor(135,206,235,180))); //蓝色的外框 + + painter->setBrush(QBrush(QColor(135,206,235,180))); + + double dx = m_boundingRect_selected.x(); + double dy = m_boundingRect_selected.y(); + double dWidth = m_boundingRect_selected.width(); + double dHeight = m_boundingRect_selected.height(); + + + //画弧线0度是3点钟方向 + if(dWidth > dHeight) + { + painter->drawRect(m_boundingRect_selected); + painter->drawChord(dx-dHeight*0.5,dy,dHeight,dHeight,90*16,180*16); + painter->drawChord(dx+dWidth-dHeight*0.5,dy,dHeight,dHeight,(-90)*16,180*16); + } + else if(dWidth < dHeight) + { + painter->drawRect(m_boundingRect_selected); + painter->drawChord(dx,dy-dWidth*0.5,dWidth,dWidth,0*16,180*16); + painter->drawChord(dx,dy+dHeight-dWidth*0.5,dWidth,dWidth,180*16,180*16); + //painter->drawChord(dx+dWidth-dHeight*0.5,dy,dHeight,dHeight,(-90)*16,180*16); + } + else + { + painter->drawEllipse(QPointF(dx+dWidth*0.5,dy+dHeight*0.5),dHeight*0.5,dHeight*0.5); + } + + //绘制变换原点 + QPointF originPoint = transformOriginPoint(); + //qDebug() << "originPoint:" << originPoint; + painter->setBrush(Qt::red); + painter->drawEllipse(originPoint, 4, 4); + } +} + +void ElectricSvgItem::loadSvg(const QString& str) +{ + m_pRender = new QSvgRenderer(str); +} + +void ElectricSvgItem::resize(int nHandle,double dSX, double dSY, const QPointF& basePoint) +{ + switch (nHandle) + { + case H_left: + case H_right: + dSY = 1; //拖拽的是左点右点,为水平缩放,忽略垂直变化 + break; + case H_top: + case H_bottom: + dSX = 1; //拖拽的是顶点底点,为垂直缩放,忽略水平变化 + break; + default: + break; + } + + QTransform trans; + //缩放是以图元原点(中心)位置为基准,所以每帧都先移动移动到想要的基准点,缩放之后再移回 + trans.translate(basePoint.x(), basePoint.y()); + trans.scale(dSX, dSY); + trans.translate(-basePoint.x(), -basePoint.y()); + + prepareGeometryChange(); + m_boundingRect = trans.mapRect(m_lastBoudingRect); + m_dWidth = m_boundingRect.width(); + m_dHeight = m_boundingRect.height(); + updateHandles(); +} + +void ElectricSvgItem::move(const QPointF& point) +{ + moveBy(point.x(), point.y()); +} + +void ElectricSvgItem::editShape(int nHandle,const QPointF& ptMouse) +{ + prepareGeometryChange(); + updateHandles(); +} diff --git a/source/graphicsItem/electricSvgItemBus.cpp b/source/graphicsItem/electricSvgItemBus.cpp new file mode 100644 index 0000000..ceab1c7 --- /dev/null +++ b/source/graphicsItem/electricSvgItemBus.cpp @@ -0,0 +1,28 @@ +#include "graphicsItem/electricSvgItemBus.h" +#include "graphicsItem/itemControlHandle.h" + +#include +#include + +ElectricSvgItemBus::ElectricSvgItemBus(const QRect &rect, QGraphicsItem *parent) + : ElectricSvgItem(rect,parent) +{ + loadSvg(":/images/element/svg_bus.svg"); +} + +ElectricSvgItemBus::~ElectricSvgItemBus() +{ + +} + +void ElectricSvgItemBus::updateHandles() +{ + ElectricSvgItem::updateHandles(); +} + + +void ElectricSvgItemBus::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + ElectricSvgItem::paint(painter,option,widget); +} + diff --git a/source/graphicsItem/electricSvgItemRect.cpp b/source/graphicsItem/electricSvgItemRect.cpp new file mode 100644 index 0000000..4417bec --- /dev/null +++ b/source/graphicsItem/electricSvgItemRect.cpp @@ -0,0 +1,55 @@ +#include "graphicsItem/electricSvgItemRect.h" +#include "graphicsItem/itemControlHandle.h" + +#include +#include + +ElectricSvgItemRect::ElectricSvgItemRect(const QRect &rect, QGraphicsItem *parent) + : ElectricSvgItem(rect,parent) +{ + loadSvg(":/images/element/svg_rect.svg"); + + //入线口 + ItemControlHandle* pHandle1 = new ItemControlHandle(this); + pHandle1->setType(T_lineIn); + pHandle1->setTag(H_connect); + m_vecHanle.insert(H_connect,pHandle1); + //出线口 + ItemControlHandle* pHandle2 = new ItemControlHandle(this); + pHandle2->setType(T_lineOut); + pHandle2->setTag(H_connect+1); + m_vecHanle.insert(H_connect+1,pHandle2); + + m_dRatioX = 0.5; +} + +ElectricSvgItemRect::~ElectricSvgItemRect() +{ + +} + +void ElectricSvgItemRect::updateHandles() +{ + ElectricSvgItem::updateHandles(); + if( m_vecHanle.contains(H_connect)) + { + const QRectF& boundingRect = this->boundingRect(); + + if(m_vecHanle.contains(H_connect)) + { + m_vecHanle[H_connect]->move(boundingRect.right() - boundingRect.width() * m_dRatioX, boundingRect.top()); + } + + if(m_vecHanle.contains(H_connect + 1)) + { + m_vecHanle[H_connect + 1]->move(boundingRect.right() - boundingRect.width() * m_dRatioX, boundingRect.bottom()); + } + } +} + + +void ElectricSvgItemRect::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + ElectricSvgItem::paint(painter,option,widget); +} + diff --git a/source/graphicsItem/electricSvgItemTriangle.cpp b/source/graphicsItem/electricSvgItemTriangle.cpp new file mode 100644 index 0000000..8c13d14 --- /dev/null +++ b/source/graphicsItem/electricSvgItemTriangle.cpp @@ -0,0 +1,64 @@ +#include "graphicsItem/electricSvgItemTriangle.h" +#include "graphicsItem/itemControlHandle.h" + +#include +#include + +ElectricSvgItemTriangle::ElectricSvgItemTriangle(const QRect &rect, QGraphicsItem *parent) + : ElectricSvgItem(rect,parent) +{ + loadSvg(":/images/element/svg_triangle.svg"); + + //入线口 + ItemControlHandle* pHandle1 = new ItemControlHandle(this); + pHandle1->setType(T_lineIn); + pHandle1->setTag(H_connect); + m_vecHanle.insert(H_connect,pHandle1); + //出线口 + ItemControlHandle* pHandle2 = new ItemControlHandle(this); + pHandle2->setType(T_lineOut); + pHandle2->setTag(H_connect+1); + m_vecHanle.insert(H_connect+1,pHandle2); + + ItemControlHandle* pHandle3 = new ItemControlHandle(this); + pHandle3->setType(T_lineOut); + pHandle3->setTag(H_connect+2); + m_vecHanle.insert(H_connect+2,pHandle3); + + m_dTopRatioX = 0.5; + m_dBottomRatioX = 0.333; +} + +ElectricSvgItemTriangle::~ElectricSvgItemTriangle() +{ + +} + +void ElectricSvgItemTriangle::updateHandles() +{ + ElectricSvgItem::updateHandles(); + if( m_vecHanle.contains(H_connect)) + { + const QRectF& boundingRect = this->boundingRect(); + + if(m_vecHanle.contains(H_connect)) + { + m_vecHanle[H_connect]->move(boundingRect.right() - boundingRect.width() * m_dTopRatioX, boundingRect.top()); + } + if(m_vecHanle.contains(H_connect + 1)) + { + m_vecHanle[H_connect + 1]->move(boundingRect.left() + boundingRect.width() * m_dBottomRatioX, boundingRect.bottom()); + } + if(m_vecHanle.contains(H_connect + 2)) + { + m_vecHanle[H_connect + 2]->move(boundingRect.right() - boundingRect.width() * m_dBottomRatioX, boundingRect.bottom()); + } + } +} + + +void ElectricSvgItemTriangle::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + ElectricSvgItem::paint(painter,option,widget); +} + diff --git a/source/graphicsItem/graphicsBaseItem.cpp b/source/graphicsItem/graphicsBaseItem.cpp new file mode 100644 index 0000000..e9e0aeb --- /dev/null +++ b/source/graphicsItem/graphicsBaseItem.cpp @@ -0,0 +1,116 @@ +#include "graphicsItem/graphicsBaseItem.h" +#include + + +GraphicsBaseItem::GraphicsBaseItem(QGraphicsItem *parent) + : AbstractShapeType(parent) +{ + m_type = T_item; + //初始化缩放操作用的handle + //m_vecHanle.reserve(H_left); + for(int i = H_leftTop; i <= H_left; i++) + { + ItemControlHandle* pHandle = new ItemControlHandle(this); + pHandle->setType(T_resize); + pHandle->setTag(i); + m_vecHanle.insert(i-1,pHandle); + } + for(int i = H_rotate_leftTop; i <= H_rotate_leftBottom; i++) + { + ItemControlHandle* pHandle = new ItemControlHandle(this); + pHandle->setType(T_rotate); + pHandle->setTag(i); + m_vecHanle.insert(i-1,pHandle); + } + + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsSelectable, true); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); + setAcceptHoverEvents(true); +} + +GraphicsBaseItem::~GraphicsBaseItem() +{ + +} + + +void GraphicsBaseItem::createOperationCopy() +{ + m_pOperationCopy = new QGraphicsPathItem(this->shape()); + m_pOperationCopy->setPen(Qt::DashLine); + m_pOperationCopy->setPos(this->pos()); + m_pOperationCopy->setTransformOriginPoint(this->transformOriginPoint()); + m_pOperationCopy->setTransform(this->transform()); + m_pOperationCopy->setRotation(this->rotation()); + m_pOperationCopy->setScale(this->scale()); + m_pOperationCopy->setZValue(this->zValue()); + + QGraphicsScene* scene = this->scene(); + if(scene && m_pOperationCopy) + { + scene->addItem(m_pOperationCopy); + m_movingIniPos = this->pos(); + } +} + +void GraphicsBaseItem::removeOperationCopy() +{ + QGraphicsScene* scene = this->scene(); + if(scene && m_pOperationCopy) + { + if(this->pos() != m_pOperationCopy->pos()) + this->setPos(m_pOperationCopy->pos()); //本体移动到副本的位置 + if(this->rotation() != m_pOperationCopy->rotation()) + this->setRotation(m_pOperationCopy->rotation()); //本体旋转至副本的角度 + + scene->removeItem(m_pOperationCopy); + delete m_pOperationCopy; + m_pOperationCopy = nullptr; + } +} + +void GraphicsBaseItem::moveOperationCopy(const QPointF& distance) +{ + if(m_pOperationCopy) + m_pOperationCopy->setPos(m_movingIniPos + distance); +} + +void GraphicsBaseItem::rotateOperationCopy(const double& dAngle) +{ + if(m_pOperationCopy) + m_pOperationCopy->setRotation(dAngle); +} + +void GraphicsBaseItem::syncRotationDataFromParent(const double& data) +{ + //m_dSyncRotationByParent = rotation() + data; + m_dSyncRotationByParent += data; + //让角度保持在正负180的区间,也就是上下两个半圈,这样易于象限判断 + if (m_dSyncRotationByParent > 180) + m_dSyncRotationByParent -= 360; + if (m_dSyncRotationByParent < -180) + m_dSyncRotationByParent += 360; +} + +QVariant GraphicsBaseItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant& value) +{ + if (change == QGraphicsItem::ItemSelectedHasChanged) + { + QGraphicsItemGroup *group = dynamic_cast(parentItem()); + if(!group) + setHandleVisible(value.toBool()); + else //在某一组群中,由组群展示是否选中,自身不做展示 + { + setSelected(false); + return QVariant::fromValue(false); + } + } + return QGraphicsItem::itemChange(change, value); +} + +void GraphicsBaseItem::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) +{ + Q_UNUSED(event); +} + diff --git a/source/graphicsItem/graphicsItemGroup.cpp b/source/graphicsItem/graphicsItemGroup.cpp new file mode 100644 index 0000000..b09c2e4 --- /dev/null +++ b/source/graphicsItem/graphicsItemGroup.cpp @@ -0,0 +1,286 @@ +#include "graphicsItem/graphicsItemGroup.h" +#include +#include +#include +#include + + +GraphicsItemGroup::GraphicsItemGroup(QGraphicsItem *parent) + : AbstractShapeType(parent) +{ + m_type = T_group; + + m_boundingRect = QRectF(); + m_lastBoudingRect = QGraphicsItemGroup::boundingRect(); + + //初始化缩放操作用的handle + //m_vecHanle.reserve(H_left); + for(int i = H_leftTop; i <= H_left; i++) + { + ItemControlHandle* pHandle = new ItemControlHandle(this); + pHandle->setType(T_resize); + pHandle->setTag(i); + m_vecHanle.insert(i-1,pHandle); + } + for(int i = H_rotate_leftTop; i <= H_rotate_leftBottom; i++) + { + ItemControlHandle* pHandle = new ItemControlHandle(this); + pHandle->setType(T_rotate); + pHandle->setTag(i); + m_vecHanle.insert(i-1,pHandle); + } + + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsSelectable, true); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); + setAcceptHoverEvents(true); +} + +GraphicsItemGroup::~GraphicsItemGroup() +{ +} + +QPainterPath GraphicsItemGroup::shape() +{ + QPainterPath path; + path.addRect(m_boundingRect); + return path; +} + +void GraphicsItemGroup::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + if (option->state & QStyle::State_Selected) //是选中状态,绘制选中框 + { + int nPenWidth = 1; + painter->setPen(QPen(QColor(70,70,70), nPenWidth, Qt::DashLine)); //蓝色的外框 + painter->setBrush(Qt::NoBrush); + painter->drawRect(m_boundingRect_selected); + + //绘制变换原点 + /*QPointF originPoint = transformOriginPoint(); + //qDebug() << "originPoint:" << originPoint << " boundingRect:" << m_boundingRect; + painter->setBrush(Qt::red); + painter->drawEllipse(QPointF(0,0), 4, 4); + painter->setBrush(Qt::blue); + painter->drawEllipse(originPoint, 4, 4);*/ + } +} + +void GraphicsItemGroup::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) +{ + Q_UNUSED(event); +} + +QVariant GraphicsItemGroup::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant& value) +{ + if (change == QGraphicsItem::ItemSelectedHasChanged) + { + QGraphicsItemGroup *group = dynamic_cast(parentItem()); + if(!group) + setHandleVisible(value.toBool()); + else //在某一组群中,由组群展示是否选中,自身不做展示 + { + setSelected(false); + return QVariant::fromValue(false); + } + } + return QGraphicsItemGroup::itemChange(change, value); +} + +void GraphicsItemGroup::syncRotationDataFromParent(const double& data) +{ + /*m_dSyncRotationByParent += data; + //让角度保持在正负180的区间,也就是上下两个半圈,这样易于象限判断 + if (m_dSyncRotationByParent > 180) + m_dSyncRotationByParent -= 360; + if (m_dSyncRotationByParent < -180) + m_dSyncRotationByParent += 360; + //同步给子项 + foreach (QGraphicsItem* item, childItems()) + { + if(qgraphicsitem_cast(item)) + continue; + + AbstractShape* shape = qgraphicsitem_cast(item); + if(shape && shape->getType()==T_group) + { + GraphicsItemGroup *group = qgraphicsitem_cast(item); + if(group) + group->syncRotationDataFromParent(data); + } + else + { + GraphicsBaseItem *baseItem = qgraphicsitem_cast(item); + if (baseItem) + baseItem->syncRotationDataFromParent(data); + } + }*/ +} + +void GraphicsItemGroup::updateCoordinate() //当执行了resie和editShape函数后,boundingRect发生了变换,需要将item的原点(以中心点为原点)校准至boundingRect.center() +{ + if (!parentItem()) + { + if (m_boundingRect.isNull()) + { + m_boundingRect = QGraphicsItemGroup::boundingRect(); + m_dWidth = m_boundingRect.width(); + m_dHeight = m_boundingRect.height(); + } + + //因为group包含子item,boundingRect会根据子item自动计算生成,所以不再通过重设boundingRect(中心和原点重合)重合的方式,因为此种方式需要子item做各种同步计算 + //最简单的方式是,每次boundingRect发生变化(缩放)后重新设置变换中心,但是会出现漂移(因为item只存储旋转角度数据,每帧都根据角度进行重绘),具体见开发学习笔记,消除这个漂移即可 + prepareGeometryChange(); + //具体计算 + double dAngle = qDegreesToRadians(rotation()); + QPointF centerPt = m_boundingRect.center(); + QPointF originPt = transformOriginPoint(); + QPointF targetPt = QPointF(0, 0); + targetPt.setX(originPt.x() + qCos(dAngle)*(centerPt.x() - originPt.x()) - qSin(dAngle)*(centerPt.y() - originPt.y())); + targetPt.setY(originPt.y() + qSin(dAngle)*(centerPt.x() - originPt.x()) + qCos(dAngle)*(centerPt.y() - originPt.y())); + QPointF delta = targetPt - centerPt; + //m_boundingRect.adjust(delta.x(), delta.y(), delta.x(), delta.y()); + setTransformOriginPoint(m_boundingRect.center()); + if(dAngle != 0) + moveBy(delta.x(), delta.y()); + + //QTimer::singleShot(2000,[&](){moveBy(-delta.x(), -delta.y());}); + + updateHandles(); + } + + //调用该组内所有item(过滤handle)相关函数 + foreach (QGraphicsItem *item, childItems()) + { + GraphicsBaseItem *baseItem = qgraphicsitem_cast(item); + if (baseItem && !qgraphicsitem_cast(baseItem)) + { + baseItem->updateCoordinate(); + } + } + m_lastBoudingRect = m_boundingRect; +} + +void GraphicsItemGroup::resize(int nHandle,double dSX, double dSY, const QPointF& basePoint) +{ + switch (nHandle) + { + case H_left: + case H_right: + dSY = 1; //拖拽的是左点右点,为水平缩放,忽略垂直变化 + break; + case H_top: + case H_bottom: + dSX = 1; //拖拽的是顶点底点,为垂直缩放,忽略水平变化 + break; + default: + break; + } + + QTransform trans; + //缩放是以图元变换原点为基准,所以每帧都先移动移动到想要的基准点,缩放之后再移回 + trans.translate(basePoint.x(), basePoint.y()); + trans.scale(dSX, dSY); + trans.translate(-basePoint.x(), -basePoint.y()); + + prepareGeometryChange(); + m_boundingRect = trans.mapRect(m_lastBoudingRect); + m_dWidth = m_boundingRect.width(); + m_dHeight = m_boundingRect.height(); + updateHandles(); + + //调用该组内所有item(过滤handle)相关函数 + foreach (QGraphicsItem *item, childItems()) + { + GraphicsBaseItem *baseItem = qgraphicsitem_cast(item); + if (baseItem && !qgraphicsitem_cast(baseItem)) + { + baseItem->resize(nHandle, dSX, dSY, baseItem->mapFromParent(basePoint)); + } + } +} + +void GraphicsItemGroup::createOperationCopy() +{ + m_pOperationCopy = new QGraphicsPathItem(this->shape()); + m_pOperationCopy->setPen(Qt::DashLine); + m_pOperationCopy->setPos(this->pos()); + m_pOperationCopy->setTransformOriginPoint(this->transformOriginPoint()); + m_pOperationCopy->setTransform(this->transform()); + m_pOperationCopy->setRotation(this->rotation()); + m_pOperationCopy->setScale(this->scale()); + m_pOperationCopy->setZValue(this->zValue()); + + QGraphicsScene* scene = this->scene(); + if(scene && m_pOperationCopy) + { + scene->addItem(m_pOperationCopy); + m_movingIniPos = this->pos(); + } +} + +void GraphicsItemGroup::removeOperationCopy() +{ + QGraphicsScene* scene = this->scene(); + if(scene && m_pOperationCopy) + { + if(this->pos() != m_pOperationCopy->pos()) + this->setPos(m_pOperationCopy->pos()); //本体移动到副本的位置 + if(this->rotation() != m_pOperationCopy->rotation()) + { + double dAngle = m_pOperationCopy->rotation(); + this->setRotation(dAngle); //本体旋转至副本的角度 + //子item的旋转数据并不会和所在组同步,需要手动同步 + foreach (QGraphicsItem* item, childItems()) + { + if(qgraphicsitem_cast(item)) + continue; + + // AbstractShape* shape = qgraphicsitem_cast(item); + // if(shape && shape->getType()==T_group) + // { + // GraphicsItemGroup *group = qgraphicsitem_cast(item); + // if(group) + // group->syncRotationDataFromParent(dAngle); + // } + // else + { + GraphicsBaseItem *baseItem = qgraphicsitem_cast(item); + if (baseItem) + baseItem->syncRotationDataFromParent(dAngle); + } + } + } + + scene->removeItem(m_pOperationCopy); + delete m_pOperationCopy; + m_pOperationCopy = nullptr; + } +} + +void GraphicsItemGroup::moveOperationCopy(const QPointF& distance) +{ + if(m_pOperationCopy) + m_pOperationCopy->setPos(m_movingIniPos + distance); +} + +void GraphicsItemGroup::rotateOperationCopy(const double& dAngle) +{ + if(m_pOperationCopy) + { + m_pOperationCopy->setRotation(dAngle); + } + +} + +void GraphicsItemGroup::addItems(const QList& items) +{ + foreach (QGraphicsItem *item, items) + { + item->setSelected(false); + addToGroup(item); + m_listItem.push_back(item); + } + + updateCoordinate(); +} diff --git a/source/graphicsItem/graphicsPolygonItem.cpp b/source/graphicsItem/graphicsPolygonItem.cpp new file mode 100644 index 0000000..f8538bb --- /dev/null +++ b/source/graphicsItem/graphicsPolygonItem.cpp @@ -0,0 +1,155 @@ +#include "graphicsItem/graphicsPolygonItem.h" +#include "graphicsItem/itemControlHandle.h" + +#include +#include + +GraphicPolygonItem::GraphicPolygonItem(QGraphicsItem *parent) + : GraphicsBaseItem(parent) +{ + m_pen = QPen(Qt::black); + m_brush = QBrush(Qt::NoBrush); +} + +GraphicPolygonItem::~GraphicPolygonItem() +{ + +} + +QPainterPath GraphicPolygonItem::shape() +{ + QPainterPath path; + path.addPolygon(m_points); + path.closeSubpath(); //将路径闭合 + return path; +} + +QRectF GraphicPolygonItem::boundingRect() +{ + //m_boundingRect = shape().controlPointRect(); //返回路径中所有点和控制点的矩形,文档介绍比返回精确边界框的boundingRect()函数计算要快 + return m_boundingRect; +} + +void GraphicPolygonItem::updateHandles() +{ + GraphicsBaseItem::updateHandles(); + for(int i = 0; i < m_points.size(); i++) + { + if(m_vecHanle.contains(H_edit + i -1)) + { + m_vecHanle[H_edit + i -1]->move(m_points[i].x(), m_points[i].y()); + } + } +} + +void GraphicPolygonItem::updateCoordinate() //当执行了resie和editShape函数后,boundingRect发生了变换,需要将item的原点(以中心点为原点)校准至boundingRect.center() +{ + if (!parentItem()) + { + QPointF pt1, pt2, delta; + pt1 = mapToScene(QPointF(0, 0)); + pt2 = mapToScene(m_boundingRect.center()); + delta = pt1 - pt2; + + prepareGeometryChange(); + //更改图形绘制节点坐标,让图形中心和原点对齐 + QPolygonF ptsOnScene = mapToScene(m_points); //所有操作都在scene坐标系下进行,然后在还原至item坐标系,否则会有跳转 + for(int i=0; isetPen(m_pen); + painter->setBrush(m_brush); + painter->drawPolygon(m_points); + + if (option->state & QStyle::State_Selected) //是选中状态,绘制选中框 + { + int nPenWidth = 1; + painter->setPen(QPen(QColor(70,70,70), nPenWidth, Qt::DashLine)); //蓝色的外框 + painter->setBrush(Qt::NoBrush); + painter->drawRect(m_boundingRect_selected); + + //绘制变换原点 + QPointF originPoint = transformOriginPoint(); + painter->setBrush(Qt::red); + painter->drawEllipse(originPoint, 4, 4); + } +} + +void GraphicPolygonItem::resize(int nHandle,double dSX, double dSY, const QPointF& basePoint) +{ + switch (nHandle) + { + case H_left: + case H_right: + dSY = 1; //拖拽的是左点右点,为水平缩放,忽略垂直变化 + break; + case H_top: + case H_bottom: + dSX = 1; //拖拽的是顶点底点,为垂直缩放,忽略水平变化 + break; + default: + break; + } + + QTransform trans; + //缩放是以图元原点(中心)位置为基准,所以每帧都先移动移动到想要的基准点,缩放之后再移回 + trans.translate(basePoint.x(), basePoint.y()); + trans.scale(dSX, dSY); + trans.translate(-basePoint.x(), -basePoint.y()); + + prepareGeometryChange(); + m_points = trans.map(m_lastPoints); + m_boundingRect = m_points.boundingRect(); + m_dWidth = m_boundingRect.width(); + m_dHeight = m_boundingRect.height(); + updateHandles(); +} + +void GraphicPolygonItem::move(const QPointF& point) +{ + moveBy(point.x(), point.y()); +} + +void GraphicPolygonItem::editShape(int nHandle,const QPointF& ptMouse) +{ + QPointF pt = mapFromScene(ptMouse); + m_points[nHandle - H_rotate_leftBottom - 1] = pt; + prepareGeometryChange(); + m_boundingRect = m_points.boundingRect(); + m_dWidth = m_boundingRect.width(); + m_dHeight = m_boundingRect.height(); + m_lastPoints = m_points; + updateHandles(); +} + +void GraphicPolygonItem::addPoint(const QPointF& point) +{ + m_points.append(mapFromScene(point)); + int nCount = m_points.count(); + //每个顶点都可以编辑,所以一个顶点需要编辑handle + ItemControlHandle* pHandle = new ItemControlHandle(this); + pHandle->setType(T_editShape); + pHandle->setTag(H_edit + nCount - 1); + m_vecHanle.insert(H_edit + nCount - 1,pHandle); +} + +bool GraphicPolygonItem::endDrawing() +{ + bool bSuccess = true; + int nPointCount = m_points.count(); + if(nPointCount < 3) + bSuccess = false; + + return bSuccess; +} diff --git a/source/graphicsItem/graphicsRectItem.cpp b/source/graphicsItem/graphicsRectItem.cpp new file mode 100644 index 0000000..5121cea --- /dev/null +++ b/source/graphicsItem/graphicsRectItem.cpp @@ -0,0 +1,230 @@ +#include "graphicsItem/graphicsRectItem.h" +#include "graphicsItem/itemControlHandle.h" + +#include +#include + +GraphicsRectItem::GraphicsRectItem(const QRect &rect, bool isRound, QGraphicsItem *parent) + : GraphicsBaseItem(parent), m_bIsRound(isRound), m_dRatioX(1 / 10.0), m_dRatioY(1 / 10.0) +{ + m_pen = QPen(Qt::black); + m_brush = QBrush(Qt::NoBrush); + m_lastBoudingRect = rect; + m_boundingRect = rect; + m_dWidth = rect.width(); + m_dHeight = rect.height(); + + if (m_bIsRound) //圆角矩形添加两个圆角大小控制点 + { + //横轴X控制点 + ItemControlHandle* pHandle1 = new ItemControlHandle(this); + pHandle1->setType(T_editShape); + pHandle1->setTag(H_edit); + m_vecHanle.insert(H_edit,pHandle1); + //纵轴Y控制点 + ItemControlHandle* pHandle2 = new ItemControlHandle(this); + pHandle2->setType(T_editShape); + pHandle2->setTag(H_edit+1); + m_vecHanle.insert(H_edit+1,pHandle2); + } +} + +GraphicsRectItem::~GraphicsRectItem() +{ + +} + +QPainterPath GraphicsRectItem::shape() +{ + QPainterPath path; + double dHandleX = 0.0; + double dHandleY = 0.0; + if(m_dRatioX>0) + dHandleX = m_dWidth * m_dRatioX + 0.5; + if(m_dRatioY>0) + dHandleY = m_dHeight * m_dRatioY + 0.5; + + if(m_bIsRound) + path.addRoundedRect(m_boundingRect, dHandleX, dHandleY); + else + path.addRect(m_boundingRect); + + return path; +} + +void GraphicsRectItem::updateHandles() +{ + GraphicsBaseItem::updateHandles(); + if(m_bIsRound && m_vecHanle.size() == H_edit + 1) + { + const QRectF& boundingRect = this->boundingRect(); + //H_edit=9所以index号需要-1 + if(m_vecHanle.contains(H_edit -1)) + { + m_vecHanle[H_edit -1]->move(boundingRect.right() - boundingRect.width() * m_dRatioX, boundingRect.top()); + } + if(m_vecHanle.contains(H_edit + 1 -1)) + { + m_vecHanle[H_edit + 1 -1]->move(boundingRect.right(), boundingRect.top() + boundingRect.height() * m_dRatioY); + } + + } +} + +void GraphicsRectItem::updateCoordinate() //当执行了resie和editShape函数后,boundingRect发生了变换,需要将item的原点(以中心点为原点)校准至boundingRect.center() +{ + if (!parentItem()) + { + QPointF pt1, pt2, delta; + pt1 = mapToScene(QPointF(0, 0)); + pt2 = mapToScene(m_boundingRect.center()); + delta = pt1 - pt2; + + prepareGeometryChange(); + //将boundingRect设置成中心点和原点(也是默认变换原点),这样三点重合,有助于简化计算 + m_boundingRect = QRectF(-m_dWidth / 2, -m_dHeight / 2, m_dWidth, m_dHeight); + //setTransformOriginPoint(m_boundingRect.center()); //变换中心默认为item的(0,0)点,所以不执行这句话也没有问题 + //更新bouondingRect后重回会显示位置会有变化,需要做对应的移动 + moveBy(-delta.x(), -delta.y()); + updateHandles(); + } + + m_lastBoudingRect = m_boundingRect; + //qDebug() << "itemPos:" << mapToParent(pos()); +} + +void GraphicsRectItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + painter->setPen(m_pen); + painter->setBrush(m_brush); + + if(m_bIsRound) + { + double dRadiusX = 0.0; + double dRadiusY = 0.0; + if(m_dRatioX>0) + dRadiusX = m_dWidth * m_dRatioX + 0.5; + if(m_dRatioY>0) + dRadiusY = m_dHeight * m_dRatioY + 0.5; + + painter->drawRoundedRect(m_boundingRect, dRadiusX, dRadiusY); + } + else + painter->drawRect(m_boundingRect); + + + if (option->state & QStyle::State_Selected) //是选中状态,绘制选中框 + { + int nPenWidth = 1; + painter->setPen(QPen(QColor(135,206,235,180))); //蓝色的外框 + + painter->setBrush(QBrush(QColor(135,206,235,180))); + + /*double dx = m_boundingRect_selected.x(); + double dy = m_boundingRect_selected.y(); + double dWidth = m_boundingRect_selected.width(); + double dHeight = m_boundingRect_selected.height(); + + + //画弧线0度是3点钟方向 + if(dWidth > dHeight) + { + painter->drawRect(m_boundingRect_selected); + painter->drawChord(dx-dHeight*0.5,dy,dHeight,dHeight,90*16,180*16); + painter->drawChord(dx+dWidth-dHeight*0.5,dy,dHeight,dHeight,(-90)*16,180*16); + } + else if(dWidth < dHeight) + { + painter->drawRect(m_boundingRect_selected); + painter->drawChord(dx,dy-dWidth*0.5,dWidth,dWidth,0*16,180*16); + painter->drawChord(dx,dy+dHeight-dWidth*0.5,dWidth,dWidth,180*16,180*16); + //painter->drawChord(dx+dWidth-dHeight*0.5,dy,dHeight,dHeight,(-90)*16,180*16); + } + else + { + painter->drawEllipse(QPointF(dx+dWidth*0.5,dy+dHeight*0.5),dHeight*0.5,dHeight*0.5); + }*/ + painter->drawRect(m_boundingRect_selected); + + //绘制变换原点 + QPointF originPoint = transformOriginPoint(); + //qDebug() << "originPoint:" << originPoint; + painter->setBrush(Qt::red); + painter->drawEllipse(originPoint, 4, 4); + } +} + +void GraphicsRectItem::resize(int nHandle,double dSX, double dSY, const QPointF& basePoint) +{ + switch (nHandle) + { + case H_left: + case H_right: + dSY = 1; //拖拽的是左点右点,为水平缩放,忽略垂直变化 + break; + case H_top: + case H_bottom: + dSX = 1; //拖拽的是顶点底点,为垂直缩放,忽略水平变化 + break; + default: + break; + } + + QTransform trans; + //缩放是以图元原点(中心)位置为基准,所以每帧都先移动移动到想要的基准点,缩放之后再移回 + trans.translate(basePoint.x(), basePoint.y()); + trans.scale(dSX, dSY); + trans.translate(-basePoint.x(), -basePoint.y()); + + prepareGeometryChange(); + m_boundingRect = trans.mapRect(m_lastBoudingRect); + m_dWidth = m_boundingRect.width(); + m_dHeight = m_boundingRect.height(); + updateHandles(); +} + +void GraphicsRectItem::move(const QPointF& point) +{ + moveBy(point.x(), point.y()); +} + +void GraphicsRectItem::editShape(int nHandle,const QPointF& ptMouse) +{ + QPointF ptOnItem = mapFromParent(ptMouse); + switch (nHandle) + { + //横轴X控制点 + case H_edit: + { + double dMouseX = ptOnItem.x(); + if(dMouseX < m_boundingRect.center().x()) + dMouseX = m_boundingRect.center().x(); + else if(dMouseX > m_boundingRect.right()) + dMouseX = m_boundingRect.right(); + double dWidth = m_boundingRect.width(); + if(dWidth == 0.0) + dWidth = 1.0; + m_dRatioX = (m_boundingRect.right() - dMouseX) / dWidth; + } + break; + //纵轴Y控制点 + case H_edit + 1: + { + double dMouseY = ptOnItem.y(); + if(dMouseY > m_boundingRect.center().y()) + dMouseY = m_boundingRect.center().y(); + else if(dMouseY < m_boundingRect.top()) + dMouseY = m_boundingRect.top(); + double dHeight = m_boundingRect.height(); + if(dHeight == 0.0) + dHeight = 1.0; + m_dRatioY = (dMouseY - m_boundingRect.top()) / dHeight; + } + break; + default: + break; + } + + prepareGeometryChange(); + updateHandles(); +} diff --git a/source/graphicsItem/itemControlHandle.cpp b/source/graphicsItem/itemControlHandle.cpp new file mode 100644 index 0000000..768f80e --- /dev/null +++ b/source/graphicsItem/itemControlHandle.cpp @@ -0,0 +1,77 @@ +#include "graphicsItem/itemControlHandle.h" +#include + +#define HNDLE_SIZE 8 + +ItemControlHandle::ItemControlHandle(QGraphicsItem *parent) + : QGraphicsRectItem(-HNDLE_SIZE / 2, + -HNDLE_SIZE / 2, + HNDLE_SIZE, + HNDLE_SIZE, parent) +{ + m_type = T_resize; + m_tag = H_none; +} + +ItemControlHandle::~ItemControlHandle() +{ +} + +void ItemControlHandle::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + painter->setPen(Qt::SolidLine); + painter->setRenderHint(QPainter::Antialiasing, true); + + if(m_type==T_resize) + { + painter->setBrush(Qt::white); + painter->drawRect(rect()); + } + else if(m_type==T_rotate) + { + painter->setPen(Qt::NoPen); + painter->setBrush(Qt::NoBrush); + painter->drawRect(rect()); + } + else if(m_type==T_editShape) + { + painter->setBrush(Qt::green); + painter->drawEllipse(rect().center(), HNDLE_SIZE / 2, HNDLE_SIZE / 2); + } + else if(m_type==T_lineIn) + { + painter->setPen(Qt::NoPen); + painter->setBrush(Qt::green); + painter->drawEllipse(rect().center(), HNDLE_SIZE / 2, HNDLE_SIZE / 2); + } + else if(m_type==T_lineOut) + { + painter->setPen(Qt::NoPen); + painter->setBrush(Qt::red); + painter->drawEllipse(rect().center(), HNDLE_SIZE / 2, HNDLE_SIZE / 2); + } +} + +void ItemControlHandle::hoverEnterEvent(QGraphicsSceneHoverEvent* event) +{ + QGraphicsRectItem::hoverEnterEvent(event); +} + +void ItemControlHandle::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) +{ + QGraphicsRectItem::hoverLeaveEvent(event); +} + +int ItemControlHandle::getSize() +{ + int nSize = HNDLE_SIZE; + return nSize; +} + +void ItemControlHandle::move(double x, double y) +{ + setPos(x, y); +} diff --git a/source/graphicsViewStyle.cpp b/source/graphicsViewStyle.cpp new file mode 100644 index 0000000..2238b54 --- /dev/null +++ b/source/graphicsViewStyle.cpp @@ -0,0 +1,93 @@ +#include "graphicsViewStyle.h" + +#include +#include +#include +#include + +#include "styleCollection.h" + + +inline void initResources() +{ + Q_INIT_RESOURCE(DiagramDesigner); +} + +GraphicsViewStyle::GraphicsViewStyle() +{ + // Explicit resources inialization for preventing the static initialization + // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order + initResources(); + + // This configuration is stored inside the compiled unit and is loaded statically + loadJsonFile(":DefaultStyle.json"); +} + +GraphicsViewStyle::GraphicsViewStyle(QString jsonText) +{ + loadJsonText(jsonText); +} + +void GraphicsViewStyle::setStyle(QString jsonText) +{ + GraphicsViewStyle style(jsonText); + + StyleCollection::setGraphicsViewStyle(style); +} + +#ifdef STYLE_DEBUG +#define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(v, variable) \ + { \ + if (v.type() == QJsonValue::Undefined || v.type() == QJsonValue::Null) \ + qWarning() << "Undefined value for parameter:" << #variable; \ + } +#else +#define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(v, variable) +#endif + +#define FLOW_VIEW_STYLE_READ_COLOR(values, variable) \ + { \ + auto valueRef = values[#variable]; \ + FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ + if (valueRef.isArray()) { \ + auto colorArray = valueRef.toArray(); \ + std::vector rgb; \ + rgb.reserve(3); \ + for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ + rgb.push_back((*it).toInt()); \ + } \ + variable = QColor(rgb[0], rgb[1], rgb[2]); \ + } else { \ + variable = QColor(valueRef.toString()); \ + } \ + } + +#define FLOW_VIEW_STYLE_WRITE_COLOR(values, variable) \ + { \ + values[#variable] = variable.name(); \ + } + +void GraphicsViewStyle::loadJson(QJsonObject const &json) +{ + QJsonValue nodeStyleValues = json["GraphicsViewStyle"]; + + QJsonObject obj = nodeStyleValues.toObject(); + + FLOW_VIEW_STYLE_READ_COLOR(obj, BackgroundColor); + FLOW_VIEW_STYLE_READ_COLOR(obj, FineGridColor); + FLOW_VIEW_STYLE_READ_COLOR(obj, CoarseGridColor); +} + +QJsonObject GraphicsViewStyle::toJson() const +{ + QJsonObject obj; + + FLOW_VIEW_STYLE_WRITE_COLOR(obj, BackgroundColor); + FLOW_VIEW_STYLE_WRITE_COLOR(obj, FineGridColor); + FLOW_VIEW_STYLE_WRITE_COLOR(obj, CoarseGridColor); + + QJsonObject root; + root["GraphicsViewStyle"] = obj; + + return root; +} diff --git a/source/locateNode.cpp b/source/locateNode.cpp new file mode 100644 index 0000000..75cbd93 --- /dev/null +++ b/source/locateNode.cpp @@ -0,0 +1,36 @@ +#include "locateNode.h" + +#include +#include + +#include "nodeGraphicsObject.h" + +NodeGraphicsObject *locateNodeAt(QPointF scenePoint, + QGraphicsScene &scene, + QTransform const &viewTransform) +{ + // items under cursor + QList items = scene.items(scenePoint, + Qt::IntersectsItemShape, + Qt::DescendingOrder, + viewTransform); + + // items convertable to NodeGraphicsObject + std::vector filteredItems; + + std::copy_if(items.begin(), + items.end(), + std::back_inserter(filteredItems), + [](QGraphicsItem *item) { + return (qgraphicsitem_cast(item) != nullptr); + }); + + NodeGraphicsObject *node = nullptr; + + if (!filteredItems.empty()) { + QGraphicsItem *graphicsItem = filteredItems.front(); + node = dynamic_cast(graphicsItem); + } + + return node; +} diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..fa4c4fd --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + CMainWindow w; + w.show(); + return a.exec(); +} diff --git a/source/mainwindow.cpp b/source/mainwindow.cpp new file mode 100644 index 0000000..68acf0f --- /dev/null +++ b/source/mainwindow.cpp @@ -0,0 +1,194 @@ +#include "mainwindow.h" + +#include "graphicsItem/graphicsItemGroup.h" +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "diagramCavas.h" +#include "designerScene.h" +#include "graphicElementsPanel.h" +#include "operationCommand.h" +#include "electricElementsBox.h" +#include "electricElementsPanel.h" +#include "toolBox.h" + +//using namespace ads; + + +CMainWindow::CMainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::CMainWindow) +{ + ui->setupUi(this); + m_pUndoStack = nullptr; + + initializeDockUi(); + initializeAction(); + +} + +CMainWindow::~CMainWindow() +{ + delete ui; + if(m_pElectricElementsBox) + delete m_pElectricElementsBox; +} + + +void CMainWindow::closeEvent(QCloseEvent* event) +{ + // Delete dock manager here to delete all floating widgets. This ensures + // that all top level windows of the dock manager are properly closed + QMainWindow::closeEvent(event); +} + +void CMainWindow::changeEvent(QEvent* event) +{ + //if (event->type() == QEvent::WindowStateChange) + //m_pDrawingPanel->grahpicsViewZoomFit(); +} + +void CMainWindow::initializeDockUi() +{ + /*ElectricElementsPanel* pPanel1 = new ElectricElementsPanel(); + QMap map1; + map1.insert(QString::fromWCharArray(L"三角"),GIT_rect); + map1.insert(QString::fromWCharArray(L"四边"),GIT_rect); + pPanel1->setData(map1); + pPanel1->show();*/ + + m_pElectricElementsBox = new ElectricElementsBox(); + m_pElectricElementsBox->initial(); + QWidget* pBox = m_pElectricElementsBox->getToolBox(); + QDockWidget* ElectricElementsDock = new QDockWidget(QString::fromWCharArray(L"图元面板"),this); + ElectricElementsDock->setWidget(pBox); + ElectricElementsDock->setMinimumSize(200,150); + ElectricElementsDock->setAllowedAreas(Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea); + this->addDockWidget(Qt::RightDockWidgetArea,ElectricElementsDock); + + m_pDiagramCavas = new DiagramCavas(); + m_pDiagramCavas->initial(); + this->setCentralWidget(m_pDiagramCavas); + connect(m_pElectricElementsBox,&ElectricElementsBox::addEletricItem,m_pDiagramCavas,&DiagramCavas::onSignal_addGraphicsItem); + + /*QTableWidget* propertiesTable = new QTableWidget(); + propertiesTable->setColumnCount(3); + propertiesTable->setRowCount(10); + CDockWidget* PropertiesDockWidget = new CDockWidget(QString::fromWCharArray(L"属性编辑器")); + PropertiesDockWidget->setWidget(propertiesTable); + PropertiesDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + PropertiesDockWidget->resize(250, 150); + PropertiesDockWidget->setMinimumSize(200,150); + DockManager->addDockWidget(DockWidgetArea::RightDockWidgetArea, PropertiesDockWidget, CentralDockArea); + ui->menuView->addAction(PropertiesDockWidget->toggleViewAction());*/ +} + +void CMainWindow::initializeAction() +{ + //撤销、重做 + m_pUndoStack = new QUndoStack(this); + ui->actionUndo = m_pUndoStack->createUndoAction(this, tr("撤销")); + ui->actionUndo->setIcon(QIcon::fromTheme(QString::fromUtf8("edit-undo"))); + ui->actionUndo->setShortcuts(QKeySequence::Undo); + ui->actionRedo = m_pUndoStack->createRedoAction(this, tr("重做")); + ui->actionRedo->setIcon(QIcon::fromTheme(QString::fromUtf8("edit-redo"))); + ui->actionRedo->setShortcuts(QKeySequence::Redo); + ui->toolBar->addAction(ui->actionUndo); + ui->toolBar->addAction(ui->actionRedo); + ui->actionUndo->setEnabled(m_pUndoStack->canUndo()); + ui->actionRedo->setEnabled(m_pUndoStack->canRedo()); + + ui->actionDelete->setShortcut(QKeySequence::Delete); + + connect(ui->actionDelete, SIGNAL(triggered()), this, SLOT(onSignal_deleteItem())); + connect(ui->actionZoomIn, SIGNAL(triggered()), this, SLOT(onAction_zoomIn())); + connect(ui->actionZoomOut, SIGNAL(triggered()), this, SLOT(onAction_zoomOut())); + connect(ui->actionZoomFit, SIGNAL(triggered()), this, SLOT(onAction_zoomFit())); + connect(ui->actionGroup, SIGNAL(triggered()), this, SLOT(onAction_createGroup())); + connect(ui->actionUngroup, SIGNAL(triggered()), this, SLOT(onAction_destroyGroup())); +} + +void CMainWindow::onAction_zoomIn() +{ + //m_pDrawingPanel->grahpicsViewZoomIn(); +} + +void CMainWindow::onAction_zoomOut() +{ + //m_pDrawingPanel->grahpicsViewZoomOut(); +} + +void CMainWindow::onAction_zoomFit() +{ + //m_pDrawingPanel->grahpicsViewZoomFit(); +} + +void CMainWindow::onAction_createGroup() +{ + /*GraphicsItemGroup* group = m_pDrawingPanel->createItemGroup(); + if(group) + { + QGraphicsScene* scene = m_pDrawingPanel->getQGraphicsScene(); + QUndoCommand* createItemGropu = new CreateItemGoupCommand(group, scene); + m_pUndoStack->push(createItemGropu); + }*/ +} + +void CMainWindow::onAction_destroyGroup() +{ + /*QGraphicsScene* scene = m_pDrawingPanel->getQGraphicsScene(); + QList listItem = scene->selectedItems(); + if(listItem.count() != 1) + return; //只能选择一个解组 + + QGraphicsItem* item = listItem.first(); + if(!item) + return; + + GraphicsItemGroup *group = dynamic_cast(item); + if(group) + { + QUndoCommand* destroyItemGropu = new DestroyItemGoupCommand(group, scene); + m_pUndoStack->push(destroyItemGropu); + }*/ +} + +void CMainWindow::onSignal_addItem(QGraphicsItem* item) +{ + if(item) + { + QUndoCommand* addItemCommand = new AddItemCommand(item, item->scene()); + m_pUndoStack->push(addItemCommand); + } +} + +void CMainWindow::onSignal_deleteItem() +{ + /*QGraphicsScene* scene = m_pDrawingPanel->getQGraphicsScene(); + if (scene && scene->selectedItems().isEmpty()) + return; + + QUndoCommand* deleteItemCommand = new DeleteItemCommand(scene); + m_pUndoStack->push(deleteItemCommand); //push时会自动调用一次command的redo函数*/ +} + +GraphicElementsPanel* CMainWindow::graphicsElementsPanel() const +{ + if(m_pGraphicElementsPanel) + return m_pGraphicElementsPanel; + else + { + qDebug("get m_pGraphicElementsPanel err"); + return NULL; + } +} + + diff --git a/source/nodeConnectionInteraction.cpp b/source/nodeConnectionInteraction.cpp new file mode 100644 index 0000000..cfbf5bd --- /dev/null +++ b/source/nodeConnectionInteraction.cpp @@ -0,0 +1,149 @@ +#include "nodeConnectionInteraction.h" + +#include "abstractNodeGeometry.h" +#include "basicGraphicsScene.h" +#include "connectionGraphicsObject.h" +#include "connectionIdUtils.h" +#include "nodeGraphicsObject.h" +#include "undoCommands.h" + +#include + +#include + + +NodeConnectionInteraction::NodeConnectionInteraction(NodeGraphicsObject &ngo, + ConnectionGraphicsObject &cgo, + BasicGraphicsScene &scene) + : _ngo(ngo) + , _cgo(cgo) + , _scene(scene) +{} + +bool NodeConnectionInteraction::canConnect(PortIndex *portIndex) const +{ + // 1. Connection requires a port. + + PortType requiredPort = _cgo.connectionState().requiredPort(); + + if (requiredPort == PortType::None) { + return false; + } + + NodeId connectedNodeId = getNodeId(oppositePort(requiredPort), _cgo.connectionId()); + + // 2. Forbid connecting the node to itself. + + if (_ngo.nodeId() == connectedNodeId) + return false; + + // 3. Connection loose end is above the node port. + + QPointF connectionPoint = _cgo.sceneTransform().map(_cgo.endPoint(requiredPort)); + + *portIndex = nodePortIndexUnderScenePoint(requiredPort, connectionPoint); + + if (*portIndex == InvalidPortIndex) { + return false; + } + + // 4. Model allows connection. + + AbstractGraphModel &model = _ngo.nodeScene()->graphModel(); + + ConnectionId connectionId = makeCompleteConnectionId(_cgo.connectionId(), // incomplete + _ngo.nodeId(), // missing node id + *portIndex); // missing port index + + return model.connectionPossible(connectionId); +} + +bool NodeConnectionInteraction::tryConnect() const +{ + // 1. Check conditions from 'canConnect'. + + PortIndex targetPortIndex = InvalidPortIndex; + if (!canConnect(&targetPortIndex)) { + return false; + } + + // 2. Create new connection. + + ConnectionId incompleteConnectionId = _cgo.connectionId(); + + ConnectionId newConnectionId = makeCompleteConnectionId(incompleteConnectionId, + _ngo.nodeId(), + targetPortIndex); + + _ngo.nodeScene()->resetDraftConnection(); + + _ngo.nodeScene()->undoStack().push(new ConnectCommand(_ngo.nodeScene(), newConnectionId)); + + return true; +} + +bool NodeConnectionInteraction::disconnect(PortType portToDisconnect) const +{ + ConnectionId connectionId = _cgo.connectionId(); + + _scene.undoStack().push(new DisconnectCommand(&_scene, connectionId)); + + AbstractNodeGeometry &geometry = _scene.nodeGeometry(); + + QPointF scenePos = geometry.portScenePosition(_ngo.nodeId(), + portToDisconnect, + getPortIndex(portToDisconnect, connectionId), + _ngo.sceneTransform()); + + // Converted to "draft" connection with the new incomplete id. + ConnectionId incompleteConnectionId = makeIncompleteConnectionId(connectionId, portToDisconnect); + + // Grabs the mouse + auto const &draftConnection = _scene.makeDraftConnection(incompleteConnectionId); + + QPointF const looseEndPos = draftConnection->mapFromScene(scenePos); + draftConnection->setEndPoint(portToDisconnect, looseEndPos); + + // Repaint connection points. + NodeId connectedNodeId = getNodeId(oppositePort(portToDisconnect), connectionId); + _scene.nodeGraphicsObject(connectedNodeId)->update(); + + NodeId disconnectedNodeId = getNodeId(portToDisconnect, connectionId); + _scene.nodeGraphicsObject(disconnectedNodeId)->update(); + + return true; +} + +// ------------------ util functions below + +PortType NodeConnectionInteraction::connectionRequiredPort() const +{ + auto const &state = _cgo.connectionState(); + + return state.requiredPort(); +} + +QPointF NodeConnectionInteraction::nodePortScenePosition(PortType portType, + PortIndex portIndex) const +{ + AbstractNodeGeometry &geometry = _scene.nodeGeometry(); + + QPointF p = geometry.portScenePosition(_ngo.nodeId(), + portType, + portIndex, + _ngo.sceneTransform()); + + return p; +} + +PortIndex NodeConnectionInteraction::nodePortIndexUnderScenePoint(PortType portType, + QPointF const &scenePoint) const +{ + AbstractNodeGeometry &geometry = _scene.nodeGeometry(); + + QTransform sceneTransform = _ngo.sceneTransform(); + + QPointF nodePoint = sceneTransform.inverted().map(scenePoint); + + return geometry.checkPortHit(_ngo.nodeId(), portType, nodePoint); +} diff --git a/source/nodeDelegateModel.cpp b/source/nodeDelegateModel.cpp new file mode 100644 index 0000000..83432af --- /dev/null +++ b/source/nodeDelegateModel.cpp @@ -0,0 +1,52 @@ +#include "nodeDelegateModel.h" + +#include "styleCollection.h" + + +NodeDelegateModel::NodeDelegateModel() + : _nodeStyle(StyleCollection::nodeStyle()) +{ + // Derived classes can initialize specific style here +} + +QJsonObject NodeDelegateModel::save() const +{ + QJsonObject modelJson; + + modelJson["model-name"] = name(); + + return modelJson; +} + +void NodeDelegateModel::load(QJsonObject const &) +{ + // +} + +ConnectionPolicy NodeDelegateModel::portConnectionPolicy(PortType portType, PortIndex) const +{ + auto result = ConnectionPolicy::One; + switch (portType) { + case PortType::In: + result = ConnectionPolicy::One; + break; + case PortType::Out: + result = ConnectionPolicy::Many; + break; + case PortType::None: + break; + } + + return result; +} + +NodeStyle const &NodeDelegateModel::nodeStyle() const +{ + return _nodeStyle; +} + +void NodeDelegateModel::setNodeStyle(NodeStyle const &style) +{ + _nodeStyle = style; +} + diff --git a/source/nodeDelegateModelRegistry.cpp b/source/nodeDelegateModelRegistry.cpp new file mode 100644 index 0000000..1cf39df --- /dev/null +++ b/source/nodeDelegateModelRegistry.cpp @@ -0,0 +1,35 @@ +#include "nodeDelegateModelRegistry.h" +#include "nodeData.h" +#include "nodeDelegateModel.h" + +#include +#include + + +QSharedPointer NodeDelegateModelRegistry::create(QString const &modelName) +{ + auto it = _registeredItemCreators.find(modelName); + + if (it != _registeredItemCreators.end()) { + return *it; + } + + return nullptr; +} + +NodeDelegateModelRegistry::RegisteredModelCreatorsMap const & +NodeDelegateModelRegistry::registeredModelCreators() const +{ + return _registeredItemCreators; +} + +NodeDelegateModelRegistry::RegisteredModelsCategoryMap const & +NodeDelegateModelRegistry::registeredModelsCategoryAssociation() const +{ + return _registeredModelsCategory; +} + +NodeDelegateModelRegistry::CategoriesSet const &NodeDelegateModelRegistry::categories() const +{ + return _categories; +} diff --git a/source/nodeGraphicsObject.cpp b/source/nodeGraphicsObject.cpp new file mode 100644 index 0000000..5afc337 --- /dev/null +++ b/source/nodeGraphicsObject.cpp @@ -0,0 +1,360 @@ +#include "nodeGraphicsObject.h" + +#include +#include + +#include +#include + +#include "abstractGraphModel.h" +#include "abstractNodeGeometry.h" +#include "abstractNodePainter.h" +#include "basicGraphicsScene.h" +#include "connectionGraphicsObject.h" +#include "connectionIdUtils.h" +#include "nodeConnectionInteraction.h" +#include "styleCollection.h" +#include "undoCommands.h" + + +NodeGraphicsObject::NodeGraphicsObject(BasicGraphicsScene &scene, NodeId nodeId) + : _nodeId(nodeId) + , _graphModel(scene.graphModel()) + , _nodeState(*this) + , _proxyWidget(nullptr) +{ + scene.addItem(this); + + setFlag(QGraphicsItem::ItemDoesntPropagateOpacityToChildren, true); + setFlag(QGraphicsItem::ItemIsFocusable, true); + + setLockedState(); + + setCacheMode(QGraphicsItem::DeviceCoordinateCache); + + QJsonObject nodeStyleJson = _graphModel.nodeData(_nodeId, NodeRole::Style).toJsonObject(); + + NodeStyle nodeStyle(nodeStyleJson); + + { + auto effect = new QGraphicsDropShadowEffect; + effect->setOffset(4, 4); + effect->setBlurRadius(20); + effect->setColor(nodeStyle.ShadowColor); + + setGraphicsEffect(effect); + } + + setOpacity(nodeStyle.Opacity); + + setAcceptHoverEvents(true); + + setZValue(0); + + embedQWidget(); + + nodeScene()->nodeGeometry().recomputeSize(_nodeId); + + QPointF const pos = _graphModel.nodeData(_nodeId, NodeRole::Position); + + setPos(pos); + + connect(&_graphModel, &AbstractGraphModel::nodeFlagsUpdated, [this](NodeId const nodeId) { + if (_nodeId == nodeId) + setLockedState(); + }); +} + +AbstractGraphModel &NodeGraphicsObject::graphModel() const +{ + return _graphModel; +} + +BasicGraphicsScene *NodeGraphicsObject::nodeScene() const +{ + return dynamic_cast(scene()); +} + +void NodeGraphicsObject::embedQWidget() +{ + AbstractNodeGeometry &geometry = nodeScene()->nodeGeometry(); + geometry.recomputeSize(_nodeId); + + if (auto w = _graphModel.nodeData(_nodeId, NodeRole::Widget).value()) { + _proxyWidget = new QGraphicsProxyWidget(this); + + _proxyWidget->setWidget(w); + + _proxyWidget->setPreferredWidth(5); + + geometry.recomputeSize(_nodeId); + + if (w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag) { + unsigned int widgetHeight = geometry.size(_nodeId).height() + - geometry.captionRect(_nodeId).height(); + + // If the widget wants to use as much vertical space as possible, set + // it to have the geom's equivalentWidgetHeight. + _proxyWidget->setMinimumHeight(widgetHeight); + } + + _proxyWidget->setPos(geometry.widgetPosition(_nodeId)); + + //update(); + + _proxyWidget->setOpacity(1.0); + _proxyWidget->setFlag(QGraphicsItem::ItemIgnoresParentOpacity); + } +} + +void NodeGraphicsObject::setLockedState() +{ + NodeFlags flags = _graphModel.nodeFlags(_nodeId); + + bool const locked = flags.testFlag(NodeFlag::Locked); + + setFlag(QGraphicsItem::ItemIsMovable, !locked); + setFlag(QGraphicsItem::ItemIsSelectable, !locked); + setFlag(QGraphicsItem::ItemSendsScenePositionChanges, !locked); +} + +QRectF NodeGraphicsObject::boundingRect() const +{ + AbstractNodeGeometry &geometry = nodeScene()->nodeGeometry(); + return geometry.boundingRect(_nodeId); + //return NodeGeometry(_nodeId, _graphModel, nodeScene()).boundingRect(); +} + +void NodeGraphicsObject::setGeometryChanged() +{ + prepareGeometryChange(); +} + +void NodeGraphicsObject::moveConnections() const +{ + auto const &connected = _graphModel.allConnectionIds(_nodeId); + + for (auto &cnId : connected) { + auto cgo = nodeScene()->connectionGraphicsObject(cnId); + + if (cgo) + cgo->move(); + } +} + +void NodeGraphicsObject::reactToConnection(ConnectionGraphicsObject const *cgo) +{ + _nodeState.storeConnectionForReaction(cgo); + + update(); +} + +void NodeGraphicsObject::paint(QPainter *painter, QStyleOptionGraphicsItem const *option, QWidget *) +{ + painter->setClipRect(option->exposedRect); + + nodeScene()->nodePainter().paint(painter, *this); +} + +QVariant NodeGraphicsObject::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemScenePositionHasChanged && scene()) { + moveConnections(); + } + + return QGraphicsObject::itemChange(change, value); +} + +void NodeGraphicsObject::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + //if (_nodeState.locked()) + //return; + + AbstractNodeGeometry &geometry = nodeScene()->nodeGeometry(); + + for (PortType portToCheck : {PortType::In, PortType::Out}) { + QPointF nodeCoord = sceneTransform().inverted().map(event->scenePos()); + + PortIndex const portIndex = geometry.checkPortHit(_nodeId, portToCheck, nodeCoord); + + if (portIndex == InvalidPortIndex) + continue; + + auto const &connected = _graphModel.connections(_nodeId, portToCheck, portIndex); + + // Start dragging existing connection. + if (!connected.empty() && portToCheck == PortType::In) { + auto const &cnId = *connected.begin(); + + // Need ConnectionGraphicsObject + + NodeConnectionInteraction interaction(*this, + *nodeScene()->connectionGraphicsObject(cnId), + *nodeScene()); + + if (_graphModel.detachPossible(cnId)) + interaction.disconnect(portToCheck); + } else // initialize new Connection + { + if (portToCheck == PortType::Out) { + auto const outPolicy = _graphModel + .portData(_nodeId, + portToCheck, + portIndex, + PortRole::ConnectionPolicyRole) + .value(); + + if (!connected.empty() && outPolicy == ConnectionPolicy::One) { + for (auto &cnId : connected) { + _graphModel.deleteConnection(cnId); + } + } + } // if port == out + + ConnectionId const incompleteConnectionId = makeIncompleteConnectionId(_nodeId, + portToCheck, + portIndex); + + nodeScene()->makeDraftConnection(incompleteConnectionId); + } + } + + if (_graphModel.nodeFlags(_nodeId) & NodeFlag::Resizable) { + auto pos = event->pos(); + bool const hit = geometry.resizeHandleRect(_nodeId).contains(QPoint(pos.x(), pos.y())); + _nodeState.setResizing(hit); + } + + QGraphicsObject::mousePressEvent(event); + + if (isSelected()) { + Q_EMIT nodeScene()->nodeSelected(_nodeId); + } +} + +void NodeGraphicsObject::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + // Deselect all other items after this one is selected. + // Unless we press a CTRL button to add the item to the selected group before + // starting moving. + if (!isSelected()) { + if (!event->modifiers().testFlag(Qt::ControlModifier)) + scene()->clearSelection(); + + setSelected(true); + } + + if (_nodeState.resizing()) { + auto diff = event->pos() - event->lastPos(); + + if (auto w = _graphModel.nodeData(_nodeId, NodeRole::Widget)) { + prepareGeometryChange(); + + auto oldSize = w->size(); + + oldSize += QSize(diff.x(), diff.y()); + + w->resize(oldSize); + + AbstractNodeGeometry &geometry = nodeScene()->nodeGeometry(); + + // Passes the new size to the model. + geometry.recomputeSize(_nodeId); + + update(); + + moveConnections(); + + event->accept(); + } + } else { + auto diff = event->pos() - event->lastPos(); + + nodeScene()->undoStack().push(new MoveNodeCommand(nodeScene(), diff)); + + event->accept(); + } + + QRectF r = nodeScene()->sceneRect(); + + r = r.united(mapToScene(boundingRect()).boundingRect()); + + nodeScene()->setSceneRect(r); +} + +void NodeGraphicsObject::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + _nodeState.setResizing(false); + + QGraphicsObject::mouseReleaseEvent(event); + + // position connections precisely after fast node move + moveConnections(); + + nodeScene()->nodeClicked(_nodeId); +} + +void NodeGraphicsObject::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + // bring all the colliding nodes to background + QList overlapItems = collidingItems(); + + for (QGraphicsItem *item : overlapItems) { + if (item->zValue() > 0.0) { + item->setZValue(0.0); + } + } + + // bring this node forward + setZValue(1.0); + + _nodeState.setHovered(true); + + update(); + + Q_EMIT nodeScene()->nodeHovered(_nodeId, event->screenPos()); + + event->accept(); +} + +void NodeGraphicsObject::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + _nodeState.setHovered(false); + + setZValue(0.0); + + update(); + + Q_EMIT nodeScene()->nodeHoverLeft(_nodeId); + + event->accept(); +} + +void NodeGraphicsObject::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + auto pos = event->pos(); + + //NodeGeometry geometry(_nodeId, _graphModel, nodeScene()); + AbstractNodeGeometry &geometry = nodeScene()->nodeGeometry(); + + if ((_graphModel.nodeFlags(_nodeId) | NodeFlag::Resizable) + && geometry.resizeHandleRect(_nodeId).contains(QPoint(pos.x(), pos.y()))) { + setCursor(QCursor(Qt::SizeFDiagCursor)); + } else { + setCursor(QCursor()); + } + + event->accept(); +} + +void NodeGraphicsObject::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem::mouseDoubleClickEvent(event); + + Q_EMIT nodeScene()->nodeDoubleClicked(_nodeId); +} + +void NodeGraphicsObject::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + Q_EMIT nodeScene()->nodeContextMenu(_nodeId, mapToScene(event->pos())); +} diff --git a/source/nodeState.cpp b/source/nodeState.cpp new file mode 100644 index 0000000..0d14cb1 --- /dev/null +++ b/source/nodeState.cpp @@ -0,0 +1,38 @@ +#include "nodeState.h" + +#include "connectionGraphicsObject.h" +#include "nodeGraphicsObject.h" + +NodeState::NodeState(NodeGraphicsObject &ngo) + : _ngo(ngo) + , _hovered(false) + , _resizing(false) + , _connectionForReaction{nullptr} +{ + Q_UNUSED(_ngo); +} + +void NodeState::setResizing(bool resizing) +{ + _resizing = resizing; +} + +bool NodeState::resizing() const +{ + return _resizing; +} + +ConnectionGraphicsObject const *NodeState::connectionForReaction() const +{ + return _connectionForReaction.data(); +} + +void NodeState::storeConnectionForReaction(ConnectionGraphicsObject const *cgo) +{ + _connectionForReaction = cgo; +} + +void NodeState::resetConnectionForReaction() +{ + _connectionForReaction.clear(); +} diff --git a/source/nodeStyle.cpp b/source/nodeStyle.cpp new file mode 100644 index 0000000..ac2e00e --- /dev/null +++ b/source/nodeStyle.cpp @@ -0,0 +1,145 @@ +#include "nodeStyle.h" + +//#include + +#include +#include +#include + +#include + +#include "styleCollection.h" + + +inline void initResources() +{ + Q_INIT_RESOURCE(DiagramDesigner); +} + +NodeStyle::NodeStyle() +{ + // Explicit resources inialization for preventing the static initialization + // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order + initResources(); + + // This configuration is stored inside the compiled unit and is loaded statically + loadJsonFile(":DefaultStyle.json"); +} + +NodeStyle::NodeStyle(QString jsonText) +{ + loadJsonText(jsonText); +} + +NodeStyle::NodeStyle(QJsonObject const &json) +{ + loadJson(json); +} + +void NodeStyle::setNodeStyle(QString jsonText) +{ + NodeStyle style(jsonText); + + StyleCollection::setNodeStyle(style); +} + +#ifdef STYLE_DEBUG +#define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable) \ + { \ + if (v.type() == QJsonValue::Undefined || v.type() == QJsonValue::Null) \ + qWarning() << "Undefined value for parameter:" << #variable; \ + } +#else +#define NODE_STYLE_CHECK_UNDEFINED_VALUE(v, variable) +#endif + +#define NODE_STYLE_READ_COLOR(values, variable) \ + { \ + auto valueRef = values[#variable]; \ + NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ + if (valueRef.isArray()) { \ + auto colorArray = valueRef.toArray(); \ + std::vector rgb; \ + rgb.reserve(3); \ + for (auto it = colorArray.begin(); it != colorArray.end(); ++it) { \ + rgb.push_back((*it).toInt()); \ + } \ + variable = QColor(rgb[0], rgb[1], rgb[2]); \ + } else { \ + variable = QColor(valueRef.toString()); \ + } \ + } + +#define NODE_STYLE_WRITE_COLOR(values, variable) \ + { \ + values[#variable] = variable.name(); \ + } + +#define NODE_STYLE_READ_FLOAT(values, variable) \ + { \ + auto valueRef = values[#variable]; \ + NODE_STYLE_CHECK_UNDEFINED_VALUE(valueRef, variable) \ + variable = valueRef.toDouble(); \ + } + +#define NODE_STYLE_WRITE_FLOAT(values, variable) \ + { \ + values[#variable] = variable; \ + } + +void NodeStyle::loadJson(QJsonObject const &json) +{ + QJsonValue nodeStyleValues = json["NodeStyle"]; + + QJsonObject obj = nodeStyleValues.toObject(); + + NODE_STYLE_READ_COLOR(obj, NormalBoundaryColor); + NODE_STYLE_READ_COLOR(obj, SelectedBoundaryColor); + NODE_STYLE_READ_COLOR(obj, GradientColor0); + NODE_STYLE_READ_COLOR(obj, GradientColor1); + NODE_STYLE_READ_COLOR(obj, GradientColor2); + NODE_STYLE_READ_COLOR(obj, GradientColor3); + NODE_STYLE_READ_COLOR(obj, ShadowColor); + NODE_STYLE_READ_COLOR(obj, FontColor); + NODE_STYLE_READ_COLOR(obj, FontColorFaded); + NODE_STYLE_READ_COLOR(obj, ConnectionPointColor); + NODE_STYLE_READ_COLOR(obj, FilledConnectionPointColor); + NODE_STYLE_READ_COLOR(obj, WarningColor); + NODE_STYLE_READ_COLOR(obj, ErrorColor); + + NODE_STYLE_READ_FLOAT(obj, PenWidth); + NODE_STYLE_READ_FLOAT(obj, HoveredPenWidth); + NODE_STYLE_READ_FLOAT(obj, ConnectionPointDiameter); + + NODE_STYLE_READ_FLOAT(obj, Opacity); +} + +QJsonObject NodeStyle::toJson() const +{ + QJsonObject obj; + + NODE_STYLE_WRITE_COLOR(obj, NormalBoundaryColor); + NODE_STYLE_WRITE_COLOR(obj, SelectedBoundaryColor); + NODE_STYLE_WRITE_COLOR(obj, GradientColor0); + NODE_STYLE_WRITE_COLOR(obj, GradientColor1); + NODE_STYLE_WRITE_COLOR(obj, GradientColor2); + NODE_STYLE_WRITE_COLOR(obj, GradientColor3); + NODE_STYLE_WRITE_COLOR(obj, ShadowColor); + NODE_STYLE_WRITE_COLOR(obj, FontColor); + NODE_STYLE_WRITE_COLOR(obj, FontColorFaded); + NODE_STYLE_WRITE_COLOR(obj, ConnectionPointColor); + NODE_STYLE_WRITE_COLOR(obj, FilledConnectionPointColor); + NODE_STYLE_WRITE_COLOR(obj, WarningColor); + NODE_STYLE_WRITE_COLOR(obj, ErrorColor); + + NODE_STYLE_WRITE_FLOAT(obj, PenWidth); + NODE_STYLE_WRITE_FLOAT(obj, HoveredPenWidth); + NODE_STYLE_WRITE_FLOAT(obj, ConnectionPointDiameter); + + NODE_STYLE_WRITE_FLOAT(obj, Opacity); + + QJsonObject root; + root["NodeStyle"] = obj; + + return root; +} diff --git a/source/operationCommand.cpp b/source/operationCommand.cpp new file mode 100644 index 0000000..c5598e0 --- /dev/null +++ b/source/operationCommand.cpp @@ -0,0 +1,141 @@ +#include "operationCommand.h" +#include "graphicsItem/graphicsItemGroup.h" +#include + + +AddItemCommand::AddItemCommand(QGraphicsItem* item, QGraphicsScene* scene, QUndoCommand* parent) + : QUndoCommand(parent) +{ + m_pItem = item; + m_itemPos = item->pos(); + m_pGraphicsScene = scene; +} +AddItemCommand::~AddItemCommand() +{ +} +void AddItemCommand::undo() +{ + m_pGraphicsScene->removeItem(m_pItem); + m_pGraphicsScene->update(); +} +void AddItemCommand::redo() +{ + if(m_pItem->scene()) //因为添加图元后同步创建一条该指令,平且在push进入stack的时候redo会被触发一次,因此这里加判断,防止重复操作 + return; + + m_pGraphicsScene->addItem(m_pItem); + m_pItem->setPos(m_itemPos); + m_pGraphicsScene->update(); +} + +DeleteItemCommand::DeleteItemCommand(QGraphicsScene* scene, QUndoCommand* parent) + : QUndoCommand(parent) +{ + m_pGraphicsScene = scene; + m_listItem = scene->selectedItems(); +} +DeleteItemCommand::~DeleteItemCommand() +{ +} +void DeleteItemCommand::undo() +{ + foreach(QGraphicsItem* item, m_listItem) + { + // QGraphicsItemGroup* group = dynamic_cast(item->parentItem()); + // if(!group) + { + m_pGraphicsScene->addItem(item); + } + } + + m_pGraphicsScene->update(); +} +void DeleteItemCommand::redo() +{ + foreach(QGraphicsItem* item, m_listItem) + { + // QGraphicsItemGroup* group = dynamic_cast(item->parentItem()); + // if(!group) + { + m_pGraphicsScene->removeItem(item); //remove即可,不要delete,因为会影响撤回(undo)操作 + } + } + + m_pGraphicsScene->update(); +} + +CreateItemGoupCommand::CreateItemGoupCommand(GraphicsItemGroup* group, QGraphicsScene* scene, QUndoCommand* parent) + : QUndoCommand(parent) +{ + m_pGroup = group; + m_pGraphicsScene = scene; + m_listItem = group->getItems(); +} +CreateItemGoupCommand::~CreateItemGoupCommand() +{ +} +void CreateItemGoupCommand::undo() +{ + m_pGroup->setSelected(false); + foreach (QGraphicsItem *item, m_listItem) + { + //item->setSelected(true); + m_pGroup->removeFromGroup(item); + AbstractShape *ab = qgraphicsitem_cast(item); + ab->updateCoordinate(); + ab->setSelected(true); + } + m_pGraphicsScene->removeItem(m_pGroup); + m_pGraphicsScene->update(); +} +void CreateItemGoupCommand::redo() +{ + if(!m_pGroup->scene()) //因为添加图元后同步创建一条该指令,平且在push进入stack的时候redo会被触发一次,因此这里加判断,防止重复操作 + { + foreach (QGraphicsItem *item, m_listItem) + { + item->setSelected(false); + m_pGroup->addToGroup(item); + } + m_pGraphicsScene->addItem(m_pGroup); + } + + m_pGroup->setSelected(true); + m_pGraphicsScene->update(); +} + +DestroyItemGoupCommand::DestroyItemGoupCommand(GraphicsItemGroup* group, QGraphicsScene* scene, QUndoCommand* parent) + : QUndoCommand(parent) +{ + m_pGroup = group; + m_pGraphicsScene = scene; + m_listItem = group->getItems(); +} +DestroyItemGoupCommand::~DestroyItemGoupCommand() +{ +} +void DestroyItemGoupCommand::undo() +{ + foreach (QGraphicsItem *item, m_listItem) + { + item->setSelected(false); + m_pGroup->addToGroup(item); + } + m_pGroup->setSelected(true); + m_pGraphicsScene->addItem(m_pGroup); + m_pGraphicsScene->update(); +} +void DestroyItemGoupCommand::redo() +{ + m_pGroup->setSelected(false); + foreach (QGraphicsItem *item, m_listItem) + { + //item->setSelected(true); + m_pGroup->removeFromGroup(item); + AbstractShape *ab = qgraphicsitem_cast(item); + ab->updateCoordinate(); + ab->setSelected(true); + } + m_pGraphicsScene->removeItem(m_pGroup); + m_pGraphicsScene->update(); +} diff --git a/source/styleCollection.cpp b/source/styleCollection.cpp new file mode 100644 index 0000000..a0f19ff --- /dev/null +++ b/source/styleCollection.cpp @@ -0,0 +1,39 @@ +#include "styleCollection.h" + + +NodeStyle const &StyleCollection::nodeStyle() +{ + return instance()._nodeStyle; +} + +ConnectionStyle const &StyleCollection::connectionStyle() +{ + return instance()._connectionStyle; +} + +GraphicsViewStyle const &StyleCollection::flowViewStyle() +{ + return instance()._flowViewStyle; +} + +void StyleCollection::setNodeStyle(NodeStyle nodeStyle) +{ + instance()._nodeStyle = nodeStyle; +} + +void StyleCollection::setConnectionStyle(ConnectionStyle connectionStyle) +{ + instance()._connectionStyle = connectionStyle; +} + +void StyleCollection::setGraphicsViewStyle(GraphicsViewStyle flowViewStyle) +{ + instance()._flowViewStyle = flowViewStyle; +} + +StyleCollection &StyleCollection::instance() +{ + static StyleCollection collection; + + return collection; +} diff --git a/source/toolBox.cpp b/source/toolBox.cpp new file mode 100644 index 0000000..be73ea2 --- /dev/null +++ b/source/toolBox.cpp @@ -0,0 +1,58 @@ +#include "toolBox.h" +#include "toolPage.h" + +#include +#include + +ToolBox::ToolBox(QWidget *parent) : + QWidget(parent), + m_pContentVBoxLayout(nullptr) +{ + + QVBoxLayout *vBoxLayout = new QVBoxLayout(this); + vBoxLayout->setContentsMargins(0, 0, 0, 0); + //vBoxLayout->addLayout(m_pContentVBoxLayout); + //vBoxLayout->addStretch(0); + +} + +ToolBox::~ToolBox() +{ + +} + +void ToolBox::addWidget(const QString &title, QWidget *pWidget) +{ + ToolPage *page = new ToolPage(this); + page->addWidget(title, pWidget); + + QBoxLayout* pLayout = dynamic_cast(layout()); + pLayout->insertWidget(0,page); + pLayout->addStretch(0); + m_mapWidget.insert(title,page); +} + +void ToolBox::removeWidget(const QString &title) +{ + bool bExist = m_mapWidget.contains(title); + if(bExist) + { + QWidget *pWidget = m_mapWidget.take(title); + if(pWidget) + { + QBoxLayout* pLayout = dynamic_cast(layout()); + ToolPage* toolPage = dynamic_cast(pWidget); + if(toolPage) + { + pLayout->removeWidget(toolPage); + delete toolPage; + } + + } + } + else + { + //cerr + } + +} diff --git a/source/toolPage.cpp b/source/toolPage.cpp new file mode 100644 index 0000000..5c5da78 --- /dev/null +++ b/source/toolPage.cpp @@ -0,0 +1,77 @@ +#include "ToolPage.h" + +#include +#include +#include +#include +#include +#include + +ToolPage::ToolPage(QWidget *parent) : + QWidget(parent), + m_bIsExpanded(true), + m_pLabel(nullptr), + m_pPushButtonFold(nullptr), + m_pContent(nullptr) +{ + setAttribute(Qt::WA_StyledBackground); + + m_pPushButtonFold = new QPushButton(this); + m_pLabel = new QLabel(this); + m_pLabel->setFixedSize(20, 20); + m_pLabel->setPixmap(QPixmap(":/images/icon_down_arrow.png").scaled(m_pLabel->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + QHBoxLayout *hLayout = new QHBoxLayout(m_pPushButtonFold); + QVBoxLayout *vLayout = new QVBoxLayout(this); + vLayout->setContentsMargins(0, 0, 0, 0); + vLayout->setSpacing(2); + vLayout->addWidget(m_pPushButtonFold); + hLayout->setContentsMargins(0, 0, 5, 0); + hLayout->addStretch(1); + hLayout->addWidget(m_pLabel); + + connect(m_pPushButtonFold, &QPushButton::clicked, this, &ToolPage::onPushButtonFoldClicked); +} + +ToolPage::~ToolPage() +{ + if(m_pContent) + delete m_pContent; + if(m_pPushButtonFold) + delete m_pPushButtonFold; +} + +void ToolPage::addWidget(const QString &title, QWidget *widget) +{ + if(!m_pContent) + { + m_pPushButtonFold->setText(title); + layout()->addWidget(widget); + m_pContent = widget; + } + +} + +void ToolPage::expand() +{ + if(m_pContent) + m_pContent->show(); + m_bIsExpanded = true; + m_pLabel->setPixmap(QPixmap(":/images/icon_down_arrow.png").scaled(m_pLabel->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); +} + +void ToolPage::collapse() +{ + if(m_pContent) + m_pContent->hide(); + m_bIsExpanded = false; + m_pLabel->setPixmap(QPixmap(":/images/icon_left_arrow.png").scaled(m_pLabel->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); +} + +void ToolPage::onPushButtonFoldClicked() +{ + if (m_bIsExpanded) { + collapse(); + } else { + expand(); + } +} diff --git a/source/undoCommands.cpp b/source/undoCommands.cpp new file mode 100644 index 0000000..024e98c --- /dev/null +++ b/source/undoCommands.cpp @@ -0,0 +1,458 @@ +#include "undoCommands.h" + +#include "basicGraphicsScene.h" +#include "connectionGraphicsObject.h" +#include "connectionIdUtils.h" +#include "global.h" +#include "nodeGraphicsObject.h" + +#include +#include +#include +#include +#include +#include + +#include + +static QJsonObject serializeSelectedItems(BasicGraphicsScene *scene) +{ + QJsonObject serializedScene; + + auto &graphModel = scene->graphModel(); + + QSet selectedNodes; + + QJsonArray nodesJsonArray; + + for (QGraphicsItem *item : scene->selectedItems()) { + if (auto n = qgraphicsitem_cast(item)) { + nodesJsonArray.append(graphModel.saveNode(n->nodeId())); + + selectedNodes.insert(n->nodeId()); + } + } + + QJsonArray connJsonArray; + + for (QGraphicsItem *item : scene->selectedItems()) { + if (auto c = qgraphicsitem_cast(item)) { + auto const &cid = c->connectionId(); + + if (selectedNodes.contains(cid.outNodeId) > 0 && selectedNodes.contains(cid.inNodeId) > 0) { + connJsonArray.append(toJson(cid)); + } + } + } + + serializedScene["nodes"] = nodesJsonArray; + serializedScene["connections"] = connJsonArray; + + return serializedScene; +} + +static void insertSerializedItems(QJsonObject const &json, BasicGraphicsScene *scene) +{ + AbstractGraphModel &graphModel = scene->graphModel(); + + QJsonArray const &nodesJsonArray = json["nodes"].toArray(); + + for (QJsonValue node : nodesJsonArray) { + QJsonObject obj = node.toObject(); + + graphModel.loadNode(obj); + + auto id = obj["id"].toInt(); + scene->nodeGraphicsObject(id)->setZValue(1.0); + scene->nodeGraphicsObject(id)->setSelected(true); + } + + QJsonArray const &connJsonArray = json["connections"].toArray(); + + for (QJsonValue connection : connJsonArray) { + QJsonObject connJson = connection.toObject(); + + ConnectionId connId = fromJson(connJson); + + // Restore the connection + graphModel.addConnection(connId); + + scene->connectionGraphicsObject(connId)->setSelected(true); + } +} + +static void deleteSerializedItems(QJsonObject &sceneJson, AbstractGraphModel &graphModel) +{ + QJsonArray connectionJsonArray = sceneJson["connections"].toArray(); + + for (QJsonValueRef connection : connectionJsonArray) { + QJsonObject connJson = connection.toObject(); + + ConnectionId connId = fromJson(connJson); + + graphModel.deleteConnection(connId); + } + + QJsonArray nodesJsonArray = sceneJson["nodes"].toArray(); + + for (QJsonValueRef node : nodesJsonArray) { + QJsonObject nodeJson = node.toObject(); + graphModel.deleteNode(nodeJson["id"].toInt()); + } +} + +static QPointF computeAverageNodePosition(QJsonObject const &sceneJson) +{ + QPointF averagePos(0, 0); + + QJsonArray nodesJsonArray = sceneJson["nodes"].toArray(); + + for (QJsonValueRef node : nodesJsonArray) { + QJsonObject nodeJson = node.toObject(); + + averagePos += QPointF(nodeJson["position"].toObject()["x"].toDouble(), + nodeJson["position"].toObject()["y"].toDouble()); + } + + averagePos /= static_cast(nodesJsonArray.size()); + + return averagePos; +} + +//------------------------------------- + +CreateCommand::CreateCommand(BasicGraphicsScene *scene, + QString const name, + QPointF const &mouseScenePos) + : _scene(scene) + , _sceneJson(QJsonObject()) +{ + _nodeId = _scene->graphModel().addNode(name); + if (_nodeId != InvalidNodeId) { + _scene->graphModel().setNodeData(_nodeId, NodeRole::Position, mouseScenePos); + } else { + setObsolete(true); + } +} + +void CreateCommand::undo() +{ + QJsonArray nodesJsonArray; + nodesJsonArray.append(_scene->graphModel().saveNode(_nodeId)); + _sceneJson["nodes"] = nodesJsonArray; + + _scene->graphModel().deleteNode(_nodeId); +} + +void CreateCommand::redo() +{ + if (_sceneJson.empty() || _sceneJson["nodes"].toArray().empty()) + return; + + insertSerializedItems(_sceneJson, _scene); +} + +//------------------------------------- + +DeleteCommand::DeleteCommand(BasicGraphicsScene *scene) + : _scene(scene) +{ + auto &graphModel = _scene->graphModel(); + + QJsonArray connJsonArray; + // Delete the selected connections first, ensuring that they won't be + // automatically deleted when selected nodes are deleted (deleting a + // node deletes some connections as well) + for (QGraphicsItem *item : _scene->selectedItems()) { + if (auto c = qgraphicsitem_cast(item)) { + auto const &cid = c->connectionId(); + + connJsonArray.append(toJson(cid)); + } + } + + QJsonArray nodesJsonArray; + // Delete the nodes; this will delete many of the connections. + // Selected connections were already deleted prior to this loop, + for (QGraphicsItem *item : _scene->selectedItems()) { + if (auto n = qgraphicsitem_cast(item)) { + // saving connections attached to the selected nodes + for (auto const &cid : graphModel.allConnectionIds(n->nodeId())) { + connJsonArray.append(toJson(cid)); + } + + nodesJsonArray.append(graphModel.saveNode(n->nodeId())); + } + } + + // If nothing is deleted, cancel this operation + if (connJsonArray.isEmpty() && nodesJsonArray.isEmpty()) + setObsolete(true); + + _sceneJson["nodes"] = nodesJsonArray; + _sceneJson["connections"] = connJsonArray; +} + +void DeleteCommand::undo() +{ + insertSerializedItems(_sceneJson, _scene); +} + +void DeleteCommand::redo() +{ + deleteSerializedItems(_sceneJson, _scene->graphModel()); +} + +//------------------------------------- + +void offsetNodeGroup(QJsonObject &sceneJson, QPointF const &diff) +{ + QJsonArray nodesJsonArray = sceneJson["nodes"].toArray(); + + QJsonArray newNodesJsonArray; + for (QJsonValueRef node : nodesJsonArray) { + QJsonObject obj = node.toObject(); + + QPointF oldPos(obj["position"].toObject()["x"].toDouble(), + obj["position"].toObject()["y"].toDouble()); + + oldPos += diff; + + QJsonObject posJson; + posJson["x"] = oldPos.x(); + posJson["y"] = oldPos.y(); + obj["position"] = posJson; + + newNodesJsonArray.append(obj); + } + + sceneJson["nodes"] = newNodesJsonArray; +} + +//------------------------------------- + +CopyCommand::CopyCommand(BasicGraphicsScene *scene) +{ + QJsonObject sceneJson = serializeSelectedItems(scene); + + if (sceneJson.empty() || sceneJson["nodes"].toArray().empty()) { + setObsolete(true); + return; + } + + QClipboard *clipboard = QApplication::clipboard(); + + QByteArray const data = QJsonDocument(sceneJson).toJson(); + + QMimeData *mimeData = new QMimeData(); + mimeData->setData("application/qt-nodes-graph", data); + mimeData->setText(data); + + clipboard->setMimeData(mimeData); + + // Copy command does not have any effective redo/undo operations. + // It copies the data to the clipboard and could be immediately removed + // from the stack. + setObsolete(true); +} + +//------------------------------------- + +PasteCommand::PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos) + : _scene(scene) + , _mouseScenePos(mouseScenePos) +{ + _newSceneJson = takeSceneJsonFromClipboard(); + + if (_newSceneJson.empty() || _newSceneJson["nodes"].toArray().empty()) { + setObsolete(true); + return; + } + + _newSceneJson = makeNewNodeIdsInScene(_newSceneJson); + + QPointF averagePos = computeAverageNodePosition(_newSceneJson); + + offsetNodeGroup(_newSceneJson, _mouseScenePos - averagePos); +} + +void PasteCommand::undo() +{ + deleteSerializedItems(_newSceneJson, _scene->graphModel()); +} + +void PasteCommand::redo() +{ + _scene->clearSelection(); + + // Ignore if pasted in content does not generate nodes. + try { + insertSerializedItems(_newSceneJson, _scene); + } catch (...) { + // If the paste does not work, delete all selected nodes and connections + // `deleteNode(...)` implicitly removed connections + auto &graphModel = _scene->graphModel(); + + QJsonArray nodesJsonArray; + for (QGraphicsItem *item : _scene->selectedItems()) { + if (auto n = qgraphicsitem_cast(item)) { + graphModel.deleteNode(n->nodeId()); + } + } + + setObsolete(true); + } +} + +QJsonObject PasteCommand::takeSceneJsonFromClipboard() +{ + QClipboard const *clipboard = QApplication::clipboard(); + QMimeData const *mimeData = clipboard->mimeData(); + + QJsonDocument json; + if (mimeData->hasFormat("application/qt-nodes-graph")) { + json = QJsonDocument::fromJson(mimeData->data("application/qt-nodes-graph")); + } else if (mimeData->hasText()) { + json = QJsonDocument::fromJson(mimeData->text().toUtf8()); + } + + return json.object(); +} + +QJsonObject PasteCommand::makeNewNodeIdsInScene(QJsonObject const &sceneJson) +{ + AbstractGraphModel &graphModel = _scene->graphModel(); + + std::unordered_map mapNodeIds; + + QJsonArray nodesJsonArray = sceneJson["nodes"].toArray(); + + QJsonArray newNodesJsonArray; + for (QJsonValueRef node : nodesJsonArray) { + QJsonObject nodeJson = node.toObject(); + + NodeId oldNodeId = nodeJson["id"].toInt(); + + NodeId newNodeId = graphModel.newNodeId(); + + mapNodeIds[oldNodeId] = newNodeId; + + // Replace NodeId in json + nodeJson["id"] = static_cast(newNodeId); + + newNodesJsonArray.append(nodeJson); + } + + QJsonArray connectionJsonArray = sceneJson["connections"].toArray(); + + QJsonArray newConnJsonArray; + for (QJsonValueRef connection : connectionJsonArray) { + QJsonObject connJson = connection.toObject(); + + ConnectionId connId = fromJson(connJson); + + ConnectionId newConnId{mapNodeIds[connId.outNodeId], + connId.outPortIndex, + mapNodeIds[connId.inNodeId], + connId.inPortIndex}; + + newConnJsonArray.append(toJson(newConnId)); + } + + QJsonObject newSceneJson; + + newSceneJson["nodes"] = newNodesJsonArray; + newSceneJson["connections"] = newConnJsonArray; + + return newSceneJson; +} + +//------------------------------------- + +DisconnectCommand::DisconnectCommand(BasicGraphicsScene *scene, ConnectionId const connId) + : _scene(scene) + , _connId(connId) +{ + // +} + +void DisconnectCommand::undo() +{ + _scene->graphModel().addConnection(_connId); +} + +void DisconnectCommand::redo() +{ + _scene->graphModel().deleteConnection(_connId); +} + +//------ + +ConnectCommand::ConnectCommand(BasicGraphicsScene *scene, ConnectionId const connId) + : _scene(scene) + , _connId(connId) +{ + // +} + +void ConnectCommand::undo() +{ + _scene->graphModel().deleteConnection(_connId); +} + +void ConnectCommand::redo() +{ + _scene->graphModel().addConnection(_connId); +} + +//------ + +MoveNodeCommand::MoveNodeCommand(BasicGraphicsScene *scene, QPointF const &diff) + : _scene(scene) + , _diff(diff) +{ + _selectedNodes.clear(); + for (QGraphicsItem *item : _scene->selectedItems()) { + if (auto n = qgraphicsitem_cast(item)) { + _selectedNodes.insert(n->nodeId()); + } + } +} + +void MoveNodeCommand::undo() +{ + for (auto nodeId : _selectedNodes) { + auto oldPos = _scene->graphModel().nodeData(nodeId, NodeRole::Position).value(); + + oldPos -= _diff; + + _scene->graphModel().setNodeData(nodeId, NodeRole::Position, oldPos); + } +} + +void MoveNodeCommand::redo() +{ + for (auto nodeId : _selectedNodes) { + auto oldPos = _scene->graphModel().nodeData(nodeId, NodeRole::Position).value(); + + oldPos += _diff; + + _scene->graphModel().setNodeData(nodeId, NodeRole::Position, oldPos); + } +} + +int MoveNodeCommand::id() const +{ + return static_cast(typeid(MoveNodeCommand).hash_code()); +} + +bool MoveNodeCommand::mergeWith(QUndoCommand const *c) +{ + auto mc = static_cast(c); + + if (_selectedNodes == mc->_selectedNodes) { + _diff += mc->_diff; + return true; + } + return false; +} diff --git a/source/util/baseSelector.cpp b/source/util/baseSelector.cpp new file mode 100644 index 0000000..d3c7387 --- /dev/null +++ b/source/util/baseSelector.cpp @@ -0,0 +1,313 @@ +#include "util/baseSelector.h" +#include +#include +#include +#include + +QPointF BaseSelector::ms_ptMouseDown(0.0,0.0); +QPointF BaseSelector::ms_ptMouseLast(0.0,0.0); +double BaseSelector::ms_dAngleMouseDownToItem = 0.0; +int BaseSelector::ms_nDragHandle = 0; + +BaseSelector::BaseSelector(QObject *parent) + : QObject(parent) +{ + m_type = ST_base; + m_opMode = OM_none; + m_bHoverOnHandel = false; +} +BaseSelector::~BaseSelector() +{ + +} + +void BaseSelector::mousePressEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + if (event->button() != Qt::LeftButton) + return; + + ms_ptMouseDown = event->scenePos(); + ms_ptMouseLast = event->scenePos(); + + if(!m_bHoverOnHandel) + scene->callParentEvent(event); //此处是通过触发QGraphicsScene的事件函数来取消所有被选中item的选中状态 + + m_opMode = OM_none; + + QList items = scene->selectedItems(); + if (items.count() == 1) //只有一个选中 + { + AbstractShape* item = qgraphicsitem_cast(items.first()); + if(item) + { + //需要增加当前是否点击在控制点的判断函数 + ms_nDragHandle = item->collidesWithHandle(event->scenePos()); + if(ms_nDragHandle != H_none && ms_nDragHandle <= H_left) //在缩放控制点上 + { + m_opMode = OM_scale; + emit setWorkingSelector(ST_scaling); + } + else if(ms_nDragHandle >= H_rotate_leftTop && ms_nDragHandle <= H_rotate_leftBottom) //在旋转控制点上 + { + m_opMode = OM_rotate; + //计算夹角 + QPointF originPoint = item->mapToScene(item->boundingRect().center()); + double dLengthY = ms_ptMouseLast.y() - originPoint.y(); + double dLengthX = ms_ptMouseLast.x() - originPoint.x(); + ms_dAngleMouseDownToItem = atan2(dLengthY, dLengthX) * 180 / M_PI; + // if(atan2(dLengthY, dLengthX) < 0) + // ms_dAngleMouseDownToItem += 360.0; + //创建副本 + item->createOperationCopy(); + emit setWorkingSelector(ST_rotation); + } + else if(ms_nDragHandle > H_rotate_leftBottom && ms_nDragHandle < H_connect) //编辑控制点上 + { + m_opMode = OM_edit; + setCursor(scene, Qt::ClosedHandCursor); + emit setWorkingSelector(ST_editing); + } + else if(ms_nDragHandle >= H_connect ) //连接控制点 + { + m_opMode = OM_connect; + setCursor(scene, Qt::ClosedHandCursor); + emit setWorkingSelector(ST_connecting); + } + else + { + m_opMode = OM_move; + setCursor(scene, Qt::ClosedHandCursor); + emit setWorkingSelector(ST_moving); + } + } + } + else if (items.count() > 1) //选中多个 + { + m_opMode = OM_move; + emit setWorkingSelector(ST_moving); + setCursor(scene, Qt::ClosedHandCursor); + } + + if(m_opMode == OM_move) //可以多个选中同时移动 + { + for(int n = 0; n < items.size(); n++) + { + //创建副本 + AbstractShape* item = qgraphicsitem_cast(items.at(n)); + item->createOperationCopy(); + } + } + else if(m_opMode == OM_none) + { + QGraphicsView *view = scene->getView(); + if(view) + view->setDragMode(QGraphicsView::RubberBandDrag); + } + +} + +void BaseSelector::mouseMoveEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + ms_ptMouseLast = event->scenePos(); + + QList items = scene->selectedItems(); + + if (items.count() == 1) + { + AbstractShape* item = qgraphicsitem_cast(items.first()); + if(item) + { + if(ms_nDragHandle == H_none) + { + //设置光标样式 + int nHandle = item->collidesWithHandle(event->scenePos()); + if(nHandle == H_none) + { + setCursor(scene, Qt::ArrowCursor); + m_bHoverOnHandel = false; + } + else if(nHandle >= H_edit) + { + setCursor(scene, Qt::OpenHandCursor); + m_bHoverOnHandel = true; + } + else + { + //划分为四组区间范围,分别水平组、垂直组、一三象限倾斜组、二四象限倾斜组,每组由两个对称区间构成 + double dRotation = item->rotation(); + dRotation += item->getSyncRotationDataFromParent(); + //让角度保持在正负180的区间,也就是上下两个半圈,这样易于象限判断 + if (dRotation > 180) + dRotation -= 360; + if (dRotation < -180) + dRotation += 360; + //qDebug() << "selfRotation:" << item->rotation() << " syncRotation:" << item->getSyncRotationDataFromParent() << " fininalRotatio:" << dRotation; + double dTileAngle = 15.0; + + switch (nHandle) + { + case H_leftTop: + { + if((dRotation > -145+dTileAngle && dRotation < -45-dTileAngle) || (dRotation > 45+dTileAngle && dRotation < 145-dTileAngle)) //垂直区间 + setCursor(scene, Qt::SizeBDiagCursor); + else if((dRotation >= -45-dTileAngle && dRotation <= -45+dTileAngle) || (dRotation >= 145-dTileAngle && dRotation <= 145+dTileAngle)) //一三象限倾斜 + setCursor(scene, Qt::SizeHorCursor); + else if((dRotation >= -145-dTileAngle && dRotation <= -145+dTileAngle) || (dRotation >= 45-dTileAngle && dRotation <= 45+dTileAngle)) //二四象限倾斜 + setCursor(scene, Qt::SizeVerCursor); + else //水平区间 + setCursor(scene, Qt::SizeFDiagCursor); + break; + } + case H_top: + { + if((dRotation > -145+dTileAngle && dRotation < -45-dTileAngle) || (dRotation > 45+dTileAngle && dRotation < 145-dTileAngle)) //垂直区间 + setCursor(scene, Qt::SizeHorCursor); + else if((dRotation >= -45-dTileAngle && dRotation <= -45+dTileAngle) || (dRotation >= 145-dTileAngle && dRotation <= 145+dTileAngle)) //一三象限倾斜 + setCursor(scene, Qt::SizeFDiagCursor); + else if((dRotation >= -145-dTileAngle && dRotation <= -145+dTileAngle) || (dRotation >= 45-dTileAngle && dRotation <= 45+dTileAngle)) //二四象限倾斜 + setCursor(scene, Qt::SizeBDiagCursor); + else + setCursor(scene, Qt::SizeVerCursor); + break; + } + case H_rightTop: + { + if((dRotation > -145+dTileAngle && dRotation < -45-dTileAngle) || (dRotation > 45+dTileAngle && dRotation < 145-dTileAngle)) //垂直区间 + setCursor(scene, Qt::SizeFDiagCursor); + else if((dRotation >= -45-dTileAngle && dRotation <= -45+dTileAngle) || (dRotation >= 145-dTileAngle && dRotation <= 145+dTileAngle)) //一三象限倾斜 + setCursor(scene, Qt::SizeVerCursor); + else if((dRotation >= -145-dTileAngle && dRotation <= -145+dTileAngle) || (dRotation >= 45-dTileAngle && dRotation <= 45+dTileAngle)) //二四象限倾斜 + setCursor(scene, Qt::SizeHorCursor); + else + setCursor(scene, Qt::SizeBDiagCursor); + break; + } + case H_right: + { + if((dRotation > -145+dTileAngle && dRotation < -45-dTileAngle) || (dRotation > 45+dTileAngle && dRotation < 145-dTileAngle)) //垂直区间 + setCursor(scene, Qt::SizeVerCursor); + else if((dRotation >= -45-dTileAngle && dRotation <= -45+dTileAngle) || (dRotation >= 145-dTileAngle && dRotation <= 145+dTileAngle)) //一三象限倾斜 + setCursor(scene, Qt::SizeBDiagCursor); + else if((dRotation >= -145-dTileAngle && dRotation <= -145+dTileAngle) || (dRotation >= 45-dTileAngle && dRotation <= 45+dTileAngle)) //二四象限倾斜 + setCursor(scene, Qt::SizeFDiagCursor); + else + setCursor(scene, Qt::SizeHorCursor); + break; + } + case H_rightBottom: + { + if((dRotation > -145+dTileAngle && dRotation < -45-dTileAngle) || (dRotation > 45+dTileAngle && dRotation < 145-dTileAngle)) //垂直区间 + setCursor(scene, Qt::SizeBDiagCursor); + else if((dRotation >= -45-dTileAngle && dRotation <= -45+dTileAngle) || (dRotation >= 145-dTileAngle && dRotation <= 145+dTileAngle)) //一三象限倾斜 + setCursor(scene, Qt::SizeHorCursor); + else if((dRotation >= -145-dTileAngle && dRotation <= -145+dTileAngle) || (dRotation >= 45-dTileAngle && dRotation <= 45+dTileAngle)) //二四象限倾斜 + setCursor(scene, Qt::SizeVerCursor); + else //水平区间 + setCursor(scene, Qt::SizeFDiagCursor); + break; + } + case H_bottom: + { + if((dRotation > -145+dTileAngle && dRotation < -45-dTileAngle) || (dRotation > 45+dTileAngle && dRotation < 145-dTileAngle)) //垂直区间 + setCursor(scene, Qt::SizeHorCursor); + else if((dRotation >= -45-dTileAngle && dRotation <= -45+dTileAngle) || (dRotation >= 145-dTileAngle && dRotation <= 145+dTileAngle)) //一三象限倾斜 + setCursor(scene, Qt::SizeFDiagCursor); + else if((dRotation >= -145-dTileAngle && dRotation <= -145+dTileAngle) || (dRotation >= 45-dTileAngle && dRotation <= 45+dTileAngle)) //二四象限倾斜 + setCursor(scene, Qt::SizeBDiagCursor); + else + setCursor(scene, Qt::SizeVerCursor); + break; + } + case H_leftBottom: + { + if((dRotation > -145+dTileAngle && dRotation < -45-dTileAngle) || (dRotation > 45+dTileAngle && dRotation < 145-dTileAngle)) //垂直区间 + setCursor(scene, Qt::SizeFDiagCursor); + else if((dRotation >= -45-dTileAngle && dRotation <= -45+dTileAngle) || (dRotation >= 145-dTileAngle && dRotation <= 145+dTileAngle)) //一三象限倾斜 + setCursor(scene, Qt::SizeVerCursor); + else if((dRotation >= -145-dTileAngle && dRotation <= -145+dTileAngle) || (dRotation >= 45-dTileAngle && dRotation <= 45+dTileAngle)) //二四象限倾斜 + setCursor(scene, Qt::SizeHorCursor); + else //水平区间 + setCursor(scene, Qt::SizeBDiagCursor); + break; + } + case H_left: + { + if((dRotation > -145+dTileAngle && dRotation < -45-dTileAngle) || (dRotation > 45+dTileAngle && dRotation < 145-dTileAngle)) //垂直区间 + setCursor(scene, Qt::SizeVerCursor); + else if((dRotation >= -45-dTileAngle && dRotation <= -45+dTileAngle) || (dRotation >= 145-dTileAngle && dRotation <= 145+dTileAngle)) //一三象限倾斜 + setCursor(scene, Qt::SizeBDiagCursor); + else if((dRotation >= -145-dTileAngle && dRotation <= -145+dTileAngle) || (dRotation >= 45-dTileAngle && dRotation <= 45+dTileAngle)) //二四象限倾斜 + setCursor(scene, Qt::SizeFDiagCursor); + else + setCursor(scene, Qt::SizeHorCursor); + break; + } + case H_rotate_leftTop: + { + int nSize = 24; + QCursor rotateCursor = QCursor(QPixmap(":/images/icon_rotate.png")/*.scaled(nSize, nSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)*/); + setCursor(scene, rotateCursor); + break; + } + case H_rotate_rightTop: + { + int nSize = 24; + QCursor rotateCursor = QCursor(QPixmap(":/images/icon_rotate.png")/*.scaled(nSize, nSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)*/); + setCursor(scene, rotateCursor); + break; + } + case H_rotate_rightBottom: + { + int nSize = 24; + QCursor rotateCursor = QCursor(QPixmap(":/images/icon_rotate.png")/*.scaled(nSize, nSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)*/); + setCursor(scene, rotateCursor); + break; + } + case H_rotate_leftBottom: + { + int nSize = 24; + QCursor rotateCursor = QCursor(QPixmap(":/images/icon_rotate.png")/*.scaled(nSize, nSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)*/); + setCursor(scene, rotateCursor); + break; + } + default: + break; + } + + m_bHoverOnHandel = true; + } + } + } + } +} + +void BaseSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + setCursor(scene, Qt::ArrowCursor); + + if(m_opMode == OM_none) + { + QGraphicsView *view = scene->getView(); + if(view) + view->setDragMode(QGraphicsView::NoDrag); + } + + m_opMode = OM_none; + m_bHoverOnHandel = false; + ms_nDragHandle = H_none; + scene->callParentEvent(event); +} + +void BaseSelector::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + Q_UNUSED(event) + Q_UNUSED(scene) +} + +void BaseSelector::setCursor(DesignerScene *scene, const QCursor &cursor) +{ + QGraphicsView *view = scene->getView(); + if (view) + view->setCursor(cursor); +} + diff --git a/source/util/connectingSelector.cpp b/source/util/connectingSelector.cpp new file mode 100644 index 0000000..558bf55 --- /dev/null +++ b/source/util/connectingSelector.cpp @@ -0,0 +1,66 @@ +#include "util/connectingSelector.h" +#include +#include +#include + +ConnectingSelector::ConnectingSelector(QObject *parent) + : BaseSelector(parent),m_pConnectingItem(nullptr) +{ + m_type = ST_connecting; +} +ConnectingSelector::~ConnectingSelector() +{ + +} + +void ConnectingSelector::mousePressEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + ms_ptMouseLast = event->scenePos(); + +} + +void ConnectingSelector::mouseMoveEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + ms_ptMouseLast = event->scenePos(); + if(m_pConnectingItem == nullptr) + { + QList items = scene->selectedItems(); + if (items.count() == 1) + { + GraphicsBaseItem* item = qgraphicsitem_cast(items.first()); + if(item) + { + if(ms_nDragHandle != H_none) + { + item->setState(S_lineOut); + QPointF start = item->mapFromScene(ms_ptMouseDown); + QPointF end = item->mapFromScene(ms_ptMouseLast); + item->setBeginConnectPos(start); + item->setEndConnectPos(end); + m_pConnectingItem = item; + } + } + } + } + else + { + if(m_pConnectingItem) + { + QPointF end = m_pConnectingItem->mapFromScene(ms_ptMouseLast); + m_pConnectingItem->setEndConnectPos(end); + } + } + +} + +void ConnectingSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + if(m_pConnectingItem) + m_pConnectingItem->setState(S_normal); + m_pConnectingItem = nullptr; + ms_nDragHandle = H_none; + setCursor(scene, Qt::ArrowCursor); + scene->callParentEvent(event); + emit setWorkingSelector(ST_base); +} + diff --git a/source/util/creatingSelector.cpp b/source/util/creatingSelector.cpp new file mode 100644 index 0000000..318ccfa --- /dev/null +++ b/source/util/creatingSelector.cpp @@ -0,0 +1,188 @@ +#include "util/creatingSelector.h" +#include "graphicsItem/graphicsRectItem.h" +#include "graphicsItem/graphicsPolygonItem.h" +#include "graphicsItem/electricSvgItemBus.h" +#include "graphicsItem/electricSvgItemRect.h" +#include "graphicsItem/electricSvgItemTriangle.h" +#include +#include +#include + + +CreatingSelector::CreatingSelector(QObject *parent) + : BaseSelector(parent) +{ + m_type = ST_cerating; + m_creatingMethod = CM_drag; + m_pCreatingItem = nullptr; + m_scalBasePoint = QPointF(); +} +CreatingSelector::~CreatingSelector() +{ + +} + +void CreatingSelector::mousePressEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + if (event->button() != Qt::LeftButton) + return; + + ms_ptMouseDown = event->scenePos(); + ms_ptMouseLast = event->scenePos(); + + if(m_pCreatingItem == nullptr) + { + scene->clearSelection(); + + switch (m_creatingItemType) + { + case GIT_rect: + { + m_creatingMethod = CM_drag; + m_pCreatingItem = new GraphicsRectItem(QRect(1, 1, 1, 1)); + } + break; + case GIT_roundRect: + { + m_creatingMethod = CM_drag; + m_pCreatingItem = new GraphicsRectItem(QRect(1, 1, 1, 1), true); + } + break; + case GIT_polygon: + { + m_creatingMethod = CM_click; + m_pCreatingItem = new GraphicPolygonItem(); + } + break; + case GIT_bus: + { + m_creatingMethod = CM_click; + m_pCreatingItem = new ElectricSvgItemBus(QRect(-100, -3, 200, 6)); + + m_pCreatingItem->editShape(ms_nDragHandle, ms_ptMouseLast); + emit setWorkingSelector(ST_base); + } + break; + case GIT_itemRect: + { + m_creatingMethod = CM_click; + m_pCreatingItem = new ElectricSvgItemRect(QRect(-30, -30, 60, 60)); + + m_pCreatingItem->editShape(ms_nDragHandle, ms_ptMouseLast); + emit setWorkingSelector(ST_base); + } + break; + case GIT_itemTri: + { + m_creatingMethod = CM_click; + m_pCreatingItem = new ElectricSvgItemTriangle(QRect(-30, -30, 60, 60)); + + m_pCreatingItem->editShape(ms_nDragHandle, ms_ptMouseLast); + emit setWorkingSelector(ST_base); + } + break; + default: + break; + } + + if(m_pCreatingItem) + { + m_pCreatingItem->setPos(event->scenePos()); + m_pCreatingItem->setSelected(true); + scene->addItem(m_pCreatingItem); + + if(m_creatingMethod == CM_drag) + { + ms_ptMouseDown += QPoint(2, 2); + ms_nDragHandle = H_rightBottom; + } + else if(m_creatingMethod == CM_click) + m_pCreatingItem->addPoint(ms_ptMouseDown); + } + } + + if(m_pCreatingItem && m_creatingMethod == CM_click) + { + //创建时添加了第一个点,紧接着再次添加第二点,然后从第二个点开始进行移动绘制 + m_pCreatingItem->addPoint(ms_ptMouseDown); + ms_nDragHandle = m_pCreatingItem->handleCount(); + m_pCreatingItem = nullptr; //先舍弃多次点击创建对象241124 by + } +} + +void CreatingSelector::mouseMoveEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + setCursor(scene, Qt::CrossCursor); + ms_ptMouseLast = event->scenePos(); + + if (m_pCreatingItem && m_creatingMethod == CM_drag) + { + + if(m_scalBasePoint.isNull()) //基准点不能采用临时变量,因为handle的坐标也在不断变化,计算会出现问题 + { + m_scalBasePoint = m_pCreatingItem->getSymmetricPointPos(ms_nDragHandle); + if(m_scalBasePoint.x() == 0) + m_scalBasePoint.setX(1); + if(m_scalBasePoint.y() == 0) + m_scalBasePoint.setY(1); + } + + QPointF scaleBasePoint = m_pCreatingItem->boundingRect().topLeft(); + + //计算缩放倍数 + QPointF iniDelta = m_pCreatingItem->mapFromScene(ms_ptMouseDown) - scaleBasePoint; + QPointF lastDelta = m_pCreatingItem->mapFromScene(ms_ptMouseLast) - scaleBasePoint; + double sx = lastDelta.x() / iniDelta.x(); + double sy = lastDelta.y() / iniDelta.y(); + + m_pCreatingItem->resize(ms_nDragHandle, sx, sy, scaleBasePoint); + } + else if (m_pCreatingItem && m_creatingMethod == CM_click) + { + if(ms_nDragHandle > H_left) + { + m_pCreatingItem->editShape(ms_nDragHandle, ms_ptMouseLast); + } + } +} + +void CreatingSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + if (m_pCreatingItem && m_creatingMethod == CM_drag) + { + if (event->scenePos() == (ms_ptMouseDown - QPoint(2, 2))) //最小拖动范围 + { + m_pCreatingItem->setSelected(false); + scene->removeItem(m_pCreatingItem); + delete m_pCreatingItem; + } + else if (ms_ptMouseLast != ms_ptMouseDown) + { + m_pCreatingItem->updateCoordinate(); + emit scene->signalAddItem(m_pCreatingItem); + } + + ms_nDragHandle = H_none; + m_pCreatingItem = nullptr; + m_scalBasePoint = QPointF(); + setCursor(scene, Qt::ArrowCursor); + emit setWorkingSelector(ST_base); + } + else if (m_pCreatingItem && m_creatingMethod == CM_click && event->button() == Qt::RightButton) //右键结束绘制 + { + if(m_pCreatingItem->endDrawing()) + m_pCreatingItem->updateCoordinate(); + else + { + m_pCreatingItem->setSelected(false); + scene->removeItem(m_pCreatingItem); + delete m_pCreatingItem; + } + + ms_nDragHandle = H_none; + m_pCreatingItem = nullptr; + setCursor(scene, Qt::ArrowCursor); + emit setWorkingSelector(ST_base); + } +} + diff --git a/source/util/editingSelector.cpp b/source/util/editingSelector.cpp new file mode 100644 index 0000000..c991e4a --- /dev/null +++ b/source/util/editingSelector.cpp @@ -0,0 +1,54 @@ +#include "util/editingSelector.h" +#include +#include + + +EditingSelector::EditingSelector(QObject *parent) + : BaseSelector(parent) +{ + m_type = ST_editing; +} +EditingSelector::~EditingSelector() +{ + +} + +void EditingSelector::mousePressEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + +} + +void EditingSelector::mouseMoveEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + ms_ptMouseLast = event->scenePos(); + QList items = scene->selectedItems(); + if (items.count() == 1) + { + GraphicsBaseItem* item = qgraphicsitem_cast(items.first()); + if(item) + { + if(ms_nDragHandle > H_left) + { + item->editShape(ms_nDragHandle, ms_ptMouseLast); + } + } + } +} + +void EditingSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + QList items = scene->selectedItems(); + if (items.count() == 1) + { + GraphicsBaseItem* item = qgraphicsitem_cast(items.first()); + if(item && ms_ptMouseLast != ms_ptMouseDown) + { + item->updateCoordinate(); + } + } + + ms_nDragHandle = H_none; + setCursor(scene, Qt::ArrowCursor); + emit setWorkingSelector(ST_base); +} + diff --git a/source/util/movingSelector.cpp b/source/util/movingSelector.cpp new file mode 100644 index 0000000..5b546b1 --- /dev/null +++ b/source/util/movingSelector.cpp @@ -0,0 +1,48 @@ +#include "util/movingSelector.h" +#include +#include +#include + +MovingSelector::MovingSelector(QObject *parent) + : BaseSelector(parent) +{ + m_type = ST_moving; +} +MovingSelector::~MovingSelector() +{ + +} + +void MovingSelector::mousePressEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + +} + +void MovingSelector::mouseMoveEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + ms_ptMouseLast = event->scenePos(); + + QList items = scene->selectedItems(); + for(int n = 0; n < items.size(); n++) + { + AbstractShape* item = qgraphicsitem_cast(items.at(n)); + if(item) + item->moveOperationCopy(ms_ptMouseLast - ms_ptMouseDown); + } +} + +void MovingSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + QList items = scene->selectedItems(); + for(int n = 0; n < items.size(); n++) + { + AbstractShape* item = qgraphicsitem_cast(items.at(n)); + if(item) + item->removeOperationCopy(); + } + + setCursor(scene, Qt::ArrowCursor); + scene->callParentEvent(event); + emit setWorkingSelector(ST_base); +} + diff --git a/source/util/rotationSelector.cpp b/source/util/rotationSelector.cpp new file mode 100644 index 0000000..01bdeb5 --- /dev/null +++ b/source/util/rotationSelector.cpp @@ -0,0 +1,69 @@ +#include "util/rotationSelector.h" +#include +#include +#include + + +RotationSelector::RotationSelector(QObject *parent) + : BaseSelector(parent) +{ + m_type = ST_rotation; +} +RotationSelector::~RotationSelector() +{ + +} + +void RotationSelector::mousePressEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + +} + +void RotationSelector::mouseMoveEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + ms_ptMouseLast = event->scenePos(); + QList items = scene->selectedItems(); + if (items.count() == 1) + { + AbstractShape* item = qgraphicsitem_cast(items.first()); + if(item) + { + //计算夹角 + QPointF originPoint = item->mapToScene(item->boundingRect().center()); + double dLengthY = ms_ptMouseLast.y() - originPoint.y(); + double dLengthX = ms_ptMouseLast.x() - originPoint.x(); + double dAngleMouseToItem = atan2(dLengthY, dLengthX) * 180 / M_PI; + // if(atan2(dLengthY, dLengthX) < 0) + // dAngleMouseToItem += 360.0; + + double rotationAngle = item->rotation() + (dAngleMouseToItem - ms_dAngleMouseDownToItem); + //让角度保持在正负180的区间,也就是上下两个半圈,这样易于象限判断 + if (rotationAngle > 180) + rotationAngle -= 360; + if (rotationAngle < -180) + rotationAngle += 360; + + item->rotateOperationCopy(rotationAngle); + //qDebug() << "rotationAngle: " << rotationAngle; + } + } +} + +void RotationSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + QList items = scene->selectedItems(); + for(int n = 0; n < items.size(); n++) + { + AbstractShape* item = qgraphicsitem_cast(items.at(n)); + if(item) + { + item->removeOperationCopy(); + } + + } + + ms_nDragHandle = H_none; + setCursor(scene, Qt::ArrowCursor); + emit setWorkingSelector(ST_base); +} + diff --git a/source/util/scalingSelector.cpp b/source/util/scalingSelector.cpp new file mode 100644 index 0000000..ceae42f --- /dev/null +++ b/source/util/scalingSelector.cpp @@ -0,0 +1,70 @@ +#include "util/scalingSelector.h" +#include +#include +#include + +ScalingSelector::ScalingSelector(QObject *parent) + : BaseSelector(parent) +{ + m_type = ST_scaling; + m_scalBasePoint = QPointF(); +} +ScalingSelector::~ScalingSelector() +{ + +} + +void ScalingSelector::mousePressEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + BaseSelector::mousePressEvent(event,scene); +} + +void ScalingSelector::mouseMoveEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + ms_ptMouseLast = event->scenePos(); + QList items = scene->selectedItems(); + if (items.count() == 1) + { + AbstractShape* item = qgraphicsitem_cast(items.first()); + if(item) + { + if(ms_nDragHandle != H_none) + { + if(m_scalBasePoint.isNull()) //基准点不能采用临时变量,因为handle的坐标也在不断变化,计算会出现问题 + { + m_scalBasePoint = item->getSymmetricPointPos(ms_nDragHandle); + if(m_scalBasePoint.x() == 0) + m_scalBasePoint.setX(1); + if(m_scalBasePoint.y() == 0) + m_scalBasePoint.setY(1); + } + + //计算缩放倍数 + QPointF iniDelta = item->mapFromScene(ms_ptMouseDown) - m_scalBasePoint; + QPointF lastDelta = item->mapFromScene(ms_ptMouseLast) - m_scalBasePoint; + double sx = lastDelta.x() / iniDelta.x(); + double sy = lastDelta.y() / iniDelta.y(); + + item->resize(ms_nDragHandle, sx, sy, m_scalBasePoint); + } + } + } +} + +void ScalingSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, DesignerScene* scene) +{ + QList items = scene->selectedItems(); + if (items.count() == 1) + { + AbstractShape* item = qgraphicsitem_cast(items.first()); + if(item && ms_ptMouseLast != ms_ptMouseDown) + { + item->updateCoordinate(); + } + } + + ms_nDragHandle = H_none; + m_scalBasePoint = QPointF(); + setCursor(scene, Qt::ArrowCursor); + emit setWorkingSelector(ST_base); +} diff --git a/source/util/selectorManager.cpp b/source/util/selectorManager.cpp new file mode 100644 index 0000000..e78e0a6 --- /dev/null +++ b/source/util/selectorManager.cpp @@ -0,0 +1,76 @@ +#include "util/selectorManager.h" +#include "util/creatingSelector.h" +#include "util/movingSelector.h" +#include "util/rotationSelector.h" +#include "util/scalingSelector.h" +#include "util/editingSelector.h" +#include "util/connectingSelector.h" + + +SelectorManager::SelectorManager(QObject *parent) + : QObject(parent) +{ + + //创建所有的selector + BaseSelector* baseSelector = new BaseSelector(this); + connect(baseSelector, SIGNAL(setWorkingSelector(SelectorType)), this, SLOT(onSignal_setWorkingSelector(SelectorType))); + m_vecSelectors.push_back(baseSelector); + CreatingSelector* creatingSelector = new CreatingSelector(this); + connect(creatingSelector, SIGNAL(setWorkingSelector(SelectorType)), this, SLOT(onSignal_setWorkingSelector(SelectorType))); + m_vecSelectors.push_back(creatingSelector); + MovingSelector* movingSelector = new MovingSelector(this); + connect(movingSelector, SIGNAL(setWorkingSelector(SelectorType)), this, SLOT(onSignal_setWorkingSelector(SelectorType))); + m_vecSelectors.push_back(movingSelector); + RotationSelector* rotationSelector = new RotationSelector(this); + connect(rotationSelector, SIGNAL(setWorkingSelector(SelectorType)), this, SLOT(onSignal_setWorkingSelector(SelectorType))); + m_vecSelectors.push_back(rotationSelector); + ScalingSelector* scalingSelector = new ScalingSelector(this); + connect(scalingSelector, SIGNAL(setWorkingSelector(SelectorType)), this, SLOT(onSignal_setWorkingSelector(SelectorType))); + m_vecSelectors.push_back(scalingSelector); + EditingSelector* editingSelector = new EditingSelector(this); + connect(editingSelector, SIGNAL(setWorkingSelector(SelectorType)), this, SLOT(onSignal_setWorkingSelector(SelectorType))); + m_vecSelectors.push_back(editingSelector); + ConnectingSelector* connectingSelector = new ConnectingSelector(this); + connect(connectingSelector, SIGNAL(setWorkingSelector(SelectorType)), this, SLOT(onSignal_setWorkingSelector(SelectorType))); + m_vecSelectors.push_back(connectingSelector); + + m_curSelector = ST_base; +} +SelectorManager::~SelectorManager() +{ + //析构所有的selector,因为是通过基类指针析构,所以基类的析构函数必须为虚函数 + // for(auto it = m_vecSelectors.begin(); it != m_vecSelectors.end(); it++) + // delete (*it); +} + + +BaseSelector* SelectorManager::getWorkingSelector() +{ + for(auto it = m_vecSelectors.begin(); it != m_vecSelectors.end(); it++) + { + if((*it)->getSelectorType()==m_curSelector) + { + return (*it); + } + } + + return nullptr; +} + +void SelectorManager::setDrawGraphicsItem(GraphicsItemType item) +{ + for(auto it = m_vecSelectors.begin(); it != m_vecSelectors.end(); it++) + { + if((*it)->getSelectorType()==ST_cerating) + { + CreatingSelector* creatingSelector = dynamic_cast(*it); + creatingSelector->setCreatingItem(item); + } + } +} + +void SelectorManager::onSignal_setWorkingSelector(SelectorType s) +{ + setWorkingSelector(s); +} + diff --git a/ui/drawingPanel.ui b/ui/drawingPanel.ui new file mode 100644 index 0000000..d70447e --- /dev/null +++ b/ui/drawingPanel.ui @@ -0,0 +1,41 @@ + + + drawingPanel + + + + 0 + 0 + 801 + 501 + + + + Form + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + diff --git a/ui/graphicElementsPanel.ui b/ui/graphicElementsPanel.ui new file mode 100644 index 0000000..d18d984 --- /dev/null +++ b/ui/graphicElementsPanel.ui @@ -0,0 +1,80 @@ + + + graphicElementsPanel + + + + 0 + 0 + 167 + 721 + + + + Form + + + + + + QTabWidget::South + + + 0 + + + + 基本图元 + + + + + 30 + 20 + 81 + 51 + + + + 直角矩形 + + + + + + 30 + 100 + 81 + 51 + + + + 圆角矩形 + + + + + + 30 + 180 + 81 + 51 + + + + 多边形 + + + + + + 电力图元 + + + + + + + + + diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui new file mode 100644 index 0000000..3c9ba68 --- /dev/null +++ b/ui/mainwindow.ui @@ -0,0 +1,267 @@ + + + CMainWindow + + + + 0 + 0 + 1696 + 1079 + + + + DiagramDesigner + + + + + + 0 + 0 + 1696 + 33 + + + + + 文件(F) + + + + + 视图(V) + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + + + + + + + + + + + + + + + + + + + 新建 + + + 新建(N) + + + QAction::MenuRole::NoRole + + + + + + + + 打开 + + + 打开(O) + + + QAction::MenuRole::NoRole + + + + + + + + 保存 + + + 保存(S) + + + QAction::MenuRole::NoRole + + + + + + + + 复制 + + + 复制(C) + + + QAction::MenuRole::NoRole + + + + + + + + 剪切 + + + 剪切(T) + + + QAction::MenuRole::NoRole + + + + + + + + 粘贴 + + + 粘贴(P) + + + QAction::MenuRole::NoRole + + + + + + + + 删除 + + + 删除(D) + + + QAction::MenuRole::NoRole + + + + + + + + 放大 + + + 放大 + + + QAction::MenuRole::NoRole + + + + + + + + 缩小 + + + 缩小 + + + QAction::MenuRole::NoRole + + + + + + + + 自适应 + + + 自适应 + + + QAction::MenuRole::NoRole + + + + + 网格 + + + 网格 + + + QAction::MenuRole::NoRole + + + + + + + + 撤销 + + + 撤销 + + + + + + + + 重做 + + + 重做 + + + QAction::MenuRole::NoRole + + + + + 群组 + + + 群组 + + + QAction::MenuRole::NoRole + + + + + 解组 + + + 解组 + + + QAction::MenuRole::NoRole + + + + + +