implement entire project in nodes

Project items are now represented directly in nodes opening up more versatility and possibilities. This is the first iteration of this and will be buggy. Need to test thoroughly.
This commit is contained in:
itsmattkc
2021-02-15 21:31:57 +11:00
parent 155470bec1
commit d7c5ac4b44
174 changed files with 3515 additions and 4658 deletions
+88 -159
View File
@@ -30,7 +30,6 @@
#include "common/define.h"
#include "core.h"
#include "dialog/footageproperties/footageproperties.h"
#include "dialog/sequence/sequence.h"
#include "projectexplorerundo.h"
#include "task/precache/precachetask.h"
@@ -149,7 +148,7 @@ void ProjectExplorer::BrowseToFolder(const QModelIndex &index)
// Set navbar text to folder's name
if (index.isValid()) {
Folder* f = static_cast<Folder*>(sort_model_.mapToSource(index).internalPointer());
nav_bar_->set_text(f->name());
nav_bar_->set_text(f->GetLabel());
} else {
// Or set it to an empty string if the index is valid (which means we're browsing to the root directory)
nav_bar_->set_text(QString());
@@ -202,8 +201,7 @@ void ProjectExplorer::ItemDoubleClickedSlot(const QModelIndex &index)
Item* i = static_cast<Item*>(sort_model_.mapToSource(index).internalPointer());
// If the item is a folder, browse to it
if (i->CanHaveChildren()
&& (view_type() == ProjectToolbar::ListView || view_type() == ProjectToolbar::IconView)) {
if (dynamic_cast<Folder*>(i) && (view_type() == ProjectToolbar::ListView || view_type() == ProjectToolbar::IconView)) {
BrowseToFolder(index);
@@ -273,18 +271,16 @@ void ProjectExplorer::ShowContextMenu()
if (context_menu_items_.size() == 1) {
Item* context_menu_item = context_menu_items_.first();
switch (context_menu_item->type()) {
case Item::kFolder:
{
if (dynamic_cast<Folder*>(context_menu_item)) {
QAction* open_in_new_tab = menu.addAction(tr("Open in New Tab"));
connect(open_in_new_tab, &QAction::triggered, this, &ProjectExplorer::OpenContextMenuItemInNewTab);
QAction* open_in_new_window = menu.addAction(tr("Open in New Window"));
connect(open_in_new_window, &QAction::triggered, this, &ProjectExplorer::OpenContextMenuItemInNewWindow);
break;
}
case Item::kFootage:
{
} else if (dynamic_cast<Footage*>(context_menu_item)) {
QString reveal_text;
#if defined(Q_OS_WINDOWS)
@@ -297,10 +293,7 @@ void ProjectExplorer::ShowContextMenu()
QAction* reveal_action = menu.addAction(reveal_text);
connect(reveal_action, &QAction::triggered, this, &ProjectExplorer::RevealSelectedFootage);
break;
}
case Item::kSequence:
break;
}
menu.addSeparator();
@@ -311,15 +304,18 @@ void ProjectExplorer::ShowContextMenu()
bool all_items_are_footage_or_sequence = true;
foreach (Item* i, context_menu_items_) {
if (i->type() == Item::kFootage && !static_cast<Footage*>(i)->HasEnabledStreamsOfType(Stream::kVideo)) {
Footage* footage_cast_test = dynamic_cast<Footage*>(i);
Sequence* sequence_cast_test = dynamic_cast<Sequence*>(i);
if (footage_cast_test && !footage_cast_test->HasEnabledStreamsOfType(Stream::kVideo)) {
all_items_have_video_streams = false;
}
if (i->type() != Item::kFootage) {
if (!footage_cast_test) {
all_items_are_footage = false;
}
if (i->type() != Item::kFootage && i->type() != Item::kSequence) {
if (!footage_cast_test && !sequence_cast_test) {
all_items_are_footage_or_sequence = false;
}
}
@@ -328,14 +324,14 @@ void ProjectExplorer::ShowContextMenu()
Menu* proxy_menu = new Menu(tr("Pre-Cache"), &menu);
menu.addMenu(proxy_menu);
QVector<Item*> sequences = project()->get_items_of_type(Item::kSequence);
QVector<Sequence*> sequences = project()->root()->ListOutputsOfType<Sequence>();
if (sequences.isEmpty()) {
QAction* a = proxy_menu->addAction(tr("No sequences exist in project"));
a->setEnabled(false);
} else {
foreach (Item* i, sequences) {
QAction* a = proxy_menu->addAction(tr("For \"%1\"").arg(i->name()));
foreach (Sequence* i, sequences) {
QAction* a = proxy_menu->addAction(tr("For \"%1\"").arg(i->GetLabel()));
a->setData(Node::PtrToValue(i));
}
@@ -360,26 +356,20 @@ void ProjectExplorer::ShowItemPropertiesDialog()
{
Item* sel = context_menu_items_.first();
switch (sel->type()) {
case Item::kFootage:
{
// FIXME: Support for multiple items
FootagePropertiesDialog fpd(this, static_cast<Footage*>(sel));
fpd.exec();
break;
}
case Item::kFolder:
{
// FIXME: Rename dialog probably
break;
}
case Item::kSequence:
{
// FIXME: Support for multiple items
// FIXME: Support for multiple items
if (dynamic_cast<Footage*>(sel)) {
Core::instance()->LabelNodes({static_cast<Footage*>(sel)});
} else if (dynamic_cast<Folder*>(sel)) {
Core::instance()->LabelNodes({static_cast<Folder*>(sel)});
} else if (dynamic_cast<Sequence*>(sel)) {
SequenceDialog sd(static_cast<Sequence*>(sel), SequenceDialog::kExisting, this);
sd.exec();
break;
}
}
}
@@ -410,35 +400,30 @@ void ProjectExplorer::RevealSelectedFootage()
void ProjectExplorer::OpenContextMenuItemInNewTab()
{
Core::instance()->main_window()->FolderOpen(project(), context_menu_items_.first(), false);
Core::instance()->main_window()->FolderOpen(project(), static_cast<Folder*>(context_menu_items_.first()), false);
}
void ProjectExplorer::OpenContextMenuItemInNewWindow()
{
Core::instance()->main_window()->FolderOpen(project(), context_menu_items_.first(), true);
Core::instance()->main_window()->FolderOpen(project(), static_cast<Folder*>(context_menu_items_.first()), true);
}
void ProjectExplorer::ContextMenuStartProxy(QAction *a)
{
QVector<VideoStream*> video_streams;
Sequence* sequence = Node::ValueToPtr<Sequence>(a->data());
// To get here, the `context_menu_items_` must be all kFootage
foreach (Item* i, context_menu_items_) {
Footage* f = static_cast<Footage*>(i);
VideoStream* s = static_cast<VideoStream*>(f->get_first_enabled_stream_of_type(Stream::kVideo));
if (s) {
video_streams.append(s);
QVector<int> video_streams = f->GetStreamIndexesOfType(Stream::kVideo);
foreach (int stream, video_streams) {
// Start a background task for proxying
PreCacheTask* proxy_task = new PreCacheTask(f, stream, sequence);
TaskManager::instance()->AddTask(proxy_task);
}
}
Sequence* sequence = Node::ValueToPtr<Sequence>(a->data());
// Start a background task for proxying
foreach (VideoStream* video_stream, video_streams) {
PreCacheTask* proxy_task = new PreCacheTask(video_stream, sequence);
TaskManager::instance()->AddTask(proxy_task);
}
}
Project *ProjectExplorer::project() const
@@ -456,7 +441,7 @@ QModelIndex ProjectExplorer::get_root_index() const
return tree_view_->rootIndex();
}
void ProjectExplorer::set_root(Item *item)
void ProjectExplorer::set_root(Folder *item)
{
QModelIndex index = sort_model_.mapFromSource(model_.CreateIndexFromItem(item));
@@ -505,10 +490,8 @@ Folder *ProjectExplorer::GetSelectedFolder() const
Item* sel_item = selected_items.at(i);
// If this item is not a folder, presumably it's parent is
if (!sel_item->CanHaveChildren()) {
if (!dynamic_cast<Folder*>(sel_item)) {
sel_item = sel_item->item_parent();
Q_ASSERT(sel_item->CanHaveChildren());
}
if (folder == nullptr) {
@@ -545,28 +528,6 @@ void ProjectExplorer::DeselectAll()
CurrentView()->selectionModel()->clearSelection();
}
QVector<MediaInput *> ProjectExplorer::GetMediaNodesUsingFootage(Footage *item)
{
QVector<MediaInput *> list;
// Get all sequences.
QVector<Item*> sequences = model_.project()->get_items_of_type(Item::kSequence);
// Footage can contain multiple streams, all of which need to be dealt with
foreach (Item* s, sequences) {
const QVector<Node*>& nodes = static_cast<Sequence*>(s)->nodes();
foreach (Node* n, nodes) {
MediaInput* media_node = dynamic_cast<MediaInput*>(n);
if (media_node && media_node->stream()->footage() == item) {
list.append(media_node);
}
}
}
return list;
}
void ProjectExplorer::DeleteSelected()
{
QVector<Item*> selected = SelectedItems();
@@ -577,110 +538,78 @@ void ProjectExplorer::DeleteSelected()
MultiUndoCommand* command = new MultiUndoCommand();
bool dont_confirm_footage_in_use = false;
foreach (Item* item, selected) {
// 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);
Footage* footage_cast_test = dynamic_cast<Footage*>(item);
Sequence* sequence_cast_test = dynamic_cast<Sequence*>(item);
if (Core::instance()->main_window()->IsSequenceOpen(s)) {
Core::instance()->main_window()->CloseSequence(s);
bool cleared_to_delete = true;
if (sequence_cast_test) {
if (Core::instance()->main_window()->IsSequenceOpen(sequence_cast_test)) {
Core::instance()->main_window()->CloseSequence(sequence_cast_test);
}
break;
}
case Item::kFootage:
{
// If this is footage, check if it's used anywhere in any sequence
Footage* footage = static_cast<Footage*>(item);
} else if (footage_cast_test) {
if (!footage_cast_test->output_connections().empty() && !dont_confirm_footage_in_use) {
// Footage outputs to other nodes, warn the user
QMessageBox msgbox(this);
msgbox.setWindowTitle(tr("Confirm Footage Deletion"));
msgbox.setIcon(QMessageBox::Warning);
QVector<MediaInput*> footage_nodes = GetMediaNodesUsingFootage(footage);
QStringList connected_nodes;
foreach (const Node::OutputConnection& c, footage_cast_test->output_connections()) {
Node* connected = c.second.node();
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 (connected->GetLabel().isEmpty()) {
connected_nodes.append(connected->Name());
} else {
connected_nodes.append(QStringLiteral("%1 (%2)").arg(connected->GetLabel(), connected->Name()));
}
}
QString sequence_list_str;
foreach (Sequence* s, used_in_sequences) {
sequence_list_str.append(QStringLiteral("%1\n").arg(s->name()));
}
msgbox.setText(tr("The footage \"%1\" is currently connected to the following nodes:\n\n"
"%2\n\n"
"Are you sure you wish to delete this footage?")
.arg(footage_cast_test->filename(), connected_nodes.join('\n')));
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);
// 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::Yes);
msgbox.addButton(QMessageBox::YesToAll);
msgbox.addButton(QMessageBox::No);
msgbox.addButton(QMessageBox::Cancel);
// Run messagebox
msgbox.exec();
int r = 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
command->add_child(new OfflineFootageCommand(footage_nodes));
}
if (msgbox.clickedButton() == delete_clip_btn) {
// Delete any blocks that use this footage
QVector<Block*> blocks_to_remove;
foreach (Sequence* s, used_in_sequences) {
foreach (Track* track, s->viewer_output()->GetTracks()) {
foreach (Block* b, track->Blocks()) {
QVector<Node*> deps = b->GetDependencies();
foreach (MediaInput* i, footage_nodes) {
if (deps.contains(i)) {
blocks_to_remove.append(b);
break;
}
}
}
}
}
TimelineWidget::ReplaceBlocksWithGaps(blocks_to_remove, true, command);
} else if (msgbox.clickedButton() != offline_btn) {
// Must have cancelled
switch (r) {
case QMessageBox::Cancel:
// Stop this entire function
delete command;
return;
case QMessageBox::No:
cleared_to_delete = false;
break;
case QMessageBox::YesToAll:
dont_confirm_footage_in_use = true;
break;
}
}
// 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);
if (cleared_to_delete) {
// Close footage if currently open in footage panel
FootageViewerPanel* footage_panel = PanelManager::instance()->GetPanelsOfType<FootageViewerPanel>().first();
if (footage_panel->GetSelectedFootage().contains(footage_cast_test)) {
footage_panel->SetFootage(nullptr);
}
}
break;
}
case Item::kFolder:
// Do nothing
break;
}
command->add_child(new ProjectViewModel::RemoveItemCommand(&model_, item));
if (cleared_to_delete) {
command->add_child(new NodeRemoveAndDisconnectCommand(reinterpret_cast<Node*>(item)));
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);