Audacity  2.3.1
Project.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  Project.cpp
6 
7  Dominic Mazzoni
8  Vaughan Johnson
9 
10 *******************************************************************//****************************************************************//****************************************************************//****************************************************************//****************************************************************//*******************************************************************/
50 
51 #include "Audacity.h"
52 #include "Project.h"
53 
54 #include <stdio.h>
55 #include <iostream>
56 #include <wx/wxprec.h>
57 #include <wx/apptrait.h>
58 
59 #include <wx/defs.h>
60 #include <wx/app.h>
61 #include <wx/dc.h>
62 #include <wx/dcmemory.h>
63 #include <wx/dnd.h>
64 #include <wx/docview.h>
65 #include <wx/event.h>
66 #include <wx/ffile.h>
67 #include <wx/filedlg.h>
68 #include <wx/filefn.h>
69 #include <wx/filename.h>
70 #include <wx/intl.h>
71 #include <wx/log.h>
72 #include <wx/menu.h>
73 #include <wx/notebook.h>
74 #include <wx/progdlg.h>
75 #include <wx/scrolbar.h>
76 #include <wx/sizer.h>
77 #include <wx/statusbr.h>
78 #include <wx/string.h>
79 #include <wx/textfile.h>
80 #include <wx/timer.h>
81 #include <wx/display.h>
82 
83 #if defined(__WXMAC__)
84 #if !wxCHECK_VERSION(3, 0, 0)
85 #include <CoreServices/CoreServices.h>
86 #include <wx/mac/private.h>
87 #endif
88 #endif
89 
90 #include "AdornedRulerPanel.h"
91 #include "AudacityException.h"
92 #include "FreqWindow.h"
93 #include "effects/Contrast.h"
94 #include "AutoRecovery.h"
95 #include "AudacityApp.h"
96 #include "AColor.h"
97 #include "AudioIO.h"
98 #include "BatchProcessDialog.h"
99 #include "Dependencies.h"
100 #include "Diags.h"
101 #include "HistoryWindow.h"
102 #include "InconsistencyException.h"
103 #include "MixerBoard.h"
104 #include "Internat.h"
105 #include "import/Import.h"
106 #include "LabelTrack.h"
107 #include "Legacy.h"
108 #include "LyricsWindow.h"
109 #include "Menus.h"
110 #include "Mix.h"
111 #include "NoteTrack.h"
112 #include "Prefs.h"
113 #include "Sequence.h"
114 #include "Snap.h"
115 #include "Tags.h"
116 #include "TimeTrack.h"
117 #include "TrackPanel.h"
118 #include "WaveTrack.h"
119 #include "DirManager.h"
120 #include "commands/CommandManager.h"
121 #include "effects/Effect.h"
122 #include "prefs/PrefsDialog.h"
124 #include "widgets/ASlider.h"
125 #include "widgets/ErrorDialog.h"
126 #include "widgets/Warning.h"
127 #include "xml/XMLFileReader.h"
128 #include "PlatformCompatibility.h"
129 #include "Experimental.h"
130 #include "export/Export.h"
131 #include "FileNames.h"
132 #include "BlockFile.h"
133 #include "ondemand/ODManager.h"
134 #include "ondemand/ODTask.h"
136 #ifdef EXPERIMENTAL_OD_FLAC
138 #endif
139 #include "ModuleManager.h"
140 
141 #include "Theme.h"
142 #include "AllThemeResources.h"
143 
144 #include "FileDialog.h"
145 
146 #include "UndoManager.h"
147 
148 #include "toolbars/ToolManager.h"
149 #include "toolbars/ControlToolBar.h"
150 #include "toolbars/DeviceToolBar.h"
151 #include "toolbars/EditToolBar.h"
152 #include "toolbars/MeterToolBar.h"
153 #include "toolbars/MixerToolBar.h"
155 #include "toolbars/SelectionBar.h"
157 #include "toolbars/ToolsToolBar.h"
159 
163 #include "tracks/ui/Scrubbing.h"
164 
165 #include "widgets/ErrorDialog.h"
166 
168 #include "commands/CommandTargets.h"
169 #include "commands/Command.h"
170 #include "commands/CommandType.h"
171 #include "commands/CommandContext.h"
172 
173 #include "prefs/QualityPrefs.h"
174 #include "prefs/TracksPrefs.h"
175 
176 #include "../images/AudacityLogoAlpha.xpm"
177 
178 #if wxUSE_ACCESSIBILITY
180 #endif
181 
182 std::shared_ptr<TrackList> AudacityProject::msClipboard{ TrackList::Create() };
183 double AudacityProject::msClipT0 = 0.0;
184 double AudacityProject::msClipT1 = 0.0;
187 {
188  static ODLock theMutex;
189  return theMutex;
190 };
191 
192 #if defined(__WXMAC__)
193 // const int sbarSpaceWidth = 15;
194 // const int sbarControlWidth = 16;
195 // const int sbarExtraLen = 1;
196 const int sbarHjump = 30; //STM: This is how far the thumb jumps when the l/r buttons are pressed, or auto-scrolling occurs -- in pixels
197 #elif defined(__WXMSW__)
198 const int sbarSpaceWidth = 16;
199 const int sbarControlWidth = 16;
200 const int sbarExtraLen = 0;
201 const int sbarHjump = 30; //STM: This is how far the thumb jumps when the l/r buttons are pressed, or auto-scrolling occurs -- in pixels
202 #else // wxGTK, wxMOTIF, wxX11
203 const int sbarSpaceWidth = 15;
204 const int sbarControlWidth = 15;
205 const int sbarExtraLen = 0;
206 const int sbarHjump = 30; //STM: This is how far the thumb jumps when the l/r buttons are pressed, or auto-scrolling occurs -- in pixels
207 #include "Theme.h"
208 #include "AllThemeResources.h"
209 #endif
210 
211 int AudacityProject::mProjectCounter=0;// global counter.
212 
213 
217 DEFINE_EVENT_TYPE(EVT_CAPTURE_KEY);
218 
219 //
220 // This small template class resembles a try-finally block
221 //
222 // It sets var to val_entry in the constructor and
223 // var to val_exit in the destructor.
224 //
225 template <typename T>
227 {
228 public:
229  VarSetter(T* var, T val_entry, T val_exit)
230  {
231  mVar = var;
232  mValExit = val_exit;
233  *var = val_entry;
234  }
235 
237  {
238  *mVar = mValExit;
239  }
240 private:
241  T* mVar;
243 };
244 
245 // This wrapper prevents the scrollbars from retaining focus after being
246 // used. Otherwise, the only way back to the track panel is to click it
247 // and that causes your original location to be lost.
248 class ScrollBar final : public wxScrollBar
249 {
250 public:
251  ScrollBar(wxWindow* parent, wxWindowID id, long style)
252  : wxScrollBar(parent, id, wxDefaultPosition, wxDefaultSize, style)
253  {
254  }
255 
256  void OnSetFocus(wxFocusEvent & e)
257  {
258  wxWindow *w = e.GetWindow();
259  if (w != NULL) {
260  w->SetFocus();
261  }
262  }
263 
264  void SetScrollbar(int position, int thumbSize,
265  int range, int pageSize,
266  bool refresh = true) override;
267 
268 private:
269  DECLARE_EVENT_TABLE()
270 };
271 
272 void ScrollBar::SetScrollbar(int position, int thumbSize,
273  int range, int pageSize,
274  bool refresh)
275 {
276  // Mitigate flashing of scrollbars by refreshing only when something really changes.
277 
278  // PRL: This may have been made unnecessary by other fixes for flashing, see
279  // commit ac05b190bee7dd0000bce56edb0e5e26185c972f
280 
281  auto changed =
282  position != GetThumbPosition() ||
283  thumbSize != GetThumbSize() ||
284  range != GetRange() ||
285  pageSize != GetPageSize();
286  if (!changed)
287  return;
288 
289  wxScrollBar::SetScrollbar(position, thumbSize, range, pageSize, refresh);
290 }
291 
292 BEGIN_EVENT_TABLE(ScrollBar, wxScrollBar)
293  EVT_SET_FOCUS(ScrollBar::OnSetFocus)
295 
296 /* Define Global Variables */
297 //The following global counts the number of documents that have been opened
298 //for the purpose of project placement (not to keep track of the number)
299 //It is only accurate modulo ten, and does not decrement when a project is
300 //closed.
301 static int gAudacityOffsetInc = 0;
302 static int gAudacityPosInc = 0;
303 //This is a pointer to the currently-active project.
305 //This array holds onto all of the projects currently open
307 
308 /* Declare Static functions */
309 static void SetActiveProject(AudacityProject * project);
310 
312 {
313  return gActiveProject;
314 }
315 
317 {
318  gActiveProject = project;
319  wxTheApp->SetTopWindow(project);
320 }
321 
322 #if wxUSE_DRAG_AND_DROP
323 class FileObject final : public wxFileDataObject
324 {
325 public:
326  FileObject()
327  {
328  }
329 
330  bool IsSupportedFormat(const wxDataFormat & format, Direction WXUNUSED(dir = Get)) const
331  // PRL: This function does NOT override any inherited virtual! What does it do?
332  {
333  if (format.GetType() == wxDF_FILENAME) {
334  return true;
335  }
336 
337 #if defined(__WXMAC__)
338 #if !wxCHECK_VERSION(3, 0, 0)
339  if (format.GetFormatId() == kDragPromisedFlavorFindFile) {
340  return true;
341  }
342 #endif
343 #endif
344 
345  return false;
346  }
347 };
348 
349 class DropTarget final : public wxFileDropTarget
350 {
351 public:
353  {
354  mProject = proj;
355 
356  // SetDataObject takes ownership
357  SetDataObject(safenew FileObject());
358  }
359 
360  ~DropTarget()
361  {
362  }
363 
364 #if defined(__WXMAC__)
365 #if !wxCHECK_VERSION(3, 0, 0)
366  bool GetData() override
367  {
368  bool foundSupported = false;
369  bool firstFileAdded = false;
370  OSErr result;
371 
372  UInt16 items = 0;
373  CountDragItems((DragReference)m_currentDrag, &items);
374 
375  for (UInt16 index = 1; index <= items; index++) {
376 
377  DragItemRef theItem = 0;
378  GetDragItemReferenceNumber((DragReference)m_currentDrag, index, &theItem);
379 
380  UInt16 flavors = 0;
381  CountDragItemFlavors((DragReference)m_currentDrag, theItem , &flavors ) ;
382 
383  for (UInt16 flavor = 1 ;flavor <= flavors; flavor++) {
384 
385  FlavorType theType = 0;
386  result = GetFlavorType((DragReference)m_currentDrag, theItem, flavor, &theType);
387  if (theType != kDragPromisedFlavorFindFile && theType != kDragFlavorTypeHFS) {
388  continue;
389  }
390  foundSupported = true;
391 
392  Size dataSize = 0;
393  GetFlavorDataSize((DragReference)m_currentDrag, theItem, theType, &dataSize);
394 
395  ArrayOf<char> theData{ dataSize };
396  GetFlavorData((DragReference)m_currentDrag, theItem, theType, (void*) theData.get(), &dataSize, 0L);
397 
398  wxString name;
399  if (theType == kDragPromisedFlavorFindFile) {
400  name = wxMacFSSpec2MacFilename((FSSpec *)theData.get());
401  }
402  else if (theType == kDragFlavorTypeHFS) {
403  name = wxMacFSSpec2MacFilename(&((HFSFlavor *)theData.get())->fileSpec);
404  }
405 
406  if (!firstFileAdded) {
407  // reset file list
408  ((wxFileDataObject*)GetDataObject())->SetData(0, "");
409  firstFileAdded = true;
410  }
411 
412  ((wxFileDataObject*)GetDataObject())->AddFile(name);
413 
414  // We only want to process one flavor
415  break;
416  }
417  }
418  return foundSupported;
419  }
420 #endif
421 
422  bool OnDrop(wxCoord x, wxCoord y) override
423  {
424  // bool foundSupported = false;
425 #if !wxCHECK_VERSION(3, 0, 0)
426  bool firstFileAdded = false;
427  OSErr result;
428 
429  UInt16 items = 0;
430  CountDragItems((DragReference)m_currentDrag, &items);
431 
432  for (UInt16 index = 1; index <= items; index++) {
433 
434  DragItemRef theItem = 0;
435  GetDragItemReferenceNumber((DragReference)m_currentDrag, index, &theItem);
436 
437  UInt16 flavors = 0;
438  CountDragItemFlavors((DragReference)m_currentDrag, theItem , &flavors ) ;
439 
440  for (UInt16 flavor = 1 ;flavor <= flavors; flavor++) {
441 
442  FlavorType theType = 0;
443  result = GetFlavorType((DragReference)m_currentDrag, theItem, flavor, &theType);
444  if (theType != kDragPromisedFlavorFindFile && theType != kDragFlavorTypeHFS) {
445  continue;
446  }
447  return true;
448  }
449  }
450 #endif
451  return CurrentDragHasSupportedFormat();
452  }
453 
454 #endif
455 
456  bool OnDropFiles(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y), const wxArrayString& filenames) override
457  {
458  // Experiment shows that this function can be reached while there is no
459  // catch block above in wxWidgets. So stop all exceptions here.
460  return GuardedCall< bool > ( [&] {
461  //sort by OD non OD. load Non OD first so user can start editing asap.
462  wxArrayString sortednames(filenames);
463  sortednames.Sort(CompareNoCaseFileName);
464 
465  ODManager::Pauser pauser;
466 
467  auto cleanup = finally( [&] {
468  mProject->HandleResize(); // Adjust scrollers for NEW track sizes.
469  } );
470 
471  for (const auto &name : sortednames) {
472 #ifdef USE_MIDI
473  if (Importer::IsMidi(name))
474  FileActions::DoImportMIDI(mProject, name);
475  else
476 #endif
477  mProject->Import(name);
478  }
479 
480  mProject->ZoomAfterImport(nullptr);
481 
482  return true;
483  } );
484  }
485 
486 private:
487  AudacityProject *mProject;
488 };
489 
490 #endif
491 
492 
493 bool ImportXMLTagHandler::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
494 {
495  if (wxStrcmp(tag, wxT("import")) || attrs==NULL || (*attrs)==NULL || wxStrcmp(*attrs++, wxT("filename")))
496  return false;
497  wxString strAttr = *attrs;
498  if (!XMLValueChecker::IsGoodPathName(strAttr))
499  {
500  // Maybe strAttr is just a fileName, not the full path. Try the project data directory.
501  wxFileName fileName(mProject->GetDirManager()->GetProjectDataDir(), strAttr);
502  if (XMLValueChecker::IsGoodFileName(strAttr, fileName.GetPath(wxPATH_GET_VOLUME)))
503  strAttr = fileName.GetFullPath();
504  else
505  {
506  wxLogWarning(wxT("Could not import file: %s"), strAttr);
507  return false;
508  }
509  }
510 
511  WaveTrackArray trackArray;
512 
513  // Guard this call so that C++ exceptions don't propagate through
514  // the expat library
515  GuardedCall(
516  [&] { mProject->Import(strAttr, &trackArray); },
517  [&] (AudacityException*) { trackArray.clear(); }
518  );
519 
520  if (trackArray.empty())
521  return false;
522 
523  // Handle other attributes, now that we have the tracks.
524  attrs++;
525  const wxChar** pAttr;
526  bool bSuccess = true;
527 
528  for (size_t i = 0; i < trackArray.size(); i++)
529  {
530  // Most of the "import" tag attributes are the same as for "wavetrack" tags,
531  // so apply them via WaveTrack::HandleXMLTag().
532  bSuccess = trackArray[i]->HandleXMLTag(wxT("wavetrack"), attrs);
533 
534  // "offset" tag is ignored in WaveTrack::HandleXMLTag except for legacy projects,
535  // so handle it here.
536  double dblValue;
537  pAttr = attrs;
538  while (*pAttr)
539  {
540  const wxChar *attr = *pAttr++;
541  const wxChar *value = *pAttr++;
542  const wxString strValue = value;
543  if (!wxStrcmp(attr, wxT("offset")) &&
544  XMLValueChecker::IsGoodString(strValue) &&
545  Internat::CompatibleToDouble(strValue, &dblValue))
546  trackArray[i]->SetOffset(dblValue);
547  }
548  }
549  return bSuccess;
550 };
551 
553 {
554  wxRect wndRect;
555  bool bMaximized = false;
556  bool bIconized = false;
557  GetNextWindowPlacement(&wndRect, &bMaximized, &bIconized);
558 
559  // Create and show a NEW project
560  // Use a non-default deleter in the smart pointer!
561  gAudacityProjects.push_back( AProjectHolder {
563  nullptr, -1,
564  wxDefaultPosition,
565  wxSize(wndRect.width, wndRect.height)
566  ),
568  } );
569  const auto p = gAudacityProjects.back().get();
570 
571  // wxGTK3 seems to need to require creating the window using default position
572  // and then manually positioning it.
573  p->SetPosition(wndRect.GetPosition());
574 
575  if(bMaximized) {
576  p->Maximize(true);
577  }
578  else if (bIconized) {
579  // if the user close down and iconized state we could start back up and iconized state
580  // p->Iconize(TRUE);
581  }
582 
583  //Initialise the Listener
584  gAudioIO->SetListener(p);
585 
586  //Set the NEW project as active:
587  SetActiveProject(p);
588 
589  // Okay, GetActiveProject() is ready. Now we can get its CommandManager,
590  // and add the shortcut keys to the tooltips.
591  p->GetToolManager()->RegenerateTooltips();
592 
594 
595  p->Show(true);
596 
597  return p;
598 }
599 
601 {
602  size_t len = gAudacityProjects.size();
603  for (size_t i = 0; i < len; i++)
604  gAudacityProjects[i]->RedrawProject();
605 }
606 
608 {
609  size_t len = gAudacityProjects.size();
610  for (size_t i = 0; i < len; i++)
611  gAudacityProjects[i]->RefreshCursor();
612 }
613 
614 AUDACITY_DLL_API void CloseAllProjects()
615 {
616  size_t len = gAudacityProjects.size();
617  for (size_t i = 0; i < len; i++)
618  gAudacityProjects[i]->Close();
619 
620  //Set the Offset and Position increments to 0
621  gAudacityOffsetInc = 0;
622  gAudacityPosInc = 0;
623 }
624 
625 // BG: The default size and position of the first window
626 void GetDefaultWindowRect(wxRect *defRect)
627 {
628  *defRect = wxGetClientDisplayRect();
629 
630  int width = 940;
631  int height = 674;
632 
633  //These conditional values assist in improving placement and size
634  //of NEW windows on different platforms.
635 #ifdef __WXGTK__
636  height += 20;
637 #endif
638 
639 #ifdef __WXMSW__
640  height += 40;
641 #endif
642 
643 #ifdef __WXMAC__
644  height += 55;
645 #endif
646 
647  // Use screen size where it is smaller than the values we would like.
648  // Otherwise use the values we would like, and centred.
649  if (width < defRect->width)
650  {
651  defRect->x = (defRect->width - width)/2;
652  defRect->width = width;
653  }
654 
655  if (height < defRect->height)
656  {
657  defRect->y = (defRect->height - height)/2;
658  // Bug 1119 workaround
659  // Small adjustment for very small Mac screens.
660  // If there is only a tiny space at the top
661  // then instead of vertical centre, align to bottom.
662  const int pixelsFormenu = 60;
663  if( defRect->y < pixelsFormenu )
664  defRect->y *=2;
665  defRect->height = height;
666  }
667 }
668 
669 // true iff we have enough of the top bar to be able to reposition the window.
670 bool IsWindowAccessible(wxRect *requestedRect)
671 {
672  wxDisplay display;
673  wxRect targetTitleRect(requestedRect->GetLeftTop(), requestedRect->GetBottomRight());
674  // Hackery to approximate a window top bar size from a window size.
675  // and exclude the open/close and borders.
676  targetTitleRect.x += 15;
677  targetTitleRect.width -= 100;
678  if (targetTitleRect.width < 165) targetTitleRect.width = 165;
679  targetTitleRect.height = 15;
680  int targetBottom = targetTitleRect.GetBottom();
681  int targetRight = targetTitleRect.GetRight();
682  // This looks like overkill to check each and every pixel in the ranges.
683  // and decide that if any is visible on screen we are OK.
684  for (int i = targetTitleRect.GetLeft(); i < targetRight; i++) {
685  for (int j = targetTitleRect.GetTop(); j < targetBottom; j++) {
686  int monitor = display.GetFromPoint(wxPoint(i, j));
687  if (monitor != wxNOT_FOUND) {
688  return TRUE;
689  }
690  }
691  }
692  return FALSE;
693 }
694 
695 // Returns the screen containing a rectangle, or -1 if none does.
696 int ScreenContaining( wxRect & r ){
697  unsigned int n = wxDisplay::GetCount();
698  for(unsigned int i = 0;i<n;i++){
699  wxDisplay d(i);
700  wxRect scr = d.GetClientArea();
701  if( scr.Contains( r ) )
702  return (int)i;
703  }
704  return -1;
705 }
706 
707 // true IFF TL and BR corners are on a connected display.
708 // Does not need to check all four. We just need to check that
709 // the window probably is straddling screens in a sensible way.
710 // If the user wants to use mixed landscape and portrait, they can.
711 bool CornersOnScreen( wxRect & r ){
712  if( wxDisplay::GetFromPoint( r.GetTopLeft() ) == wxNOT_FOUND) return false;
713  if( wxDisplay::GetFromPoint( r.GetBottomRight() ) == wxNOT_FOUND) return false;
714  return true;
715 }
716 
717 // BG: Calculate where to place the next window (could be the first window)
718 // BG: Does not store X and Y in prefs. This is intentional.
719 //
720 // LL: This should NOT need to be this complicated...FIXME
721 void GetNextWindowPlacement(wxRect *nextRect, bool *pMaximized, bool *pIconized)
722 {
723  int inc = 25;
724 
725  wxRect defaultRect;
726  GetDefaultWindowRect(&defaultRect);
727 
728  gPrefs->Read(wxT("/Window/Maximized"), pMaximized, false);
729  gPrefs->Read(wxT("/Window/Iconized"), pIconized, false);
730 
731  wxRect windowRect;
732  gPrefs->Read(wxT("/Window/X"), &windowRect.x, defaultRect.x);
733  gPrefs->Read(wxT("/Window/Y"), &windowRect.y, defaultRect.y);
734  gPrefs->Read(wxT("/Window/Width"), &windowRect.width, defaultRect.width);
735  gPrefs->Read(wxT("/Window/Height"), &windowRect.height, defaultRect.height);
736 
737  wxRect normalRect;
738  gPrefs->Read(wxT("/Window/Normal_X"), &normalRect.x, defaultRect.x);
739  gPrefs->Read(wxT("/Window/Normal_Y"), &normalRect.y, defaultRect.y);
740  gPrefs->Read(wxT("/Window/Normal_Width"), &normalRect.width, defaultRect.width);
741  gPrefs->Read(wxT("/Window/Normal_Height"), &normalRect.height, defaultRect.height);
742 
743  // Workaround 2.1.1 and earlier bug on OSX...affects only normalRect, but let's just
744  // validate for all rects and plats
745  if (normalRect.width == 0 || normalRect.height == 0) {
746  normalRect = defaultRect;
747  }
748  if (windowRect.width == 0 || windowRect.height == 0) {
749  windowRect = defaultRect;
750  }
751 
752 
753  wxRect screenRect( wxGetClientDisplayRect());
754 #if defined(__WXMAC__)
755 
756  // On OSX, the top of the window should never be less than the menu height,
757  // so something is amiss if it is
758  if (normalRect.y < screenRect.y) {
759  normalRect = defaultRect;
760  }
761  if (windowRect.y < screenRect.y) {
762  windowRect = defaultRect;
763  }
764 #endif
765 
766  // IF projects empty, THEN it's the first window.
767  // It lands where the config says it should, and can straddle screen.
768  if (gAudacityProjects.empty()) {
769  if (*pMaximized || *pIconized) {
770  *nextRect = normalRect;
771  }
772  else {
773  *nextRect = windowRect;
774  }
775  // Resize, for example if one monitor that was on is now off.
776  if (!CornersOnScreen( wxRect(*nextRect).Deflate( 32, 32 ))) {
777  *nextRect = defaultRect;
778  }
779  if (!IsWindowAccessible(nextRect)) {
780  *nextRect = defaultRect;
781  }
782  // Do not trim the first project window down.
783  // All corners are on screen (or almost so), and
784  // the rect may straddle screens.
785  return;
786  }
787 
788 
789  // ELSE a subsequent NEW window. It will NOT straddle screens.
790 
791  // We don't mind being 32 pixels off the screen in any direction.
792  // Make sure initial sizes (pretty much) fit within the display bounds
793  // We used to trim the sizes which could result in ridiculously small windows.
794  // contributing to bug 1243.
795  // Now instead if the window significantly doesn't fit the screen, we use the default
796  // window instead, which we know does.
797  if (ScreenContaining( wxRect(normalRect).Deflate( 32, 32 ))<0) {
798  normalRect = defaultRect;
799  }
800  if (ScreenContaining( wxRect(windowRect).Deflate( 32, 32 ) )<0) {
801  windowRect = defaultRect;
802  }
803 
804  bool validWindowSize = false;
805  AudacityProject * validProject = NULL;
806  size_t numProjects = gAudacityProjects.size();
807  for (int i = numProjects; i > 0 ; i--) {
808  if (!gAudacityProjects[i-1]->IsIconized()) {
809  validWindowSize = true;
810  validProject = gAudacityProjects[i-1].get();
811  break;
812  }
813  }
814  if (validWindowSize) {
815  *nextRect = validProject->GetRect();
816  *pMaximized = validProject->IsMaximized();
817  *pIconized = validProject->IsIconized();
818  // Do not straddle screens.
819  if (ScreenContaining( wxRect(*nextRect).Deflate( 32, 32 ) )<0) {
820  *nextRect = defaultRect;
821  }
822  }
823  else {
824  *nextRect = normalRect;
825  }
826 
827  //Placement depends on the increments
828  nextRect->x += inc;
829  nextRect->y += inc;
830 
831  // defaultrect is a rectangle on the first screen. It's the right fallback to
832  // use most of the time if things are not working out right with sizing.
833  // windowRect is a saved rectangle size.
834  // normalRect seems to be a substitute for windowRect when iconized or maximised.
835 
836  // Windows can say that we are off screen when actually we are not.
837  // On Windows 10 I am seeing miscalculation by about 6 pixels.
838  // To fix this we allow some sloppiness on the edge being counted as off screen.
839  // This matters most when restoring very carefully sized windows that are maximised
840  // in one dimension (height or width) but not both.
841  const int edgeSlop = 10;
842 
843  // Next four lines are getting the rectangle for the screen that contains the
844  // top left corner of nextRect (and defaulting to rect of screen 0 otherwise).
845  wxPoint p = nextRect->GetLeftTop();
846  int scr = std::max( 0, wxDisplay::GetFromPoint( p ));
847  wxDisplay d( scr );
848  screenRect = d.GetClientArea();
849 
850  // Now we (possibly) start trimming our rectangle down.
851  // Have we hit the right side of the screen?
852  wxPoint bottomRight = nextRect->GetBottomRight();
853  if (bottomRight.x > (screenRect.GetRight()+edgeSlop)) {
854  int newWidth = screenRect.GetWidth() - nextRect->GetLeft();
855  if (newWidth < defaultRect.GetWidth()) {
856  nextRect->x = windowRect.x;
857  nextRect->y = windowRect.y;
858  nextRect->width = windowRect.width;
859  }
860  else {
861  nextRect->width = newWidth;
862  }
863  }
864 
865  // Have we hit the bottom of the screen?
866  bottomRight = nextRect->GetBottomRight();
867  if (bottomRight.y > (screenRect.GetBottom()+edgeSlop)) {
868  nextRect->y -= inc;
869  bottomRight = nextRect->GetBottomRight();
870  if (bottomRight.y > (screenRect.GetBottom()+edgeSlop)) {
871  nextRect->SetBottom(screenRect.GetBottom());
872  }
873  }
874 
875  // After all that we could have a window that does not have a visible
876  // top bar. [It is unlikely, but something might have gone wrong]
877  // If so, use the safe fallback size.
878  if (!IsWindowAccessible(nextRect)) {
879  *nextRect = defaultRect;
880  }
881 }
882 
883 static wxString CreateUniqueName()
884 {
885  static int count = 0;
886  return wxDateTime::Now().Format(wxT("%Y-%m-%d %H-%M-%S")) +
887  wxString::Format(wxT(" N-%i"), ++count);
888 }
889 
890 namespace {
891 
892 #if 0
893 std::mutex sObjectFactoriesMutex;
894 struct ObjectFactorySetLocker : private std::unique_lock< std::mutex >
895 {
896  ObjectFactorySetLocker()
897  : std::unique_lock< std::mutex > { sObjectFactoriesMutex }
898  {}
899 };
900 #else
901 struct ObjectFactorySetLocker {};
902 #endif
903 
904 std::vector<AudacityProject::AttachedObjectFactory> &sObjectFactories()
905 {
906  // Put this declaration inside a function to avoid problems of undefined
907  // sequence of initialization of file-scope statics in different
908  // compilation units.
909  // Note that mutex locking is not needed for constructing a static object
910  // in C++11:
911  //https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
912  static std::vector<AudacityProject::AttachedObjectFactory> factories;
913  return factories;
914 }
915 }
916 
920 {
921  ObjectFactorySetLocker locker;
922  mIndex = sObjectFactories().size();
923  sObjectFactories().push_back( factory );
924 
925  // In case registration happens while projects exist:
926  for (const auto &pProject : gAudacityProjects) {
927  if (pProject->mAttachedObjects.size() == mIndex) {
928  auto pObject = factory();
929  wxASSERT( pObject );
930  pProject->mAttachedObjects.push_back( std::move( pObject ) );
931  }
932  }
933 }
934 
938 {
939  ObjectFactorySetLocker locker;
940  if ( factory.mIndex >= mAttachedObjects.size() )
942  auto &pObject = mAttachedObjects[ factory.mIndex ];
943  if ( !pObject )
945  return *pObject;
946 }
947 
948 enum {
949  FirstID = 1000,
950 
951  // Window controls
952 
956 };
957 
958 
959 BEGIN_EVENT_TABLE(AudacityProject, wxFrame)
960  EVT_MENU(wxID_ANY, AudacityProject::OnMenu)
961  EVT_MOUSE_EVENTS(AudacityProject::OnMouseEvent)
962  EVT_CLOSE(AudacityProject::OnCloseWindow)
963  EVT_SIZE(AudacityProject::OnSize)
964  EVT_SHOW(AudacityProject::OnShow)
965  EVT_ICONIZE(AudacityProject::OnIconize)
966  EVT_MOVE(AudacityProject::OnMove)
967  EVT_ACTIVATE(AudacityProject::OnActivate)
968  EVT_COMMAND_SCROLL_LINEUP(HSBarID, AudacityProject::OnScrollLeftButton)
969  EVT_COMMAND_SCROLL_LINEDOWN(HSBarID, AudacityProject::OnScrollRightButton)
970  EVT_COMMAND_SCROLL(HSBarID, AudacityProject::OnScroll)
971  EVT_COMMAND_SCROLL(VSBarID, AudacityProject::OnScroll)
972  EVT_TIMER(AudacityProjectTimerID, AudacityProject::OnTimer)
973  // Fires for menu with ID #1...first menu defined
974  EVT_UPDATE_UI(1, AudacityProject::OnUpdateUI)
975  EVT_ICONIZE(AudacityProject::OnIconize)
976  EVT_COMMAND(wxID_ANY, EVT_OPEN_AUDIO_FILE, AudacityProject::OnOpenAudioFile)
977  EVT_COMMAND(wxID_ANY, EVT_TOOLBAR_UPDATED, AudacityProject::OnToolBarUpdate)
978  //mchinen:multithreaded calls - may not be threadsafe with CommandEvent: may have to change.
979  EVT_COMMAND(wxID_ANY, EVT_ODTASK_UPDATE, AudacityProject::OnODTaskUpdate)
980  EVT_COMMAND(wxID_ANY, EVT_ODTASK_COMPLETE, AudacityProject::OnODTaskComplete)
982 
983 AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id,
984  const wxPoint & pos,
985  const wxSize & size)
986  : wxFrame(parent, id, _TS("Audacity"), pos, size),
987  mViewInfo(0.0, 1.0, ZoomInfo::GetDefaultZoom()),
988  mbLoadedFromAup( false ),
989  mDefaultFormat(QualityPrefs::SampleFormatChoice()),
990  mSnapTo(gPrefs->Read(wxT("/SnapTo"), SNAP_OFF)),
991  mSelectionFormat( NumericTextCtrl::LookupFormat(
992  NumericConverter::TIME,
993  gPrefs->Read(wxT("/SelectionFormat"), wxT("")) ) ),
994  mFrequencySelectionFormatName( NumericTextCtrl::LookupFormat(
995  NumericConverter::FREQUENCY,
996  gPrefs->Read(wxT("/FrequencySelectionFormatName"), wxT("")) ) ),
997  mBandwidthSelectionFormatName( NumericTextCtrl::LookupFormat(
998  NumericConverter::BANDWIDTH,
999  gPrefs->Read(wxT("/BandwidthSelectionFormatName"), wxT("")) ) ),
1000  mUndoManager(std::make_unique<UndoManager>())
1001  , mCommandManager( std::make_unique<CommandManager>() )
1002 {
1003  if (!gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleRate"), &mRate, AudioIO::GetOptimalSupportedSampleRate())) {
1004  // The default given above can vary with host/devices. So unless there is an entry for
1005  // the default sample rate in audacity.cfg, Audacity can open with a rate which is different
1006  // from the rate with which it closed. See bug 1879.
1007  gPrefs->Write(wxT("/SamplingRate/DefaultProjectSampleRate"), mRate);
1008  gPrefs->Flush();
1009  }
1010 
1011  mTracks = TrackList::Create();
1012 
1013 #ifdef EXPERIMENTAL_DA2
1014  SetBackgroundColour(theTheme.Colour( clrMedium ));
1015 #endif
1016  // Note that the first field of the status bar is a dummy, and it's width is set
1017  // to zero latter in the code. This field is needed for wxWidgets 2.8.12 because
1018  // if you move to the menu bar, the first field of the menu bar is cleared, which
1019  // is undesirable behaviour.
1020  // In addition, the help strings of menu items are by default sent to the first
1021  // field. Currently there are no such help strings, but it they were introduced, then
1022  // there would need to be an event handler to send them to the appropriate field.
1023  mStatusBar = CreateStatusBar(4);
1024 #if wxUSE_ACCESSIBILITY
1025  // so that name can be set on a standard control
1026  mStatusBar->SetAccessible(safenew WindowAccessible(mStatusBar));
1027 #endif
1028  mStatusBar->SetName(wxT("status_line")); // not localized
1029  mProjectNo = mProjectCounter++; // Bug 322
1030 
1032 
1033  // MM: DirManager is created dynamically, freed on demand via ref-counting
1034  // MM: We don't need to Ref() here because it start with refcount=1
1035  mDirManager = std::make_shared<DirManager>();
1036 
1037  mLastSavedTracks.reset();
1038 
1039  //
1040  // Initialize view info (shared with TrackPanel)
1041  //
1042 
1043  {
1044  ObjectFactorySetLocker locker;
1045  for (const auto &factory : sObjectFactories()) {
1046  auto pObject = factory();
1047  wxASSERT( pObject );
1048  mAttachedObjects.push_back( std::move( pObject ) );
1049  }
1050  }
1051 
1052  mMenuManager = std::make_unique<MenuManager>();
1053 
1054  UpdatePrefs();
1055 
1056  mLockPlayRegion = false;
1057 
1058  // Make sure valgrind sees mIsSyncLocked is initialized, even
1059  // though we're about to set it from prefs.
1060  mIsSyncLocked = false;
1061  gPrefs->Read(wxT("/GUI/SyncLockTracks"), &mIsSyncLocked, false);
1062 
1063  // LLL: Read this!!!
1064  //
1065  // Until the time (and cpu) required to refresh the track panel is
1066  // reduced, leave the following window creations in the order specified.
1067  // This will place the refresh of the track panel last, allowing all
1068  // the others to get done quickly.
1069  //
1070  // Near as I can tell, this is only a problem under Windows.
1071  //
1072 
1073 
1074  // PRL: this panel groups the top tool dock and the ruler into one
1075  // tab cycle.
1076  // Must create it with non-default width equal to the main window width,
1077  // or else the device toolbar doesn't make initial widths of the choice
1078  // controls correct.
1079  mTopPanel = safenew wxPanelWrapper {
1080  this, wxID_ANY, wxDefaultPosition,
1081  wxSize{ this->GetSize().GetWidth(), -1 }
1082  };
1083  mTopPanel->SetLabel( "Top Panel" );// Not localised
1084  mTopPanel->SetLayoutDirection(wxLayout_LeftToRight);
1085  mTopPanel->SetAutoLayout(true);
1086 #ifdef EXPERIMENTAL_DA2
1087  mTopPanel->SetBackgroundColour(theTheme.Colour( clrMedium ));
1088 #endif
1089 
1090  //
1091  // Create the ToolDock
1092  //
1093  mToolManager = std::make_unique<ToolManager>( this, mTopPanel );
1094  GetSelectionBar()->SetListener(this);
1095 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
1096  GetSpectralSelectionBar()->SetListener(this);
1097 #endif
1098  mToolManager->LayoutToolBars();
1099 
1100  //
1101  // Create the horizontal ruler
1102  //
1103  mRuler = safenew AdornedRulerPanel( this, mTopPanel,
1104  wxID_ANY,
1105  wxDefaultPosition,
1106  wxSize( -1, AdornedRulerPanel::GetRulerHeight(false) ),
1107  &mViewInfo );
1108  mRuler->SetLayoutDirection(wxLayout_LeftToRight);
1109 
1110  //
1111  // Create the TrackPanel and the scrollbars
1112  //
1113  wxWindow * pPage;
1114 
1115 #ifdef EXPERIMENTAL_NOTEBOOK
1116  // We are using a notebook (tabbed panel), so we create the notebook and add pages.
1117  GuiFactory Factory;
1118  wxNotebook * pNotebook;
1119  mMainPanel = Factory.AddPanel(
1120  this, wxPoint( left, top ), wxSize( width, height ) );
1121  pNotebook = Factory.AddNotebook( mMainPanel );
1122  /* i18n-hint: This is an experimental feature where the main panel in
1123  Audacity is put on a notebook tab, and this is the name on that tab.
1124  Other tabs in that notebook may have instruments, patch panels etc.*/
1125  pPage = Factory.AddPage( pNotebook, _("Main Mix"));
1126 #else
1127  // Not using a notebook, so we place the track panel inside another panel,
1128  // this keeps the notebook code and normal code consistant and also
1129  // paves the way for adding additional windows inside the track panel.
1130  mMainPanel = safenew wxPanelWrapper(this, -1,
1131  wxDefaultPosition,
1132  wxDefaultSize,
1133  wxNO_BORDER);
1134  mMainPanel->SetSizer( safenew wxBoxSizer(wxVERTICAL) );
1135  mMainPanel->SetLabel("Main Panel");// Not localised.
1136  pPage = mMainPanel;
1137  // Set the colour here to the track panel background to avoid
1138  // flicker when Audacity starts up.
1139  // However, that leads to areas next to the horizontal scroller
1140  // being painted in background colour and not scroller background
1141  // colour, so suppress this for now.
1142  //pPage->SetBackgroundColour( theTheme.Colour( clrDark ));
1143 #endif
1144  pPage->SetLayoutDirection(wxLayout_LeftToRight);
1145 
1146 #ifdef EXPERIMENTAL_DA2
1147  pPage->SetBackgroundColour(theTheme.Colour( clrMedium ));
1148 #endif
1149 
1150  {
1151  auto ubs = std::make_unique<wxBoxSizer>(wxVERTICAL);
1152  ubs->Add(mToolManager->GetTopDock(), 0, wxEXPAND | wxALIGN_TOP);
1153  ubs->Add(mRuler, 0, wxEXPAND);
1154  mTopPanel->SetSizer(ubs.release());
1155  }
1156 
1157  wxBoxSizer *bs;
1158  {
1159  auto ubs = std::make_unique<wxBoxSizer>(wxVERTICAL);
1160  bs = ubs.get();
1161  bs->Add(mTopPanel, 0, wxEXPAND | wxALIGN_TOP);
1162  bs->Add(pPage, 1, wxEXPAND);
1163  bs->Add(mToolManager->GetBotDock(), 0, wxEXPAND);
1164  SetAutoLayout(true);
1165  SetSizer(ubs.release());
1166  }
1167  bs->Layout();
1168 
1169  // The right hand side translates to NEW TrackPanel(...) in normal
1170  // Audacity without additional DLLs.
1171  mTrackPanel = TrackPanel::FactoryFunction(pPage,
1172  TrackPanelID,
1173  wxDefaultPosition,
1174  wxDefaultSize,
1175  mTracks,
1176  &mViewInfo,
1177  this,
1178  mRuler);
1179  mTrackPanel->UpdatePrefs();
1180 
1181  mIndicatorOverlay = std::make_unique<PlayIndicatorOverlay>(this);
1182 
1183  mCursorOverlay = std::make_unique<EditCursorOverlay>(this);
1184 
1185  mBackgroundCell = std::make_shared<BackgroundCell>(this);
1186 
1187 #ifdef EXPERIMENTAL_SCRUBBING_BASIC
1188  mScrubOverlay = std::make_unique<ScrubbingOverlay>(this);
1189  mScrubber = std::make_unique<Scrubber>(this);
1190 #endif
1191 
1192  // More order dependencies here...
1193  // This must follow construction of *mIndicatorOverlay, because it must
1194  // attach its timer event handler later (so that its handler is invoked
1195  // earlier)
1196  mPlaybackScroller = std::make_unique<PlaybackScroller>(this);
1197 
1198  // This must follow construction of *mPlaybackScroller,
1199  // because it must
1200  // attach its timer event handler later (so that its handler is invoked
1201  // earlier)
1202  this->Bind(EVT_TRACK_PANEL_TIMER,
1204  &mViewInfo);
1205 
1206  // Add the overlays, in the sequence in which they will be painted
1207  mTrackPanel->AddOverlay(mIndicatorOverlay.get());
1208  mTrackPanel->AddOverlay(mCursorOverlay.get());
1209 #ifdef EXPERIMENTAL_SCRUBBING_BASIC
1210  mTrackPanel->AddOverlay(mScrubOverlay.get());
1211 #endif
1212 
1213  mMenuManager->CreateMenusAndCommands(*this);
1214 
1215  mTrackPanel->SetBackgroundCell(mBackgroundCell);
1216 
1217  // LLL: When Audacity starts or becomes active after returning from
1218  // another application, the first window that can accept focus
1219  // will be given the focus even if we try to SetFocus(). By
1220  // creating the scrollbars after the TrackPanel, we resolve
1221  // several focus problems.
1222  mHsbar = safenew ScrollBar(pPage, HSBarID, wxSB_HORIZONTAL);
1223  mVsbar = safenew ScrollBar(pPage, VSBarID, wxSB_VERTICAL);
1224 #if wxUSE_ACCESSIBILITY
1225  // so that name can be set on a standard control
1226  mHsbar->SetAccessible(safenew WindowAccessible(mHsbar));
1227  mVsbar->SetAccessible(safenew WindowAccessible(mVsbar));
1228 #endif
1229  mHsbar->SetLayoutDirection(wxLayout_LeftToRight);
1230  mHsbar->SetName(_("Horizontal Scrollbar"));
1231  mVsbar->SetName(_("Vertical Scrollbar"));
1232  // LLL: When Audacity starts or becomes active after returning from
1233  // another application, the first window that can accept focus
1234  // will be given the focus even if we try to SetFocus(). By
1235  // making the TrackPanel that first window, we resolve several
1236  // keyboard focus problems.
1237  pPage->MoveBeforeInTabOrder(mTopPanel);
1238 
1239  bs = (wxBoxSizer *)pPage->GetSizer();
1240 
1241  {
1242  // Top horizontal grouping
1243  auto hs = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
1244 
1245  // Track panel
1246  hs->Add(mTrackPanel, 1, wxEXPAND | wxALIGN_LEFT | wxALIGN_TOP);
1247 
1248  {
1249  // Vertical grouping
1250  auto vs = std::make_unique<wxBoxSizer>(wxVERTICAL);
1251 
1252  // Vertical scroll bar
1253  vs->Add(mVsbar, 1, wxEXPAND | wxALIGN_TOP);
1254  hs->Add(vs.release(), 0, wxEXPAND | wxALIGN_TOP);
1255  }
1256 
1257  bs->Add(hs.release(), 1, wxEXPAND | wxALIGN_LEFT | wxALIGN_TOP);
1258  }
1259 
1260  {
1261  // Bottom horizontal grouping
1262  auto hs = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
1263 
1264  // Bottom scrollbar
1265  hs->Add(mTrackPanel->GetLeftOffset() - 1, 0);
1266  hs->Add(mHsbar, 1, wxALIGN_BOTTOM);
1267  hs->Add(mVsbar->GetSize().GetWidth(), 0);
1268  bs->Add(hs.release(), 0, wxEXPAND | wxALIGN_LEFT);
1269  }
1270 
1271  // Lay it out
1272  pPage->SetAutoLayout(true);
1273  pPage->Layout();
1274 
1275 #ifdef EXPERIMENTAL_NOTEBOOK
1276  AddPages(this, Factory, pNotebook);
1277 #endif
1278 
1279  mMainPanel->Layout();
1280 
1281  wxASSERT( mTrackPanel->GetProject()==this);
1282 
1283  // MM: Give track panel the focus to ensure keyboard commands work
1284  mTrackPanel->SetFocus();
1285 
1286  // Create tags object
1287  mTags = std::make_shared<Tags>();
1288 
1289  InitialState();
1290  FixScrollbars();
1291  mRuler->SetLeftOffset(mTrackPanel->GetLeftOffset()); // bevel on AdornedRuler
1292 
1293  //
1294  // Set the Icon
1295  //
1296 
1297  // loads either the XPM or the windows resource, depending on the platform
1298 #if !defined(__WXMAC__) && !defined(__WXX11__)
1299  {
1300 #if defined(__WXMSW__)
1301  wxIcon ic{ wxICON(AudacityLogo) };
1302 #elif defined(__WXGTK__)
1303  wxIcon ic{wxICON(AudacityLogoAlpha)};
1304 #else
1305  wxIcon ic{};
1306  ic.CopyFromBitmap(theTheme.Bitmap(bmpAudacityLogo48x48));
1307 #endif
1308  SetIcon(ic);
1309  }
1310 #endif
1311  mIconized = false;
1312 
1313  mTrackFactory.reset(safenew TrackFactory{ mDirManager, &mViewInfo });
1314 
1315  int widths[] = {0, GetControlToolBar()->WidthForStatusBar(mStatusBar), -1, 150};
1316  mStatusBar->SetStatusWidths(4, widths);
1317  wxString msg = wxString::Format(_("Welcome to Audacity version %s"),
1319  mStatusBar->SetStatusText(msg, mainStatusBarField);
1320  GetControlToolBar()->UpdateStatusBar(this);
1321 
1322  mTimer = std::make_unique<wxTimer>(this, AudacityProjectTimerID);
1323  RestartTimer();
1324 
1325 #if wxUSE_DRAG_AND_DROP
1326  // We can import now, so become a drag target
1327 // SetDropTarget(safenew AudacityDropTarget(this));
1328 // mTrackPanel->SetDropTarget(safenew AudacityDropTarget(this));
1329 
1330  // SetDropTarget takes ownership
1331  mTrackPanel->SetDropTarget(safenew DropTarget(this));
1332 #endif
1333 
1334  wxTheApp->Bind(EVT_AUDIOIO_CAPTURE,
1336  this);
1337 
1338 #ifdef EXPERIMENTAL_DA2
1339  ClearBackground();// For wxGTK.
1340 #endif
1341 }
1342 
1344 {
1345  // Tool manager gives us capture sometimes
1346  if(HasCapture())
1347  ReleaseMouse();
1348 
1349  if(mTrackPanel) {
1350 #ifdef EXPERIMENTAL_SCRUBBING_BASIC
1352 #endif
1355  }
1356 }
1357 
1359 {
1360  SetBackgroundColour(theTheme.Colour( clrMedium ));
1361  ClearBackground();// For wxGTK.
1363 }
1364 
1365 
1367 {
1368  AudioIOStartStreamOptions options { GetRate() };
1369  options.timeTrack = GetTracks()->GetTimeTrack();
1370  options.listener = this;
1371  return options;
1372 }
1373 
1375 {
1376  auto PlayAtSpeedRate = gAudioIO->GetBestRate(
1377  false, //not capturing
1378  true, //is playing
1379  GetRate() //suggested rate
1380  );
1381  AudioIOStartStreamOptions options{ PlayAtSpeedRate };
1382  options.timeTrack = GetTracks()->GetTimeTrack();
1383  options.listener = this;
1384  return options;
1385 }
1386 
1387 
1389 {
1390  gPrefs->Read(wxT("/AudioFiles/ShowId3Dialog"), &mShowId3Dialog, true);
1391  gPrefs->Read(wxT("/AudioFiles/NormalizeOnLoad"),&mNormalizeOnLoad, false);
1392  gPrefs->Read(wxT("/GUI/AutoScroll"), &mViewInfo.bUpdateTrackIndicator, true);
1393  gPrefs->Read(wxT("/GUI/EmptyCanBeDirty"), &mEmptyCanBeDirty, true );
1394 // DA: Default for DA is manual from internet.
1395 #ifdef EXPERIMENTAL_DA
1396  gPrefs->Read(wxT("/GUI/Help"), &mHelpPref, wxT("FromInternet") );
1397 #else
1398  gPrefs->Read(wxT("/GUI/Help"), &mHelpPref, wxT("Local") );
1399 #endif
1400  gPrefs->Read(wxT("/GUI/ShowSplashScreen"), &mShowSplashScreen, true);
1401  gPrefs->Read(wxT("/GUI/Solo"), &mSoloPref, wxT("Simple"));
1402  // Update the old default to the NEW default.
1403  if (mSoloPref == wxT("Standard"))
1404  mSoloPref = wxT("Simple");
1405  gPrefs->Read(wxT("/GUI/TracksFitVerticallyZoomed"), &mTracksFitVerticallyZoomed, false);
1406  // gPrefs->Read(wxT("/GUI/UpdateSpectrogram"), &mViewInfo.bUpdateSpectrogram, true);
1407 
1408  // This code to change an empty projects rate is currently disabled, after discussion.
1409  // The rule 'Default sample rate' only affects newly created projects was felt to
1410  // be simpler and better.
1411 #if 0
1412  // The DefaultProjectSample rate is the rate for new projects.
1413  // Do not change this project's rate, unless there are no tracks.
1414  if( GetTrackCount() == 0){
1415  gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleRate"), &mRate, AudioIO::GetOptimalSupportedSampleRate());
1416  // If necessary, we change this rate in the selection toolbar too.
1417  auto bar = GetSelectionBar();
1418  if( bar ){
1419  bar->SetRate( mRate );
1420  }
1421  }
1422 #endif
1423 
1425 }
1426 
1428 {
1430 
1431  SetProjectTitle();
1432 
1433  {
1434  ObjectFactorySetLocker locker;
1435  for( const auto &pObject : mAttachedObjects )
1436  pObject->UpdatePrefs();
1437  }
1438 
1439  GetMenuManager(*this).UpdatePrefs();
1440 
1441  if (mTrackPanel) {
1443  }
1444  if (mMixerBoard)
1446 
1447  if (mToolManager) {
1448  mToolManager->UpdatePrefs();
1449  }
1450 
1451  if (mRuler) {
1452  mRuler->UpdatePrefs();
1453  }
1454 }
1455 
1457 {
1458  mAliasMissingWarningDialog = dialog;
1459 }
1460 
1462 {
1464 }
1465 
1466 void AudacityProject::RedrawProject(const bool bForceWaveTracks /*= false*/)
1467 {
1468  FixScrollbars();
1469  if (bForceWaveTracks && GetTracks())
1470  {
1471  for (auto pWaveTrack : GetTracks()->Any<WaveTrack>())
1472  for (const auto &clip: pWaveTrack->GetClips())
1473  clip->MarkChanged();
1474  }
1475  mTrackPanel->Refresh(false);
1476 }
1477 
1479 {
1481 }
1482 
1483 void AudacityProject::SetSel0(double newSel0)
1484 {
1485  //Bound checking should go on here
1486 
1487  mViewInfo.selectedRegion.setT0(newSel0);
1488 }
1489 
1490 void AudacityProject::SetSel1(double newSel1)
1491 {
1492  //Bound checking should go on here
1493 
1494  mViewInfo.selectedRegion.setT1(newSel1);
1495 }
1496 
1497 void AudacityProject::OnCapture(wxCommandEvent& evt)
1498 {
1499  evt.Skip();
1500 
1501  if (evt.GetInt() != 0)
1502  mIsCapturing = true;
1503  else
1504  mIsCapturing = false;
1505 }
1506 
1507 
1508 const std::shared_ptr<DirManager> &AudacityProject::GetDirManager()
1509 {
1510  return mDirManager;
1511 }
1512 
1514 {
1515  return mTrackFactory.get();
1516 }
1517 
1519 {
1520  return mRuler;
1521 }
1522 
1524 {
1525  return mAudioIOToken;
1526 }
1527 
1529 {
1530  mAudioIOToken = token;
1531 }
1532 
1534 {
1535  return GetAudioIOToken() > 0 &&
1537 }
1538 
1540 {
1541  return mTags.get();
1542 }
1543 
1544 void AudacityProject::SetTags( const std::shared_ptr<Tags> &tags )
1545 {
1546  mTags = tags;
1547 }
1548 
1550 {
1551  wxString name = wxFileNameFromPath(mFileName);
1552 
1553  // Chop off the extension
1554  size_t len = name.Len();
1555  if (len > 4 && name.Mid(len - 4) == wxT(".aup"))
1556  name = name.Mid(0, len - 4);
1557 
1558  return name;
1559 }
1560 
1561 // Pass a number in to show project number, or -1 not to.
1563 {
1564  wxString name = GetName();
1565 
1566  // If we are showing project numbers, then we also explicitly show "<untitled>" if there
1567  // is none.
1568  if( number >= 0 ){
1569  /* i18n-hint: The %02i is the project number, the %s is the project name.*/
1570  name = wxString::Format( _TS("[Project %02i] Audacity \"%s\""), number+1 ,
1571  name.IsEmpty() ? "<untitled>" : (const char *)name );
1572  }
1573  // If we are not showing numbers, then <untitled> shows as 'Audacity'.
1574  else if( name.IsEmpty() )
1575  {
1576  mbLoadedFromAup = false;
1577  name = _TS("Audacity");
1578  }
1579 
1580  if (mIsRecovered)
1581  {
1582  name += wxT(" ");
1583  /* i18n-hint: E.g this is recovered audio that had been lost.*/
1584  name += _("(Recovered)");
1585  }
1586 
1587  SetTitle( name );
1588  SetName(name); // to make the nvda screen reader read the correct title
1589 }
1590 
1592 {
1593  return mTracks->empty();
1594 }
1595 
1597 {
1598  if (mSnapTo != SNAP_OFF) {
1599  SelectedRegion &selectedRegion = mViewInfo.selectedRegion;
1601  const bool nearest = (mSnapTo == SNAP_NEAREST);
1602 
1603  const double oldt0 = selectedRegion.t0();
1604  const double oldt1 = selectedRegion.t1();
1605 
1606  nc.ValueToControls(oldt0, nearest);
1607  nc.ControlsToValue();
1608  const double t0 = nc.GetValue();
1609 
1610  nc.ValueToControls(oldt1, nearest);
1611  nc.ControlsToValue();
1612  const double t1 = nc.GetValue();
1613 
1614  if (t0 != oldt0 || t1 != oldt1) {
1615  selectedRegion.setTimes(t0, t1);
1617  return true;
1618  }
1619  }
1620 
1621  return false;
1622 }
1623 
1625 {
1626  return mRate;
1627 }
1628 
1629 // Typically this came from the SelectionToolbar and does not need to
1630 // be communicated back to it.
1632 {
1633  mRate = rate;
1634 }
1635 
1637 {
1638  return GetSnapTo();
1639 }
1640 
1642 {
1643  mSnapTo = snap;
1644 
1645 // LLL: TODO - what should this be changed to???
1646 // GetCommandManager()->Check(wxT("Snap"), mSnapTo);
1647  gPrefs->Write(wxT("/SnapTo"), mSnapTo);
1648  gPrefs->Flush();
1649 
1650  SnapSelection();
1651 
1652  RedrawProject();
1653 }
1654 
1656 {
1657  return GetSelectionFormat();
1658 }
1659 
1661 {
1663 
1664  gPrefs->Write(wxT("/SelectionFormat"), mSelectionFormat.Internal());
1665  gPrefs->Flush();
1666 
1667  if (SnapSelection() && GetTrackPanel())
1668  GetTrackPanel()->Refresh(false);
1669 }
1670 
1672 {
1673  // Return maximum of project rate and all track rates.
1674  return std::max( mRate,
1675  mTracks->Any<const WaveTrack>().max( &WaveTrack::GetRate ) );
1676 }
1677 
1679 {
1681 }
1682 
1684 {
1685  mFrequencySelectionFormatName = formatName;
1686 
1687  gPrefs->Write(wxT("/FrequencySelectionFormatName"),
1689  gPrefs->Flush();
1690 }
1691 
1693 {
1695 }
1696 
1698 {
1699  mBandwidthSelectionFormatName = formatName;
1700 
1701  gPrefs->Write(wxT("/BandwidthSelectionFormatName"),
1703  gPrefs->Flush();
1704 }
1705 
1706 void AudacityProject::SSBL_ModifySpectralSelection(double &bottom, double &top, bool done)
1707 {
1708 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
1709  double nyq = SSBL_GetRate() / 2.0;
1710  if (bottom >= 0.0)
1711  bottom = std::min(nyq, bottom);
1712  if (top >= 0.0)
1713  top = std::min(nyq, top);
1715  mTrackPanel->Refresh(false);
1716  if (done) {
1717  ModifyState(false);
1718  }
1719 #else
1720  bottom; top; done;
1721 #endif
1722 }
1723 
1725 {
1727 }
1728 
1730 {
1732 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
1733  if (GetSpectralSelectionBar()) {
1735  }
1736 #endif
1737 }
1738 
1740 {
1742 }
1743 
1745 {
1747 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
1748  if (GetSpectralSelectionBar()) {
1750  }
1751 #endif
1752 }
1753 
1755 {
1756  AS_SetSelectionFormat(format);
1757  if (GetSelectionBar()) {
1759  }
1760 }
1761 
1763 {
1764  return mSelectionFormat;
1765 }
1766 
1767 
1768 void AudacityProject::AS_ModifySelection(double &start, double &end, bool done)
1769 {
1770  mViewInfo.selectedRegion.setTimes(start, end);
1771  mTrackPanel->Refresh(false);
1772  if (done) {
1773  ModifyState(false);
1774  }
1775 }
1776 
1778 {
1779  // Set a flag so we don't have to generate two update events
1780  mAutoScrolling = true;
1781 
1782  // Call our Scroll method which updates our ViewInfo variables
1783  // to reflect the positions of the scrollbars
1784  DoScroll();
1785 
1786  mAutoScrolling = false;
1787 }
1788 
1789 
1795 {
1796  wxInt64 pos = mHsbar->GetThumbPosition();
1797  // move at least one scroll increment
1798  pos -= wxMax((wxInt64)(sbarHjump * mViewInfo.sbarScale), 1);
1799  pos = wxMax(pos, 0);
1801  mViewInfo.sbarH = std::max(mViewInfo.sbarH,
1802  -(wxInt64) PixelWidthBeforeTime(0.0));
1803 
1804 
1805  if (pos != mHsbar->GetThumbPosition()) {
1806  mHsbar->SetThumbPosition((int)pos);
1807  FinishAutoScroll();
1808  }
1809 }
1814 
1816 {
1817  wxInt64 pos = mHsbar->GetThumbPosition();
1818  // move at least one scroll increment
1819  // use wxInt64 for calculation to prevent temporary overflow
1820  pos += wxMax((wxInt64)(sbarHjump * mViewInfo.sbarScale), 1);
1821  wxInt64 max = mHsbar->GetRange() - mHsbar->GetThumbSize();
1822  pos = wxMin(pos, max);
1826  - (wxInt64) PixelWidthBeforeTime(0.0) - mViewInfo.sbarScreen);
1827 
1828  if (pos != mHsbar->GetThumbPosition()) {
1829  mHsbar->SetThumbPosition((int)pos);
1830  FinishAutoScroll();
1831  }
1832 }
1833 
1837 void AudacityProject::OnScrollLeftButton(wxScrollEvent & /*event*/)
1838 {
1839  wxInt64 pos = mHsbar->GetThumbPosition();
1840  // move at least one scroll increment
1841  pos -= wxMax((wxInt64)(sbarHjump * mViewInfo.sbarScale), 1);
1842  pos = wxMax(pos, 0);
1844  mViewInfo.sbarH = std::max(mViewInfo.sbarH,
1845  - (wxInt64) PixelWidthBeforeTime(0.0));
1846 
1847  if (pos != mHsbar->GetThumbPosition()) {
1848  mHsbar->SetThumbPosition((int)pos);
1849  DoScroll();
1850  }
1851 }
1852 
1856 void AudacityProject::OnScrollRightButton(wxScrollEvent & /*event*/)
1857 {
1858  wxInt64 pos = mHsbar->GetThumbPosition();
1859  // move at least one scroll increment
1860  // use wxInt64 for calculation to prevent temporary overflow
1861  pos += wxMax((wxInt64)(sbarHjump * mViewInfo.sbarScale), 1);
1862  wxInt64 max = mHsbar->GetRange() - mHsbar->GetThumbSize();
1863  pos = wxMin(pos, max);
1867  - (wxInt64) PixelWidthBeforeTime(0.0) - mViewInfo.sbarScreen);
1868 
1869  if (pos != mHsbar->GetThumbPosition()) {
1870  mHsbar->SetThumbPosition((int)pos);
1871  DoScroll();
1872  }
1873 }
1874 
1875 
1877 {
1879  return true;
1880 
1881  if (GetScrubber().HasMark() ||
1882  IsAudioActive()) {
1883  if (mPlaybackScroller) {
1884  auto mode = mPlaybackScroller->GetMode();
1885  if (mode == PlaybackScroller::Mode::Pinned ||
1887  return true;
1888  }
1889  }
1890 
1891  return false;
1892 }
1893 
1895 {
1896  if (!MayScrollBeyondZero())
1897  return 0;
1898  const double screen = mTrackPanel->GetScreenEndTime() - mViewInfo.h;
1899  return std::min(mTracks->GetStartTime(), -screen);
1900 }
1901 
1902 // PRL: Bug1197: we seem to need to compute all in double, to avoid differing results on Mac
1903 // That's why ViewInfo::TimeRangeToPixelWidth was defined, with some regret.
1904 double AudacityProject::PixelWidthBeforeTime(double scrollto) const
1905 {
1906  const double lowerBound = ScrollingLowerBoundTime();
1907  return
1908  // Ignoring fisheye is correct here
1909  mViewInfo.TimeRangeToPixelWidth(scrollto - lowerBound);
1910 }
1911 
1913 {
1914  const auto unscaled = PixelWidthBeforeTime(scrollto);
1915  const int max = mHsbar->GetRange() - mHsbar->GetThumbSize();
1916  const int pos =
1917  std::min(max,
1918  std::max(0,
1919  (int)(floor(0.5 + unscaled * mViewInfo.sbarScale))));
1920  mHsbar->SetThumbPosition(pos);
1921  mViewInfo.sbarH = floor(0.5 + unscaled - PixelWidthBeforeTime(0.0));
1922  mViewInfo.sbarH = std::max(mViewInfo.sbarH,
1923  - (wxInt64) PixelWidthBeforeTime(0.0));
1926  - (wxInt64) PixelWidthBeforeTime(0.0) - mViewInfo.sbarScreen);
1927 }
1928 
1929 //
1930 // This method, like the other methods prefaced with TP, handles TrackPanel
1931 // 'callback'.
1932 //
1934 {
1935  SetHorizontalThumb(scrollto);
1936 
1937  // Call our Scroll method which updates our ViewInfo variables
1938  // to reflect the positions of the scrollbars
1939  DoScroll();
1940 }
1941 
1942 //
1943 // Scroll vertically. This is called for example by the mouse wheel
1944 // handler in Track Panel. A positive argument makes the window
1945 // scroll down, while a negative argument scrolls up.
1946 //
1948 {
1949  int oldPos = mVsbar->GetThumbPosition();
1950  int pos = oldPos + delta;
1951  int max = mVsbar->GetRange() - mVsbar->GetThumbSize();
1952 
1953  // Can be negative in case of only one track
1954  if (max < 0)
1955  max = 0;
1956 
1957  if (pos > max)
1958  pos = max;
1959  else if (pos < 0)
1960  pos = 0;
1961 
1962  if (pos != oldPos)
1963  {
1964  mVsbar->SetThumbPosition(pos);
1965 
1966  DoScroll();
1967  return true;
1968  }
1969  else
1970  return false;
1971 }
1972 
1974 {
1975  if (!GetTracks())
1976  return;
1977 
1978  bool refresh = false;
1979  bool rescroll = false;
1980 
1981  int totalHeight = (mTracks->GetHeight() + 32);
1982 
1983  int panelWidth, panelHeight;
1984  mTrackPanel->GetTracksUsableArea(&panelWidth, &panelHeight);
1985 
1986  // (From Debian...at least I think this is the change cooresponding
1987  // to this comment)
1988  //
1989  // (2.) GTK critical warning "IA__gtk_range_set_range: assertion
1990  // 'min < max' failed" because of negative numbers as result of window
1991  // size checking. Added a sanity check that straightens up the numbers
1992  // in edge cases.
1993  if (panelWidth < 0) {
1994  panelWidth = 0;
1995  }
1996  if (panelHeight < 0) {
1997  panelHeight = 0;
1998  }
1999 
2000  auto LastTime = std::numeric_limits<double>::lowest();
2001  auto &tracks = *GetTracks();
2002  for (const Track *track : tracks) {
2003  // Iterate over pending changed tracks if present.
2004  track = track->SubstitutePendingChangedTrack().get();
2005  LastTime = std::max( LastTime, track->GetEndTime() );
2006  }
2007  LastTime =
2008  std::max(LastTime, mViewInfo.selectedRegion.t1());
2009 
2010  const double screen = GetScreenEndTime() - mViewInfo.h;
2011  const double halfScreen = screen / 2.0;
2012 
2013  // If we can scroll beyond zero,
2014  // Add 1/2 of a screen of blank space to the end
2015  // and another 1/2 screen before the beginning
2016  // so that any point within the union of the selection and the track duration
2017  // may be scrolled to the midline.
2018  // May add even more to the end, so that you can always scroll the starting time to zero.
2019  const double lowerBound = ScrollingLowerBoundTime();
2020  const double additional = MayScrollBeyondZero()
2021  ? -lowerBound + std::max(halfScreen, screen - LastTime)
2022  : screen / 4.0;
2023 
2024  mViewInfo.total = LastTime + additional;
2025 
2026  // Don't remove time from total that's still on the screen
2027  mViewInfo.total = std::max(mViewInfo.total, mViewInfo.h + screen);
2028 
2029  if (mViewInfo.h < lowerBound) {
2030  mViewInfo.h = lowerBound;
2031  rescroll = true;
2032  }
2033 
2034  mViewInfo.sbarTotal = (wxInt64) (mViewInfo.GetTotalWidth());
2035  mViewInfo.sbarScreen = (wxInt64)(panelWidth);
2037 
2038  // PRL: Can someone else find a more elegant solution to bug 812, than
2039  // introducing this boolean member variable?
2040  // Setting mVSbar earlier, int HandlXMLTag, didn't succeed in restoring
2041  // the vertical scrollbar to its saved position. So defer that till now.
2042  // mbInitializingScrollbar should be true only at the start of the life
2043  // of an AudacityProject reopened from disk.
2044  if (!mbInitializingScrollbar) {
2045  mViewInfo.vpos = mVsbar->GetThumbPosition() * mViewInfo.scrollStep;
2046  }
2047  mbInitializingScrollbar = false;
2048 
2049  if (mViewInfo.vpos >= totalHeight)
2050  mViewInfo.vpos = totalHeight - 1;
2051  if (mViewInfo.vpos < 0)
2052  mViewInfo.vpos = 0;
2053 
2054  bool oldhstate;
2055  bool oldvstate;
2056  bool newhstate = (GetScreenEndTime() - mViewInfo.h) < mViewInfo.total;
2057  bool newvstate = panelHeight < totalHeight;
2058 
2059 #ifdef __WXGTK__
2060  oldhstate = mHsbar->IsShown();
2061  oldvstate = mVsbar->IsShown();
2062  mHsbar->Show(newhstate);
2063  mVsbar->Show(panelHeight < totalHeight);
2064 #else
2065  oldhstate = mHsbar->IsEnabled();
2066  oldvstate = mVsbar->IsEnabled();
2067  mHsbar->Enable(newhstate);
2068  mVsbar->Enable(panelHeight < totalHeight);
2069 #endif
2070 
2071  if (panelHeight >= totalHeight && mViewInfo.vpos != 0) {
2072  mViewInfo.vpos = 0;
2073 
2074  refresh = true;
2075  rescroll = false;
2076  }
2077  if (!newhstate && mViewInfo.sbarH != 0) {
2078  mViewInfo.sbarH = 0;
2079 
2080  refresh = true;
2081  rescroll = false;
2082  }
2083 
2084  // wxScrollbar only supports int values but we need a greater range, so
2085  // we scale the scrollbar coordinates on demand. We only do this if we
2086  // would exceed the int range, so we can always use the maximum resolution
2087  // available.
2088 
2089  // Don't use the full 2^31 max int range but a bit less, so rounding
2090  // errors in calculations do not overflow max int
2091  wxInt64 maxScrollbarRange = (wxInt64)(2147483647 * 0.999);
2092  if (mViewInfo.sbarTotal > maxScrollbarRange)
2093  mViewInfo.sbarScale = ((double)maxScrollbarRange) / mViewInfo.sbarTotal;
2094  else
2095  mViewInfo.sbarScale = 1.0; // use maximum resolution
2096 
2097  {
2098  int scaledSbarH = (int)(mViewInfo.sbarH * mViewInfo.sbarScale);
2099  int scaledSbarScreen = (int)(mViewInfo.sbarScreen * mViewInfo.sbarScale);
2100  int scaledSbarTotal = (int)(mViewInfo.sbarTotal * mViewInfo.sbarScale);
2101  const int offset =
2102  (int)(floor(0.5 + mViewInfo.sbarScale * PixelWidthBeforeTime(0.0)));
2103 
2104  mHsbar->SetScrollbar(scaledSbarH + offset, scaledSbarScreen, scaledSbarTotal,
2105  scaledSbarScreen, TRUE);
2106  }
2107 
2108  // Vertical scrollbar
2109  mVsbar->SetScrollbar(mViewInfo.vpos / mViewInfo.scrollStep,
2110  panelHeight / mViewInfo.scrollStep,
2111  totalHeight / mViewInfo.scrollStep,
2112  panelHeight / mViewInfo.scrollStep, TRUE);
2113 
2114  if (refresh || (rescroll &&
2116  mTrackPanel->Refresh(false);
2117  }
2118 
2119  GetMenuManager(*this).UpdateMenus(*this);
2120 
2121  if (oldhstate != newhstate || oldvstate != newvstate) {
2122  UpdateLayout();
2123  }
2124 
2125  CallAfter(
2126  [this]{ if (GetTrackPanel())
2127  GetTrackPanel()->HandleCursorForPresentMouseState(); } );
2128 }
2129 
2131 {
2132  if (!mTrackPanel)
2133  return;
2134 
2135  // 1. Layout panel, to get widths of the docks.
2136  Layout();
2137  // 2. Layout toolbars to pack the toolbars correctly in docks which
2138  // are now the correct width.
2139  mToolManager->LayoutToolBars();
2140  // 3. Layout panel, to resize docks, in particular reducing the height
2141  // of any empty docks, or increasing the height of docks that need it.
2142  Layout();
2143 
2144  // Retrieve size of this projects window
2145  wxSize mainsz = GetSize();
2146 
2147  // Retrieve position of the track panel to use as the size of the top
2148  // third of the window
2149  wxPoint tppos = ClientToScreen(mTrackPanel->GetParent()->GetPosition());
2150 
2151  // Retrieve position of bottom dock to use as the size of the bottom
2152  // third of the window
2153  wxPoint sbpos = ClientToScreen(mToolManager->GetBotDock()->GetPosition());
2154 
2155  // The "+ 50" is the minimum height of the TrackPanel
2156  SetSizeHints(250, (mainsz.y - sbpos.y) + tppos.y + 50, 20000, 20000);
2157 }
2158 
2160 {
2161  if (!mTrackPanel)
2162  return;
2163 
2164  FixScrollbars();
2165 
2166  UpdateLayout();
2167 }
2168 
2169 // How many projects that do not have a name yet?
2171 {
2172  int j = 0;
2173  for ( size_t i = 0; i < gAudacityProjects.size(); i++) {
2174  if ( gAudacityProjects[i] )
2175  if ( gAudacityProjects[i]->GetName().IsEmpty() )
2176  j++;
2177  }
2178  return j;
2179 }
2180 
2181 void AudacityProject::RefreshAllTitles(bool bShowProjectNumbers )
2182 {
2183  for ( size_t i = 0; i < gAudacityProjects.size(); i++) {
2184  if ( gAudacityProjects[i] ) {
2185  if ( !gAudacityProjects[i]->mIconized ) {
2186  AudacityProject * p;
2187  p = gAudacityProjects[i].get();
2188  p->SetProjectTitle( bShowProjectNumbers ? p->GetProjectNumber() : -1 );
2189  }
2190  }
2191  }
2192 }
2193 
2194 void AudacityProject::OnIconize(wxIconizeEvent &event)
2195 {
2196  int VisibleProjectCount = 0;
2197 
2198  //JKC: On Iconizing we get called twice. Don't know
2199  // why but it does no harm.
2200  // Should we be returning true/false rather than
2201  // void return? I don't know.
2202  mIconized = event.IsIconized();
2203 
2204  unsigned int i;
2205 
2206  // VisibileProjectCount seems to be just a counter for debugging.
2207  // It's not used outside this function.
2208  for(i=0;i<gAudacityProjects.size();i++){
2209  if(gAudacityProjects[i]){
2210  if( !gAudacityProjects[i]->mIconized )
2211  VisibleProjectCount++;
2212  }
2213  }
2214  event.Skip();
2215 
2216  // This step is to fix part of Bug 2040, where the BackingPanel
2217  // size was not restored after we leave Iconized state.
2218 
2219  // Queue up a resize event using OnShow so that we
2220  // refresh the track panel. But skip this, if we're iconized.
2221  if( mIconized )
2222  return;
2223  wxShowEvent Evt;
2224  OnShow( Evt );
2225 }
2226 
2227 void AudacityProject::OnMove(wxMoveEvent & event)
2228 {
2229  if (!this->IsMaximized() && !this->IsIconized())
2230  SetNormalizedWindowState(this->GetRect());
2231  event.Skip();
2232 }
2233 
2234 void AudacityProject::OnSize(wxSizeEvent & event)
2235 {
2236  // (From Debian)
2237  //
2238  // (3.) GTK critical warning "IA__gdk_window_get_origin: assertion
2239  // 'GDK_IS_WINDOW (window)' failed": Received events of type wxSizeEvent
2240  // on the main project window cause calls to "ClientToScreen" - which is
2241  // not available until the window is first shown. So the class has to
2242  // keep track of wxShowEvent events and inhibit those actions until the
2243  // window is first shown.
2244  if (mShownOnce) {
2245  HandleResize();
2246  if (!this->IsMaximized() && !this->IsIconized())
2247  SetNormalizedWindowState(this->GetRect());
2248  }
2249  event.Skip();
2250 }
2251 
2252 void AudacityProject::OnShow(wxShowEvent & event)
2253 {
2254  // Remember that the window has been shown at least once
2255  mShownOnce = true;
2256 
2257  // (From Debian...see also TrackPanel::OnTimer and AudacityTimer::Notify)
2258  //
2259  // Description: Workaround for wxWidgets bug: Reentry in clipboard
2260  // The wxWidgets bug http://trac.wxwidgets.org/ticket/16636 prevents
2261  // us from doing clipboard operations in wxShowEvent and wxTimerEvent
2262  // processing because those event could possibly be processed during
2263  // the (not sufficiently protected) Yield() of a first clipboard
2264  // operation, causing reentry. Audacity had a workaround in place
2265  // for this problem (the class "CaptureEvents"), which however isn't
2266  // applicable with wxWidgets 3.0 because it's based on changing the
2267  // gdk event handler, a change that would be overridden by wxWidgets's
2268  // own gdk event handler change.
2269  // Instead, as a NEW workaround, specifically protect those processings
2270  // of wxShowEvent and wxTimerEvent that try to do clipboard operations
2271  // from being executed within Yield(). This is done by delaying their
2272  // execution by posting pure wxWidgets events - which are never executed
2273  // during Yield().
2274  // Author: Martin Stegh fer <[email protected]>
2275  // Bug-Debian: https://bugs.debian.org/765341
2276 
2277  // the actual creation/showing of the window).
2278  // Post the event instead of calling OnSize(..) directly. This ensures that
2279  // this is a pure wxWidgets event (no GDK event behind it) and that it
2280  // therefore isn't processed within the YieldFor(..) of the clipboard
2281  // operations (workaround for Debian bug #765341).
2282  // QueueEvent() will take ownership of the event
2283  GetEventHandler()->QueueEvent(safenew wxSizeEvent(GetSize()));
2284 
2285  // Further processing by default handlers
2286  event.Skip();
2287 }
2288 
2292 void AudacityProject::OnToolBarUpdate(wxCommandEvent & event)
2293 {
2294  HandleResize();
2295 
2296  event.Skip(false); /* No need to propagate any further */
2297 }
2298 
2300 void AudacityProject::OnODTaskUpdate(wxCommandEvent & WXUNUSED(event))
2301 {
2302  //todo: add track data to the event - check to see if the project contains it before redrawing.
2303  if(mTrackPanel)
2304  mTrackPanel->Refresh(false);
2305 
2306 }
2307 
2308 //redraws the task and does other book keeping after the task is complete.
2309 void AudacityProject::OnODTaskComplete(wxCommandEvent & WXUNUSED(event))
2310 {
2311  if(mTrackPanel)
2312  mTrackPanel->Refresh(false);
2313 }
2314 
2315 void AudacityProject::OnScroll(wxScrollEvent & WXUNUSED(event))
2316 {
2317  const wxInt64 offset = PixelWidthBeforeTime(0.0);
2318  mViewInfo.sbarH =
2319  (wxInt64)(mHsbar->GetThumbPosition() / mViewInfo.sbarScale) - offset;
2320  DoScroll();
2321 }
2322 
2324 {
2325  const double lowerBound = ScrollingLowerBoundTime();
2326 
2327  int width;
2328  mTrackPanel->GetTracksUsableArea(&width, NULL);
2329  mViewInfo.SetBeforeScreenWidth(mViewInfo.sbarH, width, lowerBound);
2330 
2331 
2332  if (MayScrollBeyondZero()) {
2333  enum { SCROLL_PIXEL_TOLERANCE = 10 };
2334  if (std::abs(mViewInfo.TimeToPosition(0.0, 0
2335  )) < SCROLL_PIXEL_TOLERANCE) {
2336  // Snap the scrollbar to 0
2337  mViewInfo.h = 0;
2338  SetHorizontalThumb(0.0);
2339  }
2340  }
2341 
2342  mViewInfo.vpos = mVsbar->GetThumbPosition() * mViewInfo.scrollStep;
2343 
2344  //mchinen: do not always set this project to be the active one.
2345  //a project may autoscroll while playing in the background
2346  //I think this is okay since OnMouseEvent has one of these.
2347  //SetActiveProject(this);
2348 
2349  if (!mAutoScrolling) {
2350  mTrackPanel->Refresh(false);
2351  }
2352 
2353  CallAfter(
2354  [this]{ if (GetTrackPanel())
2355  GetTrackPanel()->HandleCursorForPresentMouseState(); } );
2356 }
2357 
2358 void AudacityProject::OnMenu(wxCommandEvent & event)
2359 {
2360 #ifdef __WXMSW__
2361  // Bug 1642: We can arrive here with bogus menu IDs, which we
2362  // proceed to process. So if bogus, don't.
2363  // The bogus menu IDs are probably generated by controls on the TrackPanel,
2364  // such as the Project Rate.
2365  // 17000 is the magic number at which we start our menu.
2366  // This code would probably NOT be OK on Mac, since we assign
2367  // some specific ID numbers.
2368  if( event.GetId() < 17000){
2369  event.Skip();
2370  return;
2371  }
2372 #endif
2373  bool handled = GetCommandManager()->HandleMenuID(
2374  event.GetId(), GetMenuManager(*this).GetUpdateFlags(*this),
2376 
2377  if (handled)
2378  event.Skip(false);
2379  else{
2380  event.ResumePropagation( 999 );
2381  event.Skip(true);
2382  }
2383 }
2384 
2385 void AudacityProject::OnUpdateUI(wxUpdateUIEvent & WXUNUSED(event))
2386 {
2387  GetMenuManager(*this).UpdateMenus(*this);
2388 }
2389 
2391 {
2392  (void)show;//compiler food
2393 #ifdef __WXMAC__
2394  // Find all the floating toolbars, and show or hide them
2395  const auto &children = GetChildren();
2396  for(const auto &child : children) {
2397  if (auto frame = dynamic_cast<ToolFrame*>(child)) {
2398  if (!show)
2399  frame->Hide();
2400  else if (frame->GetBar() &&
2401  frame->GetBar()->IsVisible())
2402  frame->Show();
2403  }
2404  }
2405 #endif
2406 }
2407 
2408 void AudacityProject::OnActivate(wxActivateEvent & event)
2409 {
2410  // Activate events can fire during window teardown, so just
2411  // ignore them.
2412  if (mIsDeleting) {
2413  return;
2414  }
2415 
2416  mActive = event.GetActive();
2417 
2418  // Under Windows, focus can be "lost" when returning to
2419  // Audacity from a different application.
2420  //
2421  // This was observed by minimizing all windows using WINDOWS+M and
2422  // then ALT+TAB to return to Audacity. Focus will be given to the
2423  // project window frame which is not at all useful.
2424  //
2425  // So, we use ToolManager's observation of focus changes in a wxEventFilter.
2426  // Then, when we receive the
2427  // activate event, we restore that focus to the child or the track
2428  // panel if no child had the focus (which probably should never happen).
2429  if (!mActive) {
2430 #ifdef __WXMAC__
2431  if (IsIconized())
2432  MacShowUndockedToolbars(false);
2433 #endif
2434  }
2435  else {
2436  SetActiveProject(this);
2437  if ( ! GetToolManager()->RestoreFocus() ) {
2438  if (mTrackPanel) {
2439  mTrackPanel->SetFocus();
2440  }
2441  }
2442 
2443 #ifdef __WXMAC__
2445 #endif
2446  }
2447  event.Skip();
2448 }
2449 
2451 {
2452  return mActive;
2453 }
2454 
2455 void AudacityProject::OnMouseEvent(wxMouseEvent & event)
2456 {
2457  if (event.ButtonDown())
2458  SetActiveProject(this);
2459 }
2460 
2461 // TitleRestorer restores project window titles to what they were, in its destructor.
2463 public:
2465  if( p->IsIconized() )
2466  p->Restore();
2467  p->Raise(); // May help identifying the window on Mac
2468 
2469  // Construct this projects name and number.
2470  sProjNumber = "";
2471  sProjName = p->GetName();
2472  if (sProjName.IsEmpty()){
2473  sProjName = _("<untitled>");
2475  if( UnnamedCount > 1 ){
2476  sProjNumber.Printf( "[Project %02i] ", p->GetProjectNumber()+1 );
2478  }
2479  } else {
2480  UnnamedCount = 0;
2481  }
2482  };
2484  if( UnnamedCount > 1 )
2486  };
2487  wxString sProjNumber;
2488  wxString sProjName;
2490 };
2491 
2492 
2493 // LL: All objects that have a reference to the DirManager should
2494 // be deleted before the final mDirManager->Deref() in this
2495 // routine. Failing to do so can cause unwanted recursion
2496 // and/or attempts to DELETE objects twice.
2497 void AudacityProject::OnCloseWindow(wxCloseEvent & event)
2498 {
2499  // We are called for the wxEVT_CLOSE_WINDOW, wxEVT_END_SESSION, and
2500  // wxEVT_QUERY_END_SESSION, so we have to protect against multiple
2501  // entries. This is a hack until the whole application termination
2502  // process can be reviewed and reworked. (See bug #964 for ways
2503  // to exercise the bug that instigated this hack.)
2504  if (mIsBeingDeleted)
2505  {
2506  event.Skip();
2507  return;
2508  }
2509 
2510  if (event.CanVeto() && (::wxIsBusy() || mbBusyImporting))
2511  {
2512  event.Veto();
2513  return;
2514  }
2515 
2516  // TODO: consider postponing these steps until after the possible veto
2517  // below: closing the two analysis dialogs, and stopping audio streams.
2518  // Streams can be for play, recording, or monitoring. But maybe it still
2519  // makes sense to stop any recording before putting up the dialog.
2520 
2521  mFreqWindow.reset();
2522  mContrastDialog.reset();
2523 
2524  // Check to see if we were playing or recording
2525  // audio, and if so, make sure Audio I/O is completely finished.
2526  // The main point of this is to properly push the state
2527  // and flush the tracks once we've completely finished
2528  // recording NEW state.
2529  // This code is derived from similar code in
2530  // AudacityProject::~AudacityProject() and TrackPanel::OnTimer().
2531  if (GetAudioIOToken()>0 &&
2533 
2534  // We were playing or recording audio, but we've stopped the stream.
2535  wxCommandEvent dummyEvent;
2536  GetControlToolBar()->OnStop(dummyEvent);
2537 
2538  FixScrollbars();
2539  SetAudioIOToken(0);
2540  RedrawProject();
2541  }
2542  else if (gAudioIO->IsMonitoring()) {
2543  gAudioIO->StopStream();
2544  }
2545 
2546  // MY: Use routine here so other processes can make same check
2547  bool bHasTracks = ProjectHasTracks();
2548 
2549  // We may not bother to prompt the user to save, if the
2550  // project is now empty.
2551  if (event.CanVeto() && (mEmptyCanBeDirty || bHasTracks)) {
2552  if (GetUndoManager()->UnsavedChanges()) {
2553  TitleRestorer Restorer( this );// RAII
2554  /* i18n-hint: The first %s numbers the project, the second %s is the project name.*/
2555  wxString Title = wxString::Format(_("%sSave changes to %s?"), Restorer.sProjNumber, Restorer.sProjName);
2556  wxString Message = _("Save project before closing?");
2557  if( !bHasTracks )
2558  {
2559  Message += _("\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.");
2560  }
2561  int result = AudacityMessageBox( Message,
2562  Title,
2563  wxYES_NO | wxCANCEL | wxICON_QUESTION,
2564  this);
2565 
2566  if (result == wxCANCEL || (result == wxYES &&
2567  !GuardedCall<bool>( [&]{ return Save(); } )
2568  )) {
2569  event.Veto();
2570  return;
2571  }
2572  }
2573  }
2574 
2575 #ifdef __WXMAC__
2576  // Fix bug apparently introduced into 2.1.2 because of wxWidgets 3:
2577  // closing a project that was made full-screen (as by clicking the green dot
2578  // or command+/; not merely "maximized" as by clicking the title bar or
2579  // Zoom in the Window menu) leaves the screen black.
2580  // Fix it by un-full-screening.
2581  // (But is there a different way to do this? What do other applications do?
2582  // I don't see full screen windows of Safari shrinking, but I do see
2583  // momentary blackness.)
2584  ShowFullScreen(false);
2585 #endif
2586 
2588 
2589  // Stop the timer since there's no need to update anything anymore
2590  mTimer.reset();
2591 
2592  // The project is now either saved or the user doesn't want to save it,
2593  // so there's no need to keep auto save info around anymore
2595 
2596  // DMM: Save the size of the last window the user closes
2597  //
2598  // LL: Save before doing anything else to the window that might make
2599  // its size change.
2600  SaveWindowSize();
2601 
2602  mIsDeleting = true;
2603 
2604  // Mac: we never quit as the result of a close.
2605  // Other systems: we quit only when the close is the result of an external
2606  // command (on Windows, those are taskbar closes, "X" box, Alt+F4, etc.)
2607  bool quitOnClose;
2608 #ifdef __WXMAC__
2609  quitOnClose = false;
2610 #else
2611  quitOnClose = !mMenuClose;
2612 #endif
2613 
2614  // DanH: If we're definitely about to quit, DELETE the clipboard.
2615  // Doing this after Deref'ing the DirManager causes problems.
2616  if ((gAudacityProjects.size() == 1) && (quitOnClose || gIsQuitting))
2617  DeleteClipboard();
2618 
2619  // JKC: For Win98 and Linux do not detach the menu bar.
2620  // We want wxWidgets to clean it up for us.
2621  // TODO: Is there a Mac issue here??
2622  // SetMenuBar(NULL);
2623 
2624  // Lock all blocks in all tracks of the last saved version, so that
2625  // the blockfiles aren't deleted on disk when we DELETE the blockfiles
2626  // in memory. After it's locked, DELETE the data structure so that
2627  // there's no memory leak.
2628  if (mLastSavedTracks) {
2629  for (auto wt : mLastSavedTracks->Any<WaveTrack>())
2630  wt->CloseLock();
2631 
2632  mLastSavedTracks->Clear(); // sends an event
2633  mLastSavedTracks.reset();
2634  }
2635 
2636  // Get rid of the history window
2637  // LL: Destroy it before the TrackPanel and ToolBars since they
2638  // may/will get additional wxEVT_PAINT events since window
2639  // destruction may be queued. This seems to only be a problem
2640  // on the Mac.
2641  if (mHistoryWindow) {
2642  mHistoryWindow->Destroy();
2643  mHistoryWindow = NULL;
2644  }
2645 
2646  // Some of the AdornedRulerPanel functions refer to the TrackPanel, so destroy this
2647  // before the TrackPanel is destroyed. This change was needed to stop Audacity
2648  // crashing when running with Jaws on Windows 10 1703.
2649  if (mRuler)
2650  mRuler->Destroy();
2651 
2652  // Destroy the TrackPanel early so it's not around once we start
2653  // deleting things like tracks and such out from underneath it.
2654  // Check validity of mTrackPanel per bug 584 Comment 1.
2655  // Deeper fix is in the Import code, but this failsafes against crash.
2656  if (mTrackPanel)
2657  {
2658  mTrackPanel->Destroy();
2659  mTrackPanel = NULL; // Make sure this gets set...see HandleResize()
2660  }
2661 
2662  // Delete the tool manager before the children since it needs
2663  // to save the state of the toolbars.
2664  mToolManager.reset();
2665 
2666  DestroyChildren();
2667 
2668  mTrackFactory.reset();
2669 
2670  mTags.reset();
2671 
2672  mImportXMLTagHandler.reset();
2673 
2674  // Delete all the tracks to free up memory and DirManager references.
2675  mTracks->Clear();
2676  mTracks.reset();
2677 
2678  // This must be done before the following Deref() since it holds
2679  // references to the DirManager.
2681 
2682  // MM: Tell the DirManager it can now DELETE itself
2683  // if it finds it is no longer needed. If it is still
2684  // used (f.e. by the clipboard), it will recognize this
2685  // and will destroy itself later.
2686  //
2687  // LL: All objects with references to the DirManager should
2688  // have been deleted before this.
2689  mDirManager.reset();
2690 
2691  AProjectHolder pSelf;
2692  {
2694  auto end = gAudacityProjects.end();
2695  auto it = std::find_if(gAudacityProjects.begin(), end,
2696  [this] (const AProjectHolder &p) { return p.get() == this; });
2697  wxASSERT( it != end );
2698  pSelf = std::move( *it );
2699  gAudacityProjects.erase(it);
2700  }
2701 
2702  if (gActiveProject == this) {
2703  // Find a NEW active project
2704  if (gAudacityProjects.size() > 0) {
2706  }
2707  else {
2708  SetActiveProject(NULL);
2709  }
2710  }
2711 
2712  // Since we're going to be destroyed, make sure we're not to
2713  // receive audio notifications anymore.
2714  if (gAudioIO->GetListener() == this) {
2716  }
2717 
2718  if (gAudacityProjects.empty() && !gIsQuitting) {
2719 
2720 #if !defined(__WXMAC__)
2721  if (quitOnClose) {
2722  QuitAudacity();
2723  }
2724  else {
2727  }
2728 #endif
2729  }
2730 
2731  // Destroys this
2732  pSelf.reset();
2733  mRuler = nullptr;
2734 
2735  mIsBeingDeleted = true;
2736 
2737 }
2738 
2739 void AudacityProject::OnOpenAudioFile(wxCommandEvent & event)
2740 {
2741  const wxString &cmd = event.GetString();
2742 
2743  if (!cmd.IsEmpty()) {
2744  OpenFile(cmd);
2745  }
2746 
2747  RequestUserAttention();
2748 }
2749 
2750 // static method, can be called outside of a project
2751 wxArrayString AudacityProject::ShowOpenDialog(const wxString &extraformat, const wxString &extrafilter)
2752 {
2753  FormatList l;
2754  wxString filter;
2755  wxString all;
2758 
2760  if (extraformat != wxEmptyString)
2761  { // additional format specified
2762  all = extrafilter + wxT(';');
2763  // add it to the "all supported files" filter string
2764  }
2765 
2766  // Construct the filter
2768 
2769  for (const auto &format : l) {
2770  /* this loop runs once per supported _format_ */
2771  const Format *f = &format;
2772 
2773  wxString newfilter = f->formatName + wxT("|");
2774  // bung format name into string plus | separator
2775  for (size_t i = 0; i < f->formatExtensions.size(); i++) {
2776  /* this loop runs once per valid _file extension_ for file containing
2777  * the current _format_ */
2778  if (!newfilter.Contains(wxT("*.") + f->formatExtensions[i] + wxT(";")))
2779  newfilter += wxT("*.") + f->formatExtensions[i] + wxT(";");
2780  if (!all.Contains(wxT("*.") + f->formatExtensions[i] + wxT(";")))
2781  all += wxT("*.") + f->formatExtensions[i] + wxT(";");
2782  }
2783  newfilter.RemoveLast(1);
2784  filter += newfilter;
2785  filter += wxT("|");
2786  }
2787  all.RemoveLast(1);
2788  filter.RemoveLast(1);
2789 
2790  // For testing long filters
2791 #if 0
2792  wxString test = wxT("*.aaa;*.bbb;*.ccc;*.ddd;*.eee");
2793  all = test + wxT(';') + test + wxT(';') + test + wxT(';') +
2794  test + wxT(';') + test + wxT(';') + test + wxT(';') +
2795  test + wxT(';') + test + wxT(';') + test + wxT(';') +
2796  all;
2797 #endif
2798 
2799  /* i18n-hint: The vertical bars and * are essential here.*/
2800  wxString mask = _("All files|*|All supported files|") +
2801  all + wxT("|"); // "all" and "all supported" entries
2802  if (extraformat != wxEmptyString)
2803  { // append caller-defined format if supplied
2804  mask += extraformat + wxT("|") + extrafilter + wxT("|");
2805  }
2806  mask += filter; // put the names and extensions of all the importer formats
2807  // we built up earlier into the mask
2808 
2809  // Retrieve saved path and type
2811  wxString type = gPrefs->Read(wxT("/DefaultOpenType"),mask.BeforeFirst(wxT('|')));
2812 
2813  // Convert the type to the filter index
2814  int index = mask.First(type + wxT("|"));
2815  if (index == wxNOT_FOUND) {
2816  index = 0;
2817  }
2818  else {
2819  index = mask.Left(index).Freq(wxT('|')) / 2;
2820  if (index < 0) {
2821  index = 0;
2822  }
2823  }
2824 
2825  // Construct and display the file dialog
2826  wxArrayString selected;
2827 
2828  FileDialogWrapper dlog(NULL,
2829  _("Select one or more files"),
2830  path,
2831  wxT(""),
2832  mask,
2833  wxFD_OPEN | wxFD_MULTIPLE | wxRESIZE_BORDER);
2834 
2835  dlog.SetFilterIndex(index);
2836 
2837  int dialogResult = dlog.ShowModal();
2838 
2839  // Convert the filter index to type and save
2840  index = dlog.GetFilterIndex();
2841  for (int i = 0; i < index; i++) {
2842  mask = mask.AfterFirst(wxT('|')).AfterFirst(wxT('|'));
2843  }
2844  gPrefs->Write(wxT("/DefaultOpenType"), mask.BeforeFirst(wxT('|')));
2845  gPrefs->Write(wxT("/LastOpenType"), mask.BeforeFirst(wxT('|')));
2846  gPrefs->Flush();
2847 
2848  if (dialogResult == wxID_OK) {
2849  // Return the selected files
2850  dlog.GetPaths(selected);
2851  }
2852  return selected;
2853 }
2854 
2855 // static method, can be called outside of a project
2856 bool AudacityProject::IsAlreadyOpen(const wxString & projPathName)
2857 {
2858  const wxFileName newProjPathName(projPathName);
2859  size_t numProjects = gAudacityProjects.size();
2860  for (size_t i = 0; i < numProjects; i++)
2861  {
2862  if (newProjPathName.SameAs(gAudacityProjects[i]->mFileName))
2863  {
2864  wxString errMsg =
2865  wxString::Format(_("%s is already open in another window."),
2866  newProjPathName.GetName());
2867  wxLogError(errMsg);
2868  AudacityMessageBox(errMsg, _("Error Opening Project"), wxOK | wxCENTRE);
2869  return true;
2870  }
2871  }
2872  return false;
2873 }
2874 
2875 // static method, can be called outside of a project
2877 {
2878  /* i18n-hint: This string is a label in the file type filter in the open
2879  * and save dialogues, for the option that only shows project files created
2880  * with Audacity. Do not include pipe symbols or .aup (this extension will
2881  * now be added automatically for the Save Projects dialogues).*/
2882  wxArrayString selectedFiles = ShowOpenDialog(_("Audacity projects"), wxT("*.aup"));
2883  if (selectedFiles.GetCount() == 0) {
2884  gPrefs->Write(wxT("/LastOpenType"),wxT(""));
2885  gPrefs->Flush();
2886  return;
2887  }
2888 
2889  //sort selected files by OD status.
2890  //For the open menu we load OD first so user can edit asap.
2891  //first sort selectedFiles.
2892  selectedFiles.Sort(CompareNoCaseFileName);
2893  ODManager::Pauser pauser;
2894 
2895  auto cleanup = finally( [] {
2896  gPrefs->Write(wxT("/LastOpenType"),wxT(""));
2897  gPrefs->Flush();
2898  } );
2899 
2900  for (size_t ff = 0; ff < selectedFiles.GetCount(); ff++) {
2901  const wxString &fileName = selectedFiles[ff];
2902 
2903  // Make sure it isn't already open.
2904  if (AudacityProject::IsAlreadyOpen(fileName))
2905  continue; // Skip ones that are already open.
2906 
2908 
2909  // DMM: If the project is dirty, that means it's been touched at
2910  // all, and it's not safe to open a NEW project directly in its
2911  // place. Only if the project is brand-NEW clean and the user
2912  // hasn't done any action at all is it safe for Open to take place
2913  // inside the current project.
2914  //
2915  // If you try to Open a NEW project inside the current window when
2916  // there are no tracks, but there's an Undo history, etc, then
2917  // bad things can happen, including data files moving to the NEW
2918  // project directory, etc.
2919  if ( proj && ( proj->mDirty || !proj->mTracks->empty() ) )
2920  proj = nullptr;
2921 
2922  // This project is clean; it's never been touched. Therefore
2923  // all relevant member variables are in their initial state,
2924  // and it's okay to open a NEW project inside this window.
2925  proj = AudacityProject::OpenProject( proj, fileName );
2926  }
2927 }
2928 
2929 // Most of this string was duplicated 3 places. Made the warning consistent in this global.
2930 // The %s is to be filled with the version string.
2931 // PRL: Do not statically allocate a string in _() !
2932 static wxString gsLegacyFileWarning() { return
2933 _("This file was saved by Audacity version %s. The format has changed. \
2934 \n\nAudacity can try to open and save this file, but saving it in this \
2935 \nversion will then prevent any 1.2 or earlier version opening it. \
2936 \n\nAudacity might corrupt the file in opening it, so you should \
2937 back it up first. \
2938 \n\nOpen this file now?");
2939 }
2940 
2942 {
2943  wxString msg;
2944  msg.Printf(gsLegacyFileWarning(), _("1.0 or earlier"));
2945 
2946  // Stop icon, and choose 'NO' by default.
2947  int action =
2948  AudacityMessageBox(msg,
2949  _("Warning - Opening Old Project File"),
2950  wxYES_NO | wxICON_STOP | wxNO_DEFAULT | wxCENTRE,
2951  this);
2952  return (action != wxNO);
2953 }
2954 
2955 
2957  AudacityProject *pProject, const wxString &fileNameArg, bool addtohistory)
2958 {
2959  AudacityProject *pNewProject = nullptr;
2960  if ( ! pProject )
2961  pProject = pNewProject = CreateNewAudacityProject();
2962  auto cleanup = finally( [&] { if( pNewProject ) pNewProject->Close(true); } );
2963  pProject->OpenFile( fileNameArg, addtohistory );
2964  pNewProject = nullptr;
2965  if( pProject && pProject->mIsRecovered )
2966  pProject->Zoom( ViewActions::GetZoomOfToFit( *pProject ) );
2967 
2968  return pProject;
2969 }
2970 
2971 // FIXME:? TRAP_ERR This should return a result that is checked.
2972 // See comment in AudacityApp::MRUOpen().
2973 void AudacityProject::OpenFile(const wxString &fileNameArg, bool addtohistory)
2974 {
2975  // On Win32, we may be given a short (DOS-compatible) file name on rare
2976  // occassions (e.g. stuff like "C:\PROGRA~1\AUDACI~1\PROJEC~1.AUP"). We
2977  // convert these to long file name first.
2980 
2981  // Make sure it isn't already open.
2982  // Vaughan, 2011-03-25: This was done previously in AudacityProject::OpenFiles()
2983  // and AudacityApp::MRUOpen(), but if you open an aup file by double-clicking it
2984  // from, e.g., Win Explorer, it would bypass those, get to here with no check,
2985  // then open a NEW project from the same data with no warning.
2986  // This was reported in http://bugzilla.audacityteam.org/show_bug.cgi?id=137#c17,
2987  // but is not really part of that bug. Anyway, prevent it!
2988  if (AudacityProject::IsAlreadyOpen(fileName))
2989  return;
2990 
2991 
2992  // Data loss may occur if users mistakenly try to open ".aup.bak" files
2993  // left over from an unsuccessful save or by previous versions of Audacity.
2994  // So we always refuse to open such files.
2995  if (fileName.Lower().EndsWith(wxT(".aup.bak")))
2996  {
2998  _("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."),
2999  _("Warning - Backup File Detected"),
3000  wxOK | wxCENTRE, this);
3001  return;
3002  }
3003 
3004  if (!::wxFileExists(fileName)) {
3006  wxString::Format( _("Could not open file: %s"), fileName ),
3007  ("Error Opening File"),
3008  wxOK | wxCENTRE, this);
3009  return;
3010  }
3011 
3012  // We want to open projects using wxTextFile, but if it's NOT a project
3013  // file (but actually a WAV file, for example), then wxTextFile will spin
3014  // for a long time searching for line breaks. So, we look for our
3015  // signature at the beginning of the file first:
3016 
3017  char buf[16];
3018  {
3019  wxFFile ff(fileName, wxT("rb"));
3020  if (!ff.IsOpened()) {
3022  wxString::Format( _("Could not open file: %s"), fileName ),
3023  _("Error opening file"),
3024  wxOK | wxCENTRE, this);
3025  return;
3026  }
3027  int numRead = ff.Read(buf, 15);
3028  if (numRead != 15) {
3029  AudacityMessageBox(wxString::Format(_("File may be invalid or corrupted: \n%s"),
3030  (const wxChar*)fileName), _("Error Opening File or Project"),
3031  wxOK | wxCENTRE, this);
3032  ff.Close();
3033  return;
3034  }
3035  buf[15] = 0;
3036  }
3037 
3038  wxString temp = LAT1CTOWX(buf);
3039 
3040  if (temp == wxT("AudacityProject")) {
3041  // It's an Audacity 1.0 (or earlier) project file.
3042  // If they bail out, return and do no more.
3043  if( !WarnOfLegacyFile() )
3044  return;
3045  // Convert to the NEW format.
3046  bool success = ConvertLegacyProjectFile(wxFileName{ fileName });
3047  if (!success) {
3048  AudacityMessageBox(_("Audacity was unable to convert an Audacity 1.0 project to the new project format."),
3049  _("Error Opening Project"),
3050  wxOK | wxCENTRE, this);
3051  return;
3052  }
3053  else {
3054  temp = wxT("<?xml ");
3055  }
3056  }
3057 
3058  // FIXME: //v Surely we could be smarter about this, like checking much earlier that this is a .aup file.
3059  if (temp.Mid(0, 6) != wxT("<?xml ")) {
3060  // If it's not XML, try opening it as any other form of audio
3061 
3062 #ifdef EXPERIMENTAL_DRAG_DROP_PLUG_INS
3063  // Is it a plug-in?
3064  if (PluginManager::Get().DropFile(fileName)) {
3066  }
3067  else
3068  // No, so import.
3069 #endif
3070 
3071  {
3072 #ifdef USE_MIDI
3073  if (Importer::IsMidi(fileName))
3074  FileActions::DoImportMIDI(this, fileName);
3075  else
3076 #endif
3077  Import(fileName);
3078 
3079  ZoomAfterImport(nullptr);
3080  }
3081 
3082  return;
3083  }
3084 
3088 
3089  mFileName = fileName;
3090  mbLoadedFromAup = true;
3091 
3092  mRecoveryAutoSaveDataDir = wxT("");
3093  mIsRecovered = false;
3094 
3095  SetProjectTitle();
3096 
3097  const wxString autoSaveExt = wxT(".autosave");
3098  if (mFileName.Length() >= autoSaveExt.Length() &&
3099  mFileName.Right(autoSaveExt.Length()) == autoSaveExt)
3100  {
3101  AutoSaveFile asf;
3102  if (!asf.Decode(fileName))
3103  {
3105  wxString::Format( _("Could not decode file: %s"), fileName ),
3106  _("Error decoding file"),
3107  wxOK | wxCENTRE, this);
3108  return;
3109  }
3110  }
3111 
3112  XMLFileReader xmlFile;
3113 
3114  // 'Lossless copy' projects have dependencies. We need to always copy-in
3115  // these dependencies when converting to a normal project.
3116  wxString oldAction = gPrefs->Read(wxT("/FileFormats/CopyOrEditUncompressedData"), wxT("copy"));
3117  bool oldAsk = gPrefs->Read(wxT("/Warnings/CopyOrEditUncompressedDataAsk"), true)?true:false;
3118  if (oldAction != wxT("copy"))
3119  gPrefs->Write(wxT("/FileFormats/CopyOrEditUncompressedData"), wxT("copy"));
3120  if (oldAsk)
3121  gPrefs->Write(wxT("/Warnings/CopyOrEditUncompressedDataAsk"), (long) false);
3122  gPrefs->Flush();
3123 
3124  bool bParseSuccess = xmlFile.Parse(this, fileName);
3125 
3126  // and restore old settings if necessary.
3127  if (oldAction != wxT("copy"))
3128  gPrefs->Write(wxT("/FileFormats/CopyOrEditUncompressedData"), oldAction);
3129  if (oldAsk)
3130  gPrefs->Write(wxT("/Warnings/CopyOrEditUncompressedDataAsk"), (long) true);
3131  gPrefs->Flush();
3132 
3133  // Clean up now unused recording recovery handler if any
3134  mRecordingRecoveryHandler.reset();
3135 
3136  bool err = false;
3137 
3138  if (bParseSuccess) {
3139  // By making a duplicate set of pointers to the existing blocks
3140  // on disk, we add one to their reference count, guaranteeing
3141  // that their reference counts will never reach zero and thus
3142  // the version saved on disk will be preserved until the
3143  // user selects Save().
3144 
3146 
3147  for (auto t : GetTracks()->Any()) {
3148  if (t->GetErrorOpening())
3149  {
3150  wxLogWarning(
3151  wxT("Track %s had error reading clip values from project file."),
3152  t->GetName());
3153  err = true;
3154  }
3155 
3156  err = ( !t->LinkConsistencyCheck() ) || err;
3157 
3158  mLastSavedTracks->Add(t->Duplicate());
3159  }
3160 
3161  InitialState();
3162  mTrackPanel->SetFocusedTrack(*GetTracks()->Any().begin());
3163  HandleResize();
3164  mTrackPanel->Refresh(false);
3165  mTrackPanel->Update(); // force any repaint to happen now,
3166  // else any asynch calls into the blockfile code will not have
3167  // finished logging errors (if any) before the call to ProjectFSCK()
3168 
3169  if (addtohistory) {
3170  wxGetApp().AddFileToHistory(fileName);
3171  }
3172  }
3173 
3174  // Use a finally block here, because there are calls to Save() below which
3175  // might throw.
3176  bool closed = false;
3177  auto cleanup = finally( [&] {
3178  //release the flag.
3180 
3181  if (! closed ) {
3182  // Shouldn't need it any more.
3183  mImportXMLTagHandler.reset();
3184 
3185  if ( bParseSuccess ) {
3186  // This is a no-fail:
3187  GetDirManager()->FillBlockfilesCache();
3188  EnqueueODTasks();
3189  }
3190 
3191  // For an unknown reason, OSX requires that the project window be
3192  // raised if a recovery took place.
3193  CallAfter( [this] { Raise(); } );
3194  }
3195  } );
3196 
3197  if (bParseSuccess) {
3198  bool saved = false;
3199 
3200  if (mIsRecovered)
3201  {
3202  // This project has been recovered, so write a NEW auto-save file
3203  // now and then DELETE the old one in the auto-save folder. Note that
3204  // at this point mFileName != fileName, because when opening a
3205  // recovered file mFileName is faked to point to the original file
3206  // which has been recovered, not the one in the auto-save folder.
3207  GetDirManager()->ProjectFSCK(err, true); // Correct problems in auto-recover mode.
3208 
3209  // PushState calls AutoSave(), so no longer need to do so here.
3210  this->PushState(_("Project was recovered"), _("Recover"));
3211 
3212  if (!wxRemoveFile(fileName))
3213  AudacityMessageBox(_("Could not remove old auto save file"),
3214  _("Error"), wxICON_STOP, this);
3215  }
3216  else
3217  {
3218  // This is a regular project, check it and ask user
3219  int status = GetDirManager()->ProjectFSCK(err, false);
3220  if (status & FSCKstatus_CLOSE_REQ)
3221  {
3222  // Vaughan, 2010-08-23: Note this did not do a real close.
3223  // It could cause problems if you get this, say on missing alias files,
3224  // then try to open a project with, e.g., missing blockfiles.
3225  // It then failed in SetProject, saying it cannot find the files,
3226  // then never go through ProjectFSCK to give more info.
3227  // Going through OnClose() may be overkill, but it's safe.
3228  /*
3229  // There was an error in the load/check and the user
3230  // explictly opted to close the project.
3231  mTracks->Clear(true);
3232  mFileName = wxT("");
3233  SetProjectTitle();
3234  mTrackPanel->Refresh(true);
3235  */
3236  closed = true;
3237  SetMenuClose(true);
3238  Close();
3239  return;
3240  }
3241  else if (status & FSCKstatus_CHANGED)
3242  {
3243  // Mark the wave tracks as changed and redraw.
3244  for (auto wt : GetTracks()->Any<WaveTrack>())
3245  // Only wave tracks have a notion of "changed".
3246  for (const auto &clip: wt->GetClips())
3247  clip->MarkChanged();
3248 
3249  mTrackPanel->Refresh(true);
3250 
3251  // Vaughan, 2010-08-20: This was bogus, as all the actions in DirManager::ProjectFSCK
3252  // that return FSCKstatus_CHANGED cannot be undone.
3253  // this->PushState(_("Project checker repaired file"), _("Project Repair"));
3254 
3255  if (status & FSCKstatus_SAVE_AUP)
3256  this->Save(), saved = true;
3257  }
3258  }
3259 
3260  if (mImportXMLTagHandler) {
3261  if (!saved)
3262  // We processed an <import> tag, so save it as a normal project,
3263  // with no <import> tags.
3264  this->Save();
3265  }
3266  }
3267  else {
3268  // Vaughan, 2011-10-30:
3269  // See first topic at http://bugzilla.audacityteam.org/show_bug.cgi?id=451#c16.
3270  // Calling mTracks->Clear() with deleteTracks true results in data loss.
3271 
3272  // PRL 2014-12-19:
3273  // I made many changes for wave track memory management, but only now
3274  // read the above comment. I may have invalidated the fix above (which
3275  // may have spared the files at the expense of leaked memory). But
3276  // here is a better way to accomplish the intent, doing like what happens
3277  // when the project closes:
3278  for ( auto pTrack : mTracks->Any< WaveTrack >() )
3279  pTrack->CloseLock();
3280 
3281  mTracks->Clear(); //mTracks->Clear(true);
3282 
3283  mFileName = wxT("");
3284  SetProjectTitle();
3285 
3286  wxLogError(wxT("Could not parse file \"%s\". \nError: %s"), fileName, xmlFile.GetErrorStr());
3287 
3288  wxString errorStr = xmlFile.GetErrorStr();
3289  wxString url = wxT("FAQ:Errors_on_opening_or_recovering_an_Audacity_project");
3290 
3291  // Certain errors have dedicated help.
3292  // On April-4th-2018, we did not request translation of the XML errors.
3293  // If/when we do, we will need _() around the comparison strings.
3294  if( errorStr.Contains( ("not well-formed (invalid token)") ) )
3295  url = "Error:_not_well-formed_(invalid_token)_at_line_x";
3296  else if( errorStr.Contains(("reference to invalid character number") ))
3297  url = "Error_Opening_Project:_Reference_to_invalid_character_number_at_line_x";
3298  else if( errorStr.Contains(("mismatched tag") ))
3299  url += "#mismatched";
3300 
3301 // These two errors with FAQ entries are reported elsewhere, not here....
3302 //#[[#import-error|Error Importing: Aup is an Audacity Project file. Use the File > Open command]]
3303 //#[[#corrupt|Error Opening File or Project: File may be invalid or corrupted]]
3304 
3305 // If we did want to handle every single parse error, these are they....
3306 /*
3307  XML_L("out of memory"),
3308  XML_L("syntax error"),
3309  XML_L("no element found"),
3310  XML_L("not well-formed (invalid token)"),
3311  XML_L("unclosed token"),
3312  XML_L("partial character"),
3313  XML_L("mismatched tag"),
3314  XML_L("duplicate attribute"),
3315  XML_L("junk after document element"),
3316  XML_L("illegal parameter entity reference"),
3317  XML_L("undefined entity"),
3318  XML_L("recursive entity reference"),
3319  XML_L("asynchronous entity"),
3320  XML_L("reference to invalid character number"),
3321  XML_L("reference to binary entity"),
3322  XML_L("reference to external entity in attribute"),
3323  XML_L("XML or text declaration not at start of entity"),
3324  XML_L("unknown encoding"),
3325  XML_L("encoding specified in XML declaration is incorrect"),
3326  XML_L("unclosed CDATA section"),
3327  XML_L("error in processing external entity reference"),
3328  XML_L("document is not standalone"),
3329  XML_L("unexpected parser state - please send a bug report"),
3330  XML_L("entity declared in parameter entity"),
3331  XML_L("requested feature requires XML_DTD support in Expat"),
3332  XML_L("cannot change setting once parsing has begun"),
3333  XML_L("unbound prefix"),
3334  XML_L("must not undeclare prefix"),
3335  XML_L("incomplete markup in parameter entity"),
3336  XML_L("XML declaration not well-formed"),
3337  XML_L("text declaration not well-formed"),
3338  XML_L("illegal character(s) in public id"),
3339  XML_L("parser suspended"),
3340  XML_L("parser not suspended"),
3341  XML_L("parsing aborted"),
3342  XML_L("parsing finished"),
3343  XML_L("cannot suspend in external parameter entity"),
3344  XML_L("reserved prefix (xml) must not be undeclared or bound to another namespace name"),
3345  XML_L("reserved prefix (xmlns) must not be declared or undeclared"),
3346  XML_L("prefix must not be bound to one of the reserved namespace names")
3347 */
3348 
3350  this,
3351  _("Error Opening Project"),
3352  errorStr,
3353  url);
3354  }
3355 }
3356 
3358 {
3359  //check the ODManager to see if we should add the tracks to the ODManager.
3360  //this flag would have been set in the HandleXML calls from above, if there were
3361  //OD***Blocks.
3363  {
3364  std::vector<std::unique_ptr<ODTask>> newTasks;
3365  //std::vector<ODDecodeTask*> decodeTasks;
3366  unsigned int createdODTasks=0;
3367  for (auto wt : GetTracks()->Any<WaveTrack>()) {
3368  //check the track for blocks that need decoding.
3369  //There may be more than one type e.g. FLAC/FFMPEG/lame
3370  unsigned int odFlags = wt->GetODFlags();
3371 
3372  //add the track to the already created tasks that correspond to the od flags in the wavetrack.
3373  for(unsigned int i=0;i<newTasks.size();i++) {
3374  if(newTasks[i]->GetODType() & odFlags)
3375  newTasks[i]->AddWaveTrack(wt);
3376  }
3377 
3378  //create whatever NEW tasks we need to.
3379  //we want at most one instance of each class for the project
3380  while((odFlags|createdODTasks) != createdODTasks)
3381  {
3382  std::unique_ptr<ODTask> newTask;
3383 #ifdef EXPERIMENTAL_OD_FLAC
3384  if(!(createdODTasks&ODTask::eODFLAC) && (odFlags & ODTask::eODFLAC)) {
3385  newTask = std::make_unique<ODDecodeFlacTask>();
3386  createdODTasks = createdODTasks | ODTask::eODFLAC;
3387  }
3388  else
3389 #endif
3390  if(!(createdODTasks&ODTask::eODPCMSummary) && (odFlags & ODTask::eODPCMSummary)) {
3391  newTask = std::make_unique<ODComputeSummaryTask>();
3392  createdODTasks = createdODTasks | ODTask::eODPCMSummary;
3393  }
3394  else {
3395  wxPrintf("unrecognized OD Flag in block file.\n");
3396  //TODO:ODTODO: display to user. This can happen when we build audacity on a system that doesnt have libFLAC
3397  break;
3398  }
3399  if(newTask)
3400  {
3401  newTask->AddWaveTrack(wt);
3402  newTasks.push_back(std::move(newTask));
3403  }
3404  }
3405  }
3406  for(unsigned int i=0;i<newTasks.size();i++)
3407  ODManager::Instance()->AddNewTask(std::move(newTasks[i]));
3408  }
3409 }
3410 
3411 bool AudacityProject::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
3412 {
3413  bool bFileVersionFound = false;
3414  wxString fileVersion = _("<unrecognized version -- possibly corrupt project file>");
3415  wxString audacityVersion = _("<unrecognized version -- possibly corrupt project file>");
3416  int requiredTags = 0;
3417  long longVpos = 0;
3418 
3419  // loop through attrs, which is a null-terminated list of
3420  // attribute-value pairs
3421  while(*attrs) {
3422  const wxChar *attr = *attrs++;
3423  const wxChar *value = *attrs++;
3424 
3425  if (!value || !XMLValueChecker::IsGoodString(value))
3426  break;
3427 
3428  if (mViewInfo.ReadXMLAttribute(attr, value)) {
3429  // We need to save vpos now and restore it below
3430  longVpos = std::max(longVpos, long(mViewInfo.vpos));
3431  continue;
3432  }
3433 
3434  if (!wxStrcmp(attr, wxT("datadir")))
3435  {
3436  //
3437  // This is an auto-saved version whose data is in another directory
3438  //
3439  // Note: This attribute must currently be written and parsed before
3440  // any other attributes
3441  //
3442  if ((value[0] != 0) && XMLValueChecker::IsGoodPathString(value))
3443  {
3444  // Remember that this is a recovered project
3445  mIsRecovered = true;
3446  mRecoveryAutoSaveDataDir = value;
3447  }
3448  }
3449 
3450  else if (!wxStrcmp(attr, wxT("version")))
3451  {
3452  fileVersion = value;
3453  bFileVersionFound = true;
3454  requiredTags++;
3455  }
3456 
3457  else if (!wxStrcmp(attr, wxT("audacityversion"))) {
3458  audacityVersion = value;
3459  requiredTags++;
3460  }
3461 
3462  else if (!wxStrcmp(attr, wxT("projname"))) {
3463  wxString projName;
3464  wxString projPath;
3465 
3466  if (mIsRecovered) {
3467  // Fake the filename as if we had opened the original file
3468  // (which was lost by the crash) rather than the one in the
3469  // auto save folder
3470  wxFileName realFileDir;
3471  realFileDir.AssignDir(mRecoveryAutoSaveDataDir);
3472  realFileDir.RemoveLastDir();
3473 
3474  wxString realFileName = value;
3475  if (realFileName.Length() >= 5 &&
3476  realFileName.Right(5) == wxT("_data"))
3477  {
3478  realFileName = realFileName.Left(realFileName.Length() - 5);
3479  }
3480 
3481  if (realFileName.IsEmpty())
3482  {
3483  // A previously unsaved project has been recovered, so fake
3484  // an unsaved project. The data files just stay in the temp
3485  // directory
3486  mDirManager->SetLocalTempDir(mRecoveryAutoSaveDataDir);
3487  mFileName = wxT("");
3488  projName = wxT("");
3489  projPath = wxT("");
3490  } else
3491  {
3492  realFileName += wxT(".aup");
3493  projPath = realFileDir.GetFullPath();
3494  mFileName = wxFileName(projPath, realFileName).GetFullPath();
3495  mbLoadedFromAup = true;
3496  projName = value;
3497  }
3498 
3499  SetProjectTitle();
3500  } else {
3501  projName = value;
3502  projPath = wxPathOnly(mFileName);
3503  }
3504 
3505  if (!projName.IsEmpty())
3506  {
3507  // First try to load the data files based on the _data dir given in the .aup file
3508  // If this fails then try to use the filename of the .aup as the base directory
3509  // This is because unzipped projects e.g. those that get transfered between mac-pc
3510  // have encoding issues and end up expanding the wrong filenames for certain
3511  // international characters (such as capital 'A' with an umlaut.)
3512  if (!mDirManager->SetProject(projPath, projName, false))
3513  {
3514  projName = GetName() + wxT("_data");
3515  if (!mDirManager->SetProject(projPath, projName, false)) {
3516  AudacityMessageBox(wxString::Format(_("Couldn't find the project data folder: \"%s\""),
3517  projName),
3518  _("Error Opening Project"),
3519  wxOK | wxCENTRE, this);
3520  return false;
3521  }
3522  }
3523  }
3524 
3525  requiredTags++;
3526  }
3527 
3528  else if (!wxStrcmp(attr, wxT("rate"))) {
3531  }
3532 
3533  else if (!wxStrcmp(attr, wxT("snapto"))) {
3534  SetSnapTo(wxString(value) == wxT("on") ? true : false);
3535  }
3536 
3537  else if (!wxStrcmp(attr, wxT("selectionformat")))
3540 
3541  else if (!wxStrcmp(attr, wxT("frequencyformat")))
3544 
3545  else if (!wxStrcmp(attr, wxT("bandwidthformat")))
3548  } // while
3549 
3551 
3552  if (longVpos != 0) {
3553  // PRL: It seems this must happen after SetSnapTo
3554  mViewInfo.vpos = longVpos;
3555  mbInitializingScrollbar = true;
3556  }
3557 
3558  // Specifically detect newer versions of Audacity
3559  // WARNING: This will need review/revision if we ever have a version string
3560  // such as 1.5.10, i.e. with 2 digit numbers.
3561  // We're able to do a shortcut and use string comparison because we know
3562  // that does not happen.
3563  // TODO: Um. We actually have released 0.98 and 1.3.14 so the comment
3564  // above is inaccurate.
3565 
3566  if (!bFileVersionFound ||
3567  (fileVersion.Length() != 5) || // expecting '1.1.0', for example
3568  // JKC: I commentted out next line. IsGoodInt is not for
3569  // checking dotted numbers.
3571  (fileVersion > wxT(AUDACITY_FILE_FORMAT_VERSION)))
3572  {
3573  wxString msg;
3574  /* i18n-hint: %s will be replaced by the version number.*/
3575  msg.Printf(_("This file was saved using Audacity %s.\nYou are using Audacity %s. You may need to upgrade to a newer version to open this file."),
3576  audacityVersion,
3578  AudacityMessageBox(msg,
3579  _("Can't open project file"),
3580  wxOK | wxICON_EXCLAMATION | wxCENTRE, this);
3581  return false;
3582  }
3583 
3584  // NOTE: It looks as if there was some confusion about fileversion and audacityversion.
3585  // fileversion NOT being increased when file formats changed, so unfortunately we're
3586  // using audacityversion to rescue the situation.
3587 
3588  // KLUDGE: guess the true 'fileversion' by stripping away any '-beta-Rc' stuff on
3589  // audacityVersion.
3590  // It's fairly safe to do this as it has already been established that the
3591  // puported file version was five chars long.
3592  fileVersion = audacityVersion.Mid(0,5);
3593 
3594  bool bIsOld = fileVersion < wxT(AUDACITY_FILE_FORMAT_VERSION);
3595  bool bIsVeryOld = fileVersion < wxT("1.1.9" );
3596  // Very old file versions could even have the file version starting
3597  // with text: 'AudacityProject Version 0.95'
3598  // Atoi return zero in this case.
3599  bIsVeryOld |= wxAtoi( fileVersion )==0;
3600  // Specifically detect older versions of Audacity
3601  if ( bIsOld | bIsVeryOld ) {
3602  wxString msg;
3603  msg.Printf(gsLegacyFileWarning(), audacityVersion);
3604 
3605  int icon_choice = wxICON_EXCLAMATION;
3606  if( bIsVeryOld )
3607  // Stop icon, and choose 'NO' by default.
3608  icon_choice = wxICON_STOP | wxNO_DEFAULT;
3609  int action =
3610  AudacityMessageBox(msg,
3611  _("Warning - Opening Old Project File"),
3612  wxYES_NO | icon_choice | wxCENTRE,
3613  this);
3614  if (action == wxNO)
3615  return false;
3616  }
3617 
3618  if (wxStrcmp(tag, wxT("audacityproject")) &&
3619  wxStrcmp(tag, wxT("project"))) {
3620  // If the tag name is not one of these two (the NEW name is
3621  // "project" with an Audacity namespace, but we don't detect
3622  // the namespace yet), then we don't know what the error is
3623  return false;
3624  }
3625 
3626  if (requiredTags < 3)
3627  return false;
3628 
3629  // All other tests passed, so we succeed
3630  return true;
3631 }
3632 
3634 {
3635  if (!wxStrcmp(tag, wxT("tags"))) {
3636  return mTags.get();
3637  }
3638 
3639  // Note that TrackList::Add includes assignment of unique in-session TrackId
3640  // to a reloaded track, though no promise that it equals the id it originally
3641  // had
3642 
3643  if (!wxStrcmp(tag, wxT("wavetrack"))) {
3644  return mTracks->Add(mTrackFactory->NewWaveTrack());
3645  }
3646 
3647  #ifdef USE_MIDI
3648  if (!wxStrcmp(tag, wxT("notetrack"))) {
3649  return mTracks->Add(mTrackFactory->NewNoteTrack());
3650  }
3651  #endif // USE_MIDI
3652 
3653  if (!wxStrcmp(tag, wxT("labeltrack"))) {
3654  return mTracks->Add(mTrackFactory->NewLabelTrack());
3655  }
3656 
3657  if (!wxStrcmp(tag, wxT("timetrack"))) {
3658  return mTracks->Add(mTrackFactory->NewTimeTrack());
3659  }
3660 
3661  if (!wxStrcmp(tag, wxT("recordingrecovery"))) {
3663  mRecordingRecoveryHandler = std::make_unique<RecordingRecoveryHandler>(this);
3664  return mRecordingRecoveryHandler.get();
3665  }
3666 
3667  if (!wxStrcmp(tag, wxT("import"))) {
3668  if (!mImportXMLTagHandler)
3669  mImportXMLTagHandler = std::make_unique<ImportXMLTagHandler>(this);
3670  return mImportXMLTagHandler.get();
3671  }
3672 
3673  return NULL;
3674 }
3675 
3677 {
3678  xmlFile.Write(wxT("<?xml "));
3679  xmlFile.Write(wxT("version=\"1.0\" "));
3680  xmlFile.Write(wxT("standalone=\"no\" "));
3681  xmlFile.Write(wxT("?>\n"));
3682 
3683  xmlFile.Write(wxT("<!DOCTYPE "));
3684  xmlFile.Write(wxT("project "));
3685  xmlFile.Write(wxT("PUBLIC "));
3686  xmlFile.Write(wxT("\"-//audacityproject-1.3.0//DTD//EN\" "));
3687  xmlFile.Write(wxT("\"http://audacity.sourceforge.net/xml/audacityproject-1.3.0.dtd\" "));
3688  xmlFile.Write(wxT(">\n"));
3689 }
3690 
3691 void AudacityProject::WriteXML(XMLWriter &xmlFile, bool bWantSaveCopy)
3692 // may throw
3693 {
3694  //TIMER_START( "AudacityProject::WriteXML", xml_writer_timer );
3695  // Warning: This block of code is duplicated in Save, for now...
3696  wxString project = mFileName;
3697  if (project.Len() > 4 && project.Mid(project.Len() - 4) == wxT(".aup"))
3698  project = project.Mid(0, project.Len() - 4);
3699  wxString projName = wxFileNameFromPath(project) + wxT("_data");
3700  // End Warning -DMM
3701 
3702  xmlFile.StartTag(wxT("project"));
3703  xmlFile.WriteAttr(wxT("xmlns"), wxT("http://audacity.sourceforge.net/xml/"));
3704 
3705  if (mAutoSaving)
3706  {
3707  //
3708  // When auto-saving, remember full path to data files directory
3709  //
3710  // Note: This attribute must currently be written and parsed before
3711  // all other attributes
3712  //
3713  xmlFile.WriteAttr(wxT("datadir"), mDirManager->GetDataFilesDir());
3714 
3715  // Note that the code at the start assumes that if mFileName has a value
3716  // then the file has been saved. This is not neccessarily true when
3717  // autosaving as it gets set by AddImportedTracks (presumably as a proposal).
3718  // I don't think that mDirManager.projName gets set without a save so check that.
3719  if( !IsProjectSaved() )
3720  projName = wxT("_data");
3721  }
3722 
3723  xmlFile.WriteAttr(wxT("projname"), projName);
3724  xmlFile.WriteAttr(wxT("version"), wxT(AUDACITY_FILE_FORMAT_VERSION));
3725  xmlFile.WriteAttr(wxT("audacityversion"), AUDACITY_VERSION_STRING);
3726 
3727  mViewInfo.WriteXMLAttributes(xmlFile);
3728  xmlFile.WriteAttr(wxT("rate"), mRate);
3729  xmlFile.WriteAttr(wxT("snapto"), GetSnapTo() ? wxT("on") : wxT("off"));
3730  xmlFile.WriteAttr(wxT("selectionformat"),
3731  GetSelectionFormat().Internal());
3732  xmlFile.WriteAttr(wxT("frequencyformat"),
3733  GetFrequencySelectionFormatName().Internal());
3734  xmlFile.WriteAttr(wxT("bandwidthformat"),
3735  GetBandwidthSelectionFormatName().Internal());
3736 
3737  mTags->WriteXML(xmlFile);
3738 
3739  unsigned int ndx = 0;
3740  GetTracks()->Any().Visit(
3741  [&](WaveTrack *pWaveTrack) {
3742  if (bWantSaveCopy) {
3743  if (!pWaveTrack->IsLeader())
3744  return;
3745 
3746  //vvv This should probably be a method, WaveTrack::WriteCompressedTrackXML().
3747  xmlFile.StartTag(wxT("import"));
3748  xmlFile.WriteAttr(wxT("filename"), mStrOtherNamesArray[ndx]); // Assumes mTracks order hasn't changed!
3749 
3750  // Don't store "channel" and "linked" tags because the importer can figure that out,
3751  // e.g., from stereo Ogg files.
3752  // xmlFile.WriteAttr(wxT("channel"), t->GetChannel());
3753  // xmlFile.WriteAttr(wxT("linked"), t->GetLinked());
3754 
3755  const auto offset =
3756  TrackList::Channels( pWaveTrack ).min( &WaveTrack::GetOffset );
3757  xmlFile.WriteAttr(wxT("offset"), offset, 8);
3758  xmlFile.WriteAttr(wxT("mute"), pWaveTrack->GetMute());
3759  xmlFile.WriteAttr(wxT("solo"), pWaveTrack->GetSolo());
3760  xmlFile.WriteAttr(wxT("height"), pWaveTrack->GetActualHeight());
3761  xmlFile.WriteAttr(wxT("minimized"), pWaveTrack->GetMinimized());
3762 
3763  // Don't store "rate" tag because the importer can figure that out.
3764  // xmlFile.WriteAttr(wxT("rate"), pWaveTrack->GetRate());
3765  xmlFile.WriteAttr(wxT("gain"), (double)pWaveTrack->GetGain());
3766  xmlFile.WriteAttr(wxT("pan"), (double)pWaveTrack->GetPan());
3767  xmlFile.EndTag(wxT("import"));
3768 
3769  ndx++;
3770  }
3771  else {
3772  pWaveTrack->SetAutoSaveIdent(mAutoSaving ? ++ndx : 0);
3773  pWaveTrack->WriteXML(xmlFile);
3774  }
3775  },
3776  [&](Track *t) {
3777  t->WriteXML(xmlFile);
3778  }
3779  );
3780 
3781  if (!mAutoSaving)
3782  {
3783  // Only write closing bracket when not auto-saving, since we may add
3784  // recording log data to the end of the file later
3785  xmlFile.EndTag(wxT("project"));
3786  }
3787  //TIMER_STOP( xml_writer_timer );
3788 
3789 }
3790 
3791 #if 0
3792 // I added this to "fix" bug #334. At that time, we were on wxWidgets 2.8.12 and
3793 // there was a window between the closing of the "Save" progress dialog and the
3794 // end of the actual save where the user was able to close the project window and
3795 // recursively enter the Save code (where they could inadvertently cause the issue
3796 // described in #334).
3797 //
3798 // When we converted to wx3, this "disabler" caused focus problems when returning
3799 // to the project after the save (bug #1172) because the focus and activate events
3800 // weren't being dispatched and the focus would get lost.
3801 //
3802 // After some testing, it looks like the window described above no longer exists,
3803 // so I've disabled the disabler. However, I'm leaving it here in case we run
3804 // into the problem in the future. (even though it can't be used as-is)
3805 class ProjectDisabler
3806 {
3807 public:
3808  ProjectDisabler(wxWindow *w)
3809  : mWindow(w)
3810  {
3811  mWindow->GetEventHandler()->SetEvtHandlerEnabled(false);
3812  }
3813  ~ProjectDisabler()
3814  {
3815  mWindow->GetEventHandler()->SetEvtHandlerEnabled(true);
3816  }
3817 private:
3818  wxWindow *mWindow;
3819 };
3820 #endif
3821 
3823 {
3824  // Prompt for file name?
3825  bool bPromptingRequired = !IsProjectSaved();
3826 
3827  if (bPromptingRequired)
3828  return SaveAs();
3829 
3830  return DoSave(false, false);
3831 }
3832 
3833 
3834 // Assumes AudacityProject::mFileName has been set to the desired path.
3835 bool AudacityProject::DoSave (const bool fromSaveAs,
3836  const bool bWantSaveCopy,
3837  const bool bLossless /*= false*/)
3838 {
3839  // See explanation above
3840  // ProjectDisabler disabler(this);
3841 
3842  wxASSERT_MSG(!bWantSaveCopy || fromSaveAs, "Copy Project SHOULD only be availabele from SaveAs");
3843 
3844  // Some confirmation dialogs
3845  if (!bWantSaveCopy)
3846  {
3847  if ( ! GetTracks()->Any() )
3848  {
3849  if (GetUndoManager()->UnsavedChanges() && mEmptyCanBeDirty) {
3850  int result = AudacityMessageBox(_("Your project is now empty.\nIf saved, the project will have no tracks.\n\nTo save any previously open tracks:\nClick 'No', Edit > Undo until all tracks\nare open, then File > Save Project.\n\nSave anyway?"),
3851  _("Warning - Empty Project"),
3852  wxYES_NO | wxICON_QUESTION, this);
3853  if (result == wxNO)
3854  return false;
3855  }
3856  }
3857 
3858  // If the user has recently imported dependencies, show
3859  // a dialog where the user can see audio files that are
3860  // aliased by this project. The user may make the project
3861  // self-contained during this dialog, it modifies the project!
3863  {
3864  bool bSuccess = ShowDependencyDialogIfNeeded(this, true);
3865  if (!bSuccess)
3866  return false;
3867  mImportedDependencies = false; // do not show again
3868  }
3869  }
3870  // End of confirmations
3871 
3872  //
3873  // Always save a backup of the original project file
3874  //
3875 
3876  wxString safetyFileName = wxT("");
3877  if (wxFileExists(mFileName)) {
3878 
3879 #ifdef __WXGTK__
3880  safetyFileName = mFileName + wxT("~");
3881 #else
3882  safetyFileName = mFileName + wxT(".bak");
3883 #endif
3884 
3885  if (wxFileExists(safetyFileName))
3886  wxRemoveFile(safetyFileName);
3887 
3888  if ( !wxRenameFile(mFileName, safetyFileName) ) {
3890  wxString::Format(
3891  _("Could not create safety file: %s"), safetyFileName ),
3892  _("Error"), wxICON_STOP, this);
3893  return false;
3894  }
3895  }
3896 
3897  bool success = true;
3898  wxString project, projName, projPath;
3899 
3900  auto cleanup = finally( [&] {
3901  if (safetyFileName != wxT("")) {
3902  if (wxFileExists(mFileName))
3903  wxRemove(mFileName);
3904  wxRename(safetyFileName, mFileName);
3905  }
3906 
3907  // mStrOtherNamesArray is a temporary array of file names, used only when
3908  // saving compressed
3909  if (!success) {
3910  AudacityMessageBox(wxString::Format(_("Could not save project. Perhaps %s \nis not writable or the disk is full."),
3911  project),
3912  _("Error Saving Project"),
3913  wxICON_ERROR, this);
3914 
3915  // Make the export of tracks succeed all-or-none.
3916  auto dir = project + wxT("_data");
3917  for ( auto &name : mStrOtherNamesArray )
3918  wxRemoveFile( dir + wxFileName::GetPathSeparator() + name);
3919  // This has effect only if the folder is empty
3920  wxFileName::Rmdir( dir );
3921  }
3922  // Success or no, we can forget the names
3923  mStrOtherNamesArray.clear();
3924  } );
3925 
3926  if (fromSaveAs) {
3927  // This block of code is duplicated in WriteXML, for now...
3928  project = mFileName;
3929  if (project.Len() > 4 && project.Mid(project.Len() - 4) == wxT(".aup"))
3930  project = project.Mid(0, project.Len() - 4);
3931  projName = wxFileNameFromPath(project) + wxT("_data");
3932  projPath = wxPathOnly(project);
3933 
3934  if( !wxDir::Exists( projPath ) ){
3935  AudacityMessageBox(wxString::Format(
3936  _("Could not save project. Path not found. Try creating \ndirectory \"%s\" before saving project with this name."),
3937  projPath),
3938  _("Error Saving Project"),
3939  wxICON_ERROR, this);
3940  return (success = false);
3941  }
3942 
3943  if (bWantSaveCopy)
3944  {
3945  // Do this before saving the .aup, because we accumulate
3946  // mStrOtherNamesArray which affects the contents of the .aup
3947 
3948  // This populates the array mStrOtherNamesArray
3949  success = this->SaveCopyWaveTracks(project, bLossless);
3950  }
3951 
3952  if (!success)
3953  return false;
3954  }
3955 
3956  // Write the .aup now, before DirManager::SetProject,
3957  // because it's easier to clean up the effects of successful write of .aup
3958  // followed by failed SetProject, than the other way about.
3959  // And that cleanup is done by the destructor of saveFile, if PostCommit() is
3960  // not done.
3961  // (SetProject, when it fails, cleans itself up.)
3962  XMLFileWriter saveFile{ mFileName, _("Error Saving Project") };
3963  success = GuardedCall< bool >( [&] {
3964  WriteXMLHeader(saveFile);
3965  WriteXML(saveFile, bWantSaveCopy);
3966  // Flushes files, forcing space exhaustion errors before trying
3967  // SetProject():
3968  saveFile.PreCommit();
3969  return true;
3970  },
3971  MakeSimpleGuard(false),
3972  // Suppress the usual error dialog for failed write,
3973  // which is redundant here:
3974  [](void*){}
3975  );
3976 
3977  if (!success)
3978  return false;
3979 
3981 
3982  if (fromSaveAs && !bWantSaveCopy) {
3983  // We are about to move files from the current directory to
3984  // the NEW directory. We need to make sure files that belonged
3985  // to the last saved project don't get erased, so we "lock" them, so that
3986  // ProjectSetter's constructor copies instead of moves the files.
3987  // (Otherwise the NEW project would be fine, but the old one would
3988  // be empty of all of its files.)
3989 
3990  std::vector<std::unique_ptr<WaveTrack::Locker>> lockers;
3991  if (mLastSavedTracks) {
3992  lockers.reserve(mLastSavedTracks->size());
3993  for (auto wt : mLastSavedTracks->Any<WaveTrack>())
3994  lockers.push_back(
3995  std::make_unique<WaveTrack::Locker>(wt));
3996  }
3997 
3998  // This renames the project directory, and moves or copies
3999  // all of our block files over.
4000  pSetter.create( *mDirManager, projPath, projName, true );
4001 
4002  if (!pSetter->Ok()){
4003  success = false;
4004  return false;
4005  }
4006  }
4007 
4008  // Commit the writing of the .aup only now, after we know that the _data
4009  // folder also saved with no problems.
4010  // It is very unlikely that errors will happen:
4011  // only renaming and removing of files, not writes that might exhaust space.
4012  // So DO give a second dialog in case the unusual happens.
4013  success = success && GuardedCall< bool >( [&] {
4014  saveFile.PostCommit();
4015  return true;
4016  } );
4017 
4018  if (!success)
4019  return false;
4020 
4021  // SAVE HAS SUCCEEDED -- following are further no-fail commit operations.
4022 
4023  if (pSetter)
4024  pSetter->Commit();
4025 
4026  if ( !bWantSaveCopy )
4027  {
4028  // Now that we have saved the file, we can DELETE the auto-saved version
4030 
4031  if (mIsRecovered)
4032  {
4033  // This was a recovered file, that is, we have just overwritten the
4034  // old, crashed .aup file. There may still be orphaned blockfiles in
4035  // this directory left over from the crash, so we DELETE them now
4036  mDirManager->RemoveOrphanBlockfiles();
4037 
4038  // Before we saved this, this was a recovered project, but now it is
4039  // a regular project, so remember this.
4040  mIsRecovered = false;
4041  mRecoveryAutoSaveDataDir = wxT("");
4042  SetProjectTitle();
4043  }
4044  else if (fromSaveAs)
4045  {
4046  // On save as, always remove orphaned blockfiles that may be left over
4047  // because the user is trying to overwrite another project
4048  mDirManager->RemoveOrphanBlockfiles();
4049  }
4050 
4051  if (mLastSavedTracks)
4052  mLastSavedTracks->Clear();
4054 
4055  for (auto t : GetTracks()->Any()) {
4056  mLastSavedTracks->Add(t->Duplicate());
4057 
4058  //only after the xml has been saved we can mark it saved.
4059  //thus is because the OD blockfiles change on background thread while this is going on.
4060  // if(const auto wt = track_cast<WaveTrack*>(dupT))
4061  // wt->MarkSaved();
4062  }
4063 
4065  }
4066 
4067  // If we get here, saving the project was successful, so we can DELETE
4068  // the .bak file (because it now does not fit our block files anymore
4069  // anyway).
4070  if (safetyFileName != wxT(""))
4071  wxRemoveFile(safetyFileName),
4072  // cancel the cleanup:
4073  safetyFileName = wxT("");
4074 
4075  mStatusBar->SetStatusText(wxString::Format(_("Saved %s"),
4077 
4078  return true;
4079 }
4080 
4081 
4082 bool AudacityProject::SaveCopyWaveTracks(const wxString & strProjectPathName,
4083  const bool bLossless /*= false*/)
4084 {
4085  wxString extension, fileFormat;
4086 #ifdef USE_LIBVORBIS
4087  if (bLossless) {
4088  extension = wxT("wav");
4089  fileFormat = wxT("WAVFLT");
4090  } else {
4091  extension = wxT("ogg");
4092  fileFormat = wxT("OGG");
4093  }
4094 #else
4095  extension = wxT("wav");
4096  fileFormat = wxT("WAVFLT");
4097 #endif
4098  // Some of this is similar to code in ExportMultiple::ExportMultipleByTrack
4099  // but that code is really tied into the dialogs.
4100 
4101  // Copy the tracks because we're going to do some state changes before exporting.
4102  unsigned int numWaveTracks = 0;
4103 
4104  auto ppSavedTrackList = TrackList::Create();
4105  auto &pSavedTrackList = *ppSavedTrackList;
4106 
4107  auto trackRange = GetTracks()->Any<WaveTrack>();
4108  for (auto pWaveTrack : trackRange)
4109  {
4110  numWaveTracks++;
4111  pSavedTrackList.Add(mTrackFactory->DuplicateWaveTrack(*pWaveTrack));
4112  }
4113  auto cleanup = finally( [&] {
4114  // Restore the saved track states and clean up.
4115  auto savedTrackRange = pSavedTrackList.Any<const WaveTrack>();
4116  auto ppSavedTrack = savedTrackRange.begin();
4117  for (auto ppTrack = trackRange.begin();
4118 
4119  *ppTrack && *ppSavedTrack;
4120 
4121  ++ppTrack, ++ppSavedTrack)
4122  {
4123  auto pWaveTrack = *ppTrack;
4124  auto pSavedWaveTrack = *ppSavedTrack;
4125  pWaveTrack->SetSelected(pSavedWaveTrack->GetSelected());
4126  pWaveTrack->SetMute(pSavedWaveTrack->GetMute());
4127  pWaveTrack->SetSolo(pSavedWaveTrack->GetSolo());
4128 
4129  pWaveTrack->SetGain(pSavedWaveTrack->GetGain());
4130  pWaveTrack->SetPan(pSavedWaveTrack->GetPan());
4131  }
4132  } );
4133 
4134  if (numWaveTracks == 0)
4135  // Nothing to save compressed => success. Delete the copies and go.
4136  return true;
4137 
4138  // Okay, now some bold state-faking to default values.
4139  for (auto pWaveTrack : trackRange)
4140  {
4141  pWaveTrack->SetSelected(false);
4142  pWaveTrack->SetMute(false);
4143  pWaveTrack->SetSolo(false);
4144 
4145  pWaveTrack->SetGain(1.0);
4146  pWaveTrack->SetPan(0.0);
4147  }
4148 
4149  wxString strDataDirPathName = strProjectPathName + wxT("_data");
4150  if (!wxFileName::DirExists(strDataDirPathName) &&
4151  !wxFileName::Mkdir(strDataDirPathName, 0777, wxPATH_MKDIR_FULL))
4152  return false;
4153  strDataDirPathName += wxFileName::GetPathSeparator();
4154 
4155  // Export all WaveTracks to OGG.
4156  bool bSuccess = true;
4157 
4158  // This accumulates the names of the track files, to be written as
4159  // dependencies in the .aup file
4160  mStrOtherNamesArray.clear();
4161 
4162  Exporter theExporter;
4163  wxFileName uniqueTrackFileName;
4164  for (auto pTrack : (trackRange + &Track::IsLeader))
4165  {
4167  auto channels = TrackList::Channels(pTrack);
4168 
4169  for (auto channel : channels)
4170  channel->SetSelected(true);
4171  uniqueTrackFileName = wxFileName(strDataDirPathName, pTrack->GetName(), extension);
4172  FileNames::MakeNameUnique(mStrOtherNamesArray, uniqueTrackFileName);
4173  const auto startTime = channels.min( &Track::GetStartTime );
4174  const auto endTime = channels.max( &Track::GetEndTime );
4175  bSuccess =
4176  theExporter.Process(this, channels.size(),
4177  fileFormat, uniqueTrackFileName.GetFullPath(), true,
4178  startTime, endTime);
4179 
4180  if (!bSuccess)
4181  // If only some exports succeed, the cleanup is not done here
4182  // but trusted to the caller
4183  break;
4184  }
4185 
4186  return bSuccess;
4187 }
4188 
4189 
4190 std::vector< std::shared_ptr< Track > >
4191 AudacityProject::AddImportedTracks(const wxString &fileName,
4192  TrackHolders &&newTracks)
4193 {
4194  std::vector< std::shared_ptr< Track > > results;
4195 
4196  SelectNone();
4197 
4198  bool initiallyEmpty = mTracks->empty();
4199  double newRate = 0;
4200  wxString trackNameBase = fileName.AfterLast(wxFILE_SEP_PATH).BeforeLast('.');
4201  int i = -1;
4202 
4203  // Must add all tracks first (before using Track::IsLeader)
4204  for (auto &group : newTracks) {
4205  if (group.empty()) {
4206  wxASSERT(false);
4207  continue;
4208  }
4209  auto first = group.begin()->get();
4210  auto nChannels = group.size();
4211  for (auto &uNewTrack : group) {
4212  auto newTrack = mTracks->Add(std::move(uNewTrack));
4213  results.push_back(Track::Pointer(newTrack));
4214  }
4215  mTracks->GroupChannels(*first, nChannels);
4216  }
4217  newTracks.clear();
4218 
4219  // Now name them
4220 
4221  // Add numbers to track names only if there is more than one (mono or stereo)
4222  // track (not necessarily, more than one channel)
4223  const bool useSuffix =
4224  make_iterator_range( results.begin() + 1, results.end() )
4225  .any_of( []( decltype(*results.begin()) &pTrack )
4226  { return pTrack->IsLeader(); } );
4227 
4228  for (const auto &newTrack : results) {
4229  if ( newTrack->IsLeader() )
4230  // Count groups only
4231  ++i;
4232 
4233  newTrack->SetSelected(true);
4234 
4235  if ( useSuffix )
4236  newTrack->SetName(trackNameBase + wxString::Format(wxT(" %d" ), i + 1));
4237  else
4238  newTrack->SetName(trackNameBase);
4239 
4240  newTrack->TypeSwitch( [&](WaveTrack *wt) {
4241  if (newRate == 0)
4242  newRate = wt->GetRate();
4243 
4244  // Check if NEW track contains aliased blockfiles and if yes,
4245  // remember this to show a warning later
4246  if(WaveClip* clip = wt->GetClipByIndex(0)) {
4247  BlockArray &blocks = clip->GetSequence()->GetBlockArray();
4248  if (blocks.size())
4249  {
4250  SeqBlock& block = blocks[0];
4251  if (block.f->IsAlias())
4252  {
4253  mImportedDependencies = true;
4254  }
4255  }
4256  }
4257  });
4258  }
4259 
4260  // Automatically assign rate of imported file to whole project,
4261  // if this is the first file that is imported
4262  if (initiallyEmpty && newRate > 0) {
4263  mRate = newRate;
4265  }
4266 
4267  PushState(wxString::Format(_("Imported '%s'"), fileName),
4268  _("Import"));
4269 
4270 #if defined(__WXGTK__)
4271  // See bug #1224
4272  // The track panel hasn't we been fully created, so the DoZoomFit() will not give
4273  // expected results due to a window width of zero. Should be safe to yield here to
4274  // allow the creattion to complete. If this becomes a problem, it "might" be possible
4275  // to queue a dummy event to trigger the DoZoomFit().
4276  wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
4277 #endif
4278 
4279  if (initiallyEmpty && !IsProjectSaved() ) {
4280  wxString name = fileName.AfterLast(wxFILE_SEP_PATH).BeforeLast(wxT('.'));
4281  mFileName =::wxPathOnly(fileName) + wxFILE_SEP_PATH + name + wxT(".aup");
4282  mbLoadedFromAup = false;
4283  SetProjectTitle();
4284  }
4285 
4286  // Moved this call to higher levels to prevent flicker redrawing everything on each file.
4287  // HandleResize();
4288 
4289  return results;
4290 }
4291 
4293 {
4294  ViewActions::DoZoomFit(*this);
4295 
4296  mTrackPanel->SetFocus();
4297  RedrawProject();
4298  if (!pTrack)
4299  pTrack = mTrackPanel->GetFirstSelectedTrack();
4300  mTrackPanel->EnsureVisible(pTrack);
4301 }
4302 
4303 // If pNewTrackList is passed in non-NULL, it gets filled with the pointers to NEW tracks.
4304 bool AudacityProject::Import(const wxString &fileName, WaveTrackArray* pTrackArray /*= NULL*/)
4305 {
4306  TrackHolders newTracks;
4307  wxString errorMessage = wxEmptyString;
4308 
4309  {
4310  // Backup Tags, before the import. Be prepared to roll back changes.
4311  auto cleanup = valueRestorer( mTags,
4312  mTags ? mTags->Duplicate() : decltype(mTags){} );
4313 
4314  bool success = Importer::Get().Import(fileName,
4315  GetTrackFactory(),
4316  newTracks,
4317  mTags.get(),
4318  errorMessage);
4319 
4320  if (!errorMessage.IsEmpty()) {
4321  // Error message derived from Importer::Import
4322  // Additional help via a Help button links to the manual.
4323  ShowErrorDialog(this, _("Error Importing"),
4324  errorMessage, wxT("Importing_Audio"));
4325  }
4326  if (!success)
4327  return false;
4328 
4329  wxGetApp().AddFileToHistory(fileName);
4330 
4331  // no more errors, commit
4332  cleanup.release();
4333  }
4334 
4335  // for LOF ("list of files") files, do not import the file as if it
4336  // were an audio file itself
4337  if (fileName.AfterLast('.').IsSameAs(wxT("lof"), false)) {
4338  // PRL: don't redundantly do the steps below, because we already
4339  // did it in case of LOF, because of some weird recursion back to this
4340  // same function. I think this should be untangled.
4341 
4342  // So Undo history push is not bypassed, despite appearances.
4343  return false;
4344  }
4345 
4346  // PRL: Undo history is incremented inside this:
4347  auto newSharedTracks = AddImportedTracks(fileName, std::move(newTracks));
4348 
4349  if (pTrackArray) {
4350  for (const auto &newTrack : newSharedTracks) {
4351  newTrack->TypeSwitch( [&](WaveTrack *wt) {
4352  pTrackArray->push_back( Track::Pointer< WaveTrack >( wt ) );
4353  });
4354  }
4355  }
4356 
4357  int mode = gPrefs->Read(wxT("/AudioFiles/NormalizeOnLoad"), 0L);
4358  if (mode == 1) {
4359  //TODO: All we want is a SelectAll()
4360  SelectNone();
4361  SelectAllIfNone();
4362  const CommandContext context( *this);
4364  EffectManager::Get().GetEffectByIdentifier(wxT("Normalize")),
4365  context,
4367  }
4368 
4369  // This is a no-fail:
4370  GetDirManager()->FillBlockfilesCache();
4371  return true;
4372 }
4373 
4374 bool AudacityProject::SaveAs(const wxString & newFileName, bool bWantSaveCopy /*= false*/, bool addToHistory /*= true*/)
4375 {
4376  // This version of SaveAs is invoked only from scripting and does not
4377  // prompt for a file name
4378  wxString oldFileName = mFileName;
4379 
4380  bool bOwnsNewAupName = mbLoadedFromAup && (mFileName==newFileName);
4381  //check to see if the NEW project file already exists.
4382  //We should only overwrite it if this project already has the same name, where the user
4383  //simply chose to use the save as command although the save command would have the effect.
4384  if( !bOwnsNewAupName && wxFileExists(newFileName)) {
4386  NULL,
4387  _("The project was not saved because the file name provided would overwrite another project.\nPlease try again and select an original name."),
4388  _("Error Saving Project"),
4389  wxOK|wxICON_ERROR);
4390  m.ShowModal();
4391  return false;
4392  }
4393 
4394  mFileName = newFileName;
4395  bool success = false;
4396  auto cleanup = finally( [&] {
4397  if (!success || bWantSaveCopy)
4398  // Restore file name on error
4399  mFileName = oldFileName;
4400  } );
4401 
4402  //Don't change the title, unless we succeed.
4403  //SetProjectTitle();
4404 
4405  success = DoSave(!bOwnsNewAupName || bWantSaveCopy, bWantSaveCopy);
4406 
4407  if (success && addToHistory) {
4408  wxGetApp().AddFileToHistory(mFileName);
4409  }
4410  if (!success || bWantSaveCopy) // bWantSaveCopy doesn't actually change current project.
4411  {
4412  }
4413  else {
4414  mbLoadedFromAup = true;
4415  SetProjectTitle();
4416  }
4417 
4418  return(success);
4419 }
4420 
4421 
4422 bool AudacityProject::SaveAs(bool bWantSaveCopy /*= false*/, bool bLossless /*= false*/)
4423 {
4424  TitleRestorer Restorer(this); // RAII
4425  bool bHasPath = true;
4426  wxFileName filename(mFileName);
4427  // Save a copy of the project with 32-bit float tracks.
4428  if (bLossless)
4429  bWantSaveCopy = true;
4430 
4431  // Bug 1304: Set a default file path if none was given. For Save/SaveAs
4432  if( filename.GetFullPath().IsEmpty() ){
4433  bHasPath = false;
4434  filename = FileNames::DefaultToDocumentsFolder(wxT("/SaveAs/Path"));
4435  }
4436 
4437  wxString title;
4438  wxString message;
4439  if (bWantSaveCopy)
4440  {
4441  if (bLossless)
4442  {
4443  title = wxString::Format(_("%sSave Lossless Copy of Project \"%s\" As..."),
4444  Restorer.sProjNumber,Restorer.sProjName);
4445  message = _("\
4446 'Save Lossless Copy of Project' is for an Audacity project, not an audio file.\n\
4447 For an audio file that will open in other apps, use 'Export'.\n\n\
4448 \
4449 Lossless copies of project are a good way to backup your project, \n\
4450 with no loss of quality, but the projects are large.\n");
4451  }
4452  else
4453  {
4454  title = wxString::Format(_("%sSave Compressed Copy of Project \"%s\" As..."),
4455  Restorer.sProjNumber,Restorer.sProjName);
4456  message = _("\
4457 'Save Compressed Copy of Project' is for an Audacity project, not an audio file.\n\
4458 For an audio file that will open in other apps, use 'Export'.\n\n\
4459 \
4460 Compressed project files are a good way to transmit your project online, \n\
4461 but they have some loss of fidelity.\n");
4462  }
4463  }
4464  else
4465  {
4466  title = wxString::Format(_("%sSave Project \"%s\" As..."),
4467  Restorer.sProjNumber, Restorer.sProjName);
4468  message = _("\
4469 'Save Project' is for an Audacity project, not an audio file.\n\
4470 For an audio file that will open in other apps, use 'Export'.\n");
4471  }
4472  if (ShowWarningDialog(this, wxT("FirstProjectSave"), message, true) != wxID_OK)
4473  {
4474  return false;
4475  }
4476 
4477  bool bPrompt = (mBatchMode == 0) || (mFileName.IsEmpty());
4478  wxString fName = "";
4479 
4480  if (bPrompt) {
4481  // JKC: I removed 'wxFD_OVERWRITE_PROMPT' because we are checking
4482  // for overwrite ourselves later, and we disallow it.
4483  // We disallow overwrite because we would have to DELETE the many
4484  // smaller files too, or prompt to move them.
4486  title,
4487  filename.GetPath(),
4488  filename.GetFullName(),
4489  wxT("aup"),
4490  _("Audacity projects") + wxT(" (*.aup)|*.aup"),
4491  wxFD_SAVE | wxRESIZE_BORDER,
4492  this);
4493 
4494  if (fName == wxT(""))
4495  return false;
4496 
4497  filename = fName;
4498  };
4499 
4500  filename.SetExt(wxT("aup"));
4501  fName = filename.GetFullPath();
4502 
4503  if ((bWantSaveCopy||!bPrompt) && filename.FileExists()) {
4504  // Saving a copy of the project should never overwrite an existing project.
4506  NULL,
4507  _("Saving a copy must not overwrite an existing saved project.\nPlease try again and select an original name."),
4508  _("Error Saving Copy of Project"),
4509  wxOK|wxICON_ERROR);
4510  m.ShowModal();
4511  return false;
4512  }
4513 
4514  bool bOwnsNewAupName = mbLoadedFromAup && (mFileName==fName);
4515  // Check to see if the project file already exists, and if it does
4516  // check that the project file 'belongs' to this project.
4517  // otherwise, prompt the user before overwriting.
4518  if (!bOwnsNewAupName && filename.FileExists()) {
4519  // Ensure that project of same name is not open in another window.
4520  // fName is the destination file.
4521  // mFileName is this project.
4522  // It is possible for mFileName == fName even when this project is not
4523  // saved to disk, and we then need to check the destination file is not
4524  // open in another window.
4525  int mayOverwrite = (mFileName == fName)? 2 : 1;
4526  for (auto p : gAudacityProjects) {
4527  const wxFileName openProjectName(p->mFileName);
4528  if (openProjectName.SameAs(fName)) {
4529  mayOverwrite -= 1;
4530  if (mayOverwrite == 0)
4531  break;
4532  }
4533  }
4534 
4535  if (mayOverwrite > 0) {
4536  /* i18n-hint: In each case, %s is the name
4537  of the file being overwritten.*/
4538  wxString Message = wxString::Format(_("\
4539 Do you want to overwrite the project:\n\"%s\"?\n\n\
4540 If you select \"Yes\" the project\n\"%s\"\n\
4541 will be irreversibly overwritten."), fName, fName);
4542 
4543  // For safety, there should NOT be an option to hide this warning.
4544  int result = AudacityMessageBox(Message,
4545  /* i18n-hint: Heading: A warning that a project is about to be overwritten.*/
4546  _("Overwrite Project Warning"),
4547  wxYES_NO | wxNO_DEFAULT | wxICON_WARNING,
4548  this);
4549  if (result != wxYES) {
4550  return false;
4551  }
4552  }
4553  else
4554  {
4555  // Overwrite disalowed. The destination project is open in another window.
4557  NULL,
4558  _("The project will not saved because the selected project is open in another window.\nPlease try again and select an original name."),
4559  _("Error Saving Project"),
4560  wxOK|wxICON_ERROR);
4561  m.ShowModal();
4562  return false;
4563  }
4564  }
4565 
4566  wxString oldFileName = mFileName;
4567  mFileName = fName;
4568  bool success = false;
4569  auto cleanup = finally( [&] {
4570  if (!success || bWantSaveCopy)
4571  // Restore file name on error
4572  mFileName = oldFileName;
4573  } );
4574 
4575  success = DoSave(!bOwnsNewAupName || bWantSaveCopy, bWantSaveCopy, bLossless);
4576 
4577  if (success) {
4578  wxGetApp().AddFileToHistory(mFileName);
4579  if( !bHasPath )
4580  {
4581  gPrefs->Write( wxT("/SaveAs/Path"), filename.GetPath());
4582  gPrefs->Flush();
4583  }
4584  }
4585  if (!success || bWantSaveCopy) // bWantSaveCopy doesn't actually change current project.
4586  {
4587  }
4588  else {
4589  mbLoadedFromAup = true;
4590  SetProjectTitle();
4591  }
4592 
4593 
4594  return(success);
4595 }
4596 
4597 //
4598 // Undo/History methods
4599 //
4600 
4602 {
4604 
4606  _("Created new project"), wxT(""));
4607 
4609 
4610  GetMenuManager(*this).ModifyUndoMenuItems(*this);
4611 
4612  GetMenuManager(*this).UpdateMenus(*this);
4613 }
4614 
4616 {
4617  TrackList* trackList = GetTracks();
4618  return GetUndoManager()->UndoAvailable() &&
4619  !(trackList != nullptr && trackList->HasPendingTracks());
4620 }
4621 
4623 {
4624  TrackList* trackList = GetTracks();
4625  return GetUndoManager()->RedoAvailable() &&
4626  !(trackList != nullptr && trackList->HasPendingTracks());
4627 }
4628 
4629 void AudacityProject::PushState(const wxString &desc, const wxString &shortDesc)
4630 {
4631  PushState(desc, shortDesc, UndoPush::AUTOSAVE);
4632 }
4633 
4634 void AudacityProject::PushState(const wxString &desc,
4635  const wxString &shortDesc,
4636  UndoPush flags )
4637 {
4639  desc, shortDesc, flags);
4640 
4641  mDirty = true;
4642 
4643  GetMenuManager(*this).ModifyUndoMenuItems(*this);
4644 
4645  GetMenuManager(*this).UpdateMenus(*this);
4646 
4648  ViewActions::DoZoomFitV(*this);
4649  if((flags & UndoPush::AUTOSAVE) != UndoPush::MINIMAL)
4650  AutoSave();
4651 
4653 }
4654 
4656 {
4657  SetStateTo(GetUndoManager()->GetCurrentState());
4658 }
4659 
4660 void AudacityProject::ModifyState(bool bWantsAutoSave)
4661 {
4663  if (bWantsAutoSave)
4664  AutoSave();
4666 }
4667 
4668 // LL: Is there a memory leak here as "l" and "t" are not deleted???
4669 // Vaughan, 2010-08-29: No, as "l" is a TrackList* of an Undo stack state.
4670 // Need to keep it and its tracks "t" available for Undo/Redo/SetStateTo.
4672 {
4674 
4675  // Restore tags
4676  mTags = state.tags;
4677 
4678  TrackList *const tracks = state.tracks.get();
4679 
4680  mTracks->Clear();
4681  bool odUsed = false;
4682  std::unique_ptr<ODComputeSummaryTask> computeTask;
4683 
4684  for (auto t : tracks->Any())
4685  {
4686  auto copyTrack = mTracks->Add(t->Duplicate());
4687 
4688  //add the track to OD if the manager exists. later we might do a more rigorous check...
4689  copyTrack->TypeSwitch( [&](WaveTrack *wt) {
4690  //if the ODManager hasn't been initialized, there's no chance this track has OD blocks since this
4691  //is a "Redo" operation.
4692  //TODO: update this to look like the update loop in OpenFile that handles general purpose ODTasks.
4693  //BUT, it is too slow to go thru every blockfile and check the odtype, so maybe put a flag in wavetrack
4694  //that gets unset on OD Completion, (and we could also update the drawing there too.) The hard part is that
4695  //we would need to watch every possible way a OD Blockfile could get inserted into a wavetrack and change the
4696  //flag there.
4698  {
4699  if(!odUsed)
4700  {
4701  computeTask = std::make_unique<ODComputeSummaryTask>();
4702  odUsed=true;
4703  }
4704  // PRL: Is it correct to add all tracks to one task, even if they
4705  // are not partnered channels? Rather than
4706  // make one task for each?
4707  computeTask->AddWaveTrack(wt);
4708  }
4709  });
4710  }
4711 
4712  //add the task.
4713  if(odUsed)
4714  ODManager::Instance()->AddNewTask(std::move(computeTask));
4715 
4716  HandleResize();
4717 
4718  GetMenuManager(*this).UpdateMenus(*this);
4719 
4720  AutoSave();
4721 }
4722 
4723 void AudacityProject::SetStateTo(unsigned int n)
4724 {
4726  [this]( const UndoState &state ){ PopState(state); } );
4727 
4728  HandleResize();
4730  mTrackPanel->Refresh(false);
4731  GetMenuManager(*this).ModifyUndoMenuItems(*this);
4732 }
4733 
4734 //
4735 // Clipboard methods
4736 //
4737 
4738 //static
4740 {
4741  return msClipboard.get();
4742 }
4743 
4744 //static
4746 {
4747  msClipboard.reset();
4748 }
4749 
4751 {
4752  msClipT0 = 0.0;
4753  msClipT1 = 0.0;
4754  msClipProject = NULL;
4755  if (msClipboard) {
4756  msClipboard->Clear();
4757  }
4758 }
4759 
4760 // Utility function called by other zoom methods
4761 void AudacityProject::Zoom(double level)
4762 {
4763  mViewInfo.SetZoom(level);
4764  FixScrollbars();
4765  // See if we can center the selection on screen, and have it actually fit.
4766  // tOnLeft is the amount of time we would need before the selection left edge to center it.
4767  float t0 = mViewInfo.selectedRegion.t0();
4768  float t1 = mViewInfo.selectedRegion.t1();
4769  float tAvailable = GetScreenEndTime() - mViewInfo.h;
4770  float tOnLeft = (tAvailable - t0 + t1)/2.0;
4771  // Bug 1292 (Enh) is effectively a request to do this scrolling of the selection into view.
4772  // If tOnLeft is positive, then we have room for the selection, so scroll to it.
4773  if( tOnLeft >=0 )
4774  TP_ScrollWindow( t0-tOnLeft);
4775 }
4776 
4777 // Utility function called by other zoom methods
4778 void AudacityProject::ZoomBy(double multiplier)
4779 {
4780  mViewInfo.ZoomBy(multiplier);
4781  FixScrollbars();
4782 }
4783 
4785 // This method 'rewinds' the track, by setting the cursor to 0 and
4786 // scrolling the window to fit 0 on the left side of it
4787 // (maintaining current zoom).
4788 // If shift is held down, it will extend the left edge of the
4789 // selection to 0 (holding right edge constant), otherwise it will
4790 // move both left and right edge of selection to 0
4792 void AudacityProject::Rewind(bool shift)
4793 {
4794  mViewInfo.selectedRegion.setT0(0, false);
4795  if (!shift)
4797 
4798  TP_ScrollWindow(0);
4799 }
4800 
4801 
4803 // This method 'fast-forwards' the track, by setting the cursor to
4804 // the end of the samples on the selected track and scrolling the
4805 // window to fit the end on its right side (maintaining current zoom).
4806 // If shift is held down, it will extend the right edge of the
4807 // selection to the end (holding left edge constant), otherwise it will
4808 // move both left and right edge of selection to the end
4811 {
4812  double len = mTracks->GetEndTime();
4813 
4814  mViewInfo.selectedRegion.setT1(len, false);
4815  if (!shift)
4817 
4818  // Make sure the end of the track is visible
4820  mTrackPanel->Refresh(false);
4821 }
4822 
4823 
4825 // This fetches a pointer to the Transport Toolbar. It may
4826 // either be docked or floating out in the open.
4829 {
4830  return (ControlToolBar *)
4831  (mToolManager ?
4832  mToolManager->GetToolBar(TransportBarID) :
4833  NULL);
4834 }
4835 
4837 {
4838  return GetToolsToolBar();
4839 }
4840 
4842 {
4843  return (DeviceToolBar *)
4844  (mToolManager ?
4845  mToolManager->GetToolBar(DeviceBarID) :
4846  NULL);
4847 }
4848 
4850 {
4851  return (EditToolBar *)
4852  (mToolManager ?
4853  mToolManager->GetToolBar(EditBarID) :
4854  NULL);
4855 }
4856 
4858 {
4859  return (MixerToolBar *)
4860  (mToolManager ?
4861  mToolManager->GetToolBar(MixerBarID) :
4862  NULL);
4863 }
4864 
4866 {
4867  return dynamic_cast<ScrubbingToolBar*>
4868  (mToolManager ?
4869  mToolManager->GetToolBar(ScrubbingBarID) :
4870  nullptr);
4871 }
4872 
4874 {
4875  return (SelectionBar *)
4876  (mToolManager ?
4877  mToolManager->GetToolBar(SelectionBarID) :
4878  NULL);
4879 }
4880 
4881 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
4883 {
4884  return static_cast<SpectralSelectionBar*>(
4885  (mToolManager ?
4886  mToolManager->GetToolBar(SpectralSelectionBarID) :
4887  NULL));
4888 }
4889 #endif
4890 
4892 {
4893  return (ToolsToolBar *)
4894  (mToolManager ?
4895  mToolManager->GetToolBar(ToolsBarID) :
4896  NULL);
4897 }
4898 
4900 {
4901  return (ToolsToolBar *)
4902  (mToolManager ?
4903  mToolManager->GetToolBar(ToolsBarID) :
4904  NULL);
4905 }
4906 
4908 {
4909  return (TranscriptionToolBar *)
4910  (mToolManager ?
4911  mToolManager->GetToolBar(TranscriptionBarID) :
4912  NULL);
4913 }
4914 
4916 {
4917  return mPlaybackMeter;
4918 }
4919 
4921 {
4922  mPlaybackMeter = playback;
4923  if (gAudioIO)
4924  {
4926  }
4927 }
4928 
4930 {
4931  return mCaptureMeter;
4932 }
4933 
4935 {
4936  mCaptureMeter = capture;
4937 
4938  if (gAudioIO)
4939  {
4941  }
4942 }
4943 
4945 {
4946  if (mTimer) {
4947  // mTimer->Stop(); // not really needed
4948  mTimer->Start( 3000 ); // Update messages as needed once every 3 s.
4949  }
4950 }
4951 
4952 void AudacityProject::OnTimer(wxTimerEvent& WXUNUSED(event))
4953 {
4954  MixerToolBar *mixerToolBar = GetMixerToolBar();
4955  if( mixerToolBar )
4956  mixerToolBar->UpdateControls();
4957 
4958  // gAudioIO->GetNumCaptureChannels() should only be positive
4959  // when we are recording.
4960  if (GetAudioIOToken() > 0 && gAudioIO->GetNumCaptureChannels() > 0) {
4961  wxLongLong freeSpace = mDirManager->GetFreeDiskSpace();
4962  if (freeSpace >= 0) {
4963  wxString sMessage;
4964 
4966  sMessage.Printf(_("Disk space remaining for recording: %s"), GetHoursMinsString(iRecordingMins));
4967 
4968  // Do not change mLastMainStatusMessage
4969  mStatusBar->SetStatusText(sMessage, mainStatusBarField);
4970  }
4971  }
4972  else if(ODManager::IsInstanceCreated())
4973  {
4974  //if we have some tasks running, we should say something about it.
4975  int numTasks = ODManager::Instance()->GetTotalNumTasks();
4976  if(numTasks)
4977  {
4978  wxString msg;
4979  float ratioComplete= ODManager::Instance()->GetOverallPercentComplete();
4980 
4981  if(ratioComplete>=1.0f)
4982  {
4983  //if we are 100 percent complete and there is still a task in the queue, we should wake the ODManager
4984  //so it can clear it.
4985  //signal the od task queue loop to wake up so it can remove the tasks from the queue and the queue if it is empty.
4987 
4988 
4989  msg = _("On-demand import and waveform calculation complete.");
4990  mStatusBar->SetStatusText(msg, mainStatusBarField);
4991 
4992  }
4993  else if(numTasks>1)
4994  msg.Printf(_("Import(s) complete. Running %d on-demand waveform calculations. Overall %2.0f%% complete."),
4995  numTasks,ratioComplete*100.0);
4996  else
4997  msg.Printf(_("Import complete. Running an on-demand waveform calculation. %2.0f%% complete."),
4998  ratioComplete*100.0);
4999 
5000 
5001  mStatusBar->SetStatusText(msg, mainStatusBarField);
5002  }
5003  }
5004 
5005  // As also with the TrackPanel timer: wxTimer may be unreliable without
5006  // some restarts
5007  RestartTimer();
5008 }
5009 
5010 // TrackPanel callback method
5012 {
5013  if ( msg != mLastMainStatusMessage ) {
5014  mLastMainStatusMessage = msg;
5015  mStatusBar->SetStatusText(msg, mainStatusBarField);
5016 
5017  // When recording, let the NEW status message stay at least as long as
5018  // the timer interval (if it is not replaced again by this function),
5019  // before replacing it with the message about remaining disk capacity.
5020  RestartTimer();
5021  }
5022 }
5023 
5025 {
5026  double audioTime;
5027 
5028  if (mRuler) {
5029  if (!gAudioIO->IsBusy() && !mLockPlayRegion)
5032  else
5033  // Cause ruler redraw anyway, because we may be zooming or scrolling
5034  mRuler->Refresh();
5035  }
5036 
5037  if (gAudioIO->IsBusy())
5038  audioTime = gAudioIO->GetStreamTime();
5039  else {
5040  double playEnd;
5041  GetPlayRegion(&audioTime, &playEnd);
5042  }
5043 
5045  mViewInfo.selectedRegion.t1(), audioTime);
5046 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
5049 #endif
5050 
5051 }
5052 
5053 
5054 // TrackPanel access
5055 
5057 {
5058  wxSize s;
5059  mTrackPanel->GetTracksUsableArea(&s.x, &s.y);
5060  return s;
5061 }
5062 
5063 void AudacityProject::RefreshTPTrack(Track* pTrk, bool refreshbacking /*= true*/)
5064 {
5065  mTrackPanel->RefreshTrack(pTrk, refreshbacking);
5066 }
5067 
5068 
5069 // TrackPanel callback method
5070 void AudacityProject::TP_PushState(const wxString &desc, const wxString &shortDesc,
5071  UndoPush flags)
5072 {
5073  PushState(desc, shortDesc, flags);
5074 }
5075 
5076 // TrackPanel callback method
5077 void AudacityProject::TP_ModifyState(bool bWantsAutoSave)
5078 {
5079  ModifyState(bWantsAutoSave);
5080 }
5081 
5082 // TrackPanel callback method
5084 {
5085  OnScrollLeft();
5086 }
5087 
5088 // TrackPanel callback method
5090 {
5091  OnScrollRight();
5092 }
5093 
5094 // TrackPanel callback method
5096 {
5097  FixScrollbars();
5098 }
5099 
5101 {
5102  HandleResize();
5103 }
5104 
5105 void AudacityProject::GetPlayRegion(double* playRegionStart,
5106  double *playRegionEnd)
5107 {
5108  if (mRuler)
5109  mRuler->GetPlayRegion(playRegionStart, playRegionEnd);
5110  else
5111  *playRegionEnd = *playRegionStart = 0;
5112 }
5113 
5115 {
5116  // SonifyBeginAutoSave(); // part of RBD's r10680 stuff now backed out
5117 
5118  // To minimize the possibility of race conditions, we first write to a
5119  // file with the extension ".tmp", then rename the file to .autosave
5120  wxString projName;
5121 
5122  if (mFileName.IsEmpty())
5123  projName = wxT("New Project");
5124  else
5125  projName = wxFileName(mFileName).GetName();
5126 
5127  wxString fn = wxFileName(FileNames::AutoSaveDir(),
5128  projName + wxString(wxT(" - ")) + CreateUniqueName()).GetFullPath();
5129 
5130  // PRL: I found a try-catch and rewrote it,
5131  // but this guard is unnecessary because AutoSaveFile does not throw
5132  bool success = GuardedCall< bool >( [&]
5133  {
5134  VarSetter<bool> setter(&mAutoSaving, true, false);
5135 
5136  AutoSaveFile buffer;
5137  WriteXMLHeader(buffer);
5138  WriteXML(buffer, false);
5139  mStrOtherNamesArray.Clear();
5140 
5141  wxFFile saveFile;
5142  saveFile.Open(fn + wxT(".tmp"), wxT("wb"));
5143  return buffer.Write(saveFile);
5144  } );
5145 
5146  if (!success)
5147  return;
5148 
5149  // Now that we have a NEW auto-save file, DELETE the old one
5151 
5152  if (!mAutoSaveFileName.IsEmpty())
5153  return; // could not remove auto-save file
5154 
5155  if (!wxRenameFile(fn + wxT(".tmp"), fn + wxT(".autosave")))
5156  {
5158  wxString::Format( _("Could not create autosave file: %s"),
5159  fn + wxT(".autosave") ),
5160  _("Error"), wxICON_STOP, this);
5161  return;
5162  }
5163 
5164  mAutoSaveFileName += fn + wxT(".autosave");
5165  // no-op cruft that's not #ifdefed for NoteTrack
5166  // See above for further comments.
5167  // SonifyEndAutoSave();
5168 }
5169 
5171 {
5172  if (!mAutoSaveFileName.IsEmpty())
5173  {
5174  if (wxFileExists(mAutoSaveFileName))
5175  {
5176  if (!wxRemoveFile(mAutoSaveFileName))
5177  {
5179  wxString::Format(
5180  _("Could not remove old autosave file: %s"), mAutoSaveFileName ),
5181  _("Error"), wxICON_STOP, this);
5182  return;
5183  }
5184  }
5185 
5186  mAutoSaveFileName = wxT("");
5187  }
5188 }
5189 
5190 
5192 {
5193 #ifdef EXPERIMENTAL_EXTRA_MONITORING
5194  bool bAlwaysMonitor;
5195  gPrefs->Read( wxT("GUI/AlwaysMonitor"), &bAlwaysMonitor, true );
5196  if( !bAlwaysMonitor )
5197  return;
5198 
5199  MeterToolBar * pToolBar = GetMeterToolBar();
5200  if( pToolBar == NULL )
5201  return;
5202  pToolBar->StartMonitoring();
5203 #endif
5204 }
5205 
5207 {
5208  wxString display;
5209  if (rate > 0) {
5210  display = wxString::Format(_("Actual Rate: %d"), rate);
5211  }
5212  else
5213  // clear the status field
5214  ;
5215 
5216  int x, y;
5217  mStatusBar->GetTextExtent(display, &x, &y);
5218  int widths[] = {0, GetControlToolBar()->WidthForStatusBar(mStatusBar), -1, x+50};
5219  mStatusBar->SetStatusWidths(4, widths);
5220  mStatusBar->SetStatusText(display, rateStatusBarField);
5221 }
5222 
5224 {
5225  // Before recording is started, auto-save the file. The file will have
5226  // empty tracks at the bottom where the recording will be put into
5227  AutoSave();
5228 }
5229 
5230 // This is called after recording has stopped and all tracks have flushed.
5232 {
5233  // Only push state if we were capturing and not monitoring
5234  if (GetAudioIOToken() > 0)
5235  {
5236  auto &intervals = gAudioIO->LostCaptureIntervals();
5237  if (intervals.size()) {
5238  // Make a track with labels for recording errors
5239  auto uTrack = GetTrackFactory()->NewLabelTrack();
5240  auto pTrack = uTrack.get();
5241  GetTracks()->Add( std::move(uTrack) );
5242  /* i18n-hint: A name given to a track, appearing as its menu button.
5243  The translation should be short or else it will not display well.
5244  At most, about 11 Latin characters.
5245  Dropout is a loss of a short sequence audio sample data from the
5246  recording */
5247  pTrack->SetName(_("Dropouts"));
5248  long counter = 1;
5249  for (auto &interval : intervals)
5250  pTrack->AddLabel(
5251  SelectedRegion{ interval.first,
5252  interval.first + interval.second },
5253  wxString::Format(wxT("%ld"), counter++),
5254  -2 );
5255  ShowWarningDialog(this, wxT("DropoutDetected"), _("\
5256 Recorded audio was lost at the labeled locations. Possible causes:\n\
5257 \n\
5258 Other applications are competing with Audacity for processor time\n\
5259 \n\
5260 You are saving directly to a slow external storage device\n\
5261 "
5262  ),
5263  false,
5264  _("Turn off dropout detection"));
5265  }
5266 
5267  // Add to history
5268  PushState(_("Recorded Audio"), _("Record"));
5269 
5270  // Reset timer record
5271  if (IsTimerRecordCancelled())
5272  {
5273  EditActions::DoUndo( *this );
5275  }
5276 
5277  // Refresh the project window
5278  FixScrollbars();
5279  RedrawProject();
5280  }
5281 
5282  // Write all cached files to disk, if any
5283  mDirManager->WriteCacheToDisk();
5284 
5285  // Now we auto-save again to get the project to a "normal" state again.
5286  AutoSave();
5287 }
5288 
5290 {
5291  // New blockfiles have been created, so add them to the auto-save file
5292  if (!mAutoSaveFileName.IsEmpty())
5293  {
5294  wxFFile f(mAutoSaveFileName, wxT("ab"));
5295  if (!f.IsOpened())
5296  return; // Keep recording going, there's not much we can do here
5297  blockFileLog.Append(f);
5298  f.Close();
5299  }
5300 }
5301 
5303 {
5304  AS_SetSnapTo(snap);
5305  if (GetSelectionBar()) {
5306  GetSelectionBar()->SetSnapTo(snap);
5307  }
5308 }
5309 
5311 {
5312  return mSnapTo;
5313 }
5314 
5316 {
5317 #ifdef EXPERIMENTAL_SYNC_LOCK
5318  return mIsSyncLocked;
5319 #else
5320  return false;
5321 #endif
5322 }
5323 
5325 {
5326  if (flag != mIsSyncLocked) {
5327  mIsSyncLocked = flag;
5328  if (GetTrackPanel())
5329  GetTrackPanel()->Refresh(false);
5330  }
5331 }
5332 
5333 // Keyboard capture
5334 
5335 // static
5336 bool AudacityProject::HasKeyboardCapture(const wxWindow *handler)
5337 {
5338  return GetKeyboardCaptureHandler() == handler;
5339 }
5340 
5341 // static
5343 {
5344  AudacityProject *project = GetActiveProject();
5345  if (project)
5346  {
5347  return project->mKeyboardCaptureHandler;
5348  }
5349 
5350  return NULL;
5351 }
5352 
5353 // static
5354 void AudacityProject::CaptureKeyboard(wxWindow *handler)
5355 {
5356  AudacityProject *project = GetActiveProject();
5357  if (project)
5358  {
5359 // wxASSERT(project->mKeyboardCaptureHandler == NULL);
5360  project->mKeyboardCaptureHandler = handler;
5361  }
5362 }
5363 
5364 // static
5365 void AudacityProject::ReleaseKeyboard(wxWindow * /* handler */)
5366 {
5367  AudacityProject *project = GetActiveProject();
5368  if (project)
5369  {
5370 // wxASSERT(project->mKeyboardCaptureHandler == handler);
5371  project->mKeyboardCaptureHandler = NULL;
5372  }
5373 
5374  return;
5375 }
5376 
5377 bool AudacityProject::ExportFromTimerRecording(wxFileName fnFile, int iFormat, int iSubFormat, int iFilterIndex)
5378 {
5379  Exporter e;
5380 
5382  return e.ProcessFromTimerRecording(this, false, 0.0, mTracks->GetEndTime(), fnFile, iFormat, iSubFormat, iFilterIndex);
5383 }
5384 
5386  return gAudacityProjects.size();
5387 }
5388 
5390  // This is true if a project was opened from an .aup
5391  // Otherwise it becomes true only when a project is first saved successfully
5392  // in DirManager::SetProject
5393  wxString sProjectName = mDirManager->GetProjectName();
5394  return (sProjectName != wxT(""));
5395 }
5396 
5397 // This is done to empty out the tracks, but without creating a new project.
5401  // A new DirManager.
5402  mDirManager = std::make_shared<DirManager>();
5404 
5405  // mLastSavedTrack code copied from OnCloseWindow.
5406  // Lock all blocks in all tracks of the last saved version, so that
5407  // the blockfiles aren't deleted on disk when we DELETE the blockfiles
5408  // in memory. After it's locked, DELETE the data structure so that
5409  // there's no memory leak.
5410  if (mLastSavedTracks) {
5411  for (auto t : mLastSavedTracks->Any<WaveTrack>())
5412  t->CloseLock();
5413 
5414  mLastSavedTracks->Clear(); // sends an event
5415  mLastSavedTracks.reset();
5416  }
5417 
5418  //mLastSavedTracks = TrackList::Create();
5419  mFileName = "";
5420  mIsRecovered = false;
5421  mbLoadedFromAup = false;
5422  SetProjectTitle();
5423  mDirty = false;
5425 }
5426 
5428  // MY: Will save the project to a NEW location a-la Save As
5429  // and then tidy up after itself.
5430 
5431  wxString sNewFileName = fnFile.GetFullPath();
5432 
5433  // MY: To allow SaveAs from Timer Recording we need to check what
5434  // the value of mFileName is before we change it.
5435  wxString sOldFilename = "";
5436  if (IsProjectSaved()) {
5437  sOldFilename = mFileName;
5438  }
5439 
5440  // MY: If the project file already exists then bail out
5441  // and send populate the message string (pointer) so
5442  // we can tell the user what went wrong.
5443  if (wxFileExists(sNewFileName)) {
5444  return false;
5445  }
5446 
5447  mFileName = sNewFileName;
5448  bool bSuccess = false;
5449  auto cleanup = finally( [&] {
5450  if (!bSuccess)
5451  // Restore file name on error
5452  mFileName = sOldFilename;
5453  } );
5454 
5455  bSuccess = DoSave(true, false);
5456 
5457  if (bSuccess) {
5459  mbLoadedFromAup = true;
5460  SetProjectTitle();
5461  }
5462 
5463  return bSuccess;
5464 }
5465 
5466 // Does the project have any tracks?
5468  // Test for an 'empty' project.
5469  // of course it could still have a history at this stage.
5470  return ! ( GetTracks()->Any() ).empty();
5471 }
5472 
5474 {
5475 
5476  wxString sFormatted = wxEmptyString;
5477 
5478  if (iMinutes < 1) {
5479  // Less than a minute...
5480  sFormatted = _("Less than 1 minute");
5481  return sFormatted;
5482  }
5483 
5484  // Calculate
5485  int iHours = iMinutes / 60;
5486  int iMins = iMinutes % 60;
5487 
5488  auto sHours =
5489  wxString::Format( wxPLURAL("%d hour", "%d hours", iHours), iHours );
5490  auto sMins =
5491  wxString::Format( wxPLURAL("%d minute", "%d minutes", iMins), iMins );
5492 
5493  /* i18n-hint: A time in hours and minutes. Only translate the "and". */
5494  sFormatted.Printf( _("%s and %s."), sHours, sMins);
5495  return sFormatted;
5496 }
5497 
5498 // This routine will give an estimate of how many
5499 // minutes of recording time we have available.
5500 // The calculations made are based on the user's current
5501 // preferences.
5503 
5504  // Obtain the current settings
5505  auto oCaptureFormat = QualityPrefs::SampleFormatChoice();
5506  if (lCaptureChannels == 0) {
5507  gPrefs->Read(wxT("/AudioIO/RecordChannels"), &lCaptureChannels, 2L);
5508  }
5509 
5510  // Find out how much free space we have on disk
5511  wxLongLong lFreeSpace = mDirManager->GetFreeDiskSpace();
5512  if (lFreeSpace < 0) {
5513  return 0;
5514  }
5515 
5516  // Calculate the remaining time
5517  double dRecTime = 0.0;
5518  double bytesOnDiskPerSample = SAMPLE_SIZE_DISK(oCaptureFormat);
5519  dRecTime = lFreeSpace.GetHi() * 4294967296.0 + lFreeSpace.GetLo();
5520  dRecTime /= bytesOnDiskPerSample;
5521  dRecTime /= lCaptureChannels;
5522  dRecTime /= GetRate();
5523 
5524  // Convert to minutes before returning
5525  int iRecMins = (int)round(dRecTime / 60.0);
5526  return iRecMins;
5527 }
5528 
5529 
5531 : mProject(project)
5532 {
5533  mProject->Bind(EVT_TRACK_PANEL_TIMER,
5535  this);
5536 }
5537 
5539 {
5540  // Let other listeners get the notification
5541  event.Skip();
5542 
5543  if(!mProject->IsAudioActive())
5544  return;
5545  else if (mMode == Mode::Refresh) {
5546  // PRL: see comments in Scrubbing.cpp for why this is sometimes needed.
5547  // These unnecessary refreshes cause wheel rotation events to be delivered more uniformly
5548  // to the application, so scrub speed control is smoother.
5549  // (So I see at least with OS 10.10 and wxWidgets 3.0.2.)
5550  // Is there another way to ensure that than by refreshing?
5551  const auto trackPanel = mProject->GetTrackPanel();
5552  trackPanel->Refresh(false);
5553  }
5554  else if (mMode != Mode::Off) {
5555  // Pan the view, so that we put the play indicator at some fixed
5556  // fraction of the window width.
5557 
5558  ViewInfo &viewInfo = mProject->GetViewInfo();
5559  TrackPanel *const trackPanel = mProject->GetTrackPanel();
5560  const int posX = viewInfo.TimeToPosition(viewInfo.mRecentStreamTime);
5561  int width;
5562  trackPanel->GetTracksUsableArea(&width, NULL);
5563  int deltaX;
5564  switch (mMode)
5565  {
5566  default:
5567  wxASSERT(false);
5568  /* fallthru */
5569  case Mode::Pinned:
5570  deltaX =
5572  break;
5573  case Mode::Right:
5574  deltaX = posX - width; break;
5575  }
5576  viewInfo.h =
5577  viewInfo.OffsetTimeByPixels(viewInfo.h, deltaX, true);
5578  if (!mProject->MayScrollBeyondZero())
5579  // Can't scroll too far left
5580  viewInfo.h = std::max(0.0, viewInfo.h);
5581  trackPanel->Refresh(false);
5582  }
5583 }
5584 
5585 bool AudacityProject::IsFocused( const wxWindow *window ) const
5586 {
5587  return window == mFocusLender || window == wxWindow::FindFocus();
5588 }
5589 
5591 {
5592  if (create && !mLyricsWindow)
5594  return mLyricsWindow;
5595 }
5596 
5598 {
5599  if (create && !mMixerBoardFrame) {
5602  }
5603  return mMixerBoardFrame;
5604 }
5605 
5607 {
5608  if (create && !mHistoryWindow)
5610  return mHistoryWindow;
5611 }
5612 
5613 MacrosWindow *AudacityProject::GetMacrosWindow(bool bExpanded, bool create)
5614 {
5615  if (create && !mMacrosWindow)
5616  mMacrosWindow = safenew MacrosWindow{ this, bExpanded };
5617 
5618  if (mMacrosWindow) {
5619  mMacrosWindow->Show();
5620  mMacrosWindow->Raise();
5621  mMacrosWindow->UpdateDisplay( bExpanded );
5622  }
5623  return mMacrosWindow;
5624 }
5625 
5627 {
5628  if (create && !mFreqWindow)
5630  this, -1, _("Frequency Analysis"),
5631  wxPoint{ 150, 150 }
5632  } );
5633  return mFreqWindow.get();
5634 }
5635 
5637 {
5638  // All of this goes away when the Contrast Dialog is converted to a module
5639  if(create && !mContrastDialog)
5641  this, -1, _("Contrast Analysis (WCAG 2 compliance)"),
5642  wxPoint{ 150, 150 }
5643  } );
5644 
5645  return mContrastDialog.get();
5646 }
5647 
5649 {
5650  return mTrackPanel->GetScreenEndTime();
5651 }
5652 
5654 {
5655  for (auto t : GetTracks()->Any())
5656  t->SetSelected(false);
5657 
5658  mTrackPanel->Refresh(false);
5659 }
5660 
5661 void AudacityProject::ZoomInByFactor( double ZoomFactor )
5662 {
5663  // LLL: Handling positioning differently when audio is
5664  // actively playing. Don't do this if paused.
5665  if ((gAudioIO->IsStreamActive(GetAudioIOToken()) != 0) &&
5666  !gAudioIO->IsPaused()){
5667  ZoomBy(ZoomFactor);
5669  mTrackPanel->Refresh(false);
5670  return;
5671  }
5672 
5673  // DMM: Here's my attempt to get logical zooming behavior
5674  // when there's a selection that's currently at least
5675  // partially on-screen
5676 
5677  const double endTime = GetScreenEndTime();
5678  const double duration = endTime - mViewInfo.h;
5679 
5680  bool selectionIsOnscreen =
5681  (mViewInfo.selectedRegion.t0() < endTime) &&
5683 
5684  bool selectionFillsScreen =
5686  (mViewInfo.selectedRegion.t1() > endTime);
5687 
5688  if (selectionIsOnscreen && !selectionFillsScreen) {
5689  // Start with the center of the selection
5690  double selCenter = (mViewInfo.selectedRegion.t0() +
5691  mViewInfo.selectedRegion.t1()) / 2;
5692 
5693  // If the selection center is off-screen, pick the
5694  // center of the part that is on-screen.
5695  if (selCenter < mViewInfo.h)
5696  selCenter = mViewInfo.h +
5698  if (selCenter > endTime)
5699  selCenter = endTime -
5700  (endTime - mViewInfo.selectedRegion.t0()) / 2;
5701 
5702  // Zoom in
5703  ZoomBy(ZoomFactor);
5704  const double newDuration = GetScreenEndTime() - mViewInfo.h;
5705 
5706  // Recenter on selCenter
5707  TP_ScrollWindow(selCenter - newDuration / 2);
5708  return;
5709  }
5710 
5711 
5712  double origLeft = mViewInfo.h;
5713  double origWidth = duration;
5714  ZoomBy(ZoomFactor);
5715 
5716  const double newDuration = GetScreenEndTime() - mViewInfo.h;
5717  double newh = origLeft + (origWidth - newDuration) / 2;
5718 
5719  // MM: Commented this out because it was confusing users
5720  /*
5721  // make sure that the *right-hand* end of the selection is
5722  // no further *left* than 1/3 of the way across the screen
5723  if (mViewInfo.selectedRegion.t1() < newh + mViewInfo.screen / 3)
5724  newh = mViewInfo.selectedRegion.t1() - mViewInfo.screen / 3;
5725 
5726  // make sure that the *left-hand* end of the selection is
5727  // no further *right* than 2/3 of the way across the screen
5728  if (mViewInfo.selectedRegion.t0() > newh + mViewInfo.screen * 2 / 3)
5729  newh = mViewInfo.selectedRegion.t0() - mViewInfo.screen * 2 / 3;
5730  */
5731 
5732  TP_ScrollWindow(newh);
5733 }
5734 
5735 void AudacityProject::ZoomOutByFactor( double ZoomFactor )
5736 {
5737  //Zoom() may change these, so record original values:
5738  const double origLeft = mViewInfo.h;
5739  const double origWidth = GetScreenEndTime() - origLeft;
5740 
5741  ZoomBy(ZoomFactor);
5742  const double newWidth = GetScreenEndTime() - mViewInfo.h;
5743 
5744  const double newh = origLeft + (origWidth - newWidth) / 2;
5745  // newh = (newh > 0) ? newh : 0;
5746  TP_ScrollWindow(newh);
5747 }
5748 
5749 // Select the full time range, if no
5750 // time range is selected.
5752 {
5753  auto flags = GetMenuManager(*this).GetUpdateFlags(*this);
5754  if(!(flags & TracksSelectedFlag) ||
5757 }
5758 
5759 // Stop playing or recording, if paused.
5761 {
5762  auto flags = GetMenuManager(*this).GetUpdateFlags(*this);
5763  if( flags & PausedFlag )
5764  TransportActions::DoStop(*this);
5765 }
Definition: Snap.h:54
std::shared_ptr< TrackList > tracks
Definition: UndoManager.h:84
XMLTagHandler * HandleXMLChild(const wxChar *tag) override
Definition: Project.cpp:3633
void OnODTaskComplete(wxCommandEvent &event)
Definition: Project.cpp:2309
void WriteXMLAttributes(XMLWriter &xmlFile) const
Definition: ViewInfo.cpp:169
void SetZoom(double pixelsPerSecond)
Definition: ViewInfo.cpp:94
Data structure containing pointer to a BlockFile and a start time. Element of a BlockArray.
Definition: Sequence.h:31
void RefreshTrack(Track *trk, bool refreshbacking=true)
Definition: TrackPanel.cpp:990
const int sbarSpaceWidth
Definition: Project.cpp:203
static wxString FindDefaultPath(Operation op)
Definition: FileNames.cpp:406
void OnMenu(wxCommandEvent &event)
Definition: Project.cpp:2358
static bool IsInstanceCreated()
returns whether or not the singleton instance was created yet
Definition: ODManager.cpp:205
bool GetSolo() const
Definition: Track.h:777
void WriteXMLHeader(XMLWriter &xmlFile) const
Definition: Project.cpp:3676
std::unique_ptr< Overlay > mIndicatorOverlay
Definition: Project.h:778
void SetSyncLock(bool flag)
Definition: Project.cpp:5324
A ToolBar that has the main Transport buttons.
double SSBL_GetRate() const override
Definition: Project.cpp:1671
void OnODTaskUpdate(wxCommandEvent &event)
Handles the redrawing necessary for tasks as they partially update in the background.
Definition: Project.cpp:2300
A ToolBar that holds the VU Meter.
Definition: MeterToolBar.h:32
std::unique_ptr< ImportXMLTagHandler > mImportXMLTagHandler
Definition: Project.h:723
void RedrawProject(const bool bForceWaveTracks=false)
Definition: Project.cpp:1466
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
void SetFrequencySelectionFormatName(const NumericFormatId &format)
Definition: Project.cpp:1729
bool mbLoadedFromAup
Definition: Project.h:593
void StopStream()
Stop recording, playback or input monitoring.
Definition: AudioIO.cpp:2408
#define wxPLURAL(sing, plur, n)
Definition: Internat.h:82
void DoSelectAll(AudacityProject &project)
std::vector< std::shared_ptr< Track > > AddImportedTracks(const wxString &fileName, TrackHolders &&newTracks)
Definition: Project.cpp:4191
#define AUDACITY_VERSION_STRING
Definition: Audacity.h:81
static ODManager *(* Instance)()
Definition: ODManager.h:49
void SetSel1(double)
Definition: Project.cpp:1490
AUDACITY_DLL_API Theme theTheme
Definition: Theme.cpp:209
void SetBeforeScreenWidth(wxInt64 beforeWidth, wxInt64 screenWidth, double lowerBoundTime=0.0)
Definition: ViewInfo.cpp:161
void SetRate(double rate)
void InitialState()
Definition: Project.cpp:4601
AudioIOStartStreamOptions GetDefaultPlayOptions()
Definition: Project.cpp:1366
static int gAudacityOffsetInc
Definition: Project.cpp:301
#define FSCKstatus_SAVE_AUP
Definition: DirManager.h:33
void TP_PushState(const wxString &longDesc, const wxString &shortDesc, UndoPush flags) override
Definition: Project.cpp:5070
void OnToolBarUpdate(wxCommandEvent &event)
Definition: Project.cpp:2292
int GetActualHeight() const
Definition: Track.h:301
virtual void StartTag(const wxString &name)
Definition: XMLWriter.cpp:78
bool bUpdateTrackIndicator
Definition: ViewInfo.h:184
double t0() const
void GetNextWindowPlacement(wxRect *nextRect, bool *pMaximized, bool *pIconized)
Definition: Project.cpp:721
std::unique_ptr< PlaybackScroller > mPlaybackScroller
Definition: Project.h:824
virtual int GetFilterIndex() const
void SetMissingAliasFileDialog(wxDialog *dialog)
Sets the wxDialog that is being displayed Used by the custom dialog warning constructor and destructo...
Definition: Project.cpp:1456
ViewInfo is used mainly to hold the zooming, selection and scroll information. It also has some statu...
Definition: ViewInfo.h:143
void WriteXML(XMLWriter &xmlFile) const override
Definition: WaveTrack.cpp:1802
Works with UndoManager to allow user to see descriptions of and undo previous commands. Also allows you to selectively clear the undo memory so as to free up space.
Definition: HistoryWindow.h:27
double AS_GetRate() override
Definition: Project.cpp:1624
bool isPoint() const
const NumericFormatId & SSBL_GetBandwidthSelectionFormatName() override
Definition: Project.cpp:1692
DEFINE_EVENT_TYPE(EVT_CAPTURE_KEY)
Custom events.
bool mbInitializingScrollbar
Definition: Project.h:757
void PushState(const TrackList *l, const SelectedRegion &selectedRegion, const std::shared_ptr< Tags > &tags, const wxString &longDescription, const wxString &shortDescription, UndoPush flags=UndoPush::AUTOSAVE)
Scrubber & GetScrubber()
Definition: Project.h:786
void ZoomOutByFactor(double ZoomFactor)
Definition: Project.cpp:5735
root of a hierarchy of classes that are thrown and caught by Audacity.
wxString mRecoveryAutoSaveDataDir
Definition: Project.h:735
void EnsureVisible(Track *t)
Abstract base class used in importing a file.
Definition: Import.h:32
bool SaveFromTimerRecording(wxFileName fnFile)
Definition: Project.cpp:5427
TimeTrack * timeTrack
Definition: AudioIO.h:138
SelectedRegion selectedRegion
Definition: ViewInfo.h:162
bool mIsSyncLocked
Definition: Project.h:719
void DoRemoveTracks(AudacityProject &project)
Definition: TrackMenus.cpp:538
ToolsToolBar * TP_GetToolsToolBar() override
Definition: Project.cpp:4836
static ModuleManager & Get()
void EnqueueODTasks()
Definition: Project.cpp:3357
MacrosWindow * GetMacrosWindow(bool bExpanded, bool create=false)
Definition: Project.cpp:5613
virtual void WriteAttr(const wxString &name, const wxString &value)
Definition: XMLWriter.cpp:131
double GetStreamTime()
During playback, the track time most recently played.
Definition: AudioIO.cpp:2895
wxInt64 sbarH
Definition: ViewInfo.h:168
int AS_GetSnapTo() override
Definition: Project.cpp:1636
static void CaptureKeyboard(wxWindow *handler)
Definition: Project.cpp:5354
bool mIconized
Definition: Project.h:662
std::unique_ptr< wxTimer > mTimer
Definition: Project.h:642
WaveClip * GetClipByIndex(int index)
Definition: WaveTrack.cpp:2298
virtual void EndTag(const wxString &name)
Definition: XMLWriter.cpp:101
void StateSaved()
void SignalTaskQueueLoop()
Wakes the queue loop up by signalling its condition variable.
Definition: ODManager.cpp:112
bool RedoAvailable()
Definition: Project.cpp:4622
(not quite a Toolbar) at foot of screen for setting and viewing the selection range.
Definition: SelectionBar.h:44
double GetZoomOfToFit(const AudacityProject &project)
Definition: ViewMenus.cpp:115
AudacityProject * DoImportMIDI(AudacityProject *pProject, const wxString &fileName)
Definition: FileMenus.cpp:104
void FinishAutoScroll()
Definition: Project.cpp:1777
static wxFileNameWrapper DefaultToDocumentsFolder(const wxString &preference)
Definition: FileNames.cpp:370
void SetMissingAliasedFileWarningShouldShow(bool b)
Changes the behavior of missing aliased blockfiles warnings.
const wxChar * desc
Definition: ExportPCM.cpp:58
bool IsTimerRecordCancelled()
Definition: Project.h:695
MixerBoardFrame * mMixerBoardFrame
Definition: Project.h:667
bool HandleMenuID(int id, CommandFlag flags, CommandMask mask)
bool mShowId3Dialog
Definition: Project.h:712
bool mAutoScrolling
Definition: Project.h:660
void SaveWindowSize()
const NumericFormatId & GetFrequencySelectionFormatName() const
Definition: Project.cpp:1724
void SetTimes(double start, double end, double audio)
wxScrollBar * mVsbar
Definition: Project.h:654
void SetSel0(double)
Definition: Project.cpp:1483
static bool IsMidi(const wxString &fName)
Definition: Import.cpp:326
AudacityProject * mProject
Definition: Project.h:154
void SetSelectionFormat(const NumericFormatId &format)
static bool IsAlreadyOpen(const wxString &projPathName)
Definition: Project.cpp:2856
AProjectArray gAudacityProjects
Definition: Project.cpp:306
double GetScreenEndTime() const
Definition: TrackPanel.cpp:455
void DoUndo(AudacityProject &project)
Definition: EditMenus.cpp:217
static wxArrayString ShowOpenDialog(const wxString &extraformat=wxEmptyString, const wxString &extrafilter=wxEmptyString)
Show an open dialogue for opening audio files, and possibly other sorts of files. ...
Definition: Project.cpp:2751
float GetPan() const
Definition: WaveTrack.cpp:431
bool mLockPlayRegion
Definition: Project.h:721
void UpdateMenus(AudacityProject &project, bool checkActive=true)
Definition: Menus.cpp:646
Contains declarations for CommandType class.
void PopState(const UndoState &state)
Definition: Project.cpp:4671
virtual double GetEndTime() const =0
void MacShowUndockedToolbars(bool show)
Definition: Project.cpp:2390
Track * Add(std::unique_ptr< TrackKind > &&t)
Add a Track, giving it a fresh id.
Definition: Track.cpp:732
int ScreenContaining(wxRect &r)
Definition: Project.cpp:696
bool ShowDependencyDialogIfNeeded(AudacityProject *project, bool isSaving)
void UpdatePrefs()
Definition: TrackPanel.cpp:301
A ToolBar that has the edit buttons on it.
Definition: EditToolBar.h:73
void SetAudioIOToken(int token)
Definition: Project.cpp:1528
void Rewind(bool shift)
Definition: Project.cpp:4792
wxString mSoloPref
Definition: Project.h:688
unsigned GetNumCaptureChannels() const
Definition: AudioIO.h:1028
void OnScrollRightButton(wxScrollEvent &event)
Definition: Project.cpp:1856
EditToolBar * GetEditToolBar()
Definition: Project.cpp:4849
Contains declaration of Command base class.
double GetOffset() const override
Definition: WaveTrack.cpp:218
#define FSCKstatus_CHANGED
Definition: DirManager.h:32
float GetOverallPercentComplete()
Gets the total percent complete for all tasks combined.
Definition: ODManager.cpp:539
FreqWindow * GetFreqWindow(bool create=false)
Definition: Project.cpp:5626
bool Process(AudacityProject *project, bool selectedOnly, double t0, double t1)
Definition: Export.cpp:369
#define _TS(s)
Definition: Internat.h:30