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