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