diff --git a/src/AudacityApp.cpp b/src/AudacityApp.cpp index 66220b96d..1df4cf0cb 100644 --- a/src/AudacityApp.cpp +++ b/src/AudacityApp.cpp @@ -813,8 +813,6 @@ bool AudacityApp::MRUOpen(const FilePath &fullPathStr) { if (ProjectFileManager::IsAlreadyOpen(fullPathStr)) return false; - if (proj && !ProjectManager::SafeToOpenProjectInto(*proj)) - proj = nullptr; ( void ) ProjectManager::OpenProject( proj, fullPathStr, true /* addtohistory */, false /* reuseNonemptyProject */ ); } diff --git a/src/ProjectFileManager.cpp b/src/ProjectFileManager.cpp index 827ecc472..21479469a 100644 --- a/src/ProjectFileManager.cpp +++ b/src/ProjectFileManager.cpp @@ -862,14 +862,9 @@ bool ProjectFileManager::IsAlreadyOpen(const FilePath &projPathName) return false; } -// FIXME:? TRAP_ERR This should return a result that is checked. -// See comment in AudacityApp::MRUOpen(). -AudacityProject *ProjectFileManager::OpenFile( +AudacityProject *ProjectFileManager::OpenFile( const ProjectChooserFn &chooser, const FilePath &fileNameArg, bool addtohistory) { - auto &project = mProject; - auto &window = ProjectWindow::Get( project ); - // On Win32, we may be given a short (DOS-compatible) file name on rare // occasions (e.g. stuff like "C:\PROGRA~1\AUDACI~1\PROJEC~1.AUP"). We // convert these to long file name first. @@ -902,7 +897,7 @@ AudacityProject *ProjectFileManager::OpenFile( "You are trying to open an automatically created backup file.\nDoing this may result in severe data loss.\n\nPlease open the actual Audacity project file instead."), XO("Warning - Backup File Detected"), wxOK | wxCENTRE, - &window); + nullptr); return nullptr; } @@ -911,7 +906,7 @@ AudacityProject *ProjectFileManager::OpenFile( XO("Could not open file: %s").Format( fileName ), XO("Error Opening File"), wxOK | wxCENTRE, - &window); + nullptr); return nullptr; } @@ -932,7 +927,7 @@ AudacityProject *ProjectFileManager::OpenFile( XO("Could not open file: %s").Format( fileName ), XO("Error opening file"), wxOK | wxCENTRE, - &window); + nullptr); return nullptr; } @@ -943,7 +938,7 @@ AudacityProject *ProjectFileManager::OpenFile( XO("File may be invalid or corrupted: \n%s").Format( fileName ), XO("Error Opening File or Project"), wxOK | wxCENTRE, - &window); + nullptr); return nullptr; } @@ -961,6 +956,7 @@ AudacityProject *ProjectFileManager::OpenFile( #endif #ifdef USE_MIDI if (FileNames::IsMidi(fileName)) { + auto &project = chooser(false); // If this succeeds, indo history is incremented, and it also does // ZoomAfterImport: if(DoImportMIDI(project, fileName)) @@ -968,18 +964,21 @@ AudacityProject *ProjectFileManager::OpenFile( return nullptr; } #endif + auto &project = chooser(false); // Undo history is incremented inside this: - if (Import(fileName)) { + if (Get(project).Import(fileName)) { + // Undo history is incremented inside this: // Bug 2743: Don't zoom with lof. if (!fileName.AfterLast('.').IsSameAs(wxT("lof"), false)) - window.ZoomAfterImport(nullptr); + ProjectWindow::Get(project).ZoomAfterImport(nullptr); return &project; } return nullptr; } } - return OpenProjectFile(fileName, addtohistory); + auto &project = chooser(true); + return Get(project).OpenProjectFile(fileName, addtohistory); } AudacityProject *ProjectFileManager::OpenProjectFile( diff --git a/src/ProjectFileManager.h b/src/ProjectFileManager.h index 75b4cf47d..c5d7c9a63 100644 --- a/src/ProjectFileManager.h +++ b/src/ProjectFileManager.h @@ -11,6 +11,7 @@ Paul Licameli split from AudacityProject.h #ifndef __AUDACITY_PROJECT_FILE_MANAGER__ #define __AUDACITY_PROJECT_FILE_MANAGER__ +#include #include #include @@ -81,13 +82,17 @@ public: static bool IsAlreadyOpen(const FilePath &projPathName); + //! A function that returns a project to use for opening a file; argument is true if opening a project file + using ProjectChooserFn = std::function; + /*! Opens files of many kinds. In case of import (sound, MIDI, or .aup), the undo history is pushed. + @param chooser told whether opening a project file; decides which project to open into @param fileName the name and contents are examined to decide a type and open appropriately @param addtohistory whether to add .aup3 files to the MRU list (but always done for imports) @return if something was successfully opened, the project containing it; else null */ - AudacityProject *OpenFile( + static AudacityProject *OpenFile( const ProjectChooserFn &chooser, const FilePath &fileName, bool addtohistory = true); bool Import(const FilePath &fileName, diff --git a/src/ProjectManager.cpp b/src/ProjectManager.cpp index 07e9a4317..7d7c2bcf1 100644 --- a/src/ProjectManager.cpp +++ b/src/ProjectManager.cpp @@ -842,9 +842,12 @@ void ProjectManager::OnOpenAudioFile(wxCommandEvent & event) { const wxString &cmd = event.GetString(); if (!cmd.empty()) { - if (auto project = ProjectFileManager::Get( mProject ).OpenFile(cmd)) { + ProjectChooser chooser{ &mProject, true }; + if (auto project = ProjectFileManager::OpenFile( + std::ref(chooser), cmd)) { auto &window = GetProjectFrame( *project ); window.RequestUserAttention(); + chooser.Commit(); } } } @@ -871,8 +874,6 @@ void ProjectManager::OpenFiles(AudacityProject *proj) if (ProjectFileManager::IsAlreadyOpen(fileName)) continue; // Skip ones that are already open. - if (proj && !SafeToOpenProjectInto(*proj)) - proj = nullptr; proj = OpenProject( proj, fileName, true /* addtohistory */, false /* reuseNonemptyProject */ ); } @@ -901,39 +902,60 @@ bool ProjectManager::SafeToOpenProjectInto(AudacityProject &proj) return true; } -AudacityProject *ProjectManager::OpenProject( - AudacityProject *pProject, const FilePath &fileNameArg, - bool addtohistory, bool) +ProjectManager::ProjectChooser::~ProjectChooser() { - bool success = false; - AudacityProject *pNewProject = nullptr; - if ( ! pProject ) - pProject = pNewProject = New(); - auto cleanup = finally( [&] { - if ( pNewProject ) - GetProjectFrame( *pNewProject ).Close(true); - else if ( !success ) + if (mpUsedProject) { + if (mpUsedProject == mpGivenProject) { // Ensure that it happens here: don't wait for the application level // exception handler, because the exception may be intercepted - ProjectHistory::Get(*pProject).RollbackState(); + ProjectHistory::Get(*mpGivenProject).RollbackState(); // Any exception now continues propagating - } ); - ProjectFileManager::Get( *pProject ).OpenFile( fileNameArg, addtohistory ); - - // The above didn't throw, so change what finally will do - success = true; - pNewProject = nullptr; - - auto &projectFileIO = ProjectFileIO::Get( *pProject ); - if( projectFileIO.IsRecovered() ) { - auto &window = ProjectWindow::Get( *pProject ); - window.Zoom( window.GetZoomOfToFit() ); - // "Project was recovered" replaces "Create new project" in Undo History. - auto &undoManager = UndoManager::Get( *pProject ); - undoManager.RemoveStates(0, 1); + } + else + GetProjectFrame( *mpUsedProject ).Close(true); } +} - return pProject; +AudacityProject & +ProjectManager::ProjectChooser::operator() ( bool openingProjectFile ) +{ + if (mpGivenProject) { + // Always check before opening a project file (for safety); + // May check even when opening other files + // (to preserve old behavior; as with the File > Open command specifying + // multiple files of whatever types, so that each gets its own window) + bool checkReuse = (openingProjectFile || !mReuseNonemptyProject); + if (!checkReuse || SafeToOpenProjectInto(*mpGivenProject)) + return *(mpUsedProject = mpGivenProject); + } + return *(mpUsedProject = New()); +} + +void ProjectManager::ProjectChooser::Commit() +{ + mpUsedProject = nullptr; +} + +AudacityProject *ProjectManager::OpenProject( + AudacityProject *pGivenProject, const FilePath &fileNameArg, + bool addtohistory, bool reuseNonemptyProject) +{ + ProjectManager::ProjectChooser chooser{ pGivenProject, reuseNonemptyProject }; + if (auto pProject = ProjectFileManager::OpenFile( + std::ref(chooser), fileNameArg, addtohistory )) { + chooser.Commit(); + + auto &projectFileIO = ProjectFileIO::Get( *pProject ); + if( projectFileIO.IsRecovered() ) { + auto &window = ProjectWindow::Get( *pProject ); + window.Zoom( window.GetZoomOfToFit() ); + // "Project was recovered" replaces "Create new project" in Undo History. + auto &undoManager = UndoManager::Get( *pProject ); + undoManager.RemoveStates(0, 1); + } + return pProject; + } + return nullptr; } // This is done to empty out the tracks, but without creating a new project. diff --git a/src/ProjectManager.h b/src/ProjectManager.h index 22d800ed2..914d468cf 100644 --- a/src/ProjectManager.h +++ b/src/ProjectManager.h @@ -48,17 +48,52 @@ public: //! False when it is unsafe to overwrite proj with contents of an .aup3 file static bool SafeToOpenProjectInto(AudacityProject &proj); + //! Callable object that supplies the `chooser` argument of ProjectFileManager::OpenFile + /*! + Its operator(), called lower down in ProjectFileManager, decides which project to put new file data into, + using file type information deduced there. It may have the side effect of creating a project. + + At the higher level where it is constructed, it provides conditional RAII. + One indicates there that the file opening succeeded by calling Commit(). But if that is never + called, creation of projects, or changes to a preexisting project, are undone. + */ + class ProjectChooser { + public: + /*! + @param pProject if not null, an existing project to reuse if possible + @param reuseNonemptyProject if true, may reuse the given project when nonempty, + but only if importing (not for a project file) + */ + ProjectChooser( AudacityProject *pProject, bool reuseNonemptyProject ) + : mpGivenProject{ pProject } + , mReuseNonemptyProject{ reuseNonemptyProject } + {} + //! Don't copy. Use std::ref to pass it to ProjectFileManager + ProjectChooser( const ProjectChooser& ) PROHIBITED; + //! Destroy any fresh project, or rollback the existing project, unless committed + ~ProjectChooser(); + //! May create a fresh project + AudacityProject &operator() ( bool openingProjectFile ); + //! Commit the creation of any fresh project or changes to the existing project + void Commit(); + + private: + AudacityProject *mpGivenProject; + AudacityProject *mpUsedProject = nullptr; + bool mReuseNonemptyProject; + }; + //! Open a file into an AudacityProject, returning the project, or nullptr for failure /*! If an exception escapes this function, no projects are created. - @param pProject if not null, a project that may be reused + @param pGivenProject if not null, a project that may be reused @param fileNameArg path to the file to open; not always an Audacity project file, may be an import @param addtohistory whether to add .aup3 files to the MRU list (but always done for imports) @param reuseNonemptyProject if true, may reuse the given project when nonempty, but only if importing (not for a project file) */ static AudacityProject *OpenProject( - AudacityProject *pProject, + AudacityProject *pGivenProject, const FilePath &fileNameArg, bool addtohistory, bool reuseNonemptyProject); void ResetProjectToEmpty(); diff --git a/src/commands/OpenSaveCommands.cpp b/src/commands/OpenSaveCommands.cpp index ab7d8cdd0..0b325ea96 100644 --- a/src/commands/OpenSaveCommands.cpp +++ b/src/commands/OpenSaveCommands.cpp @@ -65,8 +65,10 @@ bool OpenProjectCommand::Apply(const CommandContext & context){ } else { - ProjectFileManager::Get( context.project ) - .OpenFile(mFileName, mbAddToHistory); + ProjectManager::ProjectChooser chooser{ &context.project, true }; + if(ProjectFileManager::OpenFile( + std::ref(chooser), mFileName, mbAddToHistory)) + chooser.Commit(); } const auto &newFileName = projectFileIO.GetFileName();