From 182e1599af0a074e6ed7cc07d456a05841e90734 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Tue, 10 May 2022 15:42:09 -0700 Subject: [PATCH] keyframes: implemented copying/pasting --- app/node/keyframe.cpp | 5 + app/node/keyframe.h | 20 +- app/node/project/serializer/serializer.h | 10 +- .../project/serializer/serializer220403.cpp | 256 ++++++++++++++---- .../project/serializer/serializer220403.h | 4 + app/panel/timebased/timebased.cpp | 15 + app/panel/timebased/timebased.h | 6 + app/panel/timeline/timeline.cpp | 17 +- app/panel/timeline/timeline.h | 6 - app/widget/curvewidget/curveview.h | 5 + app/widget/curvewidget/curvewidget.cpp | 30 ++ app/widget/curvewidget/curvewidget.h | 6 + app/widget/keyframeview/keyframeview.cpp | 63 +++++ app/widget/keyframeview/keyframeview.h | 6 + .../keyframeviewinputconnection.h | 5 + app/widget/nodeparamview/nodeparamview.cpp | 86 +++++- app/widget/nodeparamview/nodeparamview.h | 6 + .../nodeparamview/nodeparamviewitembase.h | 2 + app/widget/timebased/timebasedwidget.cpp | 18 ++ app/widget/timebased/timebasedwidget.h | 4 + app/widget/timelinewidget/timelinewidget.cpp | 122 +++++---- app/widget/timelinewidget/timelinewidget.h | 8 +- app/widget/timeruler/seekablewidget.cpp | 25 +- app/widget/timeruler/seekablewidget.h | 2 +- 24 files changed, 567 insertions(+), 160 deletions(-) diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index 968876021..eb6d5dea1 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -41,6 +41,11 @@ NodeKeyframe::NodeKeyframe(const rational &time, const QVariant &value, Type typ setParent(parent); } +NodeKeyframe::NodeKeyframe() +{ + type_ = NodeKeyframe::kLinear; +} + NodeKeyframe::~NodeKeyframe() { setParent(nullptr); diff --git a/app/node/keyframe.h b/app/node/keyframe.h index 053b07298..a002aa8fc 100644 --- a/app/node/keyframe.h +++ b/app/node/keyframe.h @@ -64,6 +64,7 @@ public: * @brief NodeKeyframe Constructor */ NodeKeyframe(const rational& time, const QVariant& value, Type type, int track, int element, const QString& input, QObject* parent = nullptr); + NodeKeyframe(); virtual ~NodeKeyframe() override; @@ -71,10 +72,9 @@ public: NodeKeyframe* copy(QObject* parent = nullptr) const; Node* parent() const; - const QString& input() const - { - return input_; - } + + const QString& input() const { return input_; } + void set_input(const QString& input) { input_ = input; } NodeKeyframeTrackReference key_track_ref() const { @@ -141,15 +141,11 @@ public: * For the majority of keyfreames, this will be 0, but for some types, such as kVec2, this will be 0 for X keyframes * and 1 for Y keyframes, etc. */ - int track() const - { - return track_; - } + int track() const { return track_; } + void set_track(int t) { track_ = t; } - int element() const - { - return element_; - } + int element() const { return element_; } + void set_element(int e) { element_ = e; } /** * @brief Convenience function for getting the opposite handle type (e.g. kInHandle <-> kOutHandle) diff --git a/app/node/project/serializer/serializer.h b/app/node/project/serializer/serializer.h index 1586891ef..831c98d44 100644 --- a/app/node/project/serializer/serializer.h +++ b/app/node/project/serializer/serializer.h @@ -55,6 +55,7 @@ public: }; using SerializedProperties = QHash >; + using SerializedKeyframes = QHash >; class LoadData { @@ -65,6 +66,8 @@ public: std::vector markers; + SerializedKeyframes keyframes; + }; class Result @@ -105,7 +108,7 @@ public: class SaveData { public: - SaveData(Project *project, const QString &filename = QString()) + SaveData(Project *project = nullptr, const QString &filename = QString()) { project_ = project; filename_ = filename; @@ -128,6 +131,9 @@ public: const std::vector &GetOnlySerializeMarkers() const { return only_serialize_markers_; } void SetOnlySerializeMarkers(const std::vector &only) { only_serialize_markers_ = only; } + const std::vector &GetOnlySerializeKeyframes() const { return only_serialize_keyframes_; } + void SetOnlySerializeKeyframes(const std::vector &only) { only_serialize_keyframes_ = only; } + const SerializedProperties &GetProperties() const { return properties_; } void SetProperties(const SerializedProperties &p) { properties_ = p; } @@ -142,6 +148,8 @@ public: std::vector only_serialize_markers_; + std::vector only_serialize_keyframes_; + }; static void Initialize(); diff --git a/app/node/project/serializer/serializer220403.cpp b/app/node/project/serializer/serializer220403.cpp index 6f161a07c..ae78bdf5c 100644 --- a/app/node/project/serializer/serializer220403.cpp +++ b/app/node/project/serializer/serializer220403.cpp @@ -99,6 +99,102 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project } } + } 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)) { @@ -221,8 +317,6 @@ ProjectSerializer220403::LoadData ProjectSerializer220403::Load(Project *project void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &data, void *reserved) const { - Project *project = data.GetProject(); - if (!data.GetOnlySerializeMarkers().empty()) { writer->writeStartElement(QStringLiteral("markers")); @@ -239,9 +333,63 @@ void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &dat writer->writeEndElement(); // markers - } else { + } else if (!data.GetOnlySerializeKeyframes().empty()) { - writer->writeTextElement(QStringLiteral("uuid"), data.GetProject()->GetUuid().toString()); + writer->writeStartElement(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++) { + writer->writeStartElement(QStringLiteral("node")); + + writer->writeAttribute(QStringLiteral("id"), it.key()); + + for (auto jt=it.value().cbegin(); jt!=it.value().cend(); jt++) { + writer->writeStartElement(QStringLiteral("input")); + + writer->writeAttribute(QStringLiteral("id"), jt.key()); + + for (auto kt=jt.value().cbegin(); kt!=jt.value().cend(); kt++) { + writer->writeStartElement(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(); + + writer->writeStartElement(QStringLiteral("track")); + + writer->writeAttribute(QStringLiteral("id"), QString::number(lt.key())); + + for (NodeKeyframe *key : keys) { + writer->writeStartElement(QStringLiteral("key")); + SaveKeyframe(writer, key, key->parent()->GetInputDataType(key->input())); + writer->writeEndElement(); // key + } + + writer->writeEndElement(); // track + } + + writer->writeEndElement(); // element + } + + writer->writeEndElement(); // input + } + + writer->writeEndElement(); // node; + } + + writer->writeEndElement(); // keyframes + + } else if (Project *project = data.GetProject()) { + + writer->writeTextElement(QStringLiteral("uuid"), project->GetUuid().toString()); writer->writeStartElement(QStringLiteral("nodes")); @@ -310,6 +458,10 @@ void ProjectSerializer220403::Save(QXmlStreamWriter *writer, const SaveData &dat // Save main window project layout project->GetLayoutInfo().toXml(writer); + } else { + + qCritical() << "Cannot save nodes without project object"; + } } @@ -601,40 +753,13 @@ void ProjectSerializer220403::LoadImmediate(QXmlStreamReader *reader, Node *node } if (reader->name() == QStringLiteral("key")) { - QString key_input; - rational key_time; - NodeKeyframe::Type key_type = NodeKeyframe::kLinear; - QVariant key_value; - QPointF key_in_handle; - QPointF key_out_handle; + NodeKeyframe* key = new NodeKeyframe(); + key->set_input(input); + key->set_element(element); + key->set_track(track); - XMLAttributeLoop(reader, attr) { - if (IsCancelled()) { - return; - } - - if (attr.name() == QStringLiteral("input")) { - key_input = attr.value().toString(); - } else if (attr.name() == QStringLiteral("time")) { - key_time = rational::fromString(attr.value().toString()); - } else if (attr.name() == QStringLiteral("type")) { - key_type = static_cast(attr.value().toInt()); - } else if (attr.name() == QStringLiteral("inhandlex")) { - key_in_handle.setX(attr.value().toDouble()); - } else if (attr.name() == QStringLiteral("inhandley")) { - key_in_handle.setY(attr.value().toDouble()); - } else if (attr.name() == QStringLiteral("outhandlex")) { - key_out_handle.setX(attr.value().toDouble()); - } else if (attr.name() == QStringLiteral("outhandley")) { - key_out_handle.setY(attr.value().toDouble()); - } - } - - key_value = NodeValue::StringToValue(data_type, reader->readElementText(), true); - - NodeKeyframe* key = new NodeKeyframe(key_time, key_value, key_type, track, element, key_input, node); - key->set_bezier_control_in(key_in_handle); - key->set_bezier_control_out(key_out_handle); + LoadKeyframe(reader, key, data_type); + key->setParent(node); } else { reader->skipCurrentElement(); } @@ -695,15 +820,7 @@ void ProjectSerializer220403::SaveImmediate(QXmlStreamWriter *writer, Node *node for (NodeKeyframe* key : track) { writer->writeStartElement(QStringLiteral("key")); - writer->writeAttribute(QStringLiteral("input"), key->input()); - writer->writeAttribute(QStringLiteral("time"), key->time().toString()); - writer->writeAttribute(QStringLiteral("type"), QString::number(key->type())); - writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(key->bezier_control_in().x())); - writer->writeAttribute(QStringLiteral("inhandley"), QString::number(key->bezier_control_in().y())); - writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(key->bezier_control_out().x())); - writer->writeAttribute(QStringLiteral("outhandley"), QString::number(key->bezier_control_out().y())); - - writer->writeCharacters(NodeValue::ValueToString(data_type, key->value(), true)); + SaveKeyframe(writer, key, data_type); writer->writeEndElement(); // key } @@ -722,6 +839,53 @@ void ProjectSerializer220403::SaveImmediate(QXmlStreamWriter *writer, Node *node } } +void ProjectSerializer220403::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())); + } else if (attr.name() == QStringLiteral("type")) { + key->set_type(static_cast(attr.value().toInt())); + } else if (attr.name() == QStringLiteral("inhandlex")) { + key_in_handle.setX(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("inhandley")) { + key_in_handle.setY(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("outhandlex")) { + key_out_handle.setX(attr.value().toDouble()); + } else if (attr.name() == QStringLiteral("outhandley")) { + key_out_handle.setY(attr.value().toDouble()); + } + } + + key->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 ProjectSerializer220403::SaveKeyframe(QXmlStreamWriter *writer, NodeKeyframe *key, NodeValue::Type data_type) const +{ + writer->writeAttribute(QStringLiteral("input"), key->input()); + writer->writeAttribute(QStringLiteral("time"), key->time().toString()); + writer->writeAttribute(QStringLiteral("type"), QString::number(key->type())); + writer->writeAttribute(QStringLiteral("inhandlex"), QString::number(key->bezier_control_in().x())); + writer->writeAttribute(QStringLiteral("inhandley"), QString::number(key->bezier_control_in().y())); + writer->writeAttribute(QStringLiteral("outhandlex"), QString::number(key->bezier_control_out().x())); + writer->writeAttribute(QStringLiteral("outhandley"), QString::number(key->bezier_control_out().y())); + + writer->writeCharacters(NodeValue::ValueToString(data_type, key->value(), true)); +} + bool ProjectSerializer220403::LoadPosition(QXmlStreamReader *reader, quintptr *node_ptr, Node::Position *pos) const { bool got_node_ptr = false; diff --git a/app/node/project/serializer/serializer220403.h b/app/node/project/serializer/serializer220403.h index 959a251d3..4612b2fce 100644 --- a/app/node/project/serializer/serializer220403.h +++ b/app/node/project/serializer/serializer220403.h @@ -86,6 +86,10 @@ private: 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; diff --git a/app/panel/timebased/timebased.cpp b/app/panel/timebased/timebased.cpp index f1da8ea99..b10220dab 100644 --- a/app/panel/timebased/timebased.cpp +++ b/app/panel/timebased/timebased.cpp @@ -216,4 +216,19 @@ void TimeBasedPanel::DeleteSelected() GetTimeBasedWidget()->DeleteSelected(); } +void TimeBasedPanel::CutSelected() +{ + GetTimeBasedWidget()->CopySelected(true); +} + +void TimeBasedPanel::CopySelected() +{ + GetTimeBasedWidget()->CopySelected(false); +} + +void TimeBasedPanel::Paste() +{ + GetTimeBasedWidget()->Paste(); +} + } diff --git a/app/panel/timebased/timebased.h b/app/panel/timebased/timebased.h index 713ce85c7..dff33ccca 100644 --- a/app/panel/timebased/timebased.h +++ b/app/panel/timebased/timebased.h @@ -100,6 +100,12 @@ public: virtual void DeleteSelected() override; + virtual void CutSelected() override; + + virtual void CopySelected() override; + + virtual void Paste() override; + public slots: void SetTimebase(const rational& timebase); diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index 8cccbd416..f08f7b998 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -108,24 +108,9 @@ void TimelinePanel::ToggleLinks() timeline_widget()->ToggleLinksOnSelected(); } -void TimelinePanel::CutSelected() -{ - timeline_widget()->CopySelected(true); -} - -void TimelinePanel::CopySelected() -{ - timeline_widget()->CopySelected(false); -} - -void TimelinePanel::Paste() -{ - timeline_widget()->Paste(false); -} - void TimelinePanel::PasteInsert() { - timeline_widget()->Paste(true); + timeline_widget()->PasteInsert(); } void TimelinePanel::DeleteInToOut() diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 06f4dc114..90de35332 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -68,12 +68,6 @@ public: virtual void ToggleLinks() override; - virtual void CutSelected() override; - - virtual void CopySelected() override; - - virtual void Paste() override; - virtual void PasteInsert() override; virtual void DeleteInToOut() override; diff --git a/app/widget/curvewidget/curveview.h b/app/widget/curvewidget/curveview.h index 44ba36a95..a01598548 100644 --- a/app/widget/curvewidget/curveview.h +++ b/app/widget/curvewidget/curveview.h @@ -41,6 +41,11 @@ public: void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color); + const QHash &GetConnections() const + { + return track_connections_; + } + public slots: void ZoomToFit(); diff --git a/app/widget/curvewidget/curvewidget.cpp b/app/widget/curvewidget/curvewidget.cpp index 25d333d90..d58bde7cd 100644 --- a/app/widget/curvewidget/curvewidget.cpp +++ b/app/widget/curvewidget/curvewidget.cpp @@ -129,6 +129,36 @@ void CurveWidget::DeleteSelected() view_->DeleteSelected(); } +Node *CurveWidget::GetSelectedNodeWithID(const QString &id) +{ + for (auto it=view_->GetConnections().cbegin(); it!=view_->GetConnections().cend(); it++) { + Node *n = it.key().input().node(); + if (n->id() == id) { + return n; + } + } + + return nullptr; +} + +bool CurveWidget::CopySelected(bool cut) +{ + if (super::CopySelected(cut)) { + return true; + } + + return view_->CopySelected(cut); +} + +bool CurveWidget::Paste() +{ + if (super::Paste()) { + return true; + } + + return view_->Paste(std::bind(&CurveWidget::GetSelectedNodeWithID, this, std::placeholders::_1)); +} + void CurveWidget::SetNodes(const QVector &nodes) { tree_view_->SetNodes(nodes); diff --git a/app/widget/curvewidget/curvewidget.h b/app/widget/curvewidget/curvewidget.h index fd2e7af25..5fae1b4df 100644 --- a/app/widget/curvewidget/curvewidget.h +++ b/app/widget/curvewidget/curvewidget.h @@ -55,6 +55,12 @@ public: view_->DeselectAll(); } + Node *GetSelectedNodeWithID(const QString &id); + + virtual bool CopySelected(bool cut) override; + + virtual bool Paste() override; + public slots: void SetNodes(const QVector &nodes); diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index 2c944f5fc..fba3b6299 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -28,6 +28,7 @@ #include "dialog/keyframeproperties/keyframeproperties.h" #include "keyframeviewundo.h" #include "node/node.h" +#include "node/project/serializer/serializer.h" #include "widget/menu/menu.h" #include "widget/menu/menushared.h" #include "widget/nodeparamview/nodeparamviewundo.h" @@ -181,6 +182,68 @@ void KeyframeView::SelectionManagerDeselectEvent(void *obj) emit SelectionChanged(); } +bool KeyframeView::CopySelected(bool cut) +{ + if (!selection_manager_.GetSelectedObjects().empty()) { + ProjectSerializer::SaveData sdata; + sdata.SetOnlySerializeKeyframes(selection_manager_.GetSelectedObjects()); + + ProjectSerializer::Copy(sdata, QStringLiteral("keyframes")); + + if (cut) { + DeleteSelected(); + } + + return true; + } + + return false; +} + +bool KeyframeView::Paste(std::function find_node_function) +{ + ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("keyframes")); + if (res == ProjectSerializer::kSuccess) { + const ProjectSerializer::SerializedKeyframes &keys = res.GetLoadData().keyframes; + + MultiUndoCommand *command = new MultiUndoCommand(); + + rational min = RATIONAL_MAX; + for (auto it=keys.cbegin(); it!=keys.cend(); it++) { + for (NodeKeyframe *key : it.value()) { + min = std::min(min, key->time()); + } + } + min -= GetTime(); + + for (auto it=keys.cbegin(); it!=keys.cend(); it++) { + const QString &paste_id = it.key(); + + // Find a node with this ID + Node *node_with_id = find_node_function(paste_id); + + if (node_with_id) { + for (NodeKeyframe *key : it.value()) { + key->set_time(key->time() - min); + + if (NodeKeyframe *existing = node_with_id->GetKeyframeAtTimeOnTrack(key->input(), key->time(), key->track(), key->element())) { + command->add_child(new NodeParamRemoveKeyframeCommand(existing)); + } + + command->add_child(new NodeParamInsertKeyframeCommand(node_with_id, key)); + } + } else { + qDeleteAll(it.value()); + } + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); + return true; + } + + return false; +} + void KeyframeView::mousePressEvent(QMouseEvent *event) { NodeKeyframe *key_under_cursor = selection_manager_.GetObjectAtPoint(event->pos()); diff --git a/app/widget/keyframeview/keyframeview.h b/app/widget/keyframeview/keyframeview.h index 6388d5e44..2558f8cc7 100644 --- a/app/widget/keyframeview/keyframeview.h +++ b/app/widget/keyframeview/keyframeview.h @@ -21,6 +21,8 @@ #ifndef KEYFRAMEVIEWBASE_H #define KEYFRAMEVIEWBASE_H +#include + #include "keyframeviewinputconnection.h" #include "node/keyframe.h" #include "widget/menu/menu.h" @@ -77,6 +79,10 @@ public: UpdateSceneRect(); } + bool CopySelected(bool cut); + + bool Paste(std::function find_node_function); + signals: void Dragged(int current_x, int current_y); diff --git a/app/widget/keyframeview/keyframeviewinputconnection.h b/app/widget/keyframeview/keyframeviewinputconnection.h index 4c9d0be26..766879b93 100644 --- a/app/widget/keyframeview/keyframeviewinputconnection.h +++ b/app/widget/keyframeview/keyframeviewinputconnection.h @@ -60,6 +60,11 @@ public: return brush_; } + const NodeKeyframeTrackReference &GetReference() const + { + return input_; + } + void SetBrush(const QBrush &brush); signals: diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 051f21b55..f56cdddd5 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -28,6 +28,8 @@ #include "common/functiontimer.h" #include "common/timecodefunctions.h" #include "node/output/viewer/viewer.h" +#include "node/project/serializer/serializer.h" +#include "widget/nodeparamview/nodeparamviewundo.h" #include "widget/nodeview/nodeviewundo.h" #include "widget/timeruler/timeruler.h" @@ -519,6 +521,84 @@ void NodeParamView::SetSelectedNodes(const QVector &nodes, bo } } +Node *NodeParamView::GetNodeWithID(const QString &id) +{ + for (NodeParamViewItem *item : selected_nodes_) { + if (item->GetNode()->id() == id) { + return item->GetNode(); + } + } + + for (NodeParamViewContext *ctx : context_items_) { + for (NodeParamViewItem *item : ctx->GetItems()) { + if (item->GetNode()->id() == id) { + return item->GetNode(); + } + } + } + + return nullptr; +} + +bool NodeParamView::CopySelected(bool cut) +{ + if (super::CopySelected(cut)) { + return true; + } + + if (keyframe_view_ && keyframe_view_->hasFocus()) { + if (keyframe_view_->CopySelected(cut)) { + return true; + } + } + + if (contexts_.empty()) { + return false; + } + + ProjectSerializer::SaveData sdata(contexts_.first()->project()); + ProjectSerializer::SerializedProperties properties; + QVector nodes; + + for (NodeParamViewItem *item : selected_nodes_) { + Node *n = item->GetNode(); + + if (!nodes.contains(n)) { + nodes.append(n); + + Node::Position pos = item->GetContext()->GetNodePositionDataInContext(n); + + properties[n][QStringLiteral("x")] = QString::number(pos.position.x()); + properties[n][QStringLiteral("y")] = QString::number(pos.position.y()); + properties[n][QStringLiteral("expanded")] = QString::number(pos.expanded); + } + } + + sdata.SetOnlySerializeNodesAndResolveGroups(nodes); + sdata.SetProperties(properties); + + ProjectSerializer::Copy(sdata, QStringLiteral("nodes")); + + if (cut) { + DeleteSelected(); + } + + return false; +} + +bool NodeParamView::Paste() +{ + if (keyframe_view_) { + if (keyframe_view_->Paste(std::bind(&NodeParamView::GetNodeWithID, this, std::placeholders::_1))) { + return true; + } + } + + // FIXME: Pasting nodes + + return false; +} + void NodeParamView::UpdateItemTime(const rational &time) { foreach (NodeParamViewContext* item, context_items_) { @@ -764,14 +844,14 @@ void NodeParamView::KeyframeViewDragged(int x, int y) void NodeParamView::UpdateElementY() { - foreach (NodeParamViewContext *ctx, context_items_) { + for (NodeParamViewContext *ctx : context_items_) { for (auto it=ctx->GetItems().cbegin(); it!=ctx->GetItems().cend(); it++) { NodeParamViewItem *item = *it; Node *node = item->GetNode(); const KeyframeView::NodeConnections &connections = item->GetKeyframeConnections(); if (!connections.isEmpty()) { - foreach (const QString& input, node->inputs()) { + for (const QString& input : node->inputs()) { if (!(node->GetInputFlags(input) & kInputFlagHidden)) { int arr_sz = NodeGroup::ResolveInput(NodeInput(node, input)).GetArraySize(); @@ -787,7 +867,7 @@ void NodeParamView::UpdateElementY() int use_index = i + 1; if (use_index < input_con.size()) { const KeyframeView::ElementConnections &ele_con = input_con.at(ic.element()+1); - foreach (KeyframeViewInputConnection *track, ele_con) { + for (KeyframeViewInputConnection *track : ele_con) { track->SetKeyframeY(y); } } diff --git a/app/widget/nodeparamview/nodeparamview.h b/app/widget/nodeparamview/nodeparamview.h index 8acb8f3a6..26bde1fc2 100644 --- a/app/widget/nodeparamview/nodeparamview.h +++ b/app/widget/nodeparamview/nodeparamview.h @@ -64,11 +64,17 @@ public: void SetSelectedNodes(const QVector &nodes, bool handle_focused_node = true, bool emit_signal = true); void SetSelectedNodes(const QVector &nodes, bool emit_signal = true); + Node *GetNodeWithID(const QString &id); + const QVector &GetContexts() const { return contexts_; } + virtual bool CopySelected(bool cut) override; + + virtual bool Paste() override; + public slots: void SetContexts(const QVector &contexts); diff --git a/app/widget/nodeparamview/nodeparamviewitembase.h b/app/widget/nodeparamview/nodeparamviewitembase.h index 574c851c9..ff31fa104 100644 --- a/app/widget/nodeparamview/nodeparamviewitembase.h +++ b/app/widget/nodeparamview/nodeparamviewitembase.h @@ -41,6 +41,8 @@ public: update(); } + bool IsHighlighted() const { return highlighted_; } + bool IsExpanded() const; static QString GetTitleBarTextFromNode(Node *n); diff --git a/app/widget/timebased/timebasedwidget.cpp b/app/widget/timebased/timebasedwidget.cpp index f0a78ddbf..c5d079ede 100644 --- a/app/widget/timebased/timebasedwidget.cpp +++ b/app/widget/timebased/timebasedwidget.cpp @@ -860,4 +860,22 @@ void TimeBasedWidget::HideSnaps() } } +bool TimeBasedWidget::CopySelected(bool cut) +{ + if (ruler()->hasFocus() && ruler()->CopySelected(cut)) { + return true; + } + + return false; +} + +bool TimeBasedWidget::Paste() +{ + if (ruler()->hasFocus() && ruler()->PasteMarkers()) { + return true; + } + + return false; +} + } diff --git a/app/widget/timebased/timebasedwidget.h b/app/widget/timebased/timebasedwidget.h index a0ed7a91b..82924160f 100644 --- a/app/widget/timebased/timebasedwidget.h +++ b/app/widget/timebased/timebasedwidget.h @@ -73,6 +73,10 @@ public: void ShowSnaps(const std::vector ×); void HideSnaps(); + virtual bool CopySelected(bool cut); + + virtual bool Paste(); + public slots: void SetTime(const rational &time); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index ff0bc0c9b..193649988 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -568,18 +568,14 @@ void TimelineWidget::ToggleLinksOnSelected() Core::instance()->undo_stack()->push(new NodeLinkManyCommand(blocks, link)); } -void TimelineWidget::CopySelected(bool cut) +bool TimelineWidget::CopySelected(bool cut) { - if (!GetConnectedNode()) { - return; + if (super::CopySelected(cut)) { + return true; } - if (ruler()->hasFocus() && ruler()->CopySelected(cut)) { - return; - } - - if (selected_blocks_.isEmpty()) { - return; + if (!GetConnectedNode() || selected_blocks_.isEmpty()) { + return false; } QVector selected_nodes; @@ -619,58 +615,24 @@ void TimelineWidget::CopySelected(bool cut) if (cut) { DeleteSelected(); } + + return true; } -void TimelineWidget::Paste(bool insert) +bool TimelineWidget::Paste() { - if (!GetConnectedNode()) { - return; + if (super::Paste()) { + return true; + } if (!GetConnectedNode()) { + return false; } - if (ruler()->hasFocus() && ruler()->PasteMarkers(insert, GetTime())) { - return; - } + return PasteInternal(false); +} - ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("timeline")); - if (res.GetLoadedNodes().isEmpty()) { - return; - } - - MultiUndoCommand *command = new MultiUndoCommand(); - - foreach (Node *n, res.GetLoadedNodes()) { - command->add_child(new NodeAddCommand(GetConnectedNode()->project(), n)); - } - - rational paste_start = GetTime(); - - if (insert) { - rational paste_end = GetTime(); - - for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { - rational length = static_cast(it.key())->length(); - rational in = rational::fromString(it.value()[QStringLiteral("in")]); - - paste_end = qMax(paste_end, paste_start + in + length); - } - - if (paste_end != paste_start) { - InsertGapsAt(paste_start, paste_end - paste_start, command); - } - } - - for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { - Block *block = static_cast(it.key()); - rational in = rational::fromString(it.value()[QStringLiteral("in")]); - Track::Reference track = Track::Reference::FromString(it.value()[QStringLiteral("track")]); - - command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), - track.index(), - block, - paste_start + in)); - } - - Core::instance()->undo_stack()->pushIfHasChildren(command); +void TimelineWidget::PasteInsert() +{ + PasteInternal(true); } void TimelineWidget::DeleteInToOut(bool ripple) @@ -1629,6 +1591,56 @@ QVector TimelineWidget::GetBlocksInGlobalRect(const QPoint &p1, const Q return blocks_in_rect; } +bool TimelineWidget::PasteInternal(bool insert) +{ + if (!GetConnectedNode()) { + return false; + } + + ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("timeline")); + if (res.GetLoadedNodes().isEmpty()) { + return false; + } + + MultiUndoCommand *command = new MultiUndoCommand(); + + foreach (Node *n, res.GetLoadedNodes()) { + command->add_child(new NodeAddCommand(GetConnectedNode()->project(), n)); + } + + rational paste_start = GetTime(); + + if (insert) { + rational paste_end = GetTime(); + + for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { + rational length = static_cast(it.key())->length(); + rational in = rational::fromString(it.value()[QStringLiteral("in")]); + + paste_end = qMax(paste_end, paste_start + in + length); + } + + if (paste_end != paste_start) { + InsertGapsAt(paste_start, paste_end - paste_start, command); + } + } + + for (auto it=res.GetLoadData().properties.cbegin(); it!=res.GetLoadData().properties.cend(); it++) { + Block *block = static_cast(it.key()); + rational in = rational::fromString(it.value()[QStringLiteral("in")]); + Track::Reference track = Track::Reference::FromString(it.value()[QStringLiteral("track")]); + + command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()), + track.index(), + block, + paste_start + in)); + } + + Core::instance()->undo_stack()->pushIfHasChildren(command); + + return true; +} + QByteArray TimelineWidget::SaveSplitterState() const { return view_splitter_->saveState(); diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 86ca06bb6..99d3e7f1f 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -79,9 +79,11 @@ public: void ToggleLinksOnSelected(); - void CopySelected(bool cut); + virtual bool CopySelected(bool cut) override; - void Paste(bool insert); + virtual bool Paste() override; + + void PasteInsert(); void DeleteInToOut(bool ripple); @@ -294,6 +296,8 @@ private: QVector GetBlocksInGlobalRect(const QPoint &p1, const QPoint &p2); + bool PasteInternal(bool insert); + QPoint drag_origin_; QRubberBand rubberband_; diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp index f843d6b92..c56c73330 100644 --- a/app/widget/timeruler/seekablewidget.cpp +++ b/app/widget/timeruler/seekablewidget.cpp @@ -102,7 +102,7 @@ void SeekableWidget::DeleteSelected() bool SeekableWidget::CopySelected(bool cut) { if (!selection_manager_.GetSelectedObjects().empty()) { - ProjectSerializer::SaveData sdata(Project::GetProjectFromObject(timeline_points_)); + ProjectSerializer::SaveData sdata; sdata.SetOnlySerializeMarkers(selection_manager_.GetSelectedObjects()); ProjectSerializer::Copy(sdata, QStringLiteral("markers")); @@ -117,7 +117,7 @@ bool SeekableWidget::CopySelected(bool cut) } } -bool SeekableWidget::PasteMarkers(bool insert, rational insert_time) +bool SeekableWidget::PasteMarkers() { ProjectSerializer::Result res = ProjectSerializer::Paste(QStringLiteral("markers")); if (res == ProjectSerializer::kSuccess) { @@ -128,30 +128,19 @@ bool SeekableWidget::PasteMarkers(bool insert, rational insert_time) // Normalize markers to start at playhead rational min = RATIONAL_MAX; for (auto it=markers.cbegin(); it!=markers.cend(); it++) { - min = qMin(min, (*it)->time()); + min = std::min(min, (*it)->time()); } min -= GetTime(); - // Avoid duplicates - bool loop; - do { - loop = false; - for (auto it=markers.cbegin(); it!=markers.cend(); it++) { - rational proposed_time = (*it)->time() - min; - - if (timeline_points_->markers()->GetMarkerAtTime(proposed_time)) { - min -= timebase(); - loop = true; - break; - } - } - } while (loop); - for (auto it=markers.cbegin(); it!=markers.cend(); it++) { TimelineMarker *m = *it; m->set_time(m->time() - min); + if (TimelineMarker *existing = timeline_points_->markers()->GetMarkerAtTime(m->time())) { + command->add_child(new MarkerRemoveCommand(existing)); + } + command->add_child(new MarkerAddCommand(timeline_points_->markers(), m)); } diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h index 47e552183..dc28e9dbe 100644 --- a/app/widget/timeruler/seekablewidget.h +++ b/app/widget/timeruler/seekablewidget.h @@ -54,7 +54,7 @@ public: bool CopySelected(bool cut); - bool PasteMarkers(bool insert, rational insert_time); + bool PasteMarkers(); void DeselectAllMarkers();