Skip to content

Commit 0d28130

Browse files
g-abiliotatatupihudsonmiranda291Gabrielnmds
authored
Adds Node Group functionality (#514)
The node group feature allows nodes to be grouped, giving users the flexibility to organize their workspace. Groups can be locked, making the constituent nodes inaccessible via the mouse, or unlocked for easy access. Saving and loading groups further empower users to tailor their setup to their needs, enhancing control over their workflow. Co-authored-by: Taiguara Tupinambás <tatatupi@gmail.com> Co-authored-by: Hudson Miranda <hudsonmiranda291@gmail.com> Co-authored-by: Gabrielnmds <gabrielnmds21@gmail.com>
1 parent f84b5d8 commit 0d28130

28 files changed

+2229
-91
lines changed

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ set(CPP_SOURCE_FILES
8787
src/StyleCollection.cpp
8888
src/UndoCommands.cpp
8989
src/locateNode.cpp
90+
src/GroupGraphicsObject.cpp
91+
src/NodeGroup.cpp
9092
resources/resources.qrc
9193
)
9294

@@ -127,6 +129,8 @@ set(HPP_HEADER_FILES
127129
include/QtNodes/internal/DefaultVerticalNodeGeometry.hpp
128130
include/QtNodes/internal/NodeConnectionInteraction.hpp
129131
include/QtNodes/internal/UndoCommands.hpp
132+
include/QtNodes/internal/NodeGroup.hpp
133+
include/QtNodes/internal/GroupGraphicsObject.hpp
130134
)
131135

132136
# If we want to give the option to build a static library,

examples/calculator/main.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ int main(int argc, char *argv[])
112112
mainWidget.setWindowModified(true);
113113
});
114114

115+
if (scene->groupingEnabled()) {
116+
auto loadGroupAction = menu->addAction("Load Group...");
117+
QObject::connect(loadGroupAction, &QAction::triggered, [scene] { scene->loadGroupFile(); });
118+
}
119+
115120
mainWidget.setWindowTitle("[*]Data Flow: simplest calculator");
116121
mainWidget.resize(800, 600);
117122
// Center window.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include "internal/GroupGraphicsObject.hpp"

include/QtNodes/NodeGroup

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include "internal/NodeGroup.hpp"

include/QtNodes/internal/BasicGraphicsScene.hpp

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
#include "ConnectionIdHash.hpp"
66
#include "Definitions.hpp"
77
#include "Export.hpp"
8+
#include "GroupGraphicsObject.hpp"
9+
#include "NodeGroup.hpp"
10+
#include "UndoCommands.hpp"
811

9-
#include "QUuidStdHash.hpp"
10-
11-
#include <QtCore/QUuid>
12+
#include <QtCore/QJsonObject>
1213
#include <QtWidgets/QGraphicsScene>
1314
#include <QtWidgets/QMenu>
1415

@@ -27,6 +28,11 @@ class AbstractNodePainter;
2728
class ConnectionGraphicsObject;
2829
class NodeGraphicsObject;
2930
class NodeStyle;
31+
class DeleteCommand;
32+
class CopyCommand;
33+
class NodeGroup;
34+
class GroupGraphicsObject;
35+
struct ConnectionId;
3036

3137
/// An instance of QGraphicsScene, holds connections and nodes.
3238
class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene
@@ -62,6 +68,17 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene
6268

6369
QUndoStack &undoStack();
6470

71+
/**
72+
* @brief Setter for the _groupingEnabled flag.
73+
* @param boolean to set or not the flag.
74+
*/
75+
void setGroupingEnabled(bool enabled);
76+
77+
/**
78+
* @brief Getter for the _groupingEnabled flag.
79+
*/
80+
bool groupingEnabled() const { return _groupingEnabled; }
81+
6582
public:
6683
/**
6784
* @brief Creates a "draft" instance of ConnectionGraphicsObject.
@@ -87,6 +104,83 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene
87104
/// Deletes all the nodes. Connections are removed automatically.
88105
void clearScene();
89106

107+
/**
108+
* @brief Creates a list of the connections that are incident only to nodes within a
109+
* given group.
110+
* @param groupID ID of the desired group.
111+
* @return List of (pointers of) connections whose both endpoints belong to members of
112+
* the specified group.
113+
*/
114+
std::vector<std::shared_ptr<ConnectionId>> connectionsWithinGroup(GroupId groupID);
115+
/**
116+
* @brief Creates a group in the scene containing the given nodes.
117+
* @param nodes Reference to the list of nodes to be included in the group.
118+
* @param name Group's name.
119+
* @param groupId Group's id.
120+
* @return Pointer to the newly-created group.
121+
*/
122+
std::weak_ptr<NodeGroup> createGroup(std::vector<NodeGraphicsObject *> &nodes,
123+
QString name = QStringLiteral(""),
124+
GroupId groupId = InvalidGroupId);
125+
126+
/**
127+
* @brief Creates a group in the scene containing the currently selected nodes.
128+
* @param name Group's name
129+
* @return Pointer to the newly-created group.
130+
*/
131+
std::weak_ptr<NodeGroup> createGroupFromSelection(QString groupName = QStringLiteral(""));
132+
133+
/**
134+
* @brief Restores a group from a JSON object.
135+
* @param groupJson JSON object containing the group data.
136+
* @return Pair consisting of a pointer to the newly-created group and the mapping
137+
* between old and new nodes.
138+
*/
139+
std::pair<std::weak_ptr<NodeGroup>, std::unordered_map<GroupId, GroupId>> restoreGroup(
140+
QJsonObject const &groupJson);
141+
142+
/**
143+
* @brief Returns a const reference to the mapping of existing groups.
144+
*/
145+
std::unordered_map<GroupId, std::shared_ptr<NodeGroup>> const &groups() const;
146+
147+
/**
148+
* @brief Loads a group from a file specified by the user.
149+
* @return Pointer to the newly-created group.
150+
*/
151+
std::weak_ptr<NodeGroup> loadGroupFile();
152+
153+
/**
154+
* @brief Saves a group in a .group file.
155+
* @param groupID Group's id.
156+
*/
157+
void saveGroupFile(GroupId groupID);
158+
159+
/**
160+
* @brief Calculates the selected nodes.
161+
* @return Vector containing the NodeGraphicsObject pointers related to the selected nodes.
162+
*/
163+
std::vector<NodeGraphicsObject *> selectedNodes() const;
164+
165+
/**
166+
* @brief Calculates the selected groups.
167+
* @return Vector containing the GroupGraphicsObject pointers related to the selected groups.
168+
*/
169+
std::vector<GroupGraphicsObject *> selectedGroups() const;
170+
171+
/**
172+
* @brief Adds a node to a group, if both node and group exists.
173+
* @param nodeId Node's id.
174+
* @param groupId Group's id.
175+
*/
176+
void addNodeToGroup(NodeId nodeId, GroupId groupId);
177+
178+
/**
179+
* @brief Removes a node from a group, if the node exists and is within a group.
180+
* @param nodeId Node's id.
181+
*/
182+
void removeNodeFromGroup(NodeId nodeId);
183+
90184
public:
91185
/**
92186
* @returns NodeGraphicsObject associated with the given nodeId.
@@ -111,6 +205,17 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene
111205
*/
112206
virtual QMenu *createSceneMenu(QPointF const scenePos);
113207

208+
/**
209+
* @brief Creates the default menu when a node is selected.
210+
*/
211+
QMenu *createStdMenu(QPointF const scenePos);
212+
213+
/**
214+
* @brief Creates the menu when a group is selected.
215+
* @param groupGo reference to the GroupGraphicsObject related to the selected group.
216+
*/
217+
QMenu *createGroupMenu(QPointF const scenePos, GroupGraphicsObject *groupGo);
218+
114219
Q_SIGNALS:
115220
void modified(BasicGraphicsScene *);
116221
void nodeMoved(NodeId const nodeId, QPointF const &newLocation);
@@ -141,6 +246,24 @@ class NODE_EDITOR_PUBLIC BasicGraphicsScene : public QGraphicsScene
141246
/// Redraws adjacent nodes for given `connectionId`
142247
void updateAttachedNodes(ConnectionId const connectionId, PortType const portType);
143248

249+
/**
250+
* @brief Loads a JSON object that represents a node, with the option
251+
* to keep the stored node id or generate a new one.
252+
* @param nodeJson The JSON object representing a node.
253+
* @param keepOriginalId If true, the loaded node will have the same id as the one stored in
254+
* the file; otherwise, a new id will be generated
255+
* @return A reference to the NodeGraphicsObject related to the loaded node.
256+
*/
257+
NodeGraphicsObject &loadNodeToMap(QJsonObject nodeJson, bool keepOriginalId = false);
258+
259+
/**
260+
* @brief Loads a connection between nodes from a JSON file.
261+
* @param connectionJson JSON object that stores the connection's endpoints.
262+
* @param nodeIdMap Map of nodes (i.e. all possible endpoints).
263+
*/
264+
void loadConnectionToMap(QJsonObject const &connectionJson,
265+
std::unordered_map<NodeId, NodeId> const &nodeIdMap);
266+
144267
public Q_SLOTS:
145268
/// Slot called when the `connectionId` is erased form the AbstractGraphModel.
146269
virtual void onConnectionDeleted(ConnectionId const connectionId);
@@ -155,21 +278,37 @@ public Q_SLOTS:
155278
virtual void onNodeClicked(NodeId const nodeId);
156279
virtual void onModelReset();
157280

281+
/**
282+
* @brief Slot called to trigger the copy command action.
283+
*/
284+
void onCopySelectedObjects() { undoStack().push(new CopyCommand(this)); }
285+
286+
/**
287+
* @brief Slot called to trigger the delete command action.
288+
*/
289+
void onDeleteSelectedObjects() { undoStack().push(new DeleteCommand(this)); }
290+
158291
private:
159292
AbstractGraphModel &_graphModel;
160293

161294
using UniqueNodeGraphicsObject = std::unique_ptr<NodeGraphicsObject>;
162295
using UniqueConnectionGraphicsObject = std::unique_ptr<ConnectionGraphicsObject>;
296+
using SharedGroup = std::shared_ptr<NodeGroup>;
163297

164298
std::unordered_map<NodeId, UniqueNodeGraphicsObject> _nodeGraphicsObjects;
165299
std::unordered_map<ConnectionId, UniqueConnectionGraphicsObject> _connectionGraphicsObjects;
300+
GroupId nextGroupId();
301+
302+
std::unordered_map<GroupId, SharedGroup> _groups{};
303+
GroupId _nextGroupId{0};
166304
std::unique_ptr<ConnectionGraphicsObject> _draftConnection;
167305
std::unique_ptr<AbstractNodeGeometry> _nodeGeometry;
168306
std::unique_ptr<AbstractNodePainter> _nodePainter;
169307
std::unique_ptr<AbstractConnectionPainter> _connectionPainter;
170308
bool _nodeDrag;
171309
QUndoStack *_undoStack;
172310
Qt::Orientation _orientation;
311+
bool _groupingEnabled;
173312
};
174313

175314
} // namespace QtNodes

include/QtNodes/internal/Definitions.hpp

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,23 @@ NODE_EDITOR_PUBLIC Q_NAMESPACE
1818
Q_NAMESPACE_EXPORT(NODE_EDITOR_PUBLIC)
1919
#endif
2020

21-
/**
21+
/**
2222
* Constants used for fetching QVariant data from GraphModel.
2323
*/
24-
enum class NodeRole {
25-
Type = 0, ///< Type of the current node, usually a string.
26-
Position = 1, ///< `QPointF` positon of the node on the scene.
27-
Size = 2, ///< `QSize` for resizable nodes.
28-
CaptionVisible = 3, ///< `bool` for caption visibility.
29-
Caption = 4, ///< `QString` for node caption.
30-
Style = 5, ///< Custom NodeStyle as QJsonDocument
31-
InternalData = 6, ///< Node-stecific user data as QJsonObject
32-
InPortCount = 7, ///< `unsigned int`
33-
OutPortCount = 9, ///< `unsigned int`
34-
Widget = 10, ///< Optional `QWidget*` or `nullptr`
35-
ValidationState = 11, ///< Enum NodeValidationState of the node
36-
ProcessingStatus = 12 ///< Enum NodeProcessingStatus of the node
37-
};
24+
enum class NodeRole {
25+
Type = 0, ///< Type of the current node, usually a string.
26+
Position = 1, ///< `QPointF` positon of the node on the scene.
27+
Size = 2, ///< `QSize` for resizable nodes.
28+
CaptionVisible = 3, ///< `bool` for caption visibility.
29+
Caption = 4, ///< `QString` for node caption.
30+
Style = 5, ///< Custom NodeStyle as QJsonDocument
31+
InternalData = 6, ///< Node-stecific user data as QJsonObject
32+
InPortCount = 7, ///< `unsigned int`
33+
OutPortCount = 9, ///< `unsigned int`
34+
Widget = 10, ///< Optional `QWidget*` or `nullptr`
35+
ValidationState = 11, ///< Enum NodeValidationState of the node
36+
ProcessingStatus = 12 ///< Enum NodeProcessingStatus of the node
37+
};
3838
Q_ENUM_NS(NodeRole)
3939

4040
/**
@@ -94,6 +94,11 @@ using NodeId = unsigned int;
9494

9595
static constexpr NodeId InvalidNodeId = std::numeric_limits<NodeId>::max();
9696

97+
/// Unique Id associated with each node group.
98+
using GroupId = unsigned int;
99+
100+
static constexpr GroupId InvalidGroupId = std::numeric_limits<GroupId>::max();
101+
97102
/**
98103
* A unique connection identificator that stores
99104
* out `NodeId`, out `PortIndex`, in `NodeId`, in `PortIndex`

include/QtNodes/internal/GraphicsView.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public Q_SLOTS:
9191
private:
9292
QAction *_clearSelectionAction = nullptr;
9393
QAction *_deleteSelectionAction = nullptr;
94+
QAction *_cutSelectionAction = nullptr;
9495
QAction *_duplicateSelectionAction = nullptr;
9596
QAction *_copySelectionAction = nullptr;
9697
QAction *_pasteAction = nullptr;

0 commit comments

Comments
 (0)