diff --git a/src/Lyrics.cpp b/src/Lyrics.cpp index c54aa4317..71dc2f16c 100644 --- a/src/Lyrics.cpp +++ b/src/Lyrics.cpp @@ -119,10 +119,10 @@ LyricsPanel::LyricsPanel(wxWindow* parent, wxWindowID id, parent->Bind(wxEVT_SHOW, &LyricsPanel::OnShow, this); - auto undoManager = project->GetUndoManager(); - undoManager->Bind(EVT_UNDO_PUSHED, &LyricsPanel::UpdateLyrics, this); - undoManager->Bind(EVT_UNDO_MODIFIED, &LyricsPanel::UpdateLyrics, this); - undoManager->Bind(EVT_UNDO_RESET, &LyricsPanel::UpdateLyrics, this); + auto &undoManager = UndoManager::Get( *project ); + undoManager.Bind(EVT_UNDO_PUSHED, &LyricsPanel::UpdateLyrics, this); + undoManager.Bind(EVT_UNDO_MODIFIED, &LyricsPanel::UpdateLyrics, this); + undoManager.Bind(EVT_UNDO_RESET, &LyricsPanel::UpdateLyrics, this); wxTheApp->Bind(EVT_AUDIOIO_PLAYBACK, &LyricsPanel::OnStartStop, this); wxTheApp->Bind(EVT_AUDIOIO_CAPTURE, &LyricsPanel::OnStartStop, this); diff --git a/src/Menus.cpp b/src/Menus.cpp index 267d9ce98..d6f861c0d 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -303,7 +303,7 @@ void MenuCreator::CreateMenusAndCommands(AudacityProject &project) void MenuManager::ModifyUndoMenuItems(AudacityProject &project) { wxString desc; - auto &undoManager = *project.GetUndoManager(); + auto &undoManager = UndoManager::Get( project ); auto &commandManager = *project.GetCommandManager(); int cur = undoManager.GetCurrentState(); @@ -493,7 +493,7 @@ CommandFlag MenuManager::GetUpdateFlags if( Clipboard::Get().Duration() > 0 ) flags |= ClipboardFlag; - auto &undoManager = *project.GetUndoManager(); + auto &undoManager = UndoManager::Get( project ); if (undoManager.UnsavedChanges() || !project.IsProjectSaved()) flags |= UnsavedChangesFlag; diff --git a/src/Project.cpp b/src/Project.cpp index 0fd0854de..1f4fe0011 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -1058,8 +1058,7 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id, gPrefs->Read(wxT("/FrequencySelectionFormatName"), wxT("")) ) ), mBandwidthSelectionFormatName( NumericTextCtrl::LookupFormat( NumericConverter::BANDWIDTH, - gPrefs->Read(wxT("/BandwidthSelectionFormatName"), wxT("")) ) ), - mUndoManager(std::make_unique()) + gPrefs->Read(wxT("/BandwidthSelectionFormatName"), wxT("")) ) ) , mCommandManager( std::make_unique() ) { auto &project = *this; @@ -2562,7 +2561,7 @@ void AudacityProject::OnCloseWindow(wxCloseEvent & event) // We may not bother to prompt the user to save, if the // project is now empty. if (event.CanVeto() && (mEmptyCanBeDirty || bHasTracks)) { - if (GetUndoManager()->UnsavedChanges()) { + if ( UndoManager::Get( project ).UnsavedChanges() ) { TitleRestorer Restorer( this );// RAII /* i18n-hint: The first %s numbers the project, the second %s is the project name.*/ wxString Title = wxString::Format(_("%sSave changes to %s?"), Restorer.sProjNumber, Restorer.sProjName); @@ -2678,7 +2677,7 @@ void AudacityProject::OnCloseWindow(wxCloseEvent & event) // This must be done before the following Deref() since it holds // references to the DirManager. - GetUndoManager()->ClearStates(); + UndoManager::Get( project ).ClearStates(); // MM: Tell the DirManager it can now DELETE itself // if it finds it is no longer needed. If it is still @@ -3854,6 +3853,7 @@ bool AudacityProject::DoSave (const bool fromSaveAs, { // See explanation above // ProjectDisabler disabler(this); + auto &proj = *this; wxASSERT_MSG(!bWantSaveCopy || fromSaveAs, "Copy Project SHOULD only be availabele from SaveAs"); @@ -3864,7 +3864,7 @@ bool AudacityProject::DoSave (const bool fromSaveAs, auto &tracks = TrackList::Get( project ); if ( ! tracks.Any() ) { - if (GetUndoManager()->UnsavedChanges() && mEmptyCanBeDirty) { + if ( UndoManager::Get( proj ).UnsavedChanges() && mEmptyCanBeDirty) { int result = AudacityMessageBox(_("Your project is now empty.\nIf saved, the project will have no tracks.\n\nTo save any previously open tracks:\nClick 'No', Edit > Undo until all tracks\nare open, then File > Save Project.\n\nSave anyway?"), _("Warning - Empty Project"), wxYES_NO | wxICON_QUESTION, this); @@ -4074,8 +4074,7 @@ bool AudacityProject::DoSave (const bool fromSaveAs, mLastSavedTracks->Clear(); mLastSavedTracks = TrackList::Create(); - auto &project = *this; - auto &tracks = TrackList::Get( project ); + auto &tracks = TrackList::Get( proj ); for ( auto t : tracks.Any() ) { mLastSavedTracks->Add(t->Duplicate()); @@ -4085,7 +4084,7 @@ bool AudacityProject::DoSave (const bool fromSaveAs, // wt->MarkSaved(); } - GetUndoManager()->StateSaved(); + UndoManager::Get( proj ).StateSaved(); } // If we get here, saving the project was successful, so we can DELETE @@ -4634,14 +4633,15 @@ void AudacityProject::InitialState() auto &project = *this; auto &tracks = TrackList::Get( project ); auto &viewInfo = ViewInfo::Get( project ); + auto &undoManager = UndoManager::Get( project ); - GetUndoManager()->ClearStates(); + undoManager.ClearStates(); - GetUndoManager()->PushState( + undoManager.PushState( &tracks, viewInfo.selectedRegion, mTags, _("Created new project"), wxT("")); - GetUndoManager()->StateSaved(); + undoManager.StateSaved(); GetMenuManager(*this).ModifyUndoMenuItems(*this); @@ -4652,7 +4652,8 @@ bool AudacityProject::UndoAvailable() { auto &project = *this; auto &tracks = TrackList::Get( project ); - return GetUndoManager()->UndoAvailable() && + auto &undoManager = UndoManager::Get( project ); + return undoManager.UndoAvailable() && !tracks.HasPendingTracks(); } @@ -4660,8 +4661,9 @@ bool AudacityProject::RedoAvailable() { auto &project = *this; auto &tracks = TrackList::Get( project ); - return GetUndoManager()->RedoAvailable() && - !tracks.HasPendingTracks(); + auto &undoManager = UndoManager::Get( project ); + return undoManager.RedoAvailable() && + !tracks.HasPendingTracks(); } void AudacityProject::PushState(const wxString &desc, const wxString &shortDesc) @@ -4676,7 +4678,8 @@ void AudacityProject::PushState(const wxString &desc, auto &project = *this; auto &tracks = TrackList::Get( project ); auto &viewInfo = ViewInfo::Get( project ); - GetUndoManager()->PushState( + auto &undoManager = UndoManager::Get( project ); + undoManager.PushState( &tracks, viewInfo.selectedRegion, mTags, desc, shortDesc, flags); @@ -4696,7 +4699,9 @@ void AudacityProject::PushState(const wxString &desc, void AudacityProject::RollbackState() { - SetStateTo(GetUndoManager()->GetCurrentState()); + auto &project = *this; + auto &undoManager = UndoManager::Get( project ); + SetStateTo( undoManager.GetCurrentState() ); } void AudacityProject::ModifyState(bool bWantsAutoSave) @@ -4704,7 +4709,8 @@ void AudacityProject::ModifyState(bool bWantsAutoSave) auto &project = *this; auto &tracks = TrackList::Get( project ); auto &viewInfo = ViewInfo::Get( project ); - GetUndoManager()->ModifyState( + auto &undoManager = UndoManager::Get( project ); + undoManager.ModifyState( &tracks, viewInfo.selectedRegion, mTags); if (bWantsAutoSave) AutoSave(); @@ -4772,7 +4778,9 @@ void AudacityProject::PopState(const UndoState &state) void AudacityProject::SetStateTo(unsigned int n) { - GetUndoManager()->SetStateTo(n, + auto &project = *this; + auto &undoManager = UndoManager::Get( project ); + undoManager.SetStateTo(n, [this]( const UndoState &state ){ PopState(state); } ); HandleResize(); @@ -5354,7 +5362,8 @@ void AudacityProject::ResetProjectToEmpty() { projectFileIO.ResetProjectFileIO(); mDirty = false; - GetUndoManager()->ClearStates(); + auto &undoManager = UndoManager::Get( project ); + undoManager.ClearStates(); } void AudacityProject::ResetProjectFileIO() @@ -5545,8 +5554,10 @@ MixerBoardFrame* AudacityProject::GetMixerBoardFrame(bool create) HistoryWindow *AudacityProject::GetHistoryWindow(bool create) { + auto &project = *this; + auto &undoManager = UndoManager::Get( project ); if (create && !mHistoryWindow) - mHistoryWindow = safenew HistoryWindow{ this, GetUndoManager() }; + mHistoryWindow = safenew HistoryWindow{ this, &undoManager }; return mHistoryWindow; } diff --git a/src/Project.h b/src/Project.h index def39c882..dc4d0a0d0 100644 --- a/src/Project.h +++ b/src/Project.h @@ -200,8 +200,6 @@ class AUDACITY_DLL_API AudacityProject final : public wxFrame, AudioIOStartStreamOptions GetDefaultPlayOptions(); AudioIOStartStreamOptions GetSpeedPlayOptions(); - UndoManager *GetUndoManager() { return mUndoManager.get(); } - sampleFormat GetDefaultFormat() { return mDefaultFormat; } double GetRate() const { return mRate; } @@ -565,8 +563,6 @@ public: static ODLock &AllProjectDeleteMutex(); private: - // History/Undo manager - std::unique_ptr mUndoManager; bool mDirty{ false }; // Commands diff --git a/src/UndoManager.cpp b/src/UndoManager.cpp index 011f935fc..070a8d6a0 100644 --- a/src/UndoManager.cpp +++ b/src/UndoManager.cpp @@ -28,6 +28,7 @@ UndoManager #include "BlockFile.h" #include "Clipboard.h" #include "Diags.h" +#include "Project.h" #include "Sequence.h" #include "WaveClip.h" #include "WaveTrack.h" // temp @@ -63,6 +64,20 @@ struct UndoStackElem { wxString shortDescription; }; +static const AudacityProject::AttachedObjects::RegisteredFactory key{ + [](AudacityProject&) { return std::make_unique(); } +}; + +UndoManager &UndoManager::Get( AudacityProject &project ) +{ + return project.AttachedObjects::Get< UndoManager >( key ); +} + +const UndoManager &UndoManager::Get( const AudacityProject &project ) +{ + return Get( const_cast< AudacityProject & >( project ) ); +} + UndoManager::UndoManager() { current = -1; diff --git a/src/UndoManager.h b/src/UndoManager.h index 29d713c41..d0e4e5c1c 100644 --- a/src/UndoManager.h +++ b/src/UndoManager.h @@ -52,6 +52,7 @@ #include #include // to declare custom event types #include "ondemand/ODTaskThread.h" +#include "ClientData.h" #include "SelectedRegion.h" // Events emitted by UndoManager for the use of listeners @@ -67,6 +68,7 @@ wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API, EVT_UNDO_MODIFIED, wxCommandEvent); // contents did not change other than the pointer to current state wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API, EVT_UNDO_RESET, wxCommandEvent); +class AudacityProject; class Tags; class Track; class TrackList; @@ -102,8 +104,14 @@ inline UndoPush operator | (UndoPush a, UndoPush b) inline UndoPush operator & (UndoPush a, UndoPush b) { return static_cast(static_cast(a) & static_cast(b)); } -class AUDACITY_DLL_API UndoManager : public wxEvtHandler { +class AUDACITY_DLL_API UndoManager + : public wxEvtHandler + , public ClientData::Base +{ public: + static UndoManager &Get( AudacityProject &project ); + static const UndoManager &Get( const AudacityProject &project ); + UndoManager(); ~UndoManager(); diff --git a/src/effects/EffectRack.cpp b/src/effects/EffectRack.cpp index 2310ea8fb..b69e8fa19 100644 --- a/src/effects/EffectRack.cpp +++ b/src/effects/EffectRack.cpp @@ -294,7 +294,7 @@ void EffectRack::OnApply(wxCommandEvent & WXUNUSED(evt)) AudacityProject *project = GetActiveProject(); bool success = false; - auto state = project->GetUndoManager()->GetCurrentState(); + auto state = UndoManager::Get( *project ).GetCurrentState(); auto cleanup = finally( [&] { if(!success) project->SetStateTo(state); diff --git a/src/menus/ClipMenus.cpp b/src/menus/ClipMenus.cpp index 8e46ea70f..ed7863864 100644 --- a/src/menus/ClipMenus.cpp +++ b/src/menus/ClipMenus.cpp @@ -691,7 +691,7 @@ double DoClipMove void DoClipLeftOrRight (AudacityProject &project, bool right, bool keyUp ) { - auto &undoManager = *project.GetUndoManager(); + auto &undoManager = UndoManager::Get( project ); if (keyUp) { undoManager.StopConsolidating(); diff --git a/src/menus/EditMenus.cpp b/src/menus/EditMenus.cpp index ae220464c..57a3aef10 100644 --- a/src/menus/EditMenus.cpp +++ b/src/menus/EditMenus.cpp @@ -223,7 +223,7 @@ bool DoEditMetadata void DoUndo(AudacityProject &project) { auto trackPanel = project.GetTrackPanel(); - auto &undoManager = *project.GetUndoManager(); + auto &undoManager = UndoManager::Get( project ); if (!project.UndoAvailable()) { AudacityMessageBox(_("Nothing to undo")); @@ -258,7 +258,7 @@ void OnRedo(const CommandContext &context) { auto &project = context.project; auto trackPanel = project.GetTrackPanel(); - auto &undoManager = *project.GetUndoManager(); + auto &undoManager = UndoManager::Get( project ); if (!project.RedoAvailable()) { AudacityMessageBox(_("Nothing to redo")); diff --git a/src/menus/TrackMenus.cpp b/src/menus/TrackMenus.cpp index 232751979..e1198bdfa 100644 --- a/src/menus/TrackMenus.cpp +++ b/src/menus/TrackMenus.cpp @@ -905,7 +905,7 @@ void OnResample(const CommandContext &context) auto &project = context.project; auto projectRate = project.GetRate(); auto &tracks = TrackList::Get( project ); - auto &undoManager = *project.GetUndoManager(); + auto &undoManager = UndoManager::Get( project ); int newRate; diff --git a/src/menus/TransportMenus.cpp b/src/menus/TransportMenus.cpp index 4ddd09d1e..06e271b04 100644 --- a/src/menus/TransportMenus.cpp +++ b/src/menus/TransportMenus.cpp @@ -400,7 +400,7 @@ void OnRecord2ndChoice(const CommandContext &context) void OnTimerRecord(const CommandContext &context) { auto &project = context.project; - auto &undoManager = *project.GetUndoManager(); + auto &undoManager = UndoManager::Get( project ); // MY: Due to improvements in how Timer Recording saves and/or exports // it is now safer to disable Timer Recording when there is more than diff --git a/src/ondemand/ODTask.cpp b/src/ondemand/ODTask.cpp index 36b1ef2f0..679a6ca73 100644 --- a/src/ondemand/ODTask.cpp +++ b/src/ondemand/ODTask.cpp @@ -135,7 +135,7 @@ void ODTask::DoSome(float amountWork) if(IsTaskAssociatedWithProject(gAudacityProjects[i].get())) { //mark the changes so that the project can be resaved. - gAudacityProjects[i]->GetUndoManager()->SetODChangesFlag(); + UndoManager::Get( *gAudacityProjects[i] ).SetODChangesFlag(); break; } } @@ -161,7 +161,7 @@ void ODTask::DoSome(float amountWork) //this assumes tasks are only associated with one project. gAudacityProjects[i]->GetEventHandler()->AddPendingEvent(event); //mark the changes so that the project can be resaved. - gAudacityProjects[i]->GetUndoManager()->SetODChangesFlag(); + UndoManager::Get( *gAudacityProjects[i] ).SetODChangesFlag(); break; } }