diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index c02c8acf2..e08078af0 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -45,5 +45,7 @@ set(OLIVE_SOURCES common/timelinecommon.h common/timerange.h common/timerange.cpp + common/xmlutils.h + common/xmlutils.cpp PARENT_SCOPE ) diff --git a/app/common/xmlutils.cpp b/app/common/xmlutils.cpp new file mode 100644 index 000000000..c16c7fc69 --- /dev/null +++ b/app/common/xmlutils.cpp @@ -0,0 +1,37 @@ +#include "xmlutils.h" + +#include "node/factory.h" + +Node* XMLLoadNode(QXmlStreamReader* reader) { + QString node_id; + + XMLAttributeLoop(reader, attr) { + if (attr.name() == "id") { + node_id = attr.value().toString(); + + // Currently the only thing we need + break; + } + } + + if (node_id.isEmpty()) { + qWarning() << "Found node with no ID"; + return nullptr; + } + + Node* node = NodeFactory::CreateFromID(node_id); + + if (!node) { + qWarning() << "Failed to load" << node_id << "- no node with that ID is installed"; + } + + return node; +} + +void XMLConnectNodes(const QHash& output_ptrs, const QList& desired_connections) +{ + foreach (const NodeParam::SerializedConnection& con, desired_connections) { + NodeParam::ConnectEdge(output_ptrs.value(con.output), + con.input); + } +} diff --git a/app/common/xmlreadloop.h b/app/common/xmlutils.h similarity index 61% rename from app/common/xmlreadloop.h rename to app/common/xmlutils.h index 80e101c5f..a25f040bd 100644 --- a/app/common/xmlreadloop.h +++ b/app/common/xmlutils.h @@ -1,6 +1,10 @@ #ifndef XMLREADLOOP_H #define XMLREADLOOP_H +#include + +#include "node/node.h" + #define XMLReadLoop(reader, section) \ while (!reader->atEnd() && !(reader->name() == section && reader->isEndElement()) && reader->readNext()) @@ -8,4 +12,8 @@ QXmlStreamAttributes __attributes = reader->attributes(); \ foreach (const QXmlStreamAttribute& item, __attributes) +Node *XMLLoadNode(QXmlStreamReader* reader); + +void XMLConnectNodes(const QHash &output_ptrs, const QList &desired_connections); + #endif // XMLREADLOOP_H diff --git a/app/node/input.cpp b/app/node/input.cpp index c4a5281f6..8d9f62bac 100644 --- a/app/node/input.cpp +++ b/app/node/input.cpp @@ -26,7 +26,7 @@ #include "common/bezier.h" #include "common/lerp.h" -#include "common/xmlreadloop.h" +#include "common/xmlutils.h" #include "node.h" #include "output.h" #include "inputarray.h" diff --git a/app/node/inputarray.cpp b/app/node/inputarray.cpp index e7d0e0e01..ac8c9406f 100644 --- a/app/node/inputarray.cpp +++ b/app/node/inputarray.cpp @@ -2,7 +2,7 @@ #include -#include "common/xmlreadloop.h" +#include "common/xmlutils.h" #include "node.h" NodeInputArray::NodeInputArray(const QString &id, const DataType &type, const QVariant &default_value) : diff --git a/app/node/node.cpp b/app/node/node.cpp index 38a3b6500..7c2deb60f 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -24,7 +24,7 @@ #include #include -#include "common/xmlreadloop.h" +#include "common/xmlutils.h" Node::Node() : can_be_deleted_(true) diff --git a/app/node/output.cpp b/app/node/output.cpp index dd87ac9c1..efcdeda7e 100644 --- a/app/node/output.cpp +++ b/app/node/output.cpp @@ -20,7 +20,7 @@ #include "output.h" -#include "common/xmlreadloop.h" +#include "common/xmlutils.h" #include "node/node.h" NodeOutput::NodeOutput(const QString &id) : diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp index d9be8e307..f21fe2098 100644 --- a/app/panel/node/node.cpp +++ b/app/panel/node/node.cpp @@ -59,6 +59,21 @@ void NodePanel::DeleteSelected() node_view_->DeleteSelected(); } +void NodePanel::CutSelected() +{ + node_view_->CopySelected(true); +} + +void NodePanel::CopySelected() +{ + node_view_->CopySelected(false); +} + +void NodePanel::Paste() +{ + node_view_->Paste(); +} + void NodePanel::Select(const QList &nodes) { node_view_->Select(nodes); diff --git a/app/panel/node/node.h b/app/panel/node/node.h index e84d9c7c8..5ce5b75ca 100644 --- a/app/panel/node/node.h +++ b/app/panel/node/node.h @@ -40,6 +40,11 @@ public: virtual void DeleteSelected() override; + virtual void CutSelected() override; + virtual void CopySelected() override; + + virtual void Paste() override; + public slots: void Select(const QList& nodes); void SelectWithDependencies(const QList& nodes); diff --git a/app/project/item/folder/folder.cpp b/app/project/item/folder/folder.cpp index 35a516332..e8cb04456 100644 --- a/app/project/item/folder/folder.cpp +++ b/app/project/item/folder/folder.cpp @@ -20,7 +20,7 @@ #include "folder.h" -#include "common/xmlreadloop.h" +#include "common/xmlutils.h" #include "project/item/footage/footage.h" #include "project/item/sequence/sequence.h" #include "ui/icons/icons.h" diff --git a/app/project/item/footage/footage.cpp b/app/project/item/footage/footage.cpp index 299597bf2..cccf67b75 100644 --- a/app/project/item/footage/footage.cpp +++ b/app/project/item/footage/footage.cpp @@ -24,7 +24,7 @@ #include "codec/decoder.h" #include "common/timecodefunctions.h" -#include "common/xmlreadloop.h" +#include "common/xmlutils.h" #include "config/config.h" #include "ui/icons/icons.h" diff --git a/app/project/item/footage/imagestream.cpp b/app/project/item/footage/imagestream.cpp index 855c0a806..312bca748 100644 --- a/app/project/item/footage/imagestream.cpp +++ b/app/project/item/footage/imagestream.cpp @@ -20,7 +20,7 @@ #include "imagestream.h" -#include "common/xmlreadloop.h" +#include "common/xmlutils.h" #include "footage.h" #include "project/project.h" #include "render/colormanager.h" diff --git a/app/project/item/item.cpp b/app/project/item/item.cpp index 661708260..d694bc1e4 100644 --- a/app/project/item/item.cpp +++ b/app/project/item/item.cpp @@ -76,11 +76,19 @@ const QList &Item::children() const return children_; } -ItemPtr Item::shared_ptr_from_raw(Item *item) +ItemPtr Item::shared_ptr_from_raw(Item *item, bool traverse) { for (int i=0;iCanHaveChildren()) { + ItemPtr grandchild = shared_ptr_from_raw(item); + + if (grandchild) { + return grandchild; + } } } @@ -147,6 +155,23 @@ void Item::set_project(Project *project) project_ = project; } +QList Item::get_children_of_type(Type type, bool recursive) const +{ + QList list; + + foreach (ItemPtr item, children_) { + if (item->type() == type) { + list.append(item); + } + + if (recursive && item->CanHaveChildren()) { + list.append(item->get_children_of_type(type, recursive)); + } + } + + return list; +} + bool Item::CanHaveChildren() const { return false; diff --git a/app/project/item/item.h b/app/project/item/item.h index e8385f09f..42cb83bf7 100644 --- a/app/project/item/item.h +++ b/app/project/item/item.h @@ -77,7 +77,7 @@ public: Item* child(int i) const; const QList& children() const; - ItemPtr shared_ptr_from_raw(Item* item); + ItemPtr shared_ptr_from_raw(Item* item, bool traverse = false); const QString& name() const; void set_name(const QString& n); @@ -97,6 +97,8 @@ public: Project* project() const; void set_project(Project* project); + QList get_children_of_type(Type type, bool recursive) const; + virtual bool CanHaveChildren() const; bool ChildExistsWithName(const QString& name); diff --git a/app/project/item/sequence/sequence.cpp b/app/project/item/sequence/sequence.cpp index e8edb5601..e91fea51e 100644 --- a/app/project/item/sequence/sequence.cpp +++ b/app/project/item/sequence/sequence.cpp @@ -25,7 +25,7 @@ #include "config/config.h" #include "common/channellayout.h" #include "common/timecodefunctions.h" -#include "common/xmlreadloop.h" +#include "common/xmlutils.h" #include "node/factory.h" #include "panel/panelmanager.h" #include "panel/node/node.h" @@ -110,28 +110,7 @@ void Sequence::Load(QXmlStreamReader *reader, QHash &, QLis Node* node; if (reader->name() == "node") { - QString node_id; - - XMLAttributeLoop(reader, attr) { - if (attr.name() == "id") { - node_id = attr.value().toString(); - - // Currently the only thing we need - break; - } - } - - if (node_id.isEmpty()) { - qDebug() << "Found node with no ID"; - continue; - } - - node = NodeFactory::CreateFromID(node_id); - - if (!node) { - qDebug() << "Failed to load" << node_id << "- no node with that ID is installed"; - continue; - } + node = XMLLoadNode(reader); } else { node = viewer_output_; } @@ -146,10 +125,7 @@ void Sequence::Load(QXmlStreamReader *reader, QHash &, QLis } // Make connections - foreach (const NodeParam::SerializedConnection& con, desired_connections) { - NodeParam::ConnectEdge(output_ptrs.value(con.output), - con.input); - } + XMLConnectNodes(output_ptrs, desired_connections); // Ensure this and all children are in the main thread // (FIXME: Weird place for this? This should probably be in ProjectLoadManager somehow) diff --git a/app/project/project.cpp b/app/project/project.cpp index 4cb69bd59..5b7231d95 100644 --- a/app/project/project.cpp +++ b/app/project/project.cpp @@ -23,7 +23,7 @@ #include #include -#include "common/xmlreadloop.h" +#include "common/xmlutils.h" #include "core.h" #include "dialog/progress/progress.h" #include "window/mainwindow/mainwindow.h" @@ -137,3 +137,8 @@ ColorManager *Project::color_manager() { return &color_manager_; } + +QList Project::get_items_of_type(Item::Type type) const +{ + return root_.get_children_of_type(type, true); +} diff --git a/app/project/project.h b/app/project/project.h index b7ee369e1..3a90bb6f3 100644 --- a/app/project/project.h +++ b/app/project/project.h @@ -63,6 +63,8 @@ public: ColorManager* color_manager(); + QList get_items_of_type(Item::Type type) const; + signals: void NameChanged(); diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index d128017ac..fb36b2238 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -34,10 +34,10 @@ MenuShared::MenuShared() new_folder_item_ = Menu::CreateItem(this, "newfolder", Core::instance(), SLOT(CreateNewFolder())); // "Edit" menu shared items - edit_cut_item_ = Menu::CreateItem(this, "cut", nullptr, nullptr, "Ctrl+X"); - edit_copy_item_ = Menu::CreateItem(this, "copy", nullptr, nullptr, "Ctrl+C"); - edit_paste_item_ = Menu::CreateItem(this, "paste", nullptr, nullptr, "Ctrl+V"); - edit_paste_insert_item_ = Menu::CreateItem(this, "pasteinsert", nullptr, nullptr, "Ctrl+Shift+V"); + edit_cut_item_ = Menu::CreateItem(this, "cut", this, SLOT(CutTriggered()), "Ctrl+X"); + edit_copy_item_ = Menu::CreateItem(this, "copy", this, SLOT(CopyTriggered()), "Ctrl+C"); + edit_paste_item_ = Menu::CreateItem(this, "paste", this, SLOT(PasteTriggered()), "Ctrl+V"); + edit_paste_insert_item_ = Menu::CreateItem(this, "pasteinsert", this, SLOT(PasteInsertTriggered()), "Ctrl+Shift+V"); edit_duplicate_item_ = Menu::CreateItem(this, "duplicate", nullptr, nullptr, "Ctrl+D"); edit_delete_item_ = Menu::CreateItem(this, "delete", this, SLOT(DeleteSelectedTriggered()), "Del"); edit_ripple_delete_item_ = Menu::CreateItem(this, "rippledelete", this, SLOT(RippleDeleteTriggered()), "Shift+Del"); @@ -161,6 +161,26 @@ void MenuShared::ToggleLinksTriggered() PanelManager::instance()->CurrentlyFocused()->ToggleLinks(); } +void MenuShared::CutTriggered() +{ + PanelManager::instance()->CurrentlyFocused()->CutSelected(); +} + +void MenuShared::CopyTriggered() +{ + PanelManager::instance()->CurrentlyFocused()->CopySelected(); +} + +void MenuShared::PasteTriggered() +{ + PanelManager::instance()->CurrentlyFocused()->Paste(); +} + +void MenuShared::PasteInsertTriggered() +{ + PanelManager::instance()->CurrentlyFocused()->PasteInsert(); +} + void MenuShared::Retranslate() { // "New" menu shared items diff --git a/app/widget/menu/menushared.h b/app/widget/menu/menushared.h index d15408f57..c1e6e54ff 100644 --- a/app/widget/menu/menushared.h +++ b/app/widget/menu/menushared.h @@ -93,6 +93,14 @@ private slots: void ToggleLinksTriggered(); + void CutTriggered(); + + void CopyTriggered(); + + void PasteTriggered(); + + void PasteInsertTriggered(); + }; #endif // MENUSHARED_H diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp index f1d895a05..2c728acf3 100644 --- a/app/widget/nodeview/nodeview.cpp +++ b/app/widget/nodeview/nodeview.cpp @@ -20,11 +20,14 @@ #include "nodeview.h" +#include #include +#include #include "core.h" #include "nodeviewundo.h" #include "node/factory.h" +#include "common/xmlutils.h" NodeView::NodeView(QWidget *parent) : QGraphicsView(parent), @@ -90,16 +93,7 @@ void NodeView::DeleteSelected() return; } - QList selected = scene_.selectedItems(); - QList selected_nodes; - - foreach (QGraphicsItem* item, selected) { - NodeViewItem* node_item = dynamic_cast(item); - - if (node_item) { - selected_nodes.append(node_item->node()); - } - } + QList selected_nodes = scene_.GetSelectedNodes(); if (selected_nodes.isEmpty()) { return; @@ -133,9 +127,6 @@ void NodeView::Select(const QList &nodes) foreach (Node* n, nodes) { NodeViewItem* item = scene_.NodeToUIObject(n); - Q_ASSERT(n); - Q_ASSERT(item); - item->setSelected(true); } } @@ -150,6 +141,117 @@ void NodeView::SelectWithDependencies(QList nodes) Select(nodes); } +void NodeView::CopySelected(bool cut) +{ + if (!graph_) { + return; + } + + QList selected = scene_.GetSelectedNodes(); + + if (selected.isEmpty()) { + return; + } + + QString copy_str; + + QXmlStreamWriter writer(©_str); + writer.setAutoFormatting(true); + + writer.writeStartDocument(); + writer.writeStartElement(QStringLiteral("olive")); + + foreach (Node* n, selected) { + n->Save(&writer); + } + + writer.writeEndElement(); // clipboard + writer.writeEndDocument(); + + if (cut) { + DeleteSelected(); + } + + QGuiApplication::clipboard()->setText(copy_str); +} + +void NodeView::Paste() +{ + if (!graph_) { + return; + } + + QString clipboard = QGuiApplication::clipboard()->text(); + + if (clipboard.isEmpty()) { + return; + } + + QXmlStreamReader reader(clipboard); + + QList pasted_nodes; + QHash output_ptrs; + QList desired_connections; + QList footage_connections; + + XMLReadLoop((&reader), QStringLiteral("olive")) { + if (reader.name() == QStringLiteral("node")) { + Node* node = XMLLoadNode(&reader); + + if (node) { + node->Load(&reader, output_ptrs, desired_connections, footage_connections, nullptr, reader.name().toString()); + + graph_->AddNode(node); + + pasted_nodes.append(node); + } + } + } + + // Make connections + if (!desired_connections.isEmpty()) { + XMLConnectNodes(output_ptrs, desired_connections); + } + + // Connect footage to existing footage if it exists + if (!footage_connections.isEmpty()) { + // Get list of all footage from project + // FIXME: Assumes sequence + QList footage = static_cast(graph_)->project()->get_items_of_type(Item::kFootage); + + if (!footage.isEmpty()) { + foreach (const NodeInput::FootageConnection& con, footage_connections) { + if (con.footage) { + // Assume this is a pointer to a Stream* + Stream* loaded_stream = reinterpret_cast(con.footage); + + bool found = false; + + foreach (ItemPtr item, footage) { + const QList& streams = std::static_pointer_cast(item)->streams(); + + foreach (StreamPtr s, streams) { + if (s.get() == loaded_stream) { + con.input->set_standard_value(QVariant::fromValue(s)); + found = true; + break; + } + } + + if (found) { + break; + } + } + } + } + } + } + + if (!pasted_nodes.isEmpty()) { + // FIXME: Attach to cursor so user can drop in place + } +} + void NodeView::ItemsChanged() { QHash::const_iterator i; @@ -263,21 +365,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event) void NodeView::SceneSelectionChangedSlot() { - // Get the scene's selected items and convert it into a list of selected nodes - QList selected_items = scene_.selectedItems(); - - QList selected_nodes; - - for (int i=0;i(selected_items.at(i)); - - if (item != nullptr) { - selected_nodes.append(item->node()); - } - } - - emit SelectionChanged(selected_nodes); + emit SelectionChanged(scene_.GetSelectedNodes()); } void NodeView::ShowContextMenu(const QPoint &pos) diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h index 4ea98bea3..7811ae4bb 100644 --- a/app/widget/nodeview/nodeview.h +++ b/app/widget/nodeview/nodeview.h @@ -57,6 +57,9 @@ public: void Select(const QList& nodes); void SelectWithDependencies(QList nodes); + void CopySelected(bool cut); + void Paste(); + signals: /** * @brief Signal emitted when the selected nodes have changed diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp index 750e391a2..a514eb898 100644 --- a/app/widget/nodeview/nodeviewscene.cpp +++ b/app/widget/nodeview/nodeviewscene.cpp @@ -42,6 +42,34 @@ void NodeViewScene::SetGraph(NodeGraph *graph) graph_ = graph; } +QList NodeViewScene::GetSelectedNodes() const +{ + QHash::const_iterator iterator; + QList selected; + + for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { + if (iterator.value()->isSelected()) { + selected.append(iterator.key()); + } + } + + return selected; +} + +QList NodeViewScene::GetSelectedItems() const +{ + QHash::const_iterator iterator; + QList selected; + + for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) { + if (iterator.value()->isSelected()) { + selected.append(iterator.value()); + } + } + + return selected; +} + const QHash &NodeViewScene::item_map() const { return item_map_; diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h index 2c768fd11..014791759 100644 --- a/app/widget/nodeview/nodeviewscene.h +++ b/app/widget/nodeview/nodeviewscene.h @@ -38,6 +38,9 @@ public: void SetGraph(NodeGraph* graph); + QList GetSelectedNodes() const; + QList GetSelectedItems() const; + const QHash& item_map() const; const QHash& edge_map() const; diff --git a/app/widget/panel/panel.h b/app/widget/panel/panel.h index 8e447a948..ff8ce54b1 100644 --- a/app/widget/panel/panel.h +++ b/app/widget/panel/panel.h @@ -132,6 +132,14 @@ public: virtual void ToggleLinks(){} + virtual void CutSelected(){} + + virtual void CopySelected(){} + + virtual void Paste(){} + + virtual void PasteInsert(){} + protected: /** * @brief paintEvent