more work on project management

This commit is contained in:
itsmattkc
2019-07-06 09:46:55 -05:00
parent bc631c495d
commit a6971b3fde
15 changed files with 290 additions and 114 deletions
+37 -4
View File
@@ -26,6 +26,7 @@
#include <QFileDialog> #include <QFileDialog>
#include <QFileInfo> #include <QFileInfo>
#include <QMessageBox> #include <QMessageBox>
#include <QHBoxLayout>
#include "panel/panelfocusmanager.h" #include "panel/panelfocusmanager.h"
#include "panel/project/project.h" #include "panel/project/project.h"
@@ -34,7 +35,9 @@
#include "task/taskmanager.h" #include "task/taskmanager.h"
#include "ui/icons/icons.h" #include "ui/icons/icons.h"
#include "ui/style/style.h" #include "ui/style/style.h"
#include "undo/undostack.h"
#include "widget/menu/menushared.h" #include "widget/menu/menushared.h"
#include "widget/taskview/taskviewitem.h"
Core olive::core; Core olive::core;
@@ -100,14 +103,14 @@ olive::MainWindow *Core::main_window()
return 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()) { if (urls.isEmpty()) {
QMessageBox::critical(main_window_, tr("Import error"), tr("Nothing to import")); QMessageBox::critical(main_window_, tr("Import error"), tr("Nothing to import"));
return; return;
} }
olive::task_manager.AddTask(new ImportTask(parent, urls)); olive::task_manager.AddTask(new ImportTask(model, parent, urls));
} }
const olive::tool::Tool &Core::tool() const olive::tool::Tool &Core::tool()
@@ -115,6 +118,26 @@ const olive::tool::Tool &Core::tool()
return 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) void Core::SetTool(const olive::tool::Tool &tool)
{ {
tool_ = tool; tool_ = tool;
@@ -144,7 +167,7 @@ void Core::StartImportFootage()
// Get the selected folder in this panel // Get the selected folder in this panel
Folder* folder = active_project_panel->GetSelectedFolder(); 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 // Get the selected folder in this panel
Folder* folder = active_project_panel->GetSelectedFolder(); Folder* folder = active_project_panel->GetSelectedFolder();
// Create new folder
Folder* new_folder = new Folder(); Folder* new_folder = new Folder();
// Set a default name
new_folder->set_name(tr("New Folder")); 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) void Core::AddOpenProject(ProjectPtr p)
+15 -1
View File
@@ -24,7 +24,9 @@
#include <QList> #include <QList>
#include "project/project.h" #include "project/project.h"
#include "project/projectviewmodel.h"
#include "window/mainwindow/mainwindow.h" #include "window/mainwindow/mainwindow.h"
#include "task/task.h"
#include "tool/tool.h" #include "tool/tool.h"
/** /**
@@ -73,15 +75,27 @@ public:
/** /**
* @brief Import a list of files * @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 * @param urls
*/ */
void ImportFiles(const QStringList& urls, Folder *parent); void ImportFiles(const QStringList& urls, ProjectViewModel *model, Folder *parent);
/** /**
* @brief Get the currently active tool * @brief Get the currently active tool
*/ */
const olive::tool::Tool& 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: public slots:
/** /**
* @brief Set the current application-wide tool * @brief Set the current application-wide tool
-4
View File
@@ -186,8 +186,6 @@ bool FFmpegDecoder::Probe(Footage *f)
str = video_stream; 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) { } else if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
// Create an audio stream object // Create an audio stream object
@@ -199,8 +197,6 @@ bool FFmpegDecoder::Probe(Footage *f)
str = audio_stream; str = audio_stream;
qDebug() << "Stream A" << i << "Len" << (rational(avstream_->duration) * rational(avstream_->time_base)).ToDouble();
} else { } else {
// This is data we can't utilize at the moment, but we make a Stream object anyway to keep parity with the file // This is data we can't utilize at the moment, but we make a Stream object anyway to keep parity with the file
+6 -1
View File
@@ -32,7 +32,7 @@ ProjectPanel::ProjectPanel(QWidget *parent) :
QWidget* central_widget = new QWidget(this); QWidget* central_widget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(central_widget); QVBoxLayout* layout = new QVBoxLayout(central_widget);
layout->setMargin(0); layout->setMargin(0);
layout->setSpacing(0); //layout->setSpacing(0);
setWidget(central_widget); setWidget(central_widget);
// Set up project toolbar // Set up project toolbar
@@ -84,6 +84,11 @@ ProjectViewModel *ProjectPanel::model()
return explorer_->model(); return explorer_->model();
} }
void ProjectPanel::Edit(Item* item)
{
explorer_->Edit(item);
}
void ProjectPanel::changeEvent(QEvent *e) void ProjectPanel::changeEvent(QEvent *e)
{ {
if (e->type() == QEvent::LanguageChange) { if (e->type() == QEvent::LanguageChange) {
+3
View File
@@ -40,6 +40,9 @@ public:
ProjectViewModel* model(); ProjectViewModel* model();
public slots:
void Edit(Item *item);
protected: protected:
virtual void changeEvent(QEvent* e) override; virtual void changeEvent(QEvent* e) override;
-18
View File
@@ -23,24 +23,6 @@
Project::Project() Project::Project()
{ {
name_ = tr("(untitled)"); 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() Folder *Project::root()
+55 -7
View File
@@ -370,7 +370,7 @@ bool ProjectViewModel::dropMimeData(const QMimeData *data, Qt::DropAction action
} }
// Trigger an import // Trigger an import
olive::core.ImportFiles(urls, static_cast<Folder*>(drop_item)); olive::core.ImportFiles(urls, this, static_cast<Folder*>(drop_item));
} }
return false; return false;
@@ -391,6 +391,32 @@ void ProjectViewModel::AddChild(Item *parent, Item *child)
endInsertRows(); 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 int ProjectViewModel::IndexOfChild(Item *item) const
{ {
// Find parent's index within its own parent // Find parent's index within its own parent
@@ -498,19 +524,41 @@ ProjectViewModel::RenameItemCommand::RenameItemCommand(ProjectViewModel* model,
void ProjectViewModel::RenameItemCommand::redo() void ProjectViewModel::RenameItemCommand::redo()
{ {
set_name(new_name_); model_->RenameChild(item_, new_name_);
} }
void ProjectViewModel::RenameItemCommand::undo() 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;
} }
+64 -44
View File
@@ -94,6 +94,70 @@ public:
/** Other model functions */ /** Other model functions */
void AddChild(Item* parent, Item* child); 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: private:
/** /**
* @brief Retrieve the index of `item` in its parent * @brief Retrieve the index of `item` in its parent
@@ -144,53 +208,9 @@ private:
*/ */
void MoveItemInternal(Item* item, Item* destination); 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_; Project* project_;
QVector<ColumnType> columns_; QVector<ColumnType> 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 #endif // VIEWMODEL_H
+30 -20
View File
@@ -1,3 +1,4 @@
/*** /***
Olive - Non-Linear Video Editor Olive - Non-Linear Video Editor
@@ -33,8 +34,10 @@
#include "project/item/footage/footage.h" #include "project/item/footage/footage.h"
#include "task/probe/probe.h" #include "task/probe/probe.h"
#include "task/taskmanager.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), urls_(urls),
parent_(parent) parent_(parent)
{ {
@@ -43,9 +46,20 @@ ImportTask::ImportTask(Folder *parent, const QStringList &urls) :
bool ImportTask::Action() bool ImportTask::Action()
{ {
for (int i=0;i<urls_.size();i++) { QUndoCommand* command = new QUndoCommand();
const QString& url = urls_.at(i); Import(urls_, parent_, command);
olive::undo_stack.push(command);
return true;
}
void ImportTask::Import(const QStringList &files, Folder *folder, QUndoCommand *parent_command)
{
for (int i=0;i<files.size();i++) {
const QString& url = files.at(i);
QFileInfo file_info(url); QFileInfo file_info(url);
@@ -65,7 +79,11 @@ bool ImportTask::Action()
f->set_name(file_info.fileName()); f->set_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 // Convert QFileInfoList into QStringList
QStringList full_urls; QStringList full_urls;
@@ -76,12 +94,8 @@ bool ImportTask::Action()
} }
} }
// Start a new import task for this folder too // Recursively follow this path
ImportTask* it = new ImportTask(f, full_urls); Import(full_urls, f, parent_command);
it->moveToThread(qApp->thread());
olive::task_manager.AddTask(it);
} }
} else { } else {
@@ -95,7 +109,11 @@ bool ImportTask::Action()
f->set_name(file_info.fileName()); f->set_name(file_info.fileName());
f->set_timestamp(file_info.lastModified()); 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 // Create ProbeTask to analyze this media
ProbeTask* pt = new ProbeTask(f); 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<ProjectPanel>();
p->set_project(p->project());
// End test code
return true;
} }
+5 -1
View File
@@ -21,6 +21,7 @@
#ifndef IMPORT_H #ifndef IMPORT_H
#define IMPORT_H #define IMPORT_H
#include "project/projectviewmodel.h"
#include "project/item/folder/folder.h" #include "project/item/folder/folder.h"
#include "task/task.h" #include "task/task.h"
@@ -36,11 +37,14 @@ class ImportTask : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
ImportTask(Folder *parent_, const QStringList& urls); ImportTask(ProjectViewModel* model, Folder *parent, const QStringList& urls);
virtual bool Action() override; virtual bool Action() override;
private: private:
void Import(const QStringList& files, Folder* folder, QUndoCommand* parent_command);
ProjectViewModel* model_;
QStringList urls_; QStringList urls_;
Folder* parent_; Folder* parent_;
}; };
+2
View File
@@ -116,4 +116,6 @@ void Task::ThreadComplete()
} else { } else {
set_status(kError); set_status(kError);
} }
emit Finished();
} }
+5
View File
@@ -176,6 +176,11 @@ signals:
*/ */
void ProgressChanged(int p); void ProgressChanged(int p);
/**
* @brief Signal emitted when the Task finishes whether it succeeded or failed
*/
void Finished();
private: private:
/** /**
* @brief Set the status of this Task (also emits StatusChanged()) * @brief Set the status of this Task (also emits StatusChanged())
+51 -13
View File
@@ -62,6 +62,10 @@ ProjectExplorer::ProjectExplorer(QWidget *parent) :
// Set default icon size // Set default icon size
SizeChangedSlot(olive::kProjectIconSizeDefault); 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() 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) void ProjectExplorer::AddView(QAbstractItemView *view)
{ {
view->setModel(&model_); view->setModel(&model_);
view->setEditTriggers(QAbstractItemView::NoEditTriggers);
connect(view, SIGNAL(DoubleClickedView(const QModelIndex&)), this, SLOT(DoubleClickViewSlot(const QModelIndex&))); 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); stacked_widget_->addWidget(view);
} }
void ProjectExplorer::BrowseToFolder(const QModelIndex &index) void ProjectExplorer::BrowseToFolder(const QModelIndex &index)
{ {
// Make sure any rename timers are stopped
rename_timer_.stop();
// Set appropriate views to this index // Set appropriate views to this index
icon_view_->setRootIndex(index); icon_view_->setRootIndex(index);
list_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()); nav_bar_->set_dir_up_enabled(index.isValid());
} }
QAbstractItemView *ProjectExplorer::CurrentView()
{
return static_cast<QAbstractItemView*>(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) void ProjectExplorer::DoubleClickViewSlot(const QModelIndex &index)
{ {
if (index.isValid()) { 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() Project *ProjectExplorer::project()
{ {
return model_.project(); return model_.project();
@@ -172,20 +222,8 @@ void ProjectExplorer::set_project(Project *p)
QList<Item *> ProjectExplorer::SelectedItems() QList<Item *> ProjectExplorer::SelectedItems()
{ {
QModelIndexList index_list;
// Determine which view is active and get its selected indexes // Determine which view is active and get its selected indexes
switch (view_type_) { QModelIndexList index_list = CurrentView()->selectionModel()->selectedRows();
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;
}
// Convert indexes to item objects // Convert indexes to item objects
QList<Item*> selected_items; QList<Item*> selected_items;
@@ -22,6 +22,7 @@
#define PROJECTEXPLORER_H #define PROJECTEXPLORER_H
#include <QStackedWidget> #include <QStackedWidget>
#include <QTimer>
#include <QTreeView> #include <QTreeView>
#include "project/project.h" #include "project/project.h"
@@ -77,6 +78,8 @@ public:
public slots: public slots:
void set_view_type(olive::ProjectViewType type); void set_view_type(olive::ProjectViewType type);
void Edit(Item* item);
signals: signals:
/** /**
* @brief Emitted when an Item is double clicked * @brief Emitted when an Item is double clicked
@@ -110,6 +113,11 @@ private:
*/ */
void BrowseToFolder(const QModelIndex& index); void BrowseToFolder(const QModelIndex& index);
/**
* @brief Get the currently active QAbstractItemView
*/
QAbstractItemView* CurrentView();
QStackedWidget* stacked_widget_; QStackedWidget* stacked_widget_;
ProjectExplorerNavigation* nav_bar_; ProjectExplorerNavigation* nav_bar_;
@@ -122,12 +130,20 @@ private:
ProjectViewModel model_; ProjectViewModel model_;
QModelIndex clicked_index_;
QTimer rename_timer_;
private slots: private slots:
void ItemClickedSlot(const QModelIndex& index);
void DoubleClickViewSlot(const QModelIndex& index); void DoubleClickViewSlot(const QModelIndex& index);
void SizeChangedSlot(int s); void SizeChangedSlot(int s);
void DirUpSlot(); void DirUpSlot();
void RenameTimerSlot();
}; };
#endif // PROJECTEXPLORER_H #endif // PROJECTEXPLORER_H
@@ -29,7 +29,7 @@ class ProjectExplorerListViewBase : public QListView
public: public:
ProjectExplorerListViewBase(QWidget* parent); ProjectExplorerListViewBase(QWidget* parent);
protected: protected:
/** /**
* @brief Double click event override * @brief Double click event override
* *