From 2c82f37751bf7c53c4cb793da9bfff534fb8e392 Mon Sep 17 00:00:00 2001 From: Shweta Menon Date: Thu, 19 Feb 2026 18:20:00 +0800 Subject: [PATCH 1/6] Copy Paste Src file - Only nodes and connections were copied earlier. Added copy paste of data and fn src files also. - Fixes bug. --- include/QtNodes/internal/DagGraphicsScene.hpp | 4 ++ include/QtNodes/internal/UndoCommands.hpp | 10 ++++- src/DagGraphicsScene.cpp | 10 +++++ src/GraphicsView.cpp | 18 ++++++-- src/UndoCommands.cpp | 42 ++++++++++++++++--- 5 files changed, 73 insertions(+), 11 deletions(-) diff --git a/include/QtNodes/internal/DagGraphicsScene.hpp b/include/QtNodes/internal/DagGraphicsScene.hpp index 7fcaaba91..51a63f898 100644 --- a/include/QtNodes/internal/DagGraphicsScene.hpp +++ b/include/QtNodes/internal/DagGraphicsScene.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include "BasicGraphicsScene.hpp" #include "DirectedAcyclicGraphModel.hpp" @@ -23,6 +24,8 @@ class NODE_EDITOR_PUBLIC DagGraphicsScene : public BasicGraphicsScene QMenu *createSceneMenu(QPointF const scenePos) override; bool isEmpty() const { return _graphModel.isEmpty(); } bool isBlank() const { return _graphModel.isEmpty(); } + QDir getDataDir() const; + void setDataDir(QDir const &dir); public Q_SLOTS: bool save(const QString &filePath, const QJsonObject &metadata = {}) const; @@ -34,6 +37,7 @@ public Q_SLOTS: private: DirectedAcyclicGraphModel &_graphModel; + QDir _dataDir; // store current tab's data dir }; } // namespace QtNodes diff --git a/include/QtNodes/internal/UndoCommands.hpp b/include/QtNodes/internal/UndoCommands.hpp index 7aed4d60b..e5052e825 100644 --- a/include/QtNodes/internal/UndoCommands.hpp +++ b/include/QtNodes/internal/UndoCommands.hpp @@ -3,6 +3,7 @@ #include "Definitions.hpp" #include "Export.hpp" +#include #include #include #include @@ -47,13 +48,17 @@ class NODE_EDITOR_PUBLIC DeleteCommand : public QUndoCommand class NODE_EDITOR_PUBLIC CopyCommand : public QUndoCommand { public: - CopyCommand(BasicGraphicsScene *scene); + CopyCommand(BasicGraphicsScene *scene, QDir sourceDataDir); + QDir sourceDataDir() const { return _sourceDataDir; } + +private: + QDir _sourceDataDir; // store source data dir }; class NODE_EDITOR_PUBLIC PasteCommand : public QUndoCommand { public: - PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos); + PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos, QDir const &sourceDataDir); void undo() override; void redo() override; @@ -66,6 +71,7 @@ class NODE_EDITOR_PUBLIC PasteCommand : public QUndoCommand BasicGraphicsScene *_scene; QPointF const &_mouseScenePos; QJsonObject _newSceneJson; + QDir _sourceDataDir; // store source data dir }; class NODE_EDITOR_PUBLIC DisconnectCommand : public QUndoCommand diff --git a/src/DagGraphicsScene.cpp b/src/DagGraphicsScene.cpp index 94c24e41a..e251ab1f4 100644 --- a/src/DagGraphicsScene.cpp +++ b/src/DagGraphicsScene.cpp @@ -202,4 +202,14 @@ void DagGraphicsScene::createNodeAt(const QString &name, const QPointF &pos) this->undoStack().push(new CreateCommand(this, name, pos)); } +QDir DagGraphicsScene::getDataDir() const +{ + return _dataDir; +} + +void DagGraphicsScene::setDataDir(QDir const &dir) +{ + _dataDir = dir; +} + } // namespace QtNodes diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index c587f081c..5198d1ce0 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -2,6 +2,7 @@ #include "BasicGraphicsScene.hpp" #include "ConnectionGraphicsObject.hpp" +#include "DagGraphicsScene.hpp" #include "NodeGraphicsObject.hpp" #include "StyleCollection.hpp" #include "UndoCommands.hpp" @@ -21,6 +22,7 @@ #include #include +#include using QtNodes::BasicGraphicsScene; using QtNodes::GraphicsView; @@ -302,15 +304,23 @@ void GraphicsView::onDuplicateSelectedObjects() QPointF const pastePosition = scenePastePosition(); - nodeScene()->undoStack().push(new CopyCommand(nodeScene())); - nodeScene()->undoStack().push(new PasteCommand(nodeScene(), pastePosition)); + QDir sourceDir; + if (auto dagScene = dynamic_cast(nodeScene())) { + sourceDir = dagScene->getDataDir(); + } + nodeScene()->undoStack().push(new CopyCommand(nodeScene(), sourceDir)); + nodeScene()->undoStack().push(new PasteCommand(nodeScene(), pastePosition, QDir())); } void GraphicsView::onCopySelectedObjects() { if (!nodeScene()) return; - nodeScene()->undoStack().push(new CopyCommand(nodeScene())); + QDir sourceDir; + if (auto dagScene = dynamic_cast(nodeScene())) { + sourceDir = dagScene->getDataDir(); + } + nodeScene()->undoStack().push(new CopyCommand(nodeScene(), sourceDir)); } void GraphicsView::onPasteObjects() @@ -318,7 +328,7 @@ void GraphicsView::onPasteObjects() if (!nodeScene()) return; QPointF const pastePosition = scenePastePosition(); - nodeScene()->undoStack().push(new PasteCommand(nodeScene(), pastePosition)); + nodeScene()->undoStack().push(new PasteCommand(nodeScene(), pastePosition, QDir())); } void GraphicsView::keyPressEvent(QKeyEvent *event) diff --git a/src/UndoCommands.cpp b/src/UndoCommands.cpp index c98289cf6..986697a1a 100644 --- a/src/UndoCommands.cpp +++ b/src/UndoCommands.cpp @@ -3,6 +3,7 @@ #include "BasicGraphicsScene.hpp" #include "ConnectionGraphicsObject.hpp" #include "ConnectionIdUtils.hpp" +#include "DagGraphicsScene.hpp" #include "Definitions.hpp" #include "NodeGraphicsObject.hpp" @@ -232,7 +233,8 @@ void offsetNodeGroup(QJsonObject &sceneJson, QPointF const &diff) //------------------------------------- -CopyCommand::CopyCommand(BasicGraphicsScene *scene) +CopyCommand::CopyCommand(BasicGraphicsScene *scene, QDir sourceDataDir) + : _sourceDataDir(sourceDataDir) { QJsonObject sceneJson = serializeSelectedItems(scene); @@ -240,10 +242,13 @@ CopyCommand::CopyCommand(BasicGraphicsScene *scene) setObsolete(true); return; } + // Wrap scene and sourceDir + QJsonObject wrapper; + wrapper["scene"] = sceneJson; + wrapper["sourceDir"] = _sourceDataDir.absolutePath(); QClipboard *clipboard = QApplication::clipboard(); - - QByteArray const data = QJsonDocument(sceneJson).toJson(); + QByteArray const data = QJsonDocument(wrapper).toJson(); QMimeData *mimeData = new QMimeData(); mimeData->setData("application/qt-nodes-graph", data); @@ -259,11 +264,13 @@ CopyCommand::CopyCommand(BasicGraphicsScene *scene) //------------------------------------- -PasteCommand::PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos) +PasteCommand::PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos, QDir const &) : _scene(scene) , _mouseScenePos(mouseScenePos) { - _newSceneJson = takeSceneJsonFromClipboard(); + QJsonObject wrapper = takeSceneJsonFromClipboard(); + _sourceDataDir = QDir(wrapper["sourceDir"].toString()); + _newSceneJson = wrapper["scene"].toObject(); if (_newSceneJson.empty() || _newSceneJson["nodes"].toArray().empty()) { setObsolete(true); @@ -288,6 +295,31 @@ void PasteCommand::redo() // Ignore if pasted in content does not generate nodes. try { + QDir targetDir; + if (auto dagScene = dynamic_cast(_scene)) { + targetDir = dagScene->getDataDir(); + } + + QJsonArray nodesJsonArray = _newSceneJson["nodes"].toArray(); + for (auto nodeVal : nodesJsonArray) { + QJsonObject obj = nodeVal.toObject(); + QJsonObject internalData = obj["internal-data"].toObject(); + + QString fileName; + if (internalData.contains("data-name")) + fileName = internalData["data-name"].toString(); + else if (internalData.contains("saved_function")) + fileName = internalData["saved_function"].toString(); + + if (!fileName.isEmpty()) { + QString srcPath = _sourceDataDir.filePath(fileName); + QString targetPath = targetDir.filePath(fileName); + + if (QFile::exists(srcPath) && !QFile::exists(targetPath)) { + QFile::copy(srcPath, targetPath); + } + } + } insertSerializedItems(_newSceneJson, _scene); } catch (...) { // If the paste does not work, delete all selected nodes and connections From a0bb274300da74e043f7fdd460a7e548ec30cca7 Mon Sep 17 00:00:00 2001 From: Shweta Menon Date: Thu, 19 Feb 2026 19:24:48 +0800 Subject: [PATCH 2/6] Refactoring --- include/QtNodes/internal/UndoCommands.hpp | 10 +++------- src/DagGraphicsScene.cpp | 4 +++- src/GraphicsView.cpp | 18 ++++-------------- src/UndoCommands.cpp | 16 +++++++++++----- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/include/QtNodes/internal/UndoCommands.hpp b/include/QtNodes/internal/UndoCommands.hpp index e5052e825..dc7fb79c2 100644 --- a/include/QtNodes/internal/UndoCommands.hpp +++ b/include/QtNodes/internal/UndoCommands.hpp @@ -48,17 +48,13 @@ class NODE_EDITOR_PUBLIC DeleteCommand : public QUndoCommand class NODE_EDITOR_PUBLIC CopyCommand : public QUndoCommand { public: - CopyCommand(BasicGraphicsScene *scene, QDir sourceDataDir); - QDir sourceDataDir() const { return _sourceDataDir; } - -private: - QDir _sourceDataDir; // store source data dir + CopyCommand(BasicGraphicsScene *scene); }; class NODE_EDITOR_PUBLIC PasteCommand : public QUndoCommand { public: - PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos, QDir const &sourceDataDir); + PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos); void undo() override; void redo() override; @@ -71,7 +67,7 @@ class NODE_EDITOR_PUBLIC PasteCommand : public QUndoCommand BasicGraphicsScene *_scene; QPointF const &_mouseScenePos; QJsonObject _newSceneJson; - QDir _sourceDataDir; // store source data dir + QDir _sourceDataDir; // source data dir for paste operation }; class NODE_EDITOR_PUBLIC DisconnectCommand : public QUndoCommand diff --git a/src/DagGraphicsScene.cpp b/src/DagGraphicsScene.cpp index e251ab1f4..262f84355 100644 --- a/src/DagGraphicsScene.cpp +++ b/src/DagGraphicsScene.cpp @@ -204,7 +204,9 @@ void DagGraphicsScene::createNodeAt(const QString &name, const QPointF &pos) QDir DagGraphicsScene::getDataDir() const { - return _dataDir; + if (_dataDir.exists()) + return _dataDir; + return QDir(); } void DagGraphicsScene::setDataDir(QDir const &dir) diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index 5198d1ce0..f65bc5c4c 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -303,24 +303,14 @@ void GraphicsView::onDuplicateSelectedObjects() if (!nodeScene()) return; QPointF const pastePosition = scenePastePosition(); - - QDir sourceDir; - if (auto dagScene = dynamic_cast(nodeScene())) { - sourceDir = dagScene->getDataDir(); - } - nodeScene()->undoStack().push(new CopyCommand(nodeScene(), sourceDir)); - nodeScene()->undoStack().push(new PasteCommand(nodeScene(), pastePosition, QDir())); + nodeScene()->undoStack().push(new CopyCommand(nodeScene())); + nodeScene()->undoStack().push(new PasteCommand(nodeScene(), pastePosition)); } void GraphicsView::onCopySelectedObjects() { if (!nodeScene()) return; - - QDir sourceDir; - if (auto dagScene = dynamic_cast(nodeScene())) { - sourceDir = dagScene->getDataDir(); - } - nodeScene()->undoStack().push(new CopyCommand(nodeScene(), sourceDir)); + nodeScene()->undoStack().push(new CopyCommand(nodeScene())); } void GraphicsView::onPasteObjects() @@ -328,7 +318,7 @@ void GraphicsView::onPasteObjects() if (!nodeScene()) return; QPointF const pastePosition = scenePastePosition(); - nodeScene()->undoStack().push(new PasteCommand(nodeScene(), pastePosition, QDir())); + nodeScene()->undoStack().push(new PasteCommand(nodeScene(), pastePosition)); } void GraphicsView::keyPressEvent(QKeyEvent *event) diff --git a/src/UndoCommands.cpp b/src/UndoCommands.cpp index 986697a1a..2a5bdbbdd 100644 --- a/src/UndoCommands.cpp +++ b/src/UndoCommands.cpp @@ -233,8 +233,7 @@ void offsetNodeGroup(QJsonObject &sceneJson, QPointF const &diff) //------------------------------------- -CopyCommand::CopyCommand(BasicGraphicsScene *scene, QDir sourceDataDir) - : _sourceDataDir(sourceDataDir) +CopyCommand::CopyCommand(BasicGraphicsScene *scene) { QJsonObject sceneJson = serializeSelectedItems(scene); @@ -245,7 +244,10 @@ CopyCommand::CopyCommand(BasicGraphicsScene *scene, QDir sourceDataDir) // Wrap scene and sourceDir QJsonObject wrapper; wrapper["scene"] = sceneJson; - wrapper["sourceDir"] = _sourceDataDir.absolutePath(); + if (auto dagScene = dynamic_cast(scene)) { + if (auto srcDir = dagScene->getDataDir(); srcDir.exists()) + wrapper["sourceDir"] = srcDir.absolutePath(); + } QClipboard *clipboard = QApplication::clipboard(); QByteArray const data = QJsonDocument(wrapper).toJson(); @@ -264,12 +266,15 @@ CopyCommand::CopyCommand(BasicGraphicsScene *scene, QDir sourceDataDir) //------------------------------------- -PasteCommand::PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos, QDir const &) +PasteCommand::PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseScenePos) : _scene(scene) , _mouseScenePos(mouseScenePos) { QJsonObject wrapper = takeSceneJsonFromClipboard(); - _sourceDataDir = QDir(wrapper["sourceDir"].toString()); + // check if sourcDir key exists in wrapper + _sourceDataDir = QDir(); + if (wrapper.contains("sourceDir")) + _sourceDataDir = QDir(wrapper["sourceDir"].toString()); _newSceneJson = wrapper["scene"].toObject(); if (_newSceneJson.empty() || _newSceneJson["nodes"].toArray().empty()) { @@ -305,6 +310,7 @@ void PasteCommand::redo() QJsonObject obj = nodeVal.toObject(); QJsonObject internalData = obj["internal-data"].toObject(); + // check if data/function source model exists in the scene, to copy the files QString fileName; if (internalData.contains("data-name")) fileName = internalData["data-name"].toString(); From c91c7ec16b1e091654a80caea5c726aaedf2c22d Mon Sep 17 00:00:00 2001 From: Shweta Menon Date: Thu, 19 Feb 2026 19:33:20 +0800 Subject: [PATCH 3/6] Removed unnecessary changes --- src/GraphicsView.cpp | 4 ++-- src/UndoCommands.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/GraphicsView.cpp b/src/GraphicsView.cpp index f65bc5c4c..c587f081c 100644 --- a/src/GraphicsView.cpp +++ b/src/GraphicsView.cpp @@ -2,7 +2,6 @@ #include "BasicGraphicsScene.hpp" #include "ConnectionGraphicsObject.hpp" -#include "DagGraphicsScene.hpp" #include "NodeGraphicsObject.hpp" #include "StyleCollection.hpp" #include "UndoCommands.hpp" @@ -22,7 +21,6 @@ #include #include -#include using QtNodes::BasicGraphicsScene; using QtNodes::GraphicsView; @@ -303,6 +301,7 @@ void GraphicsView::onDuplicateSelectedObjects() if (!nodeScene()) return; QPointF const pastePosition = scenePastePosition(); + nodeScene()->undoStack().push(new CopyCommand(nodeScene())); nodeScene()->undoStack().push(new PasteCommand(nodeScene(), pastePosition)); } @@ -310,6 +309,7 @@ void GraphicsView::onDuplicateSelectedObjects() void GraphicsView::onCopySelectedObjects() { if (!nodeScene()) return; + nodeScene()->undoStack().push(new CopyCommand(nodeScene())); } diff --git a/src/UndoCommands.cpp b/src/UndoCommands.cpp index 2a5bdbbdd..a592de10b 100644 --- a/src/UndoCommands.cpp +++ b/src/UndoCommands.cpp @@ -250,6 +250,7 @@ CopyCommand::CopyCommand(BasicGraphicsScene *scene) } QClipboard *clipboard = QApplication::clipboard(); + QByteArray const data = QJsonDocument(wrapper).toJson(); QMimeData *mimeData = new QMimeData(); From dd0db64aec226fa84a9355b3ff6203d61e126cbd Mon Sep 17 00:00:00 2001 From: Shweta Menon Date: Tue, 24 Feb 2026 12:26:10 +0800 Subject: [PATCH 4/6] Cleanup --- include/QtNodes/internal/UndoCommands.hpp | 5 +- src/UndoCommands.cpp | 62 +++++++++++++---------- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/include/QtNodes/internal/UndoCommands.hpp b/include/QtNodes/internal/UndoCommands.hpp index dc7fb79c2..9c5362b80 100644 --- a/include/QtNodes/internal/UndoCommands.hpp +++ b/include/QtNodes/internal/UndoCommands.hpp @@ -4,9 +4,9 @@ #include "Export.hpp" #include +#include #include #include -#include #include @@ -58,6 +58,9 @@ class NODE_EDITOR_PUBLIC PasteCommand : public QUndoCommand void undo() override; void redo() override; + void copyNodeDataFiles(const QJsonObject &sceneJson, + const QDir &sourceDir, + BasicGraphicsScene *scene); private: QJsonObject takeSceneJsonFromClipboard(); diff --git a/src/UndoCommands.cpp b/src/UndoCommands.cpp index a592de10b..5bb4556bb 100644 --- a/src/UndoCommands.cpp +++ b/src/UndoCommands.cpp @@ -14,7 +14,6 @@ #include #include - namespace QtNodes { static QJsonObject serializeSelectedItems(BasicGraphicsScene *scene) @@ -241,7 +240,7 @@ CopyCommand::CopyCommand(BasicGraphicsScene *scene) setObsolete(true); return; } - // Wrap scene and sourceDir + // For copy-paste data: include the data path (sourceDir) in the scene QJsonObject wrapper; wrapper["scene"] = sceneJson; if (auto dagScene = dynamic_cast(scene)) { @@ -301,32 +300,7 @@ void PasteCommand::redo() // Ignore if pasted in content does not generate nodes. try { - QDir targetDir; - if (auto dagScene = dynamic_cast(_scene)) { - targetDir = dagScene->getDataDir(); - } - - QJsonArray nodesJsonArray = _newSceneJson["nodes"].toArray(); - for (auto nodeVal : nodesJsonArray) { - QJsonObject obj = nodeVal.toObject(); - QJsonObject internalData = obj["internal-data"].toObject(); - - // check if data/function source model exists in the scene, to copy the files - QString fileName; - if (internalData.contains("data-name")) - fileName = internalData["data-name"].toString(); - else if (internalData.contains("saved_function")) - fileName = internalData["saved_function"].toString(); - - if (!fileName.isEmpty()) { - QString srcPath = _sourceDataDir.filePath(fileName); - QString targetPath = targetDir.filePath(fileName); - - if (QFile::exists(srcPath) && !QFile::exists(targetPath)) { - QFile::copy(srcPath, targetPath); - } - } - } + copyNodeDataFiles(_newSceneJson, _sourceDataDir, _scene); insertSerializedItems(_newSceneJson, _scene); } catch (...) { // If the paste does not work, delete all selected nodes and connections @@ -344,6 +318,38 @@ void PasteCommand::redo() } } +void PasteCommand::copyNodeDataFiles(const QJsonObject &sceneJson, + const QDir &sourceDir, + BasicGraphicsScene *scene) +{ + QDir targetDir; + if (auto dagScene = dynamic_cast(scene)) { + targetDir = dagScene->getDataDir(); + } + + QJsonArray nodesJsonArray = sceneJson["nodes"].toArray(); + for (const auto &nodeVal : nodesJsonArray) { + QJsonObject obj = nodeVal.toObject(); + QJsonObject internalData = obj["internal-data"].toObject(); + + // check if data/function source model exists in the scene, to copy the files + QString fileName; + if (internalData.contains("data-name")) + fileName = internalData["data-name"].toString(); + else if (internalData.contains("saved_function")) + fileName = internalData["saved_function"].toString(); + + if (!fileName.isEmpty()) { + QString srcPath = sourceDir.filePath(fileName); + QString targetPath = targetDir.filePath(fileName); + + if (QFile::exists(srcPath) && !QFile::exists(targetPath)) { + QFile::copy(srcPath, targetPath); + } + } + } +} + QJsonObject PasteCommand::takeSceneJsonFromClipboard() { QClipboard const *clipboard = QApplication::clipboard(); From 77877534b4c59a4b1161247fb23bdc692b1bc783 Mon Sep 17 00:00:00 2001 From: Shweta Menon Date: Tue, 24 Feb 2026 13:39:26 +0800 Subject: [PATCH 5/6] Update - make function private --- include/QtNodes/internal/UndoCommands.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/QtNodes/internal/UndoCommands.hpp b/include/QtNodes/internal/UndoCommands.hpp index 9c5362b80..271520c01 100644 --- a/include/QtNodes/internal/UndoCommands.hpp +++ b/include/QtNodes/internal/UndoCommands.hpp @@ -58,13 +58,13 @@ class NODE_EDITOR_PUBLIC PasteCommand : public QUndoCommand void undo() override; void redo() override; - void copyNodeDataFiles(const QJsonObject &sceneJson, - const QDir &sourceDir, - BasicGraphicsScene *scene); private: QJsonObject takeSceneJsonFromClipboard(); QJsonObject makeNewNodeIdsInScene(QJsonObject const &sceneJson); + void copyNodeDataFiles(const QJsonObject &sceneJson, + const QDir &sourceDir, + BasicGraphicsScene *scene); private: BasicGraphicsScene *_scene; From 1f32b8ef195bf982021d47dd9908ed8f8ca4eb8c Mon Sep 17 00:00:00 2001 From: Shweta Menon Date: Tue, 24 Feb 2026 13:42:21 +0800 Subject: [PATCH 6/6] Add warning comment --- src/UndoCommands.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/UndoCommands.cpp b/src/UndoCommands.cpp index 5bb4556bb..1bfa71c93 100644 --- a/src/UndoCommands.cpp +++ b/src/UndoCommands.cpp @@ -345,6 +345,8 @@ void PasteCommand::copyNodeDataFiles(const QJsonObject &sceneJson, if (QFile::exists(srcPath) && !QFile::exists(targetPath)) { QFile::copy(srcPath, targetPath); + } else { + qWarning() << "Failed to copy file from" << srcPath << "to" << targetPath; } } }