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