diff --git a/.gitignore b/.gitignore index c9eb5941f..6c8dd3dc6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.py *.pyc CMakeLists.txt.user +/build \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 23328dbbb..71390c589 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,8 @@ set(CPP_SOURCE_FILES src/FlowView.cpp src/FlowViewStyle.cpp src/Node.cpp + src/Group.cpp + src/GroupGraphicsObject.cpp src/NodeConnectionInteraction.cpp src/NodeDataModel.cpp src/NodeGeometry.cpp diff --git a/include/nodes/internal/Connection.hpp b/include/nodes/internal/Connection.hpp index a563c14ca..1fca7f872 100644 --- a/include/nodes/internal/Connection.hpp +++ b/include/nodes/internal/Connection.hpp @@ -23,6 +23,7 @@ namespace QtNodes class Node; class NodeData; class ConnectionGraphicsObject; +class Group; /// class NODE_EDITOR_PUBLIC Connection @@ -44,7 +45,8 @@ class NODE_EDITOR_PUBLIC Connection Connection(Node& nodeIn, PortIndex portIndexIn, Node& nodeOut, - PortIndex portIndexOut); + PortIndex portIndexOut, + QUuid *id = nullptr); Connection(const Connection&) = delete; Connection operator=(const Connection&) = delete; @@ -55,7 +57,8 @@ class NODE_EDITOR_PUBLIC Connection QJsonObject save() const override; - + + QJsonObject copyWithNewID(QUuid in, QUuid out); public: QUuid @@ -78,6 +81,10 @@ class NODE_EDITOR_PUBLIC Connection setNodeToPort(Node& node, PortType portType, PortIndex portIndex); + void + setGroup(Group* group, + PortType portType, + PortIndex portIndex); void removeFromNodes() const; @@ -104,8 +111,14 @@ class NODE_EDITOR_PUBLIC Connection Node*& getNode(PortType portType); + Group* + getGroup(PortType portType) const; + PortIndex getPortIndex(PortType portType) const; + + PortIndex + getGroupPortIndex(PortType portType) const; void clearNode(PortType portType); @@ -132,6 +145,12 @@ class NODE_EDITOR_PUBLIC Connection PortIndex _outPortIndex; PortIndex _inPortIndex; + Group* _outGroup = nullptr; + Group* _inGroup = nullptr; + + PortIndex _outGroupPortIndex; + PortIndex _inGroupPortIndex; + private: ConnectionState _connectionState; diff --git a/include/nodes/internal/DataModelRegistry.hpp b/include/nodes/internal/DataModelRegistry.hpp index 674bc67b9..6adab92b8 100644 --- a/include/nodes/internal/DataModelRegistry.hpp +++ b/include/nodes/internal/DataModelRegistry.hpp @@ -24,6 +24,8 @@ class NODE_EDITOR_PUBLIC DataModelRegistry using RegisteredModelsCategoryMap = std::unordered_map; using CategoriesSet = std::set; + using RegisteredTemplatesMap = std::unordered_map; + struct TypeConverterItem { RegistryItemPtr Model{}; @@ -82,10 +84,23 @@ class NODE_EDITOR_PUBLIC DataModelRegistry converter->DestinationType = converter->Model->dataType(PortType::Out, 0); auto typeConverterKey = std::make_pair(converter->SourceType.id, converter->DestinationType.id); - _registeredTypeConverters[typeConverterKey] = std::move(converter); + _registeredTypeConverters[typeConverterKey] = std::move(converter); } } + void + registerTemplate(QString templateName, QString templateFilePath) + { + _registeredTemplates[templateName] = templateFilePath; + } + + void + removeTemplate(QString templateName) + { + _registeredTemplates.erase(templateName); + } + + //Parameter order alias, so a category can be set without forcing to manually pass a model instance template void @@ -103,6 +118,9 @@ class NODE_EDITOR_PUBLIC DataModelRegistry RegisteredModelsCategoryMap const & registeredModelsCategoryAssociation() const; + RegisteredTemplatesMap & + RegisteredTemplates(); + CategoriesSet const & categories() const; @@ -116,5 +134,7 @@ class NODE_EDITOR_PUBLIC DataModelRegistry CategoriesSet _categories{}; RegisteredModelsMap _registeredModels{}; RegisteredTypeConvertersMap _registeredTypeConverters{}; + + RegisteredTemplatesMap _registeredTemplates{}; }; } diff --git a/include/nodes/internal/FlowScene.hpp b/include/nodes/internal/FlowScene.hpp index aa00de901..9b59d50ff 100644 --- a/include/nodes/internal/FlowScene.hpp +++ b/include/nodes/internal/FlowScene.hpp @@ -11,6 +11,7 @@ #include "QUuidStdHash.hpp" #include "Export.hpp" #include "DataModelRegistry.hpp" +#include namespace QtNodes { @@ -18,11 +19,32 @@ namespace QtNodes class NodeDataModel; class FlowItemInterface; class Node; +class Group; class NodeGraphicsObject; class Connection; class ConnectionGraphicsObject; class NodeStyle; + + +struct UndoRedoAction { + + std::function undoAction; + std::function redoAction; + std::string name; + UndoRedoAction(std::function undoAction, std::function redoAction, std::string name) { + this->undoAction = undoAction; + this->redoAction = redoAction; + this->name = name; + }; +}; + + +struct Anchor { + QPointF position; + double scale; +}; + /// Scene holds connections and nodes. class NODE_EDITOR_PUBLIC FlowScene : public QGraphicsScene @@ -35,6 +57,10 @@ class NODE_EDITOR_PUBLIC FlowScene ~FlowScene(); + std::vector undoActions; + std::vector redoActions; + void PrintActions(); + int historyInx; public: std::shared_ptrcreateConnection(PortType connectedPort, @@ -44,18 +70,37 @@ class NODE_EDITOR_PUBLIC FlowScene std::shared_ptrcreateConnection(Node& nodeIn, PortIndex portIndexIn, Node& nodeOut, - PortIndex portIndexOut); + PortIndex portIndexOut, + QUuid *id=nullptr); std::shared_ptrrestoreConnection(QJsonObject const &connectionJson); void deleteConnection(Connection& connection); + void deleteConnection(Connection* connection); + void deleteConnectionWithID(QUuid id); Node&createNode(std::unique_ptr && dataModel); + + Node&createNodeWithID(std::unique_ptr && dataModel, QUuid id); + + Group& createGroup(); + + Group& pasteGroup(QJsonObject const& nodeJson, QPointF nodeGroupCentroid, QPointF mousePos); - Node&restoreNode(QJsonObject const& nodeJson); + Node&restoreNode(QJsonObject const& nodeJson, bool keepId=false); + + Group& restoreGroup(QJsonObject const& nodeJson); + + QUuid pasteNode(QJsonObject &json, QPointF nodeGroupCentroid, QPointF mousePos); + + void pasteConnection(QJsonObject const &connectionJson, QUuid newIn, QUuid newOut); void removeNode(Node& node); + void removeNodeWithID(QUuid id); + + void removeGroup(Group& node); + DataModelRegistry®istry() const; void setRegistry(std::shared_ptr registry); @@ -71,13 +116,19 @@ class NODE_EDITOR_PUBLIC FlowScene void setNodePosition(Node& node, const QPointF& pos) const; QSizeF getNodeSize(const Node& node) const; + + void resolveGroups(Group& node); + + void resolveGroups(Node& n); + public: - std::unordered_map > const &nodes() const; + std::unordered_map > const &nodes() const; + std::unordered_map > &nodes(); std::unordered_map > const &connections() const; - std::vectorselectedNodes() const; + std::vector>selectedNodes() const; public: @@ -89,20 +140,45 @@ class NODE_EDITOR_PUBLIC FlowScene QByteArray saveToMemory() const; + void saveToClipBoard(); + void loadFromMemory(const QByteArray& data); + + void AddAction(UndoRedoAction action); + + void Undo(); + + void Redo(); + + void ResetHistory(); + + int GetHistoryIndex(); signals: void nodeCreated(Node &n); + + void groupCreated(Group &g); void nodeDeleted(Node &n); void connectionCreated(Connection &c); + void connectionDeleted(Connection &c); void nodeMoved(Node& n, const QPointF& newLocation); + void groupMoved(Group& n, const QPointF& newLocation); + + void nodeMoveFinished(Node& n, const QPointF& newLocation, const QPointF &oldLocation); + + void groupMoveFinished(Group& g, const QPointF& newLocation, const QPointF& oldLocation); + + void nodeClicked(Node& n); + void nodeDoubleClicked(Node& n); + + void groupDoubleClicked(Group& g); void connectionHovered(Connection& c, QPoint screenPos); @@ -113,18 +189,35 @@ class NODE_EDITOR_PUBLIC FlowScene void nodeHoverLeft(Node& n); void nodeContextMenu(Node& n, const QPointF& pos); + + +public: + + std::vector anchors; + + int gridSize=1; + bool snapping=false; private: using SharedConnection = std::shared_ptr; - using UniqueNode = std::unique_ptr; + using UniqueNode = std::shared_ptr; std::unordered_map _connections; std::unordered_map _nodes; std::shared_ptr _registry; + std::unordered_map> _groups; + + bool writeToHistory; + + +private: }; Node* locateNodeAt(QPointF scenePoint, FlowScene &scene, QTransform viewTransform); +Group* +locateGroupAt(QPointF scenePoint, FlowScene &scene, + QTransform viewTransform); } diff --git a/include/nodes/internal/FlowView.hpp b/include/nodes/internal/FlowView.hpp index 378ff2e6d..904fba717 100644 --- a/include/nodes/internal/FlowView.hpp +++ b/include/nodes/internal/FlowView.hpp @@ -3,11 +3,12 @@ #include #include "Export.hpp" - + namespace QtNodes { class FlowScene; +class NodeGraphicsObject; class NODE_EDITOR_PUBLIC FlowView : public QGraphicsView @@ -27,13 +28,28 @@ class NODE_EDITOR_PUBLIC FlowView void setScene(FlowScene *scene); + QJsonObject selectionToJson(bool includePartialConnections=false); + void jsonToScene(QJsonObject object); + void jsonToSceneMousePos(QJsonObject object); + void deleteJsonElements(const QJsonObject &object); + + void goToNode(NodeGraphicsObject *node); + void goToNodeID(QUuid ID); + + public slots: void scaleUp(); void scaleDown(); - + void deleteSelectedNodes(); + + void duplicateSelectedNode(); + + void copySelectedNodes(); + + void pasteSelectedNodes(); protected: @@ -53,6 +69,10 @@ public slots: void showEvent(QShowEvent *event) override; + void addAnchor(int index); + void goToAnchor(int index); + + protected: FlowScene * scene(); @@ -61,6 +81,13 @@ public slots: QAction* _clearSelectionAction; QAction* _deleteSelectionAction; + QAction* _duplicateSelectionAction; + QAction* _copymultiplenodes; + QAction* _pastemultiplenodes; + QAction* _undoAction; + QAction* _redoAction; + + std::vector anchorActions; QPointF _clickPos; diff --git a/include/nodes/internal/Group.hpp b/include/nodes/internal/Group.hpp new file mode 100644 index 000000000..cb3723539 --- /dev/null +++ b/include/nodes/internal/Group.hpp @@ -0,0 +1,112 @@ + +#pragma once + +#include + +#include +#include + +#include + +#include "PortType.hpp" + +#include "Export.hpp" +#include "NodeState.hpp" +#include "NodeGeometry.hpp" +#include "NodeData.hpp" +#include "GroupGraphicsObject.hpp" +#include "ConnectionGraphicsObject.hpp" +#include "Serializable.hpp" +#include "Node.hpp" +#include "NodeDataModel.hpp" + +namespace QtNodes +{ + +class GroupGraphicsObject; + +class NODE_EDITOR_PUBLIC Group + : public QObject + , public Serializable +{ + Q_OBJECT +public: + GroupGraphicsObject & + groupGraphicsObject() { + return *_groupGraphicsObject.get(); + } + + GroupGraphicsObject const & + groupGraphicsObject() const { + return *_groupGraphicsObject.get(); + } + + Group(FlowScene &scene): _scene(scene), + _id(QUuid::createUuid()), + _groupGraphicsObject(nullptr), + _name("New Group") + { + + } + + void AddNode(std::shared_ptr node) { + nodes.push_back(node); + } + + + void + setGraphicsObject(std::shared_ptr graphics) { + _groupGraphicsObject = graphics; + } + + + virtual + ~Group(){}; + + void SetName(QString _name); + QString GetName(); + +public: + + + QUuid + id() const; + + QJsonObject save() const override{ + QJsonObject groupJson; + groupJson["name"] = _name; + bool collapsed = _groupGraphicsObject->isCollapsed(); + groupJson["collapsed"] = (int)collapsed; + + QJsonObject posObj; + posObj["x"] = _groupGraphicsObject->pos().x(); + posObj["y"] = _groupGraphicsObject->pos().y(); + groupJson["position"] = posObj; + + QJsonObject sizeObj; + sizeObj["x"] = collapsed ? _groupGraphicsObject->getSavedSizeX() : _groupGraphicsObject->getSizeX(); + sizeObj["y"] = collapsed ? _groupGraphicsObject->getSavedSizeY() : _groupGraphicsObject->getSizeY(); + groupJson["size"] = sizeObj; + + QJsonObject colObj; + colObj["r"] = _groupGraphicsObject->r; + colObj["g"] = _groupGraphicsObject->g; + colObj["b"] = _groupGraphicsObject->b; + groupJson["color"] = colObj; + return groupJson; + }; + + void restore(QJsonObject const &json) override; + + void restoreAtPosition(QJsonObject const &json, QPointF position); + + std::shared_ptr _groupGraphicsObject; + QString _name; +private: + + FlowScene & _scene; + std::vector> nodes; + + QUuid _id; +}; +} \ No newline at end of file diff --git a/include/nodes/internal/GroupGraphicsObject.hpp b/include/nodes/internal/GroupGraphicsObject.hpp new file mode 100644 index 000000000..a28da495c --- /dev/null +++ b/include/nodes/internal/GroupGraphicsObject.hpp @@ -0,0 +1,159 @@ +#pragma once + +#include +#include + + +#include "NodeGeometry.hpp" +#include "NodeState.hpp" +#include "PortType.hpp" +#include +#include +#include +class QGraphicsProxyWidget; + +namespace QtNodes +{ + +class FlowScene; +class FlowItemEntry; +class Group; +class Connection; + +/// Class reacts on GUI events, mouse clicks and +/// forwards painting operation. +class GroupGraphicsObject : public QGraphicsObject +{ + Q_OBJECT + +public: + GroupGraphicsObject(FlowScene &scene, Group& group); + + virtual + ~GroupGraphicsObject(); + + Group& + group(); + + Group const& + group() const; + + + QRectF + boundingRect() const override; + + bool + isCollapsed() {return collapsed;} + + void + setGeometryChanged(); + + /// Visits all attached connections and corrects + /// their corresponding end points. + void + moveConnections() const; + + enum { Type = UserType + 5 }; + + int + type() const override { return Type; } + + void + lock(bool locked); + + uint8_t r, g, b; + QLineEdit* nameLineEdit; + QGraphicsProxyWidget* _proxyWidget; + QGraphicsProxyWidget* _collapseButton; + QPushButton *collapseButtonWidget; + + int getSizeX() {return sizeX;} + int getSizeY() {return sizeY;} + int getSavedSizeX() {return savedSizeX;} + int getSavedSizeY() {return savedSizeY;} + + void setSizeX(int size) {sizeX = size;} + void setSizeY(int size) {sizeY = size;} + + void Collapse(); + + QPointF portScenePosition(int i, PortType type) const; + + QPointF portPosition(int i, PortType type) const; + + int + checkHitScenePoint(PortType portType, + QPointF const point, + QTransform t = QTransform()) const; + + + std::array, 3> inOutNodes; + std::array, 3> inOutPorts; + + //Store pointers to connections from in and out. + std::array, 3> inOutConnections; +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: + QPointF oldPosition; + FlowScene & _scene; + Group& _group; + + bool isResizingX=false; + bool isResizingY=false; + bool isResizingXY=false; + + int sizeX=1000; + int sizeY=1000; + + + float inputSize = 10; + float spacing = 40; + float topPadding = 30; + bool collapsed=false; + int savedSizeX, savedSizeY; + + //Store connections that stay strictly inside group's bound, to set them invisible when collapsing + std::vector unusedConnections; + + //Store the labels of the in and out when collapsed + std::array, 3> inOutLabels; + + + +signals: + void CollapseTriggered(bool state); + +}; +} diff --git a/include/nodes/internal/Node.hpp b/include/nodes/internal/Node.hpp index 793e8a854..8659f81cc 100644 --- a/include/nodes/internal/Node.hpp +++ b/include/nodes/internal/Node.hpp @@ -36,6 +36,7 @@ class NODE_EDITOR_PUBLIC Node /// NodeDataModel should be an rvalue and is moved into the Node Node(std::unique_ptr && dataModel); + virtual ~Node(); @@ -44,14 +45,30 @@ class NODE_EDITOR_PUBLIC Node QJsonObject save() const override; + + QJsonObject copyWithNewID(QUuid newId) const; + + void restore(QJsonObject const &json) override; + void + paste(QJsonObject const &json, QUuid ID); + + + void + updateView(); + + void + eraseInputAtIndex(int portIndex); + public: QUuid id() const; + void setId(QUuid id); + void reactToPossibleConnection(PortType, NodeDataType, QPointF const & scenePoint); @@ -85,6 +102,13 @@ class NODE_EDITOR_PUBLIC Node NodeDataModel* nodeDataModel() const; + void setInputSelected(int inx, bool selected); + + int targetInputConnections=0; + int currentInputConnections=0; + + std::vector inputSelected; + public slots: // data propagation /// Propagates incoming data to the underlying model. @@ -97,6 +121,11 @@ public slots: // data propagation void onDataUpdated(PortIndex index); + /// Fetches data from model's OUT #index port + /// and propagates it to the connection + void + onDataUpdatedConnection(PortIndex index, Connection* connection); + private: // addressing @@ -111,6 +140,7 @@ public slots: // data propagation // painting + NodeGeometry _nodeGeometry; std::unique_ptr _nodeGraphicsObject; diff --git a/include/nodes/internal/NodeDataModel.hpp b/include/nodes/internal/NodeDataModel.hpp index 51714ad16..16fc55ac9 100644 --- a/include/nodes/internal/NodeDataModel.hpp +++ b/include/nodes/internal/NodeDataModel.hpp @@ -60,6 +60,8 @@ class NODE_EDITOR_PUBLIC NodeDataModel /// Function creates instances of a model stored in DataModelRegistry virtual std::unique_ptr clone() const = 0; + + void setToolTipText(QString toolTipText); public: @@ -113,7 +115,10 @@ class NODE_EDITOR_PUBLIC NodeDataModel virtual bool - resizable() const { return false; } + resizable() const { + + return true; + } virtual NodeValidationState @@ -126,6 +131,8 @@ class NODE_EDITOR_PUBLIC NodeDataModel virtual NodePainterDelegate* painterDelegate() const { return nullptr; } + QString toolTipText(); + signals: void @@ -139,9 +146,14 @@ class NODE_EDITOR_PUBLIC NodeDataModel void computingFinished(); + + void setToolTipTextSignal(QString text); + private: + QString _toolTipText; + NodeStyle _nodeStyle; }; } diff --git a/include/nodes/internal/NodeGeometry.hpp b/include/nodes/internal/NodeGeometry.hpp index 062459020..5ee5f8c16 100644 --- a/include/nodes/internal/NodeGeometry.hpp +++ b/include/nodes/internal/NodeGeometry.hpp @@ -73,6 +73,7 @@ class NODE_EDITOR_PUBLIC NodeGeometry setDraggingPosition(QPointF const& pos) { _draggingPos = pos; } + void recalculateInOut(); public: QRectF diff --git a/include/nodes/internal/NodeGraphicsObject.hpp b/include/nodes/internal/NodeGraphicsObject.hpp index fe87c6c80..77d7ab8a0 100644 --- a/include/nodes/internal/NodeGraphicsObject.hpp +++ b/include/nodes/internal/NodeGraphicsObject.hpp @@ -35,6 +35,9 @@ class NodeGraphicsObject : public QGraphicsObject Node const& node() const; + FlowScene& + flowScene(); + QRectF boundingRect() const override; @@ -94,7 +97,7 @@ class NodeGraphicsObject : public QGraphicsObject private: FlowScene & _scene; - + QPointF oldPosition; Node& _node; bool _locked; diff --git a/include/nodes/internal/NodeState.hpp b/include/nodes/internal/NodeState.hpp index fc1b175f1..f3f6052f5 100644 --- a/include/nodes/internal/NodeState.hpp +++ b/include/nodes/internal/NodeState.hpp @@ -45,6 +45,9 @@ class NodeState ConnectionPtrSet connections(PortType portType, PortIndex portIndex) const; + + std::vector + allConnections() const; void setConnection(PortType portType, @@ -56,6 +59,11 @@ class NodeState PortIndex portIndex, QUuid id); + + + void + eraseInputAtIndex(PortIndex portIndex); + ReactToConnectionState reaction() const; @@ -81,6 +89,9 @@ class NodeState bool resizing() const; + void + updateEntries(); + private: std::vector _inConnections; @@ -90,6 +101,9 @@ class NodeState PortType _reactingPortType; NodeDataType _reactingDataType; + + std::unique_ptr const &_model; + bool _resizing; }; } diff --git a/include/nodes/internal/NodeStyle.hpp b/include/nodes/internal/NodeStyle.hpp index 5f0518880..43f5d6b5d 100644 --- a/include/nodes/internal/NodeStyle.hpp +++ b/include/nodes/internal/NodeStyle.hpp @@ -39,6 +39,7 @@ class NODE_EDITOR_PUBLIC NodeStyle : public Style QColor ShadowColor; QColor FontColor; QColor FontColorFaded; + QColor FontColorSelected; QColor ConnectionPointColor; QColor FilledConnectionPointColor; diff --git a/resources/DefaultStyle.json b/resources/DefaultStyle.json index 8375b4a15..90ba80d0b 100644 --- a/resources/DefaultStyle.json +++ b/resources/DefaultStyle.json @@ -14,6 +14,7 @@ "ShadowColor": [20, 20, 20], "FontColor" : "white", "FontColorFaded" : "gray", + "FontColorSelected" : "yellow", "ConnectionPointColor": [169, 169, 169], "FilledConnectionPointColor": "cyan", "ErrorColor": "red", diff --git a/src/Connection.cpp b/src/Connection.cpp index 194b6cac2..38a460590 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -14,6 +14,8 @@ #include "NodeGraphicsObject.hpp" #include "NodeDataModel.hpp" +#include "Group.hpp" + #include "ConnectionState.hpp" #include "ConnectionGeometry.hpp" #include "ConnectionGraphicsObject.hpp" @@ -27,6 +29,7 @@ using QtNodes::NodeData; using QtNodes::NodeDataType; using QtNodes::ConnectionGraphicsObject; using QtNodes::ConnectionGeometry; +using QtNodes::Group; Connection:: Connection(PortType portType, @@ -47,8 +50,9 @@ Connection:: Connection(Node& nodeIn, PortIndex portIndexIn, Node& nodeOut, - PortIndex portIndexOut) - : _id(QUuid::createUuid()) + PortIndex portIndexOut, + QUuid *id) + : _id((id ==nullptr) ? QUuid::createUuid() : *id) , _outNode(&nodeOut) , _inNode(&nodeIn) , _outPortIndex(portIndexOut) @@ -60,6 +64,7 @@ Connection(Node& nodeIn, } + Connection:: ~Connection() { @@ -74,6 +79,7 @@ Connection:: { _outNode->nodeGraphicsObject().update(); } + _connectionState.setLastHoveredNode(nullptr); } @@ -96,6 +102,22 @@ save() const } +QJsonObject Connection::copyWithNewID(QUuid in, QUuid out) { + QJsonObject connectionJson; + + if (_inNode && _outNode) + { + connectionJson["in_id"] = in.toString(); + connectionJson["in_index"] = _inPortIndex; + + connectionJson["out_id"] = out.toString(); + connectionJson["out_index"] = _outPortIndex; + } + + return connectionJson; +} + + QUuid Connection:: id() const @@ -196,6 +218,30 @@ getPortIndex(PortType portType) const return result; } +PortIndex +Connection:: +getGroupPortIndex(PortType portType) const +{ + PortIndex result = INVALID; + + switch (portType) + { + case PortType::In: + result = _inGroupPortIndex; + break; + + case PortType::Out: + result = _outGroupPortIndex; + + break; + + default: + break; + } + + return result; +} + void Connection:: @@ -218,6 +264,24 @@ setNodeToPort(Node& node, } + + +void +Connection:: +setGroup(Group* group, PortType portType, PortIndex portIndex) +{ + if(portType==PortType::In) { + _inGroup = group; + _inGroupPortIndex = portIndex; + } + else + { + _outGroup = group; + _outGroupPortIndex = portIndex; + } +} + + void Connection:: removeFromNodes() const @@ -291,6 +355,27 @@ getNode(PortType portType) const return nullptr; } +Group* +Connection:: +getGroup(PortType portType) const +{ + switch (portType) + { + case PortType::In: + return _inGroup; + break; + + case PortType::Out: + return _outGroup; + break; + + default: + // not possible + break; + } + return nullptr; +} + Node*& Connection:: diff --git a/src/ConnectionGraphicsObject.cpp b/src/ConnectionGraphicsObject.cpp index a0b86358c..a07d7de28 100644 --- a/src/ConnectionGraphicsObject.cpp +++ b/src/ConnectionGraphicsObject.cpp @@ -15,12 +15,15 @@ #include "ConnectionBlurEffect.hpp" #include "NodeGraphicsObject.hpp" +#include "GroupGraphicsObject.hpp" #include "NodeConnectionInteraction.hpp" #include "Node.hpp" +#include "Group.hpp" using QtNodes::ConnectionGraphicsObject; +using QtNodes::GroupGraphicsObject; using QtNodes::Connection; using QtNodes::FlowScene; @@ -129,8 +132,49 @@ move() } }; - moveEndPoint(PortType::In); - moveEndPoint(PortType::Out); + auto MoveEndPointGroup = + [this] (PortType portType) + { + if (auto group = _connection.getGroup(portType)) + { + GroupGraphicsObject const &groupGraphics = group->groupGraphicsObject(); + + QPointF scenePos = groupGraphics.portScenePosition(_connection.getGroupPortIndex(portType), + portType); + + { + //localToScene transform + QTransform sceneTransform = this->sceneTransform(); + + //Map from scene position to local position + QPointF connectionPos = sceneTransform.inverted().map(scenePos); + _connection.connectionGeometry().setEndPoint(portType, + connectionPos); + + _connection.getConnectionGraphicsObject().setGeometryChanged(); + _connection.getConnectionGraphicsObject().update(); + } + } + }; + + if(_connection.getGroup(PortType::In)!=nullptr) + { + MoveEndPointGroup(PortType::In); + } + else + { + moveEndPoint(PortType::In); + } + + if(_connection.getGroup(PortType::Out)!=nullptr) + { + MoveEndPointGroup(PortType::Out); + } + else + { + moveEndPoint(PortType::Out); + } + } void ConnectionGraphicsObject::lock(bool locked) @@ -218,11 +262,39 @@ mouseReleaseEvent(QGraphicsSceneMouseEvent* event) if (node && interaction.tryConnect()) { node->resetReactionToConnection(); + return; } - else if (_connection.connectionState().requiresPort()) + + // Get node in the group it's connected to + auto group = locateGroupAt(event->scenePos(), _scene, _scene.views()[0]->transform()); + if(group != nullptr) { + int hitPoint = group->groupGraphicsObject().checkHitScenePoint(PortType::In, + event->scenePos(), + this->sceneTransform()); + + if(hitPoint>=0) + { + Node *outNode = group->groupGraphicsObject().inOutNodes[(int)PortType::In][hitPoint]; + int outPort = group->groupGraphicsObject().inOutPorts[(int)PortType::In][hitPoint]; + Node *inNode = _connection.getNode(PortType::Out); + int inPort = _connection.getPortIndex(PortType::Out); + + _scene.createConnection(*outNode, outPort, *inNode, inPort); + + //Expands + group->groupGraphicsObject().Collapse(); + + //Collapses back + group->groupGraphicsObject().Collapse(); + _scene.deleteConnection(_connection); + return; + } + } - _scene.deleteConnection(_connection); + if (_connection.connectionState().requiresPort()) + { + _scene.deleteConnection(_connection); } } diff --git a/src/DataModelRegistry.cpp b/src/DataModelRegistry.cpp index 8a1426ac9..028cfed76 100644 --- a/src/DataModelRegistry.cpp +++ b/src/DataModelRegistry.cpp @@ -29,6 +29,14 @@ registeredModels() const } +DataModelRegistry::RegisteredTemplatesMap & +DataModelRegistry::RegisteredTemplates() +{ + return _registeredTemplates; +} + + + DataModelRegistry::RegisteredModelsCategoryMap const & DataModelRegistry:: registeredModelsCategoryAssociation() const diff --git a/src/FlowScene.cpp b/src/FlowScene.cpp index a8d854c6d..e62ded465 100644 --- a/src/FlowScene.cpp +++ b/src/FlowScene.cpp @@ -17,6 +17,7 @@ #include #include "Node.hpp" +#include "Group.hpp" #include "NodeGraphicsObject.hpp" #include "NodeGraphicsObject.hpp" @@ -29,6 +30,7 @@ using QtNodes::FlowScene; using QtNodes::Node; +using QtNodes::Group; using QtNodes::NodeGraphicsObject; using QtNodes::Connection; using QtNodes::DataModelRegistry; @@ -37,11 +39,63 @@ using QtNodes::NodeDataModel; using QtNodes::PortType; using QtNodes::PortIndex; + + FlowScene:: FlowScene(std::shared_ptr registry) : _registry(registry) { setItemIndexMethod(QGraphicsScene::NoIndex); + + ResetHistory(); + + auto UpdateLamda = [this](Node& n, const QPointF& newPosition, const QPointF &oldPosition) + { + resolveGroups(n); + QUuid id = n.id(); + AddAction( + UndoRedoAction( + [this, id, oldPosition](void *) + { + UniqueNode n = _nodes[id]; + n->nodeGraphicsObject().setPos(oldPosition); + return 0; + }, + [this, id, newPosition](void *) + { + + UniqueNode n = _nodes[id]; + n->nodeGraphicsObject().setPos(newPosition); + return 0; + }, + "Moved Node " + n.nodeDataModel()->name().toStdString() + ) + ); + }; + connect(this, &FlowScene::nodeMoveFinished, this, UpdateLamda); + + auto GroupUpdateLamda = [this](Group& g, const QPointF& newPosition, const QPointF& oldPosition) + { + resolveGroups(g); + + + AddAction(UndoRedoAction( + [&g, oldPosition](void *ptr) + { + g.groupGraphicsObject().setPos(oldPosition); + return 0; + }, + [&g, newPosition](void *ptr) + { + g.groupGraphicsObject().setPos(newPosition); + return 0; + }, + "Moved Group " + g.GetName().toStdString() + )); + }; + connect(this, &FlowScene::groupMoveFinished, this, GroupUpdateLamda); + + anchors.resize(10); } @@ -79,14 +133,16 @@ FlowScene:: createConnection(Node& nodeIn, PortIndex portIndexIn, Node& nodeOut, - PortIndex portIndexOut) + PortIndex portIndexOut, + QUuid *id) { auto connection = std::make_shared(nodeIn, portIndexIn, nodeOut, - portIndexOut); + portIndexOut, + id); auto cgo = std::make_unique(*this, *connection); @@ -102,7 +158,7 @@ createConnection(Node& nodeIn, _connections[connection->id()] = connection; connectionCreated(*connection); - + return connection; } @@ -120,9 +176,34 @@ restoreConnection(QJsonObject const &connectionJson) auto nodeIn = _nodes[nodeInId].get(); auto nodeOut = _nodes[nodeOutId].get(); + std::vector connIn = nodeIn->nodeState().getEntries(PortType::In); + std::vector connOut = nodeOut->nodeState().getEntries(PortType::Out); + int numConnectionsIn = connIn.size(); + int numConnectionsOut = connOut.size(); + + portIndexIn = std::min(numConnectionsIn - 1, portIndexIn); + portIndexOut = std::min(numConnectionsOut - 1, portIndexOut); + + nodeIn->currentInputConnections++; + return createConnection(*nodeIn, portIndexIn, *nodeOut, portIndexOut); } +void FlowScene::pasteConnection(QJsonObject const &connectionJson, QUuid newIn, QUuid newOut) +{ + QUuid nodeInId = QUuid(connectionJson["in_id"].toString()); + QUuid nodeOutId = QUuid(connectionJson["out_id"].toString()); + + PortIndex portIndexIn = connectionJson["in_index"].toInt(); + PortIndex portIndexOut = connectionJson["out_index"].toInt(); + + auto nodeIn = _nodes[newIn].get(); + auto nodeOut = _nodes[newOut].get(); + + createConnection(*nodeIn, portIndexIn, *nodeOut, portIndexOut); + +} + void FlowScene:: @@ -133,6 +214,24 @@ deleteConnection(Connection& connection) _connections.erase(connection.id()); } +void +FlowScene:: +deleteConnection(Connection* connection) +{ + // connectionDeleted(connection); + connection->removeFromNodes(); + _connections.erase(connection->id()); +} + + +void +FlowScene:: +deleteConnectionWithID(QUuid id) +{ + Connection *connection = _connections[id].get(); + deleteConnection(connection); +} + Node& FlowScene:: @@ -147,16 +246,130 @@ createNode(std::unique_ptr && dataModel) _nodes[node->id()] = std::move(node); nodeCreated(*nodePtr); + return *nodePtr; } Node& FlowScene:: -restoreNode(QJsonObject const& nodeJson) +createNodeWithID(std::unique_ptr && dataModel, QUuid id) +{ + auto node = std::make_unique(std::move(dataModel)); + node->setId(id); + + auto ngo = std::make_unique(*this, *node); + + node->setGraphicsObject(std::move(ngo)); + + auto nodePtr = node.get(); + _nodes[node->id()] = std::move(node); + + nodeCreated(*nodePtr); + + return *nodePtr; +} + + +Group& FlowScene::createGroup() { + auto group = std::make_shared(*this); + auto ggo = std::make_shared(*this, *group); + + QUuid id = group->id(); + auto groupPtr = group.get(); + group->setGraphicsObject(ggo); + _groups[id] = group; + + return *groupPtr; +} + + + + +Group& +FlowScene:: +restoreGroup(QJsonObject const& nodeJson) { + auto group = std::make_shared(*this); + auto ggo = std::make_shared(*this, *group); + + QUuid id = group->id(); + auto groupPtr = group.get(); + + group->setGraphicsObject(ggo); + _groups[id] = group; + + + group->restore(nodeJson); + + + return *groupPtr; +} + +Group& +FlowScene:: +pasteGroup(QJsonObject const& groupJson, QPointF nodeGroupCentroid, QPointF mousePos) { + auto group = std::make_shared(*this); + auto ggo = std::make_shared(*this, *group); + + QUuid id = group->id(); + auto groupPtr = group.get(); + + group->setGraphicsObject(ggo); + _groups[id] = group; + + QJsonObject positionJson = groupJson["position"].toObject(); + QPointF point(positionJson["x"].toDouble(), + positionJson["y"].toDouble()); + QPointF pos = mousePos + (point - nodeGroupCentroid); + group->restoreAtPosition(groupJson, pos); + + + + // group->groupGraphicsObject().setPos(pos); + + // Group &groupRef = *(group); + // resolveGroups(groupRef); + + // bool collapsed = (bool)groupJson["collapsed"].toInt(); + // if(collapsed) + // { + // group->groupGraphicsObject().Collapse(); + // } + + return *groupPtr; +} + +Node& +FlowScene:: +restoreNode(QJsonObject const& nodeJson, bool keepId) { QString modelName = nodeJson["model"].toObject()["name"].toString(); + auto dataModel = registry().create(modelName); //This is where it looks for the node by name + + if (!dataModel) + { + dataModel = registry().create("DeletedNode"); + } + + auto node = std::make_shared(std::move(dataModel)); + if(keepId) node->setId(QUuid( nodeJson["id"].toString() )); + auto ngo = std::make_unique(*this, *node); + node->setGraphicsObject(std::move(ngo)); + + auto nodePtr = node.get(); + nodeCreated(*nodePtr); + + node->restore(nodeJson); + _nodes[node->id()] = std::move(node); + + resolveGroups(*nodePtr); + + return *nodePtr; +} + +QUuid FlowScene::pasteNode(QJsonObject &nodeJson, QPointF nodeGroupCentroid, QPointF mousePos) { + QString modelName = nodeJson["model"].toObject()["name"].toString(); auto dataModel = registry().create(modelName); if (!dataModel) @@ -165,18 +378,24 @@ restoreNode(QJsonObject const& nodeJson) auto node = std::make_unique(std::move(dataModel)); auto ngo = std::make_unique(*this, *node); + node->setGraphicsObject(std::move(ngo)); + - node->restore(nodeJson); + QUuid newId = QUuid::createUuid(); + node->paste(nodeJson, newId); + + QPointF pos = mousePos + (node->nodeGraphicsObject().pos() - nodeGroupCentroid); + node->nodeGraphicsObject().setPos(pos); auto nodePtr = node.get(); _nodes[node->id()] = std::move(node); - nodeCreated(*nodePtr); - return *nodePtr; + return newId; } + void FlowScene:: removeNode(Node& node) @@ -200,10 +419,64 @@ removeNode(Node& node) deleteConnections(PortType::In); deleteConnections(PortType::Out); + + _nodes.erase(node.id()); +} + + + +void +FlowScene:: +removeNodeWithID(QUuid id) +{ + UniqueNode nodePtr = _nodes[id]; + Node &node = *nodePtr; + // call signal + nodeDeleted(node); + + auto deleteConnections = + [&node, this] (PortType portType) + { + auto nodeState = node.nodeState(); + auto const & nodeEntries = nodeState.getEntries(portType); + + for (auto &connections : nodeEntries) + { + for (auto const &pair : connections) + deleteConnection(*pair.second); + } + }; + + deleteConnections(PortType::In); + deleteConnections(PortType::Out); + + _nodes.erase(node.id()); } +void +FlowScene:: +removeGroup(Group& group) +{ + + GroupGraphicsObject &ggo = group.groupGraphicsObject(); + for(int i=ggo.childItems().size()-1; i>=0; i--) + { + QGraphicsItem *child = ggo.childItems()[i]; + NodeGraphicsObject* n = qgraphicsitem_cast(child); + if(n != nullptr) + { + QPointF position = n->scenePos(); + ggo.childItems()[i]->setParentItem(0); + n->setPos(position); + n->moveConnections(); + } + } + _groups.erase(group.id()); +} + + DataModelRegistry& FlowScene:: registry() const @@ -341,14 +614,108 @@ getNodeSize(const Node& node) const return QSizeF(node.nodeGeometry().width(), node.nodeGeometry().height()); } +void +FlowScene:: +resolveGroups(Group& group) { + if(group.groupGraphicsObject().isCollapsed()) return; + + GroupGraphicsObject& ggo = group.groupGraphicsObject(); + QRectF groupRect = ggo.mapRectToScene(ggo.boundingRect()); + + //Check if the nodes that were inside the group still are (When resizing) + for(int i=ggo.childItems().size()-1; i>=0; i--) { + QGraphicsObject* node = (QGraphicsObject*) ggo.childItems()[i]; + QRectF nodeRect = node->mapRectToScene(node->boundingRect()); + if(!groupRect.contains(nodeRect)) { + QPointF scenePos = node->scenePos(); + node->setParentItem(nullptr); + ggo.childItems().removeAt(i); + node->setPos(scenePos); + } + } + + //Check all the the other things that collide the group at its new location + QListothers = collidingItems(&ggo, Qt::IntersectsItemBoundingRect); + for(int i=0; i(other); + GroupGraphicsObject* ggo1 = dynamic_cast(other); + if(ngo || ggo1) { + QRectF otherRect = other->mapRectToScene(other->boundingRect()); + + //checks what is inside + if(groupRect.contains(otherRect)) { + QPointF scenePos = other->scenePos(); + QPointF parentPos = ggo.mapFromScene(scenePos); + if(!other->isAncestorOf(&ggo)) { + other->setParentItem(&ggo); + other->setPos(parentPos); + } + } else if(otherRect.contains(groupRect)) { // Checks inside of what it is + QPointF scenePos = ggo.scenePos(); + QPointF parentPos = other->mapFromScene(scenePos); + if(!ggo.isAncestorOf(other)) { + ggo.setParentItem(other); + ggo.setPos(parentPos); + } + } + } else { + } + + + } + ggo.moveConnections(); +} + +void +FlowScene:: +resolveGroups(Node& n) { + NodeGraphicsObject& c = n.nodeGraphicsObject(); + bool hasIntersect = false; + + //Check if the final position is inside a group + for (auto& group : _groups) + { + auto groupPtr = group.second.get(); + GroupGraphicsObject* ggo = (GroupGraphicsObject*) groupPtr->_groupGraphicsObject.get(); + + QRectF groupRect = ggo->mapRectToScene(ggo->boundingRect()); + QRectF nodeRect = c.mapRectToScene(c.boundingRect()); + + if(groupRect.contains(nodeRect)) { + hasIntersect = true; + if(c.parentItem() != ggo) { + QPointF scenePos = c.scenePos(); + QPointF parentPos = ggo->mapFromScene(scenePos); + c.setParentItem(ggo); + c.setPos(parentPos); + } + } + } + + //Check if it went out of a group + if(!hasIntersect && c.parentItem() != nullptr ) { + QPointF newPos = c.parentItem()->mapToScene(c.pos()); + c.setParentItem(nullptr); + c.setPos(newPos); + } +} -std::unordered_map > const & + +std::unordered_map > const & FlowScene:: nodes() const { return _nodes; } +std::unordered_map > & +FlowScene:: +nodes() +{ + return _nodes; +} + std::unordered_map > const & FlowScene:: @@ -358,14 +725,15 @@ connections() const } -std::vector +std::vector> FlowScene:: selectedNodes() const { QList graphicsItems = selectedItems(); - std::vector ret; + std::vector> ret; ret.reserve(graphicsItems.size()); + for (QGraphicsItem* item : graphicsItems) { @@ -373,7 +741,9 @@ selectedNodes() const if (ngo != nullptr) { - ret.push_back(&ngo->node()); + Node* n = &(ngo->node()); + std::shared_ptr ptr(n); + ret.push_back(ptr); } } @@ -400,6 +770,12 @@ clearScene() { removeNode(*node); } + + _groups.clear(); + // for (auto& group : _groups) + // { + // _groups.erase(group.first); + // } } @@ -461,15 +837,21 @@ saveToMemory() const { QJsonObject sceneJson; - QJsonArray nodesJsonArray; + QJsonArray groupsJsonArray; + for (auto const & pair : _groups) + { + auto const &group = pair.second; + groupsJsonArray.append(group->save()); + } + sceneJson["groups"] = groupsJsonArray; + QJsonArray nodesJsonArray; for (auto const & pair : _nodes) { auto const &node = pair.second; nodesJsonArray.append(node->save()); } - sceneJson["nodes"] = nodesJsonArray; QJsonArray connectionJsonArray; @@ -485,12 +867,28 @@ saveToMemory() const sceneJson["connections"] = connectionJsonArray; + QJsonArray anchorsJsonArray; + for(int i=0; itargetInputConnections++; + } + + std::stable_sort( connectionJsonArray.begin( ), connectionJsonArray.end( ), [this]( const auto& lhs, const auto& rhs ) + { + QPointF posA, posB; + QString nameA, nameB; + { + QJsonObject objA = lhs.toObject(); + QUuid nodeInId = QUuid(objA["in_id"].toString()); + PortIndex portIndexIn = objA["in_index"].toInt(); + auto nodeIn = _nodes[nodeInId].get(); + NodeGraphicsObject & ngo = nodeIn->nodeGraphicsObject(); + posA = ngo.scenePos(); + + NodeDataModel *dm = nodeIn->nodeDataModel(); + nameA = dm->name(); + } + + { + QJsonObject objB = rhs.toObject(); + QUuid nodeInId = QUuid(objB["in_id"].toString()); + PortIndex portIndexIn = objB["in_index"].toInt(); + auto nodeIn = _nodes[nodeInId].get(); + NodeGraphicsObject & ngo = nodeIn->nodeGraphicsObject(); + posB = ngo.scenePos(); + + NodeDataModel *dm = nodeIn->nodeDataModel(); + nameB = dm->name(); + } + + //We put input variables at the end, so we only compute them when all the data is loaded. + if (QString::compare(nameA, QString("InputVariable")) == 0 && QString::compare(nameB, QString("InputVariable")) != 0) { + return false; + } + else if (QString::compare(nameB, QString("InputVariable")) == 0 && QString::compare(nameA, QString("InputVariable")) != 0) { + return true; + } + else { + return posA.x() < posB.x(); + } + + }); + + for (int i = 0; i < connectionJsonArray.size(); ++i) { restoreConnection(connectionJsonArray[i].toObject()); } + + QJsonArray groupsJsonArray = jsonDocument["groups"].toArray(); + for (int i = 0; i < groupsJsonArray.size(); ++i) + { + restoreGroup(groupsJsonArray[i].toObject()); + } + + + if(jsonDocument.contains("anchors")) { + QJsonArray anchorsJsonArray = jsonDocument["anchors"].toArray(); + for(int i=0; i0) + { + writeToHistory = false; + UndoRedoAction action = undoActions[undoActions.size()-1]; + undoActions.pop_back(); + action.undoAction(0); + + redoActions.push_back(action); + historyInx--; + writeToHistory = true; + } +} + +void FlowScene::Redo() +{ + PrintActions(); + if(redoActions.size()>0) + { + writeToHistory = false; + UndoRedoAction action = redoActions[redoActions.size()-1]; + redoActions.pop_back(); + action.redoAction(0); + + undoActions.push_back(action); + writeToHistory = true; + historyInx++; + } +} + + + +void FlowScene::ResetHistory() +{ + historyInx=0; + undoActions.clear(); + redoActions.clear(); +} + + +int FlowScene::GetHistoryIndex() { + return historyInx; } @@ -551,4 +1091,39 @@ locateNodeAt(QPointF scenePoint, FlowScene &scene, return resultNode; } + +Group* +locateGroupAt(QPointF scenePoint, FlowScene &scene, + QTransform viewTransform) +{ + // items under cursor + QList items = + scene.items(scenePoint, + Qt::IntersectsItemShape, + Qt::DescendingOrder, + viewTransform); + + //// items convertable to GroupGraphicsObject + std::vector filteredItems; + + std::copy_if(items.begin(), + items.end(), + std::back_inserter(filteredItems), + [] (QGraphicsItem * item) + { + return (dynamic_cast(item) != nullptr); + }); + + Group* resultGroup = nullptr; + + if (!filteredItems.empty()) + { + QGraphicsItem* graphicsItem = filteredItems.front(); + auto ggo = dynamic_cast(graphicsItem); + + resultGroup = &ggo->group(); + } + + return resultGroup; +} } diff --git a/src/FlowView.cpp b/src/FlowView.cpp index 4e5e1e9b4..c54c92afa 100644 --- a/src/FlowView.cpp +++ b/src/FlowView.cpp @@ -1,7 +1,7 @@ #include "FlowView.hpp" #include - +#include #include #include #include @@ -19,12 +19,19 @@ #include "FlowScene.hpp" #include "DataModelRegistry.hpp" #include "Node.hpp" +#include "Group.hpp" #include "NodeGraphicsObject.hpp" #include "ConnectionGraphicsObject.hpp" #include "StyleCollection.hpp" +#include "Connection.hpp" +#include "NodeConnectionInteraction.hpp" + using QtNodes::FlowView; using QtNodes::FlowScene; +using QtNodes::Connection; +using QtNodes::NodeConnectionInteraction; +using QtNodes::NodeGraphicsObject; FlowView:: FlowView(QWidget *parent) @@ -48,8 +55,12 @@ FlowView(QWidget *parent) setTransformationAnchor(QGraphicsView::AnchorUnderMouse); setCacheMode(QGraphicsView::CacheBackground); - - //setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); + + setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); + + setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + + } @@ -76,6 +87,52 @@ deleteSelectionAction() const return _deleteSelectionAction; } +void +FlowView::addAnchor(int index) +{ + qreal x1, y1, x2, y2; + sceneRect().getCoords(&x1, &y1, &x2, &y2); + + Anchor a; + a.position = QPointF((x2 + x1) * 0.5, (y1 + y2) * 0.5); + a.scale = 10; + + _scene->anchors[index] = a; +} + +void +FlowView::goToAnchor(int index) +{ + qreal x1, y1, x2, y2; + sceneRect().getCoords(&x1, &y1, &x2, &y2); + QPointF currentPosition = QPointF((x2 + x1) * 0.5, (y1 + y2) * 0.5); + + QPointF difference = _scene->anchors[index].position - currentPosition; + + setSceneRect(sceneRect().translated(difference.x(), difference.y())); +} + +void FlowView::goToNode(NodeGraphicsObject *node) +{ + qreal x1, y1, x2, y2; + sceneRect().getCoords(&x1, &y1, &x2, &y2); + QPointF currentPosition = QPointF((x2 + x1) * 0.5, (y1 + y2) * 0.5); + + QPointF difference = node->pos() - currentPosition; + + setSceneRect(sceneRect().translated(difference.x(), difference.y())); + + + float scaleX = 1.2f / transform().m11(); + float scaleY = 1.2f / transform().m22(); + scale(scaleX, scaleY); +} + +void +FlowView::goToNodeID(QUuid ID) +{ + goToNode(&(_scene->nodes()[ID]->nodeGraphicsObject())); +} void FlowView::setScene(FlowScene *scene) @@ -83,9 +140,10 @@ FlowView::setScene(FlowScene *scene) _scene = scene; QGraphicsView::setScene(_scene); + // setup actions delete _clearSelectionAction; - _clearSelectionAction = new QAction(QStringLiteral("Clear Selection"), this); + _clearSelectionAction = new QAction(QStringLiteral("Clear Selection"), this); _clearSelectionAction->setShortcut(Qt::Key_Escape); connect(_clearSelectionAction, &QAction::triggered, _scene, &QGraphicsScene::clearSelection); addAction(_clearSelectionAction); @@ -93,12 +151,63 @@ FlowView::setScene(FlowScene *scene) delete _deleteSelectionAction; _deleteSelectionAction = new QAction(QStringLiteral("Delete Selection"), this); _deleteSelectionAction->setShortcut(Qt::Key_Delete); + _deleteSelectionAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); connect(_deleteSelectionAction, &QAction::triggered, this, &FlowView::deleteSelectedNodes); addAction(_deleteSelectionAction); + + _duplicateSelectionAction = new QAction(QStringLiteral("Duplicate Selection"), this); + _duplicateSelectionAction->setShortcut(QKeySequence(tr("Ctrl+D"))); + _duplicateSelectionAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); + connect(_duplicateSelectionAction, &QAction::triggered, this, &FlowView::duplicateSelectedNode); + addAction(_duplicateSelectionAction); + + _copymultiplenodes = new QAction(QStringLiteral("Copy Multiple Nodes"), this); + _copymultiplenodes->setShortcut(QKeySequence(tr("Ctrl+C"))); + _copymultiplenodes->setShortcutContext(Qt::WidgetWithChildrenShortcut); + connect(_copymultiplenodes, &QAction::triggered, this, &FlowView::copySelectedNodes); + addAction(_copymultiplenodes); + + _pastemultiplenodes = new QAction(QStringLiteral("Paste Multiple Nodes"), this); + _pastemultiplenodes->setShortcut(QKeySequence(tr("Ctrl+V"))); + _pastemultiplenodes->setShortcutContext(Qt::WidgetWithChildrenShortcut); + connect(_pastemultiplenodes, &QAction::triggered, this, &FlowView::pasteSelectedNodes); + addAction(_pastemultiplenodes); + + _undoAction = new QAction(QStringLiteral("Undo"), this); + _undoAction->setShortcut(QKeySequence(tr("Ctrl+Z"))); + _undoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); + connect(_undoAction, &QAction::triggered, _scene, &FlowScene::Undo); + addAction(_undoAction); + + _redoAction = new QAction(QStringLiteral("Redo"), this); + _redoAction->setShortcut(QKeySequence(tr("Ctrl+Y"))); + _redoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); + connect(_redoAction, &QAction::triggered, _scene, &FlowScene::Redo); + addAction(_redoAction); + + for(int i=0; i<10; i++) { + QAction* _addAnchor = new QAction(QStringLiteral("Add Anchor"), this); + QString sequenceString = QString("Ctrl+") + QString::number(i); + _addAnchor->setShortcut(QKeySequence(sequenceString)); + _addAnchor->setShortcutContext(Qt::WidgetWithChildrenShortcut ); + connect(_addAnchor, &QAction::triggered, _scene, [this, i]() { + addAnchor(i); + }); + addAction(_addAnchor); + anchorActions.push_back(_addAnchor); + + QAction* _goToAnchor = new QAction(QStringLiteral("Go to Anchor"), this); + _goToAnchor->setShortcut(QKeySequence(tr(std::to_string(i).c_str()))); + _goToAnchor->setShortcutContext(Qt::WidgetWithChildrenShortcut); + connect(_goToAnchor, &QAction::triggered, _scene, [this, i]() { + goToAnchor(i); + }); + addAction(_goToAnchor); + anchorActions.push_back(_goToAnchor); + } } - -void +void FlowView:: contextMenuEvent(QContextMenuEvent *event) { @@ -107,7 +216,7 @@ contextMenuEvent(QContextMenuEvent *event) QGraphicsView::contextMenuEvent(event); return; } - + QMenu modelMenu; auto skipText = QStringLiteral("skip me"); @@ -141,6 +250,12 @@ contextMenuEvent(QContextMenuEvent *event) topLevelItems[cat] = item; } + //Add templates category + auto templatesCategory = new QTreeWidgetItem(treeView); + templatesCategory->setText(0, "Templates"); + templatesCategory->setData(0, Qt::UserRole, "Templates"); + topLevelItems["Templates"] = templatesCategory; + for (auto const &assoc : _scene->registry().registeredModelsCategoryAssociation()) { auto parent = topLevelItems[assoc.second]; @@ -148,10 +263,31 @@ contextMenuEvent(QContextMenuEvent *event) item->setText(0, assoc.first); item->setData(0, Qt::UserRole, assoc.first); } + + for (auto const &assoc : _scene->registry().RegisteredTemplates()) + { + QString name = assoc.first; + auto item = new QTreeWidgetItem(templatesCategory); + item->setText(0, name); + item->setData(0, Qt::UserRole, name); + } + + //Add templates category + // auto GroupsCategory = new QTreeWidgetItem(treeView); + // GroupsCategory->setText(0, "Groups"); + // GroupsCategory->setData(0, Qt::UserRole, "Groups"); + // topLevelItems["Groups"] = GroupsCategory; + + auto groupItem = new QTreeWidgetItem(treeView); + groupItem->setText(0, "Group"); + groupItem->setData(0, Qt::UserRole, "Group"); + topLevelItems["Group"] = groupItem; + treeView->expandAll(); - connect(treeView, &QTreeWidget::itemClicked, [&](QTreeWidgetItem *item, int) + + connect(treeView, &QTreeWidget::itemActivated, [&](QTreeWidgetItem *item, int) { QString modelName = item->data(0, Qt::UserRole).toString(); @@ -160,17 +296,64 @@ contextMenuEvent(QContextMenuEvent *event) return; } + if(modelName == "Group") + { + Group& group = _scene->createGroup(); + QPoint pos = event->pos(); + QPointF posView = this->mapToScene(pos); + group.groupGraphicsObject().setPos(posView); + modelMenu.close(); + return; + } + + if (item->parent() != nullptr) + { + QString parent = item->parent()->data(0, Qt::UserRole).toString(); + if(parent == "Templates") + { + DataModelRegistry::RegisteredTemplatesMap map = _scene->registry().RegisteredTemplates(); + QString fileName = map[modelName]; + + QFile file; + file.setFileName(fileName); + file.open(QIODevice::ReadOnly | QIODevice::Text); + QString val = file.readAll(); + file.close(); + qWarning() << val; + QJsonDocument d = QJsonDocument::fromJson(val.toUtf8()); + QJsonObject sett2 = d.object(); + jsonToSceneMousePos(sett2); + + modelMenu.close(); + } + } + auto type = _scene->registry().create(modelName); if (type) { - auto& node = _scene->createNode(std::move(type)); - + Node& node = _scene->createNode(std::move(type)); QPoint pos = event->pos(); - QPointF posView = this->mapToScene(pos); - node.nodeGraphicsObject().setPos(posView); + + QUuid id = node.id(); + _scene->AddAction(UndoRedoAction( + [this, id](void *ptr) + { + _scene->removeNodeWithID(id); + return 0; + }, + [this, pos, modelName, id](void *ptr) + { + auto type = _scene->registry().create(modelName); + auto& node = _scene->createNodeWithID(std::move(type), id); + QPointF posView = this->mapToScene(pos); + node.nodeGraphicsObject().setPos(posView); + return 0; + }, + "Created Node " + node.nodeDataModel()->name().toStdString() + )); } else { @@ -185,12 +368,14 @@ contextMenuEvent(QContextMenuEvent *event) { for (auto& topLvlItem : topLevelItems) { + bool shouldHideCategory = true; for (int i = 0; i < topLvlItem->childCount(); ++i) { auto child = topLvlItem->child(i); auto modelName = child->data(0, Qt::UserRole).toString(); if (modelName.contains(text, Qt::CaseInsensitive)) { + shouldHideCategory = false; child->setHidden(false); } else @@ -198,12 +383,18 @@ contextMenuEvent(QContextMenuEvent *event) child->setHidden(true); } } + auto catName = topLvlItem->data(0, Qt::UserRole).toString(); + if(catName.contains(text, Qt::CaseInsensitive)) + { + shouldHideCategory=false; + } + + topLvlItem->setHidden(shouldHideCategory); } }); // make sure the text box gets focus so the user doesn't have to click on it txtBox->setFocus(); - modelMenu.exec(event->globalPos()); } @@ -226,6 +417,7 @@ wheelEvent(QWheelEvent *event) scaleUp(); else scaleDown(); + } @@ -242,6 +434,7 @@ scaleUp() return; scale(factor, factor); + } @@ -260,18 +453,298 @@ void FlowView:: deleteSelectedNodes() { - // delete the nodes, this will delete many of the connections + QJsonObject sceneJson = selectionToJson(true); + + std::vector nodeIds; + std::vector connectionsIds; for (QGraphicsItem * item : _scene->selectedItems()) { - if (auto n = qgraphicsitem_cast(item)) - _scene->removeNode(n->node()); + if(item) + { + if (auto c = qgraphicsitem_cast(item)) + { + _scene->deleteConnection(c->connection()); + } + } } - for (QGraphicsItem * item : _scene->selectedItems()) { - if (auto c = qgraphicsitem_cast(item)) - _scene->deleteConnection(c->connection()); + if(item) + { + if (auto c = qgraphicsitem_cast(item)) + { + _scene->removeGroup(c->group()); + } + else if (auto c = qgraphicsitem_cast(item)) + { + _scene->removeNode(c->node()); + } + } + } + + + _scene->AddAction(UndoRedoAction( + //Undo action + [this, sceneJson](void *ptr) + { + jsonToScene(sceneJson); + return 0; + }, + //Redo action + [this, sceneJson](void *ptr) + { + deleteJsonElements(sceneJson); + return 0; + }, + //Name + "Deletes nodes" + )); +} + +void FlowView::deleteJsonElements(const QJsonObject &jsonDocument) +{ + QJsonArray nodesJsonArray = jsonDocument["nodes"].toArray(); + for (int i = 0; i < nodesJsonArray.size(); ++i) + { + QJsonObject nodeJson = nodesJsonArray[i].toObject(); + QUuid id = QUuid( nodeJson["id"].toString() ); + _scene->removeNodeWithID(id); + } + +} + +void FlowView::copySelectedNodes() { + QJsonObject sceneJson = selectionToJson(); + + QJsonDocument document(sceneJson); + std::string json = document.toJson().toStdString(); + + QClipboard *p_Clipboard = QApplication::clipboard(); + p_Clipboard->setText( QString::fromStdString(json)); +} + +void FlowView::jsonToScene(QJsonObject jsonDocument) +{ + QJsonArray nodesJsonArray = jsonDocument["nodes"].toArray(); + + + + for (int i = 0; i < nodesJsonArray.size(); ++i) + { + _scene->restoreNode(nodesJsonArray[i].toObject(), true); + } + + QJsonArray connectionJsonArray = jsonDocument["connections"].toArray(); + for (int i = 0; i < connectionJsonArray.size(); ++i) + { + QUuid in = QUuid(connectionJsonArray[i].toObject()["in_id"].toString()); + QUuid out = QUuid(connectionJsonArray[i].toObject()["out_id"].toString()); + + _scene->pasteConnection(connectionJsonArray[i].toObject(), in, out ); + } + + QJsonArray groupsJsonArray = jsonDocument["groups"].toArray(); + for (int i = 0; i < groupsJsonArray.size(); ++i) + { + _scene->restoreGroup(groupsJsonArray[i].toObject()); + } +} + + +void FlowView::jsonToSceneMousePos(QJsonObject jsonDocument) +{ + QJsonArray nodesJsonArray = jsonDocument["nodes"].toArray(); + + std::map addedIds; + + //Get Bounds of all the selected items + float minx = 10000000000; + float miny = 10000000000; + float maxx = -1000000000; + float maxy = -1000000000; + for (int i = 0; i < nodesJsonArray.size(); ++i) + { + QJsonObject nodeJsonObject = nodesJsonArray[i].toObject(); + QJsonObject positionJson = nodeJsonObject["position"].toObject(); + QPointF pos(positionJson["x"].toDouble(), positionJson["y"].toDouble()); + + if(pos.x() < minx) minx = pos.x(); + if(pos.y() < miny) miny = pos.y(); + if(pos.x() > maxx) maxx = pos.x(); + if(pos.y() > maxy) maxy = pos.y(); + } + + float centroidX = (maxx - minx) / 2.0 + minx; + float centroidY = (maxy - miny) / 2.0 + miny; + QPointF centroid(centroidX, centroidY); + + QPoint viewPointMouse = this->mapFromGlobal(QCursor::pos()); + QPointF posViewMouse = this->mapToScene(viewPointMouse); + + + + + for (int i = 0; i < nodesJsonArray.size(); ++i) + { + QJsonObject nodeJson = nodesJsonArray[i].toObject(); + QUuid currentId = QUuid( nodeJson["id"].toString() ); + QUuid newId = _scene->pasteNode(nodesJsonArray[i].toObject(), centroid, posViewMouse); + + addedIds.insert(std::pair(currentId, newId)); + nodesJsonArray[i] = _scene->nodes()[newId]->save(); + } + jsonDocument["nodes"] = nodesJsonArray; + + QJsonArray connectionJsonArray = jsonDocument["connections"].toArray(); + for (int i = 0; i < connectionJsonArray.size(); ++i) + { + QJsonObject connectionJson = connectionJsonArray[i].toObject(); + + QUuid in = QUuid(connectionJson["in_id"].toString()); + QUuid newIn = addedIds[in]; + + QUuid out = QUuid(connectionJson["out_id"].toString()); + QUuid newOut = addedIds[out]; + + _scene->pasteConnection(connectionJson, newIn, newOut ); + + connectionJson["in_id"] = newIn.toString(); + connectionJson["out_id"] = newOut.toString(); + connectionJsonArray[i] = connectionJson; + } + jsonDocument["connections"] =connectionJsonArray; + + QJsonArray groupsJsonArray = jsonDocument["groups"].toArray(); + for (int i = 0; i < groupsJsonArray.size(); ++i) + { + _scene->pasteGroup(groupsJsonArray[i].toObject(), centroid, posViewMouse); + } + + + _scene->AddAction(UndoRedoAction( + [this, addedIds](void *ptr) + { + //Delete all the created nodes (and their connections) + for(auto &id: addedIds) + { + _scene->removeNodeWithID(id.second); + } + return 0; + }, + [this, jsonDocument](void *ptr) + { + jsonToScene(jsonDocument); //jsonDocument has now been updated with new IDs and new positions + return 0; + }, + "Created Node " + )); + +} + +QJsonObject FlowView::selectionToJson(bool includePartialConnections) +{ + QJsonObject sceneJson; + QJsonArray nodesJsonArray; + QJsonArray connectionJsonArray; + std::vector addedIds; + + for (QGraphicsItem * item : _scene->selectedItems()) + { + if (auto n = qgraphicsitem_cast(item)) + { + Node& node = n->node(); + nodesJsonArray.append(node.save()); + addedIds.push_back(node.id()); + + if(includePartialConnections) + { + std::vector allConnections = node.nodeState().allConnections(); + for(int i=0; isave(); + if (!connectionJson.isEmpty()) + connectionJsonArray.append(connectionJson); + } + } + } } + + for (QGraphicsItem * item : _scene->selectedItems()) + { + if (auto c = qgraphicsitem_cast(item)) { + Connection& connection = c->connection(); + + if((std::find(addedIds.begin(), addedIds.end(), connection.getNode(PortType::In)->id()) != addedIds.end() && + std::find(addedIds.begin(), addedIds.end(), connection.getNode(PortType::Out)->id()) != addedIds.end()) || includePartialConnections) { + QJsonObject connectionJson = connection.save(); + + if (!connectionJson.isEmpty()) + connectionJsonArray.append(connectionJson); + } + } + } + + QJsonArray groupJsonArray; + for (QGraphicsItem * item : _scene->selectedItems()) + { + if (auto c = qgraphicsitem_cast(item)) { + Group& group = c->group(); + + //If collapsed + if(group.groupGraphicsObject().isCollapsed()) + { + //Add all child items to nodes array + for(int i=0; i(group.groupGraphicsObject().childItems()[i]); + if(ngo!=nullptr) + { + Node& node = ngo->node(); + nodesJsonArray.append(node.save()); + } + } + + //Add all connections to nodes array + for(int i=0; isave(); + if (!connectionJson.isEmpty()) + connectionJsonArray.append(connectionJson); + } + } + } + + QJsonObject groupJson = group.save(); + if (!groupJson.isEmpty()) + groupJsonArray.append(groupJson); + } + } + + sceneJson["nodes"] = nodesJsonArray; + sceneJson["connections"] = connectionJsonArray; + sceneJson["groups"] = groupJsonArray; + + return sceneJson; +} + +void FlowView::pasteSelectedNodes() { + QClipboard *p_Clipboard = QApplication::clipboard(); + QByteArray text = p_Clipboard->text().toUtf8(); + + QJsonObject const jsonDocument = QJsonDocument::fromJson(text).object(); + jsonToSceneMousePos(jsonDocument); + + +} + + + +void FlowView::duplicateSelectedNode() +{ + QJsonObject selectionJson = selectionToJson(); + jsonToSceneMousePos(selectionJson); } @@ -284,7 +757,6 @@ keyPressEvent(QKeyEvent *event) case Qt::Key_Shift: setDragMode(QGraphicsView::RubberBandDrag); break; - default: break; } @@ -357,6 +829,9 @@ drawBackground(QPainter* painter, const QRectF& r) double bottom = std::floor(tl.y() / gridStep - 0.5); double top = std::floor (br.y() / gridStep + 1.0); + if(right - left > 100) + return; + // vertical lines for (int xi = int(left); xi <= int(right); ++xi) { diff --git a/src/Group.cpp b/src/Group.cpp new file mode 100644 index 000000000..5c50653df --- /dev/null +++ b/src/Group.cpp @@ -0,0 +1,93 @@ +#include "Group.hpp" +#include "GroupGraphicsObject.hpp" +#include "FlowScene.hpp" + +using QtNodes::Group; + +QUuid +Group:: +id() const +{ + return _id; +} + +void +Group:: +SetName(QString _name) { + this->_name = _name; +} + +QString +Group:: +GetName() { + return _name; +} + + +void +Group:: +restore(QJsonObject const &json){ + QJsonObject positionJson = json["position"].toObject(); + QPointF point(positionJson["x"].toDouble(), + positionJson["y"].toDouble()); + _groupGraphicsObject->setPos(point); + + QJsonObject sizeJson = json["size"].toObject(); + _groupGraphicsObject->setSizeX(sizeJson["x"].toDouble()); + _groupGraphicsObject->setSizeY(sizeJson["y"].toDouble()); + + + QJsonObject colorJson = json["color"].toObject(); + _groupGraphicsObject->r = colorJson["r"].toInt(); + _groupGraphicsObject->g = colorJson["g"].toInt(); + _groupGraphicsObject->b = colorJson["b"].toInt(); + + SetName(json["name"].toString()); + _groupGraphicsObject->nameLineEdit->setText(_name); + + + Group &groupRef = *(this); + _scene.resolveGroups(groupRef); + + bool collapsed = (bool)json["collapsed"].toInt(); + if(collapsed) + { + _groupGraphicsObject->Collapse(); + } +} + +void +Group:: +restoreAtPosition(QJsonObject const &json, QPointF position){ + _groupGraphicsObject->setPos(position); + + QJsonObject sizeJson = json["size"].toObject(); + _groupGraphicsObject->setSizeX(sizeJson["x"].toDouble()); + _groupGraphicsObject->setSizeY(sizeJson["y"].toDouble()); + + + QJsonObject colorJson = json["color"].toObject(); + _groupGraphicsObject->r = colorJson["r"].toInt(); + _groupGraphicsObject->g = colorJson["g"].toInt(); + _groupGraphicsObject->b = colorJson["b"].toInt(); + + SetName(json["name"].toString()); + _groupGraphicsObject->nameLineEdit->setText(_name); + + // "Problem : when restoring the group, it doesn't see the nodes that are within it." + + Group &groupRef = *(this); + _scene.resolveGroups(groupRef); + + bool collapsed = (bool)json["collapsed"].toInt(); + if(collapsed) + { + _groupGraphicsObject->Collapse(); + } +}; + + +// GroupGraphicsObject& Group::groupGraphicsObject(){ +// return *_groupGraphicsObject.get(); +// } + diff --git a/src/GroupGraphicsObject.cpp b/src/GroupGraphicsObject.cpp new file mode 100644 index 000000000..facd4d782 --- /dev/null +++ b/src/GroupGraphicsObject.cpp @@ -0,0 +1,648 @@ +#include "GroupGraphicsObject.hpp" + +#include +#include + +#include +#include + +#include "ConnectionGraphicsObject.hpp" +#include "ConnectionState.hpp" +#include "Connection.hpp" + +#include "FlowScene.hpp" +#include "NodePainter.hpp" + +#include "Group.hpp" +#include "NodeDataModel.hpp" +#include "NodeConnectionInteraction.hpp" + +#include "StyleCollection.hpp" + +using QtNodes::GroupGraphicsObject; +using QtNodes::Node; +using QtNodes::Group; +using QtNodes::FlowScene; +using QtNodes::Connection; +using QtNodes::PortType; + +GroupGraphicsObject:: +GroupGraphicsObject(FlowScene &scene, Group& group) + : _scene(scene) + , _group(group) +{ + _scene.addItem(this); + + setFlag(QGraphicsItem::ItemDoesntPropagateOpacityToChildren, true); + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsFocusable, true); + setFlag(QGraphicsItem::ItemIsSelectable, true); + setFlag(QGraphicsItem::ItemSendsScenePositionChanges, true); + + setCacheMode( QGraphicsItem::DeviceCoordinateCache ); + + setAcceptHoverEvents(true); + setAcceptTouchEvents(true); + + setZValue(-2); + + r = g = b = 135; + + _proxyWidget = new QGraphicsProxyWidget(this); + _collapseButton = new QGraphicsProxyWidget(this); + + QString name = group.GetName(); + nameLineEdit = new QLineEdit(name); + nameLineEdit->setAlignment(Qt::AlignHCenter); + nameLineEdit->setStyleSheet("\ + QLineEdit {\ + border: 0px solid gray;\ + border-radius: 0px;\ + padding: 0 0px;\ + background: rgb(135, 135, 135);\ + selection-background-color: darkgray;\ + color: rgb(220, 220, 220);\ + font-size: 30px;\ + }\ + QLineEdit:hover {\ + background: rgb(90, 90, 90);\ + }\ + "); + + connect(nameLineEdit, &QLineEdit::textChanged, this, [&group](const QString &text) { + group.SetName(text); + }); + + _proxyWidget->setWidget(nameLineEdit); + _proxyWidget->setPos(QPointF(sizeX/2 - _proxyWidget->size().width()/2, 0)); + + collapseButtonWidget = new QPushButton("X"); + collapseButtonWidget->setCheckable(true); + _collapseButton->setWidget(collapseButtonWidget); + _collapseButton->setPos(QPointF(sizeX - _collapseButton->size().width(), 0)); + + connect(collapseButtonWidget, &QPushButton::clicked, this, [this](int state){ + Collapse(); + }); +} + +void +GroupGraphicsObject:: +Collapse() +{ + unusedConnections.clear(); + + ////1. Identify all the nodes that have external inputs + inOutLabels[(int)PortType::In].clear(); + inOutConnections[(int)PortType::In].clear(); + inOutNodes[(int)PortType::In].clear(); + inOutPorts[(int)PortType::In].clear(); + //for all the nodes in the group + for (int i = 0; i < childItems().size(); i++) + { + NodeGraphicsObject* nodeGO = qgraphicsitem_cast(childItems()[i]); + if (nodeGO != nullptr) + { + Node &node = nodeGO->node(); + int numEntries = node.nodeState().getEntries(PortType::In).size(); + + //For each input of the current node + for (int j = 0; j < numEntries; j++) + { + NodeState::ConnectionPtrSet connections = node.nodeState().connections(PortType::In, j); + //Get the connection + for (std::pair connectionPair : connections) + { + Connection *connection = connectionPair.second; + Node *sourceNode = connection->getNode(PortType::Out); + Node *destNode = connection->getNode(PortType::In); + NodeGraphicsObject &sourceNodeGO = sourceNode->nodeGraphicsObject(); + + //Check if the the input is inside the group + if (childItems().indexOf(&sourceNodeGO) == -1) { //if it's not inside + inOutLabels[(int)PortType::In].push_back( + node.nodeDataModel()->name() + "." + node.nodeDataModel()->portCaption(PortType::In, j) + ); + inOutConnections[(int)PortType::In].push_back(connection); + inOutNodes[(int)PortType::In].push_back(destNode); + inOutPorts[(int)PortType::In].push_back(j); + } + else { //it's inside + unusedConnections.push_back(connection); + } + } + } + } + } + + ////2. Identify all the nodes that have external outputs + inOutLabels[(int)PortType::Out].clear(); + inOutConnections[(int)PortType::Out].clear(); + inOutNodes[(int)PortType::Out].clear(); + inOutPorts[(int)PortType::Out].clear(); + //for all the nodes in the group + for (int i = 0; i < childItems().size(); i++) + { + NodeGraphicsObject* nodeGO = qgraphicsitem_cast(childItems()[i]); + if (nodeGO != nullptr) + { + Node &node = nodeGO->node(); + int numOutputs = node.nodeState().getEntries(PortType::Out).size(); + for (int j = 0; j < numOutputs; j++) + { + NodeState::ConnectionPtrSet connections = node.nodeState().connections(PortType::Out, j); + for (std::pair connectionPair : connections) + { + Connection *connection = connectionPair.second; + Node *destNode = connection->getNode(PortType::In); + NodeGraphicsObject &destNodeGO = destNode->nodeGraphicsObject(); + + if (childItems().indexOf(&destNodeGO) == -1) { + inOutLabels[(int)PortType::Out].push_back( + node.nodeDataModel()->name() + "." + node.nodeDataModel()->portCaption(PortType::Out, j) + ); + inOutConnections[(int)PortType::Out].push_back(connection); + inOutNodes[(int)PortType::Out].push_back(destNode); + inOutPorts[(int)PortType::Out].push_back(j); + } + else { + unusedConnections.push_back(connection); + } + } + } + } + } + + + if(!collapsed) + { + int numInOut = std::max(inOutLabels[(int)PortType::In].size(), inOutLabels[(int)PortType::Out].size()); + + savedSizeX = sizeX; + savedSizeY = sizeY; + + //Do resize + sizeX = 500; + sizeY = numInOut * spacing; + _proxyWidget->setPos(QPointF(sizeX/2 - _proxyWidget->size().width()/2, 0)); + _collapseButton->setPos(QPointF(sizeX - _collapseButton->size().width(), 0)); + + + //Sets the inside nodes invisible + for(int i=0; i(childItems()[i]); + if(ngo!=nullptr) + { + childItems()[i]->setVisible(false); + } + } + + //Sets the inside connections invisible + for(int i=0; igetConnectionGraphicsObject().setVisible(false); + } + + //Change the input connection positions + for(int i=0;igetConnectionGraphicsObject().sceneTransform().inverted().map(position); + inOutConnections[(int)PortType::In][i]->connectionGeometry().setEndPoint(PortType::In, connectionPos); + + + Group &thisGroup = group(); + inOutConnections[(int)PortType::In][i]->setGroup(&thisGroup, PortType::In, i); + } + + //Change the output connection positions + for(int i=0;igetConnectionGraphicsObject().sceneTransform().inverted().map(position); + inOutConnections[(int)PortType::Out][i]->connectionGeometry().setEndPoint(PortType::Out, connectionPos); + + Group &thisGroup = group(); + inOutConnections[(int)PortType::Out][i]->setGroup(&thisGroup, PortType::Out, i); + } + + collapsed=true; + } + else + { + sizeX = savedSizeX; + sizeY = savedSizeY; + + _proxyWidget->setPos(QPointF(sizeX/2 - _proxyWidget->size().width()/2, 0)); + _collapseButton->setPos(QPointF(sizeX - _collapseButton->size().width(), 0)); + + //Sets the inside nodes invisible + for(int i=0; i(childItems()[i]); + if(ngo!=nullptr) + { + childItems()[i]->setVisible(true); + } + } + + for(int i=0; igetConnectionGraphicsObject().setVisible(true); + } + + for(int i=0; isetGroup(nullptr, PortType::In, 0); + } + + for(int i=0; isetGroup(nullptr, PortType::Out, 0); + } + + collapsed=false; + unusedConnections.clear();; + inOutLabels[(int)PortType::In].clear(); + inOutLabels[(int)PortType::Out].clear(); + inOutConnections[(int)PortType::In].clear(); + inOutConnections[(int)PortType::Out].clear(); + moveConnections(); + } +} + +QPointF GroupGraphicsObject::portScenePosition(int i, PortType type) const +{ + + if(type == PortType::In) + { + return scenePos() +QPointF(inputSize, topPadding + i * spacing); + } + else + { + return scenePos() +QPointF(sizeX - inputSize, topPadding + i * spacing); + } +} + +QPointF GroupGraphicsObject::portPosition(int i, PortType type) const +{ + + if(type == PortType::In) + { + return QPointF(inputSize, topPadding + i * spacing); + } + else + { + return QPointF(sizeX - inputSize, topPadding + i * spacing); + } +} + +int GroupGraphicsObject::checkHitScenePoint(PortType portType, + QPointF const scenePoint, + QTransform sceneTransform) const +{ + auto const &nodeStyle = StyleCollection::nodeStyle(); + + PortIndex result = INVALID; + + if (portType == PortType::None) + return result; + + double const tolerance = 2.0 * nodeStyle.ConnectionPointDiameter; + + size_t const nItems = inOutLabels[(int)portType].size(); + + for (size_t i = 0; i < nItems; ++i) + { + auto pp = portScenePosition(i, portType); + + QPointF p = pp - scenePoint; + auto distance = std::sqrt(QPointF::dotProduct(p, p)); + + if (distance < tolerance) + { + result = PortIndex(i); + break; + } + } + + return result; +} + + +GroupGraphicsObject:: +~GroupGraphicsObject() +{ + _scene.removeItem(this); +} + + +Group& +GroupGraphicsObject:: +group() { + return _group; +} + +Group const& +GroupGraphicsObject:: +group() const { + return _group; +} + + +QRectF +GroupGraphicsObject:: +boundingRect() const +{ + return QRectF(0, 0, sizeX, sizeY); +} + + +void +GroupGraphicsObject:: +setGeometryChanged() +{ + prepareGeometryChange(); +} + +void +GroupGraphicsObject:: +moveConnections() const { + for(int i=0; i(childItems()[i]); + if (ngo) { + ngo->moveConnections(); + } + + GroupGraphicsObject* ggo = dynamic_cast(childItems()[i]); + if (ggo) { + ggo->moveConnections(); + } + } +} + + + +void +GroupGraphicsObject:: +paint(QPainter * painter, + QStyleOptionGraphicsItem const* option, + QWidget* ) +{ + painter->setClipRect(option->exposedRect); + QRect rect(0, 0, sizeX, sizeY); + auto color = QColor(r, g, b, 255); + painter->drawRect(rect); + painter->fillRect(rect, QBrush(color)); + + QFontMetrics const & metrics = + painter->fontMetrics(); + + auto drawPoints = + [&](PortType portType) + { + size_t n = inOutLabels[(int)portType].size(); + for (size_t i = 0; i < n; ++i) + { + QPointF p = portPosition(i, portType); + + painter->setPen(QColor(255, 255, 255, 255)); + + QString s = inOutLabels[(int)portType][i]; + + auto rect = metrics.boundingRect(s); + + p.setY(p.y() + rect.height() / 4.0); + switch (portType) + { + case PortType::In: + p.setX(inputSize + 5.0); + break; + + case PortType::Out: + p.setX(sizeX - 5.0 - rect.width() - inputSize); + break; + + default: + break; + } + + painter->drawText(p, s); + } + }; + + drawPoints(PortType::Out); + drawPoints(PortType::In); +} + + +QVariant +GroupGraphicsObject:: +itemChange(GraphicsItemChange change, const QVariant &value) +{ + return QGraphicsItem::itemChange(change, value); +} + + +void +GroupGraphicsObject:: +mousePressEvent(QGraphicsSceneMouseEvent * event) +{ + oldPosition = pos(); + if(QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) + { + event->ignore(); + return; + } + + // deselect all other items after this one is selected + if (!isSelected() && + !(event->modifiers() & Qt::ControlModifier)) + { + _scene.clearSelection(); + } + + auto mousePos = event->pos(); + + if (abs(mousePos.x() - sizeX) < 20 && abs(mousePos.y() - sizeY) < 20) + { + isResizingXY=true; + } else if (abs(mousePos.x() - sizeX) < 20) + { + isResizingX=true; + } else if (abs(mousePos.y() - sizeY) < 20) { + isResizingY=true; + } + + auto clickPort = + [&](PortType portToCheck) + { + + // TODO do not pass sceneTransform + int portIndex = checkHitScenePoint(portToCheck, + event->scenePos(), + sceneTransform()); + if (portIndex != INVALID) + { + // start dragging existing connection + if (portToCheck == PortType::In) + { + auto con = inOutConnections[(int)PortType::In][portIndex]; + Node *currentNode = inOutConnections[(int)PortType::In][portIndex]->getNode(PortType::In); + Node ¤tNodeRef = *currentNode; + NodeConnectionInteraction interaction(currentNodeRef, *con, _scene); + interaction.disconnect(portToCheck); + } + else // initialize new Connection + { + //Get the node from which the connection is created + Node *currentNode = inOutConnections[(int)PortType::Out][portIndex]->getNode(PortType::Out); + int nodePortIndex = inOutConnections[(int)PortType::Out][portIndex]->getPortIndex(PortType::Out); + Node ¤tNodeRef = *currentNode; + + //Create the group connection + auto connection = _scene.createConnection(portToCheck, + currentNodeRef, + nodePortIndex); + + //Set connection position + QPointF position = portScenePosition(portIndex, PortType::Out); + QPointF connectionPos = connection->getConnectionGraphicsObject().sceneTransform().inverted().map(position); + connection->connectionGeometry().setEndPoint(PortType::In, connectionPos); + connection->connectionGeometry().setEndPoint(PortType::Out, connectionPos); + + //Set the group to the connection + Group &thisGroup = group(); + connection->setGroup(&thisGroup, PortType::Out, portIndex); + + + //Add to the connections + inOutConnections[(int)PortType::Out].push_back(connection.get()); + + //Set the connection to the node + currentNodeRef.nodeState().setConnection(portToCheck, + nodePortIndex, + *connection); + + + connection->getConnectionGraphicsObject().grabMouse(); + connection->getConnectionGraphicsObject().move(); + } + } + }; + + clickPort(PortType::In); + clickPort(PortType::Out); +} + + +void +GroupGraphicsObject:: +mouseMoveEvent(QGraphicsSceneMouseEvent * event) +{ + if(isResizingX) { + int diff = event->pos().x() - event->lastPos().x(); + prepareGeometryChange(); + sizeX += diff; + update(); + _proxyWidget->setPos(QPointF(sizeX/2 - _proxyWidget->size().width()/2, 0)); + _collapseButton->setPos(QPointF(sizeX - _collapseButton->size().width(), 0)); + event->accept(); + } + else if(isResizingY) { + int diff = event->pos().y() - event->lastPos().y(); + prepareGeometryChange(); + sizeY += diff; + update(); + _proxyWidget->setPos(QPointF(sizeX/2 - _proxyWidget->size().width()/2, 0)); + _collapseButton->setPos(QPointF(sizeX - _collapseButton->size().width(), 0)); + event->accept(); + } else if(isResizingXY) { + auto diff = event->pos() - event->lastPos(); + prepareGeometryChange(); + sizeX += diff.x(); + sizeY += diff.y(); + update(); + _proxyWidget->setPos(QPointF(sizeX/2 - _proxyWidget->size().width()/2, 0)); + _collapseButton->setPos(QPointF(sizeX - _collapseButton->size().width(), 0)); + event->accept(); + } else { + _scene.groupMoved(_group, pos()); + QGraphicsObject::mouseMoveEvent(event); + moveConnections(); + } +} + + +void +GroupGraphicsObject:: +mouseReleaseEvent(QGraphicsSceneMouseEvent* event) +{ + QGraphicsObject::mouseReleaseEvent(event); + _scene.groupMoveFinished(_group, pos(), oldPosition); + + isResizingX=false; + isResizingY=false; + isResizingXY=false; +} + + +void +GroupGraphicsObject:: +hoverEnterEvent(QGraphicsSceneHoverEvent * event) +{ + auto mousePos = event->pos(); + setCursor(QCursor()); + update(); + event->accept(); +} + + +void +GroupGraphicsObject:: +hoverLeaveEvent(QGraphicsSceneHoverEvent * event) +{ + update(); + event->accept(); +} + + +void +GroupGraphicsObject:: +hoverMoveEvent(QGraphicsSceneHoverEvent * event) +{ + auto mousePos = event->pos(); + + if (abs(mousePos.x() - sizeX) < 20 && abs(mousePos.y() - sizeY) < 20) + { + setCursor(QCursor(Qt::SizeFDiagCursor)); + } else if (abs(mousePos.x() - sizeX) < 20) + { + setCursor(QCursor(Qt::SizeHorCursor)); + } else if (abs(mousePos.y() - sizeY) < 20) { + setCursor(QCursor(Qt::SizeVerCursor)); + } else + { + setCursor(QCursor()); + } + + event->accept(); +} + + +void +GroupGraphicsObject:: +mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) +{ + _scene.groupDoubleClicked(group()); + + QGraphicsItem::mouseDoubleClickEvent(event); +} + +void +GroupGraphicsObject:: +contextMenuEvent(QGraphicsSceneContextMenuEvent* event) +{ + +} diff --git a/src/Node.cpp b/src/Node.cpp index e72d08184..0e94c053c 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -11,6 +11,8 @@ #include "ConnectionGraphicsObject.hpp" #include "ConnectionState.hpp" +#include "Connection.hpp" +#include using QtNodes::Node; using QtNodes::NodeGeometry; @@ -21,6 +23,8 @@ using QtNodes::NodeDataModel; using QtNodes::NodeGraphicsObject; using QtNodes::PortIndex; using QtNodes::PortType; +using QtNodes::Connection; + Node:: Node(std::unique_ptr && dataModel) @@ -35,8 +39,17 @@ Node(std::unique_ptr && dataModel) // propagate data: model => node connect(_nodeDataModel.get(), &NodeDataModel::dataUpdated, this, &Node::onDataUpdated); + + this->inputSelected.resize(_nodeDataModel->nPorts(PortType::In)); + } +void +Node:: +setInputSelected(int inx, bool selected) +{ + this->inputSelected[inx] = selected; +} Node:: ~Node() {} @@ -51,6 +64,21 @@ save() const nodeJson["model"] = _nodeDataModel->save(); + QJsonObject obj; + obj["x"] = _nodeGraphicsObject->scenePos().x(); + obj["y"] = _nodeGraphicsObject->scenePos().y(); + nodeJson["position"] = obj; + + return nodeJson; +} + +QJsonObject Node::copyWithNewID(QUuid newId) const +{ + QJsonObject nodeJson; + + nodeJson["id"] = newId.toString(); + nodeJson["model"] = _nodeDataModel->save(); + QJsonObject obj; obj["x"] = _nodeGraphicsObject->pos().x(); obj["y"] = _nodeGraphicsObject->pos().y(); @@ -60,6 +88,30 @@ save() const } +void +Node:: +updateView() +{ + nodeGeometry().recalculateInOut(); + nodeState().updateEntries(); + + nodeGraphicsObject().update(); + // QGraphicsView *view = nodeGraphicsObject().scene()->views().first(); + // view->viewport()->repaint(); +} + + +void +Node:: +eraseInputAtIndex(int portIndex) +{ + std::unordered_map connections = nodeState().connections(PortType::In, portIndex); + for (auto& connection : connections) { + nodeGraphicsObject().flowScene().deleteConnection(connection.second); + } +} + + void Node:: restore(QJsonObject const& json) @@ -74,6 +126,20 @@ restore(QJsonObject const& json) _nodeDataModel->restore(json["model"].toObject()); } +void +Node:: +paste(QJsonObject const& json, QUuid ID) +{ + _id = ID; + + QJsonObject positionJson = json["position"].toObject(); + QPointF point(positionJson["x"].toDouble(), + positionJson["y"].toDouble()); + _nodeGraphicsObject->setPos(point); + + _nodeDataModel->restore(json["model"].toObject()); +} + QUuid Node:: @@ -82,6 +148,11 @@ id() const return _id; } +void Node::setId(QUuid id) { + this->_id = id; +} + + void Node:: @@ -136,6 +207,8 @@ setGraphicsObject(std::unique_ptr&& graphics) _nodeGraphicsObject = std::move(graphics); _nodeGeometry.recalculateSize(); + + nodeGraphicsObject().setToolTip(_nodeDataModel->toolTipText()); } @@ -200,9 +273,20 @@ onDataUpdated(PortIndex index) { auto nodeData = _nodeDataModel->outData(index); - auto connections = - _nodeState.connections(PortType::Out, index); + if (_nodeState.getEntries(PortType::Out).size() > 0) + { + auto connections = + _nodeState.connections(PortType::Out, index); - for (auto const & c : connections) - c.second->propagateData(nodeData); + for (auto const & c : connections) + c.second->propagateData(nodeData); + } +} + +void +Node:: +onDataUpdatedConnection(PortIndex index, Connection* connection) +{ + auto nodeData = _nodeDataModel->outData(index); + connection->propagateData(nodeData); } diff --git a/src/NodeConnectionInteraction.cpp b/src/NodeConnectionInteraction.cpp index acab66ff0..c1aa73131 100644 --- a/src/NodeConnectionInteraction.cpp +++ b/src/NodeConnectionInteraction.cpp @@ -148,8 +148,36 @@ tryConnect() const if (outNode) { PortIndex outPortIndex = _connection->getPortIndex(PortType::Out); - outNode->onDataUpdated(outPortIndex); + // outNode->onDataUpdated(outPortIndex); + outNode->onDataUpdatedConnection(outPortIndex, _connection); } + + QUuid connectionID = _connection->id(); + FlowScene *scene = _scene; + QUuid nodeInID = _connection->getNode(PortType::In)->id(); + QUuid nodeOutID = _connection->getNode(PortType::Out)->id(); + PortIndex portIn = _connection->getPortIndex(PortType::In); + PortIndex portOut = _connection->getPortIndex(PortType::Out); + _scene->AddAction(UndoRedoAction( + [scene, connectionID](void *ptr) + { + scene->deleteConnectionWithID(connectionID); + return 0; + }, + [scene, nodeInID, portIn, nodeOutID, portOut, connectionID](void *ptr) + { + QUuid id = connectionID; + Node *nodeIn = scene->nodes()[nodeInID].get(); + Node *nodeOut = scene->nodes()[nodeOutID].get(); + scene->createConnection(*nodeIn, + portIn, + *nodeOut, + portOut, + &id); + return 0; + }, + "Created Connection " + )); return true; } @@ -162,6 +190,14 @@ bool NodeConnectionInteraction:: disconnect(PortType portToDisconnect) const { + + QUuid connectionID = _connection->id(); + FlowScene *scene = _scene; + Node *nodeIn = _connection->getNode(PortType::In); + Node *nodeOut = _connection->getNode(PortType::Out); + PortIndex portIn = _connection->getPortIndex(PortType::In); + PortIndex portOut = _connection->getPortIndex(PortType::Out); + PortIndex portIndex = _connection->getPortIndex(portToDisconnect); @@ -180,6 +216,25 @@ disconnect(PortType portToDisconnect) const _connection->getConnectionGraphicsObject().grabMouse(); + _scene->AddAction(UndoRedoAction( + [scene, nodeIn, portIn, nodeOut, portOut, connectionID](void *ptr) + { + QUuid id = connectionID; + scene->createConnection(*nodeIn, + portIn, + *nodeOut, + portOut, + &id); + return 0; + }, + [scene, connectionID](void *ptr) + { + scene->deleteConnectionWithID(connectionID); + return 0; + }, + "Removed Connection " + )); + return true; } diff --git a/src/NodeDataModel.cpp b/src/NodeDataModel.cpp index 9737b345d..1f48c5108 100644 --- a/src/NodeDataModel.cpp +++ b/src/NodeDataModel.cpp @@ -1,6 +1,7 @@ #include "NodeDataModel.hpp" #include "StyleCollection.hpp" +#include using QtNodes::NodeDataModel; using QtNodes::NodeStyle; @@ -39,3 +40,15 @@ setNodeStyle(NodeStyle const& style) { _nodeStyle = style; } + + +void NodeDataModel::setToolTipText(QString text) +{ + _toolTipText = text; + emit setToolTipTextSignal(text); +} + +QString NodeDataModel::toolTipText() +{ + return _toolTipText; +} diff --git a/src/NodeGeometry.cpp b/src/NodeGeometry.cpp index 8309abf9b..6875c1753 100644 --- a/src/NodeGeometry.cpp +++ b/src/NodeGeometry.cpp @@ -39,6 +39,14 @@ NodeGeometry(std::unique_ptr const &dataModel) } +void +NodeGeometry:: +recalculateInOut() +{ + _nSources = _dataModel->nPorts(PortType::Out); + _nSinks = _dataModel->nPorts(PortType::In); +} + QRectF NodeGeometry:: entryBoundingRect() const @@ -58,7 +66,8 @@ boundingRect() const { auto const &nodeStyle = StyleCollection::nodeStyle(); - double addon = 4 * nodeStyle.ConnectionPointDiameter; + double addon = nodeStyle.ConnectionPointDiameter * 1.6; + // double addon = 0; return QRectF(0 - addon, 0 - addon, @@ -217,8 +226,8 @@ resizeRect() const { unsigned int rectSize = 7; - return QRect(_width - rectSize, - _height - rectSize, + return QRect(_width + rectSize, + _height + rectSize, rectSize, rectSize); } diff --git a/src/NodeGraphicsObject.cpp b/src/NodeGraphicsObject.cpp index 3f394e91d..91703704c 100644 --- a/src/NodeGraphicsObject.cpp +++ b/src/NodeGraphicsObject.cpp @@ -43,12 +43,12 @@ NodeGraphicsObject(FlowScene &scene, auto const &nodeStyle = node.nodeDataModel()->nodeStyle(); { - auto effect = new QGraphicsDropShadowEffect; - effect->setOffset(4, 4); - effect->setBlurRadius(20); - effect->setColor(nodeStyle.ShadowColor); - - setGraphicsEffect(effect); + //auto effect = new QGraphicsDropShadowEffect; + //effect->setOffset(4, 4); + //effect->setBlurRadius(0); + //effect->setColor(nodeStyle.ShadowColor); + //auto effect = new QGraphicsColorizeEffect ; + //setGraphicsEffect(effect); } setOpacity(nodeStyle.Opacity); @@ -66,6 +66,10 @@ NodeGraphicsObject(FlowScene &scene, connect(this, &QGraphicsObject::xChanged, this, onMoveSlot); connect(this, &QGraphicsObject::yChanged, this, onMoveSlot); + + connect(_node.nodeDataModel(), &NodeDataModel::setToolTipTextSignal, this, [this](QString toolTipText ){ + setToolTip(toolTipText); + }); } @@ -84,6 +88,14 @@ node() } +FlowScene& +NodeGraphicsObject:: +flowScene() +{ + return _scene; +} + + Node const& NodeGraphicsObject:: node() const @@ -110,7 +122,7 @@ embedQWidget() _proxyWidget->setPos(geom.widgetPosition()); update(); - + _proxyWidget->setContentsMargins(0, 0, 0, 0); _proxyWidget->setOpacity(1.0); _proxyWidget->setFlag(QGraphicsItem::ItemIgnoresParentOpacity); } @@ -173,19 +185,49 @@ paint(QPainter * painter, QStyleOptionGraphicsItem const* option, QWidget* ) { + if(_node.inputSelected.size() != _node.nodeDataModel()->nPorts(PortType::In)) + { + _node.inputSelected.resize(_node.nodeDataModel()->nPorts(PortType::In)); + } painter->setClipRect(option->exposedRect); NodePainter::paint(painter, _node, _scene); } +int closestMultiple(int n, int x) +{ + int sign = (n < 0) ? -1 : 1; + int absN = std::abs(n); + + if(x>absN) + return x; + + absN = absN + x/2; + absN = absN - (absN%x); + return absN * sign; +} QVariant NodeGraphicsObject:: itemChange(GraphicsItemChange change, const QVariant &value) { + if (change == ItemPositionChange && scene()) { moveConnections(); + QPointF newPos = value.toPointF(); + if(_scene.snapping) + { + int xi = (int)round(newPos.x()); + int yi = (int)round(newPos.y()); + qreal xF = (qreal)closestMultiple(xi, 15 * _scene.gridSize) - 2; + qreal yF = (qreal)closestMultiple(yi, 15 * _scene.gridSize) - 2; + return QPointF(xF, yF); + } + else + { + return newPos; + } } return QGraphicsItem::itemChange(change, value); @@ -196,8 +238,9 @@ void NodeGraphicsObject:: mousePressEvent(QGraphicsSceneMouseEvent * event) { + if(_locked) return; - + oldPosition = pos(); // deselect all other items after this one is selected if (!isSelected() && !(event->modifiers() & Qt::ControlModifier)) @@ -205,6 +248,8 @@ mousePressEvent(QGraphicsSceneMouseEvent * event) _scene.clearSelection(); } + _scene.nodeClicked(node()); + auto clickPort = [&](PortType portToCheck) { @@ -312,6 +357,8 @@ mouseMoveEvent(QGraphicsSceneMouseEvent * event) moveConnections(); event->ignore(); + + } QRectF r = scene()->sceneRect(); @@ -331,6 +378,8 @@ mouseReleaseEvent(QGraphicsSceneMouseEvent* event) state.setResizing(false); QGraphicsObject::mouseReleaseEvent(event); + + _scene.nodeMoveFinished(_node, pos(), oldPosition); // position connections precisely after fast node move moveConnections(); diff --git a/src/NodePainter.cpp b/src/NodePainter.cpp index c2e55712d..5147a07b8 100644 --- a/src/NodePainter.cpp +++ b/src/NodePainter.cpp @@ -46,7 +46,7 @@ paint(QPainter* painter, drawModelName(painter, geom, state, model); - drawEntryLabels(painter, geom, state, model); + drawEntryLabels(painter, geom, state, model, node.inputSelected); drawResizeRect(painter, geom, model); @@ -281,13 +281,14 @@ NodePainter:: drawEntryLabels(QPainter * painter, NodeGeometry const & geom, NodeState const & state, - NodeDataModel const * model) + NodeDataModel const * model, + std::vector &inputSelected) { QFontMetrics const & metrics = painter->fontMetrics(); auto drawPoints = - [&](PortType portType) + [&](PortType portType, std::vector *inputSelected=nullptr) { auto const &nodeStyle = model->nodeStyle(); @@ -304,6 +305,9 @@ drawEntryLabels(QPainter * painter, else painter->setPen(nodeStyle.FontColor); + if(inputSelected != nullptr && inputSelected->at(i)) + painter->setPen(nodeStyle.FontColorSelected); + QString s; if (model->portCaptionVisible(portType, i)) @@ -339,7 +343,7 @@ drawEntryLabels(QPainter * painter, drawPoints(PortType::Out); - drawPoints(PortType::In); + drawPoints(PortType::In, &inputSelected); } diff --git a/src/NodePainter.hpp b/src/NodePainter.hpp index a226d79c3..9ad1cd293 100644 --- a/src/NodePainter.hpp +++ b/src/NodePainter.hpp @@ -48,7 +48,8 @@ class NodePainter drawEntryLabels(QPainter* painter, NodeGeometry const& geom, NodeState const& state, - NodeDataModel const * model); + NodeDataModel const * model, + std::vector &inputColors); static void diff --git a/src/NodeState.cpp b/src/NodeState.cpp index 670ea3cde..91d80f5f5 100644 --- a/src/NodeState.cpp +++ b/src/NodeState.cpp @@ -18,6 +18,7 @@ NodeState(std::unique_ptr const &model) , _reaction(NOT_REACTING) , _reactingPortType(PortType::None) , _resizing(false) + , _model(model) {} @@ -41,7 +42,7 @@ getEntries(PortType portType) else return _outConnections; } - + NodeState::ConnectionPtrSet NodeState:: @@ -53,6 +54,32 @@ connections(PortType portType, PortIndex portIndex) const } +std::vector +NodeState:: +allConnections() const +{ + std::vector res; + std::vector ins = getEntries(PortType::In); + for(int i=0; i outs = getEntries(PortType::Out); + for(int i=0; inPorts(PortType::In)); + _outConnections.resize(_model->nPorts(PortType::Out)); +} \ No newline at end of file diff --git a/src/NodeStyle.cpp b/src/NodeStyle.cpp index c62e0ac9e..37efc0c25 100644 --- a/src/NodeStyle.cpp +++ b/src/NodeStyle.cpp @@ -123,6 +123,7 @@ loadJsonFromByteArray(QByteArray const &byteArray) NODE_STYLE_READ_COLOR(obj, ShadowColor); NODE_STYLE_READ_COLOR(obj, FontColor); NODE_STYLE_READ_COLOR(obj, FontColorFaded); + NODE_STYLE_READ_COLOR(obj, FontColorSelected); NODE_STYLE_READ_COLOR(obj, ConnectionPointColor); NODE_STYLE_READ_COLOR(obj, FilledConnectionPointColor); NODE_STYLE_READ_COLOR(obj, WarningColor);