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