├── .gitignore
├── README.md
├── demo
├── QtNodeEditorDemo.pro
├── main.cpp
├── nodeeditor.cpp
└── nodeeditor.h
├── docs
└── node_editor_demo.png
├── include
├── graphlink.h
├── graphnode.h
├── graphnodefield.h
├── graphscene.h
├── graphslot.h
└── graphtypes.h
└── src
├── graphlink.cpp
├── graphnode.cpp
├── graphnodefield.cpp
├── graphscene.cpp
└── graphslot.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files
2 | *.slo
3 | *.lo
4 | *.o
5 | *.obj
6 |
7 | # Precompiled Headers
8 | *.gch
9 | *.pch
10 |
11 | # Compiled Dynamic libraries
12 | *.so
13 | *.dylib
14 | *.dll
15 |
16 | # Fortran module files
17 | *.mod
18 |
19 | # Compiled Static libraries
20 | *.lai
21 | *.la
22 | *.a
23 | *.lib
24 |
25 | # Executables
26 | *.exe
27 | *.out
28 | *.app
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # QtNodeEditor
2 |
3 | Node editor for directed acyclic graph. supports multiple input/output slots and links. It is used by my animation editor which needs a composition graph.
4 |
5 |
6 |
7 | ##Features:
8 | 1. Node/link add and remove.
9 | 2. Drag & drop for links
10 | 3. Signals for node/link add and remove.
11 |
12 | ##Usage:
13 | 1. Open "demo/QtNodeEditorDemo.pro" in QtCreator.
14 | 2. See "demo/nodeeditor.cpp" for usage.
15 |
--------------------------------------------------------------------------------
/demo/QtNodeEditorDemo.pro:
--------------------------------------------------------------------------------
1 | #-------------------------------------------------
2 | #
3 | # Project created by QtCreator 2016-03-31T21:51:39
4 | #
5 | #-------------------------------------------------
6 |
7 | QT += core gui
8 |
9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
10 |
11 | TARGET = QtNodeEditorDemo
12 | TEMPLATE = app
13 |
14 | INCLUDEPATH += ../include
15 |
16 | SOURCES += main.cpp\
17 | ../src/graphlink.cpp \
18 | ../src/graphnode.cpp \
19 | ../src/graphnodefield.cpp \
20 | ../src/graphscene.cpp \
21 | ../src/graphslot.cpp \
22 | nodeeditor.cpp
23 |
24 | HEADERS += \
25 | ../include/graphlink.h \
26 | ../include/graphnode.h \
27 | ../include/graphnodefield.h \
28 | ../include/graphscene.h \
29 | ../include/graphslot.h \
30 | ../include/graphtypes.h \
31 | nodeeditor.h
32 |
33 | FORMS +=
34 |
--------------------------------------------------------------------------------
/demo/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "nodeeditor.h"
3 |
4 | int main(int argc, char *argv[])
5 | {
6 | QApplication a(argc, argv);
7 |
8 | NodeEditor editor;
9 | editor.show();
10 |
11 | return a.exec();
12 | }
13 |
--------------------------------------------------------------------------------
/demo/nodeeditor.cpp:
--------------------------------------------------------------------------------
1 | #include "nodeeditor.h"
2 | #include "graphnode.h"
3 |
4 | NodeEditor::NodeEditor(QWidget *parent) : QGraphicsView(parent)
5 | {
6 | // Users use GraphScene class to manipulate graph nodes and links.
7 | // It supports drag & drop style link operation.
8 | // Users can also use "delete" key to remove node or link.
9 | mScene = new GraphScene(this);
10 | setScene(mScene);
11 |
12 | // node ids returns by scene
13 | int node1;
14 | int node2;
15 | int node3;
16 |
17 | // add a node
18 | {
19 | int nodeId = mScene->AddNode("Node1");
20 | // One node may contains multiple fields.
21 | // A field has a input or output slot to connect to other nodes.
22 | // There're two type of slots: input & output.
23 | // The output slot can be only linked to input slots.
24 | mScene->AddNodeField(nodeId, true, "output1", false, GraphNodeFieldTypeNode);
25 | mScene->AddNodeField(nodeId, false, "input1", true, GraphNodeFieldTypeNode);
26 | // Set the node position
27 | mScene->SetNodePosition(nodeId, QPointF(10, 10));
28 | node1 = nodeId;
29 | }
30 |
31 | // add a node
32 | {
33 | int nodeId = mScene->AddNode("Node2");
34 | mScene->AddNodeField(nodeId, true, "output1", false, GraphNodeFieldTypeNode);
35 | mScene->AddNodeField(nodeId, false, "input1", true, GraphNodeFieldTypeNode);
36 | mScene->AddNodeField(nodeId, false, "input2", false, GraphNodeFieldTypeNode);
37 | mScene->AddNodeField(nodeId, false, "input3", false, GraphNodeFieldTypeNode);
38 | mScene->SetNodePosition(nodeId, QPointF(200, 10));
39 | node2 = nodeId;
40 | }
41 |
42 | // add a node
43 | {
44 | int nodeId = mScene->AddNode("Node3");
45 | mScene->AddNodeField(nodeId, true, "output1", false, GraphNodeFieldTypeNode);
46 | mScene->AddNodeField(nodeId, false, "input1", true, GraphNodeFieldTypeNode);
47 | mScene->SetNodePosition(nodeId, QPointF(10, 200));
48 | node3 = nodeId;
49 | }
50 |
51 | // add a link
52 | {
53 | mScene->AddLink(node1, "output1", node2, "input2", 0);
54 | }
55 |
56 | // add multiple links to one slot
57 | {
58 | // An input slot can accept multiple input nodes.
59 | // User can indicate the order of input nodes.
60 | // This is useful when the output depends on the order of inputs.
61 |
62 | // For example, in composition graph a composite node can blend multiple input images.
63 | // The composite image depends on the draw order of input images.
64 |
65 | // Some implementation of DAG use separate slots to support this function.
66 | // The cost is user must sort the slots by output order.
67 | // Others use separate nodes to simulate this function, which is not intuitive.
68 | mScene->AddLink(node1, "output1", node2, "input1", 0);
69 | mScene->AddLink(node3, "output1", node2, "input1", 1);
70 | }
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/demo/nodeeditor.h:
--------------------------------------------------------------------------------
1 | #ifndef NODEEDITOR_H
2 | #define NODEEDITOR_H
3 |
4 | #include
5 | #include
6 | #include "graphscene.h"
7 |
8 | class NodeEditor : public QGraphicsView
9 | {
10 | Q_OBJECT
11 | public:
12 | explicit NodeEditor(QWidget *parent = 0);
13 |
14 | signals:
15 |
16 | public slots:
17 |
18 | private:
19 | GraphScene* mScene;
20 | };
21 |
22 | #endif // NODEEDITOR_H
23 |
--------------------------------------------------------------------------------
/docs/node_editor_demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/walkerka/QtNodeEditor/d5f0a554f38a2dd1dcb7c1bfd02811f6df17831a/docs/node_editor_demo.png
--------------------------------------------------------------------------------
/include/graphlink.h:
--------------------------------------------------------------------------------
1 | #ifndef GRAPHLINK_H
2 | #define GRAPHLINK_H
3 | #include
4 | #include "graphtypes.h"
5 |
6 | class GraphScene;
7 | class GraphSlot;
8 |
9 | class GraphLink : public QGraphicsPathItem
10 | {
11 | public:
12 | GraphLink(GraphScene* scene, GraphSlot* beginSlot, GraphSlot* endSlot, int id);
13 | ~GraphLink();
14 | int type() const { return (int)GraphTypeLink; }
15 | void updateShape();
16 |
17 | GraphSlot* beginSlot() { return mBeginSlot; }
18 | GraphSlot* endSlot() { return mEndSlot; }
19 | int id() const { return mId; }
20 |
21 | public:
22 | QPainterPath shape() const;
23 |
24 | private:
25 | GraphScene* mScene;
26 | GraphSlot* mBeginSlot;
27 | GraphSlot* mEndSlot;
28 | int mId;
29 | };
30 |
31 | #endif // GRAPHLINK_H
32 |
--------------------------------------------------------------------------------
/include/graphnode.h:
--------------------------------------------------------------------------------
1 | #ifndef GRAPHLAYERITEM_H
2 | #define GRAPHLAYERITEM_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include "graphtypes.h"
9 |
10 | class GraphSlot;
11 | class GraphScene;
12 | class GraphNodeField;
13 |
14 | enum GraphHitTestResult
15 | {
16 | GraphHitTestResultNone,
17 | GraphHitTestResultInput
18 | };
19 |
20 | class GraphNode : public QGraphicsRectItem
21 | {
22 | public:
23 | GraphNode(GraphScene* scene, const QString& name, int id);
24 | virtual ~GraphNode();
25 |
26 | int type() const { return (int)GraphTypeNode; }
27 | bool hasParent(GraphNode* node);
28 | void addField(bool isOutput, const QString& name, bool isMultipleInput, GraphNodeFieldType type);
29 | QVector& fields() { return mFields; }
30 | int id() const { return mId; }
31 | GraphNodeField* field(const QString& name) const;
32 |
33 | protected:
34 | virtual QVariant itemChange( GraphicsItemChange change, const QVariant &value );
35 |
36 | private:
37 | GraphScene* mScene;
38 | QVector mFields;
39 | int mId;
40 | };
41 |
42 |
43 | #endif // LAYERGRAPHITEM_H
44 |
--------------------------------------------------------------------------------
/include/graphnodefield.h:
--------------------------------------------------------------------------------
1 | #ifndef GRAPHNODEFIELD_H
2 | #define GRAPHNODEFIELD_H
3 |
4 | #include "graphtypes.h"
5 | #include
6 | #include
7 |
8 | class GraphScene;
9 | class GraphNode;
10 | class GraphSlot;
11 |
12 | class GraphNodeField : public QGraphicsRectItem
13 | {
14 | public:
15 | GraphNodeField(GraphScene* scene, GraphNode* node, const QString& name, bool isMultipleInput, bool isOutput, GraphNodeFieldType type);
16 | void updateLinks();
17 | GraphSlot* slot() { return mSlot; }
18 | const QString& name() const { return mName; }
19 |
20 | private:
21 | QGraphicsTextItem* mNameItem;
22 | GraphSlot* mSlot;
23 | QString mName;
24 | };
25 |
26 | #endif // GRAPHNODEFIELD_H
27 |
--------------------------------------------------------------------------------
/include/graphscene.h:
--------------------------------------------------------------------------------
1 | #ifndef GRAPHSCENE_H
2 | #define GRAPHSCENE_H
3 | #include "graphtypes.h"
4 | #include
5 |
6 | class GraphNode;
7 | class GraphSlot;
8 | class GraphLink;
9 |
10 | class GraphScene : public QGraphicsScene
11 | {
12 | Q_OBJECT
13 | public:
14 | explicit GraphScene(QObject *parent = 0);
15 |
16 | void Clear();
17 | int AddNode(const QString& name);
18 | void SetNodePosition(int id, const QPointF& position);
19 | void AddNodeField(int id, bool isOutput, const QString& name, bool isMultipleInput, GraphNodeFieldType type);
20 | void RemoveNode(int id);
21 | int AddLink(int fromNode, const QString& fromName, int toNode, const QString& toName, int index);
22 | void RemoveLink(int fromNode, const QString& fromName, int toNode, const QString& toName, int index);
23 |
24 | private:
25 | friend class GraphNode;
26 | friend class GraphSlot;
27 | friend class GraphLink;
28 | GraphNode* CreateNode(const QString& name);
29 | void RemoveNode(GraphNode* node);
30 | GraphLink* AddLink(GraphSlot* fromSlot, GraphSlot* toSlot, int index, bool enableSignal = true);
31 | GraphLink* AddLink(GraphNode* fromNode, const QString& fromName, GraphNode* toNode, const QString& toName, int index);
32 | void RemoveLink(GraphLink* link);
33 | void RemoveLinksOfNode(GraphNode* node);
34 | GraphNode* GetNode(int id);
35 | void OnNodePositionChanged(GraphNode* node);
36 | void OnNodeSelectionChanged(GraphNode* node);
37 |
38 | protected:
39 | void mousePressEvent(QGraphicsSceneMouseEvent *event);
40 | void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
41 | void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
42 | void keyPressEvent(QKeyEvent *event);
43 |
44 | signals:
45 | void linkAdded(int id, int fromNode, QString fromName, int toNode, QString toName, int index);
46 | void linkRemoved(int id, int fromNode, QString fromName, int toNode, QString toName, int index);
47 | void nodeAdded(int id);
48 | void nodeRemoved(int id);
49 | void nodeMoved(int id, QPointF position);
50 | void nodeSelected(int id);
51 |
52 | public slots:
53 |
54 | private:
55 | int mNextNodeId;
56 | int mNextLinkId;
57 | std::vector mNodes;
58 | std::vector mLinks;
59 | };
60 |
61 | #endif // GRAPHSCENE_H
62 |
--------------------------------------------------------------------------------
/include/graphslot.h:
--------------------------------------------------------------------------------
1 | #ifndef GRAPHSLOT_H
2 | #define GRAPHSLOT_H
3 | #include
4 |
5 | class GraphScene;
6 | class GraphNode;
7 | class GraphLink;
8 |
9 | class GraphSlot : public QGraphicsRectItem
10 | {
11 | public:
12 | GraphSlot(GraphScene* scene, GraphNode* node, const QString& fieldName, bool isOut, bool isMultiple);
13 | ~GraphSlot();
14 |
15 | void updateLinks();
16 | void addLink(GraphLink* l);
17 | void addLink(GraphLink* l, int index);
18 | void removeLink(GraphLink* l);
19 | int linkCount() const { return (int)mLinks.size(); }
20 | bool isMultipleInput() const { return mIsMultiple; }
21 | QPointF linkAnchorPoint(GraphLink* link) const;
22 | GraphNode* node() { return mNode; }
23 | const QVector& links() { return mLinks; }
24 | QString fieldName() { return mFieldName; }
25 | bool isOutput() const { return mIsOut; }
26 |
27 | protected:
28 | void dragEnterEvent(QGraphicsSceneDragDropEvent *event);
29 | void dropEvent(QGraphicsSceneDragDropEvent *event);
30 | void mousePressEvent(QGraphicsSceneMouseEvent *event);
31 | void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
32 | void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
33 |
34 | bool canLinkTo(GraphSlot* slot);
35 | private:
36 | GraphScene* mScene;
37 | GraphNode* mNode;
38 | QString mFieldName;
39 | int mId;
40 | bool mIsOut;
41 | bool mIsMultiple;
42 | QVector mLinks;
43 | static QVector mSlots;
44 | };
45 |
46 | #endif // GRAPHSLOT_H
47 |
--------------------------------------------------------------------------------
/include/graphtypes.h:
--------------------------------------------------------------------------------
1 | #ifndef GRAPHTYPES_H
2 | #define GRAPHTYPES_H
3 |
4 | #include
5 |
6 | enum GraphType
7 | {
8 | GraphTypeNode = QGraphicsItem::UserType + 1,
9 | GraphTypeLink
10 | };
11 |
12 | enum GraphNodeFieldType
13 | {
14 | GraphNodeFieldTypeNode,
15 | GraphNodeFieldTypeFloat,
16 | GraphNodeFieldTypeInt,
17 | GraphNodeFieldTypeBool,
18 | };
19 |
20 | #endif // GRAPHTYPES_H
21 |
22 |
--------------------------------------------------------------------------------
/src/graphlink.cpp:
--------------------------------------------------------------------------------
1 | #include "graphlink.h"
2 | #include
3 | #include "graphslot.h"
4 | #include
5 |
6 | GraphLink::GraphLink(GraphScene* scene, GraphSlot* beginSlot, GraphSlot* endSlot, int id)
7 | :mScene(scene)
8 | ,mBeginSlot(beginSlot)
9 | ,mEndSlot(endSlot)
10 | ,mId(id)
11 | {
12 | setFlag(QGraphicsItem::ItemIsSelectable, true);
13 | }
14 |
15 | GraphLink::~GraphLink()
16 | {
17 | }
18 |
19 | void GraphLink::updateShape()
20 | {
21 | const QPointF& beginPos = mBeginSlot->linkAnchorPoint(this);
22 | QPointF endPos = mEndSlot->linkAnchorPoint(this);
23 |
24 | QPainterPath path;
25 | path.moveTo(beginPos);
26 | qreal dx = endPos.x() - beginPos.x();
27 | if (dx < 0)
28 | {
29 | dx = -dx;
30 | }
31 | if (dx > 50)
32 | {
33 | dx = 50;
34 | }
35 | QPointF offset(dx, 0.0f);
36 | path.cubicTo(beginPos + offset, endPos - offset, endPos);
37 | setPath(path);
38 | update();
39 | }
40 |
41 | QPainterPath GraphLink::shape() const
42 | {
43 | QPainterPathStroker s;
44 | s.setWidth(10);
45 | return s.createStroke(QGraphicsPathItem::shape());
46 | }
47 |
--------------------------------------------------------------------------------
/src/graphnode.cpp:
--------------------------------------------------------------------------------
1 | #include "graphnode.h"
2 | #include
3 | #include
4 | #include
5 | #include "graphscene.h"
6 | #include
7 | #include "graphslot.h"
8 | #include "graphlink.h"
9 | #include "graphnodefield.h"
10 | #include
11 |
12 | GraphNode::GraphNode(GraphScene* scene, const QString& name, int id)
13 | :mScene(scene)
14 | ,mId(id)
15 | {
16 | setBrush(QBrush(QColor(200,200,200)));
17 | setRect(0, 0, 100, 20);
18 |
19 | setFlag(QGraphicsItem::ItemIsSelectable, true);
20 | setFlag(QGraphicsItem::ItemIsMovable, true);
21 | setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
22 |
23 | QGraphicsTextItem* label = mScene->addText(name);
24 | label->setPos(5, (rect().height() - label->boundingRect().height()) * 0.5);
25 | label->setDefaultTextColor(QColor(0,0,0));
26 | label->setParentItem(this);
27 | }
28 |
29 | GraphNode::~GraphNode()
30 | {
31 | }
32 |
33 | void GraphNode::addField(bool isOutput, const QString& name, bool isMultipleInput, GraphNodeFieldType type)
34 | {
35 | GraphNodeField* field = new GraphNodeField(mScene, this, name, isMultipleInput, isOutput, type);
36 | field->setParentItem(this);
37 | mFields.push_back(field);
38 |
39 | qreal y = rect().height();
40 | foreach (GraphNodeField* f, mFields)
41 | {
42 | f->setPos(0, y);
43 | y += f->rect().height();
44 | }
45 | update();
46 | }
47 |
48 | QVariant GraphNode::itemChange( GraphicsItemChange change, const QVariant &value )
49 | {
50 | if (change == QGraphicsItem::ItemPositionHasChanged)
51 | {
52 | foreach (GraphNodeField* f, mFields)
53 | {
54 | f->updateLinks();
55 | }
56 | mScene->OnNodePositionChanged(this);
57 | }
58 | else if (change == QGraphicsItem::ItemSelectedHasChanged)
59 | {
60 | mScene->OnNodeSelectionChanged(this);
61 | }
62 | return value;
63 | }
64 |
65 |
66 | bool GraphNode::hasParent(GraphNode* node)
67 | {
68 | std::set inNodes;
69 | foreach (GraphNodeField* f, mFields)
70 | {
71 | if (f->slot()->isOutput())
72 | {
73 | continue;
74 | }
75 | foreach (GraphLink* link, f->slot()->links())
76 | {
77 | GraphNode* p = link->beginSlot()->node();
78 | if (p == node)
79 | {
80 | return true;
81 | }
82 | inNodes.insert(p);
83 | }
84 | }
85 |
86 | for (std::set::iterator it = inNodes.begin();
87 | it != inNodes.end(); ++it)
88 | {
89 | if ((*it)->hasParent(node))
90 | {
91 | return true;
92 | }
93 | }
94 | return false;
95 | }
96 |
97 | GraphNodeField* GraphNode::field(const QString& name) const
98 | {
99 | for (int i = 0; i < mFields.size(); ++i)
100 | {
101 | if (mFields[i]->name() == name)
102 | {
103 | return mFields[i];
104 | }
105 | }
106 | return NULL;
107 | }
108 |
--------------------------------------------------------------------------------
/src/graphnodefield.cpp:
--------------------------------------------------------------------------------
1 | #include "graphnodefield.h"
2 | #include
3 | #include "graphslot.h"
4 | #include "graphscene.h"
5 |
6 | GraphNodeField::GraphNodeField(GraphScene* scene, GraphNode* node, const QString& name, bool isMultipleInput, bool isOutput, GraphNodeFieldType type)
7 | :mName(name)
8 | {
9 | setRect(0, 0, 100, 30);
10 | mNameItem = scene->addText(name);
11 | mNameItem->setParentItem(this);
12 | mNameItem->setPos(5, (rect().height() - mNameItem->boundingRect().height()) * 0.5);
13 |
14 | if (isOutput)
15 | {
16 | mSlot = new GraphSlot(scene, node, name, true, false);
17 | mSlot->setParentItem(this);
18 | mSlot->setPos(rect().width(), (rect().height() - mSlot->rect().height()) * 0.5);
19 | }
20 | else
21 | {
22 | mSlot = new GraphSlot(scene, node, name, false, isMultipleInput);
23 | mSlot->setParentItem(this);
24 | mSlot->setPos(-mSlot->rect().width(), (rect().height() - mSlot->rect().height()) * 0.5);
25 |
26 | // @TODO: Embedded editable fields are not finished yet
27 | QWidget* editorWidget = NULL;
28 | switch (type)
29 | {
30 | case GraphNodeFieldTypeInt:
31 | editorWidget = new QSpinBox();
32 | break;
33 | case GraphNodeFieldTypeFloat:
34 | editorWidget = new QDoubleSpinBox();
35 | break;
36 | case GraphNodeFieldTypeBool:
37 | editorWidget = new QCheckBox();
38 | break;
39 | case GraphNodeFieldTypeNode:
40 | default:
41 | break;
42 | }
43 |
44 | if (editorWidget)
45 | {
46 | QGraphicsProxyWidget* editor = new QGraphicsProxyWidget(this);
47 | editor->setWidget(editorWidget);
48 | editor->setPos(mNameItem->boundingRect().right() + 5, (rect().height() - editor->rect().height()) * 0.5);
49 | }
50 | }
51 | }
52 |
53 | void GraphNodeField::updateLinks()
54 | {
55 | mSlot->updateLinks();
56 | }
57 |
--------------------------------------------------------------------------------
/src/graphscene.cpp:
--------------------------------------------------------------------------------
1 | #include "graphscene.h"
2 | #include "graphnode.h"
3 | #include "graphlink.h"
4 | #include "graphslot.h"
5 | #include "graphnodefield.h"
6 | #include
7 | #include
8 |
9 | GraphScene::GraphScene(QObject *parent)
10 | :QGraphicsScene(parent)
11 | ,mNextNodeId(1)
12 | ,mNextLinkId(1)
13 | {
14 | }
15 |
16 | GraphNode* GraphScene::CreateNode(const QString& name)
17 | {
18 | GraphNode* node = new GraphNode(this, name, mNextNodeId++);
19 | addItem(node);
20 | mNodes.push_back(node);
21 | emit nodeAdded(node->id());
22 | return node;
23 | }
24 |
25 | void GraphScene::RemoveNode(GraphNode* node)
26 | {
27 | for (size_t i = 0; i < mNodes.size(); ++i)
28 | {
29 | if (mNodes[i] == node)
30 | {
31 | RemoveLinksOfNode(node);
32 | int id = node->id();
33 | std::vector::iterator it = mNodes.begin();
34 | it += i;
35 | mNodes.erase(it);
36 | removeItem(node);
37 | delete node;
38 | emit nodeRemoved(id);
39 | break;
40 | }
41 | }
42 | }
43 |
44 | void GraphScene::RemoveLinksOfNode(GraphNode* node)
45 | {
46 | foreach(GraphNodeField* field, node->fields())
47 | {
48 | foreach(GraphLink* link, field->slot()->links())
49 | {
50 | RemoveLink(link);
51 | }
52 | }
53 | }
54 |
55 | GraphLink* GraphScene::AddLink(GraphSlot* fromSlot, GraphSlot* toSlot, int index, bool enableSignal)
56 | {
57 | if (!toSlot->isMultipleInput())
58 | {
59 | for (size_t i = 0; i < mLinks.size(); ++i)
60 | {
61 | if (mLinks[i]->endSlot() == toSlot)
62 | {
63 | RemoveLink(mLinks[i]);
64 | break;
65 | }
66 | }
67 | }
68 |
69 | GraphLink* link = new GraphLink(this, fromSlot, toSlot, mNextNodeId++);
70 | addItem(link);
71 | link->setZValue(-1);
72 | link->updateShape();
73 | fromSlot->addLink(link);
74 | toSlot->addLink(link, index);
75 | mLinks.push_back(link);
76 | if (enableSignal)
77 | {
78 | emit linkAdded(link->id(), fromSlot->node()->id(), fromSlot->fieldName(), toSlot->node()->id(), toSlot->fieldName(), index);
79 | }
80 | return link;
81 | }
82 |
83 | GraphLink* GraphScene::AddLink(GraphNode* fromNode, const QString& fromName, GraphNode* toNode, const QString& toName, int index)
84 | {
85 | GraphNodeField* fromField = fromNode->field(fromName);
86 | if (!fromField)
87 | {
88 | return NULL;
89 | }
90 | foreach (GraphNodeField* field, toNode->fields())
91 | {
92 | if (field->name() == toName)
93 | {
94 | return AddLink(fromField->slot(), field->slot(), index, false);
95 | }
96 | }
97 | return NULL;
98 | }
99 |
100 | void GraphScene::RemoveLink(GraphLink* link)
101 | {
102 | for (size_t i = 0; i < mLinks.size(); ++i)
103 | {
104 | if (mLinks[i] == link)
105 | {
106 | int id = link->id();
107 | int fromId = link->beginSlot()->node()->id();
108 | int toId = link->endSlot()->node()->id();
109 | QString fromName = link->beginSlot()->fieldName();
110 | QString toName = link->endSlot()->fieldName();
111 |
112 | std::vector::iterator it = mLinks.begin();
113 | it += i;
114 | link->beginSlot()->removeLink(link);
115 | link->endSlot()->removeLink(link);
116 | mLinks.erase(it);
117 | removeItem(link);
118 | delete link;
119 | emit linkRemoved(id, fromId, fromName, toId, toName, i);
120 | break;
121 | }
122 | }
123 | }
124 |
125 | void GraphScene::Clear()
126 | {
127 | for (size_t i = 0; i < mLinks.size(); ++i)
128 | {
129 | GraphLink* link = mLinks[i];
130 | removeItem(link);
131 | delete link;
132 | }
133 |
134 | for (size_t i = 0; i < mNodes.size(); ++i)
135 | {
136 | GraphNode* node = mNodes[i];
137 | removeItem(node);
138 | delete node;
139 | }
140 | mLinks.clear();
141 | mNodes.clear();
142 | }
143 |
144 | void GraphScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
145 | {
146 | QGraphicsScene::mousePressEvent(event);
147 | }
148 |
149 | void GraphScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
150 | {
151 | QGraphicsScene::mouseMoveEvent(event);
152 | }
153 |
154 | void GraphScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
155 | {
156 | QGraphicsScene::mouseReleaseEvent(event);
157 | }
158 |
159 | void GraphScene::keyPressEvent(QKeyEvent *event)
160 | {
161 | if (event->key() == Qt::Key_Delete)
162 | {
163 | const QList& sel = selectedItems();
164 | if (sel.empty())
165 | {
166 | return;
167 | }
168 |
169 | QGraphicsItem* item = sel.front();
170 | GraphType type = (GraphType)item->type();
171 | if (type == GraphTypeNode)
172 | {
173 | RemoveNode((GraphNode*)item);
174 | }
175 | else if (type == GraphTypeLink)
176 | {
177 | RemoveLink((GraphLink*)item);
178 | }
179 | }
180 | }
181 |
182 | int GraphScene::AddNode(const QString& name)
183 | {
184 | GraphNode* node = CreateNode(name);
185 | return node->id();
186 | }
187 |
188 | void GraphScene::RemoveNode(int id)
189 | {
190 | for (size_t i = 0; i < mNodes.size(); ++i)
191 | {
192 | if (mNodes[i]->id() == id)
193 | {
194 | RemoveNode(mNodes[i]);
195 | break;
196 | }
197 | }
198 | }
199 |
200 | void GraphScene::SetNodePosition(int id, const QPointF& position)
201 | {
202 | GraphNode* node = GetNode(id);
203 | if (node)
204 | {
205 | node->setPos(position);
206 | }
207 | }
208 |
209 | void GraphScene::AddNodeField(int id, bool isOutput, const QString& name, bool isMultipleInput, GraphNodeFieldType type)
210 | {
211 | GraphNode* node = GetNode(id);
212 | if (node)
213 | {
214 | node->addField(isOutput, name, isMultipleInput, type);
215 | }
216 | }
217 |
218 | int GraphScene::AddLink(int fromNode, const QString& fromName, int toNode, const QString& toName, int index)
219 | {
220 | GraphNode* from = GetNode(fromNode);
221 | GraphNode* to = GetNode(toNode);
222 |
223 | if (from == NULL || to == NULL)
224 | {
225 | return 0;
226 | }
227 |
228 | GraphLink* link = AddLink(from, fromName, to, toName, index);
229 | return link->id();
230 | }
231 |
232 | void GraphScene::RemoveLink(int fromNode, const QString& /*fromName*/, int toNode, const QString& toName, int index)
233 | {
234 | GraphNode* from = GetNode(fromNode);
235 | GraphNode* to = GetNode(toNode);
236 |
237 | if (from == NULL || to == NULL)
238 | {
239 | return;
240 | }
241 |
242 | GraphNodeField* f = to->field(toName);
243 | if (f == NULL || f->slot()->linkCount() <= index)
244 | {
245 | return;
246 | }
247 |
248 | GraphLink* l = f->slot()->links()[index];
249 | if (l->beginSlot()->node() == from)
250 | {
251 | RemoveLink(l);
252 | }
253 | }
254 |
255 | GraphNode* GraphScene::GetNode(int id)
256 | {
257 | for (size_t i = 0; i < mNodes.size(); ++i)
258 | {
259 | if (mNodes[i]->id() == id)
260 | {
261 | return mNodes[i];
262 | }
263 | }
264 | return NULL;
265 | }
266 |
267 | void GraphScene::OnNodePositionChanged(GraphNode* node)
268 | {
269 | emit nodeMoved(node->id(), node->pos());
270 | }
271 |
272 | void GraphScene::OnNodeSelectionChanged(GraphNode* node)
273 | {
274 | emit nodeSelected(node->id());
275 | }
276 |
--------------------------------------------------------------------------------
/src/graphslot.cpp:
--------------------------------------------------------------------------------
1 | #include "graphslot.h"
2 | #include
3 | #include "graphscene.h"
4 | #include "graphlink.h"
5 | #include "graphnode.h"
6 |
7 | QVector GraphSlot::mSlots;
8 |
9 | GraphSlot::GraphSlot(GraphScene* scene, GraphNode* node, const QString& fieldName, bool isOut, bool isMultiple)
10 | :mScene(scene)
11 | ,mNode(node)
12 | ,mFieldName(fieldName)
13 | ,mId(mSlots.size())
14 | ,mIsOut(isOut)
15 | ,mIsMultiple(isMultiple)
16 | {
17 | setRect(0, 0, 10, isMultiple ? 20 : 10);
18 |
19 | setAcceptDrops(true);
20 | mSlots.push_back(this);
21 | }
22 |
23 | GraphSlot::~GraphSlot()
24 | {
25 | }
26 |
27 | void GraphSlot::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
28 | {
29 | if (event->mimeData() && event->mimeData()->hasFormat("application/x-graphslot"))
30 | {
31 | QByteArray b = event->mimeData()->data("application/x-graphslot");
32 | QDataStream dataStream(&b, QIODevice::ReadOnly);
33 |
34 | int id;
35 | int index;
36 | dataStream >> id;
37 | dataStream >> index;
38 | if (id != mId && id < mSlots.size())
39 | {
40 | GraphSlot* slot = mSlots[id];
41 | if (canLinkTo(slot))
42 | {
43 | event->setAccepted(true);
44 | return;
45 | }
46 | }
47 | }
48 |
49 | event->setAccepted(false);
50 | }
51 |
52 | void GraphSlot::dropEvent(QGraphicsSceneDragDropEvent *event)
53 | {
54 | if (event->mimeData() && event->mimeData()->hasFormat("application/x-graphslot"))
55 | {
56 | QByteArray b = event->mimeData()->data("application/x-graphslot");
57 | QDataStream dataStream(&b, QIODevice::ReadOnly);
58 |
59 | int id;
60 | int index;
61 | dataStream >> id;
62 | dataStream >> index;
63 | if (id != mId && id < mSlots.size())
64 | {
65 | GraphSlot* slot = mSlots[id];
66 | if (canLinkTo(slot))
67 | {
68 | if (mIsOut)
69 | {
70 | mScene->AddLink(this, slot, index);
71 | }
72 | else
73 | {
74 | if (mIsMultiple)
75 | {
76 | index = (int)((mLinks.size() + 1) * event->pos().y() / rect().height());
77 | mScene->AddLink(slot, this, index);
78 | }
79 | else
80 | {
81 | mScene->AddLink(slot, this, 0);
82 | }
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
89 | void GraphSlot::mousePressEvent(QGraphicsSceneMouseEvent* /*event*/)
90 | {
91 | }
92 |
93 | void GraphSlot::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
94 | {
95 | if (QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton))
96 | .length() < QApplication::startDragDistance()) {
97 | return;
98 | }
99 |
100 | QDrag *drag = new QDrag(event->widget());
101 | QMimeData *mime = new QMimeData;
102 |
103 | QByteArray itemData;
104 | QDataStream dataStream(&itemData, QIODevice::WriteOnly);
105 | dataStream << mId;
106 | if (!mIsOut && mIsMultiple)
107 | {
108 | int index = (int)((mLinks.size() + 1) * event->pos().y() / rect().height());
109 | dataStream << index;
110 | }
111 | else
112 | {
113 | dataStream << 0;
114 | }
115 |
116 | mime->setData("application/x-graphslot", itemData);
117 | drag->setMimeData(mime);
118 |
119 | drag->exec();
120 | }
121 |
122 | void GraphSlot::mouseReleaseEvent(QGraphicsSceneMouseEvent* /*event*/)
123 | {
124 | }
125 |
126 | void GraphSlot::updateLinks()
127 | {
128 | for (int i = 0; i < mLinks.size(); ++i)
129 | {
130 | mLinks[i]->updateShape();
131 | }
132 | }
133 |
134 | void GraphSlot::addLink(GraphLink* l)
135 | {
136 | if (mLinks.indexOf(l) != -1)
137 | {
138 | return;
139 | }
140 | mLinks.push_back(l);
141 | updateLinks();
142 | }
143 |
144 | void GraphSlot::addLink(GraphLink* l, int index)
145 | {
146 | if (mLinks.indexOf(l) != -1)
147 | {
148 | return;
149 | }
150 |
151 | if (index < 0)
152 | {
153 | index = 0;
154 | }
155 | if (index > mLinks.size())
156 | {
157 | index = mLinks.size();
158 | }
159 |
160 | mLinks.insert(index, l);
161 | updateLinks();
162 | }
163 |
164 | void GraphSlot::removeLink(GraphLink* l)
165 | {
166 | int index = mLinks.indexOf(l);
167 | if (index != -1)
168 | {
169 | mLinks.removeAt(index);
170 | updateLinks();
171 | }
172 | }
173 |
174 | bool GraphSlot::canLinkTo(GraphSlot* slot)
175 | {
176 | if (slot->mNode != mNode && slot->mIsOut != mIsOut)
177 | {
178 | GraphNode* nIn = NULL;
179 | GraphNode* nOut = NULL;
180 |
181 | if (mIsOut)
182 | {
183 | nOut = mNode;
184 | nIn = slot->mNode;
185 | }
186 | else
187 | {
188 | nIn = mNode;
189 | nOut = slot->mNode;
190 | }
191 |
192 | return !nOut->hasParent(nIn);
193 | }
194 | return false;
195 | }
196 |
197 | QPointF GraphSlot::linkAnchorPoint(GraphLink* link) const
198 | {
199 | QPointF endPos = mapToScene(rect().center());
200 | int linkCount = this->linkCount();
201 | if (isMultipleInput() && linkCount > 1)
202 | {
203 | int index = mLinks.indexOf(link);
204 | if (index != -1)
205 | {
206 | endPos.setY(endPos.y() + rect().height() * (((qreal)index + 1) / (linkCount + 1) - 0.5));
207 | }
208 | }
209 | return endPos;
210 | }
211 |
--------------------------------------------------------------------------------