第一次上传
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# DiagramDesigner
|
||||
|
||||
桌面端单线图设计时(Desktop one-line diagram DesignTime)
|
||||
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
#pragma once
|
||||
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
#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<NodeId> allNodeIds() const = 0;
|
||||
|
||||
/**
|
||||
* A collection of all input and output connections for the given `nodeId`.
|
||||
*/
|
||||
virtual QSet<ConnectionId> 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<ConnectionId> 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<typename T>
|
||||
T nodeData(NodeId nodeId, NodeRole role) const
|
||||
{
|
||||
return nodeData(nodeId, role).value<T>();
|
||||
}
|
||||
|
||||
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<typename T>
|
||||
T portData(NodeId nodeId, PortType portType, PortIndex index, PortRole role) const
|
||||
{
|
||||
return portData(nodeId, portType, index, role).value<T>();
|
||||
}
|
||||
|
||||
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<ConnectionId> _shiftedByDynamicPortsConnections;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#include "global.h"
|
||||
|
||||
#include <QRectF>
|
||||
#include <QSize>
|
||||
#include <QTransform>
|
||||
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#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;
|
||||
};
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore/QUuid>
|
||||
#include <QtWidgets/QGraphicsScene>
|
||||
#include <QtWidgets/QMenu>
|
||||
|
||||
#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<AbstractNodePainter> 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<ConnectionGraphicsObject> 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<NodeGraphicsObject>;
|
||||
|
||||
using UniqueConnectionGraphicsObject = QSharedPointer<ConnectionGraphicsObject>;
|
||||
|
||||
QMap<NodeId, UniqueNodeGraphicsObject> _nodeGraphicsObjects;
|
||||
|
||||
QMap<ConnectionId, UniqueConnectionGraphicsObject> _connectionGraphicsObjects;
|
||||
|
||||
QSharedPointer<ConnectionGraphicsObject> _draftConnection;
|
||||
|
||||
QSharedPointer<AbstractNodeGeometry> _nodeGeometry;
|
||||
|
||||
QSharedPointer<AbstractNodePainter> _nodePainter;
|
||||
|
||||
bool _nodeDrag;
|
||||
|
||||
QUndoStack *_undoStack;
|
||||
|
||||
Qt::Orientation _orientation;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <QtCore/QUuid>
|
||||
#include <QtWidgets/QGraphicsObject>
|
||||
|
||||
#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<QPointF, QPointF> 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<QPointF, QPointF> pointsC1C2Horizontal() const;
|
||||
|
||||
std::pair<QPointF, QPointF> pointsC1C2Vertical() const;
|
||||
|
||||
private:
|
||||
ConnectionId _connectionId;
|
||||
|
||||
AbstractGraphModel &_graphModel;
|
||||
|
||||
ConnectionState _connectionState;
|
||||
|
||||
mutable QPointF _out;
|
||||
mutable QPointF _in;
|
||||
};
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
#pragma once
|
||||
|
||||
#include "global.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
|
||||
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<qint64>(connId.outNodeId);
|
||||
connJson["outPortIndex"] = static_cast<qint64>(connId.outPortIndex);
|
||||
connJson["intNodeId"] = static_cast<qint64>(connId.inNodeId);
|
||||
connJson["inPortIndex"] = static_cast<qint64>(connId.inPortIndex);
|
||||
|
||||
return connJson;
|
||||
}
|
||||
|
||||
inline ConnectionId fromJson(QJsonObject const &connJson)
|
||||
{
|
||||
ConnectionId connId{static_cast<NodeId>(connJson["outNodeId"].toInt(InvalidNodeId)),
|
||||
static_cast<PortIndex>(connJson["outPortIndex"].toInt(InvalidPortIndex)),
|
||||
static_cast<NodeId>(connJson["intNodeId"].toInt(InvalidNodeId)),
|
||||
static_cast<PortIndex>(connJson["inPortIndex"].toInt(InvalidPortIndex))};
|
||||
|
||||
return connId;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QPainterPath>
|
||||
|
||||
#include "global.h"
|
||||
|
||||
class ConnectionGeometry;
|
||||
class ConnectionGraphicsObject;
|
||||
|
||||
class ConnectionPainter
|
||||
{
|
||||
public:
|
||||
static void paint(QPainter *painter, ConnectionGraphicsObject const &cgo);
|
||||
|
||||
static QPainterPath getPainterStroke(ConnectionGraphicsObject const &cgo);
|
||||
};
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#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};
|
||||
};
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtGui/QColor>
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
#pragma once
|
||||
|
||||
#include "abstractGraphModel.h"
|
||||
#include "connectionIdUtils.h"
|
||||
#include "nodeDelegateModelRegistry.h"
|
||||
#include "serializable.h"
|
||||
#include "styleCollection.h"
|
||||
|
||||
#include <QSharedPointer>
|
||||
#include <QJsonObject>
|
||||
|
||||
|
||||
class DataFlowGraphModel : public AbstractGraphModel, public Serializable
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct NodeGeometryData
|
||||
{
|
||||
QSize size;
|
||||
QPointF pos;
|
||||
};
|
||||
|
||||
public:
|
||||
DataFlowGraphModel(QSharedPointer<NodeDelegateModelRegistry> registry);
|
||||
|
||||
QSharedPointer<NodeDelegateModelRegistry> dataModelRegistry() { return _registry; }
|
||||
|
||||
public:
|
||||
QSet<NodeId> allNodeIds() const override;
|
||||
|
||||
QSet<ConnectionId> allConnectionIds(NodeId const nodeId) const override;
|
||||
|
||||
QSet<ConnectionId> 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<typename NodeDelegateModelType>
|
||||
NodeDelegateModelType *delegateModel(NodeId const nodeId)
|
||||
{
|
||||
auto it = _models.find(nodeId);
|
||||
if (it == _models.end())
|
||||
return nullptr;
|
||||
|
||||
//auto model = dynamic_cast<NodeDelegateModelType *>(it->second.get());
|
||||
auto model = dynamic_cast<NodeDelegateModelType *>(*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<NodeDelegateModelRegistry> _registry;
|
||||
|
||||
NodeId _nextNodeId;
|
||||
|
||||
QMap<NodeId, QSharedPointer<NodeDelegateModel>> _models;
|
||||
|
||||
QSet<ConnectionId> _connectivity;
|
||||
|
||||
mutable QMap<NodeId, NodeGeometryData> _nodeGeometryData;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include "abstractNodeGeometry.h"
|
||||
|
||||
#include <QtGui/QFontMetrics>
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
#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;
|
||||
};
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include "abstractNodeGeometry.h"
|
||||
|
||||
#include <QtGui/QFontMetrics>
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
#ifndef DESIGNER_SCENE_H
|
||||
#define DESIGNER_SCENE_H
|
||||
|
||||
#include <QGraphicsScene>
|
||||
#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<NodeId> 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
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#ifndef DESIGNER_VIEW_H
|
||||
#define DESIGNER_VIEW_H
|
||||
|
||||
#include <QGraphicsView>
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef DIAGRAMCAVAS_H
|
||||
#define DIAGRAMCAVAS_H
|
||||
|
||||
#include <QMdiArea>
|
||||
#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<QString,DrawingPanel*> m_mapDrawPanel;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
#ifndef DRAWINGPANEL_H
|
||||
#define DRAWINGPANEL_H
|
||||
|
||||
#include <QWidget>
|
||||
#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<NodeDelegateModelRegistry> registerDataModels();
|
||||
private:
|
||||
Ui::drawingPanel *ui;
|
||||
DesignerView* m_pGraphicsView;
|
||||
DesignerScene* m_pGraphicsScene;
|
||||
SelectorManager* m_pSelectorManager;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef ELETRICELEMENTSPANELCONTAINER_H
|
||||
#define ELETRICELEMENTSPANELCONTAINER_H
|
||||
|
||||
#include <QObject>
|
||||
#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<QString,ElectricElementsPanel*> m_mapPanels;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef ELETRICELEMENTLISTWIDGET_H
|
||||
#define ELETRICELEMENTLISTWIDGET_H
|
||||
|
||||
#include <QListWidget>
|
||||
#include <QMouseEvent>
|
||||
|
||||
class ElectricElementsListwidget : public QListWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ElectricElementsListwidget(QListWidget *parent = nullptr);
|
||||
~ElectricElementsListwidget();
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef ELETRICELEMENTSPANEL_H
|
||||
#define ELETRICELEMENTSPANEL_H
|
||||
|
||||
#include <QWidget>
|
||||
#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<QString,GraphicsItemType>&);
|
||||
private:
|
||||
void initial();
|
||||
public slots:
|
||||
void onItemClicked(QListWidgetItem*);
|
||||
private:
|
||||
ElectricElementsListwidget* m_pListWidget;
|
||||
QMap<QString,GraphicsItemType> m_mapEleData;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
#ifndef GLOBAL_H
|
||||
#define GLOBAL_H
|
||||
|
||||
#include <QGraphicsItem>
|
||||
#include <QtCore/QMetaObject>
|
||||
#include <QtSwap>
|
||||
#include <QHash>
|
||||
|
||||
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<NodeData>`.
|
||||
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<b.conId;
|
||||
}
|
||||
|
||||
inline bool operator==(ConnectionId const &a, ConnectionId const &b)
|
||||
{
|
||||
return a.outNodeId == b.outNodeId && a.outPortIndex == b.outPortIndex
|
||||
&& a.inNodeId == b.inNodeId && a.inPortIndex == b.inPortIndex;
|
||||
}
|
||||
|
||||
inline bool operator!=(ConnectionId const &a, ConnectionId const &b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
inline void invertConnection(ConnectionId &id)
|
||||
{
|
||||
qSwap(id.outNodeId, id.inNodeId);
|
||||
qSwap(id.outPortIndex, id.inPortIndex);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef GRAPHICELEMENTSPANEL_H
|
||||
#define GRAPHICELEMENTSPANEL_H
|
||||
|
||||
#include <QWidget>
|
||||
#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
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef ELECTRICSVGITEM_H
|
||||
#define ELECTRICSVGITEM_H
|
||||
|
||||
#include "graphicsBaseItem.h"
|
||||
#include <QGraphicsSvgItem>
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,471 @@
|
|||
#ifndef GRAPHICSBASEITEM_H
|
||||
#define GRAPHICSBASEITEM_H
|
||||
|
||||
#include "itemControlHandle.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QGraphicsItem>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QPen>
|
||||
#include <QGraphicsSvgItem>
|
||||
|
||||
|
||||
enum ShapeType
|
||||
{
|
||||
T_undefined,
|
||||
T_item,
|
||||
T_group
|
||||
};
|
||||
|
||||
enum ItemState
|
||||
{
|
||||
S_normal = 0,
|
||||
S_lineOut,
|
||||
s_lineIn
|
||||
};
|
||||
|
||||
//基类采用模板形式,QGraphicsItem是默认值,也可以是别的类型,比如QGraphicsItemGroup,这样不同的基类继承可以共用一些高层的行为定义
|
||||
template <typename BaseType = QGraphicsItem>
|
||||
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<int,ItemControlHandle*> m_vecHanle;
|
||||
};
|
||||
|
||||
typedef AbstractShapeType<QGraphicsItem> AbstractShape;
|
||||
|
||||
class GraphicsBaseItem : public QObject, public AbstractShapeType<QGraphicsItem>
|
||||
{
|
||||
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
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef GRAPHICSITEMGROUP_H
|
||||
#define GRAPHICSITEMGROUP_H
|
||||
|
||||
#include "graphicsBaseItem.h"
|
||||
|
||||
|
||||
class GraphicsItemGroup : public QObject, public AbstractShapeType<QGraphicsItemGroup>
|
||||
{
|
||||
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<QGraphicsItem*>&);
|
||||
QList<QGraphicsItem*> 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<QGraphicsItem*> m_listItem;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
#ifndef ITEMCONTROLHANDLE_H
|
||||
#define ITEMCONTROLHANDLE_H
|
||||
|
||||
#include <QGraphicsRectItem>
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtGui/QColor>
|
||||
|
||||
#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;
|
||||
};
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore/QPointF>
|
||||
#include <QtGui/QTransform>
|
||||
|
||||
class QGraphicsScene;
|
||||
|
||||
class NodeGraphicsObject;
|
||||
|
||||
NodeGraphicsObject *locateNodeAt(QPointF scenePoint,
|
||||
QGraphicsScene &scene,
|
||||
QTransform const &viewTransform);
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QComboBox>
|
||||
#include <QWidgetAction>
|
||||
|
||||
//#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
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore/QPointF>
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
|
||||
/**
|
||||
* `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);
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#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> nodeData, PortIndex const portIndex) = 0;
|
||||
|
||||
virtual std::shared_ptr<NodeData> 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;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
#pragma once
|
||||
|
||||
#include "nodeData.h"
|
||||
#include "nodeDelegateModel.h"
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
/// Class uses map for storing models (name, model)
|
||||
class NodeDelegateModelRegistry
|
||||
{
|
||||
public:
|
||||
using RegistryItemPtr = QSharedPointer<NodeDelegateModel>;
|
||||
using RegistryItemCreator = RegistryItemPtr;
|
||||
using RegisteredModelCreatorsMap = QMap<QString, RegistryItemCreator>;
|
||||
using RegisteredModelsCategoryMap = QMap<QString, QString>;
|
||||
using CategoriesSet = QSet<QString>;
|
||||
|
||||
//using RegisteredTypeConvertersMap = std::map<TypeConverterId, TypeConverter>;
|
||||
|
||||
NodeDelegateModelRegistry() = default;
|
||||
~NodeDelegateModelRegistry() = default;
|
||||
|
||||
NodeDelegateModelRegistry(NodeDelegateModelRegistry const &) = delete;
|
||||
NodeDelegateModelRegistry(NodeDelegateModelRegistry &&) = default;
|
||||
|
||||
NodeDelegateModelRegistry &operator=(NodeDelegateModelRegistry const &) = delete;
|
||||
|
||||
NodeDelegateModelRegistry &operator=(NodeDelegateModelRegistry &&) = default;
|
||||
|
||||
public:
|
||||
template<typename ModelType>
|
||||
void registerModel(RegistryItemCreator creator, QString const &category = "Nodes")
|
||||
{
|
||||
QString const name = computeName<ModelType>(HasStaticMethodName<ModelType>{}, creator);
|
||||
if (!_registeredItemCreators.count(name)) {
|
||||
_registeredItemCreators[name] = std::move(creator);
|
||||
_categories.insert(category);
|
||||
_registeredModelsCategory[name] = category;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ModelType>
|
||||
void registerModel(QString const &category = "Nodes")
|
||||
{
|
||||
RegistryItemCreator creator = []() { return std::make_unique<ModelType>(); };
|
||||
registerModel<ModelType>(std::move(creator), category);
|
||||
}
|
||||
|
||||
#if 0
|
||||
template<typename ModelType>
|
||||
void
|
||||
registerModel(RegistryItemCreator creator,
|
||||
QString const& category = "Nodes")
|
||||
{
|
||||
registerModel<ModelType>(std::move(creator), category);
|
||||
}
|
||||
|
||||
|
||||
template <typename ModelCreator>
|
||||
void
|
||||
registerModel(ModelCreator&& creator, QString const& category = "Nodes")
|
||||
{
|
||||
using ModelType = compute_model_type_t<decltype(creator())>;
|
||||
registerModel<ModelType>(std::forward<ModelCreator>(creator), category);
|
||||
}
|
||||
|
||||
|
||||
template <typename ModelCreator>
|
||||
void
|
||||
registerModel(QString const& category, ModelCreator&& creator)
|
||||
{
|
||||
registerModel(std::forward<ModelCreator>(creator), category);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
registerTypeConverter(TypeConverterId const& id,
|
||||
TypeConverter typeConverter)
|
||||
{
|
||||
_registeredTypeConverters[id] = std::move(typeConverter);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
QSharedPointer<NodeDelegateModel> 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<typename T, typename = void>
|
||||
struct HasStaticMethodName : std::false_type
|
||||
{};
|
||||
|
||||
template<typename T>
|
||||
struct HasStaticMethodName<
|
||||
T,
|
||||
typename std::enable_if<std::is_same<decltype(T::Name()), QString>::value>::type>
|
||||
: std::true_type
|
||||
{};
|
||||
|
||||
template<typename ModelType>
|
||||
static QString computeName(std::true_type, RegistryItemCreator const &)
|
||||
{
|
||||
return ModelType::Name();
|
||||
}
|
||||
|
||||
template<typename ModelType>
|
||||
static QString computeName(std::false_type, RegistryItemCreator const &creator)
|
||||
{
|
||||
return creator->name();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct UnwrapUniquePtr
|
||||
{
|
||||
// Assert always fires, but the compiler doesn't know this:
|
||||
static_assert(!std::is_same<T, T>::value,
|
||||
"The ModelCreator must return a std::unique_ptr<T>, where T "
|
||||
"inherits from NodeDelegateModel");
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct UnwrapUniquePtr<std::unique_ptr<T>>
|
||||
{
|
||||
static_assert(std::is_base_of<NodeDelegateModel, T>::value,
|
||||
"The ModelCreator must return a std::unique_ptr<T>, where T "
|
||||
"inherits from NodeDelegateModel");
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template<typename CreatorResult>
|
||||
using compute_model_type_t = typename UnwrapUniquePtr<CreatorResult>::type;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore/QUuid>
|
||||
#include <QtWidgets/QGraphicsObject>
|
||||
|
||||
#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;
|
||||
};
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore/QPointF>
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
#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<ConnectionGraphicsObject const> _connectionForReaction;
|
||||
};
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtGui/QColor>
|
||||
#include <QJsonObject>
|
||||
|
||||
#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;
|
||||
};
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
*\file operationCommand.h
|
||||
*
|
||||
*\brief 用来实现“撤销/重做”的操作指令,继承自QUndoCommand
|
||||
*
|
||||
*\author dsc
|
||||
*/
|
||||
|
||||
#ifndef OPERATIONCOMMAND_H
|
||||
#define OPERATIONCOMMAND_H
|
||||
|
||||
#include <QUndoCommand>
|
||||
#include <QGraphicsScene>
|
||||
|
||||
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<QGraphicsItem*> 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<QGraphicsItem*> 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<QGraphicsItem*> m_listItem;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
|
||||
class Serializable
|
||||
{
|
||||
public:
|
||||
virtual ~Serializable() = default;
|
||||
|
||||
virtual QJsonObject save() const { return {}; }
|
||||
|
||||
virtual void load(QJsonObject const & /*p*/) {}
|
||||
};
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
|
||||
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());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef TOOLBOX_H
|
||||
#define TOOLBOX_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
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<QString,QWidget*> m_mapWidget;
|
||||
};
|
||||
#endif // TOOLBOX_H
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef TOOLPAGE_H
|
||||
#define TOOLPAGE_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
#pragma once
|
||||
|
||||
#include "global.h"
|
||||
|
||||
#include <QUndoCommand>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QPointF>
|
||||
|
||||
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<NodeId> _selectedNodes;
|
||||
QPointF _diff;
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
*\file baseSelector.h
|
||||
*
|
||||
*\brief selector是用来实现对图元进行具体操作的类,例如创建、编辑、旋转、移动等,通过对鼠标的行为动作进行具体的逻辑编写实现操作表达
|
||||
*
|
||||
*\author dsc
|
||||
*/
|
||||
|
||||
#ifndef BASESELECTOR_H
|
||||
#define BASESELECTOR_H
|
||||
|
||||
#include <QObject>
|
||||
#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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
*\file editingSelector.h
|
||||
*
|
||||
*\brief 用来实现图元编辑的selector
|
||||
*
|
||||
*\author dsc
|
||||
*/
|
||||
|
||||
#ifndef EDITINGSELECTOR_H
|
||||
#define EDITINGSELECTOR_H
|
||||
|
||||
#include "baseSelector.h"
|
||||
#include "global.h"
|
||||
#include <graphicsItem/graphicsBaseItem.h>
|
||||
|
||||
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
*\file selectorManager.h
|
||||
*
|
||||
*\brief 所有的selector管理类,采用单例模式
|
||||
* 每个cavas实例一个selector,根据具体要实现的内容进行创建和选择
|
||||
*\author by 20241113
|
||||
*/
|
||||
|
||||
#ifndef SELECTORMANAGER_H
|
||||
#define SELECTORMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#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<BaseSelector*> m_vecSelectors;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>images/checkerboard.png</file>
|
||||
<file>images/icon_rotate_lb.png</file>
|
||||
<file>images/icon_rotate_lt.png</file>
|
||||
<file>images/icon_rotate_rb.png</file>
|
||||
<file>images/icon_rotate_rt.png</file>
|
||||
<file>images/icon_rotate.png</file>
|
||||
<file>images/icon_up_arrow.png</file>
|
||||
<file>images/icon_down_arrow.png</file>
|
||||
<file>images/icon_left_arrow.png</file>
|
||||
<file>images/element/icons_triangle.png</file>
|
||||
<file>images/element/svg_bus.svg</file>
|
||||
<file>images/element/svg_rect.svg</file>
|
||||
<file>images/element/svg_triangle.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
After Width: | Height: | Size: 137 B |
|
After Width: | Height: | Size: 657 B |
|
|
@ -0,0 +1,7 @@
|
|||
<svg width="200" height="10" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Layer_1">
|
||||
<title>Layer 1</title>
|
||||
<rect rx="2" stroke="#000" id="svg_3" height="8" width="200" y="1" x="0.33502" fill="#000000"/>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 219 B |
|
|
@ -0,0 +1,9 @@
|
|||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Layer_1">
|
||||
<title>Layer 1</title>
|
||||
<rect stroke-width="3" id="svg_1" height="60" width="60" y="20" x="20" stroke="#000" fill="#fff"/>
|
||||
<rect stroke="#000" id="svg_2" height="0" width="4" y="68" x="51.33502" fill="#fff"/>
|
||||
<line id="svg_4" y2="20" x2="50" y1="0" x1="50" stroke-width="2" stroke="#000" fill="none"/>
|
||||
<line id="svg_5" y2="100" x2="50" y1="80" x1="50" stroke-width="2" stroke="#000" fill="none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 502 B |
|
|
@ -0,0 +1,9 @@
|
|||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Layer_1">
|
||||
<title>Layer 1</title>
|
||||
<path id="svg_4" d="m14,76.97812l35.42857,-62l35.42857,62l-70.85714,0z" stroke-width="2" stroke="#000" fill="none"/>
|
||||
<line id="svg_5" y2="15" x2="50" y1="0" x1="50" stroke-width="2" stroke="#000" fill="none"/>
|
||||
<line id="svg_6" y2="100" x2="33.33502" y1="77" x1="33.33502" stroke-width="2" stroke="#000" fill="none"/>
|
||||
<line id="svg_7" y2="100" x2="67" y1="77" x1="67" stroke-width="2" stroke="#000" fill="none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 541 B |
|
After Width: | Height: | Size: 272 B |
|
After Width: | Height: | Size: 248 B |
|
After Width: | Height: | Size: 373 B |
|
After Width: | Height: | Size: 865 B |
|
After Width: | Height: | Size: 864 B |
|
After Width: | Height: | Size: 872 B |
|
After Width: | Height: | Size: 280 B |
|
After Width: | Height: | Size: 268 B |
|
|
@ -0,0 +1,102 @@
|
|||
|
||||
#include "abstractGraphModel.h"
|
||||
#include "connectionIdUtils.h"
|
||||
|
||||
|
||||
void AbstractGraphModel::portsAboutToBeDeleted(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 - 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<ConnectionId> 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<ConnectionId> 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<ConnectionId> 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();
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
#include "abstractNodeGeometry.h"
|
||||
|
||||
#include "abstractGraphModel.h"
|
||||
#include "styleCollection.h"
|
||||
|
||||
#include <QMargins>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
||||
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<unsigned int>(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;
|
||||
}
|
||||
|
||||
|
|
@ -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 <QUndoStack>
|
||||
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QGraphicsSceneMoveEvent>
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
|
||||
BasicGraphicsScene::BasicGraphicsScene(AbstractGraphModel &graphModel, QObject *parent)
|
||||
: QGraphicsScene(parent)
|
||||
, _graphModel(graphModel)
|
||||
, _nodeGeometry(QSharedPointer<DefaultHorizontalNodeGeometry>(new DefaultHorizontalNodeGeometry(_graphModel)))
|
||||
, _nodePainter(QSharedPointer<DefaultNodePainter>())
|
||||
, _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<AbstractNodePainter> newPainter)
|
||||
{
|
||||
_nodePainter = std::move(newPainter);
|
||||
}
|
||||
|
||||
QUndoStack &BasicGraphicsScene::undoStack()
|
||||
{
|
||||
return *_undoStack;
|
||||
}
|
||||
|
||||
QSharedPointer<ConnectionGraphicsObject> const &BasicGraphicsScene::makeDraftConnection(
|
||||
ConnectionId const incompleteConnectionId)
|
||||
{
|
||||
_draftConnection = QSharedPointer<ConnectionGraphicsObject>(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<DefaultHorizontalNodeGeometry>(new DefaultHorizontalNodeGeometry(_graphModel));
|
||||
break;
|
||||
|
||||
case Qt::Vertical:
|
||||
_nodeGeometry = QSharedPointer<DefaultVerticalNodeGeometry>(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<NodeGraphicsObject>(new NodeGraphicsObject(*this, nodeId));
|
||||
}
|
||||
|
||||
// Then for each node check output connections and insert them.
|
||||
for (NodeId const nodeId : allNodeIds) {
|
||||
auto nOutPorts = _graphModel.nodeData<PortCount>(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<ConnectionGraphicsObject>(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<ConnectionGraphicsObject>(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<NodeGraphicsObject>(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<QPointF>());
|
||||
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<QPointF>());
|
||||
Q_EMIT modified(this);
|
||||
}
|
||||
_nodeDrag = false;
|
||||
}
|
||||
|
||||
void BasicGraphicsScene::onModelReset()
|
||||
{
|
||||
_connectionGraphicsObjects.clear();
|
||||
_nodeGraphicsObjects.clear();
|
||||
|
||||
clear();
|
||||
|
||||
traverseGraphAndPopulateGraphicsObjects();
|
||||
}
|
||||
|
|
@ -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 <QtWidgets/QGraphicsBlurEffect>
|
||||
#include <QtWidgets/QGraphicsDropShadowEffect>
|
||||
#include <QtWidgets/QGraphicsSceneMouseEvent>
|
||||
#include <QtWidgets/QGraphicsView>
|
||||
#include <QtWidgets/QStyleOptionGraphicsItem>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
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<BasicGraphicsScene *>(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<QGraphicsView *>(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<QGraphicsView *>(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<QPointF, QPointF> 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<QPointF, QPointF> 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<QPointF, QPointF> 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);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
#include "connectionPainter.h"
|
||||
|
||||
#include <QtGui/QIcon>
|
||||
|
||||
#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<NodeDataType>();
|
||||
|
||||
auto dataTypeIn
|
||||
= graphModel.portData(cId.inNodeId, PortType::In, cId.inPortIndex, PortRole::DataType)
|
||||
.value<NodeDataType>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
#include "connectionState.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QPointF>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
#include "connectionStyle.h"
|
||||
|
||||
#include "styleCollection.h"
|
||||
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonValueRef>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
//#include <random>
|
||||
|
||||
|
||||
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<int> 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<unsigned int>(hash));
|
||||
std::uniform_int_distribution<int> 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;
|
||||
}
|
||||
|
|
@ -0,0 +1,532 @@
|
|||
#include "dataFlowGraphModel.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
//#include <stdexcept>
|
||||
|
||||
|
||||
DataFlowGraphModel::DataFlowGraphModel(QSharedPointer<NodeDelegateModelRegistry> registry)
|
||||
: _registry(registry)
|
||||
, _nextNodeId{0}
|
||||
{}
|
||||
|
||||
QSet<NodeId> DataFlowGraphModel::allNodeIds() const
|
||||
{
|
||||
QSet<NodeId> 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<ConnectionId> DataFlowGraphModel::allConnectionIds(NodeId const nodeId) const
|
||||
{
|
||||
QSet<ConnectionId> 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<ConnectionId> DataFlowGraphModel::connections(NodeId nodeId,
|
||||
PortType portType,
|
||||
PortIndex portIndex) const
|
||||
{
|
||||
QSet<ConnectionId> 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<NodeDelegateModel> 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<NodeDataType>();
|
||||
};
|
||||
|
||||
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<ConnectionPolicy>();
|
||||
|
||||
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<QPointF>();
|
||||
|
||||
Q_EMIT nodePositionUpdated(nodeId);
|
||||
|
||||
result = true;
|
||||
} break;
|
||||
|
||||
case NodeRole::Size: {
|
||||
_nodeGeometryData[nodeId].size = value.value<QSize>();
|
||||
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<std::shared_ptr<NodeData>>(), 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<qint64>(nodeId);
|
||||
|
||||
nodeJson["internal-data"] = _models[nodeId]->save();
|
||||
|
||||
{
|
||||
QPointF const pos = nodeData(nodeId, NodeRole::Position).value<QPointF>();
|
||||
|
||||
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<NodeDelegateModel> 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<ConnectionId> 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);
|
||||
}
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
#include "defaultHorizontalNodeGeometry.h"
|
||||
|
||||
#include "abstractGraphModel.h"
|
||||
#include "nodeData.h"
|
||||
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QWidget>
|
||||
|
||||
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<QSize>(nodeId, NodeRole::Size);
|
||||
}
|
||||
|
||||
void DefaultHorizontalNodeGeometry::recomputeSize(NodeId const nodeId) const
|
||||
{
|
||||
unsigned int height = maxVerticalPortsExtent(nodeId);
|
||||
|
||||
if (auto w = _graphModel.nodeData<QWidget *>(nodeId, NodeRole::Widget)) {
|
||||
height = std::max(height, static_cast<unsigned int>(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<QWidget *>(nodeId, NodeRole::Widget)) {
|
||||
width += w->width();
|
||||
}
|
||||
|
||||
width = std::max(width, static_cast<unsigned int>(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<QSize>(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<QSize>(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<bool>(nodeId, NodeRole::CaptionVisible))
|
||||
return QRect();
|
||||
|
||||
QString name = _graphModel.nodeData<QString>(nodeId, NodeRole::Caption);
|
||||
|
||||
return _boldFontMetrics.boundingRect(name);
|
||||
}
|
||||
|
||||
QPointF DefaultHorizontalNodeGeometry::captionPosition(NodeId const nodeId) const
|
||||
{
|
||||
QSize size = _graphModel.nodeData<QSize>(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<QSize>(nodeId, NodeRole::Size);
|
||||
|
||||
unsigned int captionHeight = captionRect(nodeId).height();
|
||||
|
||||
if (auto w = _graphModel.nodeData<QWidget *>(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<QSize>(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<bool>(nodeId, portType, portIndex, PortRole::CaptionVisible)) {
|
||||
s = _graphModel.portData<QString>(nodeId, portType, portIndex, PortRole::Caption);
|
||||
} else {
|
||||
auto portData = _graphModel.portData(nodeId, portType, portIndex, PortRole::DataType);
|
||||
|
||||
s = portData.value<NodeDataType>().name;
|
||||
}
|
||||
|
||||
return _fontMetrics.boundingRect(s);
|
||||
}
|
||||
|
||||
unsigned int DefaultHorizontalNodeGeometry::maxVerticalPortsExtent(NodeId const nodeId) const
|
||||
{
|
||||
PortCount nInPorts = _graphModel.nodeData<PortCount>(nodeId, NodeRole::InPortCount);
|
||||
|
||||
PortCount nOutPorts = _graphModel.nodeData<PortCount>(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<bool>(nodeId, portType, portIndex, PortRole::CaptionVisible)) {
|
||||
name = _graphModel.portData<QString>(nodeId, portType, portIndex, PortRole::Caption);
|
||||
} else {
|
||||
NodeDataType portData = _graphModel.portData<NodeDataType>(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;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,296 @@
|
|||
#include "defaultVerticalNodeGeometry.h"
|
||||
|
||||
#include "abstractGraphModel.h"
|
||||
#include "nodeData.h"
|
||||
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QWidget>
|
||||
|
||||
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<QSize>(nodeId, NodeRole::Size);
|
||||
}
|
||||
|
||||
void DefaultVerticalNodeGeometry::recomputeSize(NodeId const nodeId) const
|
||||
{
|
||||
unsigned int height = _portSpasing; // maxHorizontalPortsExtent(nodeId);
|
||||
|
||||
if (auto w = _graphModel.nodeData<QWidget *>(nodeId, NodeRole::Widget)) {
|
||||
height = std::max(height, static_cast<unsigned int>(w->height()));
|
||||
}
|
||||
|
||||
QRectF const capRect = captionRect(nodeId);
|
||||
|
||||
height += capRect.height();
|
||||
|
||||
height += _portSpasing;
|
||||
height += _portSpasing;
|
||||
|
||||
PortCount nInPorts = _graphModel.nodeData<PortCount>(nodeId, NodeRole::InPortCount);
|
||||
PortCount nOutPorts = _graphModel.nodeData<PortCount>(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<QWidget *>(nodeId, NodeRole::Widget)) {
|
||||
width = std::max(width, static_cast<unsigned int>(w->width()));
|
||||
}
|
||||
|
||||
width = std::max(width, static_cast<unsigned int>(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<QSize>(nodeId, NodeRole::Size);
|
||||
|
||||
switch (portType) {
|
||||
case PortType::In: {
|
||||
unsigned int inPortWidth = maxPortsTextAdvance(nodeId, PortType::In) + _portSpasing;
|
||||
|
||||
PortCount nInPorts = _graphModel.nodeData<PortCount>(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<PortCount>(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<QSize>(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<bool>(nodeId, NodeRole::CaptionVisible))
|
||||
return QRect();
|
||||
|
||||
QString name = _graphModel.nodeData<QString>(nodeId, NodeRole::Caption);
|
||||
|
||||
return _boldFontMetrics.boundingRect(name);
|
||||
}
|
||||
|
||||
QPointF DefaultVerticalNodeGeometry::captionPosition(NodeId const nodeId) const
|
||||
{
|
||||
QSize size = _graphModel.nodeData<QSize>(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<QSize>(nodeId, NodeRole::Size);
|
||||
|
||||
unsigned int captionHeight = captionRect(nodeId).height();
|
||||
|
||||
if (auto w = _graphModel.nodeData<QWidget *>(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<QSize>(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<bool>(nodeId, portType, portIndex, PortRole::CaptionVisible)) {
|
||||
s = _graphModel.portData<QString>(nodeId, portType, portIndex, PortRole::Caption);
|
||||
} else {
|
||||
auto portData = _graphModel.portData(nodeId, portType, portIndex, PortRole::DataType);
|
||||
|
||||
s = portData.value<NodeDataType>().name;
|
||||
}
|
||||
|
||||
return _fontMetrics.boundingRect(s);
|
||||
}
|
||||
|
||||
unsigned int DefaultVerticalNodeGeometry::maxHorizontalPortsExtent(NodeId const nodeId) const
|
||||
{
|
||||
PortCount nInPorts = _graphModel.nodeData<PortCount>(nodeId, NodeRole::InPortCount);
|
||||
|
||||
PortCount nOutPorts = _graphModel.nodeData<PortCount>(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<bool>(nodeId, portType, portIndex, PortRole::CaptionVisible)) {
|
||||
name = _graphModel.portData<QString>(nodeId, portType, portIndex, PortRole::Caption);
|
||||
} else {
|
||||
NodeDataType portData = _graphModel.portData<NodeDataType>(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<PortCount>(nodeId, NodeRole::InPortCount);
|
||||
for (PortIndex i = 0; i < nInPorts; ++i) {
|
||||
if (_graphModel.portData<bool>(nodeId, PortType::In, i, PortRole::CaptionVisible)) {
|
||||
h += _portSpasing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PortType::Out: {
|
||||
PortCount nOutPorts = _graphModel.nodeData<PortCount>(nodeId, NodeRole::OutPortCount);
|
||||
for (PortIndex i = 0; i < nOutPorts; ++i) {
|
||||
if (_graphModel.portData<bool>(nodeId, PortType::Out, i, PortRole::CaptionVisible)) {
|
||||
h += _portSpasing;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
#include "designerScene.h"
|
||||
#include "util/selectorManager.h"
|
||||
#include "graphicsItem/graphicsItemGroup.h"
|
||||
#include "drawingPanel.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QDataStream>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QGraphicsSceneMoveEvent>
|
||||
#include <QtWidgets/QHeaderView>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QTreeWidget>
|
||||
#include <QtWidgets/QWidgetAction>
|
||||
|
||||
DesignerScene::DesignerScene(DataFlowGraphModel &graphModel, QObject *parent)
|
||||
: BasicGraphicsScene(graphModel,parent),
|
||||
m_pDrawingPanel(NULL),
|
||||
_graphModel(graphModel)
|
||||
{
|
||||
m_bGridVisible = true;
|
||||
m_pView = nullptr;
|
||||
m_pDrawingPanel = dynamic_cast<DrawingPanel*>(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<QGraphicsItem*> listItem = selectedItems();
|
||||
if(listItem.isEmpty())
|
||||
return nullptr;
|
||||
else if(listItem.count() == 1) //判断只选中了一个时是不是已经打组,如果是不做操作,防止循环打组
|
||||
{
|
||||
AbstractShape* item = qgraphicsitem_cast<AbstractShape*>(listItem.first());
|
||||
if(item && item->getType()==T_group)
|
||||
return nullptr;
|
||||
}
|
||||
else //如果选择的有组群,则拆散该组群,并和其它单独的itme组合成新组群,防止多层组群出现,方便管理和计算
|
||||
{
|
||||
for(int n=0; n<listItem.count(); n++)
|
||||
{
|
||||
AbstractShape* shape = qgraphicsitem_cast<AbstractShape*>(listItem[n]);
|
||||
if(shape && shape->getType()==T_group)
|
||||
{
|
||||
GraphicsItemGroup* group = qgraphicsitem_cast<GraphicsItemGroup*>(listItem[n]);
|
||||
QList<QGraphicsItem*> childItems = group->childItems();
|
||||
foreach (QGraphicsItem* child, childItems)
|
||||
{
|
||||
if(qgraphicsitem_cast<ItemControlHandle*>(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<NodeId> DesignerScene::selectedNodes() const
|
||||
{
|
||||
QList<QGraphicsItem *> graphicsItems = selectedItems();
|
||||
|
||||
std::vector<NodeId> result;
|
||||
result.reserve(graphicsItems.size());
|
||||
|
||||
/*for (QGraphicsItem *obj : graphicsItems) {
|
||||
auto ngo = qgraphicsitem_cast<NodeGraphicsObject *>(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;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
#include "designerView.h"
|
||||
#include <QMouseEvent>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#include "drawingPanel.h"
|
||||
#include "diagramCavas.h"
|
||||
#include "mainwindow.h"
|
||||
#include <QMdiSubWindow>
|
||||
|
||||
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<DrawingPanel*>(pWindow);
|
||||
if(pPanel)
|
||||
pPanel->onSignal_addGraphicsItem(type);
|
||||
}
|
||||
|
|
@ -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<NodeDelegateModelRegistry> 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<NodeDelegateModelRegistry> DrawingPanel::registerDataModels()
|
||||
{
|
||||
auto ret = QSharedPointer<NodeDelegateModelRegistry>(new NodeDelegateModelRegistry());
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#include <QVBoxLayout>
|
||||
|
||||
#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<QString,GraphicsItemType> 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<QString,GraphicsItemType> 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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#include <QVBoxLayout>
|
||||
#include <QListWidgetItem>
|
||||
#include <QIcon>
|
||||
#include <QBrush>
|
||||
|
||||
#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<QString,GraphicsItemType>& 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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
#include "graphicsItem/electricSvgItem.h"
|
||||
#include "graphicsItem/itemControlHandle.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
#include <QSvgRenderer>
|
||||
#include <QDebug>
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#include "graphicsItem/electricSvgItemBus.h"
|
||||
#include "graphicsItem/itemControlHandle.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#include "graphicsItem/electricSvgItemRect.h"
|
||||
#include "graphicsItem/itemControlHandle.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
#include "graphicsItem/electricSvgItemTriangle.h"
|
||||
#include "graphicsItem/itemControlHandle.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
#include "graphicsItem/graphicsBaseItem.h"
|
||||
#include <QGraphicsScene>
|
||||
|
||||
|
||||
GraphicsBaseItem::GraphicsBaseItem(QGraphicsItem *parent)
|
||||
: AbstractShapeType<QGraphicsItem>(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<QGraphicsItemGroup *>(parentItem());
|
||||
if(!group)
|
||||
setHandleVisible(value.toBool());
|
||||
else //在某一组群中,由组群展示是否选中,自身不做展示
|
||||
{
|
||||
setSelected(false);
|
||||
return QVariant::fromValue<bool>(false);
|
||||
}
|
||||
}
|
||||
return QGraphicsItem::itemChange(change, value);
|
||||
}
|
||||
|
||||
void GraphicsBaseItem::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,286 @@
|
|||
#include "graphicsItem/graphicsItemGroup.h"
|
||||
#include <QGraphicsScene>
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
#include <QTimer>
|
||||
|
||||
|
||||
GraphicsItemGroup::GraphicsItemGroup(QGraphicsItem *parent)
|
||||
: AbstractShapeType<QGraphicsItemGroup>(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<QGraphicsItemGroup *>(parentItem());
|
||||
if(!group)
|
||||
setHandleVisible(value.toBool());
|
||||
else //在某一组群中,由组群展示是否选中,自身不做展示
|
||||
{
|
||||
setSelected(false);
|
||||
return QVariant::fromValue<bool>(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<ItemControlHandle*>(item))
|
||||
continue;
|
||||
|
||||
AbstractShape* shape = qgraphicsitem_cast<AbstractShape*>(item);
|
||||
if(shape && shape->getType()==T_group)
|
||||
{
|
||||
GraphicsItemGroup *group = qgraphicsitem_cast<GraphicsItemGroup *>(item);
|
||||
if(group)
|
||||
group->syncRotationDataFromParent(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
GraphicsBaseItem *baseItem = qgraphicsitem_cast<GraphicsBaseItem*>(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<GraphicsBaseItem*>(item);
|
||||
if (baseItem && !qgraphicsitem_cast<ItemControlHandle*>(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<GraphicsBaseItem*>(item);
|
||||
if (baseItem && !qgraphicsitem_cast<ItemControlHandle*>(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<ItemControlHandle*>(item))
|
||||
continue;
|
||||
|
||||
// AbstractShape* shape = qgraphicsitem_cast<AbstractShape*>(item);
|
||||
// if(shape && shape->getType()==T_group)
|
||||
// {
|
||||
// GraphicsItemGroup *group = qgraphicsitem_cast<GraphicsItemGroup *>(item);
|
||||
// if(group)
|
||||
// group->syncRotationDataFromParent(dAngle);
|
||||
// }
|
||||
// else
|
||||
{
|
||||
GraphicsBaseItem *baseItem = qgraphicsitem_cast<GraphicsBaseItem*>(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<QGraphicsItem*>& items)
|
||||
{
|
||||
foreach (QGraphicsItem *item, items)
|
||||
{
|
||||
item->setSelected(false);
|
||||
addToGroup(item);
|
||||
m_listItem.push_back(item);
|
||||
}
|
||||
|
||||
updateCoordinate();
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
#include "graphicsItem/graphicsPolygonItem.h"
|
||||
#include "graphicsItem/itemControlHandle.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
|
||||
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; i<m_points.count(); i++)
|
||||
ptsOnScene[i] += delta; //一定要在scene坐标系下执行操作,不能直接m_points += delta;因为缩放以后item自身的内部坐标系也会同步缩放,单位刻度和scene下的单位刻度不再一致,所以所有相关计算一定要在同一个坐标系下完成
|
||||
m_points = mapFromScene(ptsOnScene);
|
||||
m_boundingRect = m_points.boundingRect();
|
||||
//移动整体图形,消除节点坐标更后的绘制跳转
|
||||
moveBy(-delta.x(), -delta.y());
|
||||
updateHandles();
|
||||
}
|
||||
|
||||
m_lastPoints = m_points;
|
||||
}
|
||||
|
||||
void GraphicPolygonItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
|
||||
{
|
||||
painter->setPen(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;
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
#include "graphicsItem/graphicsRectItem.h"
|
||||
#include "graphicsItem/itemControlHandle.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyleOption>
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
#include "graphicsItem/itemControlHandle.h"
|
||||
#include <QPainter>
|
||||
|
||||
#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);
|
||||
}
|
||||