第一次上传

This commit is contained in:
baiYue 2024-12-03 20:07:25 +08:00
commit f45d3ebcd5
126 changed files with 11517 additions and 0 deletions

105
.gitignore vendored Normal file
View File

@ -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

207
CMakeLists.txt Normal file
View File

@ -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"
)

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# DiagramDesigner
桌面端单线图设计时Desktop one-line diagram DesignTime

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

149
include/connectionIdUtils.h Normal file
View File

@ -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;
}

View File

@ -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);
};

55
include/connectionState.h Normal file
View File

@ -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};
};

51
include/connectionStyle.h Normal file
View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

63
include/designerScene.h Normal file
View File

@ -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

40
include/designerView.h Normal file
View File

@ -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

33
include/diagramCavas.h Normal file
View File

@ -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

51
include/drawingPanel.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

142
include/global.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
};

12
include/locateNode.h Normal file
View File

@ -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);

70
include/mainwindow.h Normal file
View File

@ -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

View File

@ -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;
};

35
include/nodeData.h Normal file
View File

@ -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);

128
include/nodeDelegateModel.h Normal file
View File

@ -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;
};

View File

@ -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 &registeredModelCreators() const;
RegisteredModelsCategoryMap const &registeredModelsCategoryAssociation() 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;
};

View File

@ -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;
};

45
include/nodeState.h Normal file
View File

@ -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;
};

50
include/nodeStyle.h Normal file
View File

@ -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;
};

View File

@ -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

13
include/serializable.h Normal file
View File

@ -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*/) {}
};

45
include/style.h Normal file
View File

@ -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());
}
};

38
include/styleCollection.h Normal file
View File

@ -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;
};

20
include/toolBox.h Normal file
View File

@ -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

30
include/toolPage.h Normal file
View File

@ -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

118
include/undoCommands.h Normal file
View File

@ -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;
};

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

209
source/connectionStyle.cpp Normal file
View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

275
source/designerScene.cpp Normal file
View File

@ -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;
}

180
source/designerView.cpp Normal file
View File

@ -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);
}

41
source/diagramCavas.cpp Normal file
View File

@ -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);
}

94
source/drawingPanel.cpp Normal file
View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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包含子itemboundingRect会根据子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();
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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);
}

Some files were not shown because too many files have changed in this diff Show More