From a6971b3fde5c69ff05aac57e41fa2ca818364c94 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 6 Jul 2019 09:46:55 -0500 Subject: [PATCH] more work on project management --- app/core.cpp | 41 ++++++- app/core.h | 16 ++- app/decoder/ffmpeg/ffmpegdecoder.cpp | 4 - app/panel/project/project.cpp | 7 +- app/panel/project/project.h | 3 + app/project/project.cpp | 18 --- app/project/projectviewmodel.cpp | 62 ++++++++-- app/project/projectviewmodel.h | 108 +++++++++++------- app/task/import/import.cpp | 50 ++++---- app/task/import/import.h | 6 +- app/task/task.cpp | 2 + app/task/task.h | 5 + .../projectexplorer/projectexplorer.cpp | 64 ++++++++--- app/widget/projectexplorer/projectexplorer.h | 16 +++ .../projectexplorerlistviewbase.h | 2 +- 15 files changed, 290 insertions(+), 114 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index 3240d712f..6f47c7d17 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "panel/panelfocusmanager.h" #include "panel/project/project.h" @@ -34,7 +35,9 @@ #include "task/taskmanager.h" #include "ui/icons/icons.h" #include "ui/style/style.h" +#include "undo/undostack.h" #include "widget/menu/menushared.h" +#include "widget/taskview/taskviewitem.h" Core olive::core; @@ -100,14 +103,14 @@ olive::MainWindow *Core::main_window() return main_window_; } -void Core::ImportFiles(const QStringList &urls, Folder* parent) +void Core::ImportFiles(const QStringList &urls, ProjectViewModel* model, Folder* parent) { if (urls.isEmpty()) { QMessageBox::critical(main_window_, tr("Import error"), tr("Nothing to import")); return; } - olive::task_manager.AddTask(new ImportTask(parent, urls)); + olive::task_manager.AddTask(new ImportTask(model, parent, urls)); } const olive::tool::Tool &Core::tool() @@ -115,6 +118,26 @@ const olive::tool::Tool &Core::tool() return tool_; } +void Core::StartModalTask(Task *t) +{ + QDialog dialog(main_window_); + + QHBoxLayout* layout = new QHBoxLayout(&dialog); + layout->setMargin(0); + + TaskViewItem* task_view = new TaskViewItem(&dialog); + task_view->SetTask(t); + layout->addWidget(task_view); + + connect(t, SIGNAL(Finished()), &dialog, SLOT(accept())); + + // FIXME: Risk of task finishing before dialog execs? + + if (t->Start()) { + dialog.exec(); + } +} + void Core::SetTool(const olive::tool::Tool &tool) { tool_ = tool; @@ -144,7 +167,7 @@ void Core::StartImportFootage() // Get the selected folder in this panel Folder* folder = active_project_panel->GetSelectedFolder(); - ImportFiles(files, folder); + ImportFiles(files, active_project_panel->model(), folder); } } @@ -163,11 +186,21 @@ void Core::CreateNewFolder() // Get the selected folder in this panel Folder* folder = active_project_panel->GetSelectedFolder(); + // Create new folder Folder* new_folder = new Folder(); + // Set a default name new_folder->set_name(tr("New Folder")); - active_project_panel->model()->AddChild(folder, new_folder); + // Create an undoable command + ProjectViewModel::AddItemCommand* aic = new ProjectViewModel::AddItemCommand(active_project_panel->model(), + folder, + new_folder); + + olive::undo_stack.push(aic); + + // Trigger an automatic rename so users can enter the folder name + active_project_panel->Edit(new_folder); } void Core::AddOpenProject(ProjectPtr p) diff --git a/app/core.h b/app/core.h index 4f4a5caa6..132a2be53 100644 --- a/app/core.h +++ b/app/core.h @@ -24,7 +24,9 @@ #include #include "project/project.h" +#include "project/projectviewmodel.h" #include "window/mainwindow/mainwindow.h" +#include "task/task.h" #include "tool/tool.h" /** @@ -73,15 +75,27 @@ public: /** * @brief Import a list of files * + * FIXME: I kind of hate this, it needs a model to update correctly. Is there a way that Items can signal enough to + * make passing references to the model unnecessary? + * * @param urls */ - void ImportFiles(const QStringList& urls, Folder *parent); + void ImportFiles(const QStringList& urls, ProjectViewModel *model, Folder *parent); /** * @brief Get the currently active tool */ const olive::tool::Tool& tool(); + /** + * @brief Starts a modal task + * + * This function does NOT take ownership of the Task. + * + * @param t + */ + void StartModalTask(Task* t); + public slots: /** * @brief Set the current application-wide tool diff --git a/app/decoder/ffmpeg/ffmpegdecoder.cpp b/app/decoder/ffmpeg/ffmpegdecoder.cpp index bf44b9d52..a597ec07f 100644 --- a/app/decoder/ffmpeg/ffmpegdecoder.cpp +++ b/app/decoder/ffmpeg/ffmpegdecoder.cpp @@ -186,8 +186,6 @@ bool FFmpegDecoder::Probe(Footage *f) str = video_stream; - qDebug() << "Stream V" << i << "Len" << (rational(avstream_->duration) * rational(avstream_->time_base)).ToDouble(); - } else if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // Create an audio stream object @@ -199,8 +197,6 @@ bool FFmpegDecoder::Probe(Footage *f) str = audio_stream; - qDebug() << "Stream A" << i << "Len" << (rational(avstream_->duration) * rational(avstream_->time_base)).ToDouble(); - } else { // This is data we can't utilize at the moment, but we make a Stream object anyway to keep parity with the file diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp index f75bb8d1e..7203d92e6 100644 --- a/app/panel/project/project.cpp +++ b/app/panel/project/project.cpp @@ -32,7 +32,7 @@ ProjectPanel::ProjectPanel(QWidget *parent) : QWidget* central_widget = new QWidget(this); QVBoxLayout* layout = new QVBoxLayout(central_widget); layout->setMargin(0); - layout->setSpacing(0); + //layout->setSpacing(0); setWidget(central_widget); // Set up project toolbar @@ -84,6 +84,11 @@ ProjectViewModel *ProjectPanel::model() return explorer_->model(); } +void ProjectPanel::Edit(Item* item) +{ + explorer_->Edit(item); +} + void ProjectPanel::changeEvent(QEvent *e) { if (e->type() == QEvent::LanguageChange) { diff --git a/app/panel/project/project.h b/app/panel/project/project.h index c99dc4151..5527162e2 100644 --- a/app/panel/project/project.h +++ b/app/panel/project/project.h @@ -40,6 +40,9 @@ public: ProjectViewModel* model(); +public slots: + void Edit(Item *item); + protected: virtual void changeEvent(QEvent* e) override; diff --git a/app/project/project.cpp b/app/project/project.cpp index e3caa36bc..402983cac 100644 --- a/app/project/project.cpp +++ b/app/project/project.cpp @@ -23,24 +23,6 @@ Project::Project() { name_ = tr("(untitled)"); - - // FIXME: Test code - Folder* f1 = new Folder(); - f1->set_name("F1"); - f1->set_parent(&root_); - Folder* f2 = new Folder(); - f2->set_name("F2"); - f2->set_parent(f1); - Folder* f3 = new Folder(); - f3->set_name("F3"); - f3->set_parent(&root_); - Folder* f4 = new Folder(); - f4->set_name("F4"); - f4->set_parent(f1); - Folder* f5 = new Folder(); - f5->set_name("F5"); - f5->set_parent(f3); - // End Test code } Folder *Project::root() diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index d46425162..55bfed70e 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -370,7 +370,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action } // Trigger an import - olive::core.ImportFiles(urls, static_cast(drop_item)); + olive::core.ImportFiles(urls, this, static_cast(drop_item)); } return false; @@ -391,6 +391,32 @@ void ProjectViewModel::AddChild(Item *parent, Item *child) endInsertRows(); } +void ProjectViewModel::RemoveChild(Item *parent, Item *child) +{ + QModelIndex parent_index; + + if (parent != project_->root()) { + parent_index = CreateIndexFromItem(parent); + } + + int child_row = IndexOfChild(child); + + beginRemoveRows(parent_index, child_row, child_row); + + parent->remove_child(child); + + endRemoveRows(); +} + +void ProjectViewModel::RenameChild(Item *item, const QString &name) +{ + item->set_name(name); + + QModelIndex index = CreateIndexFromItem(item, columns_.indexOf(kName)); + + emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); +} + int ProjectViewModel::IndexOfChild(Item *item) const { // Find parent's index within its own parent @@ -498,19 +524,41 @@ ProjectViewModel::RenameItemCommand::RenameItemCommand(ProjectViewModel* model, void ProjectViewModel::RenameItemCommand::redo() { - set_name(new_name_); + model_->RenameChild(item_, new_name_); } void ProjectViewModel::RenameItemCommand::undo() { - set_name(old_name_); + model_->RenameChild(item_, old_name_); } -void ProjectViewModel::RenameItemCommand::set_name(const QString &n) +ProjectViewModel::AddItemCommand::AddItemCommand(ProjectViewModel* model, Item* folder, Item* child, QUndoCommand* parent) : + QUndoCommand(parent), + model_(model), + parent_(folder), + child_(child), + done_(false) { - item_->set_name(n); +} - QModelIndex index = model_->CreateIndexFromItem(item_, model_->columns_.indexOf(kName)); +ProjectViewModel::AddItemCommand::~AddItemCommand() +{ + // FIXME Use smart pointers. I hate having to do this. + if (!done_) { + delete child_; + } +} - emit model_->dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); +void ProjectViewModel::AddItemCommand::redo() +{ + model_->AddChild(parent_, child_); + + done_ = true; +} + +void ProjectViewModel::AddItemCommand::undo() +{ + model_->RemoveChild(parent_, child_); + + done_ = false; } diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index 06e4008f9..2900d070b 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -94,6 +94,70 @@ public: /** Other model functions */ void AddChild(Item* parent, Item* child); + void RemoveChild(Item* parent, Item* child); + void RenameChild(Item* item, const QString& name); + + /** + * @brief Convenience function for creating QModelIndexes from an Item object + */ + QModelIndex CreateIndexFromItem(Item* item, int column = 0); + + /** + * @brief A QUndoCommand for moving an item from one folder to another folder + */ + class MoveItemCommand : public QUndoCommand { + public: + MoveItemCommand(ProjectViewModel* model, Item* item, Folder* destination, QUndoCommand* parent = nullptr); + + virtual void redo() override; + + virtual void undo() override; + + private: + ProjectViewModel* model_; + Item* item_; + Folder* source_; + Folder* destination_; + + }; + + /** + * @brief A QUndoCommand for renaming an item + */ + class RenameItemCommand : public QUndoCommand { + public: + RenameItemCommand(ProjectViewModel* model, Item* item, const QString& name, QUndoCommand* parent = nullptr); + + virtual void redo() override; + + virtual void undo() override; + + private: + ProjectViewModel* model_; + Item* item_; + QString old_name_; + QString new_name_; + }; + + /** + * @brief A QUndoCommand for adding an item + */ + class AddItemCommand : public QUndoCommand { + public: + AddItemCommand(ProjectViewModel* model, Item* folder, Item* child, QUndoCommand* parent = nullptr); + + virtual ~AddItemCommand() override; + + virtual void redo() override; + + virtual void undo() override; + + private: + ProjectViewModel* model_; + Item* parent_; + Item* child_; + bool done_; + }; private: /** * @brief Retrieve the index of `item` in its parent @@ -144,53 +208,9 @@ private: */ void MoveItemInternal(Item* item, Item* destination); - /** - * @brief Convenience function for creating QModelIndexes from an Item object - */ - QModelIndex CreateIndexFromItem(Item* item, int column = 0); - Project* project_; QVector columns_; - - /** - * @brief A QUndoCommand for moving an item from one folder to another folder - */ - class MoveItemCommand : public QUndoCommand { - public: - MoveItemCommand(ProjectViewModel* model, Item* item, Folder* destination, QUndoCommand* parent = nullptr); - - virtual void redo() override; - - virtual void undo() override; - - private: - ProjectViewModel* model_; - Item* item_; - Folder* source_; - Folder* destination_; - - }; - - /** - * @brief A QUndoCommand for renaming an item - */ - class RenameItemCommand : public QUndoCommand { - public: - RenameItemCommand(ProjectViewModel* model, Item* item, const QString& name, QUndoCommand* parent = nullptr); - - virtual void redo() override; - - virtual void undo() override; - - private: - void set_name(const QString& n); - - ProjectViewModel* model_; - Item* item_; - QString old_name_; - QString new_name_; - }; }; #endif // VIEWMODEL_H diff --git a/app/task/import/import.cpp b/app/task/import/import.cpp index 332b893d6..f579c5a89 100644 --- a/app/task/import/import.cpp +++ b/app/task/import/import.cpp @@ -1,3 +1,4 @@ + /*** Olive - Non-Linear Video Editor @@ -33,8 +34,10 @@ #include "project/item/footage/footage.h" #include "task/probe/probe.h" #include "task/taskmanager.h" +#include "undo/undostack.h" -ImportTask::ImportTask(Folder *parent, const QStringList &urls) : +ImportTask::ImportTask(ProjectViewModel *model, Folder *parent, const QStringList &urls) : + model_(model), urls_(urls), parent_(parent) { @@ -43,9 +46,20 @@ ImportTask::ImportTask(Folder *parent, const QStringList &urls) : bool ImportTask::Action() { - for (int i=0;iset_name(file_info.fileName()); - parent_->add_child(f); + // Create undoable command that adds the items to the model + new ProjectViewModel::AddItemCommand(model_, + folder, + f, + parent_command); // Convert QFileInfoList into QStringList QStringList full_urls; @@ -76,12 +94,8 @@ bool ImportTask::Action() } } - // Start a new import task for this folder too - ImportTask* it = new ImportTask(f, full_urls); - - it->moveToThread(qApp->thread()); - - olive::task_manager.AddTask(it); + // Recursively follow this path + Import(full_urls, f, parent_command); } } else { @@ -95,7 +109,11 @@ bool ImportTask::Action() f->set_name(file_info.fileName()); f->set_timestamp(file_info.lastModified()); - parent_->add_child(f); + // Create undoable command that adds the items to the model + new ProjectViewModel::AddItemCommand(model_, + folder, + f, + parent_command); // Create ProbeTask to analyze this media ProbeTask* pt = new ProbeTask(f); @@ -109,15 +127,7 @@ bool ImportTask::Action() } - emit ProgressChanged(i * 100 / urls_.size()); + emit ProgressChanged(i * 100 / files.size()); } - - // FIXME: No good very bad test code to update the ProjectExplorer model/view - // A better solution would be for the Project itself to signal that a new row was being added - ProjectPanel* p = olive::panel_focus_manager->MostRecentlyFocused(); - p->set_project(p->project()); - // End test code - - return true; } diff --git a/app/task/import/import.h b/app/task/import/import.h index 1d294841c..ce33daf1e 100644 --- a/app/task/import/import.h +++ b/app/task/import/import.h @@ -21,6 +21,7 @@ #ifndef IMPORT_H #define IMPORT_H +#include "project/projectviewmodel.h" #include "project/item/folder/folder.h" #include "task/task.h" @@ -36,11 +37,14 @@ class ImportTask : public Task { Q_OBJECT public: - ImportTask(Folder *parent_, const QStringList& urls); + ImportTask(ProjectViewModel* model, Folder *parent, const QStringList& urls); virtual bool Action() override; private: + void Import(const QStringList& files, Folder* folder, QUndoCommand* parent_command); + + ProjectViewModel* model_; QStringList urls_; Folder* parent_; }; diff --git a/app/task/task.cpp b/app/task/task.cpp index 34e3f17f6..c0cde65a6 100644 --- a/app/task/task.cpp +++ b/app/task/task.cpp @@ -116,4 +116,6 @@ void Task::ThreadComplete() } else { set_status(kError); } + + emit Finished(); } diff --git a/app/task/task.h b/app/task/task.h index d26e3637f..501a9c37d 100644 --- a/app/task/task.h +++ b/app/task/task.h @@ -176,6 +176,11 @@ signals: */ void ProgressChanged(int p); + /** + * @brief Signal emitted when the Task finishes whether it succeeded or failed + */ + void Finished(); + private: /** * @brief Set the status of this Task (also emits StatusChanged()) diff --git a/app/widget/projectexplorer/projectexplorer.cpp b/app/widget/projectexplorer/projectexplorer.cpp index 1cb9089f9..a6df4bf13 100644 --- a/app/widget/projectexplorer/projectexplorer.cpp +++ b/app/widget/projectexplorer/projectexplorer.cpp @@ -62,6 +62,10 @@ ProjectExplorer::ProjectExplorer(QWidget *parent) : // Set default icon size SizeChangedSlot(olive::kProjectIconSizeDefault); + + // Set rename timer timeout + rename_timer_.setInterval(500); + connect(&rename_timer_, SIGNAL(timeout()), this, SLOT(RenameTimerSlot())); } const olive::ProjectViewType &ProjectExplorer::view_type() @@ -90,15 +94,25 @@ void ProjectExplorer::set_view_type(olive::ProjectViewType type) } } +void ProjectExplorer::Edit(Item *item) +{ + CurrentView()->edit(model_.CreateIndexFromItem(item)); +} + void ProjectExplorer::AddView(QAbstractItemView *view) { view->setModel(&model_); + view->setEditTriggers(QAbstractItemView::NoEditTriggers); connect(view, SIGNAL(DoubleClickedView(const QModelIndex&)), this, SLOT(DoubleClickViewSlot(const QModelIndex&))); + connect(view, SIGNAL(clicked(const QModelIndex&)), this, SLOT(ItemClickedSlot(const QModelIndex&))); stacked_widget_->addWidget(view); } void ProjectExplorer::BrowseToFolder(const QModelIndex &index) { + // Make sure any rename timers are stopped + rename_timer_.stop(); + // Set appropriate views to this index icon_view_->setRootIndex(index); list_view_->setRootIndex(index); @@ -116,6 +130,30 @@ void ProjectExplorer::BrowseToFolder(const QModelIndex &index) nav_bar_->set_dir_up_enabled(index.isValid()); } +QAbstractItemView *ProjectExplorer::CurrentView() +{ + return static_cast(stacked_widget_->currentWidget()); +} + +void ProjectExplorer::ItemClickedSlot(const QModelIndex &index) +{ + if (index.isValid()) { + if (clicked_index_ == index) { + // The item has been clicked more than once, start a timer for renaming + rename_timer_.start(); + } else { + // Cache this index for the next click + clicked_index_ = index; + + // If the rename timer had started, stop it now + rename_timer_.stop(); + } + } else { + // Stop the rename timer + rename_timer_.stop(); + } +} + void ProjectExplorer::DoubleClickViewSlot(const QModelIndex &index) { if (index.isValid()) { @@ -160,6 +198,18 @@ void ProjectExplorer::DirUpSlot() } } +void ProjectExplorer::RenameTimerSlot() +{ + // Start editing this index + CurrentView()->edit(clicked_index_); + + // Reset clicked index state + clicked_index_ = QModelIndex(); + + // Stop rename timer + rename_timer_.stop(); +} + Project *ProjectExplorer::project() { return model_.project(); @@ -172,20 +222,8 @@ void ProjectExplorer::set_project(Project *p) QList ProjectExplorer::SelectedItems() { - QModelIndexList index_list; - // Determine which view is active and get its selected indexes - switch (view_type_) { - case olive::TreeView: - index_list = tree_view_->selectionModel()->selectedRows(); - break; - case olive::ListView: - index_list = list_view_->selectionModel()->selectedRows(); - break; - case olive::IconView: - index_list = icon_view_->selectionModel()->selectedRows(); - break; - } + QModelIndexList index_list = CurrentView()->selectionModel()->selectedRows(); // Convert indexes to item objects QList selected_items; diff --git a/app/widget/projectexplorer/projectexplorer.h b/app/widget/projectexplorer/projectexplorer.h index 5f9b749ad..b62207171 100644 --- a/app/widget/projectexplorer/projectexplorer.h +++ b/app/widget/projectexplorer/projectexplorer.h @@ -22,6 +22,7 @@ #define PROJECTEXPLORER_H #include +#include #include #include "project/project.h" @@ -77,6 +78,8 @@ public: public slots: void set_view_type(olive::ProjectViewType type); + void Edit(Item* item); + signals: /** * @brief Emitted when an Item is double clicked @@ -110,6 +113,11 @@ private: */ void BrowseToFolder(const QModelIndex& index); + /** + * @brief Get the currently active QAbstractItemView + */ + QAbstractItemView* CurrentView(); + QStackedWidget* stacked_widget_; ProjectExplorerNavigation* nav_bar_; @@ -122,12 +130,20 @@ private: ProjectViewModel model_; + QModelIndex clicked_index_; + + QTimer rename_timer_; + private slots: + void ItemClickedSlot(const QModelIndex& index); + void DoubleClickViewSlot(const QModelIndex& index); void SizeChangedSlot(int s); void DirUpSlot(); + + void RenameTimerSlot(); }; #endif // PROJECTEXPLORER_H diff --git a/app/widget/projectexplorer/projectexplorerlistviewbase.h b/app/widget/projectexplorer/projectexplorerlistviewbase.h index e589ddbe0..ffbbce84f 100644 --- a/app/widget/projectexplorer/projectexplorerlistviewbase.h +++ b/app/widget/projectexplorer/projectexplorerlistviewbase.h @@ -29,7 +29,7 @@ class ProjectExplorerListViewBase : public QListView public: ProjectExplorerListViewBase(QWidget* parent); -protected: +protected: /** * @brief Double click event override *