diff --git a/include/QtNodes/internal/DagGraphicsScene.hpp b/include/QtNodes/internal/DagGraphicsScene.hpp index 7fcaaba9..51a63f89 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 7aed4d60..271520c0 100644 --- a/include/QtNodes/internal/UndoCommands.hpp +++ b/include/QtNodes/internal/UndoCommands.hpp @@ -3,9 +3,10 @@ #include "Definitions.hpp" #include "Export.hpp" +#include +#include #include #include -#include #include @@ -61,11 +62,15 @@ class NODE_EDITOR_PUBLIC PasteCommand : public QUndoCommand private: QJsonObject takeSceneJsonFromClipboard(); QJsonObject makeNewNodeIdsInScene(QJsonObject const &sceneJson); + void copyNodeDataFiles(const QJsonObject &sceneJson, + const QDir &sourceDir, + BasicGraphicsScene *scene); private: BasicGraphicsScene *_scene; QPointF const &_mouseScenePos; QJsonObject _newSceneJson; + 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 94c24e41..262f8435 100644 --- a/src/DagGraphicsScene.cpp +++ b/src/DagGraphicsScene.cpp @@ -202,4 +202,16 @@ void DagGraphicsScene::createNodeAt(const QString &name, const QPointF &pos) this->undoStack().push(new CreateCommand(this, name, pos)); } +QDir DagGraphicsScene::getDataDir() const +{ + if (_dataDir.exists()) + return _dataDir; + return QDir(); +} + +void DagGraphicsScene::setDataDir(QDir const &dir) +{ + _dataDir = dir; +} + } // namespace QtNodes diff --git a/src/UndoCommands.cpp b/src/UndoCommands.cpp index c98289cf..1bfa71c9 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" @@ -13,7 +14,6 @@ #include #include - namespace QtNodes { static QJsonObject serializeSelectedItems(BasicGraphicsScene *scene) @@ -240,10 +240,17 @@ CopyCommand::CopyCommand(BasicGraphicsScene *scene) setObsolete(true); return; } + // For copy-paste data: include the data path (sourceDir) in the scene + QJsonObject wrapper; + wrapper["scene"] = sceneJson; + 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(sceneJson).toJson(); + QByteArray const data = QJsonDocument(wrapper).toJson(); QMimeData *mimeData = new QMimeData(); mimeData->setData("application/qt-nodes-graph", data); @@ -263,7 +270,12 @@ PasteCommand::PasteCommand(BasicGraphicsScene *scene, QPointF const &mouseSceneP : _scene(scene) , _mouseScenePos(mouseScenePos) { - _newSceneJson = takeSceneJsonFromClipboard(); + QJsonObject wrapper = takeSceneJsonFromClipboard(); + // 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()) { setObsolete(true); @@ -288,6 +300,7 @@ void PasteCommand::redo() // Ignore if pasted in content does not generate nodes. try { + copyNodeDataFiles(_newSceneJson, _sourceDataDir, _scene); insertSerializedItems(_newSceneJson, _scene); } catch (...) { // If the paste does not work, delete all selected nodes and connections @@ -305,6 +318,40 @@ 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); + } else { + qWarning() << "Failed to copy file from" << srcPath << "to" << targetPath; + } + } + } +} + QJsonObject PasteCommand::takeSceneJsonFromClipboard() { QClipboard const *clipboard = QApplication::clipboard();