From 028651f9fbfcc570cd9b9ebf383e9668c9137139 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 21 Feb 2023 14:26:34 -0800 Subject: [PATCH] moved serialization --- app/common/xmlutils.cpp | 5 +- app/common/xmlutils.h | 3 +- app/core.cpp | 6 +- app/node/group/group.cpp | 149 +- app/node/group/group.h | 4 + app/node/keyframe.cpp | 45 + app/node/keyframe.h | 3 + app/node/node.cpp | 535 +++++++ app/node/node.h | 22 +- app/node/output/track/track.cpp | 18 + app/node/output/track/track.h | 3 + app/node/output/viewer/viewer.cpp | 30 + app/node/output/viewer/viewer.h | 3 + app/node/project.cpp | 131 +- app/node/project.h | 22 +- app/node/project/footage/footage.cpp | 28 + app/node/project/footage/footage.h | 3 + .../project/serializer/serializer210528.cpp | 11 +- .../project/serializer/serializer210907.cpp | 11 +- .../project/serializer/serializer211228.cpp | 11 +- .../project/serializer/serializer220403.cpp | 11 +- .../project/serializer/serializer230220.cpp | 1294 +---------------- .../project/serializer/serializer230220.h | 80 - app/node/serializeddata.cpp | 25 + app/node/serializeddata.h | 69 + app/task/project/load/loadbasetask.h | 7 + app/task/project/save/save.h | 7 + app/timeline/timelinemarker.cpp | 59 + app/timeline/timelinemarker.h | 6 + app/timeline/timelineworkarea.cpp | 42 + app/timeline/timelineworkarea.h | 3 + 31 files changed, 1203 insertions(+), 1443 deletions(-) create mode 100644 app/node/serializeddata.cpp create mode 100644 app/node/serializeddata.h diff --git a/app/common/xmlutils.cpp b/app/common/xmlutils.cpp index ec451d415..817b6e972 100644 --- a/app/common/xmlutils.cpp +++ b/app/common/xmlutils.cpp @@ -25,12 +25,13 @@ namespace olive { -bool XMLReadNextStartElement(QXmlStreamReader *reader) +bool XMLReadNextStartElement(QXmlStreamReader *reader, CancelAtom *cancel_atom) { QXmlStreamReader::TokenType token; while ((token = reader->readNext()) != QXmlStreamReader::Invalid - && token != QXmlStreamReader::EndDocument) { + && token != QXmlStreamReader::EndDocument + && (!cancel_atom || !cancel_atom->IsCancelled())) { if (reader->isEndElement()) { return false; } else if (reader->isStartElement()) { diff --git a/app/common/xmlutils.h b/app/common/xmlutils.h index 6e6670b6c..de7b54128 100644 --- a/app/common/xmlutils.h +++ b/app/common/xmlutils.h @@ -24,6 +24,7 @@ #include #include "node/param.h" +#include "render/cancelatom.h" #include "undo/undocommand.h" namespace olive { @@ -45,7 +46,7 @@ class NodeGroup; * * See also: https://stackoverflow.com/questions/46346450/qt-qxmlstreamreader-always-returns-premature-end-of-document-error */ -bool XMLReadNextStartElement(QXmlStreamReader* reader); +bool XMLReadNextStartElement(QXmlStreamReader* reader, CancelAtom *cancel_atom = nullptr); } diff --git a/app/core.cpp b/app/core.cpp index f06e48c15..72a5a7894 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -492,7 +492,7 @@ bool Core::AddOpenProjectFromTask(Task *task, bool add_to_recents) if (ValidateFootageInLoadedProject(project, project->GetSavedURL())) { AddOpenProject(project, add_to_recents); - main_window_->LoadLayout(project->GetLayoutInfo()); + main_window_->LoadLayout(load_task->GetLoadedLayout()); return true; } else { @@ -801,9 +801,6 @@ void Core::SaveProjectInternal(const QString& override_filename) // Create save manager Task* psm; - // Put layout into project - open_project_->SetLayoutInfo(main_window_->SaveLayout()); - if (open_project_->filename().endsWith(QStringLiteral(".otio"), Qt::CaseInsensitive)) { #ifdef USE_OTIO psm = new SaveOTIOTask(open_project_); @@ -817,6 +814,7 @@ void Core::SaveProjectInternal(const QString& override_filename) } else { bool use_compression = !open_project_->filename().endsWith(QStringLiteral(".ovexml"), Qt::CaseInsensitive); psm = new ProjectSaveTask(open_project_, use_compression); + static_cast(psm)->SetLayout(main_window_->SaveLayout()); if (!override_filename.isEmpty()) { // Set override filename if provided diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index 9ca495e1f..5eb53aa5d 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -20,7 +20,7 @@ #include "group.h" -#include "node/project.h" +#include "node/serializeddata.h" namespace olive { @@ -61,6 +61,153 @@ void NodeGroup::Retranslate() } } +bool NodeGroup::LoadCustom(QXmlStreamReader *reader, SerializedData *data) +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthroughs")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthrough")) { + SerializedData::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 if (reader->name() == QStringLiteral("id")) { + link.passthrough_id = reader->readElementText(); + } else if (reader->name() == QStringLiteral("name")) { + link.custom_name = reader->readElementText(); + } else if (reader->name() == QStringLiteral("flags")) { + link.custom_flags = InputFlags(reader->readElementText().toULongLong()); + } else if (reader->name() == QStringLiteral("type")) { + link.data_type = NodeValue::GetDataTypeFromName(reader->readElementText()); + } else if (reader->name() == QStringLiteral("default")) { + link.default_val = NodeValue::StringToValue(link.data_type, reader->readElementText(), false); + } else if (reader->name() == QStringLiteral("properties")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("property")) { + QString key; + QString value; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("key")) { + key = reader->readElementText(); + } else if (reader->name() == QStringLiteral("value")) { + value = reader->readElementText(); + } else { + reader->skipCurrentElement(); + } + } + + if (!key.isEmpty()) { + link.custom_properties.insert(key, value); + } + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } + + data->group_input_links.append(link); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("outputpassthrough")) { + data->group_output_links.insert(this, reader->readElementText().toULongLong()); + } else { + reader->skipCurrentElement(); + } + } + + return true; +} + +void NodeGroup::SaveCustom(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("inputpassthroughs")); + + foreach (const NodeGroup::InputPassthrough &ip, this->GetInputPassthroughs()) { + writer->writeStartElement(QStringLiteral("inputpassthrough")); + + // Reference to inner input + writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.second.node()))); + writer->writeTextElement(QStringLiteral("input"), ip.second.input()); + writer->writeTextElement(QStringLiteral("element"), QString::number(ip.second.element())); + + // ID of passthrough + writer->writeTextElement(QStringLiteral("id"), ip.first); + + // Passthrough-specific details + const QString &input = ip.first; + writer->writeTextElement(QStringLiteral("name"), this->Node::GetInputName(input)); + + writer->writeTextElement(QStringLiteral("flags"), QString::number((GetInputFlags(input) & ~ip.second.GetFlags()).value())); + + NodeValue::Type data_type = GetInputDataType(input); + writer->writeTextElement(QStringLiteral("type"), NodeValue::GetDataTypeName(data_type)); + + writer->writeTextElement(QStringLiteral("default"), NodeValue::ValueToString(data_type, GetDefaultValue(input), false)); + + writer->writeStartElement(QStringLiteral("properties")); + auto p = GetInputProperties(input); + for (auto it=p.cbegin(); it!=p.cend(); it++) { + writer->writeStartElement(QStringLiteral("property")); + writer->writeTextElement(QStringLiteral("key"), it.key()); + writer->writeTextElement(QStringLiteral("value"), it.value().toString()); + writer->writeEndElement(); // property + } + writer->writeEndElement(); // properties + + writer->writeEndElement(); // input + } + + writer->writeEndElement(); // inputpassthroughs + + writer->writeTextElement(QStringLiteral("outputpassthrough"), QString::number(reinterpret_cast(this->GetOutputPassthrough()))); +} + +void NodeGroup::PostLoadEvent(SerializedData *data) +{ + super::PostLoadEvent(data); + + foreach (const SerializedData::GroupLink &l, data->group_input_links) { + if (Node *input_node = data->node_ptrs.value(l.input_node)) { + NodeInput resolved(input_node, l.input_id, l.input_element); + + l.group->AddInputPassthrough(resolved, l.passthrough_id); + + l.group->SetInputFlag(l.passthrough_id, InputFlag(l.custom_flags.value())); + + if (!l.custom_name.isEmpty()) { + l.group->SetInputName(l.passthrough_id, l.custom_name); + } + + l.group->SetInputDataType(l.passthrough_id, l.data_type); + + l.group->SetDefaultValue(l.passthrough_id, l.default_val); + + for (auto it=l.custom_properties.cbegin(); it!=l.custom_properties.cend(); it++) { + l.group->SetInputProperty(l.passthrough_id, it.key(), it.value()); + } + } + } + + for (auto it=data->group_output_links.cbegin(); it!=data->group_output_links.cend(); it++) { + if (Node *output_node = data->node_ptrs.value(it.value())) { + it.key()->SetOutputPassthrough(output_node); + } + } +} + QString NodeGroup::AddInputPassthrough(const NodeInput &input, const QString &force_id) { Q_ASSERT(ContextContainsNode(input.node())); diff --git a/app/node/group/group.h b/app/node/group/group.h index 2eafa3aa2..4eb1f6442 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -40,6 +40,10 @@ public: virtual void Retranslate() override; + virtual bool LoadCustom(QXmlStreamReader *reader, SerializedData *data) override; + virtual void SaveCustom(QXmlStreamWriter *writer) const override; + virtual void PostLoadEvent(SerializedData *data) override; + QString AddInputPassthrough(const NodeInput &input, const QString &force_id = QString()); void RemoveInputPassthrough(const NodeInput &input); diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index 8a30ec560..d329add42 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -210,4 +210,49 @@ bool NodeKeyframe::has_sibling_at_time(const rational &t) const return k && k != this; } +bool NodeKeyframe::load(QXmlStreamReader *reader, NodeValue::Type data_type) +{ + QString key_input; + QPointF key_in_handle; + QPointF key_out_handle; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("input")) { + key_input = attr.value().toString(); + } else if (attr.name() == QStringLiteral("time")) { + this->set_time(rational::fromString(attr.value().toString().toStdString())); + } else if (attr.name() == QStringLiteral("type")) { + this->set_type_no_bezier_adj(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()); + } + } + + this->set_value(NodeValue::StringToValue(data_type, reader->readElementText(), true)); + + this->set_bezier_control_in(key_in_handle); + this->set_bezier_control_out(key_out_handle); + + return true; +} + +void NodeKeyframe::save(QXmlStreamWriter *writer, NodeValue::Type data_type) const +{ + writer->writeAttribute(QStringLiteral("input"), this->input()); + writer->writeAttribute(QStringLiteral("time"), QString::fromStdString(this->time().toString())); + writer->writeAttribute(QStringLiteral("type"), QString::number(this->type())); + writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(this->bezier_control_in().x())); + writer->writeAttribute(QStringLiteral("inhandley"), QString::number(this->bezier_control_in().y())); + writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(this->bezier_control_out().x())); + writer->writeAttribute(QStringLiteral("outhandley"), QString::number(this->bezier_control_out().y())); + + writer->writeCharacters(NodeValue::ValueToString(data_type, this->value(), true)); +} + } diff --git a/app/node/keyframe.h b/app/node/keyframe.h index eaff4d491..c59f3ffa4 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -165,6 +165,9 @@ public: bool has_sibling_at_time(const rational &t) const; + bool load(QXmlStreamReader *reader, NodeValue::Type data_type); + void save(QXmlStreamWriter *writer, NodeValue::Type data_type) const; + signals: /** * @brief Signal emitted when this keyframe's time is changed diff --git a/app/node/node.cpp b/app/node/node.cpp index 60c951a0e..b225d21ee 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -29,8 +29,10 @@ #include "core.h" #include "config/config.h" #include "node/group/group.h" +#include "node/project/serializer/typeserializer.h" #include "nodeundo.h" #include "project.h" +#include "serializeddata.h" #include "ui/colorcoding.h" #include "ui/icons/icons.h" @@ -1181,6 +1183,463 @@ bool Node::AreLinked(Node *a, Node *b) return a->links_.contains(b); } +bool Node::Load(QXmlStreamReader *reader, SerializedData *data) +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("input")) { + LoadInput(reader, data); + } else if (reader->name() == QStringLiteral("ptr")) { + quintptr ptr = reader->readElementText().toULongLong(); + data->node_ptrs.insert(ptr, this); + } else if (reader->name() == QStringLiteral("label")) { + this->SetLabel(reader->readElementText()); + } else if (reader->name() == QStringLiteral("uuid")) { + data->node_uuids.insert(this, QUuid::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("color")) { + this->SetOverrideColor(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("links")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("link")) { + data->block_links.append({this, reader->readElementText().toULongLong()}); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("custom")) { + if (!LoadCustom(reader, data)) { + return false; + } + } 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(); + } + } + + data->desired_connections.append({NodeInput(this, param_id, ele), output_node_id.toULongLong()}); + } 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; + if (!vh.load(reader)) { + return false; + } + this->SetValueHintForInput(input, vh, element); + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("context")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + quintptr node_ptr = 0; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("ptr")) { + node_ptr = attr.value().toULongLong(); + } + } + + if (node_ptr) { + Node::Position node_pos; + if (!node_pos.load(reader)) { + return false; + } + data->positions[reinterpret_cast(this)].insert(node_ptr, node_pos); + } else { + return false; + } + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("caches")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("audio")) { + this->audio_playback_cache()->SetUuid(QUuid::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("video")) { + this->video_frame_cache()->SetUuid(QUuid::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("thumb")) { + this->thumbnail_cache()->SetUuid(QUuid::fromString(reader->readElementText())); + } else if (reader->name() == QStringLiteral("waveform")) { + this->waveform_cache()->SetUuid(QUuid::fromString(reader->readElementText())); + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } + + this->LoadFinishedEvent(); + + return true; +} + +void Node::Save(QXmlStreamWriter *writer) const +{ + writer->writeAttribute(QStringLiteral("version"), QString::number(1)); + + writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); + + writer->writeTextElement(QStringLiteral("label"), this->GetLabel()); + writer->writeTextElement(QStringLiteral("color"), QString::number(this->GetOverrideColor())); + + foreach (const QString& input, this->inputs()) { + writer->writeStartElement(QStringLiteral("input")); + + SaveInput(writer, input); + + writer->writeEndElement(); // input + } + + writer->writeStartElement(QStringLiteral("links")); + foreach (Node* link, this->links()) { + writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast(link))); + } + writer->writeEndElement(); // links + + writer->writeStartElement(QStringLiteral("connections")); + for (auto it=this->input_connections().cbegin(); it!=this->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=this->GetValueHints().cbegin(); it!=this->GetValueHints().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(); // hints + + writer->writeStartElement(QStringLiteral("context")); + + const Node::PositionMap &map = this->GetContextPositions(); + + if (!map.isEmpty()) { + for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { + writer->writeStartElement(QStringLiteral("node")); + writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(this))); + jt.value().save(writer); + writer->writeEndElement(); // node + } + } + + writer->writeEndElement(); // context + + writer->writeStartElement(QStringLiteral("caches")); + + writer->writeTextElement(QStringLiteral("audio"), this->audio_playback_cache()->GetUuid().toString()); + writer->writeTextElement(QStringLiteral("video"), this->video_frame_cache()->GetUuid().toString()); + writer->writeTextElement(QStringLiteral("thumb"), this->thumbnail_cache()->GetUuid().toString()); + writer->writeTextElement(QStringLiteral("waveform"), this->waveform_cache()->GetUuid().toString()); + + writer->writeEndElement(); // caches + + writer->writeStartElement(QStringLiteral("custom")); + + SaveCustom(writer); + + writer->writeEndElement(); // custom +} + +bool Node::LoadCustom(QXmlStreamReader *reader, SerializedData *data) +{ + reader->skipCurrentElement(); + return true; +} + +void Node::PostLoadEvent(SerializedData *data) +{ + // Resolve positions + quintptr this_ptr = data->node_ptrs.key(this); + const QMap &positions = data->positions.value(this_ptr); + + for (auto jt=positions.cbegin(); jt!=positions.cend(); jt++) { + Node *n = data->node_ptrs.value(jt.key()); + if (n) { + this->SetNodePositionInContext(n, jt.value()); + } + } +} + +bool Node::LoadInput(QXmlStreamReader *reader, SerializedData *data) +{ + if (dynamic_cast(this)) { + // Ignore input of group + reader->skipCurrentElement(); + return true; + } + + 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 false; + } + + if (!this->HasInputWithID(param_id)) { + qWarning() << "Failed to load parameter that didn't exist:" << param_id; + reader->skipCurrentElement(); + return false; + } + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("primary")) { + // Load primary immediate + if (!LoadImmediate(reader, param_id, -1, data)) { + return false; + } + } else if (reader->name() == QStringLiteral("subelements")) { + // Load subelements + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("count")) { + this->InputArrayResize(param_id, attr.value().toInt()); + } + } + + int element_counter = 0; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("element")) { + if (!LoadImmediate(reader, param_id, element_counter, data)) { + return false; + } + + element_counter++; + } else { + reader->skipCurrentElement(); + } + } + } else { + reader->skipCurrentElement(); + } + } + + return true; +} + +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 = this->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::LoadImmediate(QXmlStreamReader *reader, const QString &input, int element, SerializedData *data) +{ + NodeValue::Type data_type = this->GetInputDataType(input); + + // HACK: SubtitleParams contain the actual subtitle data, so loading/replacing it will overwrite + // the valid subtitles. We hack around it by simply skipping loading subtitles, we'll see + // if this ends up being an issue in the future. + if (data_type == NodeValue::kSubtitleParams) { + reader->skipCurrentElement(); + return true; + } + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("standard")) { + // Load standard value + int val_index = 0; + + while (XMLReadNextStartElement(reader)) { + 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 = TypeSerializer::LoadAudioParams(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, true); + } + } + + this->SetSplitStandardValueOnTrack(input, val_index, value_on_track, element); + + val_index++; + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("keyframing") && this->IsInputKeyframable(input)) { + this->SetInputIsKeyframing(input, reader->readElementText().toInt(), element); + } else if (reader->name() == QStringLiteral("keyframes")) { + int track = 0; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("track")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("key")) { + NodeKeyframe* key = new NodeKeyframe(); + key->set_input(input); + key->set_element(element); + key->set_track(track); + + key->load(reader, data_type); + key->setParent(this); + } else { + reader->skipCurrentElement(); + } + } + + track++; + } else { + reader->skipCurrentElement(); + } + } + } else if (reader->name() == QStringLiteral("csinput")) { + this->SetInputProperty(input, QStringLiteral("col_input"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("csdisplay")) { + this->SetInputProperty(input, QStringLiteral("col_display"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("csview")) { + this->SetInputProperty(input, QStringLiteral("col_view"), reader->readElementText()); + } else if (reader->name() == QStringLiteral("cslook")) { + this->SetInputProperty(input, QStringLiteral("col_look"), reader->readElementText()); + } else { + reader->skipCurrentElement(); + } + } +} + +void Node::SaveImmediate(QXmlStreamWriter *writer, const QString &input, int element) const +{ + if (this->IsInputKeyframable(input)) { + writer->writeTextElement(QStringLiteral("keyframing"), QString::number(this->IsInputKeyframing(input, element))); + } + + NodeValue::Type data_type = this->GetInputDataType(input); + + // Write standard value + writer->writeStartElement(QStringLiteral("standard")); + + foreach (const QVariant& v, this->GetSplitStandardValue(input, element)) { + writer->writeStartElement(QStringLiteral("track")); + + if (data_type == NodeValue::kVideoParams) { + v.value().Save(writer); + } else if (data_type == NodeValue::kAudioParams) { + TypeSerializer::SaveAudioParams(writer, v.value()); + } 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 : this->GetKeyframeTracks(input, element)) { + writer->writeStartElement(QStringLiteral("track")); + + for (NodeKeyframe* key : track) { + writer->writeStartElement(QStringLiteral("key")); + + key->save(writer, data_type); + + writer->writeEndElement(); // key + } + + writer->writeEndElement(); // track + } + + writer->writeEndElement(); // keyframes + + if (data_type == NodeValue::kColor) { + // Save color management information + writer->writeTextElement(QStringLiteral("csinput"), this->GetInputProperty(input, QStringLiteral("col_input")).toString()); + writer->writeTextElement(QStringLiteral("csdisplay"), this->GetInputProperty(input, QStringLiteral("col_display")).toString()); + writer->writeTextElement(QStringLiteral("csview"), this->GetInputProperty(input, QStringLiteral("col_view")).toString()); + writer->writeTextElement(QStringLiteral("cslook"), this->GetInputProperty(input, QStringLiteral("col_look")).toString()); + } +} + void Node::InsertInput(const QString &id, NodeValue::Type type, const QVariant &default_value, InputFlags flags, int index) { if (id.isEmpty()) { @@ -1930,4 +2389,80 @@ std::list Node::FindPath(Node *from, Node *to, int path_index) return v; } +bool Node::ValueHint::load(QXmlStreamReader *reader) +{ + uint version = 0; + XMLAttributeLoop(reader, attr) { + version = attr.value().toUInt(); + } + + Q_UNUSED(version) + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("types")) { + QVector types; + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("type")) { + types.append(static_cast(reader->readElementText().toInt())); + } else { + reader->skipCurrentElement(); + } + } + this->set_type(types); + } else if (reader->name() == QStringLiteral("index")) { + this->set_index(reader->readElementText().toInt()); + } else if (reader->name() == QStringLiteral("tag")) { + this->set_tag(reader->readElementText()); + } else { + reader->skipCurrentElement(); + } + } +} + +void Node::ValueHint::save(QXmlStreamWriter *writer) const +{ + writer->writeAttribute(QStringLiteral("version"), QString::number(1)); + + writer->writeStartElement(QStringLiteral("types")); + + for (auto it=this->types().cbegin(); it!=this->types().cend(); it++) { + writer->writeTextElement(QStringLiteral("type"), QString::number(*it)); + } + + writer->writeEndElement(); // types + + writer->writeTextElement(QStringLiteral("index"), QString::number(this->index())); + + writer->writeTextElement(QStringLiteral("tag"), this->tag()); +} + +bool Node::Position::load(QXmlStreamReader *reader) +{ + bool got_pos_x = false; + bool got_pos_y = false; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("x")) { + this->position.setX(reader->readElementText().toDouble()); + got_pos_x = true; + } else if (reader->name() == QStringLiteral("y")) { + this->position.setY(reader->readElementText().toDouble()); + got_pos_y = true; + } else if (reader->name() == QStringLiteral("expanded")) { + this->expanded = reader->readElementText().toInt(); + } else { + reader->skipCurrentElement(); + } + } + + return got_pos_x && got_pos_y; +} + +void Node::Position::save(QXmlStreamWriter *writer) const +{ + writer->writeTextElement(QStringLiteral("x"), QString::number(this->position.x())); + writer->writeTextElement(QStringLiteral("y"), QString::number(this->position.y())); + writer->writeTextElement(QStringLiteral("expanded"), QString::number(this->expanded)); +} + } diff --git a/app/node/node.h b/app/node/node.h index 974db61f8..dce7598b5 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -56,8 +56,9 @@ namespace olive { #define NODE_COPY_FUNCTION(x) \ virtual Node *copy() const override {return new x();} -class Project; class Folder; +class Project; +class SerializedData; /** * @brief A single processing unit that can be connected with others to create intricate processing systems @@ -288,6 +289,9 @@ public: expanded = e; } + bool load(QXmlStreamReader *reader); + void save(QXmlStreamWriter *writer) const; + QPointF position; bool expanded; @@ -682,6 +686,9 @@ public: void set_index(const int &index) { index_ = index; } void set_tag(const QString &tag) { tag_ = tag; } + bool load(QXmlStreamReader *reader); + void save(QXmlStreamWriter *writer) const; + private: QVector type_; int index_; @@ -941,6 +948,19 @@ public: static bool Unlink(Node* a, Node* b); static bool AreLinked(Node* a, Node* b); + bool Load(QXmlStreamReader *reader, SerializedData *data); + void Save(QXmlStreamWriter *writer) const; + + virtual bool LoadCustom(QXmlStreamReader *reader, SerializedData *data); + virtual void SaveCustom(QXmlStreamWriter *writer) const {} + virtual void PostLoadEvent(SerializedData *data); + + bool LoadInput(QXmlStreamReader *reader, SerializedData *data); + void SaveInput(QXmlStreamWriter *writer, const QString &id) const; + + bool LoadImmediate(QXmlStreamReader *reader, const QString &input, int element, SerializedData *data); + void SaveImmediate(QXmlStreamWriter *writer, const QString &input, int element) const; + void SetFolder(Folder* folder) { folder_ = folder; diff --git a/app/node/output/track/track.cpp b/app/node/output/track/track.cpp index 33d4268d9..555c615f1 100644 --- a/app/node/output/track/track.cpp +++ b/app/node/output/track/track.cpp @@ -195,6 +195,24 @@ void Track::SetTrackHeight(const double &height) emit TrackHeightChanged(track_height_); } +bool Track::LoadCustom(QXmlStreamReader *reader, SerializedData *data) +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("height")) { + this->SetTrackHeight(reader->readElementText().toDouble()); + } else { + reader->skipCurrentElement(); + } + } + + return true; +} + +void Track::SaveCustom(QXmlStreamWriter *writer) const +{ + writer->writeTextElement(QStringLiteral("height"), QString::number(this->GetTrackHeight())); +} + void Track::InputValueChangedEvent(const QString &input, int element) { Q_UNUSED(element) diff --git a/app/node/output/track/track.h b/app/node/output/track/track.h index a23f75f50..a46c2f8da 100644 --- a/app/node/output/track/track.h +++ b/app/node/output/track/track.h @@ -102,6 +102,9 @@ public: SetTrackHeight(PixelHeightToInternalHeight(h)); } + virtual bool LoadCustom(QXmlStreamReader *reader, SerializedData *data) override; + virtual void SaveCustom(QXmlStreamWriter *writer) const override; + static int InternalHeightToPixelHeight(double h) { return qRound(h * QFontMetrics(QFont()).height()); diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp index dd7cc8dfb..adada2aa5 100644 --- a/app/node/output/viewer/viewer.cpp +++ b/app/node/output/viewer/viewer.cpp @@ -430,6 +430,36 @@ void ViewerOutput::Value(const NodeValueRow &value, const NodeGlobals &globals, } } +bool ViewerOutput::LoadCustom(QXmlStreamReader *reader, SerializedData *data) +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("markers")) { + if (!this->GetMarkers()->load(reader)) { + return false; + } + } else if (reader->name() == QStringLiteral("workarea")) { + if (!this->GetWorkArea()->load(reader)) { + return false; + } + } else { + reader->skipCurrentElement(); + } + } + + return true; +} + +void ViewerOutput::SaveCustom(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("workarea")); + this->GetWorkArea()->save(writer); + writer->writeEndElement(); // workarea + + writer->writeStartElement(QStringLiteral("markers")); + this->GetMarkers()->save(writer); + writer->writeEndElement(); // markers +} + void ViewerOutput::InputValueChangedEvent(const QString &input, int element) { if (element == 0) { diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index 053d76e67..0ad696614 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -191,6 +191,9 @@ public: const EncodingParams &GetLastUsedEncodingParams() const { return last_used_encoding_params_; } void SetLastUsedEncodingParams(const EncodingParams &p) { last_used_encoding_params_ = p; } + virtual bool LoadCustom(QXmlStreamReader *reader, SerializedData *data) override; + virtual void SaveCustom(QXmlStreamWriter *writer) const override; + static const QString kVideoParamsInput; static const QString kAudioParamsInput; static const QString kSubtitleParamsInput; diff --git a/app/node/project.cpp b/app/node/project.cpp index 6d3ac0a7e..dcd565ed6 100644 --- a/app/node/project.cpp +++ b/app/node/project.cpp @@ -28,6 +28,7 @@ #include "core.h" #include "dialog/progress/progress.h" #include "node/factory.h" +#include "node/serializeddata.h" #include "render/diskmanager.h" #include "window/mainwindow/mainwindow.h" @@ -82,6 +83,131 @@ void Project::Clear() } } +void Project::Load(QXmlStreamReader *reader) +{ + SerializedData data; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("uuid")) { + + this->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 = this->root(); + } else if (is_cm) { + node = this->color_manager(); + } else if (is_settings) { + node = this->settings(); + } else { + node = NodeFactory::CreateFromID(id); + } + + if (!node) { + qWarning() << "Failed to find node with ID" << id; + reader->skipCurrentElement(); + } else { + // Disable cache while node is being loaded (we'll re-enable it later) + node->SetCachesEnabled(false); + + node->Load(reader, &data); + + node->setParent(this); + } + } + } else { + reader->skipCurrentElement(); + } + } + + } else { + + // Skip this + reader->skipCurrentElement(); + + } + } + + for (auto it = this->nodes().cbegin(); it != this->nodes().cend(); it++){ + (*it)->PostLoadEvent(&data); + } + + foreach (const SerializedData::SerializedConnection& con, data.desired_connections) { + if (Node *out = data.node_ptrs.value(con.output_node)) { + Node::ConnectEdge(out, con.input); + } + } + + foreach (const SerializedData::BlockLink& l, data.block_links) { + Node *a = l.block; + Node *b = data.node_ptrs.value(l.link); + + Node::Link(a, b); + } + + // Re-enable caches and resolve tracks + for (Node *n : this->nodes()) { + n->SetCachesEnabled(true); + } +} + +void Project::Save(QXmlStreamWriter *writer) const +{ + writer->writeAttribute(QStringLiteral("version"), QString::number(230220)); + + writer->writeTextElement(QStringLiteral("uuid"), this->GetUuid().toString()); + + writer->writeStartElement(QStringLiteral("nodes")); + + foreach (Node* node, this->nodes()) { + writer->writeStartElement(QStringLiteral("node")); + + if (node == this->root()) { + writer->writeAttribute(QStringLiteral("root"), QStringLiteral("1")); + } else if (node == this->color_manager()) { + writer->writeAttribute(QStringLiteral("cm"), QStringLiteral("1")); + } else if (node == this->settings()) { + writer->writeAttribute(QStringLiteral("settings"), QStringLiteral("1")); + } + + writer->writeAttribute(QStringLiteral("id"), node->id()); + + node->Save(writer); + + writer->writeEndElement(); // node + } + + writer->writeEndElement(); // nodes +} + int Project::GetNumberOfContextsNodeIsIn(Node *node, bool except_itself) const { int count = 0; @@ -162,11 +288,6 @@ void Project::childEvent(QChildEvent *event) } } -Folder *Project::root() -{ - return root_; -} - QString Project::name() const { if (filename_.isEmpty()) { diff --git a/app/node/project.h b/app/node/project.h index 025b0ee44..fe9abd5f7 100644 --- a/app/node/project.h +++ b/app/node/project.h @@ -70,9 +70,10 @@ public: return default_nodes_; } - int GetNumberOfContextsNodeIsIn(Node *node, bool except_itself = false) const; + void Load(QXmlStreamReader *reader); + void Save(QXmlStreamWriter *writer) const; - Folder* root(); + int GetNumberOfContextsNodeIsIn(Node *node, bool except_itself = false) const; QString name() const; @@ -80,8 +81,9 @@ public: QString pretty_filename() const; void set_filename(const QString& s); - ColorManager* color_manager() { return color_manager_; } - ProjectSettingsNode* settings() { return settings_; } + Folder* root() const { return root_; } + ColorManager* color_manager() const { return color_manager_; } + ProjectSettingsNode* settings() const { return settings_; } bool is_modified() const { return is_modified_; } void set_modified(bool e); @@ -106,16 +108,6 @@ public: void RegenerateUuid(); - const MainWindowLayoutInfo &GetLayoutInfo() const - { - return layout_info_; - } - - void SetLayoutInfo(const MainWindowLayoutInfo &info) - { - layout_info_ = info; - } - /** * @brief Returns the filename the project was saved as, but not necessarily where it is now * @@ -195,8 +187,6 @@ private: bool autorecovery_saved_; - MainWindowLayoutInfo layout_info_; - QVector node_children_; QVector default_nodes_; diff --git a/app/node/project/footage/footage.cpp b/app/node/project/footage/footage.cpp index 87b962459..4a308b729 100644 --- a/app/node/project/footage/footage.cpp +++ b/app/node/project/footage/footage.cpp @@ -404,6 +404,34 @@ QVariant Footage::data(const DataType &d) const return super::data(d); } +bool Footage::LoadCustom(QXmlStreamReader *reader, SerializedData *data) +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("timestamp")) { + this->set_timestamp(reader->readElementText().toLongLong()); + } else if (reader->name() == QStringLiteral("viewer")) { + if (!ViewerOutput::LoadCustom(reader, data)) { + return false; + } + } else { + reader->skipCurrentElement(); + } + } + + return true; +} + +void Footage::SaveCustom(QXmlStreamWriter *writer) const +{ + writer->writeTextElement(QStringLiteral("timestamp"), QString::number(this->timestamp())); + + writer->writeStartElement(QStringLiteral("viewer")); + + ViewerOutput::SaveCustom(writer); + + writer->writeEndElement(); // viewer +} + void Footage::UpdateTooltip() { if (valid_) { diff --git a/app/node/project/footage/footage.h b/app/node/project/footage/footage.h index 20c24bab1..320ace2ab 100644 --- a/app/node/project/footage/footage.h +++ b/app/node/project/footage/footage.h @@ -173,6 +173,9 @@ public: virtual int GetTotalStreamCount() const override { return total_stream_count_; } + virtual bool LoadCustom(QXmlStreamReader *reader, SerializedData *data) override; + virtual void SaveCustom(QXmlStreamWriter *writer) const override; + static const QString kFilenameInput; protected: diff --git a/app/node/project/serializer/serializer210528.cpp b/app/node/project/serializer/serializer210528.cpp index 563d63a86..3c6e7e585 100644 --- a/app/node/project/serializer/serializer210528.cpp +++ b/app/node/project/serializer/serializer210528.cpp @@ -31,16 +31,7 @@ ProjectSerializer210528::LoadData ProjectSerializer210528::Load(Project *project XMLNodeData xml_node_data; 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.node_ptrs)); - - } else if (reader->name() == QStringLiteral("uuid")) { + if (reader->name() == QStringLiteral("uuid")) { project->SetUuid(QUuid::fromString(reader->readElementText())); diff --git a/app/node/project/serializer/serializer210907.cpp b/app/node/project/serializer/serializer210907.cpp index db926f496..1676cc2ad 100644 --- a/app/node/project/serializer/serializer210907.cpp +++ b/app/node/project/serializer/serializer210907.cpp @@ -31,16 +31,7 @@ ProjectSerializer210907::LoadData ProjectSerializer210907::Load(Project *project XMLNodeData xml_node_data; 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.node_ptrs)); - - } else if (reader->name() == QStringLiteral("uuid")) { + if (reader->name() == QStringLiteral("uuid")) { project->SetUuid(QUuid::fromString(reader->readElementText())); diff --git a/app/node/project/serializer/serializer211228.cpp b/app/node/project/serializer/serializer211228.cpp index a87c225c5..e86c6c9fe 100644 --- a/app/node/project/serializer/serializer211228.cpp +++ b/app/node/project/serializer/serializer211228.cpp @@ -33,16 +33,7 @@ ProjectSerializer211228::LoadData ProjectSerializer211228::Load(Project *project XMLNodeData xml_node_data; 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.node_ptrs)); - - } else if (reader->name() == QStringLiteral("uuid")) { + if (reader->name() == QStringLiteral("uuid")) { project->SetUuid(QUuid::fromString(reader->readElementText())); diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index a7f4eced6..387bd64b2 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -35,16 +35,7 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project LoadData load_data; 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.node_ptrs)); - - } else if (reader->name() == QStringLiteral("uuid")) { + if (reader->name() == QStringLiteral("uuid")) { project->SetUuid(QUuid::fromString(reader->readElementText())); diff --git a/app/node/project/serializer/serializer230220.cpp b/app/node/project/serializer/serializer230220.cpp index ffdbb3eba..f20c5c81e 100644 --- a/app/node/project/serializer/serializer230220.cpp +++ b/app/node/project/serializer/serializer230220.cpp @@ -26,1310 +26,18 @@ namespace olive { -// These wrappers may appear to do nothing, but they seem to address a bug where sometimes -// QXmlStreamWriter would insert nonsense null characters into a project. This is probably some -// real edge case like compiler optimization or some bullshit like that. I don't even know if it -// affects all platforms, but it definitely affected me on Linux. -void WriteStartElement(QXmlStreamWriter *writer, const QString &s) -{ - writer->writeStartElement(s); -} - -void WriteEndElement(QXmlStreamWriter *writer) -{ - writer->writeEndElement(); -} - ProjectSerializer230220::LoadData ProjectSerializer230220::Load(Project *project, QXmlStreamReader *reader, void *reserved) const { - QMap > properties; - QMap > positions; - XMLNodeData xml_node_data; - LoadData load_data; - 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.node_ptrs)); - - } 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 { - // Disable cache while node is being loaded (we'll re-enable it later) - node->SetCachesEnabled(false); - - LoadNode(node, xml_node_data, reader); - - node->setParent(project); - } - } - } else { - reader->skipCurrentElement(); - } - } - - } else if (reader->name() == QStringLiteral("keyframes")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - QString node_id; - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("id")) { - node_id = attr.value().toString(); - break; - } - } - - Node *n = nullptr; - if (!node_id.isEmpty()) { - n = NodeFactory::CreateFromID(node_id); - } - - if (!n) { - reader->skipCurrentElement(); - } else { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("input")) { - QString input_id; - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("id")) { - input_id = attr.value().toString(); - break; - } - } - - if (input_id.isEmpty()) { - reader->skipCurrentElement(); - } else { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("element")) { - QString element_id; - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("id")) { - element_id = attr.value().toString(); - break; - } - } - - if (element_id.isEmpty()) { - reader->skipCurrentElement(); - } else { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("track")) { - QString track_id; - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("id")) { - track_id = attr.value().toString(); - break; - } - } - - if (track_id.isEmpty()) { - reader->skipCurrentElement(); - } else { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("key")) { - NodeKeyframe *key = new NodeKeyframe(); - key->set_input(input_id); - key->set_element(element_id.toInt()); - key->set_track(track_id.toInt()); - - LoadKeyframe(reader, key, n->GetInputDataType(input_id)); - - load_data.keyframes[node_id].append(key); - } else { - reader->skipCurrentElement(); - } - } - } - } else { - reader->skipCurrentElement(); - } - } - } - } else { - reader->skipCurrentElement(); - } - } - } - } else { - reader->skipCurrentElement(); - } - } - } - - delete n; - } else { - reader->skipCurrentElement(); - } - } - - } else if (reader->name() == QStringLiteral("markers")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("marker")) { - TimelineMarker *marker = new TimelineMarker(); - LoadMarker(reader, marker); - load_data.markers.push_back(marker); - } 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; - } - } - - if (context_ptr) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - quintptr node_ptr; - Node::Position node_pos; - - if (LoadPosition(reader, &node_ptr, &node_pos)) { - if (node_ptr) { - positions[context_ptr].insert(node_ptr, node_pos); - } else { - qWarning() << "Failed to find pointer for node position"; - reader->skipCurrentElement(); - } - } - } else { - reader->skipCurrentElement(); - } - } - } else { - qWarning() << "Attempted to load context with no pointer"; - reader->skipCurrentElement(); - } - - } else { - - reader->skipCurrentElement(); - - } - - } - - - } else if (reader->name() == QStringLiteral("properties")) { - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("node")) { - quintptr ptr = 0; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("ptr")) { - ptr = attr.value().toULongLong(); - - // Only attribute we're looking for right now - break; - } - } - - if (ptr) { - QMap properties_for_node; - while (XMLReadNextStartElement(reader)) { - properties_for_node.insert(reader->name().toString(), reader->readElementText()); - } - properties.insert(ptr, properties_for_node); - } - } else { - reader->skipCurrentElement(); - } - } - - } else { - - // Skip this - reader->skipCurrentElement(); - - } - } - - // Resolve positions - for (auto it=positions.cbegin(); it!=positions.cend(); it++) { - Node *ctx = xml_node_data.node_ptrs.value(it.key()); - if (ctx) { - for (auto jt=it.value().cbegin(); jt!=it.value().cend(); jt++) { - Node *n = xml_node_data.node_ptrs.value(jt.key()); - if (n) { - ctx->SetNodePositionInContext(n, jt.value()); - } - } - } - } - - // Make connections - PostConnect(xml_node_data); - - // Resolve serialized properties (if any) - for (auto it=properties.cbegin(); it!=properties.cend(); it++) { - Node *node = xml_node_data.node_ptrs.value(it.key()); - if (node) { - load_data.properties.insert(node, it.value()); - } - } - - // Re-enable caches and resolve tracks - for (Node *n : project->nodes()) { - n->SetCachesEnabled(true); - } + project->Load(reader); return load_data; } void ProjectSerializer230220::Save(QXmlStreamWriter *writer, const SaveData &data, void *reserved) const { - if (!data.GetOnlySerializeMarkers().empty()) { - WriteStartElement(writer, QStringLiteral("markers")); - - for (auto it=data.GetOnlySerializeMarkers().cbegin(); it!=data.GetOnlySerializeMarkers().cend(); it++) { - TimelineMarker *marker = *it; - - WriteStartElement(writer, QStringLiteral("marker")); - - SaveMarker(writer, marker); - - WriteEndElement(writer); // marker - } - - WriteEndElement(writer); // markers - - } else if (!data.GetOnlySerializeKeyframes().empty()) { - - WriteStartElement(writer, QStringLiteral("keyframes")); - - // Organize keyframes into node+input - QHash > > > > organized; - - for (auto it=data.GetOnlySerializeKeyframes().cbegin(); it!=data.GetOnlySerializeKeyframes().cend(); it++) { - NodeKeyframe *key = *it; - organized[key->parent()->id()][key->input()][key->element()][key->track()].append(key); - } - - for (auto it=organized.cbegin(); it!=organized.cend(); it++) { - WriteStartElement(writer, QStringLiteral("node")); - - writer->writeAttribute(QStringLiteral("id"), it.key()); - - for (auto jt=it.value().cbegin(); jt!=it.value().cend(); jt++) { - WriteStartElement(writer, QStringLiteral("input")); - - writer->writeAttribute(QStringLiteral("id"), jt.key()); - - for (auto kt=jt.value().cbegin(); kt!=jt.value().cend(); kt++) { - WriteStartElement(writer, QStringLiteral("element")); - - writer->writeAttribute(QStringLiteral("id"), QString::number(kt.key())); - - for (auto lt=kt.value().cbegin(); lt!=kt.value().cend(); lt++) { - const QVector &keys = lt.value(); - - WriteStartElement(writer, QStringLiteral("track")); - - writer->writeAttribute(QStringLiteral("id"), QString::number(lt.key())); - - for (NodeKeyframe *key : keys) { - WriteStartElement(writer, QStringLiteral("key")); - SaveKeyframe(writer, key, key->parent()->GetInputDataType(key->input())); - WriteEndElement(writer); // key - } - - WriteEndElement(writer); // track - } - - WriteEndElement(writer); // element - } - - WriteEndElement(writer); // input - } - - WriteEndElement(writer); // node; - } - - WriteEndElement(writer); // keyframes - - } else if (Project *project = data.GetProject()) { - - writer->writeTextElement(QStringLiteral("uuid"), project->GetUuid().toString()); - - WriteStartElement(writer, QStringLiteral("nodes")); - - const QVector &using_node_list = (data.GetOnlySerializeNodes().isEmpty()) ? project->nodes() : data.GetOnlySerializeNodes(); - - foreach (Node* node, using_node_list) { - WriteStartElement(writer, 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); - - WriteEndElement(writer); // node - } - - WriteEndElement(writer); // nodes - - WriteStartElement(writer, QStringLiteral("positions")); - - foreach (Node* context, using_node_list) { - const Node::PositionMap &map = context->GetContextPositions(); - - if (!map.isEmpty()) { - WriteStartElement(writer, QStringLiteral("context")); - - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(context))); - - for (auto jt=map.cbegin(); jt!=map.cend(); jt++) { - if (data.GetOnlySerializeNodes().isEmpty() || data.GetOnlySerializeNodes().contains(jt.key())) { - WriteStartElement(writer, QStringLiteral("node")); - SavePosition(writer, jt.key(), jt.value()); - WriteEndElement(writer); // node - } - } - - WriteEndElement(writer); // context - } - } - - WriteEndElement(writer); // positions - - WriteStartElement(writer, QStringLiteral("properties")); - - for (auto it=data.GetProperties().cbegin(); it!=data.GetProperties().cend(); it++) { - WriteStartElement(writer, QStringLiteral("node")); - - writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast(it.key()))); - - for (auto jt=it.value().cbegin(); jt!=it.value().cend(); jt++) { - writer->writeTextElement(jt.key(), jt.value()); - } - - WriteEndElement(writer); // node - } - - WriteEndElement(writer); // properties - - // Save main window project layout - project->GetLayoutInfo().toXml(writer); - - } else { - - qCritical() << "Cannot save nodes without project object"; - - } -} - -void ProjectSerializer230220::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")) { - quintptr ptr = reader->readElementText().toULongLong(); - xml_node_data.node_ptrs.insert(ptr, node); - } else if (reader->name() == QStringLiteral("label")) { - node->SetLabel(reader->readElementText()); - } else if (reader->name() == QStringLiteral("uuid")) { - xml_node_data.node_uuids.insert(node, 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()}); - } 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 if (reader->name() == QStringLiteral("caches")) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("audio")) { - node->audio_playback_cache()->SetUuid(QUuid::fromString(reader->readElementText())); - } else if (reader->name() == QStringLiteral("video")) { - node->video_frame_cache()->SetUuid(QUuid::fromString(reader->readElementText())); - } else if (reader->name() == QStringLiteral("thumb")) { - node->thumbnail_cache()->SetUuid(QUuid::fromString(reader->readElementText())); - } else if (reader->name() == QStringLiteral("waveform")) { - node->waveform_cache()->SetUuid(QUuid::fromString(reader->readElementText())); - } else { - reader->skipCurrentElement(); - } - } - } else { - reader->skipCurrentElement(); - } - } - - node->LoadFinishedEvent(); -} - -void ProjectSerializer230220::SaveNode(Node *node, QXmlStreamWriter *writer) const -{ - writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast(node))); - - writer->writeTextElement(QStringLiteral("label"), node->GetLabel()); - writer->writeTextElement(QStringLiteral("color"), QString::number(node->GetOverrideColor())); - - foreach (const QString& input, node->inputs()) { - WriteStartElement(writer, QStringLiteral("input")); - - SaveInput(node, writer, input); - - WriteEndElement(writer); // input - } - - WriteStartElement(writer, QStringLiteral("links")); - foreach (Node* link, node->links()) { - writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast(link))); - } - WriteEndElement(writer); // links - - WriteStartElement(writer, QStringLiteral("connections")); - for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) { - WriteStartElement(writer, 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))); - - WriteEndElement(writer); // connection - } - WriteEndElement(writer); // connections - - WriteStartElement(writer, QStringLiteral("hints")); - for (auto it=node->GetValueHints().cbegin(); it!=node->GetValueHints().cend(); it++) { - WriteStartElement(writer, QStringLiteral("hint")); - - writer->writeAttribute(QStringLiteral("input"), it.key().input); - writer->writeAttribute(QStringLiteral("element"), QString::number(it.key().element)); - - SaveValueHint(&it.value(), writer); - - WriteEndElement(writer); // hint - } - WriteEndElement(writer); // hints - - WriteStartElement(writer, QStringLiteral("caches")); - - writer->writeTextElement(QStringLiteral("audio"), node->audio_playback_cache()->GetUuid().toString()); - writer->writeTextElement(QStringLiteral("video"), node->video_frame_cache()->GetUuid().toString()); - writer->writeTextElement(QStringLiteral("thumb"), node->thumbnail_cache()->GetUuid().toString()); - writer->writeTextElement(QStringLiteral("waveform"), node->waveform_cache()->GetUuid().toString()); - - WriteEndElement(writer); // caches - - WriteStartElement(writer, QStringLiteral("custom")); - - SaveNodeCustom(writer, node); - - WriteEndElement(writer); // custom -} - -void ProjectSerializer230220::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const -{ - if (dynamic_cast(node)) { - // Ignore input of group - reader->skipCurrentElement(); - return; - } - - 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 ProjectSerializer230220::SaveInput(Node *node, QXmlStreamWriter *writer, const QString &id) const -{ - writer->writeAttribute(QStringLiteral("id"), id); - - WriteStartElement(writer, QStringLiteral("primary")); - - SaveImmediate(writer, node, id, -1); - - WriteEndElement(writer); // primary - - WriteStartElement(writer, QStringLiteral("subelements")); - - int arr_sz = node->InputArraySize(id); - - writer->writeAttribute(QStringLiteral("count"), QString::number(arr_sz)); - - for (int i=0; iGetInputDataType(input); - - // HACK: SubtitleParams contain the actual subtitle data, so loading/replacing it will overwrite - // the valid subtitles. We hack around it by simply skipping loading subtitles, we'll see - // if this ends up being an issue in the future. - if (data_type == NodeValue::kSubtitleParams) { - reader->skipCurrentElement(); - return; - } - - 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 = TypeSerializer::LoadAudioParams(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, true); - } - } - - 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")) { - NodeKeyframe* key = new NodeKeyframe(); - key->set_input(input); - key->set_element(element); - key->set_track(track); - - LoadKeyframe(reader, key, data_type); - key->setParent(node); - } 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 ProjectSerializer230220::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 - WriteStartElement(writer, QStringLiteral("standard")); - - foreach (const QVariant& v, node->GetSplitStandardValue(input, element)) { - WriteStartElement(writer, QStringLiteral("track")); - - if (data_type == NodeValue::kVideoParams) { - v.value().Save(writer); - } else if (data_type == NodeValue::kAudioParams) { - TypeSerializer::SaveAudioParams(writer, v.value()); - } else { - writer->writeCharacters(NodeValue::ValueToString(data_type, v, true)); - } - - WriteEndElement(writer); // track - } - - WriteEndElement(writer); // standard - - // Write keyframes - WriteStartElement(writer, QStringLiteral("keyframes")); - - for (const NodeKeyframeTrack& track : node->GetKeyframeTracks(input, element)) { - WriteStartElement(writer, QStringLiteral("track")); - - for (NodeKeyframe* key : track) { - WriteStartElement(writer, QStringLiteral("key")); - - SaveKeyframe(writer, key, data_type); - - WriteEndElement(writer); // key - } - - WriteEndElement(writer); // track - } - - WriteEndElement(writer); // 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()); - } -} - -void ProjectSerializer230220::LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, NodeValue::Type data_type) const -{ - QString key_input; - 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->set_time(rational::fromString(attr.value().toString().toStdString())); - } else if (attr.name() == QStringLiteral("type")) { - key->set_type_no_bezier_adj(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->set_value(NodeValue::StringToValue(data_type, reader->readElementText(), true)); - - key->set_bezier_control_in(key_in_handle); - key->set_bezier_control_out(key_out_handle); -} - -void ProjectSerializer230220::SaveKeyframe(QXmlStreamWriter *writer, NodeKeyframe *key, NodeValue::Type data_type) const -{ - writer->writeAttribute(QStringLiteral("input"), key->input()); - writer->writeAttribute(QStringLiteral("time"), QString::fromStdString(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)); -} - -bool ProjectSerializer230220::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 ProjectSerializer230220::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 ProjectSerializer230220::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, l.passthrough_id); - - l.group->SetInputFlag(l.passthrough_id, InputFlag(l.custom_flags.value())); - - if (!l.custom_name.isEmpty()) { - l.group->SetInputName(l.passthrough_id, l.custom_name); - } - - l.group->SetInputDataType(l.passthrough_id, l.data_type); - - l.group->SetDefaultValue(l.passthrough_id, l.default_val); - - for (auto it=l.custom_properties.cbegin(); it!=l.custom_properties.cend(); it++) { - l.group->SetInputProperty(l.passthrough_id, it.key(), it.value()); - } - } - } - - 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 ProjectSerializer230220::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); - } 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 if (reader->name() == QStringLiteral("id")) { - link.passthrough_id = reader->readElementText(); - } else if (reader->name() == QStringLiteral("name")) { - link.custom_name = reader->readElementText(); - } else if (reader->name() == QStringLiteral("flags")) { - link.custom_flags = InputFlags(reader->readElementText().toULongLong()); - } else if (reader->name() == QStringLiteral("type")) { - link.data_type = NodeValue::GetDataTypeFromName(reader->readElementText()); - } else if (reader->name() == QStringLiteral("default")) { - link.default_val = NodeValue::StringToValue(link.data_type, reader->readElementText(), false); - } else if (reader->name() == QStringLiteral("properties")) { - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("property")) { - QString key; - QString value; - - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("key")) { - key = reader->readElementText(); - } else if (reader->name() == QStringLiteral("value")) { - value = reader->readElementText(); - } else { - reader->skipCurrentElement(); - } - } - - if (!key.isEmpty()) { - link.custom_properties.insert(key, value); - } - } else { - reader->skipCurrentElement(); - } - } - } 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 ProjectSerializer230220::SaveNodeCustom(QXmlStreamWriter *writer, Node *node) const -{ - if (ViewerOutput *viewer = dynamic_cast(node)) { - // Write TimelinePoints - WriteStartElement(writer, QStringLiteral("points")); - SaveTimelinePoints(writer, viewer); - WriteEndElement(writer); // 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)) { - WriteStartElement(writer, QStringLiteral("inputpassthroughs")); - - foreach (const NodeGroup::InputPassthrough &ip, group->GetInputPassthroughs()) { - WriteStartElement(writer, QStringLiteral("inputpassthrough")); - - // Reference to inner input - writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.second.node()))); - writer->writeTextElement(QStringLiteral("input"), ip.second.input()); - writer->writeTextElement(QStringLiteral("element"), QString::number(ip.second.element())); - - // ID of passthrough - writer->writeTextElement(QStringLiteral("id"), ip.first); - - // Passthrough-specific details - NodeInput group_input(group, ip.first, ip.second.element()); - writer->writeTextElement(QStringLiteral("name"), group->Node::GetInputName(ip.first)); - - writer->writeTextElement(QStringLiteral("flags"), QString::number((group_input.GetFlags() & ~ip.second.GetFlags()).value())); - - NodeValue::Type data_type = group_input.GetDataType(); - writer->writeTextElement(QStringLiteral("type"), NodeValue::GetDataTypeName(data_type)); - - writer->writeTextElement(QStringLiteral("default"), NodeValue::ValueToString(data_type, group_input.GetDefaultValue(), false)); - - WriteStartElement(writer, QStringLiteral("properties")); - auto p = group_input.GetProperties(); - for (auto it=p.cbegin(); it!=p.cend(); it++) { - WriteStartElement(writer, QStringLiteral("property")); - writer->writeTextElement(QStringLiteral("key"), it.key()); - writer->writeTextElement(QStringLiteral("value"), it.value().toString()); - WriteEndElement(writer); // property - } - WriteEndElement(writer); // properties - - WriteEndElement(writer); // input - } - - WriteEndElement(writer); // inputpassthroughs - - writer->writeTextElement(QStringLiteral("outputpassthrough"), QString::number(reinterpret_cast(group->GetOutputPassthrough()))); - } -} - -void ProjectSerializer230220::LoadTimelinePoints(QXmlStreamReader *reader, ViewerOutput *viewer) const -{ - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("markers")) { - LoadMarkerList(reader, viewer->GetMarkers()); - } else if (reader->name() == QStringLiteral("workarea")) { - LoadWorkArea(reader, viewer->GetWorkArea()); - } else { - reader->skipCurrentElement(); - } - } -} - -void ProjectSerializer230220::SaveTimelinePoints(QXmlStreamWriter *writer, ViewerOutput *viewer) const -{ - WriteStartElement(writer, QStringLiteral("workarea")); - SaveWorkArea(writer, viewer->GetWorkArea()); - WriteEndElement(writer); // workarea - - WriteStartElement(writer, QStringLiteral("markers")); - SaveMarkerList(writer, viewer->GetMarkers()); - WriteEndElement(writer); // markers -} - -void ProjectSerializer230220::LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const -{ - rational in, out; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == QStringLiteral("name")) { - marker->set_name(attr.value().toString()); - } else if (attr.name() == QStringLiteral("in")) { - in = rational::fromString(attr.value().toString().toStdString()); - } else if (attr.name() == QStringLiteral("out")) { - out = rational::fromString(attr.value().toString().toStdString()); - } else if (attr.name() == QStringLiteral("color")) { - marker->set_color(attr.value().toInt()); - } - } - - marker->set_time(TimeRange(in, out)); - - // This element has no inner text, so just skip it - reader->skipCurrentElement(); -} - -void ProjectSerializer230220::SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) const -{ - writer->writeAttribute(QStringLiteral("name"), marker->name()); - writer->writeAttribute(QStringLiteral("in"), QString::fromStdString(marker->time().in().toString())); - writer->writeAttribute(QStringLiteral("out"), QString::fromStdString(marker->time().out().toString())); - writer->writeAttribute(QStringLiteral("color"), QString::number(marker->color())); -} - -void ProjectSerializer230220::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().toStdString()); - } else if (attr.name() == QStringLiteral("out")) { - range_out = rational::fromString(attr.value().toString().toStdString()); - } - } - - TimeRange loaded_workarea(range_in, range_out); - - if (loaded_workarea != workarea->range()) { - workarea->set_range(loaded_workarea); - } - - reader->skipCurrentElement(); -} - -void ProjectSerializer230220::SaveWorkArea(QXmlStreamWriter *writer, TimelineWorkArea *workarea) const -{ - writer->writeAttribute(QStringLiteral("enabled"), QString::number(workarea->enabled())); - writer->writeAttribute(QStringLiteral("in"), QString::fromStdString(workarea->in().toString())); - writer->writeAttribute(QStringLiteral("out"), QString::fromStdString(workarea->out().toString())); -} - -void ProjectSerializer230220::LoadMarkerList(QXmlStreamReader *reader, TimelineMarkerList *markers) const -{ - while (XMLReadNextStartElement(reader)) { - if (reader->name() == QStringLiteral("marker")) { - TimelineMarker *marker = new TimelineMarker(markers); - LoadMarker(reader, marker); - } else { - reader->skipCurrentElement(); - } - } -} - -void ProjectSerializer230220::SaveMarkerList(QXmlStreamWriter *writer, TimelineMarkerList *markers) const -{ - for (auto it=markers->cbegin(); it!=markers->cend(); it++) { - TimelineMarker* marker = *it; - - WriteStartElement(writer, QStringLiteral("marker")); - - SaveMarker(writer, marker); - - WriteEndElement(writer); // marker - } -} - -void ProjectSerializer230220::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 ProjectSerializer230220::SaveValueHint(const Node::ValueHint *hint, QXmlStreamWriter *writer) const -{ - WriteStartElement(writer, QStringLiteral("types")); - - for (auto it=hint->types().cbegin(); it!=hint->types().cend(); it++) { - writer->writeTextElement(QStringLiteral("type"), QString::number(*it)); - } - - WriteEndElement(writer); // types - - writer->writeTextElement(QStringLiteral("index"), QString::number(hint->index())); - - writer->writeTextElement(QStringLiteral("tag"), hint->tag()); } } diff --git a/app/node/project/serializer/serializer230220.h b/app/node/project/serializer/serializer230220.h index 76b9c66f3..a71b3b4b7 100644 --- a/app/node/project/serializer/serializer230220.h +++ b/app/node/project/serializer/serializer230220.h @@ -40,86 +40,6 @@ protected: return 230220; } -private: - struct XMLNodeData { - struct SerializedConnection { - NodeInput input; - quintptr output_node; - }; - - struct BlockLink { - Node* block; - quintptr link; - }; - - struct GroupLink { - NodeGroup *group; - QString passthrough_id; - quintptr input_node; - QString input_id; - int input_element; - QString custom_name; - InputFlags custom_flags; - NodeValue::Type data_type; - QVariant default_val; - QHash custom_properties; - }; - - QHash node_ptrs; - QList desired_connections; - QList block_links; - QVector group_input_links; - QHash group_output_links; - QHash node_uuids; - - }; - - 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; - - void LoadKeyframe(QXmlStreamReader *reader, NodeKeyframe *key, NodeValue::Type data_type) const; - - void SaveKeyframe(QXmlStreamWriter *writer, NodeKeyframe *key, NodeValue::Type data_type) 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, ViewerOutput *viewer) const; - - void SaveTimelinePoints(QXmlStreamWriter *writer, ViewerOutput *viewer) const; - - void LoadMarker(QXmlStreamReader *reader, TimelineMarker *marker) const; - - void SaveMarker(QXmlStreamWriter *writer, TimelineMarker *marker) 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; - }; } diff --git a/app/node/serializeddata.cpp b/app/node/serializeddata.cpp new file mode 100644 index 000000000..3161d5cdc --- /dev/null +++ b/app/node/serializeddata.cpp @@ -0,0 +1,25 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 "serializeddata.h" + +namespace olive { + +} diff --git a/app/node/serializeddata.h b/app/node/serializeddata.h new file mode 100644 index 000000000..442e6baa9 --- /dev/null +++ b/app/node/serializeddata.h @@ -0,0 +1,69 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 Olive Studios LLC + + 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 SERIALIZEDDATA_H +#define SERIALIZEDDATA_H + +#include +#include + +#include "node.h" + +namespace olive { + +class NodeGroup; + +struct SerializedData { + struct SerializedConnection { + NodeInput input; + quintptr output_node; + }; + + struct BlockLink { + Node* block; + quintptr link; + }; + + struct GroupLink { + NodeGroup *group; + QString passthrough_id; + quintptr input_node; + QString input_id; + int input_element; + QString custom_name; + InputFlags custom_flags; + NodeValue::Type data_type; + QVariant default_val; + QHash custom_properties; + }; + + QMap > positions; + QHash node_ptrs; + QList desired_connections; + QList block_links; + QVector group_input_links; + QHash group_output_links; + QHash node_uuids; + +}; + +} + +#endif // SERIALIZEDDATA_H diff --git a/app/task/project/load/loadbasetask.h b/app/task/project/load/loadbasetask.h index 31970f044..7674764df 100644 --- a/app/task/project/load/loadbasetask.h +++ b/app/task/project/load/loadbasetask.h @@ -42,12 +42,19 @@ public: return filename_; } + const MainWindowLayoutInfo &GetLoadedLayout() const + { + return layout_; + } + protected: Project* project_; private: QString filename_; + MainWindowLayoutInfo layout_; + }; } diff --git a/app/task/project/save/save.h b/app/task/project/save/save.h index f3d8cc9aa..6ad7b80da 100644 --- a/app/task/project/save/save.h +++ b/app/task/project/save/save.h @@ -42,6 +42,11 @@ public: override_filename_ = filename; } + void SetLayout(const MainWindowLayoutInfo &layout) + { + layout_ = layout; + } + protected: virtual bool Run() override; @@ -52,6 +57,8 @@ private: bool use_compression_; + MainWindowLayoutInfo layout_; + }; } diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp index 7c72faad3..085eb3a66 100644 --- a/app/timeline/timelinemarker.cpp +++ b/app/timeline/timelinemarker.cpp @@ -144,6 +144,65 @@ QRect TimelineMarker::Draw(QPainter *p, const QPoint &pt, int max_right, double } } +bool TimelineMarker::load(QXmlStreamReader *reader) +{ + rational in, out; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("name")) { + this->set_name(attr.value().toString()); + } else if (attr.name() == QStringLiteral("in")) { + in = rational::fromString(attr.value().toString().toStdString()); + } else if (attr.name() == QStringLiteral("out")) { + out = rational::fromString(attr.value().toString().toStdString()); + } else if (attr.name() == QStringLiteral("color")) { + this->set_color(attr.value().toInt()); + } + } + + this->set_time(TimeRange(in, out)); + + // This element has no inner text, so just skip it + reader->skipCurrentElement(); +} + +void TimelineMarker::save(QXmlStreamWriter *writer) const +{ + writer->writeAttribute(QStringLiteral("name"), this->name()); + writer->writeAttribute(QStringLiteral("in"), QString::fromStdString(this->time().in().toString())); + writer->writeAttribute(QStringLiteral("out"), QString::fromStdString(this->time().out().toString())); + writer->writeAttribute(QStringLiteral("color"), QString::number(this->color())); +} + +bool TimelineMarkerList::load(QXmlStreamReader *reader) +{ + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("marker")) { + TimelineMarker *marker = new TimelineMarker(this); + if (!marker->load(reader)) { + return false; + } + } else { + reader->skipCurrentElement(); + } + } + + return true; +} + +void TimelineMarkerList::save(QXmlStreamWriter *writer) const +{ + for (auto it=this->cbegin(); it!=this->cend(); it++) { + TimelineMarker* marker = *it; + + writer->writeStartElement(QStringLiteral("marker")); + + marker->save(writer); + + writer->writeEndElement(); // marker + } +} + void TimelineMarkerList::childEvent(QChildEvent *e) { QObject::childEvent(e); diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h index f6c65dfd2..f1b6a5f3c 100644 --- a/app/timeline/timelinemarker.h +++ b/app/timeline/timelinemarker.h @@ -55,6 +55,9 @@ public: static int GetMarkerHeight(const QFontMetrics &fm); QRect Draw(QPainter *p, const QPoint &pt, int max_right, double scale, bool selected); + bool load(QXmlStreamReader *reader); + void save(QXmlStreamWriter *writer) const; + signals: void TimeChanged(const TimeRange& time); @@ -89,6 +92,9 @@ public: inline TimelineMarker *front() const { return markers_.front(); } inline size_t size() const { return markers_.size(); } + bool load(QXmlStreamReader *reader); + void save(QXmlStreamWriter *writer) const; + TimelineMarker *GetMarkerAtTime(const rational &t) const { for (auto it=markers_.cbegin(); it!=markers_.cend(); it++) { diff --git a/app/timeline/timelineworkarea.cpp b/app/timeline/timelineworkarea.cpp index 59cf04d49..9e04975be 100644 --- a/app/timeline/timelineworkarea.cpp +++ b/app/timeline/timelineworkarea.cpp @@ -55,6 +55,48 @@ void TimelineWorkArea::set_range(const TimeRange &range) emit RangeChanged(workarea_range_); } +bool TimelineWorkArea::load(QXmlStreamReader *reader) +{ + rational range_in = this->in(); + rational range_out = this->out(); + + uint version = 0; + XMLAttributeLoop(reader, attr) { + if (attr.name() == QStringLiteral("version")) { + version = attr.value().toUInt(); + } + } + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("enabled")) { + this->set_enabled(reader->readElementText() != QStringLiteral("0")); + } else if (reader->name() == QStringLiteral("in")) { + range_in = rational::fromString(reader->readElementText().toStdString()); + } else if (reader->name() == QStringLiteral("out")) { + range_out = rational::fromString(reader->readElementText().toStdString()); + } else { + reader->skipCurrentElement(); + } + } + + TimeRange loaded_workarea(range_in, range_out); + + if (loaded_workarea != this->range()) { + this->set_range(loaded_workarea); + } + + return true; +} + +void TimelineWorkArea::save(QXmlStreamWriter *writer) const +{ + writer->writeAttribute(QStringLiteral("version"), QString::number(1)); + + writer->writeTextElement(QStringLiteral("enabled"), QString::number(this->enabled())); + writer->writeTextElement(QStringLiteral("in"), QString::fromStdString(this->in().toString())); + writer->writeTextElement(QStringLiteral("out"), QString::fromStdString(this->out().toString())); +} + const rational &TimelineWorkArea::in() const { return workarea_range_.in(); diff --git a/app/timeline/timelineworkarea.h b/app/timeline/timelineworkarea.h index 2036ac857..e48e748e2 100644 --- a/app/timeline/timelineworkarea.h +++ b/app/timeline/timelineworkarea.h @@ -45,6 +45,9 @@ public: const TimeRange& range() const; void set_range(const TimeRange& range); + bool load(QXmlStreamReader *reader); + void save(QXmlStreamWriter *writer) const; + static const rational kResetIn; static const rational kResetOut;