From 8b87e376a028625b8a50bb3f695b148532d169e4 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 17:20:22 -0700 Subject: [PATCH] nodeparamview: implement copy/pasting values --- app/node/node.cpp | 112 ++++++++++++++---- app/node/node.h | 35 +++++- app/widget/nodeparamview/nodeparamview.cpp | 78 +++++++++++- .../nodeparamview/nodeparamviewundo.cpp | 8 +- app/widget/nodeparamview/nodeparamviewundo.h | 40 ++++++- app/widget/nodeview/nodeviewundo.cpp | 12 -- app/widget/nodeview/nodeviewundo.h | 26 +--- 7 files changed, 241 insertions(+), 70 deletions(-) diff --git a/app/node/node.cpp b/app/node/node.cpp index 2f774ded1..85cc3cfb5 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1064,7 +1064,7 @@ Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap& cre } // Copy values to the clone - command->add_child(new NodeCopyInputsCommand(node, copy, false)); + CopyInputs(node, copy, false, command); // Go through input connections and copy if non-item and connect if item for (auto it=node->input_connections_.cbegin(); it!=node->input_connections_.cend(); it++) { @@ -1110,7 +1110,7 @@ Node *Node::CopyNodeInGraph(Node *node, MultiUndoCommand *command) command->add_child(new NodeAddCommand(static_cast(node->parent()), copy)); - command->add_child(new NodeCopyInputsCommand(node, copy, true)); + CopyInputs(node, copy, true, command); const PositionMap &map = node->GetContextPositions(); for (auto it=map.cbegin(); it!=map.cend(); it++) { @@ -1359,7 +1359,7 @@ void Node::Hash(QCryptographicHash &hash, const NodeGlobals &globals, const Vide } } -void Node::CopyInputs(const Node *source, Node *destination, bool include_connections) +void Node::CopyInputs(const Node *source, Node *destination, bool include_connections, MultiUndoCommand *command) { Q_ASSERT(source->id() == destination->id()); @@ -1369,76 +1369,119 @@ void Node::CopyInputs(const Node *source, Node *destination, bool include_connec // passthroughs correctly. Q_ASSERT(destination->HasInputWithID(input)); - CopyInput(source, destination, input, include_connections, true); + CopyInput(source, destination, input, include_connections, true, command); } - destination->SetLabel(source->GetLabel()); - destination->SetOverrideColor(source->GetOverrideColor()); + if (command) { + command->add_child(new NodeRenameCommand(destination, source->GetLabel())); + } else { + destination->SetLabel(source->GetLabel()); + } + + if (command) { + command->add_child(new NodeOverrideColorCommand(destination, source->GetOverrideColor())); + } else { + destination->SetOverrideColor(source->GetOverrideColor()); + } } -void Node::CopyInput(const Node *src, Node *dst, const QString &input, bool include_connections, bool traverse_arrays) +void Node::CopyInput(const Node *src, Node *dst, const QString &input, bool include_connections, bool traverse_arrays, MultiUndoCommand *command) { Q_ASSERT(src->id() == dst->id()); - CopyValuesOfElement(src, dst, input, -1); + CopyValuesOfElement(src, dst, input, -1, command); // Copy array size if (src->InputIsArray(input) && traverse_arrays) { int src_array_sz = src->InputArraySize(input); for (int i=0; iinput_connections().cbegin(); it!=src->input_connections().cend(); it++) { - ConnectEdge(it->second, NodeInput(dst, input, it->first.element())); + // Copy all connections + for (auto it=src->input_connections().cbegin(); it!=src->input_connections().cend(); it++) { + if (!traverse_arrays && it->first.element() != -1) { + continue; } - } else { - // Just copy the primary connection (at -1) - if (src->IsInputConnected(input)) { - ConnectEdge(src->GetConnectedOutput(input), NodeInput(dst, input)); + + auto conn_output = it->second; + NodeInput conn_input(dst, input, it->first.element()); + + if (command) { + command->add_child(new NodeEdgeAddCommand(conn_output, conn_input)); + } else { + ConnectEdge(conn_output, conn_input); } } } } -void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, int src_element, int dst_element) +void Node::CopyValuesOfElement(const Node *src, Node *dst, const QString &input, int src_element, int dst_element, MultiUndoCommand *command) { if (dst_element >= dst->GetInternalInputArraySize(input)) { qDebug() << "Ignored destination element that was out of array bounds"; return; } + NodeInput dst_input(dst, input, dst_element); + // Copy standard value - dst->SetSplitStandardValue(input, src->GetSplitStandardValue(input, src_element), dst_element); + SplitValue standard = src->GetSplitStandardValue(input, src_element); + if (command) { + command->add_child(new NodeParamSetSplitStandardValueCommand(dst_input, standard)); + } else { + dst->SetSplitStandardValue(input, standard, dst_element); + } // Copy keyframes if (NodeInputImmediate *immediate = dst->GetImmediate(input, dst_element)) { - immediate->delete_all_keyframes(); + if (command) { + command->add_child(new ImmediateRemoveAllKeyframesCommand(immediate)); + } else { + immediate->delete_all_keyframes(); + } } + foreach (const NodeKeyframeTrack& track, src->GetImmediate(input, src_element)->keyframe_tracks()) { foreach (NodeKeyframe* key, track) { - key->copy(dst_element, dst); + NodeKeyframe *copy = key->copy(dst_element, command ? nullptr : dst); + if (command) { + command->add_child(new NodeParamInsertKeyframeCommand(dst, copy)); + } } } // Copy keyframing state if (src->IsInputKeyframable(input)) { - dst->SetInputIsKeyframing(input, src->IsInputKeyframing(input, src_element), dst_element); + bool is_keying = src->IsInputKeyframing(input, src_element); + if (command) { + command->add_child(new NodeParamSetKeyframingCommand(dst_input, is_keying)); + } else { + dst->SetInputIsKeyframing(input, is_keying, dst_element); + } } // If this is the root of an array, copy the array size if (src_element == -1 && dst_element == -1) { - dst->ArrayResizeInternal(input, src->InputArraySize(input)); + int array_sz = src->InputArraySize(input); + if (command) { + command->add_child(new Node::ArrayResizeCommand(dst, input, array_sz)); + } else { + dst->ArrayResizeInternal(input, array_sz); + } } // Copy value hint - dst->SetValueHintForInput(input, src->GetValueHintForInput(input, src_element), dst_element); + Node::ValueHint vh = src->GetValueHintForInput(input, src_element); + if (command) { + command->add_child(new NodeSetValueHintCommand(dst_input, vh)); + } else { + dst->SetValueHintForInput(input, vh, dst_element); + } } bool Node::CanBeDeleted() const @@ -2151,4 +2194,25 @@ void NodeSetPositionAndDependenciesRecursivelyCommand::move_recursively(Node *no } } +void Node::ImmediateRemoveAllKeyframesCommand::prepare() +{ + for (const NodeKeyframeTrack& track : immediate_->keyframe_tracks()) { + keys_.append(track); + } +} + +void Node::ImmediateRemoveAllKeyframesCommand::redo() +{ + for (auto it=keys_.cbegin(); it!=keys_.cend(); it++) { + (*it)->setParent(&memory_manager_); + } +} + +void Node::ImmediateRemoveAllKeyframesCommand::undo() +{ + for (auto it=keys_.crbegin(); it!=keys_.crend(); it++) { + (*it)->setParent(&memory_manager_); + } +} + } diff --git a/app/node/node.h b/app/node/node.h index b7f60b838..74ef7a484 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -834,14 +834,14 @@ public: * * Nodes must be of the same types (i.e. have the same ID) */ - static void CopyInputs(const Node *source, Node* destination, bool include_connections = true); + static void CopyInputs(const Node *source, Node* destination, bool include_connections = true, MultiUndoCommand *command = nullptr); - static void CopyInput(const Node *src, Node* dst, const QString& input, bool include_connections, bool traverse_arrays); + static void CopyInput(const Node *src, Node* dst, const QString& input, bool include_connections, bool traverse_arrays, MultiUndoCommand *command); - static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int src_element, int dst_element); - static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int element) + static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int src_element, int dst_element, MultiUndoCommand *command = nullptr); + static void CopyValuesOfElement(const Node* src, Node* dst, const QString& input, int element, MultiUndoCommand *command = nullptr) { - return CopyValuesOfElement(src, dst, input, element, element); + return CopyValuesOfElement(src, dst, input, element, element, command); } /** @@ -1265,6 +1265,31 @@ private: int array_size; }; + class ImmediateRemoveAllKeyframesCommand : public UndoCommand + { + public: + ImmediateRemoveAllKeyframesCommand(NodeInputImmediate *immediate) : + immediate_(immediate) + {} + + virtual Project* GetRelevantProject() const override { return nullptr; } + + protected: + virtual void prepare() override; + + virtual void redo() override; + + virtual void undo() override; + + private: + NodeInputImmediate *immediate_; + + QObject memory_manager_; + + QVector keys_; + + }; + NodeInputImmediate* CreateImmediate(const QString& input); NodeInputImmediate* GetImmediate(const QString& input, int element) const; diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index f56cdddd5..c902503c6 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -21,6 +21,7 @@ #include "nodeparamview.h" #include +#include #include #include #include @@ -594,9 +595,82 @@ bool NodeParamView::Paste() } } - // FIXME: Pasting nodes + ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("nodes")); + if (res.GetLoadedNodes().isEmpty()) { + return false; + } - return false; + // Determine if any nodes of this type are already in the editor + QMap existing_nodes; + for (Node *n : res.GetLoadedNodes()) { + if (Node *existing = GetNodeWithID(n->id())) { + if (!existing_nodes.contains(existing)) { + existing_nodes.insert(existing, n); + } + } + } + + QVector nodes_to_paste_as_new = res.GetLoadedNodes(); + MultiUndoCommand *command = new MultiUndoCommand(); + + if (!existing_nodes.empty()) { + QMessageBox b(this); + b.setWindowTitle(tr("Paste Nodes")); + + QStringList node_names; + for (auto it=existing_nodes.cbegin(); it!=existing_nodes.cend(); it++) { + node_names.append(it.key()->GetLabelAndName()); + } + + b.setText(tr("The following node types already exist in this context:\n\n" + "%1\n\n" + "Do you wish to paste values onto the existing nodes or paste new nodes?").arg(node_names.join('\n'))); + + auto as_vals = b.addButton(tr("Paste As Values"), QMessageBox::YesRole); + auto as_nodes = b.addButton(tr("Paste As Nodes"), QMessageBox::NoRole); + auto cancel_btn = b.addButton(QMessageBox::Cancel); + + Q_UNUSED(as_nodes) + + b.exec(); + + if (b.clickedButton() == cancel_btn) { + + // Delete pasted nodes and clear array so no later code runs + qDeleteAll(nodes_to_paste_as_new); + nodes_to_paste_as_new.clear(); + + } else if (b.clickedButton() == as_vals) { + + // Filter out existing nodes + for (auto it=existing_nodes.cbegin(); it!=existing_nodes.cend(); it++) { + Node::CopyInputs(it.value(), it.key(), false, command); + nodes_to_paste_as_new.removeOne(it.value()); + } + + } + } + + if (!nodes_to_paste_as_new.isEmpty()) { + Node::PositionMap map; + + for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { + if (nodes_to_paste_as_new.contains(it.key())) { + Node::Position pos; + + const QMap &node_props = it.value(); + pos.position.setX(node_props.value(QStringLiteral("x")).toDouble()); + pos.position.setY(node_props.value(QStringLiteral("y")).toDouble()); + pos.expanded = node_props.value(QStringLiteral("expanded")).toDouble(); + + map.insert(it.key(), pos); + } + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); + + return true; } void NodeParamView::UpdateItemTime(const rational &time) diff --git a/app/widget/nodeparamview/nodeparamviewundo.cpp b/app/widget/nodeparamview/nodeparamviewundo.cpp index 427c1eacb..a3e448fda 100644 --- a/app/widget/nodeparamview/nodeparamviewundo.cpp +++ b/app/widget/nodeparamview/nodeparamviewundo.cpp @@ -27,9 +27,8 @@ namespace olive { NodeParamSetKeyframingCommand::NodeParamSetKeyframingCommand(const NodeInput &input, bool setting) : input_(input), - setting_(setting) + new_setting_(setting) { - Q_ASSERT(setting != input_.IsKeyframing()); } Project *NodeParamSetKeyframingCommand::GetRelevantProject() const @@ -39,12 +38,13 @@ Project *NodeParamSetKeyframingCommand::GetRelevantProject() const void NodeParamSetKeyframingCommand::redo() { - input_.node()->SetInputIsKeyframing(input_, setting_); + old_setting_ = input_.IsKeyframing(); + input_.node()->SetInputIsKeyframing(input_, new_setting_); } void NodeParamSetKeyframingCommand::undo() { - input_.node()->SetInputIsKeyframing(input_, !setting_); + input_.node()->SetInputIsKeyframing(input_, old_setting_); } NodeParamSetKeyframeValueCommand::NodeParamSetKeyframeValueCommand(NodeKeyframe* key, const QVariant& value) : diff --git a/app/widget/nodeparamview/nodeparamviewundo.h b/app/widget/nodeparamview/nodeparamviewundo.h index 80a6e4e41..90cdce452 100644 --- a/app/widget/nodeparamview/nodeparamviewundo.h +++ b/app/widget/nodeparamview/nodeparamviewundo.h @@ -41,7 +41,8 @@ protected: private: NodeInput input_; - bool setting_; + bool new_setting_; + bool old_setting_; }; @@ -145,6 +146,43 @@ private: }; +class NodeParamSetSplitStandardValueCommand : public UndoCommand +{ +public: + NodeParamSetSplitStandardValueCommand(const NodeInput& input, const SplitValue& new_value, const SplitValue& old_value) : + ref_(input), + old_value_(old_value), + new_value_(new_value) + {} + + NodeParamSetSplitStandardValueCommand(const NodeInput& input, const SplitValue& value) : + NodeParamSetSplitStandardValueCommand(input, value, input.node()->GetSplitStandardValue(input.input())) + {} + + virtual Project* GetRelevantProject() const override + { + return ref_.node()->project(); + } + +protected: + virtual void redo() override + { + ref_.node()->SetSplitStandardValue(ref_.input(), new_value_, ref_.element()); + } + + virtual void undo() override + { + ref_.node()->SetSplitStandardValue(ref_.input(), old_value_, ref_.element()); + } + +private: + NodeInput ref_; + + SplitValue old_value_; + SplitValue new_value_; + +}; + class NodeParamArrayAppendCommand : public UndoCommand { public: diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index fc9d9073f..03ff5a372 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -112,18 +112,6 @@ Project *NodeAddCommand::GetRelevantProject() const return dynamic_cast(graph_); } -NodeCopyInputsCommand::NodeCopyInputsCommand(const Node *src, Node *dest, bool include_connections) : - src_(src), - dest_(dest), - include_connections_(include_connections) -{ -} - -void NodeCopyInputsCommand::redo() -{ - Node::CopyInputs(src_, dest_, include_connections_); -} - void NodeRemoveAndDisconnectCommand::prepare() { command_ = new MultiUndoCommand(); diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 3c2298cd5..f5508abb2 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -193,28 +193,6 @@ private: }; -class NodeCopyInputsCommand : public UndoCommand { -public: - NodeCopyInputsCommand(const Node* src, - Node* dest, - bool include_connections); - - virtual Project* GetRelevantProject() const override {return nullptr;} - -protected: - virtual void redo() override; - - virtual void undo() override {} - -private: - const Node* src_; - - Node* dest_; - - bool include_connections_; - -}; - class NodeLinkCommand : public UndoCommand { public: NodeLinkCommand(Node* a, Node* b, bool link) : @@ -324,6 +302,10 @@ class NodeRenameCommand : public UndoCommand { public: NodeRenameCommand() = default; + NodeRenameCommand(Node* node, const QString& new_name) + { + AddNode(node, new_name); + } void AddNode(Node* node, const QString& new_name);