cleanup and included closing footage viewer if footage is in use
This commit is contained in:
@@ -604,41 +604,4 @@ void ProjectViewModel::RemoveItemCommand::undo_internal()
|
||||
model_->AddChild(parent_, item_);
|
||||
}
|
||||
|
||||
ProjectViewModel::OfflineFootageCommand::OfflineFootageCommand(ProjectViewModel* model, ItemPtr item,
|
||||
QMap<Node*, StreamPtr> 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<Node *, StreamPtr>::const_iterator it = nodes_.constBegin();
|
||||
while (it != nodes_.constEnd()) {
|
||||
static_cast<MediaInput *>(it.key())->SetFootage(nullptr);
|
||||
++it;
|
||||
}
|
||||
|
||||
parent_ = item_->parent();
|
||||
model_->RemoveChild(parent_, item_.get());
|
||||
}
|
||||
|
||||
void ProjectViewModel::OfflineFootageCommand::undo_internal()
|
||||
{
|
||||
model_->AddChild(parent_, item_);
|
||||
|
||||
QMap<Node *, StreamPtr>::const_iterator it = nodes_.constBegin();
|
||||
while (it != nodes_.constEnd()) {
|
||||
static_cast<MediaInput *>(it.key())->SetFootage(it.value());
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -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<Node*, StreamPtr> 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<Node*, StreamPtr> nodes_;
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Retrieve the index of `item` in its parent
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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<Node*, StreamPtr> ProjectExplorer::GetFootageNodes(Item* item)
|
||||
QList<MediaInput *> ProjectExplorer::GetMediaNodesUsingFootage(Footage *item)
|
||||
{
|
||||
// Output list list
|
||||
QMap<Node*, StreamPtr> footage_nodes;
|
||||
QList<MediaInput *> list;
|
||||
|
||||
// Get all sequences.
|
||||
QList<ItemPtr> 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<Footage*>(item_ptr.get())->streams()) {
|
||||
// Check each sequence to see if it contains the footage in question
|
||||
foreach (ItemPtr seq, sequences) {
|
||||
Sequence* s = static_cast<Sequence*>(seq.get());
|
||||
// Footage can contain multiple streams, all of which need to be dealt with
|
||||
foreach (ItemPtr s, sequences) {
|
||||
const QList<Node*>& nodes = static_cast<Sequence*>(s.get())->nodes();
|
||||
foreach (Node* n, nodes) {
|
||||
if (n->IsMedia()) {
|
||||
MediaInput* media_node = static_cast<MediaInput*>(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<MediaInput*>(node)->footage() == stream) {
|
||||
footage_nodes.insert(node, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (media_node->footage()->footage() == item) {
|
||||
list.append(media_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return footage_nodes;
|
||||
}
|
||||
|
||||
QList<Block*> ProjectExplorer::GetFootageBlocks(QList<Node*> nodes)
|
||||
{
|
||||
// Get all sequences.
|
||||
QList<ItemPtr> sequences = model_.project()->get_items_of_type(Item::kSequence);
|
||||
|
||||
QList<Block*> blocks;
|
||||
|
||||
foreach (ItemPtr seq, sequences) {
|
||||
Sequence* s = static_cast<Sequence*>(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<Node*> dependancies = node->GetDependencies();
|
||||
QSet<Node*> intersection = QSet<Node*>(dependancies.begin(), dependancies.end())
|
||||
.intersect(QSet<Node*>(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<Block*>(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<Footage*>(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<Sequence*>(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<Sequence*>(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<Footage*>(item);
|
||||
|
||||
// If this is a footage item, clean up if necessary
|
||||
if (item_ptr->type() == Item::kFootage) {
|
||||
|
||||
// Check if nodes exists
|
||||
QMap<Node*, StreamPtr> 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<MediaInput*> footage_nodes = GetMediaNodesUsingFootage(footage);
|
||||
|
||||
if (!footage_nodes.isEmpty()) {
|
||||
// Footage is in use, show messagebox asking what to do about it
|
||||
QList<Sequence*> 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<Sequence*>(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<ItemPtr> 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<Node*> nodes_to_delete;
|
||||
foreach (ItemPtr seq, sequences) {
|
||||
Sequence* s = static_cast<Sequence*>(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<Block*> blocks_to_remove;
|
||||
|
||||
foreach (Sequence* s, used_in_sequences) {
|
||||
foreach (TrackOutput* track, s->viewer_output()->GetTracks()) {
|
||||
foreach (Block* b, track->Blocks()) {
|
||||
QList<Node*> 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<NodeGraph*>(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<FootageViewerPanel>().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);
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QTimer>
|
||||
#include <QTreeView>
|
||||
|
||||
#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<Node*, StreamPtr> GetFootageNodes(Item* item);
|
||||
QList<MediaInput*> GetMediaNodesUsingFootage(Footage* item);
|
||||
|
||||
/**
|
||||
* @brief Get all the blocks that solely rely on an input node
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "projectexplorerundo.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
OfflineFootageCommand::OfflineFootageCommand(const QList<MediaInput *> &media, QUndoCommand* parent) :
|
||||
UndoCommand(parent)
|
||||
{
|
||||
foreach (MediaInput* i, media) {
|
||||
stream_data_.insert(i, i->footage());
|
||||
}
|
||||
|
||||
project_ = static_cast<Sequence*>(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
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#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<MediaInput*>& media, QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo_internal() override;
|
||||
|
||||
virtual void undo_internal() override;
|
||||
|
||||
private:
|
||||
QMap<MediaInput*, StreamPtr> stream_data_;
|
||||
|
||||
Project* project_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // PROJECTEXPLORERUNDO_H
|
||||
Reference in New Issue
Block a user