/*** Olive - Non-Linear Video Editor Copyright (C) 2021 Olive Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ***/ #include "nodecopypaste.h" #include #include "core.h" #include "node/factory.h" #include "widget/nodeview/nodeviewundo.h" #include "window/mainwindow/mainwindow.h" namespace olive { void NodeCopyPasteService::CopyNodesToClipboard(const QVector &nodes, void *userdata) { QString copy_str; QXmlStreamWriter writer(©_str); writer.setAutoFormatting(true); writer.writeStartDocument(); writer.writeStartElement(QStringLiteral("olive")); writer.writeTextElement(QStringLiteral("version"), QString::number(Core::kProjectVersion)); writer.writeStartElement(QStringLiteral("nodes")); foreach (Node* n, nodes) { writer.writeStartElement(QStringLiteral("node")); writer.writeAttribute(QStringLiteral("id"), n->id()); n->Save(&writer); writer.writeEndElement(); // node } writer.writeEndElement(); // nodes writer.writeStartElement(QStringLiteral("contexts")); foreach (Node* n, nodes) { // Determine if this node is a context if (n->parent()->GetPositionMap().contains(n)) { writer.writeStartElement(QStringLiteral("context")); writer.writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(n))); const NodeGraph::PositionMap &map = n->parent()->GetNodesForContext(n); for (auto it=map.cbegin(); it!=map.cend(); it++) { writer.writeStartElement(QStringLiteral("node")); Project::SavePosition(&writer, it.key(), it.value()); writer.writeEndElement(); // node } writer.writeEndElement(); // context } } writer.writeEndElement(); // contexts writer.writeStartElement(QStringLiteral("custom")); CopyNodesToClipboardInternal(&writer, nodes, userdata); writer.writeEndElement(); // custom writer.writeEndElement(); // olive writer.writeEndDocument(); Core::CopyStringToClipboard(copy_str); } QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, MultiUndoCommand* command, void *userdata) { QString clipboard = Core::PasteStringFromClipboard(); if (clipboard.isEmpty()) { return QVector(); } QXmlStreamReader reader(clipboard); uint data_version = 0; QVector pasted_nodes; XMLNodeData xml_node_data; QMap > pasted_contexts; while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("olive")) { // Default to current version - this may not be desirable? while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("version")) { data_version = reader.readElementText().toUInt(); } else if (reader.name() == QStringLiteral("nodes")) { while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("node")) { if (data_version == 0) { // Refuse to create any nodes if we didn't get a version string return QVector(); } Node* node = nullptr; XMLAttributeLoop((&reader), attr) { if (attr.name() == QStringLiteral("id")) { node = NodeFactory::CreateFromID(attr.value().toString()); break; } } if (node) { node->Load(&reader, xml_node_data, data_version, nullptr); pasted_nodes.append(node); } } else { reader.skipCurrentElement(); } } } else if (reader.name() == QStringLiteral("contexts")) { while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("context")) { // Get context ptr QMap map; quintptr context_ptr = 0; XMLAttributeLoop((&reader), attr) { if (attr.name() == QStringLiteral("ptr")) { context_ptr = attr.value().toULongLong(); break; } } // Find nodes section while (XMLReadNextStartElement(&reader)) { if (reader.name() == QStringLiteral("node")) { quintptr node_ptr; QPointF node_pos; if (Project::LoadPosition(&reader, &node_ptr, &node_pos)) { map.insert(node_ptr, node_pos); } } else { reader.skipCurrentElement(); } } // Insert if (!map.isEmpty()) { pasted_contexts.insert(context_ptr, map); } } else { reader.skipCurrentElement(); } } } else if (reader.name() == QStringLiteral("custom")) { if (data_version == 0) { // Refuse to create any nodes if we didn't get a version string return QVector(); } PasteNodesFromClipboardInternal(&reader, xml_node_data, userdata); } else { reader.skipCurrentElement(); } } } else { reader.skipCurrentElement(); } } if (pasted_nodes.isEmpty()) { // If we passed through the whole string and there were no nodes, it must not be data for us after all return QVector(); } // If we have some nodes AND the XML data was malformed, the user should probably know if (reader.hasError()) { // Delete all nodes so this is a no-op foreach (Node* n, pasted_nodes) { delete n; } // If this was NOT an internal error, we assume it's an XML error that the user needs to know about QMessageBox::critical(Core::instance()->main_window(), QCoreApplication::translate("NodeCopyPasteWidget", "Error pasting nodes"), QCoreApplication::translate("NodeCopyPasteWidget", "Failed to paste nodes: %1").arg(reader.errorString()), QMessageBox::Ok); return QVector(); } // Add all nodes to graph foreach (Node* n, pasted_nodes) { command->add_child(new NodeAddCommand(graph, n)); } // Make connections if (!xml_node_data.desired_connections.isEmpty()) { XMLConnectNodes(xml_node_data, data_version, command); } // Link blocks XMLLinkBlocks(xml_node_data); // Process contexts for (auto it=pasted_contexts.cbegin(); it!=pasted_contexts.cend(); it++) { Node *context = xml_node_data.node_ptrs.value(it.key()); if (context) { const QMap &map = it.value(); for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { Node *subnode = xml_node_data.node_ptrs.value(jt.key()); if (subnode) { command->add_child(new NodeSetPositionCommand(subnode, context, jt.value(), false)); } } } } return pasted_nodes; } void NodeCopyPasteService::CopyNodesToClipboardInternal(QXmlStreamWriter*, const QVector &, void*) { } void NodeCopyPasteService::PasteNodesFromClipboardInternal(QXmlStreamReader* reader, XMLNodeData &xml_node_data, void*) { Q_UNUSED(xml_node_data) reader->skipCurrentElement(); } }