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