├── .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 | --------------------------------------------------------------------------------