diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 8c176f24f..a1ba9b481 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -604,41 +604,4 @@ 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 8da1c2472..e3de6e33a 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -195,30 +195,6 @@ 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); - - 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/CMakeLists.txt b/app/widget/projectexplorer/CMakeLists.txt index e999f1d1d..cc8d45b76 100644 --- a/app/widget/projectexplorer/CMakeLists.txt +++ b/app/widget/projectexplorer/CMakeLists.txt @@ -32,5 +32,7 @@ set(OLIVE_SOURCES widget/projectexplorer/projectexplorericonviewitemdelegate.cpp widget/projectexplorer/projectexplorernavigation.h widget/projectexplorer/projectexplorernavigation.cpp + widget/projectexplorer/projectexplorerundo.h + widget/projectexplorer/projectexplorerundo.cpp PARENT_SCOPE ) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index b63a40919..9041e9f38 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -32,6 +32,7 @@ #include "core.h" #include "dialog/footageproperties/footageproperties.h" #include "dialog/sequence/sequence.h" +#include "projectexplorerundo.h" #include "task/precache/precachetask.h" #include "task/taskmanager.h" #include "widget/menu/menu.h" @@ -539,104 +540,28 @@ void ProjectExplorer::DeselectAll() CurrentView()->selectionModel()->clearSelection(); } -QMap ProjectExplorer::GetFootageNodes(Item* item) +QList ProjectExplorer::GetMediaNodesUsingFootage(Footage *item) { - // Output list list - QMap footage_nodes; + QList list; // Get all sequences. QList sequences = model_.project()->get_items_of_type(Item::kSequence); - // Get item pointer. - ItemPtr item_ptr = item->get_shared_ptr(); - 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 - 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()); + // Footage can contain multiple streams, all of which need to be dealt with + foreach (ItemPtr s, sequences) { + const QList& nodes = static_cast(s.get())->nodes(); + foreach (Node* n, nodes) { + if (n->IsMedia()) { + MediaInput* media_node = static_cast(n); - // Loop through nodes to find our Footage node - foreach (Node* node, s->nodes()) { - // Check if node is of the right type - if (node->IsMedia()){ - // Check the streams are the same - if (static_cast(node)->footage() == stream) { - footage_nodes.insert(node, stream); - } - } - } + if (media_node->footage()->footage() == item) { + list.append(media_node); } } } } - return footage_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 solely depends on one of our input nodes and if so add to the block list - if (node->IsBlock()) { - int footage_deps = 0; - - QList dependancies = node->GetDependencies(); - 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)); - } - } - } - } - } - - return blocks; -} - -ProjectExplorer::FootageDeleteResponse ProjectExplorer::DeleteWarningMessage(Item* item) -{ - ItemPtr item_ptr = item->get_shared_ptr(); - - if (item_ptr->type() == Item::kFootage) { - QString clip_name = static_cast(item_ptr.get())->filename().split("/").last(); - - 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; - } - } - return kCancel; + return list; } void ProjectExplorer::DeleteSelected() @@ -650,60 +575,109 @@ void ProjectExplorer::DeleteSelected() QUndoCommand* command = new QUndoCommand(); foreach (Item* item, selected) { - ItemPtr item_ptr = item->get_shared_ptr(); - - // If this is a sequence, close it - if (item_ptr->type() == Item::kSequence) { - Sequence* s = static_cast(item_ptr.get()); + // Verify whether this item is in use anywhere + switch (item->type()) { + case Item::kSequence: + { + // If this is a sequence, check if it's open and close it if necessary + Sequence* s = static_cast(item); if (Core::instance()->main_window()->IsSequenceOpen(s)) { Core::instance()->main_window()->CloseSequence(s); } - - new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); + break; } + case Item::kFootage: + { + // If this is footage, check if it's used anywhere in any sequence + Footage* footage = static_cast(item); - // If this is a footage item, clean up if necessary - if (item_ptr->type() == Item::kFootage) { - - // Check if nodes exists - 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, footage_nodes, command); + QList footage_nodes = GetMediaNodesUsingFootage(footage); + + if (!footage_nodes.isEmpty()) { + // Footage is in use, show messagebox asking what to do about it + QList used_in_sequences; + + // Compile list of sequences to assist the user in making this decision + foreach (MediaInput* i, footage_nodes) { + Sequence* media_parent = static_cast(i->parent()); + + if (!used_in_sequences.contains(media_parent)) { + used_in_sequences.append(media_parent); + } } - if (response == kDelete) { - QUndoCommand* deleteCommand = new QUndoCommand(command); - // Delete any non-composite blocks - TimelineWidget::ReplaceBlocksWithGaps(GetFootageBlocks(footage_nodes.keys()), true, deleteCommand); - new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command); - // Catch any input nodes we missed due to composites etc. + QString sequence_list_str; + foreach (Sequence* s, used_in_sequences) { + sequence_list_str.append(QStringLiteral("%1\n").arg(s->name())); + } - QList sequences = model_.project()->get_items_of_type(Item::kSequence); + QMessageBox msgbox(this); + msgbox.setWindowTitle(tr("Confirm Footage Deletion")); + msgbox.setText(tr("The footage \"%1\" is currently used in the following sequence(s):\n\n" + "%2\nWhat would you like to do with these clips?") + .arg(footage->filename(), sequence_list_str)); + msgbox.setIcon(QMessageBox::Warning); - QList nodes_to_delete; - foreach (ItemPtr seq, sequences) { - Sequence* s = static_cast(seq.get()); - foreach (Node* node, s->nodes()) { - if (node->IsMedia()) { - if (footage_nodes.contains(node)) { - nodes_to_delete.append(node); + // Set up buttons + QPushButton* offline_btn = msgbox.addButton(tr("Offline Footage"), QMessageBox::YesRole); + QPushButton* delete_clip_btn = msgbox.addButton(tr("Delete Clips"), QMessageBox::NoRole); + msgbox.addButton(QMessageBox::Cancel); + + // Run messagebox + msgbox.exec(); + + if (msgbox.clickedButton() == offline_btn || msgbox.clickedButton() == delete_clip_btn) { + + // For safety, even if we're deleting clips, we'll offline the footage nodes too + new OfflineFootageCommand(footage_nodes, command); + + } + + if (msgbox.clickedButton() == delete_clip_btn) { + + // Delete any blocks that use this footage + QList blocks_to_remove; + + foreach (Sequence* s, used_in_sequences) { + foreach (TrackOutput* track, s->viewer_output()->GetTracks()) { + foreach (Block* b, track->Blocks()) { + QList deps = b->GetDependencies(); + + foreach (MediaInput* i, footage_nodes) { + if (deps.contains(i)) { + blocks_to_remove.append(b); + break; + } } } } - QUndoCommand* deleteNodesCommand = new QUndoCommand(deleteCommand); - new NodeRemoveCommand(static_cast(s), nodes_to_delete, deleteNodesCommand); } - } - if (response == kCancel) { + + TimelineWidget::ReplaceBlocksWithGaps(blocks_to_remove, true, command); + + } else if (msgbox.clickedButton() != offline_btn) { + + // Must have cancelled delete command; return; + } } + + // Close footage if currently open in footage panel + FootageViewerPanel* footage_panel = PanelManager::instance()->GetPanelsOfType().first(); + if (footage_panel->GetSelectedFootage().contains(footage)) { + footage_panel->SetFootage(nullptr); + } + break; } + case Item::kFolder: + // Do nothing + break; + } + + new ProjectViewModel::RemoveItemCommand(&model_, item->get_shared_ptr(), command); } Core::instance()->undo_stack()->pushIfHasChildren(command); diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index daca3aec1..90dd10841 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -25,6 +25,7 @@ #include #include +#include "node/input/media/media.h" #include "project/project.h" #include "project/projectviewmodel.h" #include "widget/projectexplorer/projectexplorericonview.h" @@ -101,26 +102,10 @@ 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(Item* item); - /** * @brief Check if an item is in use anywhere and return any relevant input nodes - * - * Returns a QMap pairing a Footage node to its StreamPtr */ - QMap GetFootageNodes(Item* item); + QList GetMediaNodesUsingFootage(Footage* item); /** * @brief Get all the blocks that solely rely on an input node diff --git a/app/widget/projectexplorer/projectexplorerundo.cpp b/app/widget/projectexplorer/projectexplorerundo.cpp new file mode 100644 index 000000000..584d4a56c --- /dev/null +++ b/app/widget/projectexplorer/projectexplorerundo.cpp @@ -0,0 +1,54 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "projectexplorerundo.h" + +OLIVE_NAMESPACE_ENTER + +OfflineFootageCommand::OfflineFootageCommand(const QList &media, QUndoCommand* parent) : + UndoCommand(parent) +{ + foreach (MediaInput* i, media) { + stream_data_.insert(i, i->footage()); + } + + project_ = static_cast(media.first()->parent())->project(); +} + +Project *OfflineFootageCommand::GetRelevantProject() const +{ + return project_; +} + +void OfflineFootageCommand::redo_internal() +{ + for (auto it=stream_data_.cbegin(); it!=stream_data_.cend(); it++) { + it.key()->SetFootage(nullptr); + } +} + +void OfflineFootageCommand::undo_internal() +{ + for (auto it=stream_data_.cbegin(); it!=stream_data_.cend(); it++) { + it.key()->SetFootage(it.value()); + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/projectexplorer/projectexplorerundo.h b/app/widget/projectexplorer/projectexplorerundo.h new file mode 100644 index 000000000..8aaabdc0e --- /dev/null +++ b/app/widget/projectexplorer/projectexplorerundo.h @@ -0,0 +1,52 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef PROJECTEXPLORERUNDO_H +#define PROJECTEXPLORERUNDO_H + +#include "node/input/media/media.h" +#include "undo/undocommand.h" + +OLIVE_NAMESPACE_ENTER + +/** + * @brief An undo command for offlining footage when it is deleted from the project explorer + */ +class OfflineFootageCommand : public UndoCommand { +public: + OfflineFootageCommand(const QList& media, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +protected: + virtual void redo_internal() override; + + virtual void undo_internal() override; + +private: + QMap stream_data_; + + Project* project_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // PROJECTEXPLORERUNDO_H