From 7c7db5c9331b6b1e98a46d4871ed15dcabe9f374 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 00:05:47 +0100 Subject: [PATCH 01/14] Check footage in use before deleteing Initial very hacky implementation that checks if the footgae is in use before deleting it and makes sure all refernences to it are properly cleaned up. Also added IsMedia() function to Node to simplify things a bit. --- app/node/input/media/audio/audio.cpp | 5 ++ app/node/input/media/audio/audio.h | 2 + app/node/input/media/media.cpp | 5 ++ app/node/input/media/media.h | 6 +++ app/node/input/media/video/video.cpp | 5 ++ app/node/input/media/video/video.h | 2 + app/node/node.cpp | 5 ++ app/node/node.h | 9 ++++ .../projectexplorer/projectexplorer.cpp | 51 +++++++++++++++++++ 9 files changed, 90 insertions(+) diff --git a/app/node/input/media/audio/audio.cpp b/app/node/input/media/audio/audio.cpp index 5f34543ec..49463aa22 100644 --- a/app/node/input/media/audio/audio.cpp +++ b/app/node/input/media/audio/audio.cpp @@ -27,6 +27,11 @@ Node *AudioInput::copy() const return new AudioInput(); } +Stream::Type AudioInput::type() const +{ + return Stream::kAudio; +} + QString AudioInput::Name() const { return tr("Audio Input"); diff --git a/app/node/input/media/audio/audio.h b/app/node/input/media/audio/audio.h index 62914b693..5cebdcc3c 100644 --- a/app/node/input/media/audio/audio.h +++ b/app/node/input/media/audio/audio.h @@ -32,6 +32,8 @@ public: virtual Node* copy() const override; + virtual Stream::Type type() const override; + virtual QString Name() const override; virtual QString ShortName() const override; virtual QString id() const override; diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp index c0e1b3feb..8bfe11bb9 100644 --- a/app/node/input/media/media.cpp +++ b/app/node/input/media/media.cpp @@ -50,6 +50,11 @@ void MediaInput::SetFootage(StreamPtr f) footage_input_->set_standard_value(QVariant::fromValue(f)); } +bool MediaInput::IsMedia() const +{ + return true; +} + void MediaInput::Retranslate() { footage_input_->set_name(tr("Footage")); diff --git a/app/node/input/media/media.h b/app/node/input/media/media.h index ba89f3168..8934e321e 100644 --- a/app/node/input/media/media.h +++ b/app/node/input/media/media.h @@ -23,6 +23,7 @@ #include "codec/decoder.h" #include "node/node.h" +#include "project/item/footage/stream.h" OLIVE_NAMESPACE_ENTER @@ -35,11 +36,16 @@ class MediaInput : public Node public: MediaInput(); + virtual Stream::Type type() const = 0; + virtual QList Category() const override; StreamPtr footage(); void SetFootage(StreamPtr f); + virtual bool IsMedia() const override; + + virtual void Retranslate() override; virtual NodeValueTable Value(NodeValueDatabase& value) const override; diff --git a/app/node/input/media/video/video.cpp b/app/node/input/media/video/video.cpp index d451daeaf..c02aeaf39 100644 --- a/app/node/input/media/video/video.cpp +++ b/app/node/input/media/video/video.cpp @@ -36,6 +36,11 @@ Node *VideoInput::copy() const return new VideoInput(); } +Stream::Type VideoInput::type() const +{ + return Stream::kVideo; +} + QString VideoInput::Name() const { return tr("Video Input"); diff --git a/app/node/input/media/video/video.h b/app/node/input/media/video/video.h index 3ef9f54e8..7000ea82b 100644 --- a/app/node/input/media/video/video.h +++ b/app/node/input/media/video/video.h @@ -35,6 +35,8 @@ public: virtual Node* copy() const override; + virtual Stream::Type type() const override; + virtual QString Name() const override; virtual QString ShortName() const override; virtual QString id() const override; diff --git a/app/node/node.cpp b/app/node/node.cpp index 15e28512a..feda8c540 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -440,6 +440,11 @@ bool Node::IsTrack() const return false; } +bool Node::IsMedia() const +{ + return false; +} + const QList& Node::parameters() const { return params_; diff --git a/app/node/node.h b/app/node/node.h index ba05359ce..478d1e1ab 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -363,6 +363,15 @@ public: */ virtual bool IsTrack() const; + + /** + * @brief Returns whether this Node is a "Media" type or not + * + * You shouldn't ever need to override this since all derivatives of Media will automatically have this set to true. + * It's just a more convenient way of checking than dynamic_casting. + */ + virtual bool IsMedia() const; + /** * @brief The main processing function * diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 0a1dd0ab7..b47cbbaa7 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -558,6 +558,57 @@ void ProjectExplorer::DeleteSelected() } } + if (item_ptr->type() == Item::kFootage) { + // Get all sequences + QList sequences = model_.project()->get_items_of_type(Item::kSequence); + + // If no sequences exist we don't need to do anything clever here + if (!sequences.isEmpty()) { + // Footage can contain multiple streams, all of which need to be dealt with + foreach (StreamPtr stream, static_cast(item_ptr.get())->streams()) { + + // Check each sequence to see if it contains the footage in question + foreach (ItemPtr seq, sequences) { + + Sequence* s = static_cast(seq.get()); + + // Loop through nodes to find our Footage node + foreach (Node* node, s->nodes()) { + + // Check if node is of the right type + if (node->IsMedia() && static_cast(node)->type() == stream.get()->type()) { + // Check the streams are the same + if (static_cast(node)->footage() == stream) { + // Loop through nodes and set any that point to the Footage node to null + foreach (Node* check_node, s->nodes()) { + // Skip itself + if (check_node == node) { + continue; + } + if (check_node->GetImmediateDependencies().contains(node)) { + QList inputs = check_node->GetInputsIncludingArrays(); + + foreach (NodeInput* input, inputs) { + foreach (NodeEdgePtr edge, input->edges()) { + Node* connected = edge->output()->parentNode(); + + if (connected == node) { + input->DisconnectEdge(edge); + } + } + } + } + } + static_cast(node)->SetFootage(nullptr); + break; + } + } + } + } + } + } + } + new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); } From 3ed0cb2ab9992d45a4033d4ca930fba4be01bd4f Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 01:33:31 +0100 Subject: [PATCH 02/14] Move search code to seperate function. --- .../projectexplorer/projectexplorer.cpp | 96 ++++++++++++------- app/widget/projectexplorer/projectexplorer.h | 6 ++ 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index b47cbbaa7..3e216ec4b 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -536,6 +536,43 @@ void ProjectExplorer::DeselectAll() CurrentView()->selectionModel()->clearSelection(); } +QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) +{ + // Output list list + QList nodes; + + // Get all sequences. + QList sequences = model_.project()->get_items_of_type(Item::kSequence); + // Get item pointer. + ItemPtr item_ptr = item->get_shared_ptr(); + + if (type == Item::kFootage) { + // If no sequences exist we don't need to do anything clever here + if (!sequences.isEmpty()) { + // Footage can contain multiple streams, all of which need to be dealt with + foreach (StreamPtr stream, static_cast(item_ptr.get())->streams()) { + // Check each sequence to see if it contains the footage in question + foreach (ItemPtr seq, sequences) { + Sequence* s = static_cast(seq.get()); + + // Loop through nodes to find our Footage node + foreach (Node* node, s->nodes()) { + // Check if node is of the right type + if (node->IsMedia() && static_cast(node)->type() == stream.get()->type() || + static_cast(node)->type() == Stream::kImage) { + // Check the streams are the same + if (static_cast(node)->footage() == stream) { + nodes.append(node); + } + } + } + } + } + } + } + return nodes; +} + void ProjectExplorer::DeleteSelected() { QList selected = SelectedItems(); @@ -558,53 +595,44 @@ void ProjectExplorer::DeleteSelected() } } + // If this is a footage item, clean up if necessary if (item_ptr->type() == Item::kFootage) { - // Get all sequences - QList sequences = model_.project()->get_items_of_type(Item::kSequence); + + // Check if nodes exists + QList nodes = GetItemNodes(item, Item::kFootage); + if (!nodes.isEmpty()){ + // Loop through Footage nodes + foreach (Node* node, nodes) { - // If no sequences exist we don't need to do anything clever here - if (!sequences.isEmpty()) { - // Footage can contain multiple streams, all of which need to be dealt with - foreach (StreamPtr stream, static_cast(item_ptr.get())->streams()) { - - // Check each sequence to see if it contains the footage in question + // Loop through sequences + QList sequences = model_.project()->get_items_of_type(Item::kSequence); foreach (ItemPtr seq, sequences) { - Sequence* s = static_cast(seq.get()); - // Loop through nodes to find our Footage node - foreach (Node* node, s->nodes()) { + // Check all nodes to see if they're linked + foreach (Node* check_node, s->nodes()) { + // Skip itself + if (nodes.contains(check_node)) { + continue; + } + if (check_node->GetImmediateDependencies().contains(node)) { + QList inputs = check_node->GetInputsIncludingArrays(); - // Check if node is of the right type - if (node->IsMedia() && static_cast(node)->type() == stream.get()->type()) { - // Check the streams are the same - if (static_cast(node)->footage() == stream) { - // Loop through nodes and set any that point to the Footage node to null - foreach (Node* check_node, s->nodes()) { - // Skip itself - if (check_node == node) { - continue; - } - if (check_node->GetImmediateDependencies().contains(node)) { - QList inputs = check_node->GetInputsIncludingArrays(); + foreach (NodeInput* input, inputs) { + foreach (NodeEdgePtr edge, input->edges()) { + Node* connected = edge->output()->parentNode(); - foreach (NodeInput* input, inputs) { - foreach (NodeEdgePtr edge, input->edges()) { - Node* connected = edge->output()->parentNode(); - - if (connected == node) { - input->DisconnectEdge(edge); - } - } - } + if (connected == node) { + input->DisconnectEdge(edge); } } - static_cast(node)->SetFootage(nullptr); - break; } } } } + + // Set footage to be null + static_cast(node)->SetFootage(nullptr); } } } diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 2fe097bbd..6ec2f9af9 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -85,6 +85,12 @@ public: void DeleteSelected(); + /** + * @brief Check if an item is in use anywhere and return any relevant input nodes + * kFootage has two streams that need to be handled + */ + QList GetItemNodes(Item* item, Item::Type type); + public slots: void set_view_type(ProjectToolbar::ViewType type); From 0e272b6bba2c84e27b944198f80842ab873e6bd3 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 15:52:14 +0100 Subject: [PATCH 03/14] Cleanup and add warning message. --- .../projectexplorer/projectexplorer.cpp | 66 +++++++++---------- app/widget/projectexplorer/projectexplorer.h | 26 ++++++-- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 3e216ec4b..fabf30cab 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -558,8 +559,7 @@ QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) // Loop through nodes to find our Footage node foreach (Node* node, s->nodes()) { // Check if node is of the right type - if (node->IsMedia() && static_cast(node)->type() == stream.get()->type() || - static_cast(node)->type() == Stream::kImage) { + if (node->IsMedia() && static_cast(node)->type() == stream.get()->type()) { // Check the streams are the same if (static_cast(node)->footage() == stream) { nodes.append(node); @@ -573,6 +573,28 @@ QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) return nodes; } +ProjectExplorer::FootageDeleteResponse ProjectExplorer::DeleteWarningMessage() +{ + QMessageBox msgBox; + msgBox.setText(tr("This footage is in use.")); + msgBox.setInformativeText(tr("Do you want to offline the footage or entirely delete it from the timeline?")); + QPushButton* offline = msgBox.addButton(tr("Offline Footage"), QMessageBox::ApplyRole); + QPushButton* deleteClips = msgBox.addButton(tr("Delete Clips"), QMessageBox::ApplyRole); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setIcon(QMessageBox::Warning); + + msgBox.exec(); + + if (msgBox.clickedButton() == offline) { + return kOffline; + } + if (msgBox.clickedButton() == deleteClips) { + return kDelete; + } + + return kCancel; +} + void ProjectExplorer::DeleteSelected() { QList selected = SelectedItems(); @@ -601,38 +623,16 @@ void ProjectExplorer::DeleteSelected() // Check if nodes exists QList nodes = GetItemNodes(item, Item::kFootage); if (!nodes.isEmpty()){ - // Loop through Footage nodes - foreach (Node* node, nodes) { - - // Loop through sequences - QList sequences = model_.project()->get_items_of_type(Item::kSequence); - foreach (ItemPtr seq, sequences) { - Sequence* s = static_cast(seq.get()); - - // Check all nodes to see if they're linked - foreach (Node* check_node, s->nodes()) { - // Skip itself - if (nodes.contains(check_node)) { - continue; - } - if (check_node->GetImmediateDependencies().contains(node)) { - QList inputs = check_node->GetInputsIncludingArrays(); - - foreach (NodeInput* input, inputs) { - foreach (NodeEdgePtr edge, input->edges()) { - Node* connected = edge->output()->parentNode(); - - if (connected == node) { - input->DisconnectEdge(edge); - } - } - } - } - } + FootageDeleteResponse response = DeleteWarningMessage(); + if (response == kOffline) { + // Loop through Footage nodes + foreach (Node* node, nodes) { + // Set footage to be null + static_cast(node)->SetFootage(nullptr); } - - // Set footage to be null - static_cast(node)->SetFootage(nullptr); + } + if (response == kCancel) { + return; } } } diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 6ec2f9af9..0e69b427c 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -85,12 +85,6 @@ public: void DeleteSelected(); - /** - * @brief Check if an item is in use anywhere and return any relevant input nodes - * kFootage has two streams that need to be handled - */ - QList GetItemNodes(Item* item, Item::Type type); - public slots: void set_view_type(ProjectToolbar::ViewType type); @@ -107,6 +101,26 @@ signals: void DoubleClickedItem(Item* item); private: + enum FootageDeleteResponse { + kDelete, + kOffline, + kCancel + }; + + + /** + * @brief Pop up a QMessageBox to warn the user if the deleted clips are in use + * + * Returns a FootageDeleteResponse + */ + FootageDeleteResponse DeleteWarningMessage(); + + /** + * @brief Check if an item is in use anywhere and return any relevant input nodes + * kFootage has two streams that need to be handled + */ + QList GetItemNodes(Item* item, Item::Type type); + /** * @brief Simple convenience function for adding a view to this stacked widget * From 0f1d4ec1b829a426f46932b2bba24d809fb75762 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 15:59:51 +0100 Subject: [PATCH 04/14] Make sure image sequences are caught. --- app/widget/projectexplorer/projectexplorer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index fabf30cab..419f6a1ea 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -559,7 +559,7 @@ QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) // Loop through nodes to find our Footage node foreach (Node* node, s->nodes()) { // Check if node is of the right type - if (node->IsMedia() && static_cast(node)->type() == stream.get()->type()) { + if (node->IsMedia()){ // Check the streams are the same if (static_cast(node)->footage() == stream) { nodes.append(node); From c1e345611de58fb09e4c467222d8b618a7a1e9a5 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 16:14:56 +0100 Subject: [PATCH 05/14] Cleanup. --- app/widget/projectexplorer/projectexplorer.cpp | 6 +++--- app/widget/projectexplorer/projectexplorer.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 419f6a1ea..0694185e3 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -537,7 +537,7 @@ void ProjectExplorer::DeselectAll() CurrentView()->selectionModel()->clearSelection(); } -QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) +QList ProjectExplorer::GetFootageNodes(Item* item) { // Output list list QList nodes; @@ -547,7 +547,7 @@ QList ProjectExplorer::GetItemNodes(Item* item, Item::Type type) // Get item pointer. ItemPtr item_ptr = item->get_shared_ptr(); - if (type == Item::kFootage) { + if (item_ptr.get()->type() == Item::kFootage) { // If no sequences exist we don't need to do anything clever here if (!sequences.isEmpty()) { // Footage can contain multiple streams, all of which need to be dealt with @@ -621,7 +621,7 @@ void ProjectExplorer::DeleteSelected() if (item_ptr->type() == Item::kFootage) { // Check if nodes exists - QList nodes = GetItemNodes(item, Item::kFootage); + QList nodes = GetFootageNodes(item); if (!nodes.isEmpty()){ FootageDeleteResponse response = DeleteWarningMessage(); if (response == kOffline) { diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 0e69b427c..12c999d5e 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -117,9 +117,9 @@ private: /** * @brief Check if an item is in use anywhere and return any relevant input nodes - * kFootage has two streams that need to be handled + * Returns a QList as Footage has two streams that need to be handled */ - QList GetItemNodes(Item* item, Item::Type type); + QList GetFootageNodes(Item* item); /** * @brief Simple convenience function for adding a view to this stacked widget From 5bb9cb0ccad27544898bbe7d5e0af36ccbf5f71f Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 17:42:09 +0100 Subject: [PATCH 06/14] Make Offlining footage an UndoCommand Created a new UndoCommand in ProjectViewModel that set the footage input to a nullptr and resets it to the correct stream on undo. --- app/project/projectviewmodel.cpp | 38 +++++++++++++++++++ app/project/projectviewmodel.h | 21 ++++++++++ .../projectexplorer/projectexplorer.cpp | 27 +++++++------ app/widget/projectexplorer/projectexplorer.h | 2 +- 4 files changed, 75 insertions(+), 13 deletions(-) diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 7dfaf453a..8c176f24f 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -25,6 +25,7 @@ #include #include "core.h" +#include "node/input/media/media.h" OLIVE_NAMESPACE_ENTER @@ -603,4 +604,41 @@ void ProjectViewModel::RemoveItemCommand::undo_internal() model_->AddChild(parent_, item_); } +ProjectViewModel::OfflineFootageCommand::OfflineFootageCommand(ProjectViewModel* model, ItemPtr item, + QMap nodes, QUndoCommand* parent) : + UndoCommand(parent), + model_(model), + item_(item), + nodes_(nodes) +{ +} + +Project *ProjectViewModel::OfflineFootageCommand::GetRelevantProject() const +{ + return model_->project(); +} + +void ProjectViewModel::OfflineFootageCommand::redo_internal() +{ + QMap::const_iterator it = nodes_.constBegin(); + while (it != nodes_.constEnd()) { + static_cast(it.key())->SetFootage(nullptr); + ++it; + } + + parent_ = item_->parent(); + model_->RemoveChild(parent_, item_.get()); +} + +void ProjectViewModel::OfflineFootageCommand::undo_internal() +{ + model_->AddChild(parent_, item_); + + QMap::const_iterator it = nodes_.constBegin(); + while (it != nodes_.constEnd()) { + static_cast(it.key())->SetFootage(it.value()); + ++it; + } +} + OLIVE_NAMESPACE_EXIT diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index 964378316..d79542aa0 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -194,6 +194,27 @@ public: }; + class OfflineFootageCommand : public UndoCommand { + public: + OfflineFootageCommand(ProjectViewModel* model, ItemPtr item, QMap nodes, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo_internal() override; + + virtual void undo_internal() override; + + private: + ProjectViewModel* model_; + + ItemPtr item_; + + Item* parent_; + + QMap nodes_; + }; + private: /** * @brief Retrieve the index of `item` in its parent diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 0694185e3..3c5ce6f07 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -537,11 +537,11 @@ void ProjectExplorer::DeselectAll() CurrentView()->selectionModel()->clearSelection(); } -QList ProjectExplorer::GetFootageNodes(Item* item) +QMap ProjectExplorer::GetFootageNodes(Item* item) { // Output list list - QList nodes; - + QMap nodes; + // Get all sequences. QList sequences = model_.project()->get_items_of_type(Item::kSequence); // Get item pointer. @@ -562,7 +562,7 @@ QList ProjectExplorer::GetFootageNodes(Item* item) if (node->IsMedia()){ // Check the streams are the same if (static_cast(node)->footage() == stream) { - nodes.append(node); + nodes.insert(node, stream); } } } @@ -615,29 +615,32 @@ void ProjectExplorer::DeleteSelected() if (Core::instance()->main_window()->IsSequenceOpen(s)) { Core::instance()->main_window()->CloseSequence(s); } + + new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); } // If this is a footage item, clean up if necessary if (item_ptr->type() == Item::kFootage) { // Check if nodes exists - QList nodes = GetFootageNodes(item); + QMap nodes = GetFootageNodes(item); if (!nodes.isEmpty()){ + // Warn user and ask them what to do FootageDeleteResponse response = DeleteWarningMessage(); if (response == kOffline) { - // Loop through Footage nodes - foreach (Node* node, nodes) { - // Set footage to be null - static_cast(node)->SetFootage(nullptr); - } + new ProjectViewModel::OfflineFootageCommand(&model_, item_ptr, nodes, command); + } + if (response == kDelete) { + // Get all sequences. + QList sequences = model_.project()->get_items_of_type(Item::kSequence); + } if (response == kCancel) { + delete command; return; } } } - - new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); } Core::instance()->undo_stack()->pushIfHasChildren(command); diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 12c999d5e..b755401ac 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -119,7 +119,7 @@ private: * @brief Check if an item is in use anywhere and return any relevant input nodes * Returns a QList as Footage has two streams that need to be handled */ - QList GetFootageNodes(Item* item); + QMap GetFootageNodes(Item* item); /** * @brief Simple convenience function for adding a view to this stacked widget From 602f96b2b6e63ff3a0e8369941a1edcefc765edf Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Sun, 27 Sep 2020 22:15:32 +0100 Subject: [PATCH 07/14] Cleanup comment --- app/widget/projectexplorer/projectexplorer.cpp | 1 - app/widget/projectexplorer/projectexplorer.h | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 3c5ce6f07..e0ca1aff2 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -577,7 +577,6 @@ ProjectExplorer::FootageDeleteResponse ProjectExplorer::DeleteWarningMessage() { QMessageBox msgBox; msgBox.setText(tr("This footage is in use.")); - msgBox.setInformativeText(tr("Do you want to offline the footage or entirely delete it from the timeline?")); QPushButton* offline = msgBox.addButton(tr("Offline Footage"), QMessageBox::ApplyRole); QPushButton* deleteClips = msgBox.addButton(tr("Delete Clips"), QMessageBox::ApplyRole); msgBox.setStandardButtons(QMessageBox::Cancel); diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index b755401ac..907da2964 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -117,7 +117,8 @@ private: /** * @brief Check if an item is in use anywhere and return any relevant input nodes - * Returns a QList as Footage has two streams that need to be handled + * + * Returns a QMap pairing a Footage node to its StreamPtr */ QMap GetFootageNodes(Item* item); From 92db73502229cf0cb1fd36d293893ec008512662 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 00:06:38 +0100 Subject: [PATCH 08/14] Add Delete functionality. Still buggy. --- app/project/projectviewmodel.cpp | 33 +++++++++++++++++++ app/project/projectviewmodel.h | 24 ++++++++++++++ .../projectexplorer/projectexplorer.cpp | 19 +++++++++++ 3 files changed, 76 insertions(+) diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 8c176f24f..5b90fde71 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -26,6 +26,7 @@ #include "core.h" #include "node/input/media/media.h" +#include "widget/timelinewidget/timelinewidget.h" OLIVE_NAMESPACE_ENTER @@ -641,4 +642,36 @@ void ProjectViewModel::OfflineFootageCommand::undo_internal() } } +ProjectViewModel::DeleteFootageCommand::DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, + QList blocks, QUndoCommand* parent) : + UndoCommand(parent), + model_(model), + item_(item), + blocks_(blocks) +{ + deleteCommand_ = new QUndoCommand(); + removalCommand_ = new QUndoCommand(); +} + +Project *ProjectViewModel::DeleteFootageCommand::GetRelevantProject() const +{ + return model_->project(); +} + +void ProjectViewModel::DeleteFootageCommand::redo_internal() +{ + TimelineWidget::ReplaceBlocksWithGaps(blocks_, true, deleteCommand_); + Core::instance()->undo_stack()->pushIfHasChildren(deleteCommand_); + + new ProjectViewModel::RemoveItemCommand(model_, item_, removalCommand_); + Core::instance()->undo_stack()->pushIfHasChildren(removalCommand_); +} + +void ProjectViewModel::DeleteFootageCommand::undo_internal() +{ + removalCommand_->undo(); + deleteCommand_->undo(); +} + + OLIVE_NAMESPACE_EXIT diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index d79542aa0..16e56fffa 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -25,6 +25,7 @@ #include "project.h" #include "undo/undocommand.h" +#include "node/block/block.h" OLIVE_NAMESPACE_ENTER @@ -215,6 +216,29 @@ public: QMap nodes_; }; + class DeleteFootageCommand : public UndoCommand { + public: + DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, QList blocks, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + + protected: + virtual void redo_internal() override; + + virtual void undo_internal() override; + + private: + ProjectViewModel* model_; + + ItemPtr item_; + + QList blocks_; + + QUndoCommand* deleteCommand_; + + QUndoCommand* removalCommand_; + }; + private: /** * @brief Retrieve the index of `item` in its parent diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index e0ca1aff2..dfdaa7228 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -37,6 +37,7 @@ #include "widget/menu/menu.h" #include "widget/menu/menushared.h" #include "window/mainwindow/mainwindow.h" +#include "widget/timelinewidget/timelinewidget.h" OLIVE_NAMESPACE_ENTER @@ -633,6 +634,24 @@ void ProjectExplorer::DeleteSelected() // Get all sequences. QList sequences = model_.project()->get_items_of_type(Item::kSequence); + QList blocks; + + foreach(ItemPtr seq, sequences) { + Sequence* s = static_cast(seq.get()); + // Loop through nodes in sequence + foreach(Node* node, s->nodes()) { + + // For each Block see if it is linked to one of the Footage nodes add it to the delete list + if (node->IsBlock()) { + foreach(Node* input, nodes.keys()) { + if(node->GetExclusiveDependencies().contains(input)) + blocks.append(static_cast(node)); + } + } + } + } + new ProjectViewModel::DeleteFootageCommand(&model_, item_ptr, blocks, command); + } if (response == kCancel) { delete command; From aef907859880cb9517d838c034475da5947558f2 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 10:56:30 +0100 Subject: [PATCH 09/14] Fix delete redo code --- app/project/projectviewmodel.cpp | 30 ++++++++++++++----- app/project/projectviewmodel.h | 9 ++++-- .../projectexplorer/projectexplorer.cpp | 6 +++- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 5b90fde71..0947a6635 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -642,15 +642,15 @@ void ProjectViewModel::OfflineFootageCommand::undo_internal() } } -ProjectViewModel::DeleteFootageCommand::DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, - QList blocks, QUndoCommand* parent) : +ProjectViewModel::DeleteFootageCommand::DeleteFootageCommand(ProjectViewModel *model, ItemPtr item, + QMap nodes, QUndoCommand *parent) + : UndoCommand(parent), model_(model), item_(item), - blocks_(blocks) + nodes_(nodes) { deleteCommand_ = new QUndoCommand(); - removalCommand_ = new QUndoCommand(); } Project *ProjectViewModel::DeleteFootageCommand::GetRelevantProject() const @@ -660,16 +660,32 @@ Project *ProjectViewModel::DeleteFootageCommand::GetRelevantProject() const void ProjectViewModel::DeleteFootageCommand::redo_internal() { + QList sequences = model_->project()->get_items_of_type(Item::kSequence); + + blocks_.clear(); + + foreach (ItemPtr seq, sequences) { + Sequence *s = static_cast(seq.get()); + // Loop through nodes in sequence + foreach (Node *node, s->nodes()) { + // For each Block see if it is linked to one of the Footage nodes add it to the delete list + if (node->IsBlock()) { + foreach (Node *input, nodes_.keys()) { + if (node->GetExclusiveDependencies().contains(input)) blocks_.append(static_cast(node)); + } + } + } + } TimelineWidget::ReplaceBlocksWithGaps(blocks_, true, deleteCommand_); Core::instance()->undo_stack()->pushIfHasChildren(deleteCommand_); - new ProjectViewModel::RemoveItemCommand(model_, item_, removalCommand_); - Core::instance()->undo_stack()->pushIfHasChildren(removalCommand_); + parent_ = item_->parent(); + model_->RemoveChild(parent_, item_.get()); } void ProjectViewModel::DeleteFootageCommand::undo_internal() { - removalCommand_->undo(); + model_->AddChild(parent_, item_); deleteCommand_->undo(); } diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index 16e56fffa..05c2671c8 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -218,7 +218,8 @@ public: class DeleteFootageCommand : public UndoCommand { public: - DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, QList blocks, QUndoCommand* parent = nullptr); + DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, QMap nodes, + QUndoCommand* parent = nullptr); virtual Project* GetRelevantProject() const override; @@ -232,11 +233,13 @@ public: ItemPtr item_; + Item* parent_; + + QMap nodes_; + QList blocks_; QUndoCommand* deleteCommand_; - - QUndoCommand* removalCommand_; }; private: diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index dfdaa7228..e666ddf1d 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -650,7 +650,11 @@ void ProjectExplorer::DeleteSelected() } } } - new ProjectViewModel::DeleteFootageCommand(&model_, item_ptr, blocks, command); + //new ProjectViewModel::DeleteFootageCommand(&model_, item_ptr, blocks, command); + + QUndoCommand* deleteCommand = new QUndoCommand(command); + TimelineWidget::ReplaceBlocksWithGaps(blocks, true, deleteCommand); + //Core::instance()->undo_stack()->pushIfHasChildren(deleteCommand); } if (response == kCancel) { From 8e6b42a160408111c0ddb15c119d99c2b1209356 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 11:31:03 +0100 Subject: [PATCH 10/14] Simplify and clean up Remove the Undo code for deleting a clip and replace it with simpler parent/child undo relationship. Move block gathering code to seperate function. It's quite brute force and might lead to issues further down the line with more complex composited node setups. --- app/project/projectviewmodel.cpp | 48 ------------------ app/project/projectviewmodel.h | 26 ---------- .../projectexplorer/projectexplorer.cpp | 50 ++++++++++--------- app/widget/projectexplorer/projectexplorer.h | 5 ++ 4 files changed, 32 insertions(+), 97 deletions(-) diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 0947a6635..3a94252d6 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -642,52 +642,4 @@ void ProjectViewModel::OfflineFootageCommand::undo_internal() } } -ProjectViewModel::DeleteFootageCommand::DeleteFootageCommand(ProjectViewModel *model, ItemPtr item, - QMap nodes, QUndoCommand *parent) - : - UndoCommand(parent), - model_(model), - item_(item), - nodes_(nodes) -{ - deleteCommand_ = new QUndoCommand(); -} - -Project *ProjectViewModel::DeleteFootageCommand::GetRelevantProject() const -{ - return model_->project(); -} - -void ProjectViewModel::DeleteFootageCommand::redo_internal() -{ - QList sequences = model_->project()->get_items_of_type(Item::kSequence); - - blocks_.clear(); - - foreach (ItemPtr seq, sequences) { - Sequence *s = static_cast(seq.get()); - // Loop through nodes in sequence - foreach (Node *node, s->nodes()) { - // For each Block see if it is linked to one of the Footage nodes add it to the delete list - if (node->IsBlock()) { - foreach (Node *input, nodes_.keys()) { - if (node->GetExclusiveDependencies().contains(input)) blocks_.append(static_cast(node)); - } - } - } - } - TimelineWidget::ReplaceBlocksWithGaps(blocks_, true, deleteCommand_); - Core::instance()->undo_stack()->pushIfHasChildren(deleteCommand_); - - parent_ = item_->parent(); - model_->RemoveChild(parent_, item_.get()); -} - -void ProjectViewModel::DeleteFootageCommand::undo_internal() -{ - model_->AddChild(parent_, item_); - deleteCommand_->undo(); -} - - OLIVE_NAMESPACE_EXIT diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index 05c2671c8..bd3e068ed 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -216,32 +216,6 @@ public: QMap nodes_; }; - class DeleteFootageCommand : public UndoCommand { - public: - DeleteFootageCommand(ProjectViewModel* model, ItemPtr item, QMap nodes, - QUndoCommand* parent = nullptr); - - virtual Project* GetRelevantProject() const override; - - protected: - virtual void redo_internal() override; - - virtual void undo_internal() override; - - private: - ProjectViewModel* model_; - - ItemPtr item_; - - Item* parent_; - - QMap nodes_; - - QList blocks_; - - QUndoCommand* deleteCommand_; - }; - private: /** * @brief Retrieve the index of `item` in its parent diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index e666ddf1d..26fedcd2d 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -574,6 +574,31 @@ QMap ProjectExplorer::GetFootageNodes(Item* item) return nodes; } +QList ProjectExplorer::GetFootageBlocks(QList nodes) +{ + // Get all sequences. + QList sequences = model_.project()->get_items_of_type(Item::kSequence); + + QList blocks; + + foreach (ItemPtr seq, sequences) { + Sequence* s = static_cast(seq.get()); + // Loop through nodes in sequence + foreach (Node* node, s->nodes()) { + // For each Block see if it is linked to one of the Footage nodes add it to the delete list + if (node->IsBlock()) { + foreach (Node* input, nodes) { + if (node->GetExclusiveDependencies().contains(input)) { + blocks.append(static_cast(node)); + } + } + } + } + } + + return blocks; +} + ProjectExplorer::FootageDeleteResponse ProjectExplorer::DeleteWarningMessage() { QMessageBox msgBox; @@ -631,31 +656,10 @@ void ProjectExplorer::DeleteSelected() new ProjectViewModel::OfflineFootageCommand(&model_, item_ptr, nodes, command); } if (response == kDelete) { - // Get all sequences. - QList sequences = model_.project()->get_items_of_type(Item::kSequence); - - QList blocks; - - foreach(ItemPtr seq, sequences) { - Sequence* s = static_cast(seq.get()); - // Loop through nodes in sequence - foreach(Node* node, s->nodes()) { - - // For each Block see if it is linked to one of the Footage nodes add it to the delete list - if (node->IsBlock()) { - foreach(Node* input, nodes.keys()) { - if(node->GetExclusiveDependencies().contains(input)) - blocks.append(static_cast(node)); - } - } - } - } - //new ProjectViewModel::DeleteFootageCommand(&model_, item_ptr, blocks, command); - QUndoCommand* deleteCommand = new QUndoCommand(command); - TimelineWidget::ReplaceBlocksWithGaps(blocks, true, deleteCommand); - //Core::instance()->undo_stack()->pushIfHasChildren(deleteCommand); + TimelineWidget::ReplaceBlocksWithGaps(GetFootageBlocks(nodes.keys()), true, deleteCommand); + new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); } if (response == kCancel) { delete command; diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 907da2964..8900fc9de 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -122,6 +122,11 @@ private: */ QMap GetFootageNodes(Item* item); + /** + * @brief Get all the blocks associated with the given footage nodes + */ + QList GetFootageBlocks(QList nodes); + /** * @brief Simple convenience function for adding a view to this stacked widget * From 791857fba1dcdce2860029fde56d2fd309149249 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 12:03:01 +0100 Subject: [PATCH 11/14] Cleanup. --- app/project/projectviewmodel.cpp | 1 - app/project/projectviewmodel.h | 3 +++ app/widget/projectexplorer/projectexplorer.cpp | 2 +- app/widget/projectexplorer/projectexplorer.h | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 3a94252d6..8c176f24f 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -26,7 +26,6 @@ #include "core.h" #include "node/input/media/media.h" -#include "widget/timelinewidget/timelinewidget.h" OLIVE_NAMESPACE_ENTER diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index bd3e068ed..8da1c2472 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -195,6 +195,9 @@ public: }; + /** + * @brief An undo command for offlining footage when it is deleted from the project explorer + */ class OfflineFootageCommand : public UndoCommand { public: OfflineFootageCommand(ProjectViewModel* model, ItemPtr item, QMap nodes, QUndoCommand* parent = nullptr); diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 26fedcd2d..fecb0ac6f 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -588,7 +588,7 @@ QList ProjectExplorer::GetFootageBlocks(QList nodes) // For each Block see if it is linked to one of the Footage nodes add it to the delete list if (node->IsBlock()) { foreach (Node* input, nodes) { - if (node->GetExclusiveDependencies().contains(input)) { + if (node->GetDependencies().contains(input)) { blocks.append(static_cast(node)); } } diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 8900fc9de..a91af4cd2 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -124,6 +124,8 @@ private: /** * @brief Get all the blocks associated with the given footage nodes + * + * Currently quite brute force. */ QList GetFootageBlocks(QList nodes); From a71df11fa657b373a404b5907550d82326d6dc07 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 13:29:59 +0100 Subject: [PATCH 12/14] Deal with composite blocks If a block relies on mulitple inputs (i.e. a composite) then don't delete these from the timeline. We catch these inputs afterwards. --- .../projectexplorer/projectexplorer.cpp | 41 ++++++++++++++++--- app/widget/projectexplorer/projectexplorer.h | 6 +-- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index fecb0ac6f..54b27d5da 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -38,6 +38,7 @@ #include "widget/menu/menushared.h" #include "window/mainwindow/mainwindow.h" #include "widget/timelinewidget/timelinewidget.h" +#include "widget/nodeview/nodeviewundo.h" OLIVE_NAMESPACE_ENTER @@ -585,13 +586,23 @@ QList ProjectExplorer::GetFootageBlocks(QList nodes) Sequence* s = static_cast(seq.get()); // Loop through nodes in sequence foreach (Node* node, s->nodes()) { - // For each Block see if it is linked to one of the Footage nodes add it to the delete list + // For each Block see if it solely depends on one of our input nodes and if so add to the block list if (node->IsBlock()) { - foreach (Node* input, nodes) { - if (node->GetDependencies().contains(input)) { - blocks.append(static_cast(node)); + int footage_deps = 0; + + QList dependancies = node->GetDependencies(); + QSet intersection = QSet(dependancies.begin(), dependancies.end()) + .intersect(QSet(nodes.begin(), nodes.end())); + if (!intersection.isEmpty()) { + foreach (Node* dep, dependancies) { + if (dep->IsMedia()) { + footage_deps++; + } + } + if (footage_deps == 1) { + blocks.append(static_cast(node)); + } } - } } } } @@ -658,8 +669,26 @@ void ProjectExplorer::DeleteSelected() if (response == kDelete) { QUndoCommand* deleteCommand = new QUndoCommand(command); TimelineWidget::ReplaceBlocksWithGaps(GetFootageBlocks(nodes.keys()), true, deleteCommand); - new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); + //Core::instance()->undo_stack()->pushIfHasChildren(command); + + // Catch any input nodes we missed do to complex composites etc. + + QList sequences = model_.project()->get_items_of_type(Item::kSequence); + + QList nodes_to_delete; + foreach (ItemPtr seq, sequences) { + Sequence* s = static_cast(seq.get()); + foreach (Node* node, s->nodes()) { + if (node->IsMedia()) { + if (nodes.contains(node)) { + nodes_to_delete.append(node); + } + } + } + QUndoCommand* deleteNodesCommand = new QUndoCommand(deleteCommand); + new NodeRemoveCommand(static_cast(s), nodes_to_delete, deleteNodesCommand); + } } if (response == kCancel) { delete command; diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index a91af4cd2..d393959fa 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -123,9 +123,9 @@ private: QMap GetFootageNodes(Item* item); /** - * @brief Get all the blocks associated with the given footage nodes - * - * Currently quite brute force. + * @brief Get all the blocks that solely rely on an input node + * + * Ignores blocks that depend on multiple inputs */ QList GetFootageBlocks(QList nodes); From 56e2b510417e11f649bdcfc5aae317e5410ea622 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 14:03:25 +0100 Subject: [PATCH 13/14] Add filename to warning message. --- .../projectexplorer/projectexplorer.cpp | 36 +++++++++++-------- app/widget/projectexplorer/projectexplorer.h | 2 +- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 54b27d5da..2294ea413 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -610,24 +610,30 @@ QList ProjectExplorer::GetFootageBlocks(QList nodes) return blocks; } -ProjectExplorer::FootageDeleteResponse ProjectExplorer::DeleteWarningMessage() +ProjectExplorer::FootageDeleteResponse ProjectExplorer::DeleteWarningMessage(Item* item) { - QMessageBox msgBox; - msgBox.setText(tr("This footage is in use.")); - QPushButton* offline = msgBox.addButton(tr("Offline Footage"), QMessageBox::ApplyRole); - QPushButton* deleteClips = msgBox.addButton(tr("Delete Clips"), QMessageBox::ApplyRole); - msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setIcon(QMessageBox::Warning); + ItemPtr item_ptr = item->get_shared_ptr(); - msgBox.exec(); + if (item_ptr->type() == Item::kFootage) { + QString clip_name = static_cast(item_ptr.get())->filename().split("/").last(); - if (msgBox.clickedButton() == offline) { - return kOffline; + QMessageBox msgBox; + msgBox.setWindowTitle(clip_name); + msgBox.setText(clip_name + tr(" is in use.")); + QPushButton* offline = msgBox.addButton(tr("Offline Footage"), QMessageBox::ApplyRole); + QPushButton* deleteClips = msgBox.addButton(tr("Delete Clips"), QMessageBox::ApplyRole); + msgBox.setStandardButtons(QMessageBox::Cancel); + msgBox.setIcon(QMessageBox::Warning); + + msgBox.exec(); + + if (msgBox.clickedButton() == offline) { + return kOffline; + } + if (msgBox.clickedButton() == deleteClips) { + return kDelete; + } } - if (msgBox.clickedButton() == deleteClips) { - return kDelete; - } - return kCancel; } @@ -662,7 +668,7 @@ void ProjectExplorer::DeleteSelected() QMap nodes = GetFootageNodes(item); if (!nodes.isEmpty()){ // Warn user and ask them what to do - FootageDeleteResponse response = DeleteWarningMessage(); + FootageDeleteResponse response = DeleteWarningMessage(item); if (response == kOffline) { new ProjectViewModel::OfflineFootageCommand(&model_, item_ptr, nodes, command); } diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index d393959fa..daca3aec1 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -113,7 +113,7 @@ private: * * Returns a FootageDeleteResponse */ - FootageDeleteResponse DeleteWarningMessage(); + FootageDeleteResponse DeleteWarningMessage(Item* item); /** * @brief Check if an item is in use anywhere and return any relevant input nodes From 7704e004430b34b506654d39874034e49d61ec49 Mon Sep 17 00:00:00 2001 From: Thomas Wilshaw Date: Tue, 29 Sep 2020 14:14:21 +0100 Subject: [PATCH 14/14] Cleanup --- .../projectexplorer/projectexplorer.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 2294ea413..b63a40919 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -542,7 +542,7 @@ void ProjectExplorer::DeselectAll() QMap ProjectExplorer::GetFootageNodes(Item* item) { // Output list list - QMap nodes; + QMap footage_nodes; // Get all sequences. QList sequences = model_.project()->get_items_of_type(Item::kSequence); @@ -564,7 +564,7 @@ QMap ProjectExplorer::GetFootageNodes(Item* item) if (node->IsMedia()){ // Check the streams are the same if (static_cast(node)->footage() == stream) { - nodes.insert(node, stream); + footage_nodes.insert(node, stream); } } } @@ -572,7 +572,7 @@ QMap ProjectExplorer::GetFootageNodes(Item* item) } } } - return nodes; + return footage_nodes; } QList ProjectExplorer::GetFootageBlocks(QList nodes) @@ -594,11 +594,13 @@ QList ProjectExplorer::GetFootageBlocks(QList nodes) QSet intersection = QSet(dependancies.begin(), dependancies.end()) .intersect(QSet(nodes.begin(), nodes.end())); if (!intersection.isEmpty()) { + // Count how many Media inputs this block depends on foreach (Node* dep, dependancies) { if (dep->IsMedia()) { footage_deps++; } } + // If it only depends on one input we can safely delete it if (footage_deps == 1) { blocks.append(static_cast(node)); } @@ -665,20 +667,20 @@ void ProjectExplorer::DeleteSelected() if (item_ptr->type() == Item::kFootage) { // Check if nodes exists - QMap nodes = GetFootageNodes(item); - if (!nodes.isEmpty()){ + QMap footage_nodes = GetFootageNodes(item); + if (!footage_nodes.isEmpty()){ // Warn user and ask them what to do FootageDeleteResponse response = DeleteWarningMessage(item); if (response == kOffline) { - new ProjectViewModel::OfflineFootageCommand(&model_, item_ptr, nodes, command); + new ProjectViewModel::OfflineFootageCommand(&model_, item_ptr, footage_nodes, command); } if (response == kDelete) { QUndoCommand* deleteCommand = new QUndoCommand(command); - TimelineWidget::ReplaceBlocksWithGaps(GetFootageBlocks(nodes.keys()), true, deleteCommand); + // Delete any non-composite blocks + TimelineWidget::ReplaceBlocksWithGaps(GetFootageBlocks(footage_nodes.keys()), true, deleteCommand); new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); - //Core::instance()->undo_stack()->pushIfHasChildren(command); - // Catch any input nodes we missed do to complex composites etc. + // Catch any input nodes we missed due to composites etc. QList sequences = model_.project()->get_items_of_type(Item::kSequence); @@ -687,7 +689,7 @@ void ProjectExplorer::DeleteSelected() Sequence* s = static_cast(seq.get()); foreach (Node* node, s->nodes()) { if (node->IsMedia()) { - if (nodes.contains(node)) { + if (footage_nodes.contains(node)) { nodes_to_delete.append(node); } }