From a4934f1d47f33032ed346d47acfb06346f41da6d Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 28 Dec 2021 21:52:28 -0800 Subject: [PATCH] implemented saving/loading for groups --- app/common/xmlutils.cpp | 65 +++++++++++++++++++--------- app/common/xmlutils.h | 16 +++++-- app/core.cpp | 2 +- app/node/factory.cpp | 11 +++++ app/node/factory.h | 3 ++ app/node/group/group.cpp | 62 +++++++++++++++++++++----- app/node/group/group.h | 52 +++------------------- app/node/nodecopypaste.cpp | 7 +-- app/node/project/project.cpp | 5 +-- app/panel/node/node.cpp | 1 + app/panel/node/node.h | 8 ++++ app/panel/param/param.h | 10 +++++ app/window/mainwindow/mainwindow.cpp | 20 ++++++++- app/window/mainwindow/mainwindow.h | 2 + 14 files changed, 170 insertions(+), 94 deletions(-) diff --git a/app/common/xmlutils.cpp b/app/common/xmlutils.cpp index f778b6440..97ec92057 100644 --- a/app/common/xmlutils.cpp +++ b/app/common/xmlutils.cpp @@ -24,15 +24,30 @@ #include "node/factory.h" #include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/nodeview/nodeviewundo.h" +//#include "widget/timelinewidget/undo/timelineundogeneral.h" namespace olive { -void XMLConnectNodes(const XMLNodeData &xml_node_data, uint version, MultiUndoCommand *command) +bool XMLReadNextStartElement(QXmlStreamReader *reader) { - foreach (const XMLNodeData::SerializedConnection& con, xml_node_data.desired_connections) { - Node *out = xml_node_data.node_ptrs.value(con.output_node); + QXmlStreamReader::TokenType token; - if (out) { + while ((token = reader->readNext()) != QXmlStreamReader::Invalid + && token != QXmlStreamReader::EndDocument) { + if (reader->isEndElement()) { + return false; + } else if (reader->isStartElement()) { + return true; + } + } + + return false; +} + +void XMLNodeData::PostConnect(uint version, MultiUndoCommand *command) const +{ + foreach (const XMLNodeData::SerializedConnection& con, desired_connections) { + if (Node *out = node_ptrs.value(con.output_node)) { // Use output param as hint tag since we grandfathered those in Node::ValueHint hint(con.output_param); @@ -53,28 +68,36 @@ void XMLConnectNodes(const XMLNodeData &xml_node_data, uint version, MultiUndoCo } } } -} -bool XMLReadNextStartElement(QXmlStreamReader *reader) -{ - QXmlStreamReader::TokenType token; - - while ((token = reader->readNext()) != QXmlStreamReader::Invalid - && token != QXmlStreamReader::EndDocument) { - if (reader->isEndElement()) { - return false; - } else if (reader->isStartElement()) { - return true; + foreach (const XMLNodeData::BlockLink& l, block_links) { + Node *a = l.block; + Node *b = node_ptrs.value(l.link); + if (command) { + command->add_child(new NodeLinkCommand(a, b, true)); + } else { + Node::Link(a, b); } } - return false; -} + foreach (const XMLNodeData::GroupLink &l, group_input_links) { + if (Node *input_node = node_ptrs.value(l.input_node)) { + NodeInput resolved(input_node, l.input_id, l.input_element); + if (command) { + command->add_child(new NodeGroupAddInputPassthrough(l.group, resolved)); + } else { + l.group->AddInputPassthrough(resolved); + } + } + } -void XMLLinkBlocks(const XMLNodeData &xml_node_data) -{ - foreach (const XMLNodeData::BlockLink& l, xml_node_data.block_links) { - Block::Link(l.block, static_cast(xml_node_data.node_ptrs.value(l.link))); + for (auto it=group_output_links.cbegin(); it!=group_output_links.cend(); it++) { + if (Node *output_node = node_ptrs.value(it.value())) { + if (command) { + command->add_child(new NodeGroupSetOutputPassthrough(it.key(), output_node)); + } else { + it.key()->SetOutputPassthrough(output_node); + } + } } } diff --git a/app/common/xmlutils.h b/app/common/xmlutils.h index e8b88d6a3..d0488ccd0 100644 --- a/app/common/xmlutils.h +++ b/app/common/xmlutils.h @@ -31,6 +31,7 @@ namespace olive { class Block; class Node; class NodeInput; +class NodeGroup; #define XMLAttributeLoop(reader, item) \ foreach (const QXmlStreamAttribute& item, reader->attributes()) @@ -49,14 +50,23 @@ struct XMLNodeData { quintptr link; }; + struct GroupLink { + NodeGroup *group; + quintptr input_node; + QString input_id; + int input_element; + }; + QHash node_ptrs; QList desired_connections; QList block_links; + QVector group_input_links; + QHash group_output_links; + + void PostConnect(uint version, MultiUndoCommand *command = nullptr) const; }; -void XMLConnectNodes(const XMLNodeData& xml_node_data, uint version, MultiUndoCommand *command = nullptr); - /** * @brief Workaround for QXmlStreamReader::readNextStartElement not detecting the end of a document * @@ -68,8 +78,6 @@ void XMLConnectNodes(const XMLNodeData& xml_node_data, uint version, MultiUndoCo */ bool XMLReadNextStartElement(QXmlStreamReader* reader); -void XMLLinkBlocks(const XMLNodeData& xml_node_data); - } #endif // XMLREADLOOP_H diff --git a/app/core.cpp b/app/core.cpp index 241d57206..d0301a329 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -75,7 +75,7 @@ namespace olive { Core* Core::instance_ = nullptr; -const uint Core::kProjectVersion = 210907; +const uint Core::kProjectVersion = 211228; Core::Core(const CoreParams& params) : main_window_(nullptr), diff --git a/app/node/factory.cpp b/app/node/factory.cpp index f4368d898..447ba0dbd 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -54,6 +54,7 @@ namespace olive { QList NodeFactory::library_; +QVector NodeFactory::hidden_; void NodeFactory::Initialize() { @@ -65,6 +66,9 @@ void NodeFactory::Initialize() library_.append(created_node); } + + hidden_.append(kTextGeneratorLegacy); + hidden_.append(kGroupNode); } void NodeFactory::Destroy() @@ -86,6 +90,11 @@ Menu *NodeFactory::CreateMenu(QWidget* parent, bool create_none_item, Node::Cate continue; } + if (hidden_.contains(i)) { + // Skip this node + continue; + } + // Make sure nodes are up-to-date with the current translation n->Retranslate(); @@ -241,6 +250,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new SubtitleBlock(); case kShapeGenerator: return new ShapeNode(); + case kGroupNode: + return new NodeGroup(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index 1d8cd72f2..10101e648 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -61,6 +61,7 @@ public: kTimeRemapNode, kSubtitleBlock, kShapeGenerator, + kGroupNode, // Count value kInternalNodeCount @@ -87,6 +88,8 @@ public: private: static QList library_; + static QVector hidden_; + }; } diff --git a/app/node/group/group.cpp b/app/node/group/group.cpp index be92d9be8..e64b551d8 100644 --- a/app/node/group/group.cpp +++ b/app/node/group/group.cpp @@ -24,6 +24,8 @@ namespace olive { +#define super Node + NodeGroup::NodeGroup() : output_passthrough_(nullptr) { @@ -31,11 +33,7 @@ NodeGroup::NodeGroup() : QString NodeGroup::Name() const { - if (custom_name_.isEmpty()) { - return tr("Group"); - } else { - return custom_name_; - } + return tr("Group"); } QString NodeGroup::id() const @@ -131,15 +129,59 @@ QString NodeGroup::GetInputName(const QString &id) const return input_passthroughs_.value(id).name(); } -void NodeGroupSetCustomNameCommand::redo() +bool NodeGroup::LoadCustom(QXmlStreamReader *reader, XMLNodeData &xml_node_data, uint version, const QAtomicInt *cancelled) { - old_name_ = group_->GetCustomName(); - group_->SetCustomName(new_name_); + if (reader->name() == QStringLiteral("inputpassthroughs")) { + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("inputpassthrough")) { + XMLNodeData::GroupLink link; + + link.group = this; + + while (XMLReadNextStartElement(reader)) { + if (reader->name() == QStringLiteral("node")) { + link.input_node = reader->readElementText().toULongLong(); + } else if (reader->name() == QStringLiteral("input")) { + link.input_id = reader->readElementText(); + } else if (reader->name() == QStringLiteral("element")) { + link.input_element = reader->readElementText().toInt(); + } else { + reader->skipCurrentElement(); + } + } + + xml_node_data.group_input_links.append(link); + } else { + reader->skipCurrentElement(); + } + } + + return true; + } else if (reader->name() == QStringLiteral("outputpassthrough")) { + xml_node_data.group_output_links.insert(this, reader->readElementText().toULongLong()); + return true; + } else { + return super::LoadCustom(reader, xml_node_data, version, cancelled); + } } -void NodeGroupSetCustomNameCommand::undo() +void NodeGroup::SaveCustom(QXmlStreamWriter *writer) const { - group_->SetCustomName(old_name_); + super::SaveCustom(writer); + + writer->writeStartElement(QStringLiteral("inputpassthroughs")); + + foreach (const NodeInput &ip, input_passthroughs_) { + writer->writeStartElement(QStringLiteral("inputpassthrough")); + writer->writeTextElement(QStringLiteral("node"), QString::number(reinterpret_cast(ip.node()))); + writer->writeTextElement(QStringLiteral("input"), ip.input()); + writer->writeTextElement(QStringLiteral("element"), QString::number(ip.element())); + writer->writeEndElement(); // input + } + + writer->writeEndElement(); // inputpassthroughs + + writer->writeTextElement(QStringLiteral("outputpassthrough"), QString::number(reinterpret_cast(output_passthrough_))); } void NodeGroupAddInputPassthrough::redo() diff --git a/app/node/group/group.h b/app/node/group/group.h index d023a7416..979a52bc7 100644 --- a/app/node/group/group.h +++ b/app/node/group/group.h @@ -52,24 +52,6 @@ public: void SetOutputPassthrough(Node *node); - const QString &GetCustomName() const - { - return custom_name_; - } - - void SetCustomName(const QString &name) - { - custom_name_ = name; - - // NOTE: Not technically the right signal, but should achieve the right goal - emit LabelChanged(custom_name_); - } - - void ClearCustomName() - { - custom_name_.clear(); - } - static QString GetGroupInputIDFromInput(const NodeInput &input); const QHash &GetInputPassthroughs() const @@ -88,40 +70,16 @@ signals: void OutputPassthroughChanged(NodeGroup *group, Node *output); +protected: + virtual bool LoadCustom(QXmlStreamReader* reader, XMLNodeData& xml_node_data, uint version, const QAtomicInt* cancelled) override; + + virtual void SaveCustom(QXmlStreamWriter* writer) const override; + private: QHash input_passthroughs_; Node *output_passthrough_; - QString custom_name_; - -}; - -class NodeGroupSetCustomNameCommand : public UndoCommand -{ -public: - NodeGroupSetCustomNameCommand(NodeGroup *group, const QString &name) : - group_(group), - new_name_(name) - {} - - virtual Project * GetRelevantProject() const override - { - return group_->project(); - } - -protected: - virtual void redo() override; - - virtual void undo() override; - -private: - NodeGroup *group_; - - QString old_name_; - - QString new_name_; - }; class NodeGroupAddInputPassthrough : public UndoCommand diff --git a/app/node/nodecopypaste.cpp b/app/node/nodecopypaste.cpp index e9c0a4d0e..2a54212af 100644 --- a/app/node/nodecopypaste.cpp +++ b/app/node/nodecopypaste.cpp @@ -205,12 +205,7 @@ QVector NodeCopyPasteService::PasteNodesFromClipboard(NodeGraph *graph, } // Make connections - if (!xml_node_data.desired_connections.isEmpty()) { - XMLConnectNodes(xml_node_data, data_version, command); - } - - // Link blocks - XMLLinkBlocks(xml_node_data); + xml_node_data.PostConnect(data_version, command); // Process contexts for (auto it=pasted_contexts.cbegin(); it!=pasted_contexts.cend(); it++) { diff --git a/app/node/project/project.cpp b/app/node/project/project.cpp index ba118ba50..0ca246374 100644 --- a/app/node/project/project.cpp +++ b/app/node/project/project.cpp @@ -193,10 +193,7 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint } // Make connections - XMLConnectNodes(xml_node_data, version); - - // Link blocks - XMLLinkBlocks(xml_node_data); + xml_node_data.PostConnect(version); } void Project::Save(QXmlStreamWriter *writer) const diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp index 6e4b842e6..d6abc3a1d 100644 --- a/app/panel/node/node.cpp +++ b/app/panel/node/node.cpp @@ -31,6 +31,7 @@ NodePanel::NodePanel(QWidget *parent) : // Connect node view signals to this panel - MAY REMOVE connect(node_widget_->view(), &NodeView::NodesSelected, this, &NodePanel::NodesSelected); connect(node_widget_->view(), &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected); + connect(node_widget_->view(), &NodeView::NodeGroupOpenRequested, this, &NodePanel::NodeGroupOpenRequested); // Set it as the main widget of this panel SetWidgetWithPadding(node_widget_); diff --git a/app/panel/node/node.h b/app/panel/node/node.h index 083445029..f1dae048f 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -40,6 +40,11 @@ public: return node_widget_; } + const QVector &GetContexts() const + { + return node_widget_->view()->GetContexts(); + } + void SetContexts(const QVector &nodes) { node_widget_->SetContexts(nodes); @@ -109,6 +114,7 @@ public slots: void Select(const QVector& nodes, bool center_view_on_item) { node_widget_->view()->Select(nodes, center_view_on_item); + this->raise(); } signals: @@ -116,6 +122,8 @@ signals: void NodesDeselected(const QVector& nodes); + void NodeGroupOpenRequested(NodeGroup *group); + private: virtual void Retranslate() override { diff --git a/app/panel/param/param.h b/app/panel/param/param.h index 4389bc15f..5d7928f04 100644 --- a/app/panel/param/param.h +++ b/app/panel/param/param.h @@ -38,6 +38,16 @@ public: return static_cast(GetTimeBasedWidget()); } + void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e) + { + GetParamView()->SetCreateCheckBoxes(e); + } + + void SetIgnoreNodeFlags(bool e) + { + GetParamView()->SetIgnoreNodeFlags(e); + } + public slots: void SelectNodes(const QVector& nodes); void DeselectNodes(const QVector& nodes); diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 1447895fe..d17ff3a0f 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -93,6 +93,7 @@ MainWindow::MainWindow(QWidget *parent) : // Make node-related connections connect(node_panel_, &NodePanel::NodesSelected, param_panel_, &ParamPanel::SelectNodes); connect(node_panel_, &NodePanel::NodesDeselected, param_panel_, &ParamPanel::DeselectNodes); + connect(node_panel_, &NodePanel::NodeGroupOpenRequested, this, &MainWindow::NodeGroupRequested); connect(param_panel_, &ParamPanel::RequestSelectNode, this, [this](const QVector& target){ node_panel_->Select(target, true); }); @@ -451,6 +452,17 @@ void MainWindow::StatusBarDoubleClicked() task_man_panel_->raise(); } +void MainWindow::NodeGroupRequested(NodeGroup *group) +{ + NodePanel *panel = new NodePanel(this); + panel->setFloating(true); + panel->setVisible(true); + panel->SetContexts({group}); + panel->SetSignalInsteadOfClose(true); + addDockWidget(Qt::LeftDockWidgetArea, panel); + connect(panel, &NodePanel::CloseRequested, panel, &NodePanel::deleteLater); +} + void MainWindow::TimelinePanelSelectionChanged(const QVector &blocks) { TimelinePanel *panel = static_cast(sender()); @@ -723,7 +735,13 @@ void MainWindow::FocusedPanelChanged(PanelWidget *panel) UpdateAudioMonitorParams(tbp->GetConnectedViewer()); } - if (TimelinePanel* timeline = dynamic_cast(panel)) { + if (NodePanel *node_panel = dynamic_cast(panel)) { + // Set param view contexts to these + bool is_default_node_panel = node_panel == node_panel_; + param_panel_->SetIgnoreNodeFlags(!is_default_node_panel); + param_panel_->SetCreateCheckBoxes(is_default_node_panel ? kNoCheckBoxes : kCheckBoxesOnNonConnected); + param_panel_->SetContexts(node_panel->GetContexts()); + } else if (TimelinePanel* timeline = dynamic_cast(panel)) { // Signal timeline focus TimelineFocused(timeline->GetConnectedViewer()); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 719652200..63dc4445f 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -187,6 +187,8 @@ private slots: void StatusBarDoubleClicked(); + void NodeGroupRequested(NodeGroup *group); + #ifdef Q_OS_LINUX void ShowNouveauWarning(); #endif