Audacity 3.2.0
ProjectManager.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5ProjectManager.cpp
6
7Paul Licameli split from AudacityProject.cpp
8
9**********************************************************************/
10
11#include "ProjectManager.h"
12
13
14
15#include "ActiveProject.h"
16#include "AdornedRulerPanel.h"
17#include "AudioIO.h"
18#include "Clipboard.h"
19#include "FileNames.h"
20#include "Menus.h"
21#include "ModuleManager.h"
22#include "Project.h"
23#include "ProjectAudioIO.h"
24#include "ProjectAudioManager.h"
25#include "ProjectFileIO.h"
26#include "ProjectFileManager.h"
27#include "ProjectHistory.h"
29#include "ProjectWindows.h"
30#include "ProjectRate.h"
31#include "ProjectSettings.h"
32#include "ProjectStatus.h"
33#include "ProjectWindow.h"
34#include "SelectUtilities.h"
35#include "TrackPanel.h"
36#include "TrackUtilities.h"
37#include "UndoManager.h"
38#include "WaveTrack.h"
39#include "wxFileNameWrapper.h"
40#include "import/Import.h"
41#include "import/ImportMIDI.h"
42#include "QualitySettings.h"
49#include "widgets/FileHistory.h"
51
52#include <wx/app.h>
53#include <wx/scrolbar.h>
54#include <wx/sizer.h>
55
56#ifdef __WXGTK__
57#include "../images/AudacityLogoAlpha.xpm"
58#endif
59
60namespace {
61 void SaveWindowPreferences(const wxRect& windowRect, const wxRect& normalRect,
62 bool isMaximized, bool isIconized) {
63 ProjectWindowX.Write(windowRect.GetX());
64 ProjectWindowY.Write(windowRect.GetY());
65 ProjectWindowWidth.Write(windowRect.GetWidth());
66 ProjectWindowHeight.Write(windowRect.GetHeight());
67 ProjectWindowMaximized.Write(isMaximized);
68 ProjectWindowNormalX.Write(normalRect.GetX());
69 ProjectWindowNormalY.Write(normalRect.GetY());
70 ProjectWindowNormalWidth.Write(normalRect.GetWidth());
71 ProjectWindowNormalHeight.Write(normalRect.GetHeight());
72 ProjectWindowIconized.Write(isIconized);
73 }
74}
75
76const int AudacityProjectTimerID = 5200;
77
79 []( AudacityProject &project ) {
80 return std::make_shared< ProjectManager >( project );
81 }
82};
83
85{
86 return project.AttachedObjects::Get< ProjectManager >( sProjectManagerKey );
87}
88
90{
91 return Get( const_cast< AudacityProject & >( project ) );
92}
93
95 : mProject{ project }
96 , mTimer{ std::make_unique<wxTimer>(this, AudacityProjectTimerID) }
97{
98 auto &window = ProjectWindow::Get( mProject );
99 window.Bind( wxEVT_CLOSE_WINDOW, &ProjectManager::OnCloseWindow, this );
102 project.Bind( EVT_RECONNECTION_FAILURE,
104}
105
107
108// PRL: This event type definition used to be in AudacityApp.h, which created
109// a bad compilation dependency. The event was never emitted anywhere. I
110// preserve it and its handler here but I move it to remove the dependency.
111// Asynchronous open
113 EVT_OPEN_AUDIO_FILE, wxCommandEvent);
114wxDEFINE_EVENT(EVT_OPEN_AUDIO_FILE, wxCommandEvent);
115
116BEGIN_EVENT_TABLE( ProjectManager, wxEvtHandler )
117 EVT_COMMAND(wxID_ANY, EVT_OPEN_AUDIO_FILE, ProjectManager::OnOpenAudioFile)
120
121bool ProjectManager::sbWindowRectAlreadySaved = false;
122bool ProjectManager::sbSkipPromptingForSave = false;
123
124void ProjectManager::SaveWindowSize()
125{
126 if (sbWindowRectAlreadySaved)
127 {
128 return;
129 }
130 bool validWindowForSaveWindowSize = FALSE;
131 ProjectWindow * validProject = nullptr;
132 bool foundIconizedProject = FALSE;
133 for ( auto pProject : AllProjects{} )
134 {
135 auto &window = ProjectWindow::Get( *pProject );
136 if (!window.IsIconized()) {
137 validWindowForSaveWindowSize = TRUE;
138 validProject = &window;
139 break;
140 }
141 else
142 foundIconizedProject = TRUE;
143 }
144
145 if (validWindowForSaveWindowSize)
146 {
147 wxRect windowRect = validProject->GetRect();
148 wxRect normalRect = validProject->GetNormalizedWindowState();
149 bool wndMaximized = validProject->IsMaximized();
150
151 SaveWindowPreferences(windowRect, normalRect, wndMaximized, false);
152 }
153 else
154 {
155 if (foundIconizedProject) {
156 validProject = &ProjectWindow::Get( **AllProjects{}.begin() );
157 bool wndMaximized = validProject->IsMaximized();
158 wxRect normalRect = validProject->GetNormalizedWindowState();
159
160 // store only the normal rectangle because the itemized rectangle
161 // makes no sense for an opening project window
162 SaveWindowPreferences(normalRect, normalRect, wndMaximized, true);
163 }
164 else {
165 // this would be a very strange case that might possibly occur on the Mac
166 // Audacity would have to be running with no projects open
167 // in this case we are going to write only the default values
168 wxRect defWndRect;
169 GetDefaultWindowRect(&defWndRect);
170 SaveWindowPreferences(defWndRect, defWndRect, false, false);
171 }
172 }
173 sbWindowRectAlreadySaved = true;
174}
175
177{
178 auto &project = window.GetProject();
179
180#ifdef EXPERIMENTAL_DA2
181 SetBackgroundColour(theTheme.Colour( clrMedium ));
182#endif
183 // Note that the first field of the status bar is a dummy, and its width is set
184 // to zero latter in the code. This field is needed for wxWidgets 2.8.12 because
185 // if you move to the menu bar, the first field of the menu bar is cleared, which
186 // is undesirable behaviour.
187 // In addition, the help strings of menu items are by default sent to the first
188 // field. Currently there are no such help strings, but it they were introduced, then
189 // there would need to be an event handler to send them to the appropriate field.
190 auto statusBar = window.CreateStatusBar(4);
191#if wxUSE_ACCESSIBILITY
192 // so that name can be set on a standard control
193 statusBar->SetAccessible(safenew WindowAccessible(statusBar));
194#endif
195 statusBar->SetName(wxT("status_line")); // not localized
196
197 auto &viewInfo = ViewInfo::Get( project );
198
199 // LLL: Read this!!!
200 //
201 // Until the time (and cpu) required to refresh the track panel is
202 // reduced, leave the following window creations in the order specified.
203 // This will place the refresh of the track panel last, allowing all
204 // the others to get done quickly.
205 //
206 // Near as I can tell, this is only a problem under Windows.
207 //
208
209 //
210 // Create the ToolDock
211 //
212 ToolManager::Get( project ).CreateWindows();
213 ToolManager::Get( project ).LayoutToolBars();
214
215 //
216 // Create the TrackPanel and the scrollbars
217 //
218
219 auto topPanel = window.GetTopPanel();
220
221 {
222 auto ubs = std::make_unique<wxBoxSizer>(wxVERTICAL);
223 ubs->Add( ToolManager::Get( project ).GetTopDock(), 0, wxEXPAND | wxALIGN_TOP );
224 topPanel->SetSizer(ubs.release());
225 }
226
227 wxBoxSizer *bs;
228 {
229 auto ubs = std::make_unique<wxBoxSizer>(wxVERTICAL);
230 bs = ubs.get();
231 bs->Add(topPanel, 0, wxEXPAND | wxALIGN_TOP);
232 bs->Add(window.GetContainerWindow(), 1, wxEXPAND);
233 bs->Add( ToolManager::Get( project ).GetBotDock(), 0, wxEXPAND );
234 window.SetAutoLayout(true);
235 window.SetSizer(ubs.release());
236 }
237 bs->Layout();
238
239 auto &trackPanel = TrackPanel::Get( project );
240
241 // LLL: When Audacity starts or becomes active after returning from
242 // another application, the first window that can accept focus
243 // will be given the focus even if we try to SetFocus(). By
244 // making the TrackPanel that first window, we resolve several
245 // keyboard focus problems.
246 window.GetContainerWindow()->MoveAfterInTabOrder(topPanel);
247
248 const auto trackListWindow = window.GetTrackListWindow();
249
250 //
251 // Create the horizontal ruler
252 //
253 auto &ruler = AdornedRulerPanel::Get( project );
254
255 bs = static_cast<wxBoxSizer*>(trackListWindow->GetSizer());
256
257 auto vsBar = &window.GetVerticalScrollBar();
258 auto hsBar = &window.GetHorizontalScrollBar();
259
260 {
261 // Top horizontal grouping
262 auto hs = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
263
264 // Track panel
265 hs->Add(&trackPanel, 1, wxEXPAND | wxALIGN_LEFT | wxALIGN_TOP);
266
267 {
268 // Vertical grouping
269 auto vs = std::make_unique<wxBoxSizer>(wxVERTICAL);
270
271 // Vertical scroll bar
272 vs->Add(vsBar, 1, wxEXPAND | wxALIGN_TOP);
273 hs->Add(vs.release(), 0, wxEXPAND | wxALIGN_TOP);
274 }
275
276 bs->Add(&ruler, 0, wxEXPAND | wxALIGN_TOP);
277 bs->Add(hs.release(), 1, wxEXPAND | wxALIGN_LEFT | wxALIGN_TOP);
278 }
279
280 {
281 // Bottom horizontal grouping
282 auto hs = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
283
284 // Bottom scrollbar
285 hs->Add(viewInfo.GetLeftOffset() - 1, 0);
286 hs->Add(hsBar, 1, wxALIGN_BOTTOM);
287 hs->Add(vsBar->GetSize().GetWidth(), 0);
288 bs->Add(hs.release(), 0, wxEXPAND | wxALIGN_LEFT);
289 }
290
291 // Lay it out
292 trackListWindow->SetAutoLayout(true);
293 trackListWindow->Layout();
294
295 wxASSERT( trackPanel.GetProject() == &project );
296
297 // MM: Give track panel the focus to ensure keyboard commands work
298 trackPanel.SetFocus();
299
300 window.FixScrollbars();
301 ruler.SetLeftOffset(viewInfo.GetLeftOffset()); // bevel on AdornedRuler
302
303 //
304 // Set the Icon
305 //
306
307 // loads either the XPM or the windows resource, depending on the platform
308#if !defined(__WXMAC__) && !defined(__WXX11__)
309 {
310#if defined(__WXMSW__)
311 wxIcon ic{ wxICON(AudacityLogo) };
312#elif defined(__WXGTK__)
313 wxIcon ic{wxICON(AudacityLogoAlpha)};
314#else
315 wxIcon ic{};
316 ic.CopyFromBitmap(theTheme.Bitmap(bmpAudacityLogo48x48));
317#endif
318 window.SetIcon(ic);
319 }
320#endif
321
322 window.UpdateStatusWidths();
323 auto msg = XO("Welcome to Audacity version %s")
324 .Format( AUDACITY_VERSION_STRING );
326
327#ifdef EXPERIMENTAL_DA2
328 ClearBackground();// For wxGTK.
329#endif
330}
331
333{
334 wxRect wndRect;
335 bool bMaximized = false;
336 bool bIconized = false;
337 GetNextWindowPlacement(&wndRect, &bMaximized, &bIconized);
338
339 // Create and show a NEW project
340 // Use a non-default deleter in the smart pointer!
341 auto sp = std::make_shared< AudacityProject >();
342 AllProjects{}.Add( sp );
343 auto p = sp.get();
344 auto &project = *p;
345 auto &projectHistory = ProjectHistory::Get( project );
346 auto &projectManager = Get( project );
347 auto &window = ProjectWindow::Get( *p );
348
349 // Issue #2569
350 // There is a dependency on the order of initialisation.
351 // The menus must be created, and registered, before
352 // InitProjectWindows can UpdateMenus.
354
355 InitProjectWindow( window );
356
357 // wxGTK3 seems to need to require creating the window using default position
358 // and then manually positioning it.
359 window.SetPosition(wndRect.GetPosition());
360
361 auto &projectFileManager = ProjectFileManager::Get( *p );
362
363 // This may report an error.
364 projectFileManager.OpenNewProject();
365
366 projectHistory.InitialState();
367 projectManager.RestartTimer();
368
369 if(bMaximized) {
370 window.Maximize(true);
371 }
372 else if (bIconized) {
373 // if the user close down and iconized state we could start back up and iconized state
374 // window.Iconize(TRUE);
375 }
376
377 //Initialise the Listeners
378 auto gAudioIO = AudioIO::Get();
379 gAudioIO->SetListener(
380 ProjectAudioManager::Get( project ).shared_from_this() );
381 auto &projectSelectionManager = ProjectSelectionManager::Get( project );
382 SelectionBar::Get( project ).SetListener( &projectSelectionManager );
383#ifdef EXPERIMENTAL_SPECTRAL_EDITING
384 SpectralSelectionBar::Get( project ).SetListener( &projectSelectionManager );
385#endif
386 TimeToolBar::Get( project ).SetListener( &projectSelectionManager );
387
388 //Set the NEW project as active:
390
391 // Okay, GetActiveProject() is ready. Now we can get its CommandManager,
392 // and add the shortcut keys to the tooltips.
394
396
397 window.Show(true);
398
399 return p;
400}
401
402void ProjectManager::OnReconnectionFailure(wxCommandEvent & event)
403{
404 event.Skip();
405 wxTheApp->CallAfter([this]{
406 ProjectWindow::Get(mProject).Close(true);
407 });
408}
409
410static bool sbClosingAll = false;
411
413{
414 sbClosingAll = closing;
415}
416
417void ProjectManager::OnCloseWindow(wxCloseEvent & event)
418{
419 auto &project = mProject;
420 auto &projectFileIO = ProjectFileIO::Get( project );
421 auto &projectFileManager = ProjectFileManager::Get( project );
422 const auto &settings = ProjectSettings::Get( project );
423 auto &projectAudioIO = ProjectAudioIO::Get( project );
424 auto &tracks = TrackList::Get( project );
425 auto &window = ProjectWindow::Get( project );
426 auto gAudioIO = AudioIO::Get();
427
428 // We are called for the wxEVT_CLOSE_WINDOW, wxEVT_END_SESSION, and
429 // wxEVT_QUERY_END_SESSION, so we have to protect against multiple
430 // entries. This is a hack until the whole application termination
431 // process can be reviewed and reworked. (See bug #964 for ways
432 // to exercise the bug that instigated this hack.)
433 if (window.IsBeingDeleted())
434 {
435 event.Skip();
436 return;
437 }
438
439 if (event.CanVeto() && (::wxIsBusy() || project.mbBusyImporting))
440 {
441 event.Veto();
442 return;
443 }
444
445 // Check to see if we were playing or recording
446 // audio, and if so, make sure Audio I/O is completely finished.
447 // The main point of this is to properly push the state
448 // and flush the tracks once we've completely finished
449 // recording NEW state.
450 // This code is derived from similar code in
451 // AudacityProject::~AudacityProject() and TrackPanel::OnTimer().
452 if (projectAudioIO.GetAudioIOToken()>0 &&
453 gAudioIO->IsStreamActive(projectAudioIO.GetAudioIOToken())) {
454
455 // We were playing or recording audio, but we've stopped the stream.
456 ProjectAudioManager::Get( project ).Stop();
457
458 projectAudioIO.SetAudioIOToken(0);
459 window.RedrawProject();
460 }
461 else if (gAudioIO->IsMonitoring()) {
462 gAudioIO->StopStream();
463 }
464
465 // MY: Use routine here so other processes can make same check
466 bool bHasTracks = !tracks.empty();
467
468 // We may not bother to prompt the user to save, if the
469 // project is now empty.
471 && event.CanVeto()
472 && (settings.EmptyCanBeDirty() || bHasTracks)) {
473 if ( UndoManager::Get( project ).UnsavedChanges() ) {
474 TitleRestorer Restorer( window, project );// RAII
475 /* i18n-hint: The first %s numbers the project, the second %s is the project name.*/
476 auto Title = XO("%sSave changes to %s?")
477 .Format( Restorer.sProjNumber, Restorer.sProjName );
478 auto Message = XO("Save project before closing?");
479 if( !bHasTracks )
480 {
481 Message += XO("\nIf saved, the project will have no tracks.\n\nTo save any previously open tracks:\nCancel, Edit > Undo until all tracks\nare open, then File > Save Project.");
482 }
483 int result = AudacityMessageBox(
484 Message,
485 Title,
486 wxYES_NO | wxCANCEL | wxICON_QUESTION,
487 &window);
488
489 if (result == wxCANCEL || (result == wxYES &&
490 !GuardedCall<bool>( [&]{ return projectFileManager.Save(); } )
491 )) {
492 event.Veto();
493 return;
494 }
495 }
496 }
497#ifdef __WXMAC__
498 // Fix bug apparently introduced into 2.1.2 because of wxWidgets 3:
499 // closing a project that was made full-screen (as by clicking the green dot
500 // or command+/; not merely "maximized" as by clicking the title bar or
501 // Zoom in the Window menu) leaves the screen black.
502 // Fix it by un-full-screening.
503 // (But is there a different way to do this? What do other applications do?
504 // I don't see full screen windows of Safari shrinking, but I do see
505 // momentary blackness.)
506 window.ShowFullScreen(false);
507#endif
508
509 // This achieves auto save on close of project before other important
510 // project state is destroyed
511 window.Publish(ProjectWindowDestroyedMessage {});
513
514 // Stop the timer since there's no need to update anything anymore
515 mTimer.reset();
516
517 // DMM: Save the size of the last window the user closes
518 //
519 // LL: Save before doing anything else to the window that might make
520 // its size change.
522
523 window.SetIsBeingDeleted();
524
525 // Mac: we never quit as the result of a close.
526 // Other systems: we quit only when the close is the result of an external
527 // command (on Windows, those are taskbar closes, "X" box, Alt+F4, etc.)
528 bool quitOnClose;
529#ifdef __WXMAC__
530 quitOnClose = false;
531#else
532 quitOnClose = !projectFileManager.GetMenuClose();
533#endif
534
535 // DanH: If we're definitely about to quit, clear the clipboard.
536 auto &clipboard = Clipboard::Get();
537 if ((AllProjects{}.size() == 1) &&
538 (quitOnClose || sbClosingAll))
539 clipboard.Clear();
540 else {
541 auto clipboardProject = clipboard.Project().lock();
542 if ( clipboardProject.get() == &mProject ) {
543 // Closing the project from which content was cut or copied.
544 // For 3.0.0, clear the clipboard, because accessing clipboard contents
545 // would depend on a database connection to the closing project, but
546 // that connection should closed now so that the project file can be
547 // freely moved.
548 // Notes:
549 // 1) maybe clipboard contents could be saved by migrating them to
550 // another temporary database, but that extra effort is beyond the
551 // scope of 3.0.0.
552 // 2) strictly speaking this is necessary only when the clipboard
553 // contains WaveTracks.
554 clipboard.Clear();
555 }
556 }
557
558 // JKC: For Win98 and Linux do not detach the menu bar.
559 // We want wxWidgets to clean it up for us.
560 // TODO: Is there a Mac issue here??
561 // SetMenuBar(NULL);
562
563 // Compact the project.
564 projectFileManager.CompactProjectOnClose();
565
566 // Set (or not) the bypass flag to indicate that deletes that would happen during
567 // the UndoManager::ClearStates() below are not necessary.
568 projectFileIO.SetBypass();
569
570 {
571 // This can reduce reference counts of sample blocks in the project's
572 // tracks.
573 UndoManager::Get( project ).ClearStates();
574
575 // Delete all the tracks to free up memory
576 tracks.Clear();
577 }
578
579 // Some of the AdornedRulerPanel functions refer to the TrackPanel, so destroy this
580 // before the TrackPanel is destroyed. This change was needed to stop Audacity
581 // crashing when running with Jaws on Windows 10 1703.
583
584 // Destroy the TrackPanel early so it's not around once we start
585 // deleting things like tracks and such out from underneath it.
586 // Check validity of mTrackPanel per bug 584 Comment 1.
587 // Deeper fix is in the Import code, but this failsafes against crash.
588 TrackPanel::Destroy( project );
589 // Finalize the tool manager before the children since it needs
590 // to save the state of the toolbars.
591 ToolManager::Get( project ).Destroy();
592
593 window.DestroyChildren();
594
595 // Close project only now, because TrackPanel might have been holding
596 // some shared_ptr to WaveTracks keeping SampleBlocks alive.
597 // We're all done with the project file, so close it now
598 projectFileManager.CloseProject();
599
600 WaveTrackFactory::Destroy( project );
601
602 // Remove self from the global array, but defer destruction of self
603 auto pSelf = AllProjects{}.Remove( project );
604 wxASSERT( pSelf );
605
606 if (GetActiveProject().lock().get() == &project) {
607 // Find a NEW active project
608 if ( !AllProjects{}.empty() ) {
610 }
611 else {
612 SetActiveProject(nullptr);
613 }
614 }
615
616 // Since we're going to be destroyed, make sure we're not to
617 // receive audio notifications anymore.
618 // PRL: Maybe all this is unnecessary now that the listener is managed
619 // by a weak pointer.
620 if ( gAudioIO->GetListener().get() == &ProjectAudioManager::Get( project ) ) {
621 auto active = GetActiveProject().lock();
622 gAudioIO->SetListener(
623 active
624 ? ProjectAudioManager::Get( *active ).shared_from_this()
625 : nullptr
626 );
627 }
628
629 if (AllProjects{}.empty() && !sbClosingAll) {
630
631#if !defined(__WXMAC__)
632 if (quitOnClose) {
633 // Simulate the application Exit menu item
634 wxCommandEvent evt{ wxEVT_MENU, wxID_EXIT };
635 wxTheApp->AddPendingEvent( evt );
636 }
637 else {
639 // For non-Mac, always keep at least one project window open
640 (void) New();
641 }
642#endif
643 }
644
645 window.Destroy();
646
647 // Destroys this
648 pSelf.reset();
649}
650
651// PRL: I preserve this handler function for an event that was never sent, but
652// I don't know the intention.
653void ProjectManager::OnOpenAudioFile(wxCommandEvent & event)
654{
655 const wxString &cmd = event.GetString();
656 if (!cmd.empty()) {
657 ProjectChooser chooser{ &mProject, true };
658 if (auto project = ProjectFileManager::OpenFile(
659 std::ref(chooser), cmd)) {
660 auto &window = GetProjectFrame( *project );
661 window.RequestUserAttention();
662 chooser.Commit();
663 }
664 }
665}
666
667// static method, can be called outside of a project
669{
670 auto selectedFiles =
671 ProjectFileManager::ShowOpenDialog(FileNames::Operation::Open);
672 if (selectedFiles.size() == 0) {
674 return;
675 }
676
677 //first sort selectedFiles.
678 selectedFiles.Sort(FileNames::CompareNoCase);
679
680 auto cleanup = finally( [] {
682 } );
683
684 for (const auto &fileName : selectedFiles) {
685 // Make sure it isn't already open.
687 continue; // Skip ones that are already open.
688
689 proj = OpenProject( proj, fileName,
690 true /* addtohistory */, false /* reuseNonemptyProject */ );
691 }
692}
693
695{
696 // DMM: If the project is dirty, that means it's been touched at
697 // all, and it's not safe to open a fresh project directly in its
698 // place. Only if the project is brandnew clean and the user
699 // hasn't done any action at all is it safe for Open to take place
700 // inside the current project.
701 //
702 // If you try to Open a fresh project inside the current window when
703 // there are no tracks, but there's an Undo history, etc, then
704 // bad things can happen, including orphan blocks, or tracks
705 // referring to non-existent blocks
706 if (
707 ProjectHistory::Get( proj ).GetDirty() ||
708 !TrackList::Get( proj ).empty()
709 )
710 return false;
711 // This project is clean; it's never been touched. Therefore
712 // all relevant member variables are in their initial state,
713 // and it's okay to open a NEW project inside its window.
714 return true;
715}
716
718{
719 if (mpUsedProject) {
721 // Ensure that it happens here: don't wait for the application level
722 // exception handler, because the exception may be intercepted
723 ProjectHistory::Get(*mpGivenProject).RollbackState();
724 // Any exception now continues propagating
725 }
726 else
727 GetProjectFrame( *mpUsedProject ).Close(true);
728 }
729}
730
733{
734 if (mpGivenProject) {
735 // Always check before opening a project file (for safety);
736 // May check even when opening other files
737 // (to preserve old behavior; as with the File > Open command specifying
738 // multiple files of whatever types, so that each gets its own window)
739 bool checkReuse = (openingProjectFile || !mReuseNonemptyProject);
740 if (!checkReuse || SafeToOpenProjectInto(*mpGivenProject))
741 return *(mpUsedProject = mpGivenProject);
742 }
743 return *(mpUsedProject = New());
744}
745
747{
748 mpUsedProject = nullptr;
749}
750
752 AudacityProject *pGivenProject, const FilePath &fileNameArg,
753 bool addtohistory, bool reuseNonemptyProject)
754{
755 ProjectManager::ProjectChooser chooser{ pGivenProject, reuseNonemptyProject };
756 if (auto pProject = ProjectFileManager::OpenFile(
757 std::ref(chooser), fileNameArg, addtohistory )) {
758 chooser.Commit();
759
760 auto &projectFileIO = ProjectFileIO::Get( *pProject );
761 if( projectFileIO.IsRecovered() ) {
762 auto &window = ProjectWindow::Get( *pProject );
763 window.Zoom( window.GetZoomOfToFit() );
764 // "Project was recovered" replaces "Create new project" in Undo History.
765 auto &undoManager = UndoManager::Get( *pProject );
766 undoManager.RemoveStates(0, 1);
767 }
768 return pProject;
769 }
770 return nullptr;
771}
772
773// This is done to empty out the tracks, but without creating a new project.
775 auto &project = mProject;
776 auto &projectFileIO = ProjectFileIO::Get( project );
777 auto &projectFileManager = ProjectFileManager::Get( project );
778 auto &projectHistory = ProjectHistory::Get( project );
779 auto &viewInfo = ViewInfo::Get( project );
780
783
784 WaveTrackFactory::Reset( project );
785
786 // InitialState will reset UndoManager
787 projectHistory.InitialState();
788 projectHistory.SetDirty(false);
789
790 projectFileManager.CloseProject();
791 projectFileManager.OpenProject();
792}
793
795{
796 if (mTimer) {
797 // mTimer->Stop(); // not really needed
798 mTimer->Start( 3000 ); // Update messages as needed once every 3 s.
799 }
800}
801
802void ProjectManager::OnTimer(wxTimerEvent& WXUNUSED(event))
803{
804 auto &project = mProject;
805 auto &projectAudioIO = ProjectAudioIO::Get( project );
806 auto meterToolBars = MeterToolBar::GetToolBars( project );
807
808 for (auto& meterToolBar : meterToolBars)
809 meterToolBar.get().UpdateControls();
810
811 auto gAudioIO = AudioIO::Get();
812 // gAudioIO->GetNumCaptureChannels() should only be positive
813 // when we are recording.
814 if (projectAudioIO.GetAudioIOToken() > 0 && gAudioIO->GetNumCaptureChannels() > 0) {
815 wxLongLong freeSpace = ProjectFileIO::Get(project).GetFreeDiskSpace();
816 if (freeSpace >= 0) {
817
818 int iRecordingMins = GetEstimatedRecordingMinsLeftOnDisk(gAudioIO->GetNumCaptureChannels());
819 auto sMessage = XO("Disk space remaining for recording: %s")
820 .Format( GetHoursMinsString(iRecordingMins) );
821
822 // Do not change mLastMainStatusMessage
824 }
825 }
826
827 // As also with the TrackPanel timer: wxTimer may be unreliable without
828 // some restarts
829 RestartTimer();
830}
831
833{
834 auto &project = mProject;
835
836 // Be careful to null-check the window. We might get to this function
837 // during shut-down, but a timer hasn't been told to stop sending its
838 // messages yet.
839 auto pWindow = ProjectWindow::Find( &project );
840 if ( !pWindow )
841 return;
842 auto &window = *pWindow;
843
844 window.UpdateStatusWidths();
845
846 const auto &msg = ProjectStatus::Get( project ).Get( field );
847 SetStatusText( msg, field );
848
849 if ( field == mainStatusBarField )
850 // When recording, let the NEW status message stay at least as long as
851 // the timer interval (if it is not replaced again by this function),
852 // before replacing it with the message about remaining disk capacity.
853 RestartTimer();
854}
855
857{
858 auto &project = mProject;
859 auto pWindow = ProjectWindow::Find( &project );
860 if ( !pWindow )
861 return;
862 auto &window = *pWindow;
863 window.GetStatusBar()->SetStatusText(text.Translation(), number);
864}
865
867{
868 if (iMinutes < 1)
869 // Less than a minute...
870 return XO("Less than 1 minute");
871
872 // Calculate
873 int iHours = iMinutes / 60;
874 int iMins = iMinutes % 60;
875
876 auto sHours = XP( "%d hour", "%d hours", 0 )( iHours );
877
878 auto sMins = XP( "%d minute", "%d minutes", 0 )( iMins );
879
880 /* i18n-hint: A time in hours and minutes. Only translate the "and". */
881 return XO("%s and %s.").Format( sHours, sMins );
882}
883
884// This routine will give an estimate of how many
885// minutes of recording time we have available.
886// The calculations made are based on the user's current
887// preferences.
889 auto &project = mProject;
890
891 // Obtain the current settings
892 auto oCaptureFormat = QualitySettings::SampleFormatChoice();
893 if (lCaptureChannels == 0)
894 lCaptureChannels = AudioIORecordChannels.Read();
895
896 // Find out how much free space we have on disk
897 wxLongLong lFreeSpace = ProjectFileIO::Get( project ).GetFreeDiskSpace();
898 if (lFreeSpace < 0) {
899 return 0;
900 }
901
902 // Calculate the remaining time
903 double dRecTime = 0.0;
904 double bytesOnDiskPerSample = SAMPLE_SIZE_DISK(oCaptureFormat);
905 dRecTime = lFreeSpace.GetHi() * 4294967296.0 + lFreeSpace.GetLo();
906 dRecTime /= bytesOnDiskPerSample;
907 dRecTime /= lCaptureChannels;
908 dRecTime /= ProjectRate::Get( project ).GetRate();
909
910 // Convert to minutes before returning
911 int iRecMins = (int)round(dRecTime / 60.0);
912 return iRecMins;
913}
AUDACITY_DLL_API std::weak_ptr< AudacityProject > GetActiveProject()
void SetActiveProject(AudacityProject *project)
Handle changing of active project and keep global project pointer.
wxT("CloseDown"))
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
IntSetting AudioIORecordChannels
END_EVENT_TABLE()
#define field(n, t)
Definition: ImportAUP.cpp:167
#define XO(s)
Definition: Internat.h:31
#define XP(sing, plur, n)
Definition: Internat.h:96
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, LabelDialog::OnFreqUpdate) LabelDialog
Definition: LabelDialog.cpp:92
#define safenew
Definition: MemoryX.h:10
@ ProjectClosing
@ ProjectInitialized
wxString FilePath
Definition: Project.h:20
wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API, EVT_OPEN_AUDIO_FILE, wxCommandEvent)
void InitProjectWindow(ProjectWindow &window)
wxDEFINE_EVENT(EVT_OPEN_AUDIO_FILE, wxCommandEvent)
static AudacityProject::AttachedObjects::RegisteredFactory sProjectManagerKey
const int AudacityProjectTimerID
static bool sbClosingAll
an object holding per-project preferred sample rate
StatusBarField
Definition: ProjectStatus.h:24
@ mainStatusBarField
Definition: ProjectStatus.h:26
IntSetting ProjectWindowY
IntSetting ProjectWindowX
void GetDefaultWindowRect(wxRect *defRect)
IntSetting ProjectWindowWidth
BoolSetting ProjectWindowIconized
BoolSetting ProjectWindowMaximized
IntSetting ProjectWindowNormalHeight
IntSetting ProjectWindowHeight
IntSetting ProjectWindowNormalX
void GetNextWindowPlacement(wxRect *nextRect, bool *pMaximized, bool *pIconized)
IntSetting ProjectWindowNormalWidth
IntSetting ProjectWindowNormalY
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
#define SAMPLE_SIZE_DISK(SampleFormat)
Return the size on disk of one uncompressed sample (bytes)
Definition: SampleFormat.h:59
THEME_API Theme theTheme
Definition: Theme.cpp:82
static Settings & settings()
Definition: TrackInfo.cpp:87
static AdornedRulerPanel & Get(AudacityProject &project)
static void Destroy(AudacityProject &project)
void Add(const value_type &pProject)
This invalidates iterators.
Definition: Project.cpp:56
size_t size() const
Definition: Project.cpp:17
value_type Remove(AudacityProject &project)
Definition: Project.cpp:42
const_iterator begin() const
Definition: Project.cpp:22
bool empty() const
Definition: Project.h:46
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
static AudioIO * Get()
Definition: AudioIO.cpp:133
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:29
static void SetLastOpenType(const FileNames::FileType &type)
Definition: Import.cpp:224
void CreateMenusAndCommands(AudacityProject &project)
Definition: Menus.cpp:378
static MenuManager & Get(AudacityProject &project)
Definition: Menus.cpp:71
static MeterToolBars GetToolBars(AudacityProject &project)
static ModuleManager & Get()
int Dispatch(ModuleDispatchTypes type)
Subscription Subscribe(Callback callback)
Connect a callback to the Publisher; later-connected are called earlier.
Definition: Observer.h:199
static ProjectAudioIO & Get(AudacityProject &project)
void Stop(bool stopStream=true)
static ProjectAudioManager & Get(AudacityProject &project)
static ProjectFileIO & Get(AudacityProject &project)
wxLongLong GetFreeDiskSpace() const
static bool IsAlreadyOpen(const FilePath &projPathName)
static wxArrayString ShowOpenDialog(FileNames::Operation op, const FileNames::FileType &extraType={})
Show an open dialogue for opening audio files, and possibly other sorts of files.
static AudacityProject * OpenFile(const ProjectChooserFn &chooser, const FilePath &fileName, bool addtohistory=true)
static ProjectFileManager & Get(AudacityProject &project)
static ProjectHistory & Get(AudacityProject &project)
Callable object that supplies the chooser argument of ProjectFileManager::OpenFile.
AudacityProject * mpGivenProject
AudacityProject & operator()(bool openingProjectFile)
May create a fresh project.
AudacityProject * mpUsedProject
~ProjectChooser()
Destroy any fresh project, or rollback the existing project, unless committed.
void Commit()
Commit the creation of any fresh project or changes to the existing project.
Object associated with a project for high-level management of the project's lifetime,...
Observer::Subscription mSubscription
static bool sbWindowRectAlreadySaved
static ProjectManager & Get(AudacityProject &project)
void OnStatusChange(StatusBarField field)
void OnCloseWindow(wxCloseEvent &event)
static bool sbSkipPromptingForSave
void OnOpenAudioFile(wxCommandEvent &event)
static AudacityProject * New()
TranslatableString GetHoursMinsString(int iMinutes)
std::unique_ptr< wxTimer > mTimer
void OnTimer(wxTimerEvent &event)
static void SaveWindowSize()
static void OpenFiles(AudacityProject *proj)
static AudacityProject * OpenProject(AudacityProject *pGivenProject, const FilePath &fileNameArg, bool addtohistory, bool reuseNonemptyProject)
Open a file into an AudacityProject, returning the project, or nullptr for failure.
static void SetClosingAll(bool closing)
~ProjectManager() override
ProjectManager(AudacityProject &project)
AudacityProject & mProject
static bool SafeToOpenProjectInto(AudacityProject &proj)
False when it is unsafe to overwrite proj with contents of an .aup3 file.
void SetStatusText(const TranslatableString &text, int number)
int GetEstimatedRecordingMinsLeftOnDisk(long lCaptureChannels=0)
void ResetProjectToEmpty()
void OnReconnectionFailure(wxCommandEvent &event)
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
double GetRate() const
Definition: ProjectRate.cpp:53
static ProjectSelectionManager & Get(AudacityProject &project)
static ProjectSettings & Get(AudacityProject &project)
static ProjectStatus & Get(AudacityProject &project)
AudacityProject & GetProject()
A top-level window associated with a project, and handling scrollbars and zooming.
Definition: ProjectWindow.h:40
wxScrollBar & GetHorizontalScrollBar()
static ProjectWindow & Get(AudacityProject &project)
wxScrollBar & GetVerticalScrollBar()
wxWindow * GetContainerWindow() noexcept
Container is a parent window for both effects panel and track list windows.
wxPanel * GetTopPanel() noexcept
Top panel contains project-related controls and tools.
static ProjectWindow * Find(AudacityProject *pProject)
wxRect GetNormalizedWindowState() const
wxWindow * GetTrackListWindow() noexcept
Track list window is the parent container for TrackPanel.
void UpdateStatusWidths()
void SetListener(SelectionBarListener *l)
static SelectionBar & Get(AudacityProject &project)
bool Write(const T &value)
Write value to config and return true if successful.
Definition: Prefs.h:252
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:200
static SpectralSelectionBar & Get(AudacityProject &project)
void SetListener(SpectralSelectionBarListener *l)
wxColour & Colour(int iIndex)
wxBitmap & Bitmap(int iIndex)
static TimeToolBar & Get(AudacityProject &project)
Definition: TimeToolBar.cpp:65
void SetListener(TimeToolBarListener *l)
wxString sProjName
wxString sProjNumber
static ToolManager & Get(AudacityProject &project)
void CreateWindows()
void RegenerateTooltips()
void LayoutToolBars()
void Destroy()
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:486
static TrackPanel & Get(AudacityProject &project)
Definition: TrackPanel.cpp:230
static void Destroy(AudacityProject &project)
Definition: TrackPanel.cpp:240
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
void ClearStates()
static UndoManager & Get(AudacityProject &project)
Definition: UndoManager.cpp:67
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
static void Destroy(AudacityProject &project)
Definition: WaveTrack.cpp:2827
static WaveTrackFactory & Reset(AudacityProject &project)
Definition: WaveTrack.cpp:2820
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
FILES_API int CompareNoCase(const wxString &first, const wxString &second)
PROJECT_RATE_API sampleFormat SampleFormatChoice()
void DoSelectAll(AudacityProject &project)
void DoRemoveTracks(AudacityProject &project)
void SaveWindowPreferences(const wxRect &windowRect, const wxRect &normalRect, bool isMaximized, bool isIconized)
TranslatableString Message(unsigned trackCount)
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
STL namespace.
Message sent when the project window is closed.
Definition: ProjectWindow.h:32