123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- #include "UndoCommands.hpp"
- #include "BasicGraphicsScene.hpp"
- #include "ConnectionGraphicsObject.hpp"
- #include "ConnectionIdUtils.hpp"
- #include "Definitions.hpp"
- #include "NodeGraphicsObject.hpp"
- #include <QtCore/QJsonArray>
- #include <QtCore/QJsonDocument>
- #include <QtCore/QMimeData>
- #include <QtGui/QClipboard>
- #include <QtWidgets/QApplication>
- #include <QtWidgets/QGraphicsObject>
- #include <typeinfo>
- namespace QtNodes {
- static QJsonObject serializeSelectedItems(BasicGraphicsScene *scene)
- {
- QJsonObject serializedScene;
- auto &graphModel = scene->graphModel();
- std::unordered_set<NodeId> selectedNodes;
- QJsonArray nodesJsonArray;
- for (QGraphicsItem *item : scene->selectedItems()) {
- if (auto n = qgraphicsitem_cast<NodeGraphicsObject *>(item)) {
- nodesJsonArray.append(graphModel.saveNode(n->nodeId()));
- selectedNodes.insert(n->nodeId());
- }
- }
- QJsonArray connJsonArray;
- for (QGraphicsItem *item : scene->selectedItems()) {
- if (auto c = qgraphicsitem_cast<ConnectionGraphicsObject *>(item)) {
- auto const &cid = c->connectionId();
- if (selectedNodes.count(cid.outNodeId) > 0 && selectedNodes.count(cid.inNodeId) > 0) {
- connJsonArray.append(toJson(cid));
- }
- }
- }
- serializedScene["nodes"] = nodesJsonArray;
- serializedScene["connections"] = connJsonArray;
- return serializedScene;
- }
- static void insertSerializedItems(QJsonObject const &json, BasicGraphicsScene *scene)
- {
- AbstractGraphModel &graphModel = scene->graphModel();
- QJsonArray const &nodesJsonArray = json["nodes"].toArray();
- for (QJsonValue node : nodesJsonArray) {
- QJsonObject obj = node.toObject();
- graphModel.loadNode(obj);
- auto id = obj["id"].toInt();
- scene->nodeGraphicsObject(id)->setZValue(1.0);
- scene->nodeGraphicsObject(id)->setSelected(true);
- }
- QJsonArray const &connJsonArray = json["connections"].toArray();
- for (QJsonValue connection : connJsonArray) {
- QJsonObject connJson = connection.toObject();
- ConnectionId connId = fromJson(connJson);
- // Restore the connection
- graphModel.addConnection(connId);
- scene->connectionGraphicsObject(connId)->setSelected(true);
- }
- }
- static void deleteSerializedItems(QJsonObject &sceneJson, AbstractGraphModel &graphModel)
- {
- QJsonArray connectionJsonArray = sceneJson["connections"].toArray();
- for (QJsonValueRef connection : connectionJsonArray) {
- QJsonObject connJson = connection.toObject();
- ConnectionId connId = fromJson(connJson);
- graphModel.deleteConnection(connId);
- }
- QJsonArray nodesJsonArray = sceneJson["nodes"].toArray();
- for (QJsonValueRef node : nodesJsonArray) {
- QJsonObject nodeJson = node.toObject();
- graphModel.deleteNode(nodeJson["id"].toInt());
- }
- }
- static QPointF computeAverageNodePosition(QJsonObject const &sceneJson)
- {
- QPointF averagePos(0, 0);
- QJsonArray nodesJsonArray = sceneJson["nodes"].toArray();
- for (QJsonValueRef node : nodesJsonArray) {
- QJsonObject nodeJson = node.toObject();
- averagePos += QPointF(nodeJson["position"].toObject()["x"].toDouble(),
- nodeJson["position"].toObject()["y"].toDouble());
- }
- averagePos /= static_cast<double>(nodesJsonArray.size());
- return averagePos;
- }
- //-------------------------------------
- CreateCommand::CreateCommand(BasicGraphicsScene *scene,
- QString const name,
- QPointF const &mouseScenePos)
- : _scene(scene)
- , _sceneJson(QJsonObject())
- {
- _nodeId = _scene->graphModel().addNode(name);
- if (_nodeId != InvalidNodeId) {
- _scene->graphModel().setNodeData(_nodeId, NodeRole::Position, mouseScenePos);
- } else {
- setObsolete(true);
- }
- }
- void CreateCommand::undo()
- {
- QJsonArray nodesJsonArray;
- nodesJsonArray.append(_scene->graphModel().saveNode(_nodeId));
- _sceneJson["nodes"] = nodesJsonArray;
- _scene->graphModel().deleteNode(_nodeId);
- }
- void CreateCommand::redo()
- {
- if (_sceneJson.empty() || _sceneJson["nodes"].toArray().empty())
- return;
- insertSerializedItems(_sceneJson, _scene);
- }
- //-------------------------------------
- DeleteCommand::DeleteCommand(BasicGraphicsScene *scene)
- : _scene(scene)
- {
- auto &graphModel = _scene->graphModel();
- QJsonArray connJsonArray;
- // Delete the selected connections first, ensuring that they won't be
- // automatically deleted when selected nodes are deleted (deleting a
- // node deletes some connections as well)
- for (QGraphicsItem *item : _scene->selectedItems()) {
- if (auto c = qgraphicsitem_cast<ConnectionGraphicsObject *>(item)) {
- auto const &cid = c->connectionId();
- connJsonArray.append(toJson(cid));
- }
- }
- QJsonArray nodesJsonArray;
- // Delete the nodes; this will delete many of the connections.
- // Selected connections were already deleted prior to this loop,
- for (QGraphicsItem *item : _scene->selectedItems()) {
- if (auto n = qgraphicsitem_cast<NodeGraphicsObject *>(item)) {
- // saving connections attached to the selected nodes
- for (auto const &cid : graphModel.allConnectionIds(n->nodeId())) {
- connJsonArray.append(toJson(cid));
- }
- nodesJsonArray.append(graphModel.saveNode(n->nodeId()));
- }
- }
- // If nothing is deleted, cancel this operation
- if (connJsonArray.isEmpty() && nodesJsonArray.isEmpty())
- setObsolete(true);
- _sceneJson["nodes"] = nodesJsonArray;
- _sceneJson["connections"] = connJsonArray;
- }
- void DeleteCommand::undo()
- {
- insertSerializedItems(_sceneJson, _scene);
- }
- void DeleteCommand::redo()
- {
- deleteSerializedItems(_sceneJson, _scene->graphModel());
- }
- //-------------------------------------
- void offsetNodeGroup(QJsonObject &sceneJson, QPointF const &diff)
- {
- QJsonArray nodesJsonArray = sceneJson["nodes"].toArray();
- QJsonArray newNodesJsonArray;
- for (QJsonValueRef node : nodesJsonArray) {
- QJsonObject obj = node.toObject();
- QPointF oldPos(obj["position"].toObject()["x"].toDouble(),
- obj["position"].toObject()["y"].toDouble());
- oldPos += diff;
- QJsonObject posJson;
- posJson["x"] = oldPos.x();
- posJson["y"] = oldPos.y();
- obj["position"] = posJson;
- newNodesJsonArray.append(obj);
- }
- sceneJson["nodes"] = newNodesJsonArray;
- }
- //-------------------------------------
- CopyCommand::CopyCommand(BasicGraphicsScene *scene)
- {
- QJsonObject sceneJson = serializeSelectedItems(scene);
- if (sceneJson.empty() || sceneJson["nodes"].toArray().empty()) {
- setObsolete(true);
- return;
- }
- QClipboard *clipboard = QApplication::clipboard();
- QByteArray const data = QJsonDocument(sceneJson).toJson();
- QMimeData *mimeData = new QMimeData();
- mimeData->setData("application/qt-nodes-graph", data);
- mimeData->setText(data);
- clipboard->setMimeData(mimeData);
- // Copy command does not have any effective redo/undo operations.
- // It copies the data to the clipboard and could be immediately removed
- // from the stack.
- setObsolete(true);
- }
- //-------------------------------------
- PasteCommand::PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos)
- : _scene(scene)
- , _mouseScenePos(mouseScenePos)
- {
- _newSceneJson = takeSceneJsonFromClipboard();
- if (_newSceneJson.empty() || _newSceneJson["nodes"].toArray().empty()) {
- setObsolete(true);
- return;
- }
- _newSceneJson = makeNewNodeIdsInScene(_newSceneJson);
- QPointF averagePos = computeAverageNodePosition(_newSceneJson);
- offsetNodeGroup(_newSceneJson, _mouseScenePos - averagePos);
- }
- void PasteCommand::undo()
- {
- deleteSerializedItems(_newSceneJson, _scene->graphModel());
- }
- void PasteCommand::redo()
- {
- _scene->clearSelection();
- // Ignore if pasted in content does not generate nodes.
- try {
- insertSerializedItems(_newSceneJson, _scene);
- } catch (...) {
- // If the paste does not work, delete all selected nodes and connections
- // `deleteNode(...)` implicitly removed connections
- auto &graphModel = _scene->graphModel();
- QJsonArray nodesJsonArray;
- for (QGraphicsItem *item : _scene->selectedItems()) {
- if (auto n = qgraphicsitem_cast<NodeGraphicsObject *>(item)) {
- graphModel.deleteNode(n->nodeId());
- }
- }
- setObsolete(true);
- }
- }
- QJsonObject PasteCommand::takeSceneJsonFromClipboard()
- {
- QClipboard const *clipboard = QApplication::clipboard();
- QMimeData const *mimeData = clipboard->mimeData();
- QJsonDocument json;
- if (mimeData->hasFormat("application/qt-nodes-graph")) {
- json = QJsonDocument::fromJson(mimeData->data("application/qt-nodes-graph"));
- } else if (mimeData->hasText()) {
- json = QJsonDocument::fromJson(mimeData->text().toUtf8());
- }
- return json.object();
- }
- QJsonObject PasteCommand::makeNewNodeIdsInScene(QJsonObject const &sceneJson)
- {
- AbstractGraphModel &graphModel = _scene->graphModel();
- std::unordered_map<NodeId, NodeId> mapNodeIds;
- QJsonArray nodesJsonArray = sceneJson["nodes"].toArray();
- QJsonArray newNodesJsonArray;
- for (QJsonValueRef node : nodesJsonArray) {
- QJsonObject nodeJson = node.toObject();
- NodeId oldNodeId = nodeJson["id"].toInt();
- NodeId newNodeId = graphModel.newNodeId();
- mapNodeIds[oldNodeId] = newNodeId;
- // Replace NodeId in json
- nodeJson["id"] = static_cast<qint64>(newNodeId);
- newNodesJsonArray.append(nodeJson);
- }
- QJsonArray connectionJsonArray = sceneJson["connections"].toArray();
- QJsonArray newConnJsonArray;
- for (QJsonValueRef connection : connectionJsonArray) {
- QJsonObject connJson = connection.toObject();
- ConnectionId connId = fromJson(connJson);
- ConnectionId newConnId{mapNodeIds[connId.outNodeId],
- connId.outPortIndex,
- mapNodeIds[connId.inNodeId],
- connId.inPortIndex};
- newConnJsonArray.append(toJson(newConnId));
- }
- QJsonObject newSceneJson;
- newSceneJson["nodes"] = newNodesJsonArray;
- newSceneJson["connections"] = newConnJsonArray;
- return newSceneJson;
- }
- //-------------------------------------
- DisconnectCommand::DisconnectCommand(BasicGraphicsScene *scene, ConnectionId const connId)
- : _scene(scene)
- , _connId(connId)
- {
- //
- }
- void DisconnectCommand::undo()
- {
- _scene->graphModel().addConnection(_connId);
- }
- void DisconnectCommand::redo()
- {
- _scene->graphModel().deleteConnection(_connId);
- }
- //------
- ConnectCommand::ConnectCommand(BasicGraphicsScene *scene, ConnectionId const connId)
- : _scene(scene)
- , _connId(connId)
- {
- //
- }
- void ConnectCommand::undo()
- {
- _scene->graphModel().deleteConnection(_connId);
- }
- void ConnectCommand::redo()
- {
- _scene->graphModel().addConnection(_connId);
- }
- //------
- MoveNodeCommand::MoveNodeCommand(BasicGraphicsScene *scene, QPointF const &diff)
- : _scene(scene)
- , _diff(diff)
- {
- _selectedNodes.clear();
- for (QGraphicsItem *item : _scene->selectedItems()) {
- if (auto n = qgraphicsitem_cast<NodeGraphicsObject *>(item)) {
- _selectedNodes.insert(n->nodeId());
- }
- }
- }
- void MoveNodeCommand::undo()
- {
- for (auto nodeId : _selectedNodes) {
- auto oldPos = _scene->graphModel().nodeData(nodeId, NodeRole::Position).value<QPointF>();
- oldPos -= _diff;
- _scene->graphModel().setNodeData(nodeId, NodeRole::Position, oldPos);
- }
- }
- void MoveNodeCommand::redo()
- {
- for (auto nodeId : _selectedNodes) {
- auto oldPos = _scene->graphModel().nodeData(nodeId, NodeRole::Position).value<QPointF>();
- oldPos += _diff;
- _scene->graphModel().setNodeData(nodeId, NodeRole::Position, oldPos);
- }
- }
- int MoveNodeCommand::id() const
- {
- return static_cast<int>(typeid(MoveNodeCommand).hash_code());
- }
- bool MoveNodeCommand::mergeWith(QUndoCommand const *c)
- {
- auto mc = static_cast<MoveNodeCommand const *>(c);
- if (_selectedNodes == mc->_selectedNodes) {
- _diff += mc->_diff;
- return true;
- }
- return false;
- }
- } // namespace QtNodes
|