From f73c41d3fa90f8fd2c03925aa2657a02832bb266 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 18 Apr 2020 01:50:47 +1000 Subject: [PATCH] various: implemented multiple project support This required various changes to various parts of the infrastructure (mostly using paths to finding the "root project" of any given object throughout). Now theoretically infinite projects can be opened and accounted for at any given time. --- app/core.cpp | 212 +++++++++++++----- app/core.h | 58 +++-- .../footageproperties/footageproperties.cpp | 10 + .../footageproperties/footageproperties.h | 4 + .../videostreamproperties.cpp | 10 + .../streamproperties/videostreamproperties.h | 4 + .../projectproperties/projectproperties.cpp | 4 +- .../projectproperties/projectproperties.h | 2 +- app/dialog/sequence/sequence.cpp | 5 + app/dialog/sequence/sequence.h | 2 + app/dialog/speedduration/speedduration.cpp | 5 + app/dialog/speedduration/speedduration.h | 2 + app/node/output/track/tracklist.h | 4 +- app/panel/project/project.cpp | 10 +- app/panel/timeline/timeline.cpp | 18 +- app/panel/timeline/timeline.h | 9 - app/project/item/item.cpp | 2 +- app/project/project.cpp | 32 ++- app/project/project.h | 14 ++ app/project/projectsavemanager.cpp | 1 + app/project/projectsavemanager.h | 3 + app/project/projectviewmodel.cpp | 20 ++ app/project/projectviewmodel.h | 8 + app/ui/style/style.cpp | 4 +- app/ui/style/style.h | 2 + app/undo/undocommand.cpp | 6 +- app/undo/undocommand.h | 3 + app/widget/keyframeview/keyframeviewundo.cpp | 14 ++ app/widget/keyframeview/keyframeviewundo.h | 4 + app/widget/menu/menu.cpp | 6 +- app/widget/menu/menushared.cpp | 7 +- app/widget/menu/menushared.h | 2 - .../nodeparamview/nodeparamviewundo.cpp | 33 +++ app/widget/nodeparamview/nodeparamviewundo.h | 12 + app/widget/nodeview/nodeviewundo.cpp | 33 ++- app/widget/nodeview/nodeviewundo.h | 14 +- app/widget/panel/panel.cpp | 29 ++- app/widget/panel/panel.h | 27 ++- app/widget/timebased/timebased.cpp | 13 +- app/widget/timebased/timebased.h | 2 + app/widget/timelinewidget/timelinewidget.cpp | 13 +- app/widget/timelinewidget/undo/undo.cpp | 132 +++++++++-- app/widget/timelinewidget/undo/undo.h | 56 ++++- app/widget/viewer/footageviewer.cpp | 5 + app/widget/viewer/footageviewer.h | 2 + app/window/mainwindow/mainmenu.cpp | 4 +- app/window/mainwindow/mainwindow.cpp | 171 +++++++++----- app/window/mainwindow/mainwindow.h | 15 +- 48 files changed, 824 insertions(+), 224 deletions(-) diff --git a/app/core.cpp b/app/core.cpp index b14e4d761..d18f421b5 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -67,8 +67,7 @@ Core::Core() : main_window_(nullptr), tool_(Tool::kPointer), addable_object_(Tool::kAddableEmpty), - snapping_(true), - queue_autorecovery_(false) + snapping_(true) { } @@ -157,7 +156,7 @@ void Core::Start() if (startup_project_.isEmpty()) { // If no load project is set, create a new one on open - AddOpenProject(std::make_shared()); + CreateNewProject(); } else { OpenProjectInternal(startup_project_); } @@ -252,6 +251,19 @@ void Core::ClearOpenRecentList() recent_projects_.clear(); } +void Core::CreateNewProject() +{ + // If we already have an empty/new project, switch to it + foreach (ProjectPtr already_open, open_projects_) { + if (already_open->is_new()) { + AddOpenProject(already_open); + return; + } + } + + AddOpenProject(std::make_shared()); +} + const bool &Core::snapping() const { return snapping_; @@ -316,7 +328,7 @@ void Core::DialogPreferencesShow() void Core::DialogProjectPropertiesShow() { - ProjectPropertiesDialog ppd(main_window_); + ProjectPropertiesDialog ppd(GetActiveProject(), main_window_); ppd.exec(); } @@ -406,6 +418,21 @@ void Core::CreateNewSequence() void Core::AddOpenProject(ProjectPtr p) { + // Ensure project is not open at the moment + foreach (ProjectPtr already_open, open_projects_) { + if (already_open == p) { + // Signal UI to switch to this project + emit ProjectOpened(p.get()); + return; + } + } + + // If we currently have an empty project, close it first + if (!open_projects_.isEmpty() && open_projects_.last()->is_new()) { + CloseProject(open_projects_.last().get(), false); + } + + connect(p.get(), &Project::ModifiedChanged, this, &Core::ProjectWasModified); open_projects_.append(p); PushRecentlyOpenedProject(p->filename()); @@ -433,6 +460,27 @@ bool Core::ConfirmImageSequence(const QString& filename) return (mb.exec() == QMessageBox::Yes); } +void Core::ProjectWasModified(bool e) +{ + //Project* p = static_cast(sender()); + + if (e) { + // If this project is modified, we know for sure the window should show a "modified" flag (the * in the titlebar) + main_window_->setWindowModified(true); + } else { + // If we just set this project to "not modified", see if all projects are not modified in which case we can hide + // the modified flag + foreach (ProjectPtr open, open_projects_) { + if (open->is_modified()) { + main_window_->setWindowModified(true); + return; + } + } + + main_window_->setWindowModified(false); + } +} + void Core::DeclareTypesForQt() { qRegisterMetaType(); @@ -490,6 +538,7 @@ void Core::StartGUI(bool full_screen) // When a new project is opened, update the mainwindow connect(this, &Core::ProjectOpened, main_window_, &MainWindow::ProjectOpen); + connect(this, &Core::ProjectClosed, main_window_, &MainWindow::ProjectClose); // Start autorecovery timer using the config value as its interval SetAutorecoveryInterval(Config::Current()["AutorecoveryInterval"].toInt()); @@ -501,7 +550,7 @@ void Core::SaveProjectInternal(Project *project) // Create save manager ProjectSaveManager* psm = new ProjectSaveManager(project); - connect(psm, &Task::Succeeded, this, &Core::ProjectSaveSucceeded); + connect(psm, &ProjectSaveManager::ProjectSaveSucceeded, this, &Core::ProjectSaveSucceeded); TaskDialog* task_dialog = new TaskDialog(psm, tr("Save Project"), main_window()); task_dialog->open(); @@ -509,21 +558,22 @@ void Core::SaveProjectInternal(Project *project) void Core::SaveAutorecovery() { - if (queue_autorecovery_) { - // FIXME: Save autorecovery of projects open - - queue_autorecovery_ = false; + foreach (ProjectPtr p, open_projects_) { + if (!p->has_autorecovery_been_saved()) { + // FIXME: SAVE AN AUTORECOVERY PROJECT + p->set_autorecovery_saved(true); + } } } -void Core::ProjectSaveSucceeded() +void Core::ProjectSaveSucceeded(Project* p) { - PushRecentlyOpenedProject(GetActiveProject()->filename()); + PushRecentlyOpenedProject(p->filename()); - SetProjectModified(false); + p->set_modified(false); } -Project *Core::GetActiveProject() +Project *Core::GetActiveProject() const { ProjectPanel* active_project_panel = PanelManager::instance()->MostRecentlyFocused(); @@ -534,7 +584,7 @@ Project *Core::GetActiveProject() } } -ProjectViewModel *Core::GetActiveProjectModel() +ProjectViewModel *Core::GetActiveProjectModel() const { ProjectPanel* active_project_panel = PanelManager::instance()->MostRecentlyFocused(); @@ -545,7 +595,7 @@ ProjectViewModel *Core::GetActiveProjectModel() } } -Folder *Core::GetSelectedFolderInActiveProject() +Folder *Core::GetSelectedFolderInActiveProject() const { ProjectPanel* active_project_panel = PanelManager::instance()->MostRecentlyFocused(); @@ -568,17 +618,6 @@ void Core::SetTimecodeDisplay(Timecode::Display d) emit TimecodeDisplayChanged(d); } -void Core::SetProjectModified(bool e) -{ - main_window()->setWindowModified(e); - queue_autorecovery_ = e; -} - -bool Core::IsProjectModified() const -{ - return main_window_->isWindowModified(); -} - void Core::SetAutorecoveryInterval(int minutes) { // Convert minutes to milliseconds @@ -599,38 +638,19 @@ bool Core::SaveActiveProject() { Project* active_project = GetActiveProject(); - if (!active_project) { - return false; + if (active_project) { + return SaveProject(active_project); } - if (active_project->filename().isEmpty()) { - return SaveActiveProjectAs(); - } else { - SaveProjectInternal(active_project); - - return true; - } + return false; } bool Core::SaveActiveProjectAs() { Project* active_project = GetActiveProject(); - if (!active_project) { - return false; - } - - QString fn = QFileDialog::getSaveFileName(main_window_, - tr("Save Project As"), - QString(), - GetProjectFilter()); - - if (!fn.isEmpty()) { - active_project->set_filename(fn); - - SaveProjectInternal(active_project); - - return true; + if (active_project) { + return SaveProjectAs(active_project); } return false; @@ -722,6 +742,35 @@ QString Core::GetRecentProjectsFilePath() return QDir(GetConfigurationLocation()).filePath(QStringLiteral("recent")); } +bool Core::SaveProject(Project *p) +{ + if (p->filename().isEmpty()) { + return SaveProjectAs(p); + } else { + SaveProjectInternal(p); + + return true; + } +} + +bool Core::SaveProjectAs(Project *p) +{ + QString fn = QFileDialog::getSaveFileName(main_window_, + tr("Save Project As"), + QString(), + GetProjectFilter()); + + if (!fn.isEmpty()) { + p->set_filename(fn); + + SaveProjectInternal(p); + + return true; + } + + return false; +} + void Core::PushRecentlyOpenedProject(const QString& s) { if (s.isEmpty()) { @@ -739,6 +788,15 @@ void Core::PushRecentlyOpenedProject(const QString& s) void Core::OpenProjectInternal(const QString &filename) { + // See if this project is open already + foreach (ProjectPtr p, open_projects_) { + if (p->filename() == filename) { + // This project is already open + AddOpenProject(p); + return; + } + } + ProjectLoadManager* plm = new ProjectLoadManager(filename); // We use a blocking queued connection here because we want to ensure we have this project instance before the @@ -818,6 +876,60 @@ void Core::OpenProjectFromRecentList(int index) } } +bool Core::CloseProject(Project *p, bool auto_open_new) +{ + for (int i=0;iis_modified()) { + QMessageBox mb(main_window_); + + mb.setIcon(QMessageBox::Question); + mb.setWindowTitle(tr("Unsaved Changes")); + mb.setText(tr("The project '%1' has unsaved changes. Would you like to save them?") + .arg(p->name())); + + QPushButton* yes_btn = mb.addButton(tr("Save"), QMessageBox::YesRole); + mb.addButton(tr("Don't Save"), QMessageBox::NoRole); + QPushButton* cancel_btn = mb.addButton(QMessageBox::Cancel); + + mb.exec(); + + if (mb.clickedButton() == cancel_btn + || (mb.clickedButton() == yes_btn && !SaveActiveProject())) { + // Don't close if the user clicked cancel on this messagebox OR they cancelled a save as operation + return false; + } + } + + disconnect(p, &Project::ModifiedChanged, this, &Core::ProjectWasModified); + emit ProjectClosed(p); + open_projects_.removeAt(i); + break; + } + } + + // Ensure a project is always active + if (auto_open_new && open_projects_.isEmpty()) { + CreateNewProject(); + } + + return true; +} + +bool Core::CloseAllProjects(bool auto_open_new) +{ + QList copy = open_projects_; + + foreach (ProjectPtr p, copy) { + if (!CloseProject(p.get(), auto_open_new)) { + return false; + } + } + + return true; +} + void Core::OpenProject() { QString file = QFileDialog::getOpenFileName(main_window_, diff --git a/app/core.h b/app/core.h index 2725682a2..b45c85755 100644 --- a/app/core.h +++ b/app/core.h @@ -134,9 +134,9 @@ public: * * The active Project file, or nullptr if the heuristic couldn't find one. */ - Project* GetActiveProject(); - ProjectViewModel* GetActiveProjectModel(); - Folder* GetSelectedFolderInActiveProject(); + Project* GetActiveProject() const; + ProjectViewModel* GetActiveProjectModel() const; + Folder* GetSelectedFolderInActiveProject() const; /** * @brief Gets current timecode display mode @@ -148,16 +148,6 @@ public: */ void SetTimecodeDisplay(Timecode::Display d); - /** - * @brief Sets state to "modified" so that the GUI will prompt the user to save before closing - * - * Call this function whenever a change is made to a currently active project. Saving the project will automatically - * unset this. - */ - void SetProjectModified(bool e); - - bool IsProjectModified() const; - /** * @brief Set how frequently an autorecovery should be saved (if the project has changed, see SetProjectModified()) */ @@ -216,6 +206,16 @@ public: */ void OpenProjectFromRecentList(int index); + /** + * @brief Closes a project + */ + bool CloseProject(Project* p, bool auto_open_new); + + /** + * @brief Closes all open projects + */ + bool CloseAllProjects(bool auto_open_new); + public slots: /** * @brief Starts an open file dialog to load a project from file @@ -291,6 +291,11 @@ public slots: */ void ClearOpenRecentList(); + /** + * @brief Creates a new empty project and opens it + */ + void CreateNewProject(); + signals: /** * @brief Signal emitted when a project is opened @@ -301,6 +306,11 @@ signals: */ void ProjectOpened(Project* p); + /** + * @brief Signal emitted when a project is closed + */ + void ProjectClosed(Project* p); + /** * @brief Signal emitted when the tool is changed from somewhere */ @@ -327,6 +337,16 @@ private: */ static QString GetRecentProjectsFilePath(); + /** + * @brief Saves a specific project + */ + bool SaveProject(Project* p); + + /** + * @brief Performs a "save as" on a specific project + */ + bool SaveProjectAs(Project* p); + /** * @brief Adds a filename to the top of the recently opened projects list (or moves it if it already exists) */ @@ -391,14 +411,6 @@ private: */ bool snapping_; - /** - * @brief Internal value for whether to make an autorecovery next interval - * - * True if the project has changed since the last autorecovery and we should save next time. False if the project has - * not changed and saving another autorecovery would be a waste. - */ - bool queue_autorecovery_; - /** * @brief Internal timer for saving autorecovery files */ @@ -422,7 +434,7 @@ private: private slots: void SaveAutorecovery(); - void ProjectSaveSucceeded(); + void ProjectSaveSucceeded(Project *p); /** * @brief Adds a project to the "open projects" list @@ -433,6 +445,8 @@ private slots: bool ConfirmImageSequence(const QString &filename); + void ProjectWasModified(bool e); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/dialog/footageproperties/footageproperties.cpp b/app/dialog/footageproperties/footageproperties.cpp index 7e935d120..2134b3666 100644 --- a/app/dialog/footageproperties/footageproperties.cpp +++ b/app/dialog/footageproperties/footageproperties.cpp @@ -143,6 +143,11 @@ FootagePropertiesDialog::FootageChangeCommand::FootageChangeCommand(Footage *foo { } +Project *FootagePropertiesDialog::FootageChangeCommand::GetRelevantProject() const +{ + return footage_->project(); +} + void FootagePropertiesDialog::FootageChangeCommand::redo_internal() { old_name_ = footage_->name(); @@ -163,6 +168,11 @@ FootagePropertiesDialog::StreamEnableChangeCommand::StreamEnableChangeCommand(St { } +Project *FootagePropertiesDialog::StreamEnableChangeCommand::GetRelevantProject() const +{ + return stream_->footage()->project(); +} + void FootagePropertiesDialog::StreamEnableChangeCommand::redo_internal() { stream_->set_enabled(new_enabled_); diff --git a/app/dialog/footageproperties/footageproperties.h b/app/dialog/footageproperties/footageproperties.h index 9aeaf6e22..d2018699f 100644 --- a/app/dialog/footageproperties/footageproperties.h +++ b/app/dialog/footageproperties/footageproperties.h @@ -62,6 +62,8 @@ private: const QString& name, QUndoCommand *command = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -79,6 +81,8 @@ private: bool enabled, QUndoCommand* command = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp index a3a6ae53a..47317e0fc 100644 --- a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp @@ -158,6 +158,11 @@ VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(ImageS { } +Project *VideoStreamProperties::VideoStreamChangeCommand::GetRelevantProject() const +{ + return stream_->footage()->project(); +} + void VideoStreamProperties::VideoStreamChangeCommand::redo_internal() { old_premultiplied_ = stream_->premultiplied_alpha(); @@ -181,6 +186,11 @@ VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(Vi { } +Project *VideoStreamProperties::ImageSequenceChangeCommand::GetRelevantProject() const +{ + return video_stream_->footage()->project(); +} + void VideoStreamProperties::ImageSequenceChangeCommand::redo_internal() { old_start_index_ = video_stream_->start_time(); diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.h b/app/dialog/footageproperties/streamproperties/videostreamproperties.h index 6472c6958..6bd164ab8 100644 --- a/app/dialog/footageproperties/streamproperties/videostreamproperties.h +++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.h @@ -75,6 +75,8 @@ private: QString colorspace, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -97,6 +99,8 @@ private: int64_t duration, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; diff --git a/app/dialog/projectproperties/projectproperties.cpp b/app/dialog/projectproperties/projectproperties.cpp index 751720f8b..c4dfadf9f 100644 --- a/app/dialog/projectproperties/projectproperties.cpp +++ b/app/dialog/projectproperties/projectproperties.cpp @@ -36,9 +36,9 @@ namespace OCIO = OCIO_NAMESPACE::v1; OLIVE_NAMESPACE_ENTER -ProjectPropertiesDialog::ProjectPropertiesDialog(QWidget *parent) : +ProjectPropertiesDialog::ProjectPropertiesDialog(Project* p, QWidget *parent) : QDialog(parent), - working_project_(Core::instance()->GetActiveProject()) + working_project_(p) { QVBoxLayout* layout = new QVBoxLayout(this); diff --git a/app/dialog/projectproperties/projectproperties.h b/app/dialog/projectproperties/projectproperties.h index 5fa2fe0d9..8df9dd281 100644 --- a/app/dialog/projectproperties/projectproperties.h +++ b/app/dialog/projectproperties/projectproperties.h @@ -33,7 +33,7 @@ class ProjectPropertiesDialog : public QDialog { Q_OBJECT public: - ProjectPropertiesDialog(QWidget* parent); + ProjectPropertiesDialog(Project *p, QWidget* parent); public slots: virtual void accept() override; diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index 059e9e503..b4b637bbf 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -211,6 +211,11 @@ SequenceDialog::SequenceParamCommand::SequenceParamCommand(Sequence* s, { } +Project *SequenceDialog::SequenceParamCommand::GetRelevantProject() const +{ + return sequence_->project(); +} + void SequenceDialog::SequenceParamCommand::redo_internal() { sequence_->set_video_params(new_video_params_); diff --git a/app/dialog/sequence/sequence.h b/app/dialog/sequence/sequence.h index a7903ba70..228b87a13 100644 --- a/app/dialog/sequence/sequence.h +++ b/app/dialog/sequence/sequence.h @@ -122,6 +122,8 @@ private: const QString& name, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; diff --git a/app/dialog/speedduration/speedduration.cpp b/app/dialog/speedduration/speedduration.cpp index 7ae37e71f..4c6fc4ac9 100644 --- a/app/dialog/speedduration/speedduration.cpp +++ b/app/dialog/speedduration/speedduration.cpp @@ -360,6 +360,11 @@ BlockReverseCommand::BlockReverseCommand(Block *block, QUndoCommand *parent) : { } +Project *BlockReverseCommand::GetRelevantProject() const +{ + return static_cast(block_->parent())->project(); +} + void BlockReverseCommand::redo_internal() { block_->set_media_in(block_->media_out()); diff --git a/app/dialog/speedduration/speedduration.h b/app/dialog/speedduration/speedduration.h index 21b17e775..28a6badc1 100644 --- a/app/dialog/speedduration/speedduration.h +++ b/app/dialog/speedduration/speedduration.h @@ -70,6 +70,8 @@ class BlockReverseCommand : public UndoCommand { public: BlockReverseCommand(Block* block, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; diff --git a/app/node/output/track/tracklist.h b/app/node/output/track/tracklist.h index 6cde374f8..283394909 100644 --- a/app/node/output/track/tracklist.h +++ b/app/node/output/track/tracklist.h @@ -50,6 +50,8 @@ public: int TrackCount() const; + NodeGraph* GetParentGraph() const; + signals: void BlockAdded(Block* block, int index); @@ -66,8 +68,6 @@ signals: void TrackHeightChanged(int index, int height); private: - NodeGraph* GetParentGraph() const; - /** * @brief A cache of connected Tracks */ diff --git a/app/panel/project/project.cpp b/app/panel/project/project.cpp index e17e181e6..cbf552c5f 100644 --- a/app/panel/project/project.cpp +++ b/app/panel/project/project.cpp @@ -82,6 +82,7 @@ void ProjectPanel::set_project(Project *p) if (project()) { disconnect(project(), &Project::NameChanged, this, &ProjectPanel::UpdateSubtitle); disconnect(project(), &Project::NameChanged, this, &ProjectPanel::ProjectNameChanged); + disconnect(project(), &Project::ModifiedChanged, this, &ProjectPanel::setWindowModified); } explorer_->set_project(p); @@ -89,9 +90,16 @@ void ProjectPanel::set_project(Project *p) if (project()) { connect(project(), &Project::NameChanged, this, &ProjectPanel::UpdateSubtitle); connect(project(), &Project::NameChanged, this, &ProjectPanel::ProjectNameChanged); + connect(project(), &Project::ModifiedChanged, this, &ProjectPanel::setWindowModified); } UpdateSubtitle(); + + if (p) { + setWindowModified(p->is_modified()); + } else { + setWindowModified(false); + } } QList ProjectPanel::SelectedItems() @@ -182,7 +190,7 @@ void ProjectPanel::UpdateSubtitle() if (project() == nullptr) { SetSubtitle(tr("(none)")); } else { - SetSubtitle(project()->name()); + SetSubtitle(QStringLiteral("[*]%1").arg(project()->name())); } } diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index f6c77a1eb..f701d3267 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -26,8 +26,7 @@ OLIVE_NAMESPACE_ENTER TimelinePanel::TimelinePanel(QWidget *parent) : - TimeBasedPanel(parent), - signal_instead_of_close_(false) + TimeBasedPanel(parent) { // FIXME: This won't work if there's ever more than one of this panel setObjectName("TimelinePanel"); @@ -168,11 +167,6 @@ void TimelinePanel::OverwriteFootageAtPlayhead(const QList &footage) static_cast(GetTimeBasedWidget())->OverwriteFootageAtPlayhead(footage); } -void TimelinePanel::SetSignalInsteadOfClose(bool e) -{ - signal_instead_of_close_ = e; -} - void TimelinePanel::Retranslate() { TimeBasedPanel::Retranslate(); @@ -180,14 +174,4 @@ void TimelinePanel::Retranslate() SetTitle(tr("Timeline")); } -void TimelinePanel::closeEvent(QCloseEvent *event) -{ - if (signal_instead_of_close_) { - event->ignore(); - emit CloseRequested(); - } else { - PanelWidget::closeEvent(event); - } -} - OLIVE_NAMESPACE_EXIT diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 83a53954d..8739b6eb5 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -83,21 +83,12 @@ public: void OverwriteFootageAtPlayhead(const QList &footage); - void SetSignalInsteadOfClose(bool e); - protected: virtual void Retranslate() override; - virtual void closeEvent(QCloseEvent* event) override; - signals: void SelectionChanged(const QList& selected_blocks); - void CloseRequested(); - -private: - bool signal_instead_of_close_; - }; OLIVE_NAMESPACE_EXIT diff --git a/app/project/item/item.cpp b/app/project/item/item.cpp index 734fd2a99..cfb5bd915 100644 --- a/app/project/item/item.cpp +++ b/app/project/item/item.cpp @@ -132,7 +132,7 @@ const Item *Item::root() const { const Item* item = this; - while (item->parent() != nullptr) { + while (item->parent()) { item = item->parent(); } diff --git a/app/project/project.cpp b/app/project/project.cpp index 15d899401..794ee37a2 100644 --- a/app/project/project.cpp +++ b/app/project/project.cpp @@ -30,7 +30,9 @@ OLIVE_NAMESPACE_ENTER -Project::Project() +Project::Project() : + is_modified_(false), + autorecovery_saved_(true) { root_.set_project(this); } @@ -156,4 +158,32 @@ QList Project::get_items_of_type(Item::Type type) const return root_.get_children_of_type(type, true); } +bool Project::is_modified() const +{ + return is_modified_; +} + +void Project::set_modified(bool e) +{ + is_modified_ = e; + autorecovery_saved_ = !e; + + emit ModifiedChanged(is_modified_); +} + +bool Project::has_autorecovery_been_saved() const +{ + return autorecovery_saved_; +} + +void Project::set_autorecovery_saved(bool e) +{ + autorecovery_saved_ = e; +} + +bool Project::is_new() const +{ + return !is_modified_ && filename_.isEmpty(); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/project/project.h b/app/project/project.h index 6be15699f..b172cef18 100644 --- a/app/project/project.h +++ b/app/project/project.h @@ -68,9 +68,19 @@ public: QList get_items_of_type(Item::Type type) const; + bool is_modified() const; + void set_modified(bool e); + + bool has_autorecovery_been_saved() const; + void set_autorecovery_saved(bool e); + + bool is_new() const; + signals: void NameChanged(); + void ModifiedChanged(bool e); + private: Folder root_; @@ -80,6 +90,10 @@ private: ColorManager color_manager_; + bool is_modified_; + + bool autorecovery_saved_; + }; using ProjectPtr = std::shared_ptr; diff --git a/app/project/projectsavemanager.cpp b/app/project/projectsavemanager.cpp index fb7bc017b..b6ebb12d8 100644 --- a/app/project/projectsavemanager.cpp +++ b/app/project/projectsavemanager.cpp @@ -55,6 +55,7 @@ void ProjectSaveManager::Action() } emit Succeeded(); + emit ProjectSaveSucceeded(project_); } OLIVE_NAMESPACE_EXIT diff --git a/app/project/projectsavemanager.h b/app/project/projectsavemanager.h index 4eab63f8a..4e0560ee2 100644 --- a/app/project/projectsavemanager.h +++ b/app/project/projectsavemanager.h @@ -32,6 +32,9 @@ class ProjectSaveManager : public Task public: ProjectSaveManager(Project* project); +signals: + void ProjectSaveSucceeded(Project* p); + protected: virtual void Action() override; diff --git a/app/project/projectviewmodel.cpp b/app/project/projectviewmodel.cpp index 0f8307608..2dee64116 100644 --- a/app/project/projectviewmodel.cpp +++ b/app/project/projectviewmodel.cpp @@ -511,6 +511,11 @@ ProjectViewModel::MoveItemCommand::MoveItemCommand(ProjectViewModel *model, setText(tr("Move Item")); } +Project *ProjectViewModel::MoveItemCommand::GetRelevantProject() const +{ + return model_->project(); +} + void ProjectViewModel::MoveItemCommand::redo_internal() { model_->MoveItemInternal(item_, destination_); @@ -532,6 +537,11 @@ ProjectViewModel::RenameItemCommand::RenameItemCommand(ProjectViewModel* model, setText(tr("Rename Item")); } +Project *ProjectViewModel::RenameItemCommand::GetRelevantProject() const +{ + return model_->project(); +} + void ProjectViewModel::RenameItemCommand::redo_internal() { model_->RenameChild(item_, new_name_); @@ -551,6 +561,11 @@ ProjectViewModel::AddItemCommand::AddItemCommand(ProjectViewModel* model, Item* { } +Project *ProjectViewModel::AddItemCommand::GetRelevantProject() const +{ + return model_->project(); +} + void ProjectViewModel::AddItemCommand::redo_internal() { model_->AddChild(parent_, child_); @@ -572,6 +587,11 @@ ProjectViewModel::RemoveItemCommand::RemoveItemCommand(ProjectViewModel *model, { } +Project *ProjectViewModel::RemoveItemCommand::GetRelevantProject() const +{ + return model_->project(); +} + void ProjectViewModel::RemoveItemCommand::redo_internal() { parent_ = item_->parent(); diff --git a/app/project/projectviewmodel.h b/app/project/projectviewmodel.h index 6042f7e5c..4e300e7ad 100644 --- a/app/project/projectviewmodel.h +++ b/app/project/projectviewmodel.h @@ -114,6 +114,8 @@ public: public: MoveItemCommand(ProjectViewModel* model, Item* item, Folder* destination, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; @@ -134,6 +136,8 @@ public: public: RenameItemCommand(ProjectViewModel* model, Item* item, const QString& name, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; @@ -153,6 +157,8 @@ public: public: AddItemCommand(ProjectViewModel* model, Item* folder, ItemPtr child, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; @@ -172,6 +178,8 @@ public: public: RemoveItemCommand(ProjectViewModel* model, ItemPtr item, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; diff --git a/app/ui/style/style.cpp b/app/ui/style/style.cpp index c3273b70b..b85ee80f1 100644 --- a/app/ui/style/style.cpp +++ b/app/ui/style/style.cpp @@ -42,14 +42,14 @@ QList StyleManager::ListInternal() return style_list; } +#ifdef Q_OS_WINDOWS void StyleManager::UseNativeWindowsStyling(QWidget *widget) { -#ifdef Q_OS_WINDOWS QStyle* s = QStyleFactory::create(QStringLiteral("windowsvista")); widget->setStyle(s); widget->setPalette(s->standardPalette()); -#endif } +#endif QPalette StyleManager::ParsePalette(const QString& ini_path) { diff --git a/app/ui/style/style.h b/app/ui/style/style.h index b82452b3d..7b146886d 100644 --- a/app/ui/style/style.h +++ b/app/ui/style/style.h @@ -52,7 +52,9 @@ public: static QList ListInternal(); +#ifdef Q_OS_WINDOWS static void UseNativeWindowsStyling(QWidget* widget); +#endif private: static QPalette ParsePalette(const QString& ini_path); diff --git a/app/undo/undocommand.cpp b/app/undo/undocommand.cpp index a380ea6d6..38c81a851 100644 --- a/app/undo/undocommand.cpp +++ b/app/undo/undocommand.cpp @@ -33,15 +33,15 @@ void UndoCommand::redo() { redo_internal(); - modified_ = Core::instance()->IsProjectModified(); - Core::instance()->SetProjectModified(true); + modified_ = GetRelevantProject()->is_modified(); + GetRelevantProject()->set_modified(true); } void UndoCommand::undo() { undo_internal(); - Core::instance()->SetProjectModified(modified_); + GetRelevantProject()->set_modified(modified_); } void UndoCommand::redo_internal() diff --git a/app/undo/undocommand.h b/app/undo/undocommand.h index 762e79e59..2da3b0c4f 100644 --- a/app/undo/undocommand.h +++ b/app/undo/undocommand.h @@ -24,6 +24,7 @@ #include #include "common/define.h" +#include "project/project.h" OLIVE_NAMESPACE_ENTER @@ -35,6 +36,8 @@ public: virtual void redo() override; virtual void undo() override; + virtual Project* GetRelevantProject() const = 0; + protected: virtual void redo_internal(); virtual void undo_internal(); diff --git a/app/widget/keyframeview/keyframeviewundo.cpp b/app/widget/keyframeview/keyframeviewundo.cpp index d08b1d44d..2ad37e0de 100644 --- a/app/widget/keyframeview/keyframeviewundo.cpp +++ b/app/widget/keyframeview/keyframeviewundo.cpp @@ -20,6 +20,10 @@ #include "keyframeviewundo.h" +#include "node/input.h" +#include "node/node.h" +#include "project/item/sequence/sequence.h" + OLIVE_NAMESPACE_ENTER KeyframeSetTypeCommand::KeyframeSetTypeCommand(NodeKeyframePtr key, NodeKeyframe::Type type, QUndoCommand *parent) : @@ -30,6 +34,11 @@ KeyframeSetTypeCommand::KeyframeSetTypeCommand(NodeKeyframePtr key, NodeKeyframe { } +Project *KeyframeSetTypeCommand::GetRelevantProject() const +{ + return static_cast(key_->parent()->parentNode()->parent())->project(); +} + void KeyframeSetTypeCommand::redo_internal() { key_->set_type(new_type_); @@ -58,6 +67,11 @@ KeyframeSetBezierControlPoint::KeyframeSetBezierControlPoint(NodeKeyframePtr key { } +Project *KeyframeSetBezierControlPoint::GetRelevantProject() const +{ + return static_cast(key_->parent()->parentNode()->parent())->project(); +} + void KeyframeSetBezierControlPoint::redo_internal() { key_->set_bezier_control(mode_, new_point_); diff --git a/app/widget/keyframeview/keyframeviewundo.h b/app/widget/keyframeview/keyframeviewundo.h index 54d021a5a..6c7610866 100644 --- a/app/widget/keyframeview/keyframeviewundo.h +++ b/app/widget/keyframeview/keyframeviewundo.h @@ -30,6 +30,8 @@ class KeyframeSetTypeCommand : public UndoCommand { public: KeyframeSetTypeCommand(NodeKeyframePtr key, NodeKeyframe::Type type, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -48,6 +50,8 @@ public: KeyframeSetBezierControlPoint(NodeKeyframePtr key, NodeKeyframe::BezierType mode, const QPointF& point, QUndoCommand* parent = nullptr); KeyframeSetBezierControlPoint(NodeKeyframePtr key, NodeKeyframe::BezierType mode, const QPointF& new_point, const QPointF& old_point, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; diff --git a/app/widget/menu/menu.cpp b/app/widget/menu/menu.cpp index 8ff9aa77b..fd4b71a6e 100644 --- a/app/widget/menu/menu.cpp +++ b/app/widget/menu/menu.cpp @@ -41,13 +41,13 @@ Menu::Menu(Menu *menu) Menu::Menu(QWidget *parent) : QMenu(parent) { - StyleManager::UseNativeWindowsStyling(this); + Init(); } Menu::Menu(const QString &s, QWidget *parent) : QMenu(s, parent) { - StyleManager::UseNativeWindowsStyling(this); + Init(); } QAction* Menu::InsertAlphabetically(const QString &s) @@ -101,7 +101,9 @@ void Menu::SetBooleanAction(QAction *a, bool* boolean) void Menu::Init() { +#ifdef Q_OS_WINDOWS StyleManager::UseNativeWindowsStyling(this); +#endif } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp index c493dc853..96015d2b8 100644 --- a/app/widget/menu/menushared.cpp +++ b/app/widget/menu/menushared.cpp @@ -31,7 +31,7 @@ MenuShared* MenuShared::instance_ = nullptr; MenuShared::MenuShared() { // "New" menu shared items - new_project_item_ = Menu::CreateItem(this, "newproj", this, &MenuShared::NewProjectTriggered, "Ctrl+N"); + new_project_item_ = Menu::CreateItem(this, "newproj", Core::instance(), &Core::CreateNewProject, "Ctrl+N"); new_sequence_item_ = Menu::CreateItem(this, "newseq", Core::instance(), &Core::CreateNewSequence, "Ctrl+Shift+N"); new_folder_item_ = Menu::CreateItem(this, "newfolder", Core::instance(), &Core::CreateNewFolder); @@ -114,11 +114,6 @@ MenuShared *MenuShared::instance() return instance_; } -void MenuShared::NewProjectTriggered() -{ - qDebug() << "FIXME: Stub"; -} - void MenuShared::SplitAtPlayheadTriggered() { TimelinePanel* timeline = PanelManager::instance()->MostRecentlyFocused(); diff --git a/app/widget/menu/menushared.h b/app/widget/menu/menushared.h index 12af66511..81848b3db 100644 --- a/app/widget/menu/menushared.h +++ b/app/widget/menu/menushared.h @@ -77,8 +77,6 @@ private: static MenuShared* instance_; private slots: - void NewProjectTriggered(); - void SplitAtPlayheadTriggered(); void DeleteSelectedTriggered(); diff --git a/app/widget/nodeparamview/nodeparamviewundo.cpp b/app/widget/nodeparamview/nodeparamviewundo.cpp index 606dfaac2..ad55dd256 100644 --- a/app/widget/nodeparamview/nodeparamviewundo.cpp +++ b/app/widget/nodeparamview/nodeparamviewundo.cpp @@ -20,6 +20,9 @@ #include "nodeparamviewundo.h" +#include "node/node.h" +#include "project/item/sequence/sequence.h" + OLIVE_NAMESPACE_ENTER NodeParamSetKeyframingCommand::NodeParamSetKeyframingCommand(NodeInput *input, bool setting, QUndoCommand *parent) : @@ -30,6 +33,11 @@ NodeParamSetKeyframingCommand::NodeParamSetKeyframingCommand(NodeInput *input, b Q_ASSERT(setting != input_->is_keyframing()); } +Project *NodeParamSetKeyframingCommand::GetRelevantProject() const +{ + return static_cast(input_->parentNode()->parent())->project(); +} + void NodeParamSetKeyframingCommand::redo_internal() { input_->set_is_keyframing(setting_); @@ -57,6 +65,11 @@ NodeParamSetKeyframeValueCommand::NodeParamSetKeyframeValueCommand(NodeKeyframeP } +Project *NodeParamSetKeyframeValueCommand::GetRelevantProject() const +{ + return static_cast(key_->parent()->parentNode()->parent())->project(); +} + void NodeParamSetKeyframeValueCommand::redo_internal() { key_->set_value(new_value_); @@ -83,6 +96,11 @@ NodeParamInsertKeyframeCommand::NodeParamInsertKeyframeCommand(NodeInput *input, { } +Project *NodeParamInsertKeyframeCommand::GetRelevantProject() const +{ + return static_cast(input_->parentNode()->parent())->project(); +} + void NodeParamInsertKeyframeCommand::redo_internal() { if (!done_) { @@ -103,6 +121,11 @@ NodeParamRemoveKeyframeCommand::NodeParamRemoveKeyframeCommand(NodeInput *input, { } +Project *NodeParamRemoveKeyframeCommand::GetRelevantProject() const +{ + return static_cast(input_->parentNode()->parent())->project(); +} + void NodeParamRemoveKeyframeCommand::redo_internal() { input_->remove_keyframe(keyframe_); @@ -129,6 +152,11 @@ NodeParamSetKeyframeTimeCommand::NodeParamSetKeyframeTimeCommand(NodeKeyframePtr { } +Project *NodeParamSetKeyframeTimeCommand::GetRelevantProject() const +{ + return static_cast(key_->parent()->parentNode()->parent())->project(); +} + void NodeParamSetKeyframeTimeCommand::redo_internal() { key_->set_time(new_time_); @@ -157,6 +185,11 @@ NodeParamSetStandardValueCommand::NodeParamSetStandardValueCommand(NodeInput *in { } +Project *NodeParamSetStandardValueCommand::GetRelevantProject() const +{ + return static_cast(input_->parentNode()->parent())->project(); +} + void NodeParamSetStandardValueCommand::redo_internal() { input_->set_standard_value(new_value_, track_); diff --git a/app/widget/nodeparamview/nodeparamviewundo.h b/app/widget/nodeparamview/nodeparamviewundo.h index 9190d896a..2ce5b5150 100644 --- a/app/widget/nodeparamview/nodeparamviewundo.h +++ b/app/widget/nodeparamview/nodeparamviewundo.h @@ -30,6 +30,8 @@ class NodeParamSetKeyframingCommand : public UndoCommand { public: NodeParamSetKeyframingCommand(NodeInput* input, bool setting, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -44,6 +46,8 @@ public: NodeParamInsertKeyframeCommand(NodeInput* input, NodeKeyframePtr keyframe, QUndoCommand *parent = nullptr); NodeParamInsertKeyframeCommand(NodeInput* input, NodeKeyframePtr keyframe, bool already_done, QUndoCommand *parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -61,6 +65,8 @@ class NodeParamRemoveKeyframeCommand : public UndoCommand { public: NodeParamRemoveKeyframeCommand(NodeInput* input, NodeKeyframePtr keyframe, QUndoCommand *parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -77,6 +83,8 @@ public: NodeParamSetKeyframeTimeCommand(NodeKeyframePtr key, const rational& time, QUndoCommand* parent = nullptr); NodeParamSetKeyframeTimeCommand(NodeKeyframePtr key, const rational& new_time, const rational& old_time, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -94,6 +102,8 @@ public: NodeParamSetKeyframeValueCommand(NodeKeyframePtr key, const QVariant& value, QUndoCommand* parent = nullptr); NodeParamSetKeyframeValueCommand(NodeKeyframePtr key, const QVariant& new_value, const QVariant& old_value, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -111,6 +121,8 @@ public: NodeParamSetStandardValueCommand(NodeInput* input, int track, const QVariant& value, QUndoCommand* parent = nullptr); NodeParamSetStandardValueCommand(NodeInput* input, int track, const QVariant& new_value, const QVariant& old_value, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; diff --git a/app/widget/nodeview/nodeviewundo.cpp b/app/widget/nodeview/nodeviewundo.cpp index 54e0c352d..b3eef2300 100644 --- a/app/widget/nodeview/nodeviewundo.cpp +++ b/app/widget/nodeview/nodeviewundo.cpp @@ -20,6 +20,8 @@ #include "nodeviewundo.h" +#include "project/item/sequence/sequence.h" + OLIVE_NAMESPACE_ENTER NodeEdgeAddCommand::NodeEdgeAddCommand(NodeOutput *output, NodeInput *input, QUndoCommand *parent) : @@ -59,6 +61,11 @@ void NodeEdgeAddCommand::undo_internal() done_ = false; } +Project *NodeEdgeAddCommand::GetRelevantProject() const +{ + return static_cast(output_->parentNode()->parent())->project(); +} + NodeEdgeRemoveCommand::NodeEdgeRemoveCommand(NodeOutput *output, NodeInput *input, QUndoCommand *parent) : UndoCommand(parent), output_(output), @@ -95,6 +102,11 @@ void NodeEdgeRemoveCommand::undo_internal() done_ = false; } +Project *NodeEdgeRemoveCommand::GetRelevantProject() const +{ + return static_cast(output_->parentNode()->parent())->project(); +} + NodeAddCommand::NodeAddCommand(NodeGraph *graph, Node *node, QUndoCommand *parent) : UndoCommand(parent), graph_(graph), @@ -114,6 +126,11 @@ void NodeAddCommand::undo_internal() graph_->TakeNode(node_, &memory_manager_); } +Project *NodeAddCommand::GetRelevantProject() const +{ + return static_cast(graph_)->project(); +} + NodeRemoveCommand::NodeRemoveCommand(NodeGraph *graph, const QList &nodes, QUndoCommand *parent) : UndoCommand(parent), graph_(graph), @@ -156,6 +173,11 @@ void NodeRemoveCommand::undo_internal() edges_.clear(); } +Project *NodeRemoveCommand::GetRelevantProject() const +{ + return static_cast(graph_)->project(); +} + NodeRemoveWithExclusiveDeps::NodeRemoveWithExclusiveDeps(NodeGraph *graph, Node *node, QUndoCommand *parent) : UndoCommand(parent) { @@ -166,8 +188,13 @@ NodeRemoveWithExclusiveDeps::NodeRemoveWithExclusiveDeps(NodeGraph *graph, Node remove_command_ = new NodeRemoveCommand(graph, node_and_its_deps, this); } +Project *NodeRemoveWithExclusiveDeps::GetRelevantProject() const +{ + return remove_command_->GetRelevantProject(); +} + NodeCopyInputsCommand::NodeCopyInputsCommand(Node *src, Node *dest, bool include_connections, QUndoCommand *parent) : - UndoCommand(parent), + QUndoCommand(parent), src_(src), dest_(dest), include_connections_(include_connections) @@ -175,14 +202,14 @@ NodeCopyInputsCommand::NodeCopyInputsCommand(Node *src, Node *dest, bool include } NodeCopyInputsCommand::NodeCopyInputsCommand(Node *src, Node *dest, QUndoCommand *parent) : - UndoCommand(parent), + QUndoCommand(parent), src_(src), dest_(dest), include_connections_(true) { } -void NodeCopyInputsCommand::redo_internal() +void NodeCopyInputsCommand::redo() { Node::CopyInputs(src_, dest_, include_connections_); } diff --git a/app/widget/nodeview/nodeviewundo.h b/app/widget/nodeview/nodeviewundo.h index 48910fdcc..4a9faefd4 100644 --- a/app/widget/nodeview/nodeviewundo.h +++ b/app/widget/nodeview/nodeviewundo.h @@ -39,6 +39,8 @@ class NodeEdgeAddCommand : public UndoCommand { public: NodeEdgeAddCommand(NodeOutput* output, NodeInput* input, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -62,6 +64,8 @@ public: NodeEdgeRemoveCommand(NodeOutput* output, NodeInput* input, QUndoCommand* parent = nullptr); NodeEdgeRemoveCommand(NodeEdgePtr edge, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -77,6 +81,8 @@ class NodeAddCommand : public UndoCommand { public: NodeAddCommand(NodeGraph* graph, Node* node, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -94,6 +100,8 @@ public: const QList& nodes, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -112,11 +120,13 @@ public: Node* node, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + private: NodeRemoveCommand* remove_command_; }; -class NodeCopyInputsCommand : public UndoCommand { +class NodeCopyInputsCommand : public QUndoCommand { public: NodeCopyInputsCommand(Node* src, Node* dest, @@ -128,7 +138,7 @@ public: QUndoCommand* parent = nullptr); protected: - virtual void redo_internal() override; + virtual void redo() override; private: Node* src_; diff --git a/app/widget/panel/panel.cpp b/app/widget/panel/panel.cpp index b557e8b54..d73dc02db 100644 --- a/app/widget/panel/panel.cpp +++ b/app/widget/panel/panel.cpp @@ -20,6 +20,8 @@ #include "panel.h" +#include +#include #include #include #include @@ -29,9 +31,12 @@ OLIVE_NAMESPACE_ENTER PanelWidget::PanelWidget(QWidget *parent) : QDockWidget(parent), - border_visible_(false) + border_visible_(false), + signal_instead_of_close_(false) { setFocusPolicy(Qt::ClickFocus); + + connect(this, &PanelWidget::visibilityChanged, this, &PanelWidget::PanelVisibilityChanged); } void PanelWidget::SetMovementLocked(bool locked) @@ -104,6 +109,28 @@ void PanelWidget::UpdateTitle() } } +void PanelWidget::PanelVisibilityChanged(bool e) +{ + if (e) { + setFocus(); + } +} + +void PanelWidget::SetSignalInsteadOfClose(bool e) +{ + signal_instead_of_close_ = e; +} + +void PanelWidget::closeEvent(QCloseEvent *event) +{ + if (signal_instead_of_close_) { + event->ignore(); + emit CloseRequested(); + } else { + QDockWidget::closeEvent(event); + } +} + void PanelWidget::changeEvent(QEvent *e) { if (e->type() == QEvent::LanguageChange) { diff --git a/app/widget/panel/panel.h b/app/widget/panel/panel.h index 5e8ef8f16..93409bf69 100644 --- a/app/widget/panel/panel.h +++ b/app/widget/panel/panel.h @@ -56,6 +56,13 @@ public: */ void SetBorderVisible(bool enabled); + /** + * @brief If enabled, sends signal CloseRequested() when the user closes instead of closing + * + * Defaults to FALSE. Use this to override default panel closing functionality. + */ + void SetSignalInsteadOfClose(bool e); + /** * @brief Called whenever this panel is focused and user uses "Zoom In" (either in menus or as a keyboard shortcut) * @@ -156,6 +163,9 @@ public: virtual void ToggleSelectedEnabled(){} +signals: + void CloseRequested(); + protected: /** * @brief paintEvent @@ -165,6 +175,8 @@ protected: virtual void changeEvent(QEvent* e) override; + virtual void closeEvent(QCloseEvent* event) override; + virtual void Retranslate(); protected slots: @@ -204,20 +216,17 @@ private: */ void UpdateTitle(); - /** - * @brief Internal title string - */ QString title_; - /** - * @brief Internal subtitle string - */ QString subtitle_; - /** - * @brief Internal border visibility value - */ bool border_visible_; + + bool signal_instead_of_close_; + +private slots: + void PanelVisibilityChanged(bool e); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timebased/timebased.cpp b/app/widget/timebased/timebased.cpp index 356ccf766..d51bab8c9 100644 --- a/app/widget/timebased/timebased.cpp +++ b/app/widget/timebased/timebased.cpp @@ -189,6 +189,11 @@ TimelinePoints *TimeBasedWidget::ConnectTimelinePoints() return static_cast(viewer_node_->parent()); } +Project *TimeBasedWidget::GetTimelinePointsProject() +{ + return static_cast(viewer_node_->parent())->project(); +} + TimelinePoints *TimeBasedWidget::GetConnectedTimelinePoints() const { return points_; @@ -339,7 +344,7 @@ void TimeBasedWidget::SetPoint(Timeline::MovementMode m, const rational& time) // Enable workarea if it isn't already enabled if (!points_->workarea()->enabled()) { - new WorkareaSetEnabledCommand(points_, true, command); + new WorkareaSetEnabledCommand(GetTimelinePointsProject(), points_, true, command); } // Determine our new range @@ -364,7 +369,7 @@ void TimeBasedWidget::SetPoint(Timeline::MovementMode m, const rational& time) } // Set workarea - new WorkareaSetRangeCommand(points_, TimeRange(in_point, out_point), command); + new WorkareaSetRangeCommand(GetTimelinePointsProject(), points_, TimeRange(in_point, out_point), command); Core::instance()->undo_stack()->push(command); } @@ -383,7 +388,7 @@ void TimeBasedWidget::ResetPoint(Timeline::MovementMode m) r.set_out(TimelineWorkArea::kResetOut); } - Core::instance()->undo_stack()->push(new WorkareaSetRangeCommand(points_, r)); + Core::instance()->undo_stack()->push(new WorkareaSetRangeCommand(GetTimelinePointsProject(), points_, r)); } void TimeBasedWidget::SetInAtPlayhead() @@ -413,7 +418,7 @@ void TimeBasedWidget::ClearInOutPoints() } - Core::instance()->undo_stack()->push(new WorkareaSetEnabledCommand(points_, false)); + Core::instance()->undo_stack()->push(new WorkareaSetEnabledCommand(GetTimelinePointsProject(), points_, false)); } void TimeBasedWidget::SetMarker() diff --git a/app/widget/timebased/timebased.h b/app/widget/timebased/timebased.h index 08457286f..596f6ffe6 100644 --- a/app/widget/timebased/timebased.h +++ b/app/widget/timebased/timebased.h @@ -116,6 +116,8 @@ protected: virtual TimelinePoints* ConnectTimelinePoints(); + virtual Project* GetTimelinePointsProject(); + TimelinePoints* GetConnectedTimelinePoints() const; void ConnectTimelineView(TimelineViewBase* base); diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 1ea795671..f1c3b717b 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -156,16 +156,13 @@ TimelineWidget::~TimelineWidget() void TimelineWidget::Clear() { - SetTimebase(0); - - QMap::iterator iterator = block_items_.begin(); - - while (iterator != block_items_.end()) { + QMap::const_iterator iterator; + for (iterator=block_items_.begin(); iterator!=block_items_.end(); iterator++) { delete iterator.value(); - iterator = block_items_.erase(iterator); } - block_items_.clear(); + + SetTimebase(0); } void TimelineWidget::TimebaseChangedEvent(const rational &timebase) @@ -725,7 +722,7 @@ void TimelineWidget::DeleteInToOut(bool ripple) } // Clear workarea after this - new WorkareaSetEnabledCommand(GetConnectedTimelinePoints(), false, command); + new WorkareaSetEnabledCommand(GetTimelinePointsProject(), GetConnectedTimelinePoints(), false, command); Core::instance()->undo_stack()->push(command); } diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp index 826682f39..8dd71617a 100644 --- a/app/widget/timelinewidget/undo/undo.cpp +++ b/app/widget/timelinewidget/undo/undo.cpp @@ -42,6 +42,11 @@ BlockResizeCommand::BlockResizeCommand(Block *block, rational new_length, QUndoC { } +Project *BlockResizeCommand::GetRelevantProject() const +{ + return static_cast(block_->parent())->project(); +} + void BlockResizeCommand::redo_internal() { block_->set_length_and_media_out(new_length_); @@ -60,6 +65,11 @@ BlockResizeWithMediaInCommand::BlockResizeWithMediaInCommand(Block *block, ratio { } +Project *BlockResizeWithMediaInCommand::GetRelevantProject() const +{ + return static_cast(block_->parent())->project(); +} + void BlockResizeWithMediaInCommand::redo_internal() { block_->set_length_and_media_in(new_length_); @@ -78,6 +88,11 @@ BlockSetMediaInCommand::BlockSetMediaInCommand(Block *block, rational new_media_ { } +Project *BlockSetMediaInCommand::GetRelevantProject() const +{ + return static_cast(block_->parent())->project(); +} + void BlockSetMediaInCommand::redo_internal() { block_->set_media_in(new_media_in_); @@ -95,6 +110,11 @@ TrackRippleRemoveBlockCommand::TrackRippleRemoveBlockCommand(TrackOutput *track, { } +Project *TrackRippleRemoveBlockCommand::GetRelevantProject() const +{ + return static_cast(block_->parent())->project(); +} + void TrackRippleRemoveBlockCommand::redo_internal() { before_ = block_->previous(); @@ -111,9 +131,9 @@ void TrackRippleRemoveBlockCommand::undo_internal() } TrackInsertBlockAfterCommand::TrackInsertBlockAfterCommand(TrackOutput *track, - Block *block, - Block *before, - QUndoCommand *parent) : + Block *block, + Block *before, + QUndoCommand *parent) : UndoCommand(parent), track_(track), block_(block), @@ -121,6 +141,11 @@ TrackInsertBlockAfterCommand::TrackInsertBlockAfterCommand(TrackOutput *track, { } +Project *TrackInsertBlockAfterCommand::GetRelevantProject() const +{ + return static_cast(block_->parent())->project(); +} + void TrackInsertBlockAfterCommand::redo_internal() { track_->InsertBlockAfter(block_, before_); @@ -143,6 +168,11 @@ TrackRippleRemoveAreaCommand::TrackRippleRemoveAreaCommand(TrackOutput *track, r { } +Project *TrackRippleRemoveAreaCommand::GetRelevantProject() const +{ + return static_cast(track_->parent())->project(); +} + void TrackRippleRemoveAreaCommand::SetInsert(Block *insert) { insert_ = insert; @@ -386,10 +416,15 @@ BlockSplitCommand::BlockSplitCommand(TrackOutput* track, Block *block, rational } } +Project *BlockSplitCommand::GetRelevantProject() const +{ + return static_cast(block_->parent())->project(); +} + void BlockSplitCommand::redo_internal() { // FIXME: Reintroduce this optimization when block waveforms update automatically -// track_->BlockInvalidateCache(); + // track_->BlockInvalidateCache(); static_cast(block_->parent())->AddNode(new_block_); Node::CopyInputs(block_, new_block_); @@ -407,12 +442,12 @@ void BlockSplitCommand::redo_internal() NodeParam::ConnectEdge(new_block_->output(), transition); } -// track_->UnblockInvalidateCache(); + // track_->UnblockInvalidateCache(); } void BlockSplitCommand::undo_internal() { -// track_->BlockInvalidateCache(); + // track_->BlockInvalidateCache(); block_->set_length_and_media_out(old_length_); track_->RippleRemoveBlock(new_block_); @@ -424,7 +459,7 @@ void BlockSplitCommand::undo_internal() NodeParam::ConnectEdge(block_->output(), transition); } -// track_->UnblockInvalidateCache(); + // track_->UnblockInvalidateCache(); } Block *BlockSplitCommand::new_block() @@ -433,7 +468,8 @@ Block *BlockSplitCommand::new_block() } TrackSplitAtTimeCommand::TrackSplitAtTimeCommand(TrackOutput *track, rational point, QUndoCommand *parent) : - UndoCommand(parent) + UndoCommand(parent), + track_(track) { // Find Block that contains this time foreach (Block* b, track->Blocks()) { @@ -442,12 +478,17 @@ TrackSplitAtTimeCommand::TrackSplitAtTimeCommand(TrackOutput *track, rational po return; } else if (b->in() < point && b->out() > point) { // We found the Block, split it - new BlockSplitCommand(track, b, point, this); + new BlockSplitCommand(track_, b, point, this); return; } } } +Project *TrackSplitAtTimeCommand::GetRelevantProject() const +{ + return static_cast(track_->parent())->project(); +} + TrackReplaceBlockCommand::TrackReplaceBlockCommand(TrackOutput* track, Block *old, Block *replace, QUndoCommand *parent) : UndoCommand(parent), track_(track), @@ -456,6 +497,11 @@ TrackReplaceBlockCommand::TrackReplaceBlockCommand(TrackOutput* track, Block *ol { } +Project *TrackReplaceBlockCommand::GetRelevantProject() const +{ + return static_cast(track_->parent())->project(); +} + void TrackReplaceBlockCommand::redo_internal() { track_->ReplaceBlock(old_, replace_); @@ -473,6 +519,11 @@ TrackPrependBlockCommand::TrackPrependBlockCommand(TrackOutput *track, Block *bl { } +Project *TrackPrependBlockCommand::GetRelevantProject() const +{ + return static_cast(track_->parent())->project(); +} + void TrackPrependBlockCommand::redo_internal() { track_->PrependBlock(block_); @@ -535,6 +586,11 @@ BlockSplitPreservingLinksCommand::BlockSplitPreservingLinksCommand(const QVector } } +Project *BlockSplitPreservingLinksCommand::GetRelevantProject() const +{ + return static_cast(blocks_.first()->parent())->project(); +} + TrackCleanGapsCommand::TrackCleanGapsCommand(TrackList *track_list, int index, QUndoCommand *parent) : UndoCommand(parent), track_list_(track_list), @@ -542,6 +598,11 @@ TrackCleanGapsCommand::TrackCleanGapsCommand(TrackList *track_list, int index, Q { } +Project *TrackCleanGapsCommand::GetRelevantProject() const +{ + return static_cast(track_list_->GetParentGraph())->project(); +} + void TrackCleanGapsCommand::redo_internal() { GapBlock* on_gap = nullptr; @@ -632,6 +693,11 @@ BlockSetSpeedCommand::BlockSetSpeedCommand(Block *block, const rational &new_spe { } +Project *BlockSetSpeedCommand::GetRelevantProject() const +{ + return static_cast(block_->parent())->project(); +} + void BlockSetSpeedCommand::redo_internal() { block_->set_speed(new_speed_); @@ -649,6 +715,11 @@ TimelineRippleDeleteGapsAtRegionsCommand::TimelineRippleDeleteGapsAtRegionsComma { } +Project *TimelineRippleDeleteGapsAtRegionsCommand::GetRelevantProject() const +{ + return static_cast(timeline_->parent())->project(); +} + void TimelineRippleDeleteGapsAtRegionsCommand::redo_internal() { foreach (const TimeRange& range, regions_) { @@ -699,14 +770,20 @@ void TimelineRippleDeleteGapsAtRegionsCommand::undo_internal() commands_.empty(); } -WorkareaSetEnabledCommand::WorkareaSetEnabledCommand(TimelinePoints *points, bool enabled, QUndoCommand *parent) : +WorkareaSetEnabledCommand::WorkareaSetEnabledCommand(Project* project, TimelinePoints *points, bool enabled, QUndoCommand *parent) : UndoCommand(parent), + project_(project), points_(points), old_enabled_(points_->workarea()->enabled()), new_enabled_(enabled) { } +Project *WorkareaSetEnabledCommand::GetRelevantProject() const +{ + return project_; +} + void WorkareaSetEnabledCommand::redo_internal() { points_->workarea()->set_enabled(new_enabled_); @@ -717,14 +794,20 @@ void WorkareaSetEnabledCommand::undo_internal() points_->workarea()->set_enabled(old_enabled_); } -WorkareaSetRangeCommand::WorkareaSetRangeCommand(TimelinePoints *points, const TimeRange &range, QUndoCommand *parent) : +WorkareaSetRangeCommand::WorkareaSetRangeCommand(Project* project, TimelinePoints *points, const TimeRange &range, QUndoCommand *parent) : UndoCommand(parent), + project_(project), points_(points), old_range_(points_->workarea()->range()), new_range_(range) { } +Project *WorkareaSetRangeCommand::GetRelevantProject() const +{ + return project_; +} + void WorkareaSetRangeCommand::redo_internal() { points_->workarea()->set_range(new_range_); @@ -743,6 +826,11 @@ BlockLinkCommand::BlockLinkCommand(Block *a, Block *b, bool link, QUndoCommand * { } +Project *BlockLinkCommand::GetRelevantProject() const +{ + return static_cast(a_->parent())->project(); +} + void BlockLinkCommand::redo_internal() { if (link_) { @@ -769,6 +857,11 @@ BlockUnlinkAllCommand::BlockUnlinkAllCommand(Block *block, QUndoCommand *parent) { } +Project *BlockUnlinkAllCommand::GetRelevantProject() const +{ + return static_cast(block_->parent())->project(); +} + void BlockUnlinkAllCommand::redo_internal() { unlinked_ = block_->linked_clips(); @@ -788,10 +881,11 @@ void BlockUnlinkAllCommand::undo_internal() } BlockLinkManyCommand::BlockLinkManyCommand(const QList blocks, bool link, QUndoCommand *parent) : - UndoCommand(parent) + UndoCommand(parent), + blocks_(blocks) { - foreach (Block* a, blocks) { - foreach (Block* b, blocks) { + foreach (Block* a, blocks_) { + foreach (Block* b, blocks_) { if (a != b) { new BlockLinkCommand(a, b, link, this); } @@ -799,6 +893,11 @@ BlockLinkManyCommand::BlockLinkManyCommand(const QList blocks, bool lin } } +Project *BlockLinkManyCommand::GetRelevantProject() const +{ + return static_cast(blocks_.first()->parent())->project(); +} + BlockEnableDisableCommand::BlockEnableDisableCommand(Block *block, bool enabled, QUndoCommand *parent) : UndoCommand(parent), block_(block), @@ -807,6 +906,11 @@ BlockEnableDisableCommand::BlockEnableDisableCommand(Block *block, bool enabled, { } +Project *BlockEnableDisableCommand::GetRelevantProject() const +{ + return static_cast(block_->parent())->project(); +} + void BlockEnableDisableCommand::redo_internal() { block_->set_enabled(new_enabled_); diff --git a/app/widget/timelinewidget/undo/undo.h b/app/widget/timelinewidget/undo/undo.h index 3098863c7..d763d007c 100644 --- a/app/widget/timelinewidget/undo/undo.h +++ b/app/widget/timelinewidget/undo/undo.h @@ -36,6 +36,8 @@ class BlockResizeCommand : public UndoCommand { public: BlockResizeCommand(Block* block, rational new_length, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -50,6 +52,8 @@ class BlockResizeWithMediaInCommand : public UndoCommand { public: BlockResizeWithMediaInCommand(Block* block, rational new_length, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -64,6 +68,8 @@ class BlockSetMediaInCommand : public UndoCommand { public: BlockSetMediaInCommand(Block* block, rational new_media_in, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -78,6 +84,8 @@ class BlockSetSpeedCommand : public UndoCommand { public: BlockSetSpeedCommand(Block* block, const rational& new_speed, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -93,6 +101,8 @@ class TrackRippleRemoveBlockCommand : public UndoCommand { public: TrackRippleRemoveBlockCommand(TrackOutput* track, Block* block, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -109,6 +119,8 @@ class TrackPrependBlockCommand : public UndoCommand { public: TrackPrependBlockCommand(TrackOutput* track, Block* block, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -122,6 +134,8 @@ class TrackInsertBlockAfterCommand : public UndoCommand { public: TrackInsertBlockAfterCommand(TrackOutput* track, Block* block, Block* before, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -145,6 +159,8 @@ class TrackRippleRemoveAreaCommand : public UndoCommand { public: TrackRippleRemoveAreaCommand(TrackOutput* track, rational in, rational out, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + void SetInsert(Block* insert); protected: @@ -203,6 +219,8 @@ class BlockSplitCommand : public UndoCommand { public: BlockSplitCommand(TrackOutput* track, Block* block, rational point, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + Block* new_block(); protected: @@ -228,12 +246,20 @@ private: class TrackSplitAtTimeCommand : public UndoCommand { public: TrackSplitAtTimeCommand(TrackOutput* track, rational point, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +private: + TrackOutput* track_; + }; class BlockSplitPreservingLinksCommand : public UndoCommand { public: BlockSplitPreservingLinksCommand(const QVector &blocks, const QList& times, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + private: QVector blocks_; @@ -249,6 +275,8 @@ class TrackReplaceBlockCommand : public UndoCommand { public: TrackReplaceBlockCommand(TrackOutput* track, Block* old, Block* replace, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -263,6 +291,8 @@ class TrackCleanGapsCommand : public UndoCommand { public: TrackCleanGapsCommand(TrackList* track_list, int index, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -290,6 +320,8 @@ class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand { public: TimelineRippleDeleteGapsAtRegionsCommand(ViewerOutput* vo, const TimeRangeList& regions, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -304,13 +336,17 @@ private: class WorkareaSetEnabledCommand : public UndoCommand { public: - WorkareaSetEnabledCommand(TimelinePoints* points, bool enabled, QUndoCommand* parent = nullptr); + WorkareaSetEnabledCommand(Project *project, TimelinePoints* points, bool enabled, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; protected: virtual void redo_internal() override; virtual void undo_internal() override; private: + Project* project_; + TimelinePoints* points_; bool old_enabled_; @@ -321,13 +357,17 @@ private: class WorkareaSetRangeCommand : public UndoCommand { public: - WorkareaSetRangeCommand(TimelinePoints* points, const TimeRange& range, QUndoCommand* parent = nullptr); + WorkareaSetRangeCommand(Project *project, TimelinePoints* points, const TimeRange& range, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; protected: virtual void redo_internal() override; virtual void undo_internal() override; private: + Project* project_; + TimelinePoints* points_; TimeRange old_range_; @@ -339,12 +379,20 @@ private: class BlockLinkManyCommand : public UndoCommand { public: BlockLinkManyCommand(const QList blocks, bool link, QUndoCommand* parent = nullptr); + + virtual Project* GetRelevantProject() const override; + +private: + QList blocks_; + }; class BlockLinkCommand : public UndoCommand { public: BlockLinkCommand(Block* a, Block* b, bool link, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -364,6 +412,8 @@ class BlockUnlinkAllCommand : public UndoCommand { public: BlockUnlinkAllCommand(Block* block, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; @@ -379,6 +429,8 @@ class BlockEnableDisableCommand : public UndoCommand { public: BlockEnableDisableCommand(Block* block, bool enabled, QUndoCommand* parent = nullptr); + virtual Project* GetRelevantProject() const override; + protected: virtual void redo_internal() override; virtual void undo_internal() override; diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp index 4cfd170bc..08f1c4b71 100644 --- a/app/widget/viewer/footageviewer.cpp +++ b/app/widget/viewer/footageviewer.cpp @@ -98,6 +98,11 @@ TimelinePoints *FootageViewerWidget::ConnectTimelinePoints() return footage_; } +Project *FootageViewerWidget::GetTimelinePointsProject() +{ + return footage_->project(); +} + void FootageViewerWidget::StartFootageDragInternal(bool enable_video, bool enable_audio) { if (!GetFootage()) { diff --git a/app/widget/viewer/footageviewer.h b/app/widget/viewer/footageviewer.h index fa76ae57c..6043b01f2 100644 --- a/app/widget/viewer/footageviewer.h +++ b/app/widget/viewer/footageviewer.h @@ -40,6 +40,8 @@ public: protected: virtual TimelinePoints* ConnectTimelinePoints() override; + virtual Project* GetTimelinePointsProject() override; + private: void StartFootageDragInternal(bool enable_video, bool enable_audio); diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index 4a07bed58..f279c3a0c 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -38,7 +38,9 @@ OLIVE_NAMESPACE_ENTER MainMenu::MainMenu(MainWindow *parent) : QMenuBar(parent) { +#ifdef Q_OS_WINDOWS StyleManager::UseNativeWindowsStyling(this); +#endif // // FILE MENU @@ -300,7 +302,7 @@ void MainMenu::TimecodeDisplayTriggered() void MainMenu::FileMenuAboutToShow() { - file_project_properties_item_->setEnabled(Core::instance()->GetActiveProject() != nullptr); + file_project_properties_item_->setEnabled(Core::instance()->GetActiveProject()); } void MainMenu::ViewMenuAboutToShow() diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index a119c29bd..626c7474e 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -70,7 +70,7 @@ MainWindow::MainWindow(QWidget *parent) : param_panel_ = PanelManager::instance()->CreatePanel(this); sequence_viewer_panel_ = PanelManager::instance()->CreatePanel(this); pixel_sampler_panel_ = PanelManager::instance()->CreatePanel(this); - project_panel_ = PanelManager::instance()->CreatePanel(this); + AppendProjectPanel(); tool_panel_ = PanelManager::instance()->CreatePanel(this); task_man_panel_ = PanelManager::instance()->CreatePanel(this); curve_panel_ = PanelManager::instance()->CreatePanel(this); @@ -98,8 +98,9 @@ MainWindow::MainWindow(QWidget *parent) : footage_viewer_panel_->ConnectPixelSamplerPanel(pixel_sampler_panel_); sequence_viewer_panel_->ConnectPixelSamplerPanel(pixel_sampler_panel_); - connect(project_panel_, &ProjectPanel::ProjectNameChanged, this, &MainWindow::UpdateTitle); UpdateTitle(); + + QMetaObject::invokeMethod(this, "SetDefaultLayout", Qt::QueuedConnection); } MainWindow::~MainWindow() @@ -114,9 +115,7 @@ MainWindow::~MainWindow() void MainWindow::OpenSequence(Sequence *sequence) { // See if this sequence is already open, and switch to it if so - for (int i=0;iGetConnectedViewer() == sequence->viewer_output()) { tl->raise(); return; @@ -222,42 +221,54 @@ void MainWindow::ToggleMaximizedPanel() void MainWindow::ProjectOpen(Project* p) { - // FIXME Use settings data to create panels and restore state if they exist - project_panel_->set_project(p); - - UpdateTitle(); - - SetDefaultLayout(); -} - -void MainWindow::closeEvent(QCloseEvent *e) -{ - if (isWindowModified()) { - QMessageBox mb(this); - - mb.setIcon(QMessageBox::Question); - mb.setWindowTitle(tr("Unsaved Changes")); - mb.setText(tr("The project '%1' has unsaved changes. Would you like to save them?") - .arg(Core::instance()->GetActiveProject()->name())); - - QPushButton* yes_btn = mb.addButton(tr("Save"), QMessageBox::YesRole); - mb.addButton(tr("Don't Save"), QMessageBox::NoRole); - QPushButton* cancel_btn = mb.addButton(QMessageBox::Cancel); - - mb.exec(); - - if (mb.clickedButton() == cancel_btn - || (mb.clickedButton() == yes_btn && !Core::instance()->SaveActiveProject())) { - // Don't close if the user clicked cancel on this messagebox OR they cancelled a save as operation - e->ignore(); + // See if this project is already open, and switch to it if so + foreach (ProjectPanel* pl, project_panels_) { + if (pl->project() == p) { + pl->raise(); return; } } - // Close viewers first since we don't want to delete any nodes while they might be mid-render - QList viewers = PanelManager::instance()->GetPanelsOfType(); - foreach (ViewerPanelBase* viewer, viewers) { - viewer->ConnectViewerNode(nullptr); + ProjectPanel* panel; + + if (!project_panels_.first()->project()) { + panel = project_panels_.first(); + } else { + panel = AppendProjectPanel(); + } + + panel->set_project(p); + + // FIXME Use settings data to create panels and restore state if they exist +} + +void MainWindow::ProjectClose(Project *p) +{ + // Close project from project panel + foreach (ProjectPanel* panel, project_panels_) { + if (panel->project() == p) { + RemoveProjectPanel(panel); + } + } + + // Close any open sequences from project + QList open_sequences = p->get_items_of_type(Item::kSequence); + + foreach (ItemPtr item, open_sequences) { + Sequence* seq = static_cast(item.get()); + + if (IsSequenceOpen(seq)) { + CloseSequence(seq); + } + } +} + +void MainWindow::closeEvent(QCloseEvent *e) +{ + // Try to close all projects (this will return false if the user chooses not to close) + if (!Core::instance()->CloseAllProjects(false)) { + e->ignore(); + return; } PanelManager::instance()->DeleteAllPanels(); @@ -292,10 +303,10 @@ bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *r void MainWindow::UpdateTitle() { - if (project_panel_->project()) { + if (Core::instance()->GetActiveProject()) { setWindowTitle(QStringLiteral("%1 %2 - [*]%3").arg(QApplication::applicationName(), QApplication::applicationVersion(), - project_panel_->project()->pretty_filename())); + Core::instance()->GetActiveProject()->pretty_filename())); } else { setWindowTitle(QStringLiteral("%1 %2").arg(QApplication::applicationName(), QApplication::applicationVersion())); @@ -307,24 +318,19 @@ void MainWindow::TimelineCloseRequested() RemoveTimelinePanel(static_cast(sender())); } +void MainWindow::ProjectCloseRequested() +{ + ProjectPanel* panel = static_cast(sender()); + Project* p = panel->project(); + + Core::instance()->CloseProject(p, true); +} + TimelinePanel* MainWindow::AppendTimelinePanel() { - TimelinePanel* panel = PanelManager::instance()->CreatePanel(this);; - - if (!timeline_panels_.isEmpty()) { - tabifyDockWidget(timeline_panels_.last(), panel); - - // For some reason raise() on its own doesn't do anything, we need both - panel->show(); - panel->raise(); - } - - timeline_panels_.append(panel); - - // Let us handle the panel closing rather than the panel itself - panel->SetSignalInsteadOfClose(true); - connect(panel, &TimelinePanel::CloseRequested, this, &MainWindow::TimelineCloseRequested); + TimelinePanel* panel = AppendPanelInternal(timeline_panels_); + connect(panel, &PanelWidget::CloseRequested, this, &MainWindow::TimelineCloseRequested); connect(panel, &TimelinePanel::TimeChanged, param_panel_, &ParamPanel::SetTime); connect(panel, &TimelinePanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTime); connect(panel, &TimelinePanel::TimeChanged, curve_panel_, &CurvePanel::SetTime); @@ -340,6 +346,16 @@ TimelinePanel* MainWindow::AppendTimelinePanel() return panel; } +ProjectPanel *MainWindow::AppendProjectPanel() +{ + ProjectPanel* panel = AppendPanelInternal(project_panels_); + + connect(panel, &PanelWidget::CloseRequested, this, &MainWindow::ProjectCloseRequested); + connect(panel, &ProjectPanel::ProjectNameChanged, this, &MainWindow::UpdateTitle); + + return panel; +} + void MainWindow::RemoveTimelinePanel(TimelinePanel *panel) { // Stop showing this timeline in the viewer @@ -354,6 +370,16 @@ void MainWindow::RemoveTimelinePanel(TimelinePanel *panel) } } +void MainWindow::RemoveProjectPanel(ProjectPanel *panel) +{ + if (project_panels_.size() == 1) { + panel->set_project(nullptr); + } else { + project_panels_.removeOne(panel); + panel->deleteLater(); + } +} + void MainWindow::TimelineFocused(ViewerOutput* viewer) { sequence_viewer_panel_->ConnectViewerNode(viewer); @@ -373,6 +399,14 @@ void MainWindow::FocusedPanelChanged(PanelWidget *panel) if (timeline) { TimelineFocused(timeline->GetConnectedViewer()); + return; + } + + ProjectPanel* project = dynamic_cast(panel); + + if (project) { + UpdateTitle(); + return; } } @@ -395,8 +429,8 @@ void MainWindow::SetDefaultLayout() pixel_sampler_panel_->setFloating(true); addDockWidget(Qt::TopDockWidgetArea, pixel_sampler_panel_); - project_panel_->show(); - addDockWidget(Qt::BottomDockWidgetArea, project_panel_); + project_panels_.first()->show(); + addDockWidget(Qt::BottomDockWidgetArea, project_panels_.first()); tool_panel_->show(); addDockWidget(Qt::BottomDockWidgetArea, tool_panel_); @@ -419,13 +453,34 @@ void MainWindow::SetDefaultLayout() {width()/3, width()/3, width()/3}, Qt::Horizontal); - resizeDocks({project_panel_, tool_panel_, timeline_panels_.first(), audio_monitor_panel_}, + resizeDocks({project_panels_.first(), tool_panel_, timeline_panels_.first(), audio_monitor_panel_}, {width()/4, 1, width(), 1}, Qt::Horizontal); - resizeDocks({node_panel_, project_panel_}, + resizeDocks({node_panel_, project_panels_.first()}, {height()/2, height()/2}, Qt::Vertical); } +template +T *MainWindow::AppendPanelInternal(QList& list) +{ + T* panel = PanelManager::instance()->CreatePanel(this); + + if (!list.isEmpty()) { + tabifyDockWidget(list.last(), panel); + } + + // For some reason raise() on its own doesn't do anything, we need both + panel->show(); + panel->raise(); + + list.append(panel); + + // Let us handle the panel closing rather than the panel itself + panel->SetSignalInsteadOfClose(true); + + return panel; +} + OLIVE_NAMESPACE_EXIT diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index 716f17a56..e14d49dd5 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -67,7 +67,11 @@ public: public slots: void ProjectOpen(Project *p); + + void ProjectClose(Project* p); + void SetFullscreen(bool fullscreen); + void ToggleMaximizedPanel(); void SetDefaultLayout(); @@ -82,8 +86,15 @@ protected: private: TimelinePanel* AppendTimelinePanel(); + ProjectPanel* AppendProjectPanel(); + + template + T* AppendPanelInternal(QList& list); + void RemoveTimelinePanel(TimelinePanel *panel); + void RemoveProjectPanel(ProjectPanel* panel); + void TimelineFocused(ViewerOutput *viewer); QByteArray premaximized_state_; @@ -93,7 +104,7 @@ private: ParamPanel* param_panel_; SequenceViewerPanel* sequence_viewer_panel_; FootageViewerPanel* footage_viewer_panel_; - ProjectPanel* project_panel_; + QList project_panels_; ToolPanel* tool_panel_; QList timeline_panels_; AudioMonitorPanel* audio_monitor_panel_; @@ -114,6 +125,8 @@ private slots: void TimelineCloseRequested(); + void ProjectCloseRequested(); + }; OLIVE_NAMESPACE_EXIT