Audacity  2.2.2
TimerRecordDialog.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  TimerRecordDialog.cpp
6 
7  Copyright 2006-2009 by Vaughan Johnson
8 
9  This program is free software; you can redistribute it and/or modify
10  it under the terms of the GNU General Public License as published by
11  the Free Software Foundation; either version 2 of the License, or
12  (at your option) any later version.
13 
14 *******************************************************************//*******************************************************************/
20 
21 #include "Audacity.h"
22 #include "TimerRecordDialog.h"
23 #include "FileNames.h"
24 
25 #include <wx/defs.h>
26 #include <wx/dir.h>
27 #include <wx/datetime.h>
28 #include <wx/filedlg.h>
29 #include <wx/intl.h>
30 #include <wx/progdlg.h>
31 #include <wx/sizer.h>
32 #include <wx/string.h>
33 #include <wx/timer.h>
34 #include <wx/dynlib.h> //<! For windows.h
35 
36 #include "ShuttleGui.h"
37 #include "Project.h"
38 #include "Internat.h"
39 #include "Prefs.h"
41 #include "widgets/HelpSystem.h"
42 #include "widgets/ErrorDialog.h"
43 
44 #define TIMER_ID 7000
45 
46 enum { // control IDs
58 };
59 
60 enum {
63 };
64 
65 // Post Timer Recording Actions
66 // Ensure this matches the enum in Menus.cpp
67 enum {
75 };
76 
77 const int kTimerInterval = 50; // ms
78 
79 static double wxDateTime_to_AudacityTime(wxDateTime& dateTime)
80 {
81  return (dateTime.GetHour() * 3600.0) + (dateTime.GetMinute() * 60.0) + dateTime.GetSecond();
82 };
83 
84 
85 // The purpose of the DatePickerCtrlAx class is to make to wxDatePickerCtrl more accessible for
86 // the NVDA screen reader.
87 // By default the msaa state of wxDatePickerCtrl is always normal (0x0), and this causes nvda not
88 // to read the control when the user tabs to it. This class
89 // modifies the state to be focusable + focused (when it's the focus).
90 // Note that even with this class NVDA still doesn't read the NEW selected part of the control when left/right
91 // arrow keys are used.
92 
93 #if wxUSE_ACCESSIBILITY
94 
95 class DatePickerCtrlAx final : public wxWindowAccessible
96 {
97 public:
98  DatePickerCtrlAx(wxDatePickerCtrl * ctrl) : wxWindowAccessible(ctrl), mCtrl(ctrl) {};
99 
100  virtual ~ DatePickerCtrlAx() {};
101 
102  // Returns a state constant.
103  wxAccStatus GetState(int childId, long *state) override;
104 
105 private:
106  wxDatePickerCtrl *mCtrl;
107 };
108 
109 // Returns a state constant.
110 wxAccStatus DatePickerCtrlAx::GetState(int WXUNUSED(childId), long *state)
111 {
112  *state = wxACC_STATE_SYSTEM_FOCUSABLE;
113  *state |= (mCtrl == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0);
114 
115  return wxACC_OK;
116 }
117 
118 #endif // wxUSE_ACCESSIBILITY
119 
120 
121 BEGIN_EVENT_TABLE(TimerRecordDialog, wxDialogWrapper)
122  EVT_DATE_CHANGED(ID_DATEPICKER_START, TimerRecordDialog::OnDatePicker_Start)
123  EVT_TEXT(ID_TIMETEXT_START, TimerRecordDialog::OnTimeText_Start)
124 
125  EVT_DATE_CHANGED(ID_DATEPICKER_END, TimerRecordDialog::OnDatePicker_End)
126  EVT_TEXT(ID_TIMETEXT_END, TimerRecordDialog::OnTimeText_End)
127 
128  EVT_TEXT(ID_TIMETEXT_DURATION, TimerRecordDialog::OnTimeText_Duration)
129 
130  EVT_BUTTON(wxID_OK, TimerRecordDialog::OnOK)
131  EVT_BUTTON(wxID_HELP, TimerRecordDialog::OnHelpButtonClick)
132 
133  EVT_TIMER(TIMER_ID, TimerRecordDialog::OnTimer)
134 
135  EVT_BUTTON(ID_AUTOSAVEPATH_BUTTON, TimerRecordDialog::OnAutoSavePathButton_Click)
136  EVT_BUTTON(ID_AUTOEXPORTPATH_BUTTON, TimerRecordDialog::OnAutoExportPathButton_Click)
137 
138  EVT_CHECKBOX(ID_AUTOSAVE_CHECKBOX, TimerRecordDialog::OnAutoSaveCheckBox_Change)
139  EVT_CHECKBOX(ID_AUTOEXPORT_CHECKBOX, TimerRecordDialog::OnAutoExportCheckBox_Change)
140 
142 
143 TimerRecordDialog::TimerRecordDialog(wxWindow* parent, bool bAlreadySaved)
144 : wxDialogWrapper(parent, -1, _("Audacity Timer Record"), wxDefaultPosition,
145  wxDefaultSize, wxCAPTION)
146 {
147  SetName(GetTitle());
148 
149  m_DateTime_Start = wxDateTime::UNow();
150  long seconds; // default duration is 1 hour = 3600 seconds
151  gPrefs->Read(wxT("/TimerRecord/LastDuration"), &seconds, 3600);
152  m_TimeSpan_Duration = wxTimeSpan::Seconds(seconds);
153  m_DateTime_End = m_DateTime_Start + m_TimeSpan_Duration;
154 
155  m_pDatePickerCtrl_Start = NULL;
156  m_pTimeTextCtrl_Start = NULL;
157 
158  m_pDatePickerCtrl_End = NULL;
159  m_pTimeTextCtrl_End = NULL;
160 
161  m_pTimeTextCtrl_Duration = NULL;
162 
163  // Do we allow the user to change the Automatic Save file?
164  m_bProjectAlreadySaved = bAlreadySaved;
165 
166  ShuttleGui S(this, eIsCreating);
167  this->PopulateOrExchange(S);
168 
169  // Set initial focus to "1" of "01h" in Duration NumericTextCtrl,
170  // instead of OK button (default).
171  m_pTimeTextCtrl_Duration->SetFocus();
172  m_pTimeTextCtrl_Duration->SetFieldFocus(3);
173 
174  m_timer.SetOwner(this, TIMER_ID);
175  m_timer.Start(kTimerInterval);
176 
177  // Do we need to tidy up when the timer recording has been completed?
178  m_bProjectCleanupRequired = !(this->HaveFilesToRecover());
179 
180 }
181 
183 {
184 }
185 
186 void TimerRecordDialog::OnTimer(wxTimerEvent& WXUNUSED(event))
187 {
188  wxDateTime dateTime_UNow = wxDateTime::UNow();
189  if (m_DateTime_Start < dateTime_UNow) {
190  m_DateTime_Start = dateTime_UNow;
193  this->UpdateEnd(); // Keep Duration constant and update End for changed Start.
194  }
195 }
196 
197 void TimerRecordDialog::OnDatePicker_Start(wxDateEvent& WXUNUSED(event))
198 {
200  double dTime = m_pTimeTextCtrl_Start->GetValue();
201  long hr = (long)(dTime / 3600.0);
202  long min = (long)((dTime - (hr * 3600.0)) / 60.0);
203  long sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
204  m_DateTime_Start.SetHour(hr);
205  m_DateTime_Start.SetMinute(min);
206  m_DateTime_Start.SetSecond(sec);
207 
208  // User might have had the dialog up for a while, or
209  // had a future day, set hour of day less than now's, then changed day to today.
210  wxTimerEvent dummyTimerEvent;
211  this->OnTimer(dummyTimerEvent);
212 
213  // Always update End for changed Start, keeping Duration constant.
214  // Note that OnTimer sometimes calls UpdateEnd, so sometimes this is redundant,
215  // but OnTimer doesn't need to always call UpdateEnd, but we must here.
216  this->UpdateEnd();
217 }
218 
219 void TimerRecordDialog::OnTimeText_Start(wxCommandEvent& WXUNUSED(event))
220 {
221  //v NumericTextCtrl doesn't implement upper ranges, i.e.,
222  // if I tell it "024 h 060 m 060 s", then
223  // user increments the hours past 23, it rolls over to 0
224  // (although if you increment below 0, it stays at 0).
225  // So instead, set the max to 99 and just catch hours > 24 and fix the ctrls.
226  double dTime = m_pTimeTextCtrl_Start->GetValue();
227  long days = (long)(dTime / (24.0 * 3600.0));
228  if (days > 0) {
229  dTime -= (double)days * 24.0 * 3600.0;
230  m_DateTime_Start += wxTimeSpan::Days(days);
233  }
234 
235  wxDateEvent dummyDateEvent;
236  this->OnDatePicker_Start(dummyDateEvent);
237 }
238 
239 void TimerRecordDialog::OnDatePicker_End(wxDateEvent& WXUNUSED(event))
240 {
242  double dTime = m_pTimeTextCtrl_End->GetValue();
243  long hr = (long)(dTime / 3600.0);
244  long min = (long)((dTime - (hr * 3600.0)) / 60.0);
245  long sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
246  m_DateTime_End.SetHour(hr);
247  m_DateTime_End.SetMinute(min);
248  m_DateTime_End.SetSecond(sec);
249 
250  // DatePickerCtrls use SetRange to make sure End is never less than Start, but
251  // need to implement it for the TimeTextCtrls.
256  }
257 
258  this->UpdateDuration(); // Keep Start constant and update Duration for changed End.
259 }
260 
261 void TimerRecordDialog::OnTimeText_End(wxCommandEvent& WXUNUSED(event))
262 {
263  //v NumericTextCtrl doesn't implement upper ranges, i.e.,
264  // if I tell it "024 h 060 m 060 s", then
265  // user increments the hours past 23, it rolls over to 0
266  // (although if you increment below 0, it stays at 0).
267  // So instead, set the max to 99 and just catch hours > 24 and fix the ctrls.
268  double dTime = m_pTimeTextCtrl_End->GetValue();
269  long days = (long)(dTime / (24.0 * 3600.0));
270  if (days > 0) {
271  dTime -= (double)days * 24.0 * 3600.0;
272  m_DateTime_End += wxTimeSpan::Days(days);
275  }
276 
277  wxDateEvent dummyDateEvent;
278  this->OnDatePicker_End(dummyDateEvent);
279 }
280 
281 void TimerRecordDialog::OnTimeText_Duration(wxCommandEvent& WXUNUSED(event))
282 {
283  double dTime = m_pTimeTextCtrl_Duration->GetValue();
284  long hr = (long)(dTime / 3600.0);
285  long min = (long)((dTime - (hr * 3600.0)) / 60.0);
286  long sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
287  m_TimeSpan_Duration = wxTimeSpan(hr, min, sec); //v milliseconds?
288 
289  this->UpdateEnd(); // Keep Start constant and update End for changed Duration.
290 }
291 
292 // New events for timer recording automation
293 void TimerRecordDialog::OnAutoSavePathButton_Click(wxCommandEvent& WXUNUSED(event))
294 {
296  _("Save Timer Recording As"),
297  m_fnAutoSaveFile.GetPath(),
298  m_fnAutoSaveFile.GetFullName(),
299  wxT("aup"),
300  _("Audacity projects") + wxT(" (*.aup)|*.aup"),
301  wxFD_SAVE | wxRESIZE_BORDER,
302  this);
303 
304  if (fName == wxT(""))
305  return;
306 
307  AudacityProject* pProject = GetActiveProject();
308 
309  // If project already exists then abort - we do not allow users to overwrite an existing project
310  // unless it is the current project.
311  if (wxFileExists(fName) && (pProject->GetFileName() != fName)) {
313  NULL,
314  _("The selected file name could not be used\nfor Timer Recording because it \
315 would overwrite another project.\nPlease try again and select an original name."),
316  _("Error Saving Timer Recording Project"),
317  wxOK|wxICON_ERROR);
318  m.ShowModal();
319  return;
320  }
321 
322  // Set this boolean to false so we now do a SaveAs at the end of the recording
323  // unless we're saving the current project.
324  m_bProjectAlreadySaved = pProject->GetFileName() == fName? true : false;
325 
326  m_fnAutoSaveFile = fName;
327  m_fnAutoSaveFile.SetExt(wxT("aup"));
328  this->UpdateTextBoxControls();
329 }
330 
331 void TimerRecordDialog::OnAutoExportPathButton_Click(wxCommandEvent& WXUNUSED(event))
332 {
333  AudacityProject* pProject = GetActiveProject();
334  Exporter eExporter;
335 
336  // Call the Exporter to set the options required
337  if (eExporter.SetAutoExportOptions(pProject)) {
338  // Populate the options so that we can destroy this instance of the Exporter
343 
344  // Update the text controls
345  this->UpdateTextBoxControls();
346  }
347 }
348 
349 void TimerRecordDialog::OnAutoSaveCheckBox_Change(wxCommandEvent& WXUNUSED(event)) {
351 }
352 
353 void TimerRecordDialog::OnAutoExportCheckBox_Change(wxCommandEvent& WXUNUSED(event)) {
355 }
356 
357 void TimerRecordDialog::OnHelpButtonClick(wxCommandEvent& WXUNUSED(event))
358 {
359  HelpSystem::ShowHelp(this, wxT("Timer_Record"), true);
360 }
361 
362 void TimerRecordDialog::OnOK(wxCommandEvent& WXUNUSED(event))
363 {
364  this->TransferDataFromWindow();
365  if (!m_TimeSpan_Duration.IsPositive())
366  {
367  AudacityMessageBox(_("Duration is zero. Nothing will be recorded."),
368  _("Error in Duration"), wxICON_EXCLAMATION | wxOK);
369  return;
370  }
371 
372  // Validate that we have a Save and/or Export path setup if the appropriate check box is ticked
373  wxString sTemp = m_fnAutoSaveFile.GetFullPath();
374  if (m_pTimerAutoSaveCheckBoxCtrl->IsChecked()) {
375  if (!m_fnAutoSaveFile.IsOk() || m_fnAutoSaveFile.IsDir()) {
376  AudacityMessageBox(_("Automatic Save path is invalid."),
377  _("Error in Automatic Save"), wxICON_EXCLAMATION | wxOK);
378  return;
379  }
380  }
381  if (m_pTimerAutoExportCheckBoxCtrl->IsChecked()) {
382  if (!m_fnAutoExportFile.IsOk() || m_fnAutoExportFile.IsDir()) {
383  AudacityMessageBox(_("Automatic Export path is invalid."),
384  _("Error in Automatic Export"), wxICON_EXCLAMATION | wxOK);
385  return;
386  }
387  }
388 
389  // MY: Estimate here if we have enough disk space to
390  // complete this Timer Recording.
391  // If we dont think there is enough space then ask the user
392  // if they want to continue.
393  // We don't stop the user from starting the recording
394  // as its possible that they plan to free up some
395  // space before the recording begins
396  AudacityProject* pProject = GetActiveProject();
397 
398  // How many minutes do we have left on the disc?
399  int iMinsLeft = pProject->GetEstimatedRecordingMinsLeftOnDisk();
400 
401  // How many minutes will this recording require?
402  int iMinsRecording = m_TimeSpan_Duration.GetMinutes();
403 
404  // Do we have enough space?
405  if (iMinsRecording >= iMinsLeft) {
406 
407  // Format the strings
408  wxString sRemainingTime = "";
409  sRemainingTime = pProject->GetHoursMinsString(iMinsLeft);
410  wxString sPlannedTime = "";
411  sPlannedTime = pProject->GetHoursMinsString(iMinsRecording);
412 
413  // Create the message string
414  wxString sMessage = "";
415  sMessage.Printf(_("You may not have enough free disk space to complete this Timer Recording, based on your current settings.\n\nDo you wish to continue?\n\nPlanned recording duration: %s\nRecording time remaining on disk: %s"),
416  sPlannedTime,
417  sRemainingTime);
418 
419  AudacityMessageDialog dlgMessage(NULL,
420  sMessage,
421  _("Timer Recording Disk Space Warning"),
422  wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
423  if (dlgMessage.ShowModal() != wxID_YES) {
424  // User decided not to continue - bail out!
425  return;
426  }
427  }
428 
429  m_timer.Stop(); // Don't need to keep updating m_DateTime_Start to prevent backdating.
430  this->EndModal(wxID_OK);
431  wxLongLong duration = m_TimeSpan_Duration.GetSeconds();
432  // this will assert if the duration won't fit in a long
433  gPrefs->Write(wxT("/TimerRecord/LastDuration"), duration.ToLong());
434  gPrefs->Flush();
435 }
436 
437 void TimerRecordDialog::EnableDisableAutoControls(bool bEnable, int iControlGoup) {
438 
439  if (iControlGoup == CONTROL_GROUP_EXPORT) {
440  m_pTimerExportPathTextCtrl->Enable( bEnable );
441  m_pTimerExportPathButtonCtrl->Enable( bEnable);
442  } else if (iControlGoup == CONTROL_GROUP_SAVE) {
443  m_pTimerSavePathTextCtrl->Enable( bEnable);
444  m_pTimerSavePathButtonCtrl->Enable(bEnable );
445  }
446 
447  // Enable or disable the Choice box - if there is no Save or Export then this will be disabled
448  if (m_pTimerAutoSaveCheckBoxCtrl->GetValue() || m_pTimerAutoExportCheckBoxCtrl->GetValue()) {
450  } else {
453  }
454 }
455 
457  // Will update the text box controls
458  m_pTimerSavePathTextCtrl->SetValue(m_fnAutoSaveFile.GetFullPath());
459  m_pTimerExportPathTextCtrl->SetValue(m_fnAutoExportFile.GetFullPath());
460 
461  // MY: Ensure we still display "Current Project" if this has already been saved
463  m_pTimerSavePathTextCtrl->SetValue(_("Current Project"));
464  }
465 }
466 
467 // Copied from AutoRecovery.cpp - for use with Timer Recording Improvements
469 {
470  wxDir dir(FileNames::AutoSaveDir());
471  if (!dir.IsOpened()) {
472  AudacityMessageBox(_("Could not enumerate files in auto save directory."),
473  _("Error"), wxICON_STOP);
474  return false;
475  }
476 
477  wxString filename;
478  bool c = dir.GetFirst(&filename, wxT("*.autosave"), wxDIR_FILES);
479 
480  return c;
481 }
482 
484 {
485  wxArrayString files;
486  wxDir::GetAllFiles(FileNames::AutoSaveDir(), &files,
487  wxT("*.autosave"), wxDIR_FILES);
488 
489  for (unsigned int i = 0; i < files.GetCount(); i++)
490  {
491  if (!wxRemoveFile(files[i]))
492  {
493  // I don't think this error message is actually useful.
494  // -dmazzoni
495  //AudacityMessageBox(_("Could not remove auto save file: " + files[i]),
496  // _("Error"), wxICON_STOP);
497  return false;
498  }
499  }
500 
501  return true;
502 }
503 
507 {
508  AudacityProject* pProject = GetActiveProject();
509 
510  auto updateResult = ProgressResult::Success;
511 
512  if (m_DateTime_Start > wxDateTime::UNow())
513  updateResult = this->WaitForStart();
514 
515  if (updateResult != ProgressResult::Success) {
516  // Don't proceed, but don't treat it as canceled recording. User just canceled waiting.
518  } else {
519  // Record for specified time.
520  pProject->OnRecord(*pProject);
521  bool bIsRecording = true;
522 
523  wxString sPostAction = m_pTimerAfterCompleteChoiceCtrl->GetString(m_pTimerAfterCompleteChoiceCtrl->GetSelection());
524 
525  // Two column layout.
527  auto &column1 = columns[0];
528  auto &column2 = columns[1];
529 
530  column1.push_back( _("Recording start:") );
531  column2.push_back( GetDisplayDate(m_DateTime_Start) );
532 
533  column1.push_back( _("Duration:") );
534  column2.push_back( m_TimeSpan_Duration.Format() );
535 
536  column1.push_back( _("Recording end:") );
537  column2.push_back( GetDisplayDate(m_DateTime_End) );
538 
539  column1.push_back( {} );
540  column2.push_back( {} );
541 
542  column1.push_back( _("Automatic Save enabled:") );
543  column2.push_back( (m_bAutoSaveEnabled ? _("Yes") : _("No")) );
544 
545  column1.push_back( _("Automatic Export enabled:") );
546  column2.push_back( (m_bAutoExportEnabled ? _("Yes") : _("No")) );
547 
548  column1.push_back( _("Action after Timer Recording:") );
549  column2.push_back( sPostAction );
550 
552  progress(m_TimeSpan_Duration.GetMilliseconds().GetValue(),
553  _("Audacity Timer Record Progress"),
554  columns,
556 
557  // Make sure that start and end time are updated, so we always get the full
558  // duration, even if there's some delay getting here.
559  wxTimerEvent dummyTimerEvent;
560  this->OnTimer(dummyTimerEvent);
561 
562  // Loop for progress display during recording.
563  while (bIsRecording && (updateResult == ProgressResult::Success)) {
564  updateResult = progress.UpdateProgress();
565  wxMilliSleep(kTimerInterval);
566  bIsRecording = (wxDateTime::UNow() <= m_DateTime_End); // Call UNow() again for extra accuracy...
567  }
568  }
569 
570  // Must do this AFTER the timer project dialog has been deleted to ensure the application
571  // responds to the AUDIOIO events...see not about bug #334 in the ProgressDialog constructor.
572  pProject->OnStop(*pProject);
573 
574  // Let the caller handle cancellation or failure from recording progress.
575  if (updateResult == ProgressResult::Cancelled || updateResult == ProgressResult::Failed)
577 
578  return ExecutePostRecordActions((updateResult == ProgressResult::Stopped));
579 }
580 
582  // MY: We no longer automatically (and silently) call ->Save() when the
583  // timer recording is completed. We can now Save and/or Export depending
584  // on the options selected by the user.
585  // Once completed, we can also close Audacity, restart the system or
586  // shutdown the system.
587  // If there was any error with the auto save or export then we will not do
588  // the actions requested and instead present an error mesasge to the user.
589  // Finally, if there is no post-record action selected then we output
590  // a dialog detailing what has been carried out instead.
591 
592  AudacityProject* pProject = GetActiveProject();
593 
594  bool bSaveOK = false;
595  bool bExportOK = false;
596  int iPostRecordAction = m_pTimerAfterCompleteChoiceCtrl->GetSelection();
597  int iOverriddenAction = iPostRecordAction;
598  bool bErrorOverride = false;
599 
600  // Do Automatic Save?
601  if (m_bAutoSaveEnabled) {
602 
603  // MY: If this project has already been saved then simply execute a Save here
605  bSaveOK = pProject->Save();
606  } else {
607  bSaveOK = pProject->SaveFromTimerRecording(m_fnAutoSaveFile);
608  }
609  }
610 
611  // Do Automatic Export?
612  if (m_bAutoExportEnabled) {
615  }
616 
617  // Check if we need to override the post recording action
618  bErrorOverride = ((m_bAutoSaveEnabled && !bSaveOK) || (m_bAutoExportEnabled && !bExportOK));
619  if (bErrorOverride || bWasStopped) {
620  iPostRecordAction = POST_TIMER_RECORD_NOTHING;
621  }
622 
623  if (iPostRecordAction == POST_TIMER_RECORD_NOTHING) {
624  // If there is no post-record action then we can show a message indicating what has been done
625 
626  wxString sMessage = (bWasStopped ? _("Timer Recording stopped.") :
627  _("Timer Recording completed."));
628 
629  if (m_bAutoSaveEnabled) {
630  if (bSaveOK) {
631  sMessage.Printf(_("%s\n\nRecording saved: %s"),
632  sMessage, m_fnAutoSaveFile.GetFullPath());
633  } else {
634  sMessage.Printf(_("%s\n\nError saving recording."), sMessage);
635  }
636  }
637  if (m_bAutoExportEnabled) {
638  if (bExportOK) {
639  sMessage.Printf(_("%s\n\nRecording exported: %s"),
640  sMessage, m_fnAutoExportFile.GetFullPath());
641  } else {
642  sMessage.Printf(_("%s\n\nError exporting recording."), sMessage);
643  }
644  }
645 
646  if (bErrorOverride) {
647 
648  if ((iOverriddenAction != iPostRecordAction) &&
649  (iOverriddenAction != POST_TIMER_RECORD_NOTHING)) {
650  // Inform the user that we have overridden the selected action
651  sMessage.Printf(_("%s\n\n'%s' has been canceled due to the error(s) noted above."),
652  sMessage,
653  m_pTimerAfterCompleteChoiceCtrl->GetString(iOverriddenAction));
654  }
655 
656  // Show Error Message Box
657  AudacityMessageBox(sMessage, _("Error"), wxICON_EXCLAMATION | wxOK);
658  } else {
659 
660  if (bWasStopped && (iOverriddenAction != POST_TIMER_RECORD_NOTHING)) {
661  sMessage.Printf(_("%s\n\n'%s' has been canceled as the recording was stopped."),
662  sMessage,
663  m_pTimerAfterCompleteChoiceCtrl->GetString(iOverriddenAction));
664  }
665 
666  AudacityMessageBox(sMessage, _("Timer Recording"), wxICON_INFORMATION | wxOK);
667  }
668  }
669 
670  // MY: Lets do some actions that only apply to Exit/Restart/Shutdown
671  if (iPostRecordAction >= POST_TIMER_RECORD_CLOSE) {
672  do {
673 
674  // Set the flags as appropriate based on what we have done
675  wxUint32 eActionFlags = TR_ACTION_NOTHING;
676  if (m_bAutoSaveEnabled && bSaveOK) {
677  eActionFlags |= TR_ACTION_SAVED;
678  }
679  if (m_bAutoExportEnabled && bExportOK) {
680  eActionFlags |= TR_ACTION_EXPORTED;
681  }
682 
683  // Lets show a warning dialog telling the user what is about to happen.
684  // If the user no longer wants to carry out this action then they can click
685  // Cancel and we will do POST_TIMER_RECORD_NOTHING instead.
686  auto iDelayOutcome = PreActionDelay(iPostRecordAction, (TimerRecordCompletedActions)eActionFlags);
687  if (iDelayOutcome != ProgressResult::Success) {
688  // Cancel the action!
689  iPostRecordAction = POST_TIMER_RECORD_NOTHING;
690  // Set this to true to avoid any chance of the temp files being deleted
691  bErrorOverride = true;
692  break;
693  }
694 
695 
696  // If we have simply recorded, exported and then plan to Exit/Restart/Shutdown
697  // then we will have a temporary project setup. Let's get rid of that!
699  // PRL: Move the following cleanup into a finally?
700  // No, I think you would want to skip this, in case recording
701  // succeeded but then save or export threw an exception.
703  }
704  } while (false);
705  }
706 
707  // Do we need to cleanup the orphaned temporary project?
708  if (m_bProjectCleanupRequired && !bErrorOverride) {
709  // PRL: Move the following cleanup into a finally?
710  // No, I think you would want to skip this, in case recording
711  // succeeded but then save or export threw an exception.
713  }
714 
715  // Return the action as required
716  return iPostRecordAction;
717 }
718 
719 wxString TimerRecordDialog::GetDisplayDate( wxDateTime & dt )
720 {
721 #if defined(__WXMSW__)
722  // On Windows, wxWidgets uses the system date control and it displays the
723  // date based on the Windows locale selected by the user. But, wxDateTime
724  // using the strftime function to return the formatted date. Since the
725  // default locale for the Windows CRT environment is "C", the dates come
726  // back in a different format.
727  //
728  // So, we make direct Windows calls to format the date like it the date
729  // control.
730  //
731  // (Most of this taken from src/msw/datectrl.cpp)
732 
733  const wxDateTime::Tm tm(dt.GetTm());
734  SYSTEMTIME st;
735  wxString s;
736  int len;
737 
738  st.wYear = (WXWORD)tm.year;
739  st.wMonth = (WXWORD)(tm.mon - wxDateTime::Jan + 1);
740  st.wDay = tm.mday;
741  st.wDayOfWeek = st.wMinute = st.wSecond = st.wMilliseconds = 0;
742 
743  len = ::GetDateFormat(LOCALE_USER_DEFAULT,
744  DATE_SHORTDATE,
745  &st,
746  NULL,
747  NULL,
748  0);
749  if (len > 0) {
750  len = ::GetDateFormat(LOCALE_USER_DEFAULT,
751  DATE_SHORTDATE,
752  &st,
753  NULL,
754  wxStringBuffer(s, len),
755  len);
756  if (len > 0) {
757  s += wxT(" ") + dt.FormatTime();
758  return s;
759  }
760  }
761 #endif
762 
763  // Use default formatting
764 wxPrintf(wxT("%s\n"), dt.Format());
765  return dt.FormatDate() + wxT(" ") + dt.FormatTime();
766 }
767 
768 TimerRecordPathCtrl * TimerRecordDialog::NewPathControl(wxWindow *wParent, const int iID,
769  const wxString &sCaption, const wxString &sValue)
770 {
771  TimerRecordPathCtrl * pTextCtrl;
772  wxASSERT(wParent); // to justify safenew
773  pTextCtrl = safenew TimerRecordPathCtrl(wParent, iID, sValue);
774  pTextCtrl->SetName(sCaption);
775  return pTextCtrl;
776 }
777 
779 {
780  bool bAutoSave = gPrefs->ReadBool("/TimerRecord/AutoSave", false);
781  bool bAutoExport = gPrefs->ReadBool("/TimerRecord/AutoExport", false);
782  int iPostTimerRecordAction = gPrefs->ReadLong("/TimerRecord/PostAction", 0);
783 
784  S.SetBorder(5);
785  S.StartMultiColumn(2, wxCENTER);
786  {
787  S.StartVerticalLay(true);
788  {
789  /* i18n-hint: This string is used to configure the controls for times when the recording is
790  * started and stopped. As such it is important that only the alphabetic parts of the string
791  * are translated, with the numbers left exactly as they are.
792  * The 'h' indicates the first number displayed is hours, the 'm' indicates the second number
793  * displayed is minutes, and the 's' indicates that the third number displayed is seconds.
794  */
795  wxString strFormat = _("099 h 060 m 060 s");
796  S.StartStatic(_("Start Date and Time"), true);
797  {
799  safenew wxDatePickerCtrl(this, // wxWindow *parent,
800  ID_DATEPICKER_START, // wxWindowID id,
801  m_DateTime_Start); // const wxDateTime& dt = wxDefaultDateTime,
802  // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDP_DEFAULT | wxDP_SHOWCENTURY, const wxValidator& validator = wxDefaultValidator, const wxString& name = "datectrl")
803  m_pDatePickerCtrl_Start->SetName(_("Start Date"));
804  m_pDatePickerCtrl_Start->SetRange(wxDateTime::Today(), wxInvalidDateTime); // No backdating.
805 #if wxUSE_ACCESSIBILITY
806  m_pDatePickerCtrl_Start->SetAccessible( safenew DatePickerCtrlAx(m_pDatePickerCtrl_Start));
807 #endif
809 
812  m_pTimeTextCtrl_Start->SetName(_("Start Time"));
818  }
819  S.EndStatic();
820 
821  S.StartStatic(_("End Date and Time"), true);
822  {
824  safenew wxDatePickerCtrl(this, // wxWindow *parent,
825  ID_DATEPICKER_END, // wxWindowID id,
826  m_DateTime_End); // const wxDateTime& dt = wxDefaultDateTime,
827  // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
828  // long style = wxDP_DEFAULT | wxDP_SHOWCENTURY,
829  // const wxValidator& validator = wxDefaultValidator,
830  // const wxString& name = "datectrl")
831  m_pDatePickerCtrl_End->SetRange(m_DateTime_Start, wxInvalidDateTime); // No backdating.
832  m_pDatePickerCtrl_End->SetName(_("End Date"));
833 #if wxUSE_ACCESSIBILITY
834  m_pDatePickerCtrl_End->SetAccessible( safenew DatePickerCtrlAx(m_pDatePickerCtrl_End));
835 #endif
837 
840  m_pTimeTextCtrl_End->SetName(_("End Time"));
845  }
846  S.EndStatic();
847 
848  S.StartStatic(_("Duration"), true);
849  {
850  /* i18n-hint: This string is used to configure the controls which shows the recording
851  * duration. As such it is important that only the alphabetic parts of the string
852  * are translated, with the numbers left exactly as they are.
853  * The string 'days' indicates that the first number in the control will be the number of days,
854  * then the 'h' indicates the second number displayed is hours, the 'm' indicates the third
855  * number displayed is minutes, and the 's' indicates that the fourth number displayed is
856  * seconds.
857  */
858  wxString strFormat1 = _("099 days 024 h 060 m 060 s");
860  m_pTimeTextCtrl_Duration->SetName(_("Duration"));
862  m_pTimeTextCtrl_Duration->SetValue(m_TimeSpan_Duration.GetSeconds().ToDouble());
865  }
866  S.EndStatic();
867  }
868  S.EndVerticalLay();
869 
870  S.StartVerticalLay(true);
871  {
872  S.StartStatic(_("Automatic Save"), true);
873  {
874  // If checked, the project will be saved when the recording is completed
875  m_pTimerAutoSaveCheckBoxCtrl = S.Id(ID_AUTOSAVE_CHECKBOX).AddCheckBox(_("Enable &Automatic Save?"),
876  (bAutoSave ? "true" : "false"));
877  S.StartMultiColumn(3, wxEXPAND);
878  {
879  wxString sInitialValue = wxT("");
880  AudacityProject* pProject = GetActiveProject();
881  wxString sSaveValue = pProject->GetFileName();
882  if (sSaveValue != wxEmptyString) {
883  m_fnAutoSaveFile.Assign(sSaveValue);
884  sInitialValue = _("Current Project");
885  }
886  S.AddPrompt(_("Save Project As:"));
887  m_pTimerSavePathTextCtrl = NewPathControl(this, ID_AUTOSAVEPATH_TEXT, _("Save Project As:"), sInitialValue);
888  m_pTimerSavePathTextCtrl->SetEditable(false);
891  }
892  S.EndMultiColumn();
893  }
894  S.EndStatic();
895 
896  S.StartStatic(_("Automatic Export"), true);
897  {
898  m_pTimerAutoExportCheckBoxCtrl = S.Id(ID_AUTOEXPORT_CHECKBOX).AddCheckBox(_("Enable Automatic &Export?"), (bAutoExport ? "true" : "false"));
899  S.StartMultiColumn(3, wxEXPAND);
900  {
901  S.AddPrompt(_("Export Project As:"));
902  m_pTimerExportPathTextCtrl = NewPathControl(this, ID_AUTOEXPORTPATH_TEXT, _("Export Project As:"), wxT(""));
903  m_pTimerExportPathTextCtrl->SetEditable(false);
906  }
907  S.EndMultiColumn();
908  }
909  S.EndStatic();
910 
911  S.StartStatic(_("Options"), true);
912  {
913 
914  wxArrayString arrayOptions;
915  arrayOptions.Add(_("Do nothing"));
916  arrayOptions.Add(_("Exit Audacity"));
917  arrayOptions.Add(_("Restart system"));
918  arrayOptions.Add(_("Shutdown system"));
919 
920  m_sTimerAfterCompleteOptionsArray.Add(arrayOptions.Item(0));
921  m_sTimerAfterCompleteOptionsArray.Add(arrayOptions.Item(1));
922 #ifdef __WINDOWS__
923  m_sTimerAfterCompleteOptionsArray.Add(arrayOptions.Item(2));
924  m_sTimerAfterCompleteOptionsArray.Add(arrayOptions.Item(3));
925 #endif
926  m_sTimerAfterCompleteOption = arrayOptions.Item(iPostTimerRecordAction);
927 
928  m_pTimerAfterCompleteChoiceCtrl = S.AddChoice(_("After Recording completes:"),
931  }
932  S.EndStatic();
933 
934  }
935  S.EndVerticalLay();
936  }
937  S.EndMultiColumn();
938 
939  // MY: Added the help button here
941 
942  Layout();
943  Fit();
944  SetMinSize(GetSize());
945  Center();
946 
949 }
950 
952 {
953  double dTime;
954  long hr;
955  long min;
956  long sec;
957 
959  dTime = m_pTimeTextCtrl_Start->GetValue();
960  hr = (long)(dTime / 3600.0);
961  min = (long)((dTime - (hr * 3600.0)) / 60.0);
962  sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
963  m_DateTime_Start.SetHour(hr);
964  m_DateTime_Start.SetMinute(min);
965  m_DateTime_Start.SetSecond(sec);
966 
968  dTime = m_pTimeTextCtrl_End->GetValue();
969  hr = (long)(dTime / 3600.0);
970  min = (long)((dTime - (hr * 3600.0)) / 60.0);
971  sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
972  m_DateTime_End.SetHour(hr);
973  m_DateTime_End.SetMinute(min);
974  m_DateTime_End.SetSecond(sec);
975 
977 
978  // Pull the settings from the auto save/export controls and write to the pref file
981 
982  // MY: Obtain the index from the choice control so we can save to the prefs file
983  int iPostRecordAction = m_pTimerAfterCompleteChoiceCtrl->GetSelection();
984 
985  // Save the options back to the prefs file
986  gPrefs->Write("/TimerRecord/AutoSave", m_bAutoSaveEnabled);
987  gPrefs->Write("/TimerRecord/AutoExport", m_bAutoExportEnabled);
988  gPrefs->Write("/TimerRecord/PostAction", iPostRecordAction);
989 
990  return true;
991 }
992 
993 // Update m_TimeSpan_Duration and ctrl based on m_DateTime_Start and m_DateTime_End.
995 {
997  m_pTimeTextCtrl_Duration->SetValue(m_TimeSpan_Duration.GetSeconds().ToDouble());
998 }
999 
1000 // Update m_DateTime_End and ctrls based on m_DateTime_Start and m_TimeSpan_Duration.
1002 {
1003  //v Use remaining disk -> record time calcs from AudacityProject::OnTimer to set range?
1006  m_pDatePickerCtrl_End->SetRange(m_DateTime_Start, wxInvalidDateTime); // No backdating.
1007  m_pDatePickerCtrl_End->Refresh();
1009 }
1010 
1012 {
1013  // MY: The Waiting For Start dialog now shows what actions will occur after recording has completed
1014  wxString sPostAction = m_pTimerAfterCompleteChoiceCtrl->GetString(m_pTimerAfterCompleteChoiceCtrl->GetSelection());
1015 
1016  // Two column layout.
1018  auto &column1 = columns[0];
1019  auto &column2 = columns[1];
1020 
1021  column1.push_back(_("Waiting to start recording at:"));
1022  column2.push_back(GetDisplayDate(m_DateTime_Start));
1023 
1024  column1.push_back(_("Recording duration:"));
1025  column2.push_back(m_TimeSpan_Duration.Format());
1026 
1027  column1.push_back(_("Scheduled to stop at:"));
1028  column2.push_back(GetDisplayDate(m_DateTime_End));
1029 
1030  column1.push_back( {} );
1031  column2.push_back( {} );
1032 
1033  column1.push_back(_("Automatic Save enabled:"));
1034  column2.push_back((m_bAutoSaveEnabled ? _("Yes") : _("No")));
1035 
1036  column1.push_back(_("Automatic Export enabled:"));
1037  column2.push_back((m_bAutoExportEnabled ? _("Yes") : _("No")));
1038 
1039  column1.push_back(_("Action after Timer Recording:"));
1040  column2.push_back(sPostAction);
1041 
1042  wxDateTime startWait_DateTime = wxDateTime::UNow();
1043  wxTimeSpan waitDuration = m_DateTime_Start - startWait_DateTime;
1044  TimerProgressDialog progress(waitDuration.GetMilliseconds().GetValue(),
1045  _("Audacity Timer Record - Waiting for Start"),
1046  columns,
1048  _("Recording will commence in:"));
1049 
1050  auto updateResult = ProgressResult::Success;
1051  bool bIsRecording = false;
1052  while (updateResult == ProgressResult::Success && !bIsRecording)
1053  {
1054  updateResult = progress.UpdateProgress();
1055  wxMilliSleep(10);
1056  bIsRecording = (m_DateTime_Start <= wxDateTime::UNow());
1057  }
1058  return updateResult;
1059 }
1060 
1062 {
1063  wxString sAction = m_pTimerAfterCompleteChoiceCtrl->GetString(iActionIndex);
1064  wxString sCountdownLabel;
1065  sCountdownLabel.Printf("%s in:", sAction);
1066 
1067  // Two column layout.
1069  auto &column1 = columns[0];
1070  auto &column2 = columns[1];
1071 
1072  column1.push_back(_("Timer Recording completed."));
1073  column2.push_back( {} );
1074 
1075  column1.push_back( {} );
1076  column2.push_back( {} );
1077 
1078  column1.push_back(_("Recording Saved:"));
1079  column2.push_back(((eCompletedActions & TR_ACTION_SAVED) ? _("Yes") : _("No")));
1080 
1081  column1.push_back(_("Recording Exported:"));
1082  column2.push_back(((eCompletedActions & TR_ACTION_EXPORTED) ? _("Yes") : _("No")));
1083 
1084  column1.push_back(_("Action after Timer Recording:"));
1085  column2.push_back(sAction);
1086 
1087  wxDateTime dtNow = wxDateTime::UNow();
1088  wxTimeSpan tsWait = wxTimeSpan(0, 1, 0, 0);
1089  wxDateTime dtActionTime = dtNow.Add(tsWait);
1090 
1091  TimerProgressDialog dlgAction(tsWait.GetMilliseconds().GetValue(),
1092  _("Audacity Timer Record - Waiting"),
1093  columns,
1095  sCountdownLabel);
1096 
1097  auto iUpdateResult = ProgressResult::Success;
1098  bool bIsTime = false;
1099  while (iUpdateResult == ProgressResult::Success && !bIsTime)
1100  {
1101  iUpdateResult = dlgAction.UpdateProgress();
1102  wxMilliSleep(10);
1103  bIsTime = (dtActionTime <= wxDateTime::UNow());
1104  }
1105  return iUpdateResult;
1106 }
void OnDatePicker_End(wxDateEvent &event)
wxCheckBox * m_pTimerAutoSaveCheckBoxCtrl
void OnTimeText_Start(wxCommandEvent &event)
void OnAutoSavePathButton_Click(wxCommandEvent &event)
void PopulateOrExchange(ShuttleGui &S)
ProgressResult
wxFileName GetAutoExportFileName()
Definition: Export.cpp:991
bool SetAutoExportOptions(AudacityProject *project)
Definition: Export.cpp:995
TimerRecordPathCtrl * m_pTimerExportPathTextCtrl
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:366
wxTimeSpan m_TimeSpan_Duration
bool SaveFromTimerRecording(wxFileName fnFile)
Definition: Project.cpp:5807
Dialog for Timer Record, i.e., timed or long recording.
wxWindow * AddWindow(wxWindow *pWindow, int Flags=wxALIGN_CENTRE|wxALL)
Definition: ShuttleGui.cpp:257
void EndMultiColumn()
int RunWaitDialog()
Runs the wait for start dialog. Returns false if the user clicks stop.
wxButton * m_pTimerSavePathButtonCtrl
void EnableMenu(bool enable=true)
std::vector< MessageColumn > MessageTable
int AudacityMessageBox(const wxString &message, const wxString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: ErrorDialog.h:92
void OnStop(const CommandContext &)
Definition: Menus.cpp:2722
void SetFormatString(const wxString &formatString)
void OnTimeText_Duration(wxCommandEvent &event)
int GetEstimatedRecordingMinsLeftOnDisk(long lCaptureChannels=0)
Definition: Project.cpp:5884
wxDatePickerCtrl * m_pDatePickerCtrl_Start
int GetAutoExportFormat()
Definition: Export.cpp:979
void EnableDisableAutoControls(bool bEnable, int iControlGoup)
#define safenew
Definition: Audacity.h:223
TimerRecordCompletedActions
void AddPrompt(const wxString &Prompt)
Right aligned text string.
Definition: ShuttleGui.cpp:215
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:161
void EndVerticalLay()
Definition: ShuttleGui.cpp:991
void OnOK(wxCommandEvent &event)
wxFileConfig * gPrefs
Definition: Prefs.cpp:72
wxCheckBox * AddCheckBox(const wxString &Prompt, const wxString &Selected)
Definition: ShuttleGui.cpp:267
TimerRecordPathCtrl * NewPathControl(wxWindow *wParent, const int iID, const wxString &sCaption, const wxString &sValue)
void OnDatePicker_Start(wxDateEvent &event)
TimerRecordPathCtrl * m_pTimerSavePathTextCtrl
NumericTextCtrl * m_pTimeTextCtrl_End
int ExecutePostRecordActions(bool bWasStopped)
wxDateTime m_DateTime_Start
const int kTimerInterval
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
Definition: ShuttleGui.cpp:998
wxChoice * m_pTimerAfterCompleteChoiceCtrl
wxChoice * AddChoice(const wxString &Prompt, const wxString &Selected, const wxArrayString *pChoices)
Definition: ShuttleGui.cpp:331
ShuttleGui & Id(int id)
void OnAutoExportCheckBox_Change(wxCommandEvent &event)
static void ShowHelp(wxWindow *parent, const wxString &localFileName, const wxString &remoteURL, bool bModal=false, bool alwaysDefaultBrowser=false)
Definition: HelpSystem.cpp:201
int min(int a, int b)
ProgressResult PreActionDelay(int iActionIndex, TimerRecordCompletedActions eCompletedActions)
int GetAutoExportFilterIndex()
Definition: Export.cpp:987
NumericTextCtrl * m_pTimeTextCtrl_Duration
wxArrayString m_sTimerAfterCompleteOptionsArray
bool ExportFromTimerRecording(wxFileName fnFile, int iFormat, int iSubFormat, int iFilterIndex)
Definition: Project.cpp:5787
wxButton * m_pTimerExportPathButtonCtrl
void SetValue(double newValue)
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
NumericTextCtrl * m_pTimeTextCtrl_Start
static wxString AutoSaveDir()
Definition: FileNames.cpp:109
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
static double wxDateTime_to_AudacityTime(wxDateTime &dateTime)
static wxString SelectFile(Operation op, const wxString &message, const wxString &default_path, const wxString &default_filename, const wxString &default_extension, const wxString &wildcard, int flags, wxWindow *parent)
Definition: FileNames.cpp:405
void OnTimeText_End(wxCommandEvent &event)
AUDACITY_DLL_API AudacityProject * GetActiveProject()
Definition: Project.cpp:302
wxString GetHoursMinsString(int iMinutes)
Definition: Project.cpp:5855
wxStaticBox * StartStatic(const wxString &Str, int iProp=0)
Definition: ShuttleGui.cpp:701
wxFileName m_fnAutoExportFile
ProgressResult WaitForStart()
void OnAutoExportPathButton_Click(wxCommandEvent &event)
void OnHelpButtonClick(wxCommandEvent &event)
bool TransferDataFromWindow() override
void AddStandardButtons(long buttons=eOkButton|eCancelButton, wxButton *extra=NULL)
void OnRecord(const CommandContext &)
Definition: Menus.cpp:2736
END_EVENT_TABLE()
static bool HaveFilesToRecover()
wxFileName m_fnAutoSaveFile
wxString GetDisplayDate(wxDateTime &dt)
void OnTimer(wxTimerEvent &event)
#define TIMER_ID
void SetBorder(int Border)
Definition: ShuttleGui.h:251
const wxString & GetFileName()
Definition: Project.h:287
wxString m_sTimerAfterCompleteOption
wxCheckBox * m_pTimerAutoExportCheckBoxCtrl
void OnAutoSaveCheckBox_Change(wxCommandEvent &event)
wxButton * AddButton(const wxString &Text, int PositionFlags=wxALIGN_CENTRE)
Definition: ShuttleGui.cpp:301
wxDatePickerCtrl * m_pDatePickerCtrl_End
static void CleanTempDir()
Definition: DirManager.cpp:436
int GetAutoExportSubFormat()
Definition: Export.cpp:983
void StartVerticalLay(int iProp=1)
Definition: ShuttleGui.cpp:982