diff --git a/app/node/project/serializer/serializer.cpp b/app/node/project/serializer/serializer.cpp index bc85d221c..da76834f0 100644 --- a/app/node/project/serializer/serializer.cpp +++ b/app/node/project/serializer/serializer.cpp @@ -136,7 +136,7 @@ ProjectSerializer::Result ProjectSerializer::Load(Project *project, QXmlStreamRe return res; } -ProjectSerializer::Result ProjectSerializer::Paste(LoadType load_type) +ProjectSerializer::Result ProjectSerializer::Paste(LoadType load_type, Project *project) { QString clipboard = Core::PasteStringFromClipboard(); if (clipboard.isEmpty()) { @@ -145,7 +145,7 @@ ProjectSerializer::Result ProjectSerializer::Paste(LoadType load_type) QXmlStreamReader reader(clipboard); - return ProjectSerializer::Load(nullptr, &reader, load_type); + return ProjectSerializer::Load(project, &reader, load_type); } ProjectSerializer::Result ProjectSerializer::Save(const SaveData &data, bool compress) diff --git a/app/node/project/serializer/serializer.h b/app/node/project/serializer/serializer.h index 6f3212b0f..45cd89517 100644 --- a/app/node/project/serializer/serializer.h +++ b/app/node/project/serializer/serializer.h @@ -81,6 +81,8 @@ public: QVector nodes; + Node::OutputConnections promised_connections; + }; class Result @@ -171,7 +173,7 @@ public: static Result Load(Project *project, const QString &filename, LoadType load_type); static Result Load(Project *project, QXmlStreamReader *read_device, LoadType load_type); - static Result Paste(LoadType load_type); + static Result Paste(LoadType load_type, Project *project = nullptr); static Result Save(const SaveData &data, bool compress); static Result Save(QXmlStreamWriter *write_device, const SaveData &data); diff --git a/app/node/project/serializer/serializer230220.cpp b/app/node/project/serializer/serializer230220.cpp index e312b5515..71a55ffd4 100644 --- a/app/node/project/serializer/serializer230220.cpp +++ b/app/node/project/serializer/serializer230220.cpp @@ -175,13 +175,25 @@ ProjectSerializer230220::LoadData ProjectSerializer230220::Load(Project *project case kOnlyNodes: { if (reader->name() == QStringLiteral("nodes")) { + QMap skipped_items; + while (XMLReadNextStartElement(reader)) { if (reader->name() == QStringLiteral("node")) { QString id; + quintptr ptr = 0; + QVector items; XMLAttributeLoop(reader, attr) { if (attr.name() == QStringLiteral("id")) { id = attr.value().toString(); + } else if (attr.name() == QStringLiteral("ptr")) { + ptr = attr.value().toULongLong(); + } else if (attr.name() == QStringLiteral("items")) { + QVector l = attr.value().split(','); + items.reserve(l.size()); + for (const QStringRef &s : l) { + items.append(s.toULongLong()); + } } } @@ -189,15 +201,49 @@ ProjectSerializer230220::LoadData ProjectSerializer230220::Load(Project *project qWarning() << "Failed to load node with empty ID"; reader->skipCurrentElement(); } else { - Node* node = NodeFactory::CreateFromID(id); - if (!node) { - qWarning() << "Failed to find node with ID" << id; + bool dependency_of_item = false; + + if (project && !items.empty()) { + for (quintptr p : items) { + if (project->nodes().contains(reinterpret_cast(p))) { + dependency_of_item = true; + break; + } + } + } + + if (dependency_of_item) { reader->skipCurrentElement(); } else { - // Disable cache while node is being loaded (we'll re-enable it later) - node->SetCachesEnabled(false); - node->Load(reader, &project_data); - load_data.nodes.append(node); + Node* node = NodeFactory::CreateFromID(id); + if (!node) { + qWarning() << "Failed to find node with ID" << id; + reader->skipCurrentElement(); + } else { + if (project && node->IsItem() && ptr) { + // If we're pasting an object into the same project, we should re-use the item + // rather than duplicate. + Node *existing = reinterpret_cast(ptr); + if (project->nodes().contains(existing)) { + // Connect this + skipped_items.insert(ptr, existing); + + // Don't continue loading this + delete node; + node = nullptr; + + // Skip element + reader->skipCurrentElement(); + } + } + + if (node) { + // Disable cache while node is being loaded (we'll re-enable it later) + node->SetCachesEnabled(false); + node->Load(reader, &project_data); + load_data.nodes.append(node); + } + } } } } else if (reader->name() == QStringLiteral("properties")) { @@ -230,6 +276,21 @@ ProjectSerializer230220::LoadData ProjectSerializer230220::Load(Project *project } } + if (!skipped_items.empty()) { + for (auto it = project_data.desired_connections.begin(); it != project_data.desired_connections.end(); ) { + const SerializedData::SerializedConnection &sc = *it; + + if (Node *si = skipped_items.value(sc.output_node)) { + // Convert this to a promised connection + Node::OutputConnection oc = {si, sc.input}; + load_data.promised_connections.push_back(oc); + it = project_data.desired_connections.erase(it); + } else { + it++; + } + } + } + PostConnect(load_data.nodes, &project_data); // Resolve serialized properties (if any) @@ -249,6 +310,21 @@ ProjectSerializer230220::LoadData ProjectSerializer230220::Load(Project *project return load_data; } +void WriteNodeMap(QXmlStreamWriter *writer, Node *node, const QVector &nodes) +{ + writer->writeStartElement(QStringLiteral("node")); + + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(node))); + + for (auto oc : node->output_connections()) { + if (nodes.contains(oc.second.node())) { + WriteNodeMap(writer, oc.second.node(), nodes); + } + } + + writer->writeEndElement(); +} + void ProjectSerializer230220::Save(QXmlStreamWriter *writer, const SaveData &data, void *reserved) const { if (!data.GetOnlySerializeMarkers().empty()) { @@ -325,8 +401,19 @@ void ProjectSerializer230220::Save(QXmlStreamWriter *writer, const SaveData &dat for (Node *n : data.GetOnlySerializeNodes()) { writer->writeStartElement(QStringLiteral("node")); + + QStringList item_list; + for (Node *i : data.GetOnlySerializeNodes()) { + if (i->IsItem() && i->InputsFrom(n, true)) { + item_list.append(QString::number(reinterpret_cast(i))); + } + } + if (!item_list.empty()) { + writer->writeAttribute(QStringLiteral("items"), item_list.join(',')); + } + n->Save(writer); - writer->writeEndElement(); + writer->writeEndElement(); // node } if (!data.GetProperties().empty()) { diff --git a/app/node/serializeddata.h b/app/node/serializeddata.h index d6bad8553..10ef7f132 100644 --- a/app/node/serializeddata.h +++ b/app/node/serializeddata.h @@ -60,7 +60,6 @@ struct SerializedData { QList block_links; QVector group_input_links; QHash group_output_links; - QHash node_uuids; }; diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 5aa06ebe5..955281e27 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1938,7 +1938,7 @@ bool TimelineWidget::PasteInternal(bool insert) return false; } - ProjectSerializer::Result res = ProjectSerializer::Paste(ProjectSerializer::kOnlyNodes); + ProjectSerializer::Result res = ProjectSerializer::Paste(ProjectSerializer::kOnlyNodes, GetConnectedNode()->project()); if (res.GetLoadData().nodes.isEmpty()) { return false; } @@ -1953,6 +1953,13 @@ bool TimelineWidget::PasteInternal(bool insert) } } + qDebug() << "pasing" << res.GetLoadData().nodes.size() << "nodes"; + + for (auto it = res.GetLoadData().promised_connections.cbegin(); it != res.GetLoadData().promised_connections.cend(); it++) { + auto oc = *it; + command->add_child(new NodeEdgeAddCommand(oc.first, oc.second)); + } + rational paste_start = GetConnectedNode()->GetPlayhead(); if (insert) {