Audacity 3.2.0
ProjectFileManager.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5ProjectFileManager.cpp
6
7Paul Licameli split from AudacityProject.cpp
8
9**********************************************************************/
10
11#include "ProjectFileManager.h"
12
13#include <wx/crt.h> // for wxPrintf
14
15#if defined(__WXGTK__)
16#include <wx/evtloop.h>
17#endif
18
19#include <wx/frame.h>
20#include <wx/log.h>
21#include "BasicUI.h"
22#include "CodeConversions.h"
23#include "Legacy.h"
25#include "Project.h"
26#include "ProjectFileIO.h"
27#include "ProjectHistory.h"
30#include "ProjectWindows.h"
31#include "ProjectRate.h"
32#include "ProjectSettings.h"
33#include "ProjectStatus.h"
34#include "ProjectWindow.h"
35#include "SelectFile.h"
36#include "SelectUtilities.h"
37#include "SelectionState.h"
38#include "Tags.h"
39#include "TempDirectory.h"
40#include "TrackPanelAx.h"
41#include "TrackPanel.h"
42#include "UndoManager.h"
43#include "WaveTrack.h"
44#include "wxFileNameWrapper.h"
45#include "Export.h"
46#include "Import.h"
48#include "ImportPlugin.h"
49#include "import/ImportMIDI.h"
52#include "AudacityMessageBox.h"
53#include "widgets/FileHistory.h"
55#include "widgets/Warning.h"
56#include "wxPanelWrapper.h"
57#include "XMLFileReader.h"
58
59#include "HelpText.h"
60
61#include <optional>
62
63#include "RealtimeEffectList.h"
64
66 []( AudacityProject &parent ){
67 auto result = std::make_shared< ProjectFileManager >( parent );
68 return result;
69 }
70};
71
73{
74 return project.AttachedObjects::Get< ProjectFileManager >( sFileManagerKey );
75}
76
78{
79 return Get( const_cast< AudacityProject & >( project ) );
80}
81
83{
84 InvisibleTemporaryProject tempProject;
85 auto &project = tempProject.Project();
86 auto &projectFileManager = Get(project);
87 // Read the project, discarding autosave
88 projectFileManager.ReadProjectFile(filename, true);
89
90 if (projectFileManager.mLastSavedTracks) {
91 for (auto wt : projectFileManager.mLastSavedTracks->Any<WaveTrack>())
92 wt->CloseLock();
93 projectFileManager.mLastSavedTracks.reset();
94 }
95
96 // Side-effect on database is done, and destructor of tempProject
97 // closes the temporary project properly
98}
99
101: mProject{ project }
102{
103}
104
106
107namespace {
108
109const char *const defaultHelpUrl =
110 "FAQ:Errors_on_opening_or_recovering_an_Audacity_project";
111
112using Pair = std::pair< const char *, const char * >;
114 {
115 "not well-formed (invalid token)",
116 "Error:_not_well-formed_(invalid_token)_at_line_x"
117 },
118 {
119 "reference to invalid character number",
120 "Error_Opening_Project:_Reference_to_invalid_character_number_at_line_x"
121 },
122 {
123 "mismatched tag",
124 "#mismatched"
125 },
126// This error with FAQ entry is reported elsewhere, not here....
127//#[[#corrupt|Error Opening File or Project: File may be invalid or corrupted]]
128};
129
130wxString FindHelpUrl( const TranslatableString &libraryError )
131{
132 wxString helpUrl;
133 if ( !libraryError.empty() ) {
134 helpUrl = defaultHelpUrl;
135
136 auto msgid = libraryError.MSGID().GET();
137 auto found = std::find_if( begin(helpURLTable), end(helpURLTable),
138 [&]( const Pair &pair ) {
139 return msgid.Contains( pair.first ); }
140 );
141 if (found != end(helpURLTable)) {
142 auto url = found->second;
143 if (url[0] == '#')
144 helpUrl += url;
145 else
146 helpUrl = url;
147 }
148 }
149
150 return helpUrl;
151}
152
153}
154
156 const FilePath &fileName, bool discardAutosave )
158{
159 auto &project = mProject;
160 auto &projectFileIO = ProjectFileIO::Get( project );
161 auto &window = GetProjectFrame( project );
162
166 auto parseResult = projectFileIO.LoadProject(fileName, discardAutosave);
167 const bool bParseSuccess = parseResult.has_value();
168
169 bool err = false;
170 bool waveTrackLinkTypeChanged = false;
171
172 TranslatableString otherError;
173
174 if (bParseSuccess)
175 {
177 for (auto t : tracks) {
178 const auto linkType = t->GetLinkType();
179 // Note, the next function may have an important upgrading side effect,
180 // and return no error; or it may find a real error and repair it, but
181 // that repaired track won't be used because opening will fail.
182 if (!t->LinkConsistencyFix()) {
183 otherError = XO("A channel of a stereo track was missing.");
184 err = true;
185 }
186 if(!err &&
187 linkType != ChannelGroup::LinkType::None &&
188 t->GetLinkType() == ChannelGroup::LinkType::None)
189 {
190 //Not an elegant way to deal with wave track linking
191 //compatibility between versions
192 if(const auto left = dynamic_cast<WaveTrack*>(t))
193 {
195 if(const auto right = dynamic_cast<WaveTrack*>(*tracks.Find(left).advance(1)))
197 waveTrackLinkTypeChanged = true;
198 }
199 }
200
201 if (const auto message = t->GetErrorOpening()) {
202 wxLogWarning(
203 wxT("Track %s had error reading clip values from project file."),
204 t->GetName());
205 err = true;
206 // Keep at most one of the error messages
207 otherError = *message;
208 }
209 }
210
211 if (!err) {
212 if(waveTrackLinkTypeChanged && !discardAutosave)
213 {
215"This project contained stereo tracks with non-aligned content.\n"
216"This feature is not supported in Audacity versions past 3.3.3.\n"
217"These stereo tracks have been split into mono tracks.\n"
218"As a result, some realtime effects may be missing.\n"
219"Please verify that everything works as intended before saving."
220 ));
221 }
222
223 parseResult->Commit();
224 if (discardAutosave)
225 // REVIEW: Failure OK?
226 projectFileIO.AutoSaveDelete();
227 else if (projectFileIO.IsRecovered()) {
228 bool resaved = false;
229
230 if (!projectFileIO.IsTemporary() &&
231 !waveTrackLinkTypeChanged)
232 {
233 // Re-save non-temporary project to its own path. This
234 // might fail to update the document blob in the database.
235 resaved = projectFileIO.SaveProject(fileName, nullptr);
236 }
237
239 resaved
240 ? XO(
241"This project was not saved properly the last time Audacity ran.\n\n"
242"It has been recovered to the last snapshot.")
243 : XO(
244"This project was not saved properly the last time Audacity ran.\n\n"
245"It has been recovered to the last snapshot, but you must save it\n"
246"to preserve its contents."),
247 XO("Project Recovered"),
248 wxICON_WARNING,
249 &window);
250 }
251
252 // By making a duplicate set of pointers to the existing blocks
253 // on disk, we add one to their reference count, guaranteeing
254 // that their reference counts will never reach zero and thus
255 // the version saved on disk will be preserved until the
256 // user selects Save().
257 mLastSavedTracks = TrackList::Create( nullptr );
258 for (auto t : tracks)
259 mLastSavedTracks->Append(std::move(*t->Duplicate()));
260 }
261 }
262
263 return {
264 bParseSuccess,
265 err,
266 (bParseSuccess ? otherError : projectFileIO.GetLastError()),
267 FindHelpUrl(projectFileIO.GetLibraryError())
268 };
269}
270
272{
273 auto &projectFileIO = ProjectFileIO::Get(mProject);
274
275 // Prompt for file name?
276 if (projectFileIO.IsTemporary())
277 {
278 return SaveAs(true);
279 }
280
281 return DoSave(projectFileIO.GetFileName(), false);
282}
283
284#if 0
285// I added this to "fix" bug #334. At that time, we were on wxWidgets 2.8.12 and
286// there was a window between the closing of the "Save" progress dialog and the
287// end of the actual save where the user was able to close the project window and
288// recursively enter the Save code (where they could inadvertently cause the issue
289// described in #334).
290//
291// When we converted to wx3, this "disabler" caused focus problems when returning
292// to the project after the save (bug #1172) because the focus and activate events
293// weren't being dispatched and the focus would get lost.
294//
295// After some testing, it looks like the window described above no longer exists,
296// so I've disabled the disabler. However, I'm leaving it here in case we run
297// into the problem in the future. (even though it can't be used as-is)
298class ProjectDisabler
299{
300public:
301 ProjectDisabler(wxWindow *w)
302 : mWindow(w)
303 {
304 mWindow->GetEventHandler()->SetEvtHandlerEnabled(false);
305 }
306 ~ProjectDisabler()
307 {
308 mWindow->GetEventHandler()->SetEvtHandlerEnabled(true);
309 }
310private:
311 wxWindow *mWindow;
312};
313#endif
314
315// Assumes ProjectFileIO::mFileName has been set to the desired path.
316bool ProjectFileManager::DoSave(const FilePath & fileName, const bool fromSaveAs)
317{
318 // See explanation above
319 // ProjectDisabler disabler(this);
320 auto &proj = mProject;
321 auto &window = GetProjectFrame( proj );
322 auto &projectFileIO = ProjectFileIO::Get( proj );
323 const auto &settings = ProjectSettings::Get( proj );
324
325 // Some confirmation dialogs
326 {
327 if (TempDirectory::FATFilesystemDenied(fileName, XO("Projects cannot be saved to FAT drives.")))
328 {
329 return false;
330 }
331
332 auto &tracks = TrackList::Get( proj );
333 if (tracks.empty())
334 {
335 if (UndoManager::Get( proj ).UnsavedChanges() &&
336 settings.EmptyCanBeDirty())
337 {
338 int result = AudacityMessageBox(
339 XO(
340 "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?"),
341 XO("Warning - Empty Project"),
342 wxYES_NO | wxICON_QUESTION,
343 &window);
344 if (result == wxNO)
345 {
346 return false;
347 }
348 }
349 }
350
351 wxULongLong fileSize = wxFileName::GetSize(projectFileIO.GetFileName());
352
353 wxDiskspaceSize_t freeSpace;
354 if (wxGetDiskSpace(FileNames::AbbreviatePath(fileName), NULL, &freeSpace))
355 {
356 if (freeSpace.GetValue() <= fileSize.GetValue())
357 {
359 XO("Insufficient Disk Space"),
360 XO("The project size exceeds the available free space on the target disk.\n\n"
361 "Please select a different disk with more free space."),
362 "Error:_Disk_full_or_not_writable"
363 );
364
365 return false;
366 }
367 }
368 }
369 // End of confirmations
370
371 // Always save a backup of the original project file
372 std::optional<ProjectFileIO::BackupProject> pBackupProject;
373 if (fromSaveAs && wxFileExists(fileName))
374 {
375 pBackupProject.emplace(projectFileIO, fileName);
376 if (!pBackupProject->IsOk())
377 return false;
378 }
379
380 if (FileNames::IsOnFATFileSystem(fileName))
381 {
382 if (wxFileName::GetSize(projectFileIO.GetFileName()) > UINT32_MAX)
383 {
385 XO("Error Saving Project"),
386 XO("The project exceeds the maximum size of 4GB when writing to a FAT32 formatted filesystem."),
387 "Error:_Unsuitable_drive"
388 );
389 return false;
390 }
391 }
392
393 bool success = projectFileIO.SaveProject(fileName, mLastSavedTracks.get());
394 if (!success)
395 {
396 // Show this error only if we didn't fail reconnection in SaveProject
397 // REVIEW: Could HasConnection() be true but SaveProject() still have failed?
398 if (!projectFileIO.HasConnection()) {
399 using namespace BasicUI;
401 XO("Error Saving Project"),
403 "Error:_Disk_full_or_not_writable",
404 ErrorDialogOptions{ ErrorDialogType::ModalErrorReport } );
405 }
406 return false;
407 }
408
409 proj.SetProjectName(wxFileName(fileName).GetName());
410 projectFileIO.SetProjectTitle();
411
413 ProjectStatus::Get(proj).Set(XO("Saved %s").Format(fileName));
414
416 {
417 mLastSavedTracks->Clear();
418 }
420
421 auto &tracks = TrackList::Get(proj);
422 for (auto t : tracks)
423 mLastSavedTracks->Append(std::move(*t->Duplicate()));
424
425 // If we get here, saving the project was successful, so we can DELETE
426 // any backup project.
427 if (pBackupProject)
428 pBackupProject->Discard();
429
430 return true;
431}
432
433// This version of SaveAs is invoked only from scripting and does not
434// prompt for a file name
435bool ProjectFileManager::SaveAs(const FilePath &newFileName, bool addToHistory /*= true*/)
436{
437 auto &project = mProject;
438 auto &projectFileIO = ProjectFileIO::Get( project );
439
440 auto oldFileName = projectFileIO.GetFileName();
441
442 bool bOwnsNewName = !projectFileIO.IsTemporary() && (oldFileName == newFileName);
443 //check to see if the NEW project file already exists.
444 //We should only overwrite it if this project already has the same name, where the user
445 //simply chose to use the save as command although the save command would have the effect.
446 if( !bOwnsNewName && wxFileExists(newFileName)) {
448 nullptr,
449 XO("The project was not saved because the file name provided would overwrite another project.\nPlease try again and select an original name."),
450 XO("Error Saving Project"),
451 wxOK|wxICON_ERROR );
452 m.ShowModal();
453 return false;
454 }
455
456 auto success = DoSave(newFileName, !bOwnsNewName);
457 if (success && addToHistory) {
458 FileHistory::Global().Append( projectFileIO.GetFileName() );
459 }
460
461 return(success);
462}
463
464bool ProjectFileManager::SaveAs(bool allowOverwrite /* = false */)
465{
466 auto &project = mProject;
467 auto &projectFileIO = ProjectFileIO::Get( project );
468 auto &window = GetProjectFrame( project );
469 TitleRestorer Restorer( window, project ); // RAII
470 wxFileName filename;
471 FilePath defaultSavePath = FileNames::FindDefaultPath(FileNames::Operation::Save);
472
473 if (projectFileIO.IsTemporary()) {
474 filename.SetPath(defaultSavePath);
475 filename.SetName(project.GetProjectName());
476 }
477 else {
478 filename = projectFileIO.GetFileName();
479 }
480
481 // Bug 1304: Set a default file path if none was given. For Save/SaveAs/SaveCopy
482 if( !FileNames::IsPathAvailable( filename.GetPath( wxPATH_GET_VOLUME| wxPATH_GET_SEPARATOR) ) ){
483 filename.SetPath(defaultSavePath);
484 }
485
486 TranslatableString title = XO("%sSave Project \"%s\" As...")
487 .Format( Restorer.sProjNumber, Restorer.sProjName );
488 TranslatableString message = XO("\
489'Save Project' is for an Audacity project, not an audio file.\n\
490For an audio file that will open in other apps, use 'Export'.\n");
491
492 if (ShowWarningDialog(&window, wxT("FirstProjectSave"), message, true) != wxID_OK) {
493 return false;
494 }
495
496 bool bPrompt = (project.mBatchMode == 0) || (projectFileIO.GetFileName().empty());
497 FilePath fName;
498 bool bOwnsNewName;
499
500 do {
501 if (bPrompt) {
502 // JKC: I removed 'wxFD_OVERWRITE_PROMPT' because we are checking
503 // for overwrite ourselves later, and we disallow it.
504 fName = SelectFile(FileNames::Operation::Save,
505 title,
506 filename.GetPath(),
507 filename.GetFullName(),
508 wxT("aup3"),
510 wxFD_SAVE | wxRESIZE_BORDER,
511 &window);
512
513 if (fName.empty())
514 return false;
515
516 filename = fName;
517 };
518
519 filename.SetExt(wxT("aup3"));
520
521 if ((!bPrompt || !allowOverwrite) && filename.FileExists()) {
522 // Saving a copy of the project should never overwrite an existing project.
524 nullptr,
525 XO("The project was not saved because the file name provided would overwrite another project.\nPlease try again and select an original name."),
526 XO("Error Saving Project"),
527 wxOK|wxICON_ERROR );
528 m.ShowModal();
529 return false;
530 }
531
532 fName = filename.GetFullPath();
533
534 bOwnsNewName = !projectFileIO.IsTemporary() && ( projectFileIO.GetFileName() == fName );
535 // Check to see if the project file already exists, and if it does
536 // check that the project file 'belongs' to this project.
537 // otherwise, prompt the user before overwriting.
538 if (!bOwnsNewName && filename.FileExists()) {
539 // Ensure that project of same name is not open in another window.
540 // fName is the destination file.
541 // mFileName is this project.
542 // It is possible for mFileName == fName even when this project is not
543 // saved to disk, and we then need to check the destination file is not
544 // open in another window.
545 int mayOverwrite = ( projectFileIO.GetFileName() == fName ) ? 2 : 1;
546 for ( auto p : AllProjects{} ) {
547 const wxFileName openProjectName{ ProjectFileIO::Get(*p).GetFileName() };
548 if (openProjectName.SameAs(fName)) {
549 mayOverwrite -= 1;
550 if (mayOverwrite == 0)
551 break;
552 }
553 }
554
555 if (mayOverwrite > 0) {
556 /* i18n-hint: In each case, %s is the name
557 of the file being overwritten.*/
558 auto Message = XO("\
559 Do you want to overwrite the project:\n\"%s\"?\n\n\
560 If you select \"Yes\" the project\n\"%s\"\n\
561 will be irreversibly overwritten.").Format( fName, fName );
562
563 // For safety, there should NOT be an option to hide this warning.
564 int result = AudacityMessageBox(
565 Message,
566 /* i18n-hint: Heading: A warning that a project is about to be overwritten.*/
567 XO("Overwrite Project Warning"),
568 wxYES_NO | wxNO_DEFAULT | wxICON_WARNING,
569 &window);
570 if (result == wxNO) {
571 continue;
572 }
573 if (result == wxCANCEL) {
574 return false;
575 }
576 }
577 else {
578 // Overwrite disallowed. The destination project is open in another window.
580 nullptr,
581 XO("The project was not saved because the selected project is open in another window.\nPlease try again and select an original name."),
582 XO("Error Saving Project"),
583 wxOK|wxICON_ERROR );
584 m.ShowModal();
585 continue;
586 }
587 }
588
589 break;
590 } while (bPrompt);
591
592
593 auto success = DoSave(fName, !bOwnsNewName);
594 if (success) {
595 FileHistory::Global().Append( projectFileIO.GetFileName() );
596 }
597
598 return(success);
599}
600
601bool ProjectFileManager::SaveCopy(const FilePath &fileName /* = wxT("") */)
602{
603 auto &project = mProject;
604 auto &projectFileIO = ProjectFileIO::Get(project);
605 auto &window = GetProjectFrame(project);
606 TitleRestorer Restorer(window, project); // RAII
607 wxFileName filename = fileName;
608 FilePath defaultSavePath = FileNames::FindDefaultPath(FileNames::Operation::Save);
609
610 if (fileName.empty())
611 {
612 if (projectFileIO.IsTemporary())
613 {
614 filename.SetPath(defaultSavePath);
615 }
616 else
617 {
618 filename = projectFileIO.GetFileName();
619 }
620 }
621
622 // Bug 1304: Set a default file path if none was given. For Save/SaveAs/SaveCopy
623 if (!FileNames::IsPathAvailable(filename.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR)))
624 {
625 filename.SetPath(defaultSavePath);
626 }
627
629 XO("%sSave Copy of Project \"%s\" As...")
630 .Format(Restorer.sProjNumber, Restorer.sProjName);
631
632 bool bPrompt = (project.mBatchMode == 0) || (projectFileIO.GetFileName().empty());
633 FilePath fName;
634
635 do
636 {
637 if (bPrompt)
638 {
639 // JKC: I removed 'wxFD_OVERWRITE_PROMPT' because we are checking
640 // for overwrite ourselves later, and we disallow it.
641 // Previously we disallowed overwrite because we would have had
642 // to DELETE the many smaller files too, or prompt to move them.
643 // Maybe we could allow it now that we have aup3 format?
644 fName = SelectFile(FileNames::Operation::Export,
645 title,
646 filename.GetPath(),
647 filename.GetFullName(),
648 wxT("aup3"),
650 wxFD_SAVE | wxRESIZE_BORDER,
651 &window);
652
653 if (fName.empty())
654 {
655 return false;
656 }
657
658 filename = fName;
659 };
660
661 filename.SetExt(wxT("aup3"));
662
663 if (TempDirectory::FATFilesystemDenied(filename.GetFullPath(), XO("Projects cannot be saved to FAT drives.")))
664 {
665 if (project.mBatchMode)
666 {
667 return false;
668 }
669
670 continue;
671 }
672
673 if (filename.FileExists())
674 {
675 // Saving a copy of the project should never overwrite an existing project.
676 AudacityMessageDialog m(nullptr,
677 XO("Saving a copy must not overwrite an existing saved project.\nPlease try again and select an original name."),
678 XO("Error Saving Copy of Project"),
679 wxOK | wxICON_ERROR);
680 m.ShowModal();
681
682 if (project.mBatchMode)
683 {
684 return false;
685 }
686
687 continue;
688 }
689
690 wxULongLong fileSize = wxFileName::GetSize(projectFileIO.GetFileName());
691
692 wxDiskspaceSize_t freeSpace;
693 if (wxGetDiskSpace(FileNames::AbbreviatePath(filename.GetFullPath()), NULL, &freeSpace))
694 {
695 if (freeSpace.GetValue() <= fileSize.GetValue())
696 {
698 XO("Insufficient Disk Space"),
699 XO("The project size exceeds the available free space on the target disk.\n\n"
700 "Please select a different disk with more free space."),
701 "Error:_Unsuitable_drive"
702 );
703
704 continue;
705 }
706 }
707
708 if (FileNames::IsOnFATFileSystem(filename.GetFullPath()))
709 {
710 if (fileSize > UINT32_MAX)
711 {
713 XO("Error Saving Project"),
714 XO("The project exceeds the maximum size of 4GB when writing to a FAT32 formatted filesystem."),
715 "Error:_Unsuitable_drive"
716 );
717
718 if (project.mBatchMode)
719 {
720 return false;
721 }
722
723 continue;
724 }
725 }
726
727 fName = filename.GetFullPath();
728 break;
729 } while (bPrompt);
730
731 if (!projectFileIO.SaveCopy(fName))
732 {
733 auto msg = FileException::WriteFailureMessage(fName);
735 nullptr, msg, XO("Error Saving Project"), wxOK | wxICON_ERROR);
736
737 m.ShowModal();
738
739 return false;
740 }
741
742 return true;
743}
744
746{
747 auto &project = mProject;
748 auto &projectFileIO = ProjectFileIO::Get( project );
749
750 // MY: Will save the project to a NEW location a-la Save As
751 // and then tidy up after itself.
752
753 wxString sNewFileName = fnFile.GetFullPath();
754
755 // MY: To allow SaveAs from Timer Recording we need to check what
756 // the value of mFileName is before we change it.
757 FilePath sOldFilename;
758 if (!projectFileIO.IsModified()) {
759 sOldFilename = projectFileIO.GetFileName();
760 }
761
762 // MY: If the project file already exists then bail out
763 // and send populate the message string (pointer) so
764 // we can tell the user what went wrong.
765 if (wxFileExists(sNewFileName)) {
766 return false;
767 }
768
769 auto success = DoSave(sNewFileName, true);
770
771 if (success) {
772 FileHistory::Global().Append( projectFileIO.GetFileName() );
773 }
774
775 return success;
776}
777
779{
780 auto &project = mProject;
781 auto &projectFileIO = ProjectFileIO::Get(project);
782
783 // Lock all blocks in all tracks of the last saved version, so that
784 // the sample blocks aren't deleted from the database when we destroy the
785 // sample block objects in memory.
787 {
788 for (auto wt : mLastSavedTracks->Any<WaveTrack>())
789 wt->CloseLock();
790
791 // Attempt to compact the project
792 projectFileIO.Compact( { mLastSavedTracks.get() } );
793
794 if ( !projectFileIO.WasCompacted() &&
796 // If compaction failed, we must do some work in case of close
797 // without save. Don't leave the document blob from the last
798 // push of undo history, when that undo state may get purged
799 // with deletion of some new sample blocks.
800 // REVIEW: UpdateSaved() might fail too. Do we need to test
801 // for that and report it?
802 projectFileIO.UpdateSaved( mLastSavedTracks.get() );
803 }
804 }
805}
806
808{
809 auto &project = mProject;
810 auto &projectFileIO = ProjectFileIO::Get(project);
811
812 return projectFileIO.OpenProject();
813}
814
816{
817 auto &project = mProject;
818 auto &projectFileIO = ProjectFileIO::Get(project);
819
820 bool bOK = OpenProject();
821 if( !bOK )
822 {
823 auto tmpdir = wxFileName(TempDirectory::UnsavedProjectFileName()).GetPath();
824
825 UnwritableLocationErrorDialog dlg(nullptr, tmpdir);
826 dlg.ShowModal();
827 }
828 return bOK;
829}
830
832{
833 auto &project = mProject;
834 auto &projectFileIO = ProjectFileIO::Get(project);
835
836 projectFileIO.CloseProject();
837
838 // Blocks were locked in CompactProjectOnClose, so DELETE the data structure so that
839 // there's no memory leak.
841 {
842 mLastSavedTracks->Clear();
843 mLastSavedTracks.reset();
844 }
845}
846
847// static method, can be called outside of a project
849 const FileNames::FileType &extraType )
850{
851 // Construct the filter
852 const auto fileTypes = Importer::Get().GetFileTypes( extraType );
853
854 // Retrieve saved path
855 auto path = FileNames::FindDefaultPath(op);
856
857 // Construct and display the file dialog
858 wxArrayString selected;
859
860 FileDialogWrapper dlog(nullptr,
861 XO("Select one or more files"),
862 path,
863 wxT(""),
864 fileTypes,
865 wxFD_OPEN | wxFD_MULTIPLE | wxRESIZE_BORDER);
866
868
869 int dialogResult = dlog.ShowModal();
870
871 // Convert the filter index to type and save
872 auto index = dlog.GetFilterIndex();
873 const auto &saveType = fileTypes[ index ];
874
876 Importer::SetLastOpenType( saveType );
877
878 if (dialogResult == wxID_OK) {
879 // Return the selected files
880 dlog.GetPaths(selected);
881
882 // Remember the directory
883 FileNames::UpdateDefaultPath(op, ::wxPathOnly(dlog.GetPath()));
884 }
885
886 return selected;
887}
888
889// static method, can be called outside of a project
891{
892 const wxFileName newProjPathName(projPathName);
893 auto start = AllProjects{}.begin(), finish = AllProjects{}.end(),
894 iter = std::find_if( start, finish,
895 [&]( const AllProjects::value_type &ptr ){
896 return newProjPathName.SameAs(wxFileNameWrapper{ ProjectFileIO::Get(*ptr).GetFileName() });
897 } );
898 if (iter != finish) {
899 auto errMsg =
900 XO("%s is already open in another window.")
901 .Format( newProjPathName.GetName() );
902 wxLogError(errMsg.Translation()); //Debug?
904 errMsg,
905 XO("Error Opening Project"),
906 wxOK | wxCENTRE);
907 return true;
908 }
909 return false;
910}
911
913 const FilePath &fileNameArg, bool addtohistory)
914{
915 // On Win32, we may be given a short (DOS-compatible) file name on rare
916 // occasions (e.g. stuff like "C:\PROGRA~1\AUDACI~1\PROJEC~1.AUP"). We
917 // convert these to long file name first.
918 auto fileName = PlatformCompatibility::GetLongFileName(fileNameArg);
919
920 // Make sure it isn't already open.
921 // Vaughan, 2011-03-25: This was done previously in AudacityProject::OpenFiles()
922 // and AudacityApp::MRUOpen(), but if you open an aup file by double-clicking it
923 // from, e.g., Win Explorer, it would bypass those, get to here with no check,
924 // then open a NEW project from the same data with no warning.
925 // This was reported in http://bugzilla.audacityteam.org/show_bug.cgi?id=137#c17,
926 // but is not really part of that bug. Anyway, prevent it!
927 if (IsAlreadyOpen(fileName))
928 return nullptr;
929
930 // Data loss may occur if users mistakenly try to open ".aup3.bak" files
931 // left over from an unsuccessful save or by previous versions of Audacity.
932 // So we always refuse to open such files.
933 if (fileName.Lower().EndsWith(wxT(".aup3.bak")))
934 {
936 XO(
937"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."),
938 XO("Warning - Backup File Detected"),
939 wxOK | wxCENTRE,
940 nullptr);
941 return nullptr;
942 }
943
944 if (!::wxFileExists(fileName)) {
946 XO("Could not open file: %s").Format( fileName ),
947 XO("Error Opening File"),
948 wxOK | wxCENTRE,
949 nullptr);
950 return nullptr;
951 }
952
953 // Following block covers cases other than a project file:
954 {
955 wxFFile ff(fileName, wxT("rb"));
956
957 auto cleanup = finally([&]
958 {
959 if (ff.IsOpened())
960 {
961 ff.Close();
962 }
963 });
964
965 if (!ff.IsOpened()) {
967 XO("Could not open file: %s").Format( fileName ),
968 XO("Error opening file"),
969 wxOK | wxCENTRE,
970 nullptr);
971 return nullptr;
972 }
973
974 char buf[7];
975 auto numRead = ff.Read(buf, 6);
976 if (numRead != 6) {
978 XO("File may be invalid or corrupted: \n%s").Format( fileName ),
979 XO("Error Opening File or Project"),
980 wxOK | wxCENTRE,
981 nullptr);
982 return nullptr;
983 }
984
985 if (wxStrncmp(buf, "SQLite", 6) != 0)
986 {
987 // Not a database
988#ifdef EXPERIMENTAL_DRAG_DROP_PLUG_INS
989 // Is it a plug-in?
990 if (PluginManager::Get().DropFile(fileName)) {
992 // Plug-in installation happened, not really opening of a file,
993 // so return null
994 return nullptr;
995 }
996#endif
997#ifdef USE_MIDI
998 if (FileNames::IsMidi(fileName)) {
999 auto &project = chooser(false);
1000 // If this succeeds, indo history is incremented, and it also does
1001 // ZoomAfterImport:
1002 if(DoImportMIDI(project, fileName))
1003 return &project;
1004 return nullptr;
1005 }
1006#endif
1007 auto &project = chooser(false);
1008 // Undo history is incremented inside this:
1009 if (Get(project).Import(fileName)) {
1010 // Undo history is incremented inside this:
1011 // Bug 2743: Don't zoom with lof.
1012 if (!fileName.AfterLast('.').IsSameAs(wxT("lof"), false))
1014 return &project;
1015 }
1016 return nullptr;
1017 }
1018 }
1019
1020 // Disallow opening of .aup3 project files from FAT drives, but only such
1021 // files, not importable types. (Bug 2800)
1023 XO("Project resides on FAT formatted drive.\n"
1024 "Copy it to another drive to open it.")))
1025 {
1026 return nullptr;
1027 }
1028
1029 auto &project = chooser(true);
1030 return Get(project).OpenProjectFile(fileName, addtohistory);
1031}
1032
1034 const FilePath &fileName, bool addtohistory)
1035{
1036 auto &project = mProject;
1037 auto &history = ProjectHistory::Get( project );
1038 auto &tracks = TrackList::Get( project );
1039 auto &trackPanel = TrackPanel::Get( project );
1040 auto &projectFileIO = ProjectFileIO::Get( project );
1041 auto &window = ProjectWindow::Get( project );
1042
1043 auto results = ReadProjectFile( fileName );
1044 const bool bParseSuccess = results.parseSuccess;
1045 const auto &errorStr = results.errorString;
1046 const bool err = results.trackError;
1047
1048 if (bParseSuccess && !err) {
1049 auto &formats = ProjectNumericFormats::Get( project );
1051 window.mbInitializingScrollbar = true;
1052
1053 auto &selectionManager = ProjectSelectionManager::Get( project );
1054
1055 selectionManager.AS_SetSelectionFormat(formats.GetSelectionFormat());
1056 selectionManager.TT_SetAudioTimeFormat(formats.GetAudioTimeFormat());
1057 selectionManager.SSBL_SetFrequencySelectionFormatName(
1058 formats.GetFrequencySelectionFormatName());
1059 selectionManager.SSBL_SetBandwidthSelectionFormatName(
1060 formats.GetBandwidthSelectionFormatName());
1061
1063 TrackFocus::Get(project).Set(*tracks.begin());
1064 window.HandleResize();
1065 trackPanel.Refresh(false);
1066
1067 // ? Old rationale in this comment no longer applies in 3.0.0, with no
1068 // more on-demand loading:
1069 trackPanel.Update(); // force any repaint to happen now,
1070 // else any asynch calls into the blockfile code will not have
1071 // finished logging errors (if any) before the call to ProjectFSCK()
1072
1073 if (addtohistory)
1074 FileHistory::Global().Append(fileName);
1075 }
1076
1077 if (bParseSuccess && !err) {
1078 if (projectFileIO.IsRecovered())
1079 {
1080 // PushState calls AutoSave(), so no longer need to do so here.
1081 history.PushState(XO("Project was recovered"), XO("Recover"));
1082 }
1083 return &project;
1084 }
1085 else {
1086 // Vaughan, 2011-10-30:
1087 // See first topic at http://bugzilla.audacityteam.org/show_bug.cgi?id=451#c16.
1088 // Calling mTracks->Clear() with deleteTracks true results in data loss.
1089
1090 // PRL 2014-12-19:
1091 // I made many changes for wave track memory management, but only now
1092 // read the above comment. I may have invalidated the fix above (which
1093 // may have spared the files at the expense of leaked memory). But
1094 // here is a better way to accomplish the intent, doing like what happens
1095 // when the project closes:
1096 for (auto pTrack : tracks.Any<WaveTrack>())
1097 pTrack->CloseLock();
1098
1099 tracks.Clear(); //tracks.Clear(true);
1100
1101 wxLogError(wxT("Could not parse file \"%s\". \nError: %s"), fileName, errorStr.Debug());
1102
1103 projectFileIO.ShowError( *ProjectFramePlacement(&project),
1104 XO("Error Opening Project"),
1105 errorStr,
1106 results.helpUrl);
1107
1108 return nullptr;
1109 }
1110}
1111
1112void
1114 TrackHolders &&newTracks)
1115{
1116 auto &project = mProject;
1117 auto &history = ProjectHistory::Get( project );
1118 auto &projectFileIO = ProjectFileIO::Get( project );
1119 auto &tracks = TrackList::Get( project );
1120
1121 std::vector<Track*> results;
1122
1124
1125 wxFileName fn(fileName);
1126
1127 bool initiallyEmpty = tracks.empty();
1128 double newRate = 0;
1129 wxString trackNameBase = fn.GetName();
1130 int i = -1;
1131
1132 // Fix the bug 2109.
1133 // In case the project had soloed tracks before importing,
1134 // all newly imported tracks are muted.
1135 const bool projectHasSolo =
1136 !(tracks.Any<PlayableTrack>() + &PlayableTrack::GetSolo).empty();
1137 if (projectHasSolo) {
1138 for (auto &group : newTracks)
1139 for (const auto pTrack : group->Any<WaveTrack>())
1140 pTrack->SetMute(true);
1141 }
1142
1143 // Must add all tracks first (before using Track::IsLeader)
1144 for (auto &group : newTracks) {
1145 if (group->empty()) {
1146 assert(false);
1147 continue;
1148 }
1149 for (const auto pTrack : group->Any<WaveTrack>())
1150 results.push_back(pTrack);
1151 tracks.Append(std::move(*group));
1152 }
1153 newTracks.clear();
1154
1155 // Now name them
1156
1157 // Add numbers to track names only if there is more than one (mono or stereo)
1158 // track (not necessarily, more than one channel)
1159 const bool useSuffix = results.size() > 1;
1160
1161 for (const auto &newTrack : results) {
1162 ++i;
1163 newTrack->SetSelected(true);
1164 if (useSuffix)
1165 //i18n-hint Name default name assigned to a clip on track import
1166 newTrack->SetName(XC("%s %d", "clip name template")
1167 .Format(trackNameBase, i + 1).Translation());
1168 else
1169 newTrack->SetName(trackNameBase);
1170
1171 newTrack->TypeSwitch([&](WaveTrack &wt) {
1172 if (newRate == 0)
1173 newRate = wt.GetRate();
1174 const auto trackName = wt.GetName();
1175 for(const auto& interval : wt.Intervals())
1176 interval->SetName(trackName);
1177 });
1178 }
1179
1180 history.PushState(XO("Imported '%s'").Format( fileName ),
1181 XO("Import"));
1182
1183#if defined(__WXGTK__)
1184 // See bug #1224
1185 // The track panel hasn't we been fully created, so the DoZoomFit() will not give
1186 // expected results due to a window width of zero. Should be safe to yield here to
1187 // allow the creation to complete. If this becomes a problem, it "might" be possible
1188 // to queue a dummy event to trigger the DoZoomFit().
1189 wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
1190#endif
1191
1192 // If the project was clean and temporary (not permanently saved), then set
1193 // the filename to the just imported path.
1194 if (initiallyEmpty && projectFileIO.IsTemporary()) {
1195 project.SetProjectName(fn.GetName());
1196 project.SetInitialImportPath(fn.GetPath());
1197 projectFileIO.SetProjectTitle();
1198 }
1199
1200 // Moved this call to higher levels to prevent flicker redrawing everything on each file.
1201 // HandleResize();
1202}
1203
1204namespace {
1205bool ImportProject(AudacityProject &dest, const FilePath &fileName)
1206{
1208 auto &project = temp.Project();
1209
1210 auto &projectFileIO = ProjectFileIO::Get(project);
1211 if (!projectFileIO.LoadProject(fileName, false))
1212 return false;
1213 auto &srcTracks = TrackList::Get(project);
1214 auto &destTracks = TrackList::Get(dest);
1215 for (const Track *pTrack : srcTracks)
1216 pTrack->PasteInto(dest, destTracks);
1218
1219 return true;
1220}
1221
1223 : public ImportProgressListener
1224{
1225 wxWeakRef<AudacityProject> mProject;
1226public:
1227
1229 : mProject(&project)
1230 {
1231
1232 }
1233
1234 bool OnImportFileOpened(ImportFileHandle& importFileHandle) override
1235 {
1236 mImportFileHandle = &importFileHandle;
1237 // File has more than one stream - display stream selector
1238 if (importFileHandle.GetStreamCount() > 1)
1239 {
1240 ImportStreamDialog ImportDlg(&importFileHandle, NULL, -1, XO("Select stream(s) to import"));
1241
1242 if (ImportDlg.ShowModal() == wxID_CANCEL)
1243 return false;
1244 }
1245 // One stream - import it by default
1246 else
1247 importFileHandle.SetStreamUsage(0,TRUE);
1248 return true;
1249 }
1250
1251 void OnImportProgress(double progress) override
1252 {
1253 constexpr double ProgressSteps { 1000.0 };
1254 if(!mProgressDialog)
1255 {
1256 wxFileName ff( mImportFileHandle->GetFilename() );
1257 auto title = XO("Importing %s").Format( mImportFileHandle->GetFileDescription() );
1258 mProgressDialog = BasicUI::MakeProgress(title, Verbatim(ff.GetFullName()));
1259 }
1260 auto result = mProgressDialog->Poll(progress * ProgressSteps, ProgressSteps);
1262 mImportFileHandle->Cancel();
1263 else if(result == BasicUI::ProgressResult::Stopped)
1264 mImportFileHandle->Stop();
1265 }
1266
1267 void OnImportResult(ImportResult result) override
1268 {
1269 mProgressDialog.reset();
1270 if(result == ImportResult::Error)
1271 {
1272 auto message = mImportFileHandle->GetErrorMessage();
1273 if(!message.empty())
1274 {
1275 AudacityMessageBox(message, XO("Import"), wxOK | wxCENTRE | wxICON_ERROR,
1276 mProject ? &GetProjectFrame(*mProject) : nullptr);
1277 }
1278 }
1279 }
1280
1281private:
1282
1283 ImportFileHandle* mImportFileHandle {nullptr};
1284 std::unique_ptr<BasicUI::ProgressDialog> mProgressDialog;
1285};
1286
1287
1288}
1289
1290// If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
1292 const FilePath &fileName,
1293 bool addToHistory /* = true */)
1294{
1295 auto &project = mProject;
1296 auto &projectFileIO = ProjectFileIO::Get(project);
1297 auto oldTags = Tags::Get( project ).shared_from_this();
1298 bool initiallyEmpty = TrackList::Get(project).empty();
1299 TrackHolders newTracks;
1300 TranslatableString errorMessage;
1301
1302#ifdef EXPERIMENTAL_IMPORT_AUP3
1303 // Handle AUP3 ("project") files directly
1304 if (fileName.AfterLast('.').IsSameAs(wxT("aup3"), false)) {
1305 if (ImportProject(project, fileName)) {
1306 auto &history = ProjectHistory::Get(project);
1307
1308 // If the project was clean and temporary (not permanently saved), then set
1309 // the filename to the just imported path.
1310 if (initiallyEmpty && projectFileIO.IsTemporary()) {
1311 wxFileName fn(fileName);
1312 project.SetProjectName(fn.GetName());
1313 project.SetInitialImportPath(fn.GetPath());
1314 projectFileIO.SetProjectTitle();
1315 }
1316
1317 history.PushState(XO("Imported '%s'").Format(fileName), XO("Import"));
1318
1319 if (addToHistory) {
1320 FileHistory::Global().Append(fileName);
1321 }
1322 }
1323 else {
1324 errorMessage = projectFileIO.GetLastError();
1325 if (errorMessage.empty()) {
1326 errorMessage = XO("Failed to import project");
1327 }
1328
1329 // Additional help via a Help button links to the manual.
1331 XO("Error Importing"),
1332 errorMessage, wxT("Importing_Audio"));
1333 }
1334
1335 return false;
1336 }
1337#endif
1338
1339 {
1340 // Backup Tags, before the import. Be prepared to roll back changes.
1341 bool committed = false;
1342 auto cleanup = finally([&]{
1343 if ( !committed )
1344 Tags::Set( project, oldTags );
1345 });
1346 auto newTags = oldTags->Duplicate();
1347 Tags::Set( project, newTags );
1348
1349#ifndef EXPERIMENTAL_IMPORT_AUP3
1350 // Handle AUP3 ("project") files specially
1351 if (fileName.AfterLast('.').IsSameAs(wxT("aup3"), false)) {
1353 XO("Error Importing"),
1354 XO( "Cannot import AUP3 format. Use File > Open instead"),
1355 wxT("File_Menu"));
1356 return false;
1357 }
1358#endif
1359 ImportProgress importProgress(project);
1360 bool success = Importer::Get().Import(project, fileName,
1361 &importProgress,
1363 newTracks,
1364 newTags.get(),
1365 errorMessage);
1366 if (!errorMessage.empty()) {
1367 // Error message derived from Importer::Import
1368 // Additional help via a Help button links to the manual.
1370 XO("Error Importing"), errorMessage, wxT("Importing_Audio"));
1371 }
1372 if (!success)
1373 return false;
1374
1375 if (addToHistory) {
1376 FileHistory::Global().Append(fileName);
1377 }
1378
1379 // no more errors, commit
1380 committed = true;
1381 }
1382
1383 // for LOF ("list of files") files, do not import the file as if it
1384 // were an audio file itself
1385 if (fileName.AfterLast('.').IsSameAs(wxT("lof"), false)) {
1386 // PRL: don't redundantly do the steps below, because we already
1387 // did it in case of LOF, because of some weird recursion back to this
1388 // same function. I think this should be untangled.
1389
1390 // So Undo history push is not bypassed, despite appearances.
1391 return false;
1392 }
1393
1394 // Handle AUP ("legacy project") files directly
1395 if (fileName.AfterLast('.').IsSameAs(wxT("aup"), false)) {
1396 // If the project was clean and temporary (not permanently saved), then set
1397 // the filename to the just imported path.
1398 if (initiallyEmpty && projectFileIO.IsTemporary()) {
1399 wxFileName fn(fileName);
1400 project.SetProjectName(fn.GetName());
1401 project.SetInitialImportPath(fn.GetPath());
1402 projectFileIO.SetProjectTitle();
1403 }
1404
1405 auto &history = ProjectHistory::Get( project );
1406
1407 history.PushState(XO("Imported '%s'").Format( fileName ), XO("Import"));
1408
1409 return true;
1410 }
1411
1412 // PRL: Undo history is incremented inside this:
1413 AddImportedTracks(fileName, std::move(newTracks));
1414
1415 return true;
1416}
1417
1418#include "Clipboard.h"
1419#include "ShuttleGui.h"
1420#include "HelpSystem.h"
1421
1422// Compact dialog
1423namespace {
1425{
1426public:
1428 : wxDialogWrapper(nullptr, wxID_ANY, XO("Compact Project"))
1429 {
1430 ShuttleGui S(this, eIsCreating);
1431
1432 S.StartVerticalLay(true);
1433 {
1434 S.AddFixedText(text, false, 500);
1435
1436 S.AddStandardButtons(eYesButton | eNoButton | eHelpButton);
1437 }
1438 S.EndVerticalLay();
1439
1440 FindWindowById(wxID_YES, this)->Bind(wxEVT_BUTTON, &CompactDialog::OnYes, this);
1441 FindWindowById(wxID_NO, this)->Bind(wxEVT_BUTTON, &CompactDialog::OnNo, this);
1442 FindWindowById(wxID_HELP, this)->Bind(wxEVT_BUTTON, &CompactDialog::OnGetURL, this);
1443
1444 Layout();
1445 Fit();
1446 Center();
1447 }
1448
1449 void OnYes(wxCommandEvent &WXUNUSED(evt))
1450 {
1451 EndModal(wxYES);
1452 }
1453
1454 void OnNo(wxCommandEvent &WXUNUSED(evt))
1455 {
1456 EndModal(wxNO);
1457 }
1458
1459 void OnGetURL(wxCommandEvent &WXUNUSED(evt))
1460 {
1461 HelpSystem::ShowHelp(this, L"File_Menu:_Compact_Project", true);
1462 }
1463};
1464}
1465
1467{
1468 auto &project = mProject;
1469 auto &undoManager = UndoManager::Get(project);
1470 auto &clipboard = Clipboard::Get();
1471 auto &projectFileIO = ProjectFileIO::Get(project);
1472 bool isBatch = project.mBatchMode > 0;
1473
1474 // Purpose of this is to remove the -wal file.
1475 projectFileIO.ReopenProject();
1476
1477 auto savedState = undoManager.GetSavedState();
1478 const auto currentState = undoManager.GetCurrentState();
1479 if (savedState < 0) {
1480 undoManager.StateSaved();
1481 savedState = undoManager.GetSavedState();
1482 if (savedState < 0) {
1483 wxASSERT(false);
1484 savedState = 0;
1485 }
1486 }
1487 const auto least = std::min<size_t>(savedState, currentState);
1488 const auto greatest = std::max<size_t>(savedState, currentState);
1489 std::vector<const TrackList*> trackLists;
1490 auto fn = [&](const UndoStackElem& elem) {
1491 if (auto pTracks = TrackList::FindUndoTracks(elem))
1492 trackLists.push_back(pTracks);
1493 };
1494 undoManager.VisitStates(fn, least, 1 + least);
1495 if (least != greatest)
1496 undoManager.VisitStates(fn, greatest, 1 + greatest);
1497
1498 int64_t total = projectFileIO.GetTotalUsage();
1499 int64_t used = projectFileIO.GetCurrentUsage(trackLists);
1500
1501 auto before = wxFileName::GetSize(projectFileIO.GetFileName());
1502
1503 CompactDialog dlg(
1504 XO("Compacting this project will free up disk space by removing unused bytes within the file.\n\n"
1505 "There is %s of free disk space and this project is currently using %s.\n"
1506 "\n"
1507 "If you proceed, the current Undo/Redo History and clipboard contents will be discarded "
1508 "and you will recover approximately %s of disk space.\n"
1509 "\n"
1510 "Do you want to continue?")
1511 .Format(Internat::FormatSize(projectFileIO.GetFreeDiskSpace()),
1512 Internat::FormatSize(before.GetValue()),
1513 Internat::FormatSize(total - used)));
1514 if (isBatch || dlg.ShowModal() == wxYES)
1515 {
1516 // We can remove redo states, if they are after the saved state.
1517 undoManager.RemoveStates(1 + greatest, undoManager.GetNumStates());
1518
1519 // We can remove all states between the current and the last saved.
1520 if (least < greatest)
1521 undoManager.RemoveStates(least + 1, greatest);
1522
1523 // We can remove all states before the current and the last saved.
1524 undoManager.RemoveStates(0, least);
1525
1526 // And clear the clipboard, if needed
1527 if (&mProject == clipboard.Project().lock().get())
1528 clipboard.Clear();
1529
1530 // Refresh the before space usage since it may have changed due to the
1531 // above actions.
1532 auto before = wxFileName::GetSize(projectFileIO.GetFileName());
1533
1534 projectFileIO.Compact(trackLists, true);
1535
1536 auto after = wxFileName::GetSize(projectFileIO.GetFileName());
1537
1538 if (!isBatch)
1539 {
1541 XO("Compacting actually freed %s of disk space.")
1542 .Format(Internat::FormatSize((before - after).GetValue())),
1543 XO("Compact Project"));
1544 }
1545
1546 undoManager.RenameState( undoManager.GetCurrentState(),
1547 XO("Compacted project file"),
1548 XO("Compact") );
1549 }
1550}
1551
1552static void RefreshAllTitles(bool bShowProjectNumbers )
1553{
1554 for ( auto pProject : AllProjects{} ) {
1555 if ( !GetProjectFrame( *pProject ).IsIconized() ) {
1557 bShowProjectNumbers ? pProject->GetProjectNumber() : -1 );
1558 }
1559 }
1560}
1561
1563 wxTopLevelWindow &window, AudacityProject &project )
1564{
1565 if( window.IsIconized() )
1566 window.Restore();
1567 window.Raise(); // May help identifying the window on Mac
1568
1569 // Construct this project's name and number.
1570 sProjName = project.GetProjectName();
1571 if ( sProjName.empty() ) {
1572 sProjName = _("<untitled>");
1573 UnnamedCount = std::count_if(
1575 []( const AllProjects::value_type &ptr ){
1576 return ptr->GetProjectName().empty();
1577 }
1578 );
1579 if ( UnnamedCount > 1 ) {
1580 sProjNumber.Printf(
1581 _("[Project %02i] "), project.GetProjectNumber() + 1 );
1582 RefreshAllTitles( true );
1583 }
1584 }
1585 else
1586 UnnamedCount = 0;
1587}
1588
1590 if( UnnamedCount > 1 )
1591 RefreshAllTitles( false );
1592}
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
Toolkit-neutral facade for basic user interface services.
Declare functions to perform UTF-8 to std::wstring conversions.
XO("Cut/Copy/Paste")
bool DoImportMIDI(AudacityProject &project, const FilePath &fileName)
Definition: ImportMIDI.cpp:39
The interface that all file import "plugins" (if you want to call them that) must implement....
std::vector< std::shared_ptr< TrackList > > TrackHolders
Definition: ImportRaw.h:24
#define XC(s, c)
Definition: Internat.h:37
#define _(s)
Definition: Internat.h:73
static const auto title
std::unique_ptr< const BasicUI::WindowPlacement > ProjectFramePlacement(AudacityProject *project)
Make a WindowPlacement object suitable for project (which may be null)
Definition: Project.cpp:129
wxString FilePath
Definition: Project.h:21
static const AudacityProject::AttachedObjects::RegisteredFactory sFileManagerKey
static void RefreshAllTitles(bool bShowProjectNumbers)
an object holding per-project preferred sample rate
AUDACITY_DLL_API wxFrame & GetProjectFrame(AudacityProject &project)
Get the top-level window associated with the project (as a wxFrame only, when you do not need to use ...
accessors for certain important windows associated with each project
FilePath SelectFile(FileNames::Operation op, const TranslatableString &message, const FilePath &default_path, const FilePath &default_filename, const FileExtension &default_extension, const FileTypes &fileTypes, int flags, wxWindow *parent)
Definition: SelectFile.cpp:17
@ eIsCreating
Definition: ShuttleGui.h:37
@ eYesButton
Definition: ShuttleGui.h:601
@ eHelpButton
Definition: ShuttleGui.h:603
@ eNoButton
Definition: ShuttleGui.h:602
const auto tracks
const auto project
#define S(N)
Definition: ToChars.cpp:64
static Settings & settings()
Definition: TrackInfo.cpp:83
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
int ShowWarningDialog(wxWindow *parent, const wxString &internalDialogName, const TranslatableString &message, bool showCancelButton, const TranslatableString &footer)
Definition: Warning.cpp:90
static const auto fn
const_iterator end() const
Definition: Project.cpp:27
Container::value_type value_type
Definition: Project.h:57
const_iterator begin() const
Definition: Project.cpp:22
Wrap wxMessageDialog so that caption IS translatable.
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
static Clipboard & Get()
Definition: Clipboard.cpp:28
virtual int GetFilterIndex() const
virtual wxString GetPath() const
virtual void GetPaths(wxArrayString &paths) const
virtual void SetFilterIndex(int filterIndex)
virtual int ShowModal()
static TranslatableString WriteFailureMessage(const wxFileName &fileName)
void Append(const FilePath &file)
Definition: FileHistory.h:46
static FileHistory & Global()
Definition: FileHistory.cpp:39
FILES_API const FileType AudacityProjects
Definition: FileNames.h:71
Abstract base class used in importing a file.
static void ShowHelp(wxWindow *parent, const FilePath &localFileName, const URLString &remoteURL, bool bModal=false, bool alwaysDefaultBrowser=false)
Definition: HelpSystem.cpp:233
const wxString & GET() const
Explicit conversion to wxString, meant to be ugly-looking and demanding of a comment why it's correct...
Definition: Identifier.h:66
Base class for FlacImportFileHandle, LOFImportFileHandle, MP3ImportFileHandle, OggImportFileHandle an...
Definition: ImportPlugin.h:111
virtual wxInt32 GetStreamCount()=0
virtual void SetStreamUsage(wxInt32 StreamID, bool Use)=0
Interface used to report on import state and progress.
static Importer & Get()
Definition: Import.cpp:103
bool Import(AudacityProject &project, const FilePath &fName, ImportProgressListener *importProgressListener, WaveTrackFactory *trackFactory, TrackHolders &tracks, Tags *tags, TranslatableString &errorMessage)
Definition: Import.cpp:492
static void SetLastOpenType(const FileNames::FileType &type)
Definition: Import.cpp:259
FileNames::FileTypes GetFileTypes(const FileNames::FileType &extraType={})
Definition: Import.cpp:214
static size_t SelectDefaultOpenType(const FileNames::FileTypes &fileTypes)
Definition: Import.cpp:279
static void SetDefaultOpenType(const FileNames::FileType &type)
Definition: Import.cpp:269
static TranslatableString FormatSize(wxLongLong size)
Convert a number to a string while formatting it in bytes, KB, MB, GB.
Definition: Internat.cpp:203
Makes a temporary project that doesn't display on the screen.
AudacityProject & Project()
static void RebuildAllMenuBars()
Definition: Menus.cpp:614
static FilePath GetLongFileName(const FilePath &shortFileName)
AudioTrack subclass that can also be audibly replayed by the program.
Definition: PlayableTrack.h:40
bool GetSolo() const
Definition: PlayableTrack.h:48
static PluginManager & Get()
void SetProjectTitle(int number=-1)
static ProjectFileIO & Get(AudacityProject &project)
const FilePath & GetFileName() const
std::shared_ptr< TrackList > mLastSavedTracks
static bool IsAlreadyOpen(const FilePath &projPathName)
AudacityProject & mProject
bool Import(const FilePath &fileName, bool addToHistory=true)
bool SaveCopy(const FilePath &fileName=wxT(""))
bool SaveAs(bool allowOverwrite=false)
static void DiscardAutosave(const FilePath &filename)
void AddImportedTracks(const FilePath &fileName, TrackHolders &&newTracks)
static wxArrayString ShowOpenDialog(FileNames::Operation op, const FileNames::FileType &extraType={})
Show an open dialogue for opening audio files, and possibly other sorts of files.
bool DoSave(const FilePath &fileName, bool fromSaveAs)
bool SaveFromTimerRecording(wxFileName fnFile)
static AudacityProject * OpenFile(const ProjectChooserFn &chooser, const FilePath &fileName, bool addtohistory=true)
ReadProjectResults ReadProjectFile(const FilePath &fileName, bool discardAutosave=false)
std::function< AudacityProject &(bool)> ProjectChooserFn
A function that returns a project to use for opening a file; argument is true if opening a project fi...
static ProjectFileManager & Get(AudacityProject &project)
ProjectFileManager(AudacityProject &project)
AudacityProject * OpenProjectFile(const FilePath &fileName, bool addtohistory)
static ProjectHistory & Get(AudacityProject &project)
static ProjectNumericFormats & Get(AudacityProject &project)
static ProjectSelectionManager & Get(AudacityProject &project)
static ProjectSettings & Get(AudacityProject &project)
static ProjectStatus & Get(AudacityProject &project)
void Set(const TranslatableString &msg, StatusBarField field=mainStatusBarField)
void ZoomAfterImport(Track *pTrack)
static ProjectWindow & Get(AudacityProject &project)
static RealtimeEffectList & Get(AudacityProject &project)
void Clear()
Use only in the main thread. Sends Remove messages.
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:630
void Merge(const Tags &other)
Definition: Tags.cpp:246
static Tags & Get(AudacityProject &project)
Definition: Tags.cpp:214
static Tags & Set(AudacityProject &project, const std::shared_ptr< Tags > &tags)
Definition: Tags.cpp:224
TitleRestorer(wxTopLevelWindow &window, AudacityProject &project)
Track * Get()
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:123
const wxString & GetName() const
Name is always the same for all channels of a group.
Definition: Track.cpp:56
bool empty() const
Definition: Track.cpp:972
static TrackListHolder Create(AudacityProject *pOwner)
Definition: Track.cpp:372
static TrackList * FindUndoTracks(const UndoStackElem &state)
Definition: Track.cpp:1423
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:354
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:232
Holds a msgid for the translation catalog; may also bind format arguments.
Identifier MSGID() const
MSGID is the English lookup key in the catalog, not necessarily for user's eyes if locale is some oth...
static UndoManager & Get(AudacityProject &project)
Definition: UndoManager.cpp:71
void StateSaved()
bool UnsavedChanges() const
An error dialog about unwritable location, that allows to navigate to settings quickly.
static WaveTrackFactory & Get(AudacityProject &project)
Definition: WaveTrack.cpp:4079
A Track that contains audio waveform data.
Definition: WaveTrack.h:220
double GetRate() const override
Definition: WaveTrack.cpp:868
auto Intervals()
Definition: WaveTrack.h:991
void OnImportResult(ImportResult result) override
Used to report on import result for file handle passed as argument to OnImportFileOpened.
bool OnImportFileOpened(ImportFileHandle &importFileHandle) override
std::unique_ptr< BasicUI::ProgressDialog > mProgressDialog
void ShowErrorDialog(const WindowPlacement &placement, const TranslatableString &dlogTitle, const TranslatableString &message, const ManualPageID &helpPage, const ErrorDialogOptions &options={})
Show an error dialog with a link to the manual for further help.
Definition: BasicUI.h:259
MessageBoxResult ShowMessageBox(const TranslatableString &message, MessageBoxOptions options={})
Show a modal message box with either Ok or Yes and No, and optionally Cancel.
Definition: BasicUI.h:274
std::unique_ptr< ProgressDialog > MakeProgress(const TranslatableString &title, const TranslatableString &message, unsigned flags=(ProgressShowStop|ProgressShowCancel), const TranslatableString &remainingLabelText={})
Create and display a progress dialog.
Definition: BasicUI.h:289
FILES_API bool IsMidi(const FilePath &fName)
FILES_API bool IsOnFATFileSystem(const FilePath &path)
FILES_API bool IsPathAvailable(const FilePath &Path)
FILES_API wxString AbbreviatePath(const wxFileName &fileName)
Give enough of the path to identify the device. (On Windows, drive letter plus ':')
FILES_API void UpdateDefaultPath(Operation op, const FilePath &path)
FILES_API FilePath FindDefaultPath(Operation op)
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
void SelectNone(AudacityProject &project)
FILES_API bool FATFilesystemDenied(const FilePath &path, const TranslatableString &msg, const BasicUI::WindowPlacement &placement={})
FILES_API wxString UnsavedProjectFileName()
bool ImportProject(AudacityProject &dest, const FilePath &fileName)
std::pair< const char *, const char * > Pair
wxString FindHelpUrl(const TranslatableString &libraryError)
TranslatableString Message(unsigned trackCount)
Options for variations of error dialogs; the default is for modal dialogs.
Definition: BasicUI.h:51
Holds one item with description and time range for the UndoManager.
Definition: UndoManager.h:117