UndoCommands.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. #include "UndoCommands.hpp"
  2. #include "BasicGraphicsScene.hpp"
  3. #include "ConnectionGraphicsObject.hpp"
  4. #include "ConnectionIdUtils.hpp"
  5. #include "Definitions.hpp"
  6. #include "NodeGraphicsObject.hpp"
  7. #include <QtCore/QJsonArray>
  8. #include <QtCore/QJsonDocument>
  9. #include <QtCore/QMimeData>
  10. #include <QtGui/QClipboard>
  11. #include <QtWidgets/QApplication>
  12. #include <QtWidgets/QGraphicsObject>
  13. #include <typeinfo>
  14. namespace QtNodes {
  15. static QJsonObject serializeSelectedItems(BasicGraphicsScene *scene)
  16. {
  17. QJsonObject serializedScene;
  18. auto &graphModel = scene->graphModel();
  19. std::unordered_set<NodeId> selectedNodes;
  20. QJsonArray nodesJsonArray;
  21. for (QGraphicsItem *item : scene->selectedItems()) {
  22. if (auto n = qgraphicsitem_cast<NodeGraphicsObject *>(item)) {
  23. nodesJsonArray.append(graphModel.saveNode(n->nodeId()));
  24. selectedNodes.insert(n->nodeId());
  25. }
  26. }
  27. QJsonArray connJsonArray;
  28. for (QGraphicsItem *item : scene->selectedItems()) {
  29. if (auto c = qgraphicsitem_cast<ConnectionGraphicsObject *>(item)) {
  30. auto const &cid = c->connectionId();
  31. if (selectedNodes.count(cid.outNodeId) > 0 && selectedNodes.count(cid.inNodeId) > 0) {
  32. connJsonArray.append(toJson(cid));
  33. }
  34. }
  35. }
  36. serializedScene["nodes"] = nodesJsonArray;
  37. serializedScene["connections"] = connJsonArray;
  38. return serializedScene;
  39. }
  40. static void insertSerializedItems(QJsonObject const &json, BasicGraphicsScene *scene)
  41. {
  42. AbstractGraphModel &graphModel = scene->graphModel();
  43. QJsonArray const &nodesJsonArray = json["nodes"].toArray();
  44. for (QJsonValue node : nodesJsonArray) {
  45. QJsonObject obj = node.toObject();
  46. graphModel.loadNode(obj);
  47. auto id = obj["id"].toInt();
  48. scene->nodeGraphicsObject(id)->setZValue(1.0);
  49. scene->nodeGraphicsObject(id)->setSelected(true);
  50. }
  51. QJsonArray const &connJsonArray = json["connections"].toArray();
  52. for (QJsonValue connection : connJsonArray) {
  53. QJsonObject connJson = connection.toObject();
  54. ConnectionId connId = fromJson(connJson);
  55. // Restore the connection
  56. graphModel.addConnection(connId);
  57. scene->connectionGraphicsObject(connId)->setSelected(true);
  58. }
  59. }
  60. static void deleteSerializedItems(QJsonObject &sceneJson, AbstractGraphModel &graphModel)
  61. {
  62. QJsonArray connectionJsonArray = sceneJson["connections"].toArray();
  63. for (QJsonValueRef connection : connectionJsonArray) {
  64. QJsonObject connJson = connection.toObject();
  65. ConnectionId connId = fromJson(connJson);
  66. graphModel.deleteConnection(connId);
  67. }
  68. QJsonArray nodesJsonArray = sceneJson["nodes"].toArray();
  69. for (QJsonValueRef node : nodesJsonArray) {
  70. QJsonObject nodeJson = node.toObject();
  71. graphModel.deleteNode(nodeJson["id"].toInt());
  72. }
  73. }
  74. static QPointF computeAverageNodePosition(QJsonObject const &sceneJson)
  75. {
  76. QPointF averagePos(0, 0);
  77. QJsonArray nodesJsonArray = sceneJson["nodes"].toArray();
  78. for (QJsonValueRef node : nodesJsonArray) {
  79. QJsonObject nodeJson = node.toObject();
  80. averagePos += QPointF(nodeJson["position"].toObject()["x"].toDouble(),
  81. nodeJson["position"].toObject()["y"].toDouble());
  82. }
  83. averagePos /= static_cast<double>(nodesJsonArray.size());
  84. return averagePos;
  85. }
  86. //-------------------------------------
  87. CreateCommand::CreateCommand(BasicGraphicsScene *scene,
  88. QString const name,
  89. QPointF const &mouseScenePos)
  90. : _scene(scene)
  91. , _sceneJson(QJsonObject())
  92. {
  93. _nodeId = _scene->graphModel().addNode(name);
  94. if (_nodeId != InvalidNodeId) {
  95. _scene->graphModel().setNodeData(_nodeId, NodeRole::Position, mouseScenePos);
  96. } else {
  97. setObsolete(true);
  98. }
  99. }
  100. void CreateCommand::undo()
  101. {
  102. QJsonArray nodesJsonArray;
  103. nodesJsonArray.append(_scene->graphModel().saveNode(_nodeId));
  104. _sceneJson["nodes"] = nodesJsonArray;
  105. _scene->graphModel().deleteNode(_nodeId);
  106. }
  107. void CreateCommand::redo()
  108. {
  109. if (_sceneJson.empty() || _sceneJson["nodes"].toArray().empty())
  110. return;
  111. insertSerializedItems(_sceneJson, _scene);
  112. }
  113. //-------------------------------------
  114. DeleteCommand::DeleteCommand(BasicGraphicsScene *scene)
  115. : _scene(scene)
  116. {
  117. auto &graphModel = _scene->graphModel();
  118. QJsonArray connJsonArray;
  119. // Delete the selected connections first, ensuring that they won't be
  120. // automatically deleted when selected nodes are deleted (deleting a
  121. // node deletes some connections as well)
  122. for (QGraphicsItem *item : _scene->selectedItems()) {
  123. if (auto c = qgraphicsitem_cast<ConnectionGraphicsObject *>(item)) {
  124. auto const &cid = c->connectionId();
  125. connJsonArray.append(toJson(cid));
  126. }
  127. }
  128. QJsonArray nodesJsonArray;
  129. // Delete the nodes; this will delete many of the connections.
  130. // Selected connections were already deleted prior to this loop,
  131. for (QGraphicsItem *item : _scene->selectedItems()) {
  132. if (auto n = qgraphicsitem_cast<NodeGraphicsObject *>(item)) {
  133. // saving connections attached to the selected nodes
  134. for (auto const &cid : graphModel.allConnectionIds(n->nodeId())) {
  135. connJsonArray.append(toJson(cid));
  136. }
  137. nodesJsonArray.append(graphModel.saveNode(n->nodeId()));
  138. }
  139. }
  140. // If nothing is deleted, cancel this operation
  141. if (connJsonArray.isEmpty() && nodesJsonArray.isEmpty())
  142. setObsolete(true);
  143. _sceneJson["nodes"] = nodesJsonArray;
  144. _sceneJson["connections"] = connJsonArray;
  145. }
  146. void DeleteCommand::undo()
  147. {
  148. insertSerializedItems(_sceneJson, _scene);
  149. }
  150. void DeleteCommand::redo()
  151. {
  152. deleteSerializedItems(_sceneJson, _scene->graphModel());
  153. }
  154. //-------------------------------------
  155. void offsetNodeGroup(QJsonObject &sceneJson, QPointF const &diff)
  156. {
  157. QJsonArray nodesJsonArray = sceneJson["nodes"].toArray();
  158. QJsonArray newNodesJsonArray;
  159. for (QJsonValueRef node : nodesJsonArray) {
  160. QJsonObject obj = node.toObject();
  161. QPointF oldPos(obj["position"].toObject()["x"].toDouble(),
  162. obj["position"].toObject()["y"].toDouble());
  163. oldPos += diff;
  164. QJsonObject posJson;
  165. posJson["x"] = oldPos.x();
  166. posJson["y"] = oldPos.y();
  167. obj["position"] = posJson;
  168. newNodesJsonArray.append(obj);
  169. }
  170. sceneJson["nodes"] = newNodesJsonArray;
  171. }
  172. //-------------------------------------
  173. CopyCommand::CopyCommand(BasicGraphicsScene *scene)
  174. {
  175. QJsonObject sceneJson = serializeSelectedItems(scene);
  176. if (sceneJson.empty() || sceneJson["nodes"].toArray().empty()) {
  177. setObsolete(true);
  178. return;
  179. }
  180. QClipboard *clipboard = QApplication::clipboard();
  181. QByteArray const data = QJsonDocument(sceneJson).toJson();
  182. QMimeData *mimeData = new QMimeData();
  183. mimeData->setData("application/qt-nodes-graph", data);
  184. mimeData->setText(data);
  185. clipboard->setMimeData(mimeData);
  186. // Copy command does not have any effective redo/undo operations.
  187. // It copies the data to the clipboard and could be immediately removed
  188. // from the stack.
  189. setObsolete(true);
  190. }
  191. //-------------------------------------
  192. PasteCommand::PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos)
  193. : _scene(scene)
  194. , _mouseScenePos(mouseScenePos)
  195. {
  196. _newSceneJson = takeSceneJsonFromClipboard();
  197. if (_newSceneJson.empty() || _newSceneJson["nodes"].toArray().empty()) {
  198. setObsolete(true);
  199. return;
  200. }
  201. _newSceneJson = makeNewNodeIdsInScene(_newSceneJson);
  202. QPointF averagePos = computeAverageNodePosition(_newSceneJson);
  203. offsetNodeGroup(_newSceneJson, _mouseScenePos - averagePos);
  204. }
  205. void PasteCommand::undo()
  206. {
  207. deleteSerializedItems(_newSceneJson, _scene->graphModel());
  208. }
  209. void PasteCommand::redo()
  210. {
  211. _scene->clearSelection();
  212. // Ignore if pasted in content does not generate nodes.
  213. try {
  214. insertSerializedItems(_newSceneJson, _scene);
  215. } catch (...) {
  216. // If the paste does not work, delete all selected nodes and connections
  217. // `deleteNode(...)` implicitly removed connections
  218. auto &graphModel = _scene->graphModel();
  219. QJsonArray nodesJsonArray;
  220. for (QGraphicsItem *item : _scene->selectedItems()) {
  221. if (auto n = qgraphicsitem_cast<NodeGraphicsObject *>(item)) {
  222. graphModel.deleteNode(n->nodeId());
  223. }
  224. }
  225. setObsolete(true);
  226. }
  227. }
  228. QJsonObject PasteCommand::takeSceneJsonFromClipboard()
  229. {
  230. QClipboard const *clipboard = QApplication::clipboard();
  231. QMimeData const *mimeData = clipboard->mimeData();
  232. QJsonDocument json;
  233. if (mimeData->hasFormat("application/qt-nodes-graph")) {
  234. json = QJsonDocument::fromJson(mimeData->data("application/qt-nodes-graph"));
  235. } else if (mimeData->hasText()) {
  236. json = QJsonDocument::fromJson(mimeData->text().toUtf8());
  237. }
  238. return json.object();
  239. }
  240. QJsonObject PasteCommand::makeNewNodeIdsInScene(QJsonObject const &sceneJson)
  241. {
  242. AbstractGraphModel &graphModel = _scene->graphModel();
  243. std::unordered_map<NodeId, NodeId> mapNodeIds;
  244. QJsonArray nodesJsonArray = sceneJson["nodes"].toArray();
  245. QJsonArray newNodesJsonArray;
  246. for (QJsonValueRef node : nodesJsonArray) {
  247. QJsonObject nodeJson = node.toObject();
  248. NodeId oldNodeId = nodeJson["id"].toInt();
  249. NodeId newNodeId = graphModel.newNodeId();
  250. mapNodeIds[oldNodeId] = newNodeId;
  251. // Replace NodeId in json
  252. nodeJson["id"] = static_cast<qint64>(newNodeId);
  253. newNodesJsonArray.append(nodeJson);
  254. }
  255. QJsonArray connectionJsonArray = sceneJson["connections"].toArray();
  256. QJsonArray newConnJsonArray;
  257. for (QJsonValueRef connection : connectionJsonArray) {
  258. QJsonObject connJson = connection.toObject();
  259. ConnectionId connId = fromJson(connJson);
  260. ConnectionId newConnId{mapNodeIds[connId.outNodeId],
  261. connId.outPortIndex,
  262. mapNodeIds[connId.inNodeId],
  263. connId.inPortIndex};
  264. newConnJsonArray.append(toJson(newConnId));
  265. }
  266. QJsonObject newSceneJson;
  267. newSceneJson["nodes"] = newNodesJsonArray;
  268. newSceneJson["connections"] = newConnJsonArray;
  269. return newSceneJson;
  270. }
  271. //-------------------------------------
  272. DisconnectCommand::DisconnectCommand(BasicGraphicsScene *scene, ConnectionId const connId)
  273. : _scene(scene)
  274. , _connId(connId)
  275. {
  276. //
  277. }
  278. void DisconnectCommand::undo()
  279. {
  280. _scene->graphModel().addConnection(_connId);
  281. }
  282. void DisconnectCommand::redo()
  283. {
  284. _scene->graphModel().deleteConnection(_connId);
  285. }
  286. //------
  287. ConnectCommand::ConnectCommand(BasicGraphicsScene *scene, ConnectionId const connId)
  288. : _scene(scene)
  289. , _connId(connId)
  290. {
  291. //
  292. }
  293. void ConnectCommand::undo()
  294. {
  295. _scene->graphModel().deleteConnection(_connId);
  296. }
  297. void ConnectCommand::redo()
  298. {
  299. _scene->graphModel().addConnection(_connId);
  300. }
  301. //------
  302. MoveNodeCommand::MoveNodeCommand(BasicGraphicsScene *scene, QPointF const &diff)
  303. : _scene(scene)
  304. , _diff(diff)
  305. {
  306. _selectedNodes.clear();
  307. for (QGraphicsItem *item : _scene->selectedItems()) {
  308. if (auto n = qgraphicsitem_cast<NodeGraphicsObject *>(item)) {
  309. _selectedNodes.insert(n->nodeId());
  310. }
  311. }
  312. }
  313. void MoveNodeCommand::undo()
  314. {
  315. for (auto nodeId : _selectedNodes) {
  316. auto oldPos = _scene->graphModel().nodeData(nodeId, NodeRole::Position).value<QPointF>();
  317. oldPos -= _diff;
  318. _scene->graphModel().setNodeData(nodeId, NodeRole::Position, oldPos);
  319. }
  320. }
  321. void MoveNodeCommand::redo()
  322. {
  323. for (auto nodeId : _selectedNodes) {
  324. auto oldPos = _scene->graphModel().nodeData(nodeId, NodeRole::Position).value<QPointF>();
  325. oldPos += _diff;
  326. _scene->graphModel().setNodeData(nodeId, NodeRole::Position, oldPos);
  327. }
  328. }
  329. int MoveNodeCommand::id() const
  330. {
  331. return static_cast<int>(typeid(MoveNodeCommand).hash_code());
  332. }
  333. bool MoveNodeCommand::mergeWith(QUndoCommand const *c)
  334. {
  335. auto mc = static_cast<MoveNodeCommand const *>(c);
  336. if (_selectedNodes == mc->_selectedNodes) {
  337. _diff += mc->_diff;
  338. return true;
  339. }
  340. return false;
  341. }
  342. } // namespace QtNodes