diff --git a/app/common/xmlutils.cpp b/app/common/xmlutils.cpp index 1b7820e26..27057ffd9 100644 --- a/app/common/xmlutils.cpp +++ b/app/common/xmlutils.cpp @@ -24,7 +24,6 @@ #include "node/factory.h" #include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/nodeview/nodeviewundo.h" -//#include "widget/timelinewidget/undo/timelineundogeneral.h" namespace olive { @@ -44,60 +43,4 @@ bool XMLReadNextStartElement(QXmlStreamReader *reader) return false; } -void XMLNodeData::PostConnect(uint version, MultiUndoCommand *command) const -{ - foreach (const XMLNodeData::SerializedConnection& con, desired_connections) { - if (Node *out = node_ptrs.value(con.output_node)) { - // Use output param as hint tag since we grandfathered those in - Node::ValueHint hint(con.output_param); - - if (command) { - command->add_child(new NodeEdgeAddCommand(out, con.input)); - } else { - Node::ConnectEdge(out, con.input); - } - - if (version < 210907) { - /// Deprecated: backwards compatibility only - if (command) { - command->add_child(new NodeSetValueHintCommand(con.input, hint)); - } else { - con.input.node()->SetValueHintForInput(con.input.input(), hint, con.input.element()); - } - } - } - } - - foreach (const XMLNodeData::BlockLink& l, block_links) { - Node *a = l.block; - Node *b = node_ptrs.value(l.link); - if (command) { - command->add_child(new NodeLinkCommand(a, b, true)); - } else { - Node::Link(a, b); - } - } - - foreach (const XMLNodeData::GroupLink &l, group_input_links) { - if (Node *input_node = node_ptrs.value(l.input_node)) { - NodeInput resolved(input_node, l.input_id, l.input_element); - if (command) { - command->add_child(new NodeGroupAddInputPassthrough(l.group, resolved)); - } else { - l.group->AddInputPassthrough(resolved); - } - } - } - - for (auto it=group_output_links.cbegin(); it!=group_output_links.cend(); it++) { - if (Node *output_node = node_ptrs.value(it.value())) { - if (command) { - command->add_child(new NodeGroupSetOutputPassthrough(it.key(), output_node)); - } else { - it.key()->SetOutputPassthrough(output_node); - } - } - } -} - } diff --git a/app/core.cpp b/app/core.cpp index c9d47386e..95a2fd7a3 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -51,6 +51,7 @@ #include "dialog/preferences/preferences.h" #include "node/color/colormanager/colormanager.h" #include "node/factory.h" +#include "node/project/serializer/serializer.h" #include "panel/panelmanager.h" #include "panel/project/project.h" #include "panel/viewer/viewer.h" @@ -151,6 +152,9 @@ void Core::Start() // Initialize ConformManager ConformManager::CreateInstance(); + // Initialize project serializers + ProjectSerializer::Initialize(); + // // Start application // @@ -206,6 +210,8 @@ void Core::Stop() } } + ProjectSerializer::Destroy(); + ConformManager::DestroyInstance(); FrameManager::DestroyInstance(); @@ -491,11 +497,10 @@ bool Core::AddOpenProjectFromTask(Task *task) if (!load_task->IsCancelled()) { Project* project = load_task->GetLoadedProject(); - MainWindowLayoutInfo layout = load_task->GetLoadedLayout(); - if (ValidateFootageInLoadedProject(project, load_task->GetFilenameProjectWasSavedAs())) { + if (ValidateFootageInLoadedProject(project, project->GetSavedURL())) { AddOpenProject(project); - main_window_->LoadLayout(layout); + main_window_->LoadLayout(project->GetLayoutInfo()); return true; } else { @@ -754,6 +759,9 @@ void Core::SaveProjectInternal(Project* project, const QString& override_filenam // Create save manager Task* psm; + // Put layout into project + project->SetLayoutInfo(main_window_->SaveLayout()); + if (project->filename().endsWith(QStringLiteral(".otio"), Qt::CaseInsensitive)) { #ifdef USE_OTIO psm = new SaveOTIOTask(project); diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index e64b551d8..1f7531fa4 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -129,61 +129,6 @@ QString NodeGroup::GetInputName(const QString &id) const return input_passthroughs_.value(id).name(); } -bool NodeGroup::LoadCustom(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) -{ - if (reader->name() == QStringLiteral("inputpassthroughs")) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("inputpassthrough")) { - XMLNodeData::GroupLink link; - - link.group = this; - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - link.input_node = reader->readElementText().toULongLong(); - } else if (reader->name() == QStringLiteral("input")) { - link.input_id = reader->readElementText(); - } else if (reader->name() == QStringLiteral("element")) { - link.input_element = reader->readElementText().toInt(); - } else { - reader->skipCurrentElement(); - } - } - - xml_node_data.group_input_links.append(link); - } else { - reader->skipCurrentElement(); - } - } - - return true; - } else if (reader->name() == QStringLiteral("outputpassthrough")) { - xml_node_data.group_output_links.insert(this, reader->readElementText().toULongLong()); - return true; - } else { - return super::LoadCustom(reader, xml_node_data, version, cancelled); - } -} - -void NodeGroup::SaveCustom(QXmlStreamWriter *writer) const -{ - super::SaveCustom(writer); - - writer->writeStartElement(QStringLiteral("inputpassthroughs")); - - foreach (const NodeInput &ip, input_passthroughs_) { - writer->writeStartElement(QStringLiteral("inputpassthrough")); - writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.node()))); - writer->writeTextElement(QStringLiteral("input"), ip.input()); - writer->writeTextElement(QStringLiteral("element"), QString::number(ip.element())); - writer->writeEndElement(); // input - } - - writer->writeEndElement(); // inputpassthroughs - - writer->writeTextElement(QStringLiteral("outputpassthrough"), QString::number(reinterpret_cast(output_passthrough_))); -} - void NodeGroupAddInputPassthrough::redo() { if (!group_->ContainsInputPassthrough(input_)) { diff --git a/app/node/group/group.h b/app/node/group/group.h index 979a52bc7..4a56b39db 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -70,11 +70,6 @@ signals: void OutputPassthroughChanged(NodeGroup *group, Node *output); -protected: - virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled) override; - - virtual void SaveCustom(QXmlStreamWriter* writer) const override; - private: QHash input_passthroughs_; diff --git a/app/node/node.cpp b/app/node/node.cpp index 90ee197f9..23040265c 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -77,151 +77,6 @@ NodeGraph *Node::parent() const return static_cast(QObject::parent()); } -void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled) -{ - while (XMLReadNextStartElement(reader)) { - if (cancelled && *cancelled) { - return; - } - - if (reader->name() == QStringLiteral("input")) { - LoadInput(reader, xml_node_data, cancelled); - } else if (reader->name() == QStringLiteral("ptr")) { - xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), this); - } else if (reader->name() == QStringLiteral("label")) { - SetLabel(reader->readElementText()); - } else if (reader->name() == QStringLiteral("uuid")) { - SetUUID(QUuid::fromString(reader->readElementText())); - } else if (reader->name() == QStringLiteral("color")) { - override_color_ = reader->readElementText().toInt(); - } else if (reader->name() == QStringLiteral("links")) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("link")) { - xml_node_data.block_links.append({this, reader->readElementText().toULongLong()}); - } else { - reader->skipCurrentElement(); - } - } - } else if (reader->name() == QStringLiteral("custom")) { - while (XMLReadNextStartElement(reader)) { - if (!LoadCustom(reader, xml_node_data, version, cancelled)) { - reader->skipCurrentElement(); - } - } - } else if (reader->name() == QStringLiteral("connections")) { - // Load connections - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("connection")) { - QString param_id; - int ele = -1; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("element")) { - ele = attr.value().toInt(); - } else if (attr.name() == QStringLiteral("input")) { - param_id = attr.value().toString(); - } - } - - QString output_node_id; - QString output_param_id; - - while (XMLReadNextStartElement(reader)) { - if ((version >= 210907 && reader->name() == QStringLiteral("output")) || reader->name() == QStringLiteral("node")) { - output_node_id = reader->readElementText(); - } else if (version < 210907 && reader->name() == QStringLiteral("output")) { - output_param_id = reader->readElementText(); - } else { - reader->skipCurrentElement(); - } - } - - xml_node_data.desired_connections.append({NodeInput(this, param_id, ele), output_node_id.toULongLong(), output_param_id}); - } else { - reader->skipCurrentElement(); - } - } - } else if (reader->name() == QStringLiteral("hints")) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("hint")) { - QString input; - int element = -1; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("input")) { - input = attr.value().toString(); - } else if (attr.name() == QStringLiteral("element")) { - element = attr.value().toInt(); - } - } - - ValueHint vh; - vh.Load(reader); - - value_hints_.insert({input, element}, vh); - } else { - reader->skipCurrentElement(); - } - } - } else { - reader->skipCurrentElement(); - } - } -} - -void Node::Save(QXmlStreamWriter *writer) const -{ - writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); - - writer->writeTextElement(QStringLiteral("uuid"), uuid_.toString()); - writer->writeTextElement(QStringLiteral("label"), GetLabel()); - writer->writeTextElement(QStringLiteral("color"), QString::number(override_color_)); - - foreach (const QString& input, input_ids_) { - writer->writeStartElement(QStringLiteral("input")); - - SaveInput(writer, input); - - writer->writeEndElement(); // input - } - - writer->writeStartElement(QStringLiteral("links")); - foreach (Node* link, links_) { - writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast(link))); - } - writer->writeEndElement(); // links - - writer->writeStartElement(QStringLiteral("connections")); - for (auto it=input_connections().cbegin(); it!=input_connections().cend(); it++) { - writer->writeStartElement(QStringLiteral("connection")); - - writer->writeAttribute(QStringLiteral("input"), it->first.input()); - writer->writeAttribute(QStringLiteral("element"), QString::number(it->first.element())); - - writer->writeTextElement(QStringLiteral("output"), QString::number(reinterpret_cast(it->second))); - - writer->writeEndElement(); // connection - } - writer->writeEndElement(); // connections - - writer->writeStartElement(QStringLiteral("hints")); - for (auto it=value_hints_.cbegin(); it!=value_hints_.cend(); it++) { - writer->writeStartElement(QStringLiteral("hint")); - - writer->writeAttribute(QStringLiteral("input"), it.key().input); - writer->writeAttribute(QStringLiteral("element"), QString::number(it.key().element)); - - it.value().Save(writer); - - writer->writeEndElement(); // hint - } - writer->writeEndElement(); - - writer->writeStartElement(QStringLiteral("custom")); - SaveCustom(writer); - writer->writeEndElement(); // custom -} - Project* Node::project() const { QObject *t = this->parent(); @@ -393,90 +248,6 @@ QString Node::GetInputName(const QString &id) const } } -void Node::LoadInput(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled) -{ - QString param_id; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("id")) { - param_id = attr.value().toString(); - - break; - } - } - - if (param_id.isEmpty()) { - qWarning() << "Failed to load parameter with missing ID"; - reader->skipCurrentElement(); - return; - } - - if (!HasInputWithID(param_id)) { - qWarning() << "Failed to load parameter that didn't exist:" << param_id; - reader->skipCurrentElement(); - return; - } - - while (XMLReadNextStartElement(reader)) { - if (cancelled && *cancelled) { - return; - } - - if (reader->name() == QStringLiteral("primary")) { - // Load primary immediate - LoadImmediate(reader, param_id, -1, xml_node_data, cancelled); - } else if (reader->name() == QStringLiteral("subelements")) { - // Load subelements - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("count")) { - InputArrayResize(param_id, attr.value().toInt()); - } - } - - int element_counter = 0; - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("element")) { - LoadImmediate(reader, param_id, element_counter, xml_node_data, cancelled); - - element_counter++; - } else { - reader->skipCurrentElement(); - } - } - } else { - reader->skipCurrentElement(); - } - } -} - -void Node::SaveInput(QXmlStreamWriter *writer, const QString &id) const -{ - writer->writeAttribute(QStringLiteral("id"), id); - - writer->writeStartElement(QStringLiteral("primary")); - - SaveImmediate(writer, id, -1); - - writer->writeEndElement(); // primary - - writer->writeStartElement(QStringLiteral("subelements")); - - int arr_sz = InputArraySize(id); - - writer->writeAttribute(QStringLiteral("count"), QString::number(arr_sz)); - - for (int i=0; iwriteStartElement(QStringLiteral("element")); - - SaveImmediate(writer, id, i); - - writer->writeEndElement(); // element - } - - writer->writeEndElement(); // subelements -} - bool Node::IsInputHidden(const QString &input) const { return (GetInputFlags(input) & kInputFlagHidden); @@ -1477,15 +1248,6 @@ void Node::IgnoreHashingFrom(const QString &input_id) ignore_when_hashing_.append(input_id); } -bool Node::LoadCustom(QXmlStreamReader *, XMLNodeData &, uint, const QAtomicInt*) -{ - return false; -} - -void Node::SaveCustom(QXmlStreamWriter *) const -{ -} - bool Node::HasGizmos() const { return false; @@ -1945,186 +1707,6 @@ void Node::ParameterValueChanged(const QString& input, int element, const TimeRa InvalidateCache(range, input, element); } -void Node::LoadImmediate(QXmlStreamReader *reader, const QString& input, int element, XMLNodeData &xml_node_data, const QAtomicInt *cancelled) -{ - Q_UNUSED(xml_node_data) - - NodeValue::Type data_type = GetInputDataType(input); - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("standard")) { - // Load standard value - int val_index = 0; - - while (XMLReadNextStartElement(reader)) { - if (cancelled && *cancelled) { - return; - } - - if (reader->name() == QStringLiteral("track")) { - QVariant value_on_track; - - if (data_type == NodeValue::kVideoParams) { - VideoParams vp; - vp.Load(reader); - value_on_track = QVariant::fromValue(vp); - } else if (data_type == NodeValue::kAudioParams) { - AudioParams ap; - ap.Load(reader); - value_on_track = QVariant::fromValue(ap); - } else { - QString value_text = reader->readElementText(); - - if (!value_text.isEmpty()) { - value_on_track = NodeValue::StringToValue(data_type, value_text, element); - } - } - - SetSplitStandardValueOnTrack(input, val_index, value_on_track, element); - - val_index++; - } else { - reader->skipCurrentElement(); - } - } - } else if (reader->name() == QStringLiteral("keyframing") && IsInputKeyframable(input)) { - SetInputIsKeyframing(input, reader->readElementText().toInt(), element); - } else if (reader->name() == QStringLiteral("keyframes")) { - int track = 0; - - while (XMLReadNextStartElement(reader)) { - if (cancelled && *cancelled) { - return; - } - - if (reader->name() == QStringLiteral("track")) { - while (XMLReadNextStartElement(reader)) { - if (cancelled && *cancelled) { - return; - } - - if (reader->name() == QStringLiteral("key")) { - QString key_input; - rational key_time; - NodeKeyframe::Type key_type = NodeKeyframe::kLinear; - QVariant key_value; - QPointF key_in_handle; - QPointF key_out_handle; - - XMLAttributeLoop(reader, attr) { - if (cancelled && *cancelled) { - return; - } - - if (attr.name() == QStringLiteral("input")) { - key_input = attr.value().toString(); - } else if (attr.name() == QStringLiteral("time")) { - key_time = rational::fromString(attr.value().toString()); - } else if (attr.name() == QStringLiteral("type")) { - key_type = static_cast(attr.value().toInt()); - } else if (attr.name() == QStringLiteral("inhandlex")) { - key_in_handle.setX(attr.value().toDouble()); - } else if (attr.name() == QStringLiteral("inhandley")) { - key_in_handle.setY(attr.value().toDouble()); - } else if (attr.name() == QStringLiteral("outhandlex")) { - key_out_handle.setX(attr.value().toDouble()); - } else if (attr.name() == QStringLiteral("outhandley")) { - key_out_handle.setY(attr.value().toDouble()); - } - } - - key_value = NodeValue::StringToValue(data_type, reader->readElementText(), true); - - NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, key_input, this); - key->set_bezier_control_in(key_in_handle); - key->set_bezier_control_out(key_out_handle); - } else { - reader->skipCurrentElement(); - } - } - - track++; - } else { - reader->skipCurrentElement(); - } - } - } else if (reader->name() == QStringLiteral("csinput")) { - SetInputProperty(input, QStringLiteral("col_input"), reader->readElementText()); - } else if (reader->name() == QStringLiteral("csdisplay")) { - SetInputProperty(input, QStringLiteral("col_display"), reader->readElementText()); - } else if (reader->name() == QStringLiteral("csview")) { - SetInputProperty(input, QStringLiteral("col_view"), reader->readElementText()); - } else if (reader->name() == QStringLiteral("cslook")) { - SetInputProperty(input, QStringLiteral("col_look"), reader->readElementText()); - } else { - reader->skipCurrentElement(); - } - } -} - -void Node::SaveImmediate(QXmlStreamWriter *writer, const QString& input, int element) const -{ - if (IsInputKeyframable(input)) { - writer->writeTextElement(QStringLiteral("keyframing"), QString::number(IsInputKeyframing(input, element))); - } - - NodeValue::Type data_type = GetInputDataType(input); - - // Write standard value - writer->writeStartElement(QStringLiteral("standard")); - - foreach (const QVariant& v, GetSplitStandardValue(input, element)) { - writer->writeStartElement(QStringLiteral("track")); - - if (data_type == NodeValue::kVideoParams) { - v.value().Save(writer); - } else if (data_type == NodeValue::kAudioParams) { - v.value().Save(writer); - } else { - writer->writeCharacters(NodeValue::ValueToString(data_type, v, true)); - } - - writer->writeEndElement(); // track - } - - writer->writeEndElement(); // standard - - // Write keyframes - writer->writeStartElement(QStringLiteral("keyframes")); - - foreach (const NodeKeyframeTrack& track, GetKeyframeTracks(input, element)) { - writer->writeStartElement(QStringLiteral("track")); - - foreach (NodeKeyframe* key, track) { - writer->writeStartElement(QStringLiteral("key")); - - writer->writeAttribute(QStringLiteral("input"), key->input()); - writer->writeAttribute(QStringLiteral("time"), key->time().toString()); - writer->writeAttribute(QStringLiteral("type"), QString::number(key->type())); - writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(key->bezier_control_in().x())); - writer->writeAttribute(QStringLiteral("inhandley"), QString::number(key->bezier_control_in().y())); - writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(key->bezier_control_out().x())); - writer->writeAttribute(QStringLiteral("outhandley"), QString::number(key->bezier_control_out().y())); - - writer->writeCharacters(NodeValue::ValueToString(data_type, key->value(), true)); - - writer->writeEndElement(); // key - } - - writer->writeEndElement(); // track - } - - writer->writeEndElement(); // keyframes - - if (data_type == NodeValue::kColor) { - // Save color management information - writer->writeTextElement(QStringLiteral("csinput"), GetInputProperty(input, QStringLiteral("col_input")).toString()); - writer->writeTextElement(QStringLiteral("csdisplay"), GetInputProperty(input, QStringLiteral("col_display")).toString()); - writer->writeTextElement(QStringLiteral("csview"), GetInputProperty(input, QStringLiteral("col_view")).toString()); - writer->writeTextElement(QStringLiteral("cslook"), GetInputProperty(input, QStringLiteral("col_look")).toString()); - } -} - TimeRange Node::GetRangeAffectedByKeyframe(NodeKeyframe *key) const { const NodeKeyframeTrack& key_track = GetTrackFromKeyframe(key); @@ -2439,42 +2021,6 @@ void Node::ValueHint::Hash(QCryptographicHash &hash) const hash.addData(tag().toUtf8()); } -void Node::ValueHint::Load(QXmlStreamReader *reader) -{ - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("types")) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("type")) { - type_.append(static_cast(reader->readElementText().toInt())); - } else { - reader->skipCurrentElement(); - } - } - } else if (reader->name() == QStringLiteral("index")) { - index_ = reader->readElementText().toInt(); - } else if (reader->name() == QStringLiteral("tag")) { - tag_ = reader->readElementText(); - } else { - reader->skipCurrentElement(); - } - } -} - -void Node::ValueHint::Save(QXmlStreamWriter *writer) const -{ - writer->writeStartElement(QStringLiteral("types")); - - for (auto it=type_.cbegin(); it!=type_.cend(); it++) { - writer->writeTextElement(QStringLiteral("type"), QString::number(*it)); - } - - writer->writeEndElement(); // types - - writer->writeTextElement(QStringLiteral("index"), QString::number(index_)); - - writer->writeTextElement(QStringLiteral("tag"), tag_); -} - void NodeSetPositionAndDependenciesRecursivelyCommand::prepare() { move_recursively(node_, pos_.position - context_->GetNodePositionDataInContext(node_).position); diff --git a/app/node/node.h b/app/node/node.h index 45516fbb8..8f89666b8 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -126,16 +126,6 @@ public: return flags_; } - /** - * @brief Clear current node variables and replace them with - */ - void Load(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled); - - /** - * @brief Save this node into a text/XML format - */ - void Save(QXmlStreamWriter* writer) const; - /** * @brief Return the name of the node * @@ -332,9 +322,6 @@ public: virtual QString GetInputName(const QString& id) const; - void LoadInput(QXmlStreamReader* reader, XMLNodeData &xml_node_data, const QAtomicInt *cancelled); - void SaveInput(QXmlStreamWriter* writer, const QString& id) const; - bool IsInputHidden(const QString& input) const; bool IsInputConnectable(const QString& input) const; bool IsInputKeyframable(const QString& input) const; @@ -579,9 +566,6 @@ public: void Hash(QCryptographicHash &hash) const; - void Load(QXmlStreamReader *reader); - void Save(QXmlStreamWriter *writer) const; - const QVector &types() const { return type_; } const int &index() const { return index_; } const QString& tag() const { return tag_; } @@ -599,6 +583,11 @@ public: static void Hash(const Node *node, const ValueHint &hint, QCryptographicHash& hash, const NodeGlobals &globals, const VideoParams& video_params); + const QMap &GetValueHints() const + { + return value_hints_; + } + ValueHint GetValueHintForInput(const QString &input, int element = -1) const { return value_hints_.value({input, element}); @@ -1005,10 +994,6 @@ protected: return operation_stack_; } - virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled); - - virtual void SaveCustom(QXmlStreamWriter* writer) const; - enum GizmoScaleHandles { kGizmoScaleTopLeft, kGizmoScaleTopCenter, @@ -1265,10 +1250,6 @@ private: ParameterValueChanged(input.input(), input.element(), range); } - void LoadImmediate(QXmlStreamReader *reader, const QString& input, int element, XMLNodeData& xml_node_data, const QAtomicInt* cancelled); - - void SaveImmediate(QXmlStreamWriter *writer, const QString &input, int element) const; - /** * @brief Intelligently determine how what time range is affected by a keyframe */ diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index a48f296cc..40f15b6fd 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -24,6 +24,7 @@ #include "core.h" #include "node/factory.h" +#include "node/project/serializer/serializer.h" #include "widget/nodeview/nodeviewundo.h" #include "window/mainwindow/mainwindow.h" @@ -34,24 +35,11 @@ void NodeCopyPasteService::CopyNodesToClipboard(QVector nodes, void *use QString copy_str; QXmlStreamWriter writer(©_str); - writer.setAutoFormatting(true); - writer.writeStartDocument(); - writer.writeStartElement(QStringLiteral("olive")); - - writer.writeTextElement(QStringLiteral("version"), QString::number(Project::kProjectVersion)); - - writer.writeStartElement(QStringLiteral("nodes")); + // For any groups, add children for (int i=0; iid()); - n->Save(&writer); - writer.writeEndElement(); // node - // If this is a group, add the child nodes too - if (NodeGroup *g = dynamic_cast(n)) { + if (NodeGroup *g = dynamic_cast(nodes.at(i))) { for (auto it=g->GetContextPositions().cbegin(); it!=g->GetContextPositions().cend(); it++) { if (!nodes.contains(it.key())) { nodes.append(it.key()); @@ -59,187 +47,37 @@ void NodeCopyPasteService::CopyNodesToClipboard(QVector nodes, void *use } } } - writer.writeEndElement(); // nodes - writer.writeStartElement(QStringLiteral("contexts")); - foreach (Node* n, nodes) { - // Determine if this node is a context - if (!n->GetContextPositions().isEmpty()) { - writer.writeStartElement(QStringLiteral("context")); - writer.writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(n))); + ProjectSerializer::Save(nodes.first()->project(), &writer, nodes); - const Node::PositionMap &map = n->GetContextPositions(); - 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")); + /*writer.writeStartElement(QStringLiteral("custom")); CopyNodesToClipboardInternal(&writer, nodes, userdata); - writer.writeEndElement(); // custom - - writer.writeEndElement(); // olive - writer.writeEndDocument(); + writer.writeEndElement(); // custom*/ Core::CopyStringToClipboard(copy_str); } -QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, MultiUndoCommand* command, void *userdata) +QVector NodeCopyPasteService::PasteNodesFromClipboard(Project *project, MultiUndoCommand* command, void *userdata) { - QString clipboard = Core::PasteStringFromClipboard(); + QVector pasted_nodes; + QString clipboard = Core::PasteStringFromClipboard(); if (clipboard.isEmpty()) { - return QVector(); + return pasted_nodes; } QXmlStreamReader reader(clipboard); - uint data_version = 0; - QVector pasted_nodes; - XMLNodeData xml_node_data; - QMap > pasted_contexts; + Project temp; + ProjectSerializer::Load(&temp, &reader); - 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; - Node::Position 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(); + foreach (Node *n, temp.nodes()) { + if (!temp.default_nodes().contains(n)) { + n->setParent(nullptr); + pasted_nodes.append(n); } } - 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) { - if (command) { - command->add_child(new NodeAddCommand(graph, n)); - } else { - n->setParent(graph); - } - } - - // 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) { - auto map = it.value(); - for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { - Node *subnode = xml_node_data.node_ptrs.value(jt.key()); - if (subnode) { - if (command) { - command->add_child(new NodeSetPositionCommand(subnode, context, jt.value())); - } else { - context->SetNodePositionInContext(subnode, jt.value()); - } - } - } - } - } - - // Make connections - xml_node_data.PostConnect(data_version, command); - return pasted_nodes; } diff --git a/app/node/nodecopypaste.h b/app/node/nodecopypaste.h index 41a25302e..067bef032 100644 --- a/app/node/nodecopypaste.h +++ b/app/node/nodecopypaste.h @@ -37,7 +37,7 @@ public: protected: void CopyNodesToClipboard(QVector nodes, void* userdata = nullptr); - QVector PasteNodesFromClipboard(NodeGraph *graph, MultiUndoCommand *command, void* userdata = nullptr); + QVector PasteNodesFromClipboard(Project *project, MultiUndoCommand *command, void* userdata = nullptr); virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector &nodes, void* userdata); diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 9506f4513..6325285d0 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -137,23 +137,6 @@ void Track::SetTrackHeight(const double &height) emit TrackHeightChangedInPixels(GetTrackHeightInPixels()); } -bool Track::LoadCustom(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled) -{ - if (reader->name() == QStringLiteral("height")) { - SetTrackHeight(reader->readElementText().toDouble()); - return true; - } else { - return super::LoadCustom(reader, xml_node_data, version, cancelled); - } -} - -void Track::SaveCustom(QXmlStreamWriter *writer) const -{ - super::SaveCustom(writer); - - writer->writeTextElement(QStringLiteral("height"), QString::number(GetTrackHeight())); -} - void Track::InputConnectedEvent(const QString &input, int element, Node *output) { if (input == kBlockInput) { diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index a7953ff09..72a1b6a78 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -455,10 +455,6 @@ signals: protected: virtual void Hash(QCryptographicHash& hash, const NodeGlobals &globals, const VideoParams& video_params) const override; - virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled) override; - - virtual void SaveCustom(QXmlStreamWriter* writer) const override; - virtual void InputConnectedEvent(const QString& input, int element, Node *output) override; virtual void InputDisconnectedEvent(const QString& input, int element, Node *output) override; diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index 82e5acdc6..9944a0e49 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -508,26 +508,6 @@ void ViewerOutput::set_parameters_from_footage(const QVector foo } } -bool ViewerOutput::LoadCustom(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) -{ - if (reader->name() == QStringLiteral("points")) { - timeline_points_.Load(reader); - return true; - } else { - return super::LoadCustom(reader, xml_node_data, version, cancelled); - } -} - -void ViewerOutput::SaveCustom(QXmlStreamWriter *writer) const -{ - super::SaveCustom(writer); - - // Write TimelinePoints - writer->writeStartElement(QStringLiteral("points")); - timeline_points_.Save(writer); - writer->writeEndElement(); // points -} - int ViewerOutput::AddStream(Track::Type type, const QVariant& value) { QString id; diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 2be79bf2f..c16b86160 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -237,10 +237,6 @@ protected: virtual void InputValueChangedEvent(const QString& input, int element) override; - virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled) override; - - virtual void SaveCustom(QXmlStreamWriter *writer) const override; - int AddStream(Track::Type type, const QVariant &value); private: diff --git a/app/node/project/CMakeLists.txt b/app/node/project/CMakeLists.txt index 75fab2bf2..3d2aded8b 100644 --- a/app/node/project/CMakeLists.txt +++ b/app/node/project/CMakeLists.txt @@ -18,6 +18,7 @@ add_subdirectory(folder) add_subdirectory(footage) add_subdirectory(projectsettings) add_subdirectory(sequence) +add_subdirectory(serializer) set(OLIVE_SOURCES ${OLIVE_SOURCES} diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index a9aa54705..de87caa4f 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -72,23 +72,6 @@ void Footage::Retranslate() SetComboBoxStrings(kLoopModeInput, {tr("None"), tr("Loop"), tr("Clamp")}); } -bool Footage::LoadCustom(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt* cancelled) -{ - if (reader->name() == QStringLiteral("timestamp")) { - set_timestamp(reader->readElementText().toLongLong()); - return true; - } else { - return super::LoadCustom(reader, xml_node_data, version, cancelled); - } -} - -void Footage::SaveCustom(QXmlStreamWriter *writer) const -{ - super::SaveCustom(writer); - - writer->writeTextElement(QStringLiteral("timestamp"), QString::number(timestamp_)); -} - void Footage::InputValueChangedEvent(const QString &input, int element) { if (input == kFilenameInput) { diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index ea98bd974..aed465106 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -196,16 +196,6 @@ public: static const QString kLoopModeInput; protected: - /** - * @brief Load function - */ - virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) override; - - /** - * @brief Save function - */ - virtual void SaveCustom(QXmlStreamWriter *writer) const override; - virtual void InputValueChangedEvent(const QString &input, int element) override; virtual rational VerifyLengthInternal(Track::Type type) const override; diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index 9641669cd..1886f5350 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -32,12 +32,6 @@ namespace olive { -// This is the project version that Olive will save -const uint Project::kProjectVersion = 211228; - -// This is the minimum project version that Olive can load -const uint Project::kProjectMinimumVersion = 210528; - Project::Project() : is_modified_(false), autorecovery_saved_(true) @@ -50,6 +44,7 @@ Project::Project() : root_->setParent(this); root_->SetLabel(tr("Root")); root_->SetCanBeDeleted(false); + AddDefaultNode(root_); // Adds a color manager "node" to this project so that it synchronizes color_manager_ = new ColorManager(); @@ -67,194 +62,6 @@ Project::Project() : this, &Project::ColorManagerValueChanged); } -void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint version, const QAtomicInt* cancelled) -{ - XMLNodeData xml_node_data; - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("root")) { - - root_->Load(reader, xml_node_data, version, cancelled); - - } else if (reader->name() == QStringLiteral("layout")) { - - // Since the main window's functions have to occur in the GUI thread (and we're likely - // loading in a secondary thread), we load all necessary data into a separate struct so we - // can continue loading and queue it with the main window so it can handle the data - // appropriately in its own thread. - - *layout = MainWindowLayoutInfo::fromXml(reader, xml_node_data); - - } else if (reader->name() == QStringLiteral("uuid")) { - - uuid_ = QUuid::fromString(reader->readElementText()); - - } else if (reader->name() == QStringLiteral("nodes")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - bool is_root = false; - bool is_cm = false; - bool is_settings = false; - QString id; - - { - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("id")) { - id = attr.value().toString(); - } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { - is_root = true; - } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { - is_cm = true; - } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { - is_settings = true; - } - } - } - - if (id.isEmpty()) { - qWarning() << "Failed to load node with empty ID"; - reader->skipCurrentElement(); - } else { - Node* node; - - if (is_root) { - node = root_; - } else if (is_cm) { - node = color_manager_; - } else if (is_settings) { - node = settings_; - } else { - node = NodeFactory::CreateFromID(id); - } - - if (!node) { - qWarning() << "Failed to find node with ID" << id; - reader->skipCurrentElement(); - } else { - node->Load(reader, xml_node_data, version, cancelled); - node->setParent(this); - } - } - } else { - reader->skipCurrentElement(); - } - } - - } else if (reader->name() == QStringLiteral("positions")) { - - while (XMLReadNextStartElement(reader)) { - - if (reader->name() == QStringLiteral("context")) { - - quintptr context_ptr = 0; - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("ptr")) { - context_ptr = attr.value().toULongLong(); - break; - } - } - - Node *context = xml_node_data.node_ptrs.value(context_ptr); - - if (!context) { - qWarning() << "Failed to find pointer for context"; - reader->skipCurrentElement(); - } else { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - quintptr node_ptr; - Node::Position node_pos; - - if (LoadPosition(reader, &node_ptr, &node_pos)) { - Node *node = xml_node_data.node_ptrs.value(node_ptr); - - if (node) { - context->SetNodePositionInContext(node, node_pos); - } else { - qWarning() << "Failed to find pointer for node position"; - reader->skipCurrentElement(); - } - } - } else { - reader->skipCurrentElement(); - } - } - } - - } else { - - reader->skipCurrentElement(); - - } - - } - - } else { - - // Skip this - reader->skipCurrentElement(); - - } - } - - // Make connections - xml_node_data.PostConnect(version); -} - -void Project::Save(QXmlStreamWriter *writer) const -{ - writer->writeTextElement(QStringLiteral("uuid"), uuid_.toString()); - - writer->writeStartElement(QStringLiteral("nodes")); - - foreach (Node* node, nodes()) { - writer->writeStartElement(QStringLiteral("node")); - - if (node == root_) { - writer->writeAttribute(QStringLiteral("root"), QStringLiteral("1")); - } else if (node == color_manager_) { - writer->writeAttribute(QStringLiteral("cm"), QStringLiteral("1")); - } else if (node == settings_) { - writer->writeAttribute(QStringLiteral("settings"), QStringLiteral("1")); - } - - writer->writeAttribute(QStringLiteral("id"), node->id()); - - node->Save(writer); - - writer->writeEndElement(); // node - } - - writer->writeEndElement(); // nodes - - writer->writeStartElement(QStringLiteral("positions")); - - foreach (Node* context, nodes()) { - const Node::PositionMap &map = context->GetContextPositions(); - - if (!map.isEmpty()) { - writer->writeStartElement(QStringLiteral("context")); - - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(context))); - - for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { - writer->writeStartElement(QStringLiteral("node")); - SavePosition(writer, jt.key(), jt.value()); - writer->writeEndElement(); // node - } - - writer->writeEndElement(); // context - } - } - - writer->writeEndElement(); // positions - - // Save main window project layout - MainWindowLayoutInfo main_window_info = Core::instance()->main_window()->SaveLayout(); - main_window_info.toXml(writer); -} - Folder *Project::root() { return root_; @@ -353,46 +160,6 @@ void Project::RegenerateUuid() uuid_ = QUuid::createUuid(); } -bool Project::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) -{ - bool got_node_ptr = false; - bool got_pos_x = false; - bool got_pos_y = false; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("ptr")) { - *node_ptr = attr.value().toULongLong(); - got_node_ptr = true; - break; - } - } - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("x")) { - pos->position.setX(reader->readElementText().toDouble()); - got_pos_x = true; - } else if (reader->name() == QStringLiteral("y")) { - pos->position.setY(reader->readElementText().toDouble()); - got_pos_y = true; - } else if (reader->name() == QStringLiteral("expanded")) { - pos->expanded = reader->readElementText().toInt(); - } else { - reader->skipCurrentElement(); - } - } - - return got_node_ptr && got_pos_x && got_pos_y; -} - -void Project::SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos) -{ - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(node))); - - writer->writeTextElement(QStringLiteral("x"), QString::number(pos.position.x())); - writer->writeTextElement(QStringLiteral("y"), QString::number(pos.position.y())); - writer->writeTextElement(QStringLiteral("expanded"), QString::number(pos.expanded)); -} - void Project::ColorManagerValueChanged(const NodeInput &input, const TimeRange &range) { Q_UNUSED(input) diff --git a/app/node/project/project.h b/app/node/project/project.h index 2565e3697..fe4f1715a 100644 --- a/app/node/project/project.h +++ b/app/node/project/project.h @@ -50,10 +50,6 @@ class Project : public NodeGraph public: Project(); - void Load(QXmlStreamReader* reader, MainWindowLayoutInfo *layout, uint version, const QAtomicInt* cancelled); - - void Save(QXmlStreamWriter* writer) const; - Folder* root(); QString name() const; @@ -80,13 +76,37 @@ public: return uuid_; } + void SetUuid(const QUuid &uuid) + { + uuid_ = uuid; + } + void RegenerateUuid(); - static bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos); - static void SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos); + const MainWindowLayoutInfo &GetLayoutInfo() const + { + return layout_info_; + } - static const uint kProjectVersion; - static const uint kProjectMinimumVersion; + void SetLayoutInfo(const MainWindowLayoutInfo &info) + { + layout_info_ = info; + } + + /** + * @brief Returns the filename the project was saved as, but not necessarily where it is now + * + * May help for resolving relative paths. + */ + const QString &GetSavedURL() const + { + return saved_url_; + } + + void SetSavedURL(const QString &url) + { + saved_url_ = url; + } signals: void NameChanged(); @@ -100,6 +120,8 @@ private: QString filename_; + QString saved_url_; + ColorManager* color_manager_; ProjectSettingsNode* settings_; @@ -108,6 +130,8 @@ private: bool autorecovery_saved_; + MainWindowLayoutInfo layout_info_; + private slots: void ColorManagerValueChanged(const NodeInput& input, const TimeRange& range); diff --git a/app/node/project/serializer/CMakeLists.txt b/app/node/project/serializer/CMakeLists.txt new file mode 100644 index 000000000..e0328450e --- /dev/null +++ b/app/node/project/serializer/CMakeLists.txt @@ -0,0 +1,31 @@ +# 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 . + + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/project/serializer/serializer.cpp + node/project/serializer/serializer.h + node/project/serializer/serializer190219.cpp + node/project/serializer/serializer190219.h + node/project/serializer/serializer210528.cpp + node/project/serializer/serializer210528.h + node/project/serializer/serializer210907.cpp + node/project/serializer/serializer210907.h + node/project/serializer/serializer211228.cpp + node/project/serializer/serializer211228.h + PARENT_SCOPE +) diff --git a/app/node/project/serializer/serializer.cpp b/app/node/project/serializer/serializer.cpp new file mode 100644 index 000000000..34f375567 --- /dev/null +++ b/app/node/project/serializer/serializer.cpp @@ -0,0 +1,204 @@ +/*** + + 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 "serializer.h" + +#include +#include +#include + +#include "common/xmlutils.h" +#include "core.h" +#include "serializer190219.h" +#include "serializer210528.h" +#include "serializer210907.h" +#include "serializer211228.h" + +namespace olive { + +QVector ProjectSerializer::instances_; + +void ProjectSerializer::Initialize() +{ + // Make sure to order these from oldest to newest + + // FIXME: Implement this - yes it's a 0.1 project loader + //instances_.append(new ProjectSerializer190219); + + instances_.append(new ProjectSerializer210528); + instances_.append(new ProjectSerializer210907); + instances_.append(new ProjectSerializer211228); +} + +void ProjectSerializer::Destroy() +{ + qDeleteAll(instances_); + instances_.clear(); +} + +ProjectSerializer::Result ProjectSerializer::Load(Project *project, const QString &filename) +{ + QFile project_file(filename); + + if (project_file.open(QFile::ReadOnly | QFile::Text)) { + QXmlStreamReader reader(&project_file); + + Result inner_result = Load(project, &reader); + + project_file.close(); + + if (inner_result.code() != kSuccess) { + return inner_result; + } + + if (reader.hasError()) { + Result r(kXmlError); + r.SetDetails(reader.errorString()); + return r; + } else { + return kSuccess; + } + } else { + return kFileError; + } +} + +ProjectSerializer::Result ProjectSerializer::Load(Project *project, QXmlStreamReader *reader) +{ + // Determine project version + uint version = 0; + + while (!version && XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("olive") || reader->name() == QStringLiteral("project")) { + while(!version && XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("version")) { + version = reader->readElementText().toUInt(); + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } + + // Failed to find version in file + if (version == 0) { + return kUnknownVersion; + } + + // We should now have the version, if we have a serializer for it, use it to load the project + ProjectSerializer *serializer = nullptr; + + foreach (ProjectSerializer *s, instances_) { + if (version == s->Version()) { + serializer = s; + break; + } else if (version < s->Version()) { + // Assuming the instance list is in order, if the project version is less than any version + // we find, we must not support it anymore + return kProjectTooOld; + } + } + + if (serializer) { + serializer->Load(project, reader, nullptr); + return kSuccess; + } else { + // Reached the end of the list with no serializer, assume too new + return kProjectTooNew; + } +} + +ProjectSerializer::Result ProjectSerializer::Save(Project *project, const QString &filename, const QVector &only) +{ + QString temp_save = FileFunctions::GetSafeTemporaryFilename(filename); + + QFile project_file(temp_save); + + if (project_file.open(QFile::WriteOnly | QFile::Text)) { + QXmlStreamWriter writer(&project_file); + + Result inner_result = Save(project, &writer, filename, only); + + project_file.close(); + + if (inner_result != kSuccess) { + return inner_result; + } + + // Save was successful, we can now rewrite the original file + if (FileFunctions::RenameFileAllowOverwrite(temp_save, filename)) { + return kSuccess; + } else { + Result r(kOverwriteError); + r.SetDetails(temp_save); + return r; + } + } else { + Result r(kFileError); + r.SetDetails(temp_save); + return r; + } +} + +ProjectSerializer::Result ProjectSerializer::Save(Project *project, QXmlStreamWriter *writer, const QString &filename, const QVector &only) +{ + writer->setAutoFormatting(true); + + writer->writeStartDocument(); + + writer->writeStartElement("olive"); + + // By default, save as last serializer which, assuming the instances are ordered correctly, + // will be the newest file format. But we may allow saving as older versions later on. + ProjectSerializer *serializer = instances_.last(); + + // Version is stored in YYMMDD from whenever the project format was last changed + // Allows easy integer math for checking project versions. + writer->writeTextElement(QStringLiteral("version"), QString::number(serializer->Version())); + + if (!filename.isEmpty()) { + writer->writeTextElement("url", filename); + } + + writer->writeStartElement(QStringLiteral("project")); + + serializer->Save(project, writer, only, nullptr); + + writer->writeEndElement(); // project + + writer->writeEndElement(); // olive + + writer->writeEndDocument(); + + if (writer->hasError()) { + return kXmlError; + } + + return kSuccess; +} + +bool ProjectSerializer::IsCancelled() const +{ + return false; +} + +} diff --git a/app/node/project/serializer/serializer.h b/app/node/project/serializer/serializer.h new file mode 100644 index 000000000..ae7db078a --- /dev/null +++ b/app/node/project/serializer/serializer.h @@ -0,0 +1,118 @@ +/*** + + 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 . + +***/ + +#ifndef PROJECTSERIALIZER_H +#define PROJECTSERIALIZER_H + +#include + +#include "common/define.h" +#include "node/project/project.h" + +namespace olive { + +/** + * @brief An abstract base class for serializing/deserializing project data + * + * The goal of this is to further abstract serialized project data from their + * in-memory representations. + */ +class ProjectSerializer +{ +public: + ProjectSerializer() = default; + + virtual ~ProjectSerializer(){}; + + DISABLE_COPY_MOVE(ProjectSerializer) + + enum ResultCode { + kSuccess, + kProjectTooOld, + kProjectTooNew, + kUnknownVersion, + kFileError, + kXmlError, + kOverwriteError, + }; + + class Result + { + public: + Result(const ResultCode &code) : + code_(code) + {} + + bool operator==(const ResultCode &code) { return code_ == code; } + bool operator!=(const ResultCode &code) { return code_ != code; } + + const ResultCode &code() const + { + return code_; + } + + const QString &GetDetails() const + { + return details_; + } + + void SetDetails(const QString &s) + { + details_ = s; + } + + private: + ResultCode code_; + + QString details_; + + }; + + static void Initialize(); + + static void Destroy(); + + static Result Load(Project *project, const QString &filename); + static Result Load(Project *project, QXmlStreamReader *read_device); + + static Result Save(Project *project, const QString &filename, const QVector &only = QVector()); + static Result Save(Project *project, QXmlStreamWriter *write_device, const QString &filename, const QVector &only = QVector()); + static Result Save(Project *project, QXmlStreamWriter *write_device, const QVector &only = QVector()) + { + return Save(project, write_device, QString(), only); + } + +protected: + virtual void Load(Project *project, QXmlStreamReader *reader, void *reserved) const = 0; + + virtual void Save(Project *project, QXmlStreamWriter *writer, const QVector &only, void *reserved) const {} + + virtual uint Version() const = 0; + + bool IsCancelled() const; + +private: + static QVector instances_; + +}; + +} + +#endif // PROJECTSERIALIZER_H diff --git a/app/node/project/serializer/serializer190219.cpp b/app/node/project/serializer/serializer190219.cpp new file mode 100644 index 000000000..663af8e1a --- /dev/null +++ b/app/node/project/serializer/serializer190219.cpp @@ -0,0 +1,29 @@ +/*** + + 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 "serializer190219.h" + +namespace olive { + +void ProjectSerializer190219::Load(Project *project, QXmlStreamReader *reader, void *reserved) const +{ +} + +} diff --git a/app/node/project/serializer/serializer190219.h b/app/node/project/serializer/serializer190219.h new file mode 100644 index 000000000..d0e282581 --- /dev/null +++ b/app/node/project/serializer/serializer190219.h @@ -0,0 +1,45 @@ +/*** + + 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 . + +***/ + +#ifndef PROJECTSERIALIZER190219_H +#define PROJECTSERIALIZER190219_H + +#include "serializer.h" + +namespace olive { + +class ProjectSerializer190219 : public ProjectSerializer +{ +public: + ProjectSerializer190219() = default; + +protected: + virtual void Load(Project *project, QXmlStreamReader *reader, void *reserved) const override; + + virtual uint Version() const override + { + return 190219; + } + +}; + +} + +#endif // SERIALIZER190219_H diff --git a/app/node/project/serializer/serializer210528.cpp b/app/node/project/serializer/serializer210528.cpp new file mode 100644 index 000000000..4cd941863 --- /dev/null +++ b/app/node/project/serializer/serializer210528.cpp @@ -0,0 +1,647 @@ +/*** + + 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 "serializer210528.h" + +#include "node/factory.h" + +namespace olive { + +void ProjectSerializer210528::Load(Project *project, QXmlStreamReader *reader, void *reserved) const +{ + XMLNodeData xml_node_data; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("url")) { + project->SetSavedURL(reader->readElementText()); + } else if (reader->name() == QStringLiteral("project")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("layout")) { + + // Since the main window's functions have to occur in the GUI thread (and we're likely + // loading in a secondary thread), we load all necessary data into a separate struct so we + // can continue loading and queue it with the main window so it can handle the data + // appropriately in its own thread. + + project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data)); + + } else if (reader->name() == QStringLiteral("uuid")) { + + project->SetUuid(QUuid::fromString(reader->readElementText())); + + } else if (reader->name() == QStringLiteral("nodes")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + bool is_root = false; + bool is_cm = false; + bool is_settings = false; + QString id; + + { + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + id = attr.value().toString(); + } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { + is_root = true; + } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { + is_cm = true; + } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { + is_settings = true; + } + } + } + + if (id.isEmpty()) { + qWarning() << "Failed to load node with empty ID"; + reader->skipCurrentElement(); + } else { + Node* node; + + if (is_root) { + node = project->root(); + } else if (is_cm) { + node = project->color_manager(); + } else if (is_settings) { + node = project->settings(); + } else { + node = NodeFactory::CreateFromID(id); + } + + if (!node) { + qWarning() << "Failed to find node with ID" << id; + reader->skipCurrentElement(); + } else { + LoadNode(node, xml_node_data, reader); + node->setParent(project); + } + } + } else { + reader->skipCurrentElement(); + } + } + + } else if (reader->name() == QStringLiteral("positions")) { + + while (XMLReadNextStartElement(reader)) { + + if (reader->name() == QStringLiteral("context")) { + + quintptr context_ptr = 0; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + context_ptr = attr.value().toULongLong(); + break; + } + } + + Node *context = xml_node_data.node_ptrs.value(context_ptr); + + if (!context) { + qWarning() << "Failed to find pointer for context"; + reader->skipCurrentElement(); + } else { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + quintptr node_ptr; + Node::Position node_pos; + + if (LoadPosition(reader, &node_ptr, &node_pos)) { + Node *node = xml_node_data.node_ptrs.value(node_ptr); + + if (node) { + context->SetNodePositionInContext(node, node_pos); + } else { + qWarning() << "Failed to find pointer for node position"; + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } + } + + } else { + + reader->skipCurrentElement(); + + } + + } + + } else { + + // Skip this + reader->skipCurrentElement(); + + } + } + } else { + reader->skipCurrentElement(); + } + } + + // Make connections + PostConnect(xml_node_data); +} + +void ProjectSerializer210528::LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const +{ + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("input")) { + LoadInput(node, reader, xml_node_data); + } else if (reader->name() == QStringLiteral("ptr")) { + xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), node); + } else if (reader->name() == QStringLiteral("label")) { + node->SetLabel(reader->readElementText()); + } else if (reader->name() == QStringLiteral("uuid")) { + node->SetUUID(QUuid::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("color")) { + node->SetOverrideColor(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("links")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("link")) { + xml_node_data.block_links.append({node, reader->readElementText().toULongLong()}); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("custom")) { + + LoadNodeCustom(reader, node, xml_node_data); + + } else if (reader->name() == QStringLiteral("connections")) { + // Load connections + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("connection")) { + QString param_id; + int ele = -1; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("element")) { + ele = attr.value().toInt(); + } else if (attr.name() == QStringLiteral("input")) { + param_id = attr.value().toString(); + } + } + + QString output_node_id; + QString output_param_id; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + output_node_id = reader->readElementText(); + } else if (reader->name() == QStringLiteral("output")) { + output_param_id = reader->readElementText(); + } else { + reader->skipCurrentElement(); + } + } + + xml_node_data.desired_connections.append({NodeInput(node, param_id, ele), output_node_id.toULongLong(), output_param_id}); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("hints")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("hint")) { + QString input; + int element = -1; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("input")) { + input = attr.value().toString(); + } else if (attr.name() == QStringLiteral("element")) { + element = attr.value().toInt(); + } + } + + Node::ValueHint vh; + LoadValueHint(&vh, reader); + node->SetValueHintForInput(input, vh, element); + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer210528::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const +{ + QString param_id; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + param_id = attr.value().toString(); + + break; + } + } + + if (param_id.isEmpty()) { + qWarning() << "Failed to load parameter with missing ID"; + reader->skipCurrentElement(); + return; + } + + if (!node->HasInputWithID(param_id)) { + qWarning() << "Failed to load parameter that didn't exist:" << param_id; + reader->skipCurrentElement(); + return; + } + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("primary")) { + // Load primary immediate + LoadImmediate(reader, node, param_id, -1, xml_node_data); + } else if (reader->name() == QStringLiteral("subelements")) { + // Load subelements + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("count")) { + node->InputArrayResize(param_id, attr.value().toInt()); + } + } + + int element_counter = 0; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("element")) { + LoadImmediate(reader, node, param_id, element_counter, xml_node_data); + + element_counter++; + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer210528::LoadImmediate(QXmlStreamReader *reader, Node *node, const QString& input, int element, XMLNodeData &xml_node_data) const +{ + Q_UNUSED(xml_node_data) + + NodeValue::Type data_type = node->GetInputDataType(input); + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("standard")) { + // Load standard value + int val_index = 0; + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("track")) { + QVariant value_on_track; + + if (data_type == NodeValue::kVideoParams) { + VideoParams vp; + vp.Load(reader); + value_on_track = QVariant::fromValue(vp); + } else if (data_type == NodeValue::kAudioParams) { + AudioParams ap; + ap.Load(reader); + value_on_track = QVariant::fromValue(ap); + } else { + QString value_text = reader->readElementText(); + + if (!value_text.isEmpty()) { + value_on_track = NodeValue::StringToValue(data_type, value_text, element); + } + } + + node->SetSplitStandardValueOnTrack(input, val_index, value_on_track, element); + + val_index++; + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("keyframing") && node->IsInputKeyframable(input)) { + node->SetInputIsKeyframing(input, reader->readElementText().toInt(), element); + } else if (reader->name() == QStringLiteral("keyframes")) { + int track = 0; + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("track")) { + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("key")) { + QString key_input; + rational key_time; + NodeKeyframe::Type key_type = NodeKeyframe::kLinear; + QVariant key_value; + QPointF key_in_handle; + QPointF key_out_handle; + + XMLAttributeLoop(reader, attr) { + if (IsCancelled()) { + return; + } + + if (attr.name() == QStringLiteral("input")) { + key_input = attr.value().toString(); + } else if (attr.name() == QStringLiteral("time")) { + key_time = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("type")) { + key_type = static_cast(attr.value().toInt()); + } else if (attr.name() == QStringLiteral("inhandlex")) { + key_in_handle.setX(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("inhandley")) { + key_in_handle.setY(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("outhandlex")) { + key_out_handle.setX(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("outhandley")) { + key_out_handle.setY(attr.value().toDouble()); + } + } + + key_value = NodeValue::StringToValue(data_type, reader->readElementText(), true); + + NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, key_input, node); + key->set_bezier_control_in(key_in_handle); + key->set_bezier_control_out(key_out_handle); + } else { + reader->skipCurrentElement(); + } + } + + track++; + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("csinput")) { + node->SetInputProperty(input, QStringLiteral("col_input"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("csdisplay")) { + node->SetInputProperty(input, QStringLiteral("col_display"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("csview")) { + node->SetInputProperty(input, QStringLiteral("col_view"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("cslook")) { + node->SetInputProperty(input, QStringLiteral("col_look"), reader->readElementText()); + } else { + reader->skipCurrentElement(); + } + } +} + +bool ProjectSerializer210528::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const +{ + bool got_node_ptr = false; + bool got_pos_x = false; + bool got_pos_y = false; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + *node_ptr = attr.value().toULongLong(); + got_node_ptr = true; + break; + } + } + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("x")) { + pos->position.setX(reader->readElementText().toDouble()); + got_pos_x = true; + } else if (reader->name() == QStringLiteral("y")) { + pos->position.setY(reader->readElementText().toDouble()); + got_pos_y = true; + } else if (reader->name() == QStringLiteral("expanded")) { + pos->expanded = reader->readElementText().toInt(); + } else { + reader->skipCurrentElement(); + } + } + + return got_node_ptr && got_pos_x && got_pos_y; +} + +void ProjectSerializer210528::PostConnect(const XMLNodeData &xml_node_data) const +{ + foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) { + if (Node *out = xml_node_data.node_ptrs.value(con.output_node)) { + // Use output param as hint tag since we grandfathered those in + Node::ValueHint hint(con.output_param); + + Node::ConnectEdge(out, con.input); + + con.input.node()->SetValueHintForInput(con.input.input(), hint, con.input.element()); + } + } + + foreach (const XMLNodeData::BlockLink& l, xml_node_data.block_links) { + Node *a = l.block; + Node *b = xml_node_data.node_ptrs.value(l.link); + + Node::Link(a, b); + } + + foreach (const XMLNodeData::GroupLink &l, xml_node_data.group_input_links) { + if (Node *input_node = xml_node_data.node_ptrs.value(l.input_node)) { + NodeInput resolved(input_node, l.input_id, l.input_element); + + l.group->AddInputPassthrough(resolved); + } + } + + for (auto it=xml_node_data.group_output_links.cbegin(); it!=xml_node_data.group_output_links.cend(); it++) { + if (Node *output_node = xml_node_data.node_ptrs.value(it.value())) { + it.key()->SetOutputPassthrough(output_node); + } + } +} + +void ProjectSerializer210528::LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const +{ + // Viewer-based nodes + if (ViewerOutput *viewer = dynamic_cast(node)) { + + Footage *footage = dynamic_cast(node); + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("points")) { + LoadTimelinePoints(reader, viewer->GetTimelinePoints()); + } else if (reader->name() == QStringLiteral("timestamp") && footage) { + footage->set_timestamp(reader->readElementText().toLongLong()); + } else { + reader->skipCurrentElement(); + } + } + + } else if (Track *track = dynamic_cast(node)) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("height")) { + track->SetTrackHeight(reader->readElementText().toDouble()); + } else { + reader->skipCurrentElement(); + } + } + + } else if (NodeGroup *group = dynamic_cast(node)) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthroughs")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthrough")) { + XMLNodeData::GroupLink link; + + link.group = group; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + link.input_node = reader->readElementText().toULongLong(); + } else if (reader->name() == QStringLiteral("input")) { + link.input_id = reader->readElementText(); + } else if (reader->name() == QStringLiteral("element")) { + link.input_element = reader->readElementText().toInt(); + } else { + reader->skipCurrentElement(); + } + } + + xml_node_data.group_input_links.append(link); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("outputpassthrough")) { + xml_node_data.group_output_links.insert(group, reader->readElementText().toULongLong()); + } else { + reader->skipCurrentElement(); + } + } + + } else { + reader->skipCurrentElement(); + } +} + +void ProjectSerializer210528::LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("markers")) { + LoadMarkerList(reader, points->markers()); + } else if (reader->name() == QStringLiteral("workarea")) { + LoadWorkArea(reader, points->workarea()); + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer210528::LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const +{ + rational range_in = workarea->in(); + rational range_out = workarea->out(); + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("enabled")) { + workarea->set_enabled(attr.value() != QStringLiteral("0")); + } else if (attr.name() == QStringLiteral("in")) { + range_in = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("out")) { + range_out = rational::fromString(attr.value().toString()); + } + } + + TimeRange loaded_workarea(range_in, range_out); + + if (loaded_workarea != workarea->range()) { + workarea->set_range(loaded_workarea); + } + + reader->skipCurrentElement(); +} + +void ProjectSerializer210528::LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("marker")) { + QString name; + rational in, out; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("name")) { + name = attr.value().toString(); + } else if (attr.name() == QStringLiteral("in")) { + in = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("out")) { + out = rational::fromString(attr.value().toString()); + } + } + + markers->AddMarker(TimeRange(in, out), name); + } + + reader->skipCurrentElement(); + } +} + +void ProjectSerializer210528::LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const +{ + QVector types; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("types")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("type")) { + types.append(static_cast(reader->readElementText().toInt())); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("index")) { + hint->set_index(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("tag")) { + hint->set_tag(reader->readElementText()); + } else { + reader->skipCurrentElement(); + } + } + + hint->set_type(types); +} + +} diff --git a/app/node/project/serializer/serializer210528.h b/app/node/project/serializer/serializer210528.h new file mode 100644 index 000000000..4e1660481 --- /dev/null +++ b/app/node/project/serializer/serializer210528.h @@ -0,0 +1,66 @@ +/*** + + 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 . + +***/ + +#ifndef SERIALIZER210528_H +#define SERIALIZER210528_H + +#include "serializer.h" + +namespace olive { + +class ProjectSerializer210528 : public ProjectSerializer +{ +public: + ProjectSerializer210528() = default; + +protected: + virtual void Load(Project *project, QXmlStreamReader *reader, void *reserved) const override; + + virtual uint Version() const override + { + return 210528; + } + +private: + void LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const; + + void LoadInput(Node *node, QXmlStreamReader* reader, XMLNodeData &xml_node_data) const; + + void LoadImmediate(QXmlStreamReader *reader, Node *node, const QString& input, int element, XMLNodeData& xml_node_data) const; + + bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const; + + void PostConnect(const XMLNodeData &xml_node_data) const; + + void LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const; + + void LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const; + + void LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const; + + void LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const; + + void LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const; + +}; + +} + +#endif // SERIALIZER211228_H diff --git a/app/node/project/serializer/serializer210907.cpp b/app/node/project/serializer/serializer210907.cpp new file mode 100644 index 000000000..2b2982ae4 --- /dev/null +++ b/app/node/project/serializer/serializer210907.cpp @@ -0,0 +1,639 @@ +/*** + + 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 "serializer210907.h" + +#include "node/factory.h" + +namespace olive { + +void ProjectSerializer210907::Load(Project *project, QXmlStreamReader *reader, void *reserved) const +{ + XMLNodeData xml_node_data; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("url")) { + project->SetSavedURL(reader->readElementText()); + } else if (reader->name() == QStringLiteral("project")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("layout")) { + + // Since the main window's functions have to occur in the GUI thread (and we're likely + // loading in a secondary thread), we load all necessary data into a separate struct so we + // can continue loading and queue it with the main window so it can handle the data + // appropriately in its own thread. + + project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data)); + + } else if (reader->name() == QStringLiteral("uuid")) { + + project->SetUuid(QUuid::fromString(reader->readElementText())); + + } else if (reader->name() == QStringLiteral("nodes")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + bool is_root = false; + bool is_cm = false; + bool is_settings = false; + QString id; + + { + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + id = attr.value().toString(); + } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { + is_root = true; + } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { + is_cm = true; + } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { + is_settings = true; + } + } + } + + if (id.isEmpty()) { + qWarning() << "Failed to load node with empty ID"; + reader->skipCurrentElement(); + } else { + Node* node; + + if (is_root) { + node = project->root(); + } else if (is_cm) { + node = project->color_manager(); + } else if (is_settings) { + node = project->settings(); + } else { + node = NodeFactory::CreateFromID(id); + } + + if (!node) { + qWarning() << "Failed to find node with ID" << id; + reader->skipCurrentElement(); + } else { + LoadNode(node, xml_node_data, reader); + node->setParent(project); + } + } + } else { + reader->skipCurrentElement(); + } + } + + } else if (reader->name() == QStringLiteral("positions")) { + + while (XMLReadNextStartElement(reader)) { + + if (reader->name() == QStringLiteral("context")) { + + quintptr context_ptr = 0; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + context_ptr = attr.value().toULongLong(); + break; + } + } + + Node *context = xml_node_data.node_ptrs.value(context_ptr); + + if (!context) { + qWarning() << "Failed to find pointer for context"; + reader->skipCurrentElement(); + } else { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + quintptr node_ptr; + Node::Position node_pos; + + if (LoadPosition(reader, &node_ptr, &node_pos)) { + Node *node = xml_node_data.node_ptrs.value(node_ptr); + + if (node) { + context->SetNodePositionInContext(node, node_pos); + } else { + qWarning() << "Failed to find pointer for node position"; + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } + } + + } else { + + reader->skipCurrentElement(); + + } + + } + + } else { + + // Skip this + reader->skipCurrentElement(); + + } + } + } else { + reader->skipCurrentElement(); + } + } + + // Make connections + PostConnect(xml_node_data); +} + +void ProjectSerializer210907::LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const +{ + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("input")) { + LoadInput(node, reader, xml_node_data); + } else if (reader->name() == QStringLiteral("ptr")) { + xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), node); + } else if (reader->name() == QStringLiteral("label")) { + node->SetLabel(reader->readElementText()); + } else if (reader->name() == QStringLiteral("uuid")) { + node->SetUUID(QUuid::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("color")) { + node->SetOverrideColor(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("links")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("link")) { + xml_node_data.block_links.append({node, reader->readElementText().toULongLong()}); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("custom")) { + + LoadNodeCustom(reader, node, xml_node_data); + + } else if (reader->name() == QStringLiteral("connections")) { + // Load connections + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("connection")) { + QString param_id; + int ele = -1; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("element")) { + ele = attr.value().toInt(); + } else if (attr.name() == QStringLiteral("input")) { + param_id = attr.value().toString(); + } + } + + QString output_node_id; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("output")) { + output_node_id = reader->readElementText(); + } else { + reader->skipCurrentElement(); + } + } + + xml_node_data.desired_connections.append({NodeInput(node, param_id, ele), output_node_id.toULongLong(), QString()}); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("hints")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("hint")) { + QString input; + int element = -1; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("input")) { + input = attr.value().toString(); + } else if (attr.name() == QStringLiteral("element")) { + element = attr.value().toInt(); + } + } + + Node::ValueHint vh; + LoadValueHint(&vh, reader); + node->SetValueHintForInput(input, vh, element); + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer210907::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const +{ + QString param_id; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + param_id = attr.value().toString(); + + break; + } + } + + if (param_id.isEmpty()) { + qWarning() << "Failed to load parameter with missing ID"; + reader->skipCurrentElement(); + return; + } + + if (!node->HasInputWithID(param_id)) { + qWarning() << "Failed to load parameter that didn't exist:" << param_id; + reader->skipCurrentElement(); + return; + } + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("primary")) { + // Load primary immediate + LoadImmediate(reader, node, param_id, -1, xml_node_data); + } else if (reader->name() == QStringLiteral("subelements")) { + // Load subelements + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("count")) { + node->InputArrayResize(param_id, attr.value().toInt()); + } + } + + int element_counter = 0; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("element")) { + LoadImmediate(reader, node, param_id, element_counter, xml_node_data); + + element_counter++; + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer210907::LoadImmediate(QXmlStreamReader *reader, Node *node, const QString& input, int element, XMLNodeData &xml_node_data) const +{ + Q_UNUSED(xml_node_data) + + NodeValue::Type data_type = node->GetInputDataType(input); + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("standard")) { + // Load standard value + int val_index = 0; + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("track")) { + QVariant value_on_track; + + if (data_type == NodeValue::kVideoParams) { + VideoParams vp; + vp.Load(reader); + value_on_track = QVariant::fromValue(vp); + } else if (data_type == NodeValue::kAudioParams) { + AudioParams ap; + ap.Load(reader); + value_on_track = QVariant::fromValue(ap); + } else { + QString value_text = reader->readElementText(); + + if (!value_text.isEmpty()) { + value_on_track = NodeValue::StringToValue(data_type, value_text, element); + } + } + + node->SetSplitStandardValueOnTrack(input, val_index, value_on_track, element); + + val_index++; + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("keyframing") && node->IsInputKeyframable(input)) { + node->SetInputIsKeyframing(input, reader->readElementText().toInt(), element); + } else if (reader->name() == QStringLiteral("keyframes")) { + int track = 0; + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("track")) { + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("key")) { + QString key_input; + rational key_time; + NodeKeyframe::Type key_type = NodeKeyframe::kLinear; + QVariant key_value; + QPointF key_in_handle; + QPointF key_out_handle; + + XMLAttributeLoop(reader, attr) { + if (IsCancelled()) { + return; + } + + if (attr.name() == QStringLiteral("input")) { + key_input = attr.value().toString(); + } else if (attr.name() == QStringLiteral("time")) { + key_time = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("type")) { + key_type = static_cast(attr.value().toInt()); + } else if (attr.name() == QStringLiteral("inhandlex")) { + key_in_handle.setX(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("inhandley")) { + key_in_handle.setY(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("outhandlex")) { + key_out_handle.setX(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("outhandley")) { + key_out_handle.setY(attr.value().toDouble()); + } + } + + key_value = NodeValue::StringToValue(data_type, reader->readElementText(), true); + + NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, key_input, node); + key->set_bezier_control_in(key_in_handle); + key->set_bezier_control_out(key_out_handle); + } else { + reader->skipCurrentElement(); + } + } + + track++; + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("csinput")) { + node->SetInputProperty(input, QStringLiteral("col_input"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("csdisplay")) { + node->SetInputProperty(input, QStringLiteral("col_display"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("csview")) { + node->SetInputProperty(input, QStringLiteral("col_view"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("cslook")) { + node->SetInputProperty(input, QStringLiteral("col_look"), reader->readElementText()); + } else { + reader->skipCurrentElement(); + } + } +} + +bool ProjectSerializer210907::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const +{ + bool got_node_ptr = false; + bool got_pos_x = false; + bool got_pos_y = false; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + *node_ptr = attr.value().toULongLong(); + got_node_ptr = true; + break; + } + } + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("x")) { + pos->position.setX(reader->readElementText().toDouble()); + got_pos_x = true; + } else if (reader->name() == QStringLiteral("y")) { + pos->position.setY(reader->readElementText().toDouble()); + got_pos_y = true; + } else if (reader->name() == QStringLiteral("expanded")) { + pos->expanded = reader->readElementText().toInt(); + } else { + reader->skipCurrentElement(); + } + } + + return got_node_ptr && got_pos_x && got_pos_y; +} + +void ProjectSerializer210907::PostConnect(const XMLNodeData &xml_node_data) const +{ + foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) { + if (Node *out = xml_node_data.node_ptrs.value(con.output_node)) { + Node::ConnectEdge(out, con.input); + } + } + + foreach (const XMLNodeData::BlockLink& l, xml_node_data.block_links) { + Node *a = l.block; + Node *b = xml_node_data.node_ptrs.value(l.link); + + Node::Link(a, b); + } + + foreach (const XMLNodeData::GroupLink &l, xml_node_data.group_input_links) { + if (Node *input_node = xml_node_data.node_ptrs.value(l.input_node)) { + NodeInput resolved(input_node, l.input_id, l.input_element); + + l.group->AddInputPassthrough(resolved); + } + } + + for (auto it=xml_node_data.group_output_links.cbegin(); it!=xml_node_data.group_output_links.cend(); it++) { + if (Node *output_node = xml_node_data.node_ptrs.value(it.value())) { + it.key()->SetOutputPassthrough(output_node); + } + } +} + +void ProjectSerializer210907::LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const +{ + // Viewer-based nodes + if (ViewerOutput *viewer = dynamic_cast(node)) { + + Footage *footage = dynamic_cast(node); + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("points")) { + LoadTimelinePoints(reader, viewer->GetTimelinePoints()); + } else if (reader->name() == QStringLiteral("timestamp") && footage) { + footage->set_timestamp(reader->readElementText().toLongLong()); + } else { + reader->skipCurrentElement(); + } + } + + } else if (Track *track = dynamic_cast(node)) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("height")) { + track->SetTrackHeight(reader->readElementText().toDouble()); + } else { + reader->skipCurrentElement(); + } + } + + } else if (NodeGroup *group = dynamic_cast(node)) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthroughs")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthrough")) { + XMLNodeData::GroupLink link; + + link.group = group; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + link.input_node = reader->readElementText().toULongLong(); + } else if (reader->name() == QStringLiteral("input")) { + link.input_id = reader->readElementText(); + } else if (reader->name() == QStringLiteral("element")) { + link.input_element = reader->readElementText().toInt(); + } else { + reader->skipCurrentElement(); + } + } + + xml_node_data.group_input_links.append(link); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("outputpassthrough")) { + xml_node_data.group_output_links.insert(group, reader->readElementText().toULongLong()); + } else { + reader->skipCurrentElement(); + } + } + + } else { + reader->skipCurrentElement(); + } +} + +void ProjectSerializer210907::LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("markers")) { + LoadMarkerList(reader, points->markers()); + } else if (reader->name() == QStringLiteral("workarea")) { + LoadWorkArea(reader, points->workarea()); + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer210907::LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const +{ + rational range_in = workarea->in(); + rational range_out = workarea->out(); + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("enabled")) { + workarea->set_enabled(attr.value() != QStringLiteral("0")); + } else if (attr.name() == QStringLiteral("in")) { + range_in = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("out")) { + range_out = rational::fromString(attr.value().toString()); + } + } + + TimeRange loaded_workarea(range_in, range_out); + + if (loaded_workarea != workarea->range()) { + workarea->set_range(loaded_workarea); + } + + reader->skipCurrentElement(); +} + +void ProjectSerializer210907::LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("marker")) { + QString name; + rational in, out; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("name")) { + name = attr.value().toString(); + } else if (attr.name() == QStringLiteral("in")) { + in = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("out")) { + out = rational::fromString(attr.value().toString()); + } + } + + markers->AddMarker(TimeRange(in, out), name); + } + + reader->skipCurrentElement(); + } +} + +void ProjectSerializer210907::LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const +{ + QVector types; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("types")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("type")) { + types.append(static_cast(reader->readElementText().toInt())); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("index")) { + hint->set_index(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("tag")) { + hint->set_tag(reader->readElementText()); + } else { + reader->skipCurrentElement(); + } + } + + hint->set_type(types); +} + +} diff --git a/app/node/project/serializer/serializer210907.h b/app/node/project/serializer/serializer210907.h new file mode 100644 index 000000000..6f4ff76fa --- /dev/null +++ b/app/node/project/serializer/serializer210907.h @@ -0,0 +1,66 @@ +/*** + + 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 . + +***/ + +#ifndef SERIALIZER210907_H +#define SERIALIZER210907_H + +#include "serializer.h" + +namespace olive { + +class ProjectSerializer210907 : public ProjectSerializer +{ +public: + ProjectSerializer210907() = default; + +protected: + virtual void Load(Project *project, QXmlStreamReader *reader, void *reserved) const override; + + virtual uint Version() const override + { + return 210907; + } + +private: + void LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const; + + void LoadInput(Node *node, QXmlStreamReader* reader, XMLNodeData &xml_node_data) const; + + void LoadImmediate(QXmlStreamReader *reader, Node *node, const QString& input, int element, XMLNodeData& xml_node_data) const; + + bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const; + + void PostConnect(const XMLNodeData &xml_node_data) const; + + void LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const; + + void LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const; + + void LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const; + + void LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const; + + void LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const; + +}; + +} + +#endif // SERIALIZER211228_H diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp new file mode 100644 index 000000000..757bd4f12 --- /dev/null +++ b/app/node/project/serializer/serializer211228.cpp @@ -0,0 +1,926 @@ +/*** + + 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 "serializer211228.h" + +#include "node/factory.h" + +namespace olive { + +void ProjectSerializer211228::Load(Project *project, QXmlStreamReader *reader, void *reserved) const +{ + XMLNodeData xml_node_data; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("url")) { + project->SetSavedURL(reader->readElementText()); + } else if (reader->name() == QStringLiteral("project")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("layout")) { + + // Since the main window's functions have to occur in the GUI thread (and we're likely + // loading in a secondary thread), we load all necessary data into a separate struct so we + // can continue loading and queue it with the main window so it can handle the data + // appropriately in its own thread. + + project->SetLayoutInfo(MainWindowLayoutInfo::fromXml(reader, xml_node_data)); + + } else if (reader->name() == QStringLiteral("uuid")) { + + project->SetUuid(QUuid::fromString(reader->readElementText())); + + } else if (reader->name() == QStringLiteral("nodes")) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + bool is_root = false; + bool is_cm = false; + bool is_settings = false; + QString id; + + { + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + id = attr.value().toString(); + } else if (attr.name() == QStringLiteral("root") && attr.value() == QStringLiteral("1")) { + is_root = true; + } else if (attr.name() == QStringLiteral("cm") && attr.value() == QStringLiteral("1")) { + is_cm = true; + } else if (attr.name() == QStringLiteral("settings") && attr.value() == QStringLiteral("1")) { + is_settings = true; + } + } + } + + if (id.isEmpty()) { + qWarning() << "Failed to load node with empty ID"; + reader->skipCurrentElement(); + } else { + Node* node; + + if (is_root) { + node = project->root(); + } else if (is_cm) { + node = project->color_manager(); + } else if (is_settings) { + node = project->settings(); + } else { + node = NodeFactory::CreateFromID(id); + } + + if (!node) { + qWarning() << "Failed to find node with ID" << id; + reader->skipCurrentElement(); + } else { + LoadNode(node, xml_node_data, reader); + node->setParent(project); + } + } + } else { + reader->skipCurrentElement(); + } + } + + } else if (reader->name() == QStringLiteral("positions")) { + + while (XMLReadNextStartElement(reader)) { + + if (reader->name() == QStringLiteral("context")) { + + quintptr context_ptr = 0; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + context_ptr = attr.value().toULongLong(); + break; + } + } + + Node *context = xml_node_data.node_ptrs.value(context_ptr); + + if (!context) { + qWarning() << "Failed to find pointer for context"; + reader->skipCurrentElement(); + } else { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + quintptr node_ptr; + Node::Position node_pos; + + if (LoadPosition(reader, &node_ptr, &node_pos)) { + Node *node = xml_node_data.node_ptrs.value(node_ptr); + + if (node) { + context->SetNodePositionInContext(node, node_pos); + } else { + qWarning() << "Failed to find pointer for node position"; + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } + } + + } else { + + reader->skipCurrentElement(); + + } + + } + + } else { + + // Skip this + reader->skipCurrentElement(); + + } + } + } else { + reader->skipCurrentElement(); + } + } + + // Make connections + PostConnect(xml_node_data); +} + +void ProjectSerializer211228::Save(Project *project, QXmlStreamWriter *writer, const QVector &only, void *reserved) const +{ + writer->writeTextElement(QStringLiteral("uuid"), project->GetUuid().toString()); + + writer->writeStartElement(QStringLiteral("nodes")); + + const QVector &using_node_list = (only.isEmpty()) ? project->nodes() : only; + + foreach (Node* node, using_node_list) { + writer->writeStartElement(QStringLiteral("node")); + + if (node == project->root()) { + writer->writeAttribute(QStringLiteral("root"), QStringLiteral("1")); + } else if (node == project->color_manager()) { + writer->writeAttribute(QStringLiteral("cm"), QStringLiteral("1")); + } else if (node == project->settings()) { + writer->writeAttribute(QStringLiteral("settings"), QStringLiteral("1")); + } + + writer->writeAttribute(QStringLiteral("id"), node->id()); + + SaveNode(node, writer); + + writer->writeEndElement(); // node + } + + writer->writeEndElement(); // nodes + + writer->writeStartElement(QStringLiteral("positions")); + + foreach (Node* context, using_node_list) { + const Node::PositionMap &map = context->GetContextPositions(); + + if (!map.isEmpty()) { + writer->writeStartElement(QStringLiteral("context")); + + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(context))); + + for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { + if (only.isEmpty() || only.contains(jt.key())) { + writer->writeStartElement(QStringLiteral("node")); + SavePosition(writer, jt.key(), jt.value()); + writer->writeEndElement(); // node + } + } + + writer->writeEndElement(); // context + } + } + + writer->writeEndElement(); // positions + + // Save main window project layout + project->GetLayoutInfo().toXml(writer); +} + +void ProjectSerializer211228::LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const +{ + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("input")) { + LoadInput(node, reader, xml_node_data); + } else if (reader->name() == QStringLiteral("ptr")) { + xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), node); + } else if (reader->name() == QStringLiteral("label")) { + node->SetLabel(reader->readElementText()); + } else if (reader->name() == QStringLiteral("uuid")) { + node->SetUUID(QUuid::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("color")) { + node->SetOverrideColor(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("links")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("link")) { + xml_node_data.block_links.append({node, reader->readElementText().toULongLong()}); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("custom")) { + + LoadNodeCustom(reader, node, xml_node_data); + + } else if (reader->name() == QStringLiteral("connections")) { + // Load connections + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("connection")) { + QString param_id; + int ele = -1; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("element")) { + ele = attr.value().toInt(); + } else if (attr.name() == QStringLiteral("input")) { + param_id = attr.value().toString(); + } + } + + QString output_node_id; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("output")) { + output_node_id = reader->readElementText(); + } else { + reader->skipCurrentElement(); + } + } + + xml_node_data.desired_connections.append({NodeInput(node, param_id, ele), output_node_id.toULongLong(), QString()}); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("hints")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("hint")) { + QString input; + int element = -1; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("input")) { + input = attr.value().toString(); + } else if (attr.name() == QStringLiteral("element")) { + element = attr.value().toInt(); + } + } + + Node::ValueHint vh; + LoadValueHint(&vh, reader); + node->SetValueHintForInput(input, vh, element); + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer211228::SaveNode(Node *node, QXmlStreamWriter *writer) const +{ + writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); + + writer->writeTextElement(QStringLiteral("uuid"), node->GetUUID().toString()); + writer->writeTextElement(QStringLiteral("label"), node->GetLabel()); + writer->writeTextElement(QStringLiteral("color"), QString::number(node->GetOverrideColor())); + + foreach (const QString& input, node->inputs()) { + writer->writeStartElement(QStringLiteral("input")); + + SaveInput(node, writer, input); + + writer->writeEndElement(); // input + } + + writer->writeStartElement(QStringLiteral("links")); + foreach (Node* link, node->links()) { + writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast(link))); + } + writer->writeEndElement(); // links + + writer->writeStartElement(QStringLiteral("connections")); + for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { + writer->writeStartElement(QStringLiteral("connection")); + + writer->writeAttribute(QStringLiteral("input"), it->first.input()); + writer->writeAttribute(QStringLiteral("element"), QString::number(it->first.element())); + + writer->writeTextElement(QStringLiteral("output"), QString::number(reinterpret_cast(it->second))); + + writer->writeEndElement(); // connection + } + writer->writeEndElement(); // connections + + writer->writeStartElement(QStringLiteral("hints")); + for (auto it=node->GetValueHints().cbegin(); it!=node->GetValueHints().cend(); it++) { + writer->writeStartElement(QStringLiteral("hint")); + + writer->writeAttribute(QStringLiteral("input"), it.key().input); + writer->writeAttribute(QStringLiteral("element"), QString::number(it.key().element)); + + SaveValueHint(&it.value(), writer); + + writer->writeEndElement(); // hint + } + writer->writeEndElement(); + + writer->writeStartElement(QStringLiteral("custom")); + + SaveNodeCustom(writer, node); + + writer->writeEndElement(); // custom +} + +void ProjectSerializer211228::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const +{ + QString param_id; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("id")) { + param_id = attr.value().toString(); + + break; + } + } + + if (param_id.isEmpty()) { + qWarning() << "Failed to load parameter with missing ID"; + reader->skipCurrentElement(); + return; + } + + if (!node->HasInputWithID(param_id)) { + qWarning() << "Failed to load parameter that didn't exist:" << param_id; + reader->skipCurrentElement(); + return; + } + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("primary")) { + // Load primary immediate + LoadImmediate(reader, node, param_id, -1, xml_node_data); + } else if (reader->name() == QStringLiteral("subelements")) { + // Load subelements + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("count")) { + node->InputArrayResize(param_id, attr.value().toInt()); + } + } + + int element_counter = 0; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("element")) { + LoadImmediate(reader, node, param_id, element_counter, xml_node_data); + + element_counter++; + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer211228::SaveInput(Node *node, QXmlStreamWriter *writer, const QString &id) const +{ + writer->writeAttribute(QStringLiteral("id"), id); + + writer->writeStartElement(QStringLiteral("primary")); + + SaveImmediate(writer, node, id, -1); + + writer->writeEndElement(); // primary + + writer->writeStartElement(QStringLiteral("subelements")); + + int arr_sz = node->InputArraySize(id); + + writer->writeAttribute(QStringLiteral("count"), QString::number(arr_sz)); + + for (int i=0; iwriteStartElement(QStringLiteral("element")); + + SaveImmediate(writer, node, id, i); + + writer->writeEndElement(); // element + } + + writer->writeEndElement(); // subelements +} + +void ProjectSerializer211228::LoadImmediate(QXmlStreamReader *reader, Node *node, const QString& input, int element, XMLNodeData &xml_node_data) const +{ + Q_UNUSED(xml_node_data) + + NodeValue::Type data_type = node->GetInputDataType(input); + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("standard")) { + // Load standard value + int val_index = 0; + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("track")) { + QVariant value_on_track; + + if (data_type == NodeValue::kVideoParams) { + VideoParams vp; + vp.Load(reader); + value_on_track = QVariant::fromValue(vp); + } else if (data_type == NodeValue::kAudioParams) { + AudioParams ap; + ap.Load(reader); + value_on_track = QVariant::fromValue(ap); + } else { + QString value_text = reader->readElementText(); + + if (!value_text.isEmpty()) { + value_on_track = NodeValue::StringToValue(data_type, value_text, element); + } + } + + node->SetSplitStandardValueOnTrack(input, val_index, value_on_track, element); + + val_index++; + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("keyframing") && node->IsInputKeyframable(input)) { + node->SetInputIsKeyframing(input, reader->readElementText().toInt(), element); + } else if (reader->name() == QStringLiteral("keyframes")) { + int track = 0; + + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("track")) { + while (XMLReadNextStartElement(reader)) { + if (IsCancelled()) { + return; + } + + if (reader->name() == QStringLiteral("key")) { + QString key_input; + rational key_time; + NodeKeyframe::Type key_type = NodeKeyframe::kLinear; + QVariant key_value; + QPointF key_in_handle; + QPointF key_out_handle; + + XMLAttributeLoop(reader, attr) { + if (IsCancelled()) { + return; + } + + if (attr.name() == QStringLiteral("input")) { + key_input = attr.value().toString(); + } else if (attr.name() == QStringLiteral("time")) { + key_time = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("type")) { + key_type = static_cast(attr.value().toInt()); + } else if (attr.name() == QStringLiteral("inhandlex")) { + key_in_handle.setX(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("inhandley")) { + key_in_handle.setY(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("outhandlex")) { + key_out_handle.setX(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("outhandley")) { + key_out_handle.setY(attr.value().toDouble()); + } + } + + key_value = NodeValue::StringToValue(data_type, reader->readElementText(), true); + + NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, key_input, node); + key->set_bezier_control_in(key_in_handle); + key->set_bezier_control_out(key_out_handle); + } else { + reader->skipCurrentElement(); + } + } + + track++; + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("csinput")) { + node->SetInputProperty(input, QStringLiteral("col_input"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("csdisplay")) { + node->SetInputProperty(input, QStringLiteral("col_display"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("csview")) { + node->SetInputProperty(input, QStringLiteral("col_view"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("cslook")) { + node->SetInputProperty(input, QStringLiteral("col_look"), reader->readElementText()); + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer211228::SaveImmediate(QXmlStreamWriter *writer, Node *node, const QString& input, int element) const +{ + if (node->IsInputKeyframable(input)) { + writer->writeTextElement(QStringLiteral("keyframing"), QString::number(node->IsInputKeyframing(input, element))); + } + + NodeValue::Type data_type = node->GetInputDataType(input); + + // Write standard value + writer->writeStartElement(QStringLiteral("standard")); + + foreach (const QVariant& v, node->GetSplitStandardValue(input, element)) { + writer->writeStartElement(QStringLiteral("track")); + + if (data_type == NodeValue::kVideoParams) { + v.value().Save(writer); + } else if (data_type == NodeValue::kAudioParams) { + v.value().Save(writer); + } else { + writer->writeCharacters(NodeValue::ValueToString(data_type, v, true)); + } + + writer->writeEndElement(); // track + } + + writer->writeEndElement(); // standard + + // Write keyframes + writer->writeStartElement(QStringLiteral("keyframes")); + + for (const NodeKeyframeTrack& track : node->GetKeyframeTracks(input, element)) { + writer->writeStartElement(QStringLiteral("track")); + + for (NodeKeyframe* key : track) { + writer->writeStartElement(QStringLiteral("key")); + + writer->writeAttribute(QStringLiteral("input"), key->input()); + writer->writeAttribute(QStringLiteral("time"), key->time().toString()); + writer->writeAttribute(QStringLiteral("type"), QString::number(key->type())); + writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(key->bezier_control_in().x())); + writer->writeAttribute(QStringLiteral("inhandley"), QString::number(key->bezier_control_in().y())); + writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(key->bezier_control_out().x())); + writer->writeAttribute(QStringLiteral("outhandley"), QString::number(key->bezier_control_out().y())); + + writer->writeCharacters(NodeValue::ValueToString(data_type, key->value(), true)); + + writer->writeEndElement(); // key + } + + writer->writeEndElement(); // track + } + + writer->writeEndElement(); // keyframes + + if (data_type == NodeValue::kColor) { + // Save color management information + writer->writeTextElement(QStringLiteral("csinput"), node->GetInputProperty(input, QStringLiteral("col_input")).toString()); + writer->writeTextElement(QStringLiteral("csdisplay"), node->GetInputProperty(input, QStringLiteral("col_display")).toString()); + writer->writeTextElement(QStringLiteral("csview"), node->GetInputProperty(input, QStringLiteral("col_view")).toString()); + writer->writeTextElement(QStringLiteral("cslook"), node->GetInputProperty(input, QStringLiteral("col_look")).toString()); + } +} + +bool ProjectSerializer211228::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const +{ + bool got_node_ptr = false; + bool got_pos_x = false; + bool got_pos_y = false; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + *node_ptr = attr.value().toULongLong(); + got_node_ptr = true; + break; + } + } + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("x")) { + pos->position.setX(reader->readElementText().toDouble()); + got_pos_x = true; + } else if (reader->name() == QStringLiteral("y")) { + pos->position.setY(reader->readElementText().toDouble()); + got_pos_y = true; + } else if (reader->name() == QStringLiteral("expanded")) { + pos->expanded = reader->readElementText().toInt(); + } else { + reader->skipCurrentElement(); + } + } + + return got_node_ptr && got_pos_x && got_pos_y; +} + +void ProjectSerializer211228::SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos) const +{ + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(node))); + + writer->writeTextElement(QStringLiteral("x"), QString::number(pos.position.x())); + writer->writeTextElement(QStringLiteral("y"), QString::number(pos.position.y())); + writer->writeTextElement(QStringLiteral("expanded"), QString::number(pos.expanded)); +} + +void ProjectSerializer211228::PostConnect(const XMLNodeData &xml_node_data) const +{ + foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) { + if (Node *out = xml_node_data.node_ptrs.value(con.output_node)) { + Node::ConnectEdge(out, con.input); + } + } + + foreach (const XMLNodeData::BlockLink& l, xml_node_data.block_links) { + Node *a = l.block; + Node *b = xml_node_data.node_ptrs.value(l.link); + + Node::Link(a, b); + } + + foreach (const XMLNodeData::GroupLink &l, xml_node_data.group_input_links) { + if (Node *input_node = xml_node_data.node_ptrs.value(l.input_node)) { + NodeInput resolved(input_node, l.input_id, l.input_element); + + l.group->AddInputPassthrough(resolved); + } + } + + for (auto it=xml_node_data.group_output_links.cbegin(); it!=xml_node_data.group_output_links.cend(); it++) { + if (Node *output_node = xml_node_data.node_ptrs.value(it.value())) { + it.key()->SetOutputPassthrough(output_node); + } + } +} + +void ProjectSerializer211228::LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const +{ + // Viewer-based nodes + if (ViewerOutput *viewer = dynamic_cast(node)) { + + Footage *footage = dynamic_cast(node); + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("points")) { + LoadTimelinePoints(reader, viewer->GetTimelinePoints()); + } else if (reader->name() == QStringLiteral("timestamp") && footage) { + footage->set_timestamp(reader->readElementText().toLongLong()); + } else { + reader->skipCurrentElement(); + } + } + + } else if (Track *track = dynamic_cast(node)) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("height")) { + track->SetTrackHeight(reader->readElementText().toDouble()); + } else { + reader->skipCurrentElement(); + } + } + + } else if (NodeGroup *group = dynamic_cast(node)) { + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthroughs")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthrough")) { + XMLNodeData::GroupLink link; + + link.group = group; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + link.input_node = reader->readElementText().toULongLong(); + } else if (reader->name() == QStringLiteral("input")) { + link.input_id = reader->readElementText(); + } else if (reader->name() == QStringLiteral("element")) { + link.input_element = reader->readElementText().toInt(); + } else { + reader->skipCurrentElement(); + } + } + + xml_node_data.group_input_links.append(link); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("outputpassthrough")) { + xml_node_data.group_output_links.insert(group, reader->readElementText().toULongLong()); + } else { + reader->skipCurrentElement(); + } + } + + } else { + reader->skipCurrentElement(); + } +} + +void ProjectSerializer211228::SaveNodeCustom(QXmlStreamWriter *writer, Node *node) const +{ + if (ViewerOutput *viewer = dynamic_cast(node)) { + // Write TimelinePoints + writer->writeStartElement(QStringLiteral("points")); + SaveTimelinePoints(writer, viewer->GetTimelinePoints()); + writer->writeEndElement(); // points + + if (Footage *footage = dynamic_cast(node)) { + writer->writeTextElement(QStringLiteral("timestamp"), QString::number(footage->timestamp())); + } + } else if (Track *track = dynamic_cast(node)) { + writer->writeTextElement(QStringLiteral("height"), QString::number(track->GetTrackHeight())); + } else if (NodeGroup *group = dynamic_cast(node)) { + writer->writeStartElement(QStringLiteral("inputpassthroughs")); + + foreach (const NodeInput &ip, group->GetInputPassthroughs()) { + writer->writeStartElement(QStringLiteral("inputpassthrough")); + writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.node()))); + writer->writeTextElement(QStringLiteral("input"), ip.input()); + writer->writeTextElement(QStringLiteral("element"), QString::number(ip.element())); + writer->writeEndElement(); // input + } + + writer->writeEndElement(); // inputpassthroughs + + writer->writeTextElement(QStringLiteral("outputpassthrough"), QString::number(reinterpret_cast(group->GetOutputPassthrough()))); + } +} + +void ProjectSerializer211228::LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("markers")) { + LoadMarkerList(reader, points->markers()); + } else if (reader->name() == QStringLiteral("workarea")) { + LoadWorkArea(reader, points->workarea()); + } else { + reader->skipCurrentElement(); + } + } +} + +void ProjectSerializer211228::SaveTimelinePoints(QXmlStreamWriter *writer, TimelinePoints *points) const +{ + writer->writeStartElement(QStringLiteral("workarea")); + SaveWorkArea(writer, points->workarea()); + writer->writeEndElement(); // workarea + + writer->writeStartElement(QStringLiteral("markers")); + SaveMarkerList(writer, points->markers()); + writer->writeEndElement(); // markers +} + +void ProjectSerializer211228::LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const +{ + rational range_in = workarea->in(); + rational range_out = workarea->out(); + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("enabled")) { + workarea->set_enabled(attr.value() != QStringLiteral("0")); + } else if (attr.name() == QStringLiteral("in")) { + range_in = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("out")) { + range_out = rational::fromString(attr.value().toString()); + } + } + + TimeRange loaded_workarea(range_in, range_out); + + if (loaded_workarea != workarea->range()) { + workarea->set_range(loaded_workarea); + } + + reader->skipCurrentElement(); +} + +void ProjectSerializer211228::SaveWorkArea(QXmlStreamWriter *writer, TimelineWorkArea *workarea) const +{ + writer->writeAttribute(QStringLiteral("enabled"), QString::number(workarea->enabled())); + writer->writeAttribute(QStringLiteral("in"), workarea->in().toString()); + writer->writeAttribute(QStringLiteral("out"), workarea->out().toString()); +} + +void ProjectSerializer211228::LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("marker")) { + QString name; + rational in, out; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("name")) { + name = attr.value().toString(); + } else if (attr.name() == QStringLiteral("in")) { + in = rational::fromString(attr.value().toString()); + } else if (attr.name() == QStringLiteral("out")) { + out = rational::fromString(attr.value().toString()); + } + } + + markers->AddMarker(TimeRange(in, out), name); + } + + reader->skipCurrentElement(); + } +} + +void ProjectSerializer211228::SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const +{ + foreach (TimelineMarker* marker, markers->list()) { + writer->writeStartElement(QStringLiteral("marker")); + + writer->writeAttribute(QStringLiteral("name"), marker->name()); + + writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString()); + writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString()); + + writer->writeEndElement(); // marker + } +} + +void ProjectSerializer211228::LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const +{ + QVector types; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("types")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("type")) { + types.append(static_cast(reader->readElementText().toInt())); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("index")) { + hint->set_index(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("tag")) { + hint->set_tag(reader->readElementText()); + } else { + reader->skipCurrentElement(); + } + } + + hint->set_type(types); +} + +void ProjectSerializer211228::SaveValueHint(const Node::ValueHint *hint, QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("types")); + + for (auto it=hint->types().cbegin(); it!=hint->types().cend(); it++) { + writer->writeTextElement(QStringLiteral("type"), QString::number(*it)); + } + + writer->writeEndElement(); // types + + writer->writeTextElement(QStringLiteral("index"), QString::number(hint->index())); + + writer->writeTextElement(QStringLiteral("tag"), hint->tag()); +} + +} diff --git a/app/node/project/serializer/serializer211228.h b/app/node/project/serializer/serializer211228.h new file mode 100644 index 000000000..0ad6762b9 --- /dev/null +++ b/app/node/project/serializer/serializer211228.h @@ -0,0 +1,86 @@ +/*** + + 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 . + +***/ + +#ifndef SERIALIZER211228_H +#define SERIALIZER211228_H + +#include "serializer.h" + +namespace olive { + +class ProjectSerializer211228 : public ProjectSerializer +{ +public: + ProjectSerializer211228() = default; + +protected: + virtual void Load(Project *project, QXmlStreamReader *reader, void *reserved) const override; + + virtual void Save(Project *project, QXmlStreamWriter *writer, const QVector &only, void *reserved) const override; + + virtual uint Version() const override + { + return 211228; + } + +private: + void LoadNode(Node *node, XMLNodeData &xml_node_data, QXmlStreamReader *reader) const; + + void SaveNode(Node *node, QXmlStreamWriter *writer) const; + + void LoadInput(Node *node, QXmlStreamReader* reader, XMLNodeData &xml_node_data) const; + + void SaveInput(Node *node, QXmlStreamWriter* writer, const QString& id) const; + + void LoadImmediate(QXmlStreamReader *reader, Node *node, const QString& input, int element, XMLNodeData& xml_node_data) const; + + void SaveImmediate(QXmlStreamWriter *writer, Node *node, const QString &input, int element) const; + + bool LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const; + + void SavePosition(QXmlStreamWriter *writer, Node *node, const Node::Position &pos) const; + + void PostConnect(const XMLNodeData &xml_node_data) const; + + void LoadNodeCustom(QXmlStreamReader *reader, Node *node, XMLNodeData &xml_node_data) const; + + void SaveNodeCustom(QXmlStreamWriter *writer, Node *node) const; + + void LoadTimelinePoints(QXmlStreamReader *reader, TimelinePoints *points) const; + + void SaveTimelinePoints(QXmlStreamWriter *writer, TimelinePoints *points) const; + + void LoadWorkArea(QXmlStreamReader *reader, TimelineWorkArea *workarea) const; + + void SaveWorkArea(QXmlStreamWriter *writer, TimelineWorkArea *workarea) const; + + void LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const; + + void SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const; + + void LoadValueHint(Node::ValueHint *hint, QXmlStreamReader *reader) const; + + void SaveValueHint(const Node::ValueHint *hint, QXmlStreamWriter *writer) const; + +}; + +} + +#endif // SERIALIZER211228_H diff --git a/app/task/project/load/load.cpp b/app/task/project/load/load.cpp index 9f1a75026..606f53e37 100644 --- a/app/task/project/load/load.cpp +++ b/app/task/project/load/load.cpp @@ -21,11 +21,8 @@ #include "load.h" #include -#include -#include -#include "common/xmlutils.h" -#include "core.h" +#include "node/project/serializer/serializer.h" namespace olive { @@ -36,78 +33,42 @@ ProjectLoadTask::ProjectLoadTask(const QString &filename) : bool ProjectLoadTask::Run() { - QFile project_file(GetFilename()); + project_ = new Project(); - if (project_file.open(QFile::ReadOnly | QFile::Text)) { - QXmlStreamReader reader(&project_file); + project_->set_filename(GetFilename()); - uint project_version = 0; + ProjectSerializer::Result result = ProjectSerializer::Load(project_, GetFilename()); - while (XMLReadNextStartElement(&reader)) { - if (reader.name() == QStringLiteral("olive")) { - while(XMLReadNextStartElement(&reader)) { - if (reader.name() == QStringLiteral("version")) { - bool ok; - - project_version = reader.readElementText().toUInt(&ok); - - if (!ok) { - SetError(tr("Failed to parse project version.")); - return false; - } else if (project_version > Project::kProjectVersion) { - // Project is newer than we support - SetError(tr("This project is from a newer version of Olive and cannot be opened in this version.")); - return false; - } else if (project_version < Project::kProjectMinimumVersion) { // Change this if we drop support for a project version - // Project is older than we support - SetError(tr("This project is from a version of Olive that is no longer supported in this version.")); - return false; - } - } else if (reader.name() == QStringLiteral("url")) { - project_saved_url_ = reader.readElementText(); - } else if (reader.name() == QStringLiteral("project")) { - if (project_version == 0) { - // Failed to find project version, throw error before we create project - SetError(tr("Failed to find project version.")); - return false; - } - - project_ = new Project(); - - project_->set_filename(GetFilename()); - - project_->Load(&reader, &layout_info_, project_version, &IsCancelled()); - - // Ensure project is in main thread - project_->moveToThread(qApp->thread()); - break; - } else { - reader.skipCurrentElement(); - } - } - } else if (reader.name() == QStringLiteral("project")) { - // 0.1 projects use "project" as the root instead of Olive. We don't currently support - // these projects - SetError(tr("This project is from a version of Olive that is no longer supported in this version.")); - return false; - } else { - reader.skipCurrentElement(); - } - } - - project_file.close(); - - emit ProgressChanged(1); - - if (reader.hasError()) { - SetError(reader.errorString()); - return false; - } else { - return true; - } - - } else { + switch (result.code()) { + case ProjectSerializer::kSuccess: + break; + case ProjectSerializer::kProjectTooOld: + SetError(tr("This project is from a version of Olive that is no longer supported in this version.")); + break; + case ProjectSerializer::kProjectTooNew: + SetError(tr("This project is from a newer version of Olive and cannot be opened in this version.")); + break; + case ProjectSerializer::kUnknownVersion: + SetError(tr("Failed to determine project version.")); + break; + case ProjectSerializer::kFileError: SetError(tr("Failed to read file \"%1\" for reading.").arg(GetFilename())); + break; + case ProjectSerializer::kXmlError: + SetError(tr("Failed to read XML document. File may be corrupt. Error was: %1").arg(result.GetDetails())); + break; + + // Errors that should never be thrown by a load + case ProjectSerializer::kOverwriteError: + SetError(tr("Unknown error.")); + break; + } + + if (result == ProjectSerializer::kSuccess) { + project_->moveToThread(qApp->thread()); + return true; + } else { + delete project_; return false; } } diff --git a/app/task/project/load/loadbasetask.h b/app/task/project/load/loadbasetask.h index 57052af89..546dd6b51 100644 --- a/app/task/project/load/loadbasetask.h +++ b/app/task/project/load/loadbasetask.h @@ -37,21 +37,6 @@ public: return project_; } - MainWindowLayoutInfo GetLoadedLayout() const - { - return layout_info_; - } - - /** - * @brief Returns the filename the project was saved as, but not necessarily where it is now - * - * May help for resolving relative paths. - */ - const QString& GetFilenameProjectWasSavedAs() const - { - return project_saved_url_; - } - const QString& GetFilename() const { return filename_; @@ -60,10 +45,6 @@ public: protected: Project* project_; - MainWindowLayoutInfo layout_info_; - - QString project_saved_url_; - private: QString filename_; diff --git a/app/task/project/save/save.cpp b/app/task/project/save/save.cpp index 052552ce0..222fc76d8 100644 --- a/app/task/project/save/save.cpp +++ b/app/task/project/save/save.cpp @@ -26,6 +26,7 @@ #include "common/filefunctions.h" #include "core.h" +#include "node/project/serializer/serializer.h" namespace olive { @@ -39,54 +40,35 @@ bool ProjectSaveTask::Run() { QString using_filename = override_filename_.isEmpty() ? project_->filename() : override_filename_; - // File to temporarily save to (ensures we can't half-write the user's main file and crash) - QString temp_save = FileFunctions::GetSafeTemporaryFilename(using_filename); + ProjectSerializer::Result result = ProjectSerializer::Save(project_, using_filename); - QFile project_file(temp_save); + bool success = false; - if (project_file.open(QFile::WriteOnly | QFile::Text)) { - QXmlStreamWriter writer(&project_file); - writer.setAutoFormatting(true); + switch (result.code()) { + case ProjectSerializer::kSuccess: + success = true; + break; + case ProjectSerializer::kXmlError: + SetError(tr("Failed to write XML data.")); + break; + case ProjectSerializer::kFileError: + SetError(tr("Failed to open file \"%1\" for writing.").arg(result.GetDetails())); + break; + case ProjectSerializer::kOverwriteError: + SetError(tr("Failed to overwrite \"%1\". Project has been saved as \"%2\" instead.") + .arg(using_filename, result.GetDetails())); + success = true; + break; - writer.writeStartDocument(); - - writer.writeStartElement("olive"); - - // Version is stored in YYMMDD from whenever the project format was last changed - // Allows easy integer math for checking project versions. - writer.writeTextElement(QStringLiteral("version"), QString::number(Project::kProjectVersion)); - - writer.writeTextElement("url", using_filename); - - writer.writeStartElement(QStringLiteral("project")); - - project_->Save(&writer); - - writer.writeEndElement(); // project - - writer.writeEndElement(); // olive - - writer.writeEndDocument(); - - project_file.close(); - - if (writer.hasError()) { - SetError(tr("Failed to write XML data")); - return false; - } - - // Save was successful, we can now rewrite the original file - if (FileFunctions::RenameFileAllowOverwrite(temp_save, using_filename)) { - return true; - } else { - SetError(tr("Failed to overwrite \"%1\". Project has been saved as \"%2\" instead.") - .arg(using_filename, temp_save)); - return false; - } - } else { - SetError(tr("Failed to open temporary file \"%1\" for writing.").arg(temp_save)); - return false; + // Errors that should never be thrown by a save + case ProjectSerializer::kProjectTooNew: + case ProjectSerializer::kProjectTooOld: + case ProjectSerializer::kUnknownVersion: + SetError(tr("Unknown error.")); + break; } + + return success; } } diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 4086e6983..c94e1b6ce 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -53,20 +53,6 @@ void TimelineMarker::set_name(const QString &name) emit NameChanged(name_); } -void TimelineMarkerList::Save(QXmlStreamWriter *writer) const -{ - foreach (TimelineMarker* marker, markers_) { - writer->writeStartElement(QStringLiteral("marker")); - - writer->writeAttribute(QStringLiteral("name"), marker->name()); - - writer->writeAttribute(QStringLiteral("in"), marker->time().in().toString()); - writer->writeAttribute(QStringLiteral("out"), marker->time().out().toString()); - - writer->writeEndElement(); // marker - } -} - TimelineMarkerList::~TimelineMarkerList() { qDeleteAll(markers_); @@ -99,28 +85,4 @@ const QList &TimelineMarkerList::list() const return markers_; } -void TimelineMarkerList::Load(QXmlStreamReader *reader) -{ - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("marker")) { - QString name; - rational in, out; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("name")) { - name = attr.value().toString(); - } else if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString()); - } else if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString()); - } - } - - AddMarker(TimeRange(in, out), name); - } - - reader->skipCurrentElement(); - } -} - } diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index ebc88a289..3c597b47d 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -67,9 +67,6 @@ public: const QList &list() const; - void Load(QXmlStreamReader* reader); - void Save(QXmlStreamWriter* writer) const; - signals: void MarkerAdded(TimelineMarker* marker); diff --git a/app/timeline/timelinepoints.cpp b/app/timeline/timelinepoints.cpp index 23bef986f..4329c42b4 100644 --- a/app/timeline/timelinepoints.cpp +++ b/app/timeline/timelinepoints.cpp @@ -39,30 +39,6 @@ const TimelineWorkArea *TimelinePoints::workarea() const return &workarea_; } -void TimelinePoints::Load(QXmlStreamReader *reader) -{ - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("markers")) { - markers_.Load(reader); - } else if (reader->name() == QStringLiteral("workarea")) { - workarea_.Load(reader); - } else { - reader->skipCurrentElement(); - } - } -} - -void TimelinePoints::Save(QXmlStreamWriter *writer) const -{ - writer->writeStartElement(QStringLiteral("workarea")); - workarea_.Save(writer); - writer->writeEndElement(); // workarea - - writer->writeStartElement(QStringLiteral("markers")); - markers_.Save(writer); - writer->writeEndElement(); // markers -} - TimelineWorkArea *TimelinePoints::workarea() { return &workarea_; diff --git a/app/timeline/timelinepoints.h b/app/timeline/timelinepoints.h index ee0da597e..14527608d 100644 --- a/app/timeline/timelinepoints.h +++ b/app/timeline/timelinepoints.h @@ -40,9 +40,6 @@ public: TimelineWorkArea* workarea(); const TimelineWorkArea* workarea() const; - void Load(QXmlStreamReader* reader); - void Save(QXmlStreamWriter* writer) const; - private: TimelineMarkerList markers_; diff --git a/app/timeline/timelineworkarea.cpp b/app/timeline/timelineworkarea.cpp index 4e43e30d1..fdb831ab2 100644 --- a/app/timeline/timelineworkarea.cpp +++ b/app/timeline/timelineworkarea.cpp @@ -55,37 +55,6 @@ void TimelineWorkArea::set_range(const TimeRange &range) emit RangeChanged(workarea_range_); } -void TimelineWorkArea::Load(QXmlStreamReader *reader) -{ - rational range_in = workarea_range_.in(); - rational range_out = workarea_range_.out(); - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("enabled")) { - set_enabled(attr.value() != QStringLiteral("0")); - } else if (attr.name() == QStringLiteral("in")) { - range_in = rational::fromString(attr.value().toString()); - } else if (attr.name() == QStringLiteral("out")) { - range_out = rational::fromString(attr.value().toString()); - } - } - - TimeRange loaded_workarea(range_in, range_out); - - if (loaded_workarea != workarea_range_) { - set_range(loaded_workarea); - } - - reader->skipCurrentElement(); -} - -void TimelineWorkArea::Save(QXmlStreamWriter *writer) const -{ - writer->writeAttribute(QStringLiteral("enabled"), QString::number(workarea_enabled_)); - writer->writeAttribute(QStringLiteral("in"), workarea_range_.in().toString()); - writer->writeAttribute(QStringLiteral("out"), workarea_range_.out().toString()); -} - const rational &TimelineWorkArea::in() const { return workarea_range_.in(); diff --git a/app/timeline/timelineworkarea.h b/app/timeline/timelineworkarea.h index ef33c8d4f..c672a9c76 100644 --- a/app/timeline/timelineworkarea.h +++ b/app/timeline/timelineworkarea.h @@ -44,9 +44,6 @@ public: const TimeRange& range() const; void set_range(const TimeRange& range); - void Load(QXmlStreamReader* reader); - void Save(QXmlStreamWriter* writer) const; - static const rational kResetIn; static const rational kResetOut; diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 1c4f02d7c..30fd2babe 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -629,7 +629,7 @@ void TimelineWidget::Paste(bool insert) MultiUndoCommand* command = new MultiUndoCommand(); QVector paste_data; - QVector pasted = PasteNodesFromClipboard(GetConnectedNode()->parent(), command, &paste_data); + QVector pasted = PasteNodesFromClipboard(GetConnectedNode()->project(), command, &paste_data); rational paste_start = GetTime();