@@ -91,24 +91,208 @@ redo()
9191{
9292 auto & graphModel = _scene->graphModel ();
9393
94+ QJsonArray connectionJsonArray = _sceneJson[" connections" ].toArray ();
95+
96+ for (QJsonValueRef connection : connectionJsonArray)
97+ {
98+ QJsonObject connJson = connection.toObject ();
99+
100+ ConnectionId connId = fromJson (connJson);
101+
102+ graphModel.deleteConnection (connId);
103+ }
104+
105+
94106 QJsonArray nodesJsonArray = _sceneJson[" nodes" ].toArray ();
95107
96108 for (QJsonValueRef node : nodesJsonArray)
97109 {
98110 QJsonObject nodeJson = node.toObject ();
99111 graphModel.deleteNode (static_cast <NodeId>(nodeJson[" id" ].toInt ()));
100112 }
113+ }
114+
115+
116+ // -------------------------------------
117+
118+
119+ DuplicateCommand::
120+ DuplicateCommand (BasicGraphicsScene* scene,
121+ QPointF const & mouseScenePos)
122+ : _scene(scene)
123+ , _mouseScenePos(mouseScenePos)
124+ {
125+ auto & graphModel = _scene->graphModel ();
126+
127+ std::unordered_set<NodeId> selectedNodes;
128+
129+ QJsonArray nodesJsonArray;
130+ // Delete the nodes; this will delete many of the connections.
131+ // Selected connections were already deleted prior to this loop,
132+ for (QGraphicsItem * item : _scene->selectedItems ())
133+ {
134+ if (auto n = qgraphicsitem_cast<NodeGraphicsObject*>(item))
135+ {
136+ nodesJsonArray.append (graphModel.saveNode (n->nodeId ()));
137+
138+ selectedNodes.insert (n->nodeId ());
139+ }
140+ }
141+
142+ QJsonArray connJsonArray;
143+ // Delete the selected connections first, ensuring that they won't be
144+ // automatically deleted when selected nodes are deleted (deleting a
145+ // node deletes some connections as well)
146+ for (QGraphicsItem * item : _scene->selectedItems ())
147+ {
148+ if (auto c = qgraphicsitem_cast<ConnectionGraphicsObject*>(item))
149+ {
150+ auto const & cid = c->connectionId ();
151+
152+ if (selectedNodes.count (cid.outNodeId ) > 0 &&
153+ selectedNodes.count (cid.inNodeId ) > 0 )
154+ {
155+ connJsonArray.append (toJson (cid));
156+ }
157+ }
158+ }
159+
160+
161+ _sceneJson[" nodes" ] = nodesJsonArray;
162+ _sceneJson[" connections" ] = connJsonArray;
163+ }
164+
165+
166+ void
167+ DuplicateCommand::
168+ undo ()
169+ {
170+ auto & graphModel = _scene->graphModel ();
171+
172+ QJsonArray connectionJsonArray = _newSceneJson[" connections" ].toArray ();
173+
174+ for (QJsonValueRef connection : connectionJsonArray)
175+ {
176+ QJsonObject connJson = connection.toObject ();
177+
178+ ConnectionId connId = fromJson (connJson);
179+
180+ graphModel.deleteConnection (connId);
181+ }
182+
183+ QJsonArray nodesJsonArray = _newSceneJson[" nodes" ].toArray ();
184+
185+ for (QJsonValueRef node : nodesJsonArray)
186+ {
187+ QJsonObject nodeJson = node.toObject ();
188+ graphModel.deleteNode (static_cast <NodeId>(nodeJson[" id" ].toInt ()));
189+ }
190+ }
191+
192+
193+ void
194+ DuplicateCommand::
195+ redo ()
196+ {
197+ _scene->clearSelection ();
198+
199+ auto & graphModel = _scene->graphModel ();
200+
201+ std::unordered_map<NodeId, NodeId> mapNodeIds;
202+
203+ QPointF averagePos;
204+
205+ QJsonArray nodesJsonArray = _sceneJson[" nodes" ].toArray ();
206+
207+ // Cycle below replaces the NodeId with the new generated value
208+ // and computes an average position of the old selected node group
209+
210+ QJsonArray newNodesJsonArray;
211+ for (QJsonValueRef node : nodesJsonArray)
212+ {
213+ QJsonObject nodeJson = node.toObject ();
214+
215+ NodeId oldNodeId = nodeJson[" id" ].toInt ();
216+
217+ averagePos +=
218+ QPointF (nodeJson[" position" ].toObject ()[" x" ].toDouble (),
219+ nodeJson[" position" ].toObject ()[" y" ].toDouble ());
220+
221+ NodeId newNodeId = graphModel.newNodeId ();
222+
223+ mapNodeIds[oldNodeId] = newNodeId;
224+
225+ // Replace NodeId in json
226+ nodeJson[" id" ] = static_cast <qint64>(newNodeId);
227+
228+ newNodesJsonArray.append (nodeJson);
229+ }
230+
231+ averagePos /= static_cast <double >(nodesJsonArray.size ());
232+
233+
234+ // The cycle below replaces old NodeIds in connections with the new values
101235
102236 QJsonArray connectionJsonArray = _sceneJson[" connections" ].toArray ();
103237
238+ QJsonArray newConnJsonArray;
104239 for (QJsonValueRef connection : connectionJsonArray)
105240 {
106241 QJsonObject connJson = connection.toObject ();
107- graphModel.deleteConnection (fromJson (connJson));
242+
243+ ConnectionId connId = fromJson (connJson);
244+
245+ ConnectionId newConnId{mapNodeIds[connId.outNodeId ],
246+ connId.outPortIndex ,
247+ mapNodeIds[connId.inNodeId ],
248+ connId.inPortIndex };
249+
250+
251+ newConnJsonArray.append (toJson (newConnId));
252+ }
253+
254+ // Cycle below offsets the new node group to the position of the mouse cursor
255+ QPointF const diff = _mouseScenePos - averagePos;
256+
257+ for (QJsonValueRef node : newNodesJsonArray)
258+ {
259+ QJsonObject obj = node.toObject ();
260+ NodeId const id = obj[" id" ].toInt ();
261+
262+ QPointF oldPos (obj[" position" ].toObject ()[" x" ].toDouble (),
263+ obj[" position" ].toObject ()[" y" ].toDouble ());
264+
265+ oldPos += diff;
266+
267+ QJsonObject posJson;
268+ posJson[" x" ] = oldPos.x ();
269+ posJson[" y" ] = oldPos.y ();
270+ obj[" position" ] = posJson;
271+
272+
273+ graphModel.loadNode (obj);
274+
275+ _scene->nodeGraphicsObject (id)->setZValue (1.0 );
276+ }
277+
278+ for (QJsonValueRef connection : newConnJsonArray)
279+ {
280+ QJsonObject connJson = connection.toObject ();
281+
282+ ConnectionId connId = fromJson (connJson);
283+
284+ // Restore the connection
285+ graphModel.addConnection (connId);
108286 }
287+
288+
289+ _newSceneJson[" nodes" ] = newNodesJsonArray;
290+ _newSceneJson[" connections" ] = newConnJsonArray;
109291}
110292
111293
294+ // -------------------------------------
295+
112296
113297
114298DisconnectCommand::
0 commit comments