Audacity 3.2.0
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
22#include "TimerRecordDialog.h"
23
24#include "FileNames.h"
25
26#include <thread>
27#include <wx/setup.h> // for wxUSE_* macros
28
29#include <wx/wxcrtvararg.h>
30#include <wx/button.h>
31#include <wx/calctrl.h>
32#include <wx/checkbox.h>
33#include <wx/choice.h>
34#include <wx/defs.h>
35#include <wx/dir.h>
36#include <wx/datectrl.h>
37#include <wx/datetime.h>
38#include <wx/intl.h>
39#include <wx/sizer.h>
40#include <wx/string.h>
41#include <wx/timer.h>
42#include <wx/dynlib.h> //<! For windows.h
43
44#include "AudioIO.h"
45#include "SelectFile.h"
46#include "ShuttleGui.h"
47#include "ProjectAudioManager.h"
48#include "ProjectFileIO.h"
49#include "ProjectFileManager.h"
50#include "ProjectManager.h"
51#include "Prefs.h"
52#include "Track.h"
54#include "widgets/HelpSystem.h"
58
59#if wxUSE_ACCESSIBILITY
61#endif
62
63#define TIMER_ID 7000
64
65enum { // control IDs
77};
78
79enum {
82};
83
84// The slow timer interval is used to update the start and end times, which only show
85// time to the nearest second. So we only need an update once a second.
86const int kSlowTimerInterval = 1000; // ms
87
88// This timer interval is used in some busy-wait loops and is much shorter.
89constexpr auto kTimerInterval = std::chrono::milliseconds{50};
90
91static double wxDateTime_to_AudacityTime(wxDateTime& dateTime)
92{
93 return (dateTime.GetHour() * 3600.0) + (dateTime.GetMinute() * 60.0) + dateTime.GetSecond();
94};
95
96
97// The purpose of the DatePickerCtrlAx class is to make to wxDatePickerCtrl more accessible for
98// the NVDA screen reader.
99// By default the msaa state of wxDatePickerCtrl is always normal (0x0), and this causes nvda not
100// to read the control when the user tabs to it. This class
101// modifies the state to be focusable + focused (when it's the focus).
102// Note that even with this class NVDA still doesn't read the NEW selected part of the control when left/right
103// arrow keys are used.
104
105#if wxUSE_ACCESSIBILITY
106
107class DatePickerCtrlAx final : public WindowAccessible
108{
109public:
110 DatePickerCtrlAx(wxDatePickerCtrl * ctrl) : WindowAccessible(ctrl), mCtrl(ctrl) {};
111
112 virtual ~ DatePickerCtrlAx() {};
113
114 // Returns a state constant.
115 wxAccStatus GetState(int childId, long *state) override;
116
117private:
118 wxDatePickerCtrl *mCtrl;
119};
120
121// Returns a state constant.
122wxAccStatus DatePickerCtrlAx::GetState(int WXUNUSED(childId), long *state)
123{
124 *state = wxACC_STATE_SYSTEM_FOCUSABLE;
125 *state |= (mCtrl == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0);
126
127 return wxACC_OK;
128}
129
130#endif // wxUSE_ACCESSIBILITY
131
132
133BEGIN_EVENT_TABLE(TimerRecordDialog, wxDialogWrapper)
136
139
141
144
146
149
152
154
156 wxWindow* parent, AudacityProject &project, bool bAlreadySaved)
157: wxDialogWrapper(parent, -1, XO("Audacity Timer Record"), wxDefaultPosition,
158 wxDefaultSize, wxCAPTION)
159, mProject{ project }
160{
161 SetName();
162
163 m_DateTime_Start = wxDateTime::UNow();
164 long seconds; // default duration is 1 hour = 3600 seconds
165 gPrefs->Read(wxT("/TimerRecord/LastDuration"), &seconds, 3600);
166 m_TimeSpan_Duration = wxTimeSpan::Seconds(seconds);
167 m_DateTime_End = m_DateTime_Start + m_TimeSpan_Duration;
168
169 m_pDatePickerCtrl_Start = NULL;
170 m_pTimeTextCtrl_Start = NULL;
171
172 m_pDatePickerCtrl_End = NULL;
173 m_pTimeTextCtrl_End = NULL;
174
175 m_pTimeTextCtrl_Duration = NULL;
176
177 // Do we allow the user to change the Automatic Save file?
178 m_bProjectAlreadySaved = bAlreadySaved;
179
180 ShuttleGui S(this, eIsCreating);
181 this->PopulateOrExchange(S);
182
183 // Set initial focus to "1" of "01h" in Duration NumericTextCtrl,
184 // instead of OK button (default).
185 m_pTimeTextCtrl_Duration->SetFocus();
186 m_pTimeTextCtrl_Duration->SetFieldFocus(3);
187
188 m_timer.SetOwner(this, TIMER_ID);
189 m_timer.Start(kSlowTimerInterval);
190}
191
193{
194}
195
196void TimerRecordDialog::OnTimer(wxTimerEvent& WXUNUSED(event))
197{
198 wxDateTime dateTime_UNow = wxDateTime::UNow();
199 if (m_DateTime_Start < dateTime_UNow) {
200 m_DateTime_Start = dateTime_UNow;
203 this->UpdateEnd(); // Keep Duration constant and update End for changed Start.
204 }
205}
206
207void TimerRecordDialog::OnDatePicker_Start(wxDateEvent& WXUNUSED(event))
208{
210 double dTime = m_pTimeTextCtrl_Start->GetValue();
211 long hr = (long)(dTime / 3600.0);
212 long min = (long)((dTime - (hr * 3600.0)) / 60.0);
213 long sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
214 m_DateTime_Start.SetHour(hr);
215 m_DateTime_Start.SetMinute(min);
216 m_DateTime_Start.SetSecond(sec);
217
218 // User might have had the dialog up for a while, or
219 // had a future day, set hour of day less than now's, then changed day to today.
220 wxTimerEvent dummyTimerEvent;
221 this->OnTimer(dummyTimerEvent);
222
223 // Always update End for changed Start, keeping Duration constant.
224 // Note that OnTimer sometimes calls UpdateEnd, so sometimes this is redundant,
225 // but OnTimer doesn't need to always call UpdateEnd, but we must here.
226 this->UpdateEnd();
227}
228
229void TimerRecordDialog::OnTimeText_Start(wxCommandEvent& WXUNUSED(event))
230{
231 //v NumericTextCtrl doesn't implement upper ranges, i.e.,
232 // if I tell it "024 h 060 m 060 s", then
233 // user increments the hours past 23, it rolls over to 0
234 // (although if you increment below 0, it stays at 0).
235 // So instead, set the max to 99 and just catch hours > 24 and fix the ctrls.
236 double dTime = m_pTimeTextCtrl_Start->GetValue();
237 long days = (long)(dTime / (24.0 * 3600.0));
238 if (days > 0) {
239 dTime -= (double)days * 24.0 * 3600.0;
240 m_DateTime_Start += wxTimeSpan::Days(days);
243 }
244
245 wxDateEvent dummyDateEvent;
246 this->OnDatePicker_Start(dummyDateEvent);
247}
248
249void TimerRecordDialog::OnDatePicker_End(wxDateEvent& WXUNUSED(event))
250{
252 double dTime = m_pTimeTextCtrl_End->GetValue();
253 long hr = (long)(dTime / 3600.0);
254 long min = (long)((dTime - (hr * 3600.0)) / 60.0);
255 long sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
256 m_DateTime_End.SetHour(hr);
257 m_DateTime_End.SetMinute(min);
258 m_DateTime_End.SetSecond(sec);
259
260 // DatePickerCtrls use SetRange to make sure End is never less than Start, but
261 // need to implement it for the TimeTextCtrls.
266 }
267
268 this->UpdateDuration(); // Keep Start constant and update Duration for changed End.
269}
270
271void TimerRecordDialog::OnTimeText_End(wxCommandEvent& WXUNUSED(event))
272{
273 //v NumericTextCtrl doesn't implement upper ranges, i.e.,
274 // if I tell it "024 h 060 m 060 s", then
275 // user increments the hours past 23, it rolls over to 0
276 // (although if you increment below 0, it stays at 0).
277 // So instead, set the max to 99 and just catch hours > 24 and fix the ctrls.
278 double dTime = m_pTimeTextCtrl_End->GetValue();
279 long days = (long)(dTime / (24.0 * 3600.0));
280 if (days > 0) {
281 dTime -= (double)days * 24.0 * 3600.0;
282 m_DateTime_End += wxTimeSpan::Days(days);
285 }
286
287 wxDateEvent dummyDateEvent;
288 this->OnDatePicker_End(dummyDateEvent);
289}
290
291void TimerRecordDialog::OnTimeText_Duration(wxCommandEvent& WXUNUSED(event))
292{
293 double dTime = m_pTimeTextCtrl_Duration->GetValue();
294 long hr = (long)(dTime / 3600.0);
295 long min = (long)((dTime - (hr * 3600.0)) / 60.0);
296 long sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
297 m_TimeSpan_Duration = wxTimeSpan(hr, min, sec); //v milliseconds?
298
299 this->UpdateEnd(); // Keep Start constant and update End for changed Duration.
300}
301
302// New events for timer recording automation
303void TimerRecordDialog::OnAutoSavePathButton_Click(wxCommandEvent& WXUNUSED(event))
304{
305 auto &projectFileIO = ProjectFileIO::Get(mProject);
306
307 wxString fName = SelectFile(FileNames::Operation::Export,
308 XO("Save Timer Recording As"),
309 m_fnAutoSaveFile.GetPath(),
310 m_fnAutoSaveFile.GetFullName(),
311 wxT("aup3"),
313 wxFD_SAVE | wxRESIZE_BORDER,
314 this);
315
316 if (fName.empty())
317 return;
318
319 // If project already exists then abort - we do not allow users to overwrite an existing project
320 // unless it is the current project.
321 if (wxFileExists(fName) && (projectFileIO.GetFileName() != fName)) {
323 nullptr,
324 XO("The selected file name could not be used\nfor Timer Recording because it \
325would overwrite another project.\nPlease try again and select an original name."),
326 XO("Error Saving Timer Recording Project"),
327 wxOK|wxICON_ERROR );
328 m.ShowModal();
329 return;
330 }
331
332 // Set this boolean to false so we now do a SaveAs at the end of the recording
333 // unless we're saving the current project.
334 m_bProjectAlreadySaved = projectFileIO.GetFileName() == fName? true : false;
335
336 m_fnAutoSaveFile = fName;
337 m_fnAutoSaveFile.SetExt(wxT("aup3"));
338 this->UpdateTextBoxControls();
339}
340
341void TimerRecordDialog::OnAutoExportPathButton_Click(wxCommandEvent& WXUNUSED(event))
342{
343 Exporter eExporter{ mProject };
344
345 // Call the Exporter to set the options required
346 if (eExporter.SetAutoExportOptions()) {
347 // Populate the options so that we can destroy this instance of the Exporter
348 m_fnAutoExportFile = eExporter.GetAutoExportFileName();
349 m_iAutoExportFormat = eExporter.GetAutoExportFormat();
350 m_iAutoExportSubFormat = eExporter.GetAutoExportSubFormat();
351 m_iAutoExportFilterIndex = eExporter.GetAutoExportFilterIndex();
352
353 // Update the text controls
354 this->UpdateTextBoxControls();
355 }
356}
357
358void TimerRecordDialog::OnAutoSaveCheckBox_Change(wxCommandEvent& WXUNUSED(event)) {
360}
361
362void TimerRecordDialog::OnAutoExportCheckBox_Change(wxCommandEvent& WXUNUSED(event)) {
364}
365
366void TimerRecordDialog::OnHelpButtonClick(wxCommandEvent& WXUNUSED(event))
367{
368 HelpSystem::ShowHelp(this, L"Timer_Record", true);
369}
370
371void TimerRecordDialog::OnOK(wxCommandEvent& WXUNUSED(event))
372{
374 if (!m_TimeSpan_Duration.IsPositive())
375 {
377 XO("Duration is zero. Nothing will be recorded."),
378 XO("Error in Duration"),
379 wxICON_EXCLAMATION | wxOK);
380 return;
381 }
382
383 // Validate that we have a Save and/or Export path setup if the appropriate check box is ticked
384 wxString sTemp = m_fnAutoSaveFile.GetFullPath();
385 if (m_pTimerAutoSaveCheckBoxCtrl->IsChecked()) {
386 if (!m_fnAutoSaveFile.IsOk() || m_fnAutoSaveFile.IsDir()) {
388 XO("Automatic Save path is invalid."),
389 XO("Error in Automatic Save"),
390 wxICON_EXCLAMATION | wxOK);
391 return;
392 }
393 }
394 if (m_pTimerAutoExportCheckBoxCtrl->IsChecked()) {
395 if (!m_fnAutoExportFile.IsOk() || m_fnAutoExportFile.IsDir()) {
397 XO("Automatic Export path is invalid."),
398 XO("Error in Automatic Export"),
399 wxICON_EXCLAMATION | wxOK);
400 return;
401 }
402 }
403
404 // MY: Estimate here if we have enough disk space to
405 // complete this Timer Recording.
406 // If we don't think there is enough space then ask the user
407 // if they want to continue.
408 // We don't stop the user from starting the recording
409 // as its possible that they plan to free up some
410 // space before the recording begins
411 auto &projectManager = ProjectManager::Get( mProject );
412
413 // How many minutes do we have left on the disc?
414 int iMinsLeft = projectManager.GetEstimatedRecordingMinsLeftOnDisk();
415
416 // How many minutes will this recording require?
417 int iMinsRecording = m_TimeSpan_Duration.GetMinutes();
418
419 // Do we have enough space?
420 if (iMinsRecording >= iMinsLeft) {
421
422 // Format the strings
423 auto sRemainingTime = projectManager.GetHoursMinsString(iMinsLeft);
424 auto sPlannedTime = projectManager.GetHoursMinsString(iMinsRecording);
425
426 // Create the message string
427 auto sMessage = XO(
428"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")
429 .Format( sPlannedTime, sRemainingTime );
430
431 AudacityMessageDialog dlgMessage(
432 nullptr,
433 sMessage,
434 XO("Timer Recording Disk Space Warning"),
435 wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
436 if (dlgMessage.ShowModal() != wxID_YES ) {
437 // User decided not to continue - bail out!
438 return;
439 }
440 }
441
442 m_timer.Stop(); // Don't need to keep updating m_DateTime_Start to prevent backdating.
443 this->EndModal(wxID_OK);
444 wxLongLong duration = m_TimeSpan_Duration.GetSeconds();
445 // this will assert if the duration won't fit in a long
446 gPrefs->Write(wxT("/TimerRecord/LastDuration"), duration.ToLong());
447 gPrefs->Flush();
448}
449
450void TimerRecordDialog::EnableDisableAutoControls(bool bEnable, int iControlGoup) {
451
452 if (iControlGoup == CONTROL_GROUP_EXPORT) {
453 m_pTimerExportPathTextCtrl->Enable( bEnable );
454 m_pTimerExportPathButtonCtrl->Enable( bEnable);
455 } else if (iControlGoup == CONTROL_GROUP_SAVE) {
456 m_pTimerSavePathTextCtrl->Enable( bEnable);
457 m_pTimerSavePathButtonCtrl->Enable(bEnable );
458 }
459
460 // Enable or disable the Choice box - if there is no Save or Export then this will be disabled
461 if (m_pTimerAutoSaveCheckBoxCtrl->GetValue() || m_pTimerAutoExportCheckBoxCtrl->GetValue()) {
463 } else {
466 }
467}
468
470 // Will update the text box controls
471 m_pTimerSavePathTextCtrl->SetValue(m_fnAutoSaveFile.GetFullPath());
472 m_pTimerExportPathTextCtrl->SetValue(m_fnAutoExportFile.GetFullPath());
473
474 // MY: Ensure we still display "Current Project" if this has already been saved
476 m_pTimerSavePathTextCtrl->SetValue(_("Current Project"));
477 }
478}
479
483{
484 auto updateResult = ProgressResult::Success;
485
486 const auto gAudioIO = AudioIO::Get();
487 gAudioIO->DelayActions(true);
488 {
489 auto cleanup = finally([gAudioIO]{ gAudioIO->DelayActions(false); });
490
491 if (m_DateTime_Start > wxDateTime::UNow())
492 updateResult = this->WaitForStart();
493
494 if (updateResult != ProgressResult::Success) {
495 // Don't proceed, but don't treat it as canceled recording. User just canceled waiting.
497 } else {
498 // Record for specified time.
500 bool bIsRecording = true;
501
502 auto sPostAction = Verbatim(
503 m_pTimerAfterCompleteChoiceCtrl->GetStringSelection() );
504
505 // Two column layout.
507 {
508 XO("Recording start:") ,
509 XO("Duration:") ,
510 XO("Recording end:") ,
511 {} ,
512 XO("Automatic Save enabled:") ,
513 XO("Automatic Export enabled:") ,
514 XO("Action after Timer Recording:") ,
515 },
516 {
518 Verbatim( m_TimeSpan_Duration.Format() ),
520 {} ,
521 (m_bAutoSaveEnabled ? XO("Yes") : XO("No")) ,
522 (m_bAutoExportEnabled ? XO("Yes") : XO("No")) ,
523 sPostAction ,
524 }
525 };
526
528 progress(m_TimeSpan_Duration.GetMilliseconds().GetValue(),
529 XO("Audacity Timer Record Progress"),
530 columns,
532
533 // Make sure that start and end time are updated, so we always get the full
534 // duration, even if there's some delay getting here.
535 wxTimerEvent dummyTimerEvent;
536 this->OnTimer(dummyTimerEvent);
537
538 // Loop for progress display during recording.
539 while (bIsRecording && (updateResult == ProgressResult::Success)) {
540 updateResult = progress.UpdateProgress();
541 using namespace std::chrono;
542 std::this_thread::sleep_for(kTimerInterval);
543 bIsRecording = (wxDateTime::UNow() <= m_DateTime_End); // Call UNow() again for extra accuracy...
544 }
545 }
546 }
547
548 // Must do this AFTER the timer project dialog has been deleted to ensure the application
549 // responds to the AUDIOIO events...see not about bug #334 in the ProgressDialog constructor.
551
552 // Let the caller handle cancellation or failure from recording progress.
553 if (updateResult == ProgressResult::Cancelled || updateResult == ProgressResult::Failed)
555
556 return ExecutePostRecordActions((updateResult == ProgressResult::Stopped));
557}
558
560 // MY: We no longer automatically (and silently) call ->Save() when the
561 // timer recording is completed. We can now Save and/or Export depending
562 // on the options selected by the user.
563 // Once completed, we can also close Audacity, restart the system or
564 // shutdown the system.
565 // If there was any error with the auto save or export then we will not do
566 // the actions requested and instead present an error mesasge to the user.
567 // Finally, if there is no post-record action selected then we output
568 // a dialog detailing what has been carried out instead.
569
570 bool bSaveOK = false;
571 bool bExportOK = false;
572 int iPostRecordAction = m_pTimerAfterCompleteChoiceCtrl->GetSelection();
573 int iOverriddenAction = iPostRecordAction;
574 bool bErrorOverride = false;
575
576 // Do Automatic Save?
577 if (m_bAutoSaveEnabled) {
578
579 auto &projectFileManager = ProjectFileManager::Get( mProject );
580 // MY: If this project has already been saved then simply execute a Save here
582 bSaveOK = projectFileManager.Save();
583 } else {
584 bSaveOK = projectFileManager.SaveFromTimerRecording(m_fnAutoSaveFile);
585 }
586 }
587
588 // Do Automatic Export?
590 Exporter e{ mProject };
591 bExportOK = e.ProcessFromTimerRecording(
592 false, 0.0, TrackList::Get( mProject ).GetEndTime(),
595 }
596
597 // Check if we need to override the post recording action
598 bErrorOverride = ((m_bAutoSaveEnabled && !bSaveOK) || (m_bAutoExportEnabled && !bExportOK));
599 if (bErrorOverride || bWasStopped) {
600 iPostRecordAction = POST_TIMER_RECORD_NOTHING;
601 }
602
603 if (iPostRecordAction == POST_TIMER_RECORD_NOTHING) {
604 // If there is no post-record action then we can show a message indicating what has been done
605
606 auto sMessage = (bWasStopped ? XO("Timer Recording stopped.") :
607 XO("Timer Recording completed."));
608
609 if (m_bAutoSaveEnabled) {
610 if (bSaveOK) {
611 sMessage = XO("%s\n\nRecording saved: %s").Format(
612 sMessage, m_fnAutoSaveFile.GetFullPath());
613 } else {
614 sMessage = XO("%s\n\nError saving recording.").Format( sMessage );
615 }
616 }
618 if (bExportOK) {
619 sMessage = XO("%s\n\nRecording exported: %s").Format(
620 sMessage, m_fnAutoExportFile.GetFullPath());
621 } else {
622 sMessage = XO("%s\n\nError exporting recording.").Format( sMessage );
623 }
624 }
625
626 if (bErrorOverride) {
627
628 if ((iOverriddenAction != iPostRecordAction) &&
629 (iOverriddenAction != POST_TIMER_RECORD_NOTHING)) {
630 // Inform the user that we have overridden the selected action
631 sMessage = XO("%s\n\n'%s' has been canceled due to the error(s) noted above.").Format(
632 sMessage,
633 m_pTimerAfterCompleteChoiceCtrl->GetString(iOverriddenAction));
634 }
635
636 // Show Error Message Box
638 sMessage,
639 XO("Error"),
640 wxICON_EXCLAMATION | wxOK);
641 } else {
642
643 if (bWasStopped && (iOverriddenAction != POST_TIMER_RECORD_NOTHING)) {
644 sMessage = XO("%s\n\n'%s' has been canceled as the recording was stopped.").Format(
645 sMessage,
646 m_pTimerAfterCompleteChoiceCtrl->GetString(iOverriddenAction));
647 }
648
650 sMessage,
651 XO("Timer Recording"),
652 wxICON_INFORMATION | wxOK);
653 }
654 }
655
656 // MY: Lets do some actions that only apply to Exit/Restart/Shutdown
657 if (iPostRecordAction >= POST_TIMER_RECORD_CLOSE) {
658 do {
659
660 // Set the flags as appropriate based on what we have done
661 wxUint32 eActionFlags = TR_ACTION_NOTHING;
662 if (m_bAutoSaveEnabled && bSaveOK) {
663 eActionFlags |= TR_ACTION_SAVED;
664 }
665 if (m_bAutoExportEnabled && bExportOK) {
666 eActionFlags |= TR_ACTION_EXPORTED;
667 }
668
669 // Lets show a warning dialog telling the user what is about to happen.
670 // If the user no longer wants to carry out this action then they can click
671 // Cancel and we will do POST_TIMER_RECORD_NOTHING instead.
672 auto iDelayOutcome = PreActionDelay(iPostRecordAction, (TimerRecordCompletedActions)eActionFlags);
673 if (iDelayOutcome != ProgressResult::Success) {
674 // Cancel the action!
675 iPostRecordAction = POST_TIMER_RECORD_NOTHING;
676 break;
677 }
678 } while (false);
679 }
680
681 // Return the action as required
682 return iPostRecordAction;
683}
684
686{
687#if defined(__WXMSW__)
688 // On Windows, wxWidgets uses the system date control and it displays the
689 // date based on the Windows locale selected by the user. But, wxDateTime
690 // using the strftime function to return the formatted date. Since the
691 // default locale for the Windows CRT environment is "C", the dates come
692 // back in a different format.
693 //
694 // So, we make direct Windows calls to format the date like it the date
695 // control.
696 //
697 // (Most of this taken from src/msw/datectrl.cpp)
698
699 const wxDateTime::Tm tm(dt.GetTm());
700 SYSTEMTIME st;
701 wxString s;
702 int len;
703
704 st.wYear = (WXWORD)tm.year;
705 st.wMonth = (WXWORD)(tm.mon - wxDateTime::Jan + 1);
706 st.wDay = tm.mday;
707 st.wDayOfWeek = st.wMinute = st.wSecond = st.wMilliseconds = 0;
708
709 len = ::GetDateFormat(LOCALE_USER_DEFAULT,
710 DATE_SHORTDATE,
711 &st,
712 NULL,
713 NULL,
714 0);
715 if (len > 0) {
716 len = ::GetDateFormat(LOCALE_USER_DEFAULT,
717 DATE_SHORTDATE,
718 &st,
719 NULL,
720 wxStringBuffer(s, len),
721 len);
722 if (len > 0) {
723 s += wxT(" ") + dt.FormatTime();
724 return Verbatim( s );
725 }
726 }
727#endif
728
729 // Use default formatting
730wxPrintf(wxT("%s\n"), dt.Format());
731 return Verbatim( dt.FormatDate() + wxT(" ") + dt.FormatTime() );
732}
733
735 wxWindow *wParent, const int iID,
736 const TranslatableString &sCaption, const TranslatableString &sValue)
737{
738 wxTextCtrlWrapper * pTextCtrl;
739 wxASSERT(wParent); // to justify safenew
740 pTextCtrl = safenew wxTextCtrlWrapper(wParent, iID, sValue.Translation());
741 pTextCtrl->SetName(sCaption.Translation());
742 return pTextCtrl;
743}
744
746{
747 bool bAutoSave = gPrefs->ReadBool("/TimerRecord/AutoSave", false);
748 bool bAutoExport = gPrefs->ReadBool("/TimerRecord/AutoExport", false);
749 int iPostTimerRecordAction = gPrefs->ReadLong("/TimerRecord/PostAction", 0);
750
751 S.SetBorder(5);
753 /* i18n-hint a format string for hours, minutes, and seconds */
754 auto strFormat = XO("099 h 060 m 060 s");
755 /* i18n-hint a format string for days, hours, minutes, and seconds */
756 auto strFormat1 = XO("099 days 024 h 060 m 060 s");
757
758 S.StartMultiColumn(2, wxCENTER);
759 {
760 S.StartVerticalLay(true);
761 {
762 /* i18n-hint: This string is used to configure the controls for times when the recording is
763 * started and stopped. As such it is important that only the alphabetic parts of the string
764 * are translated, with the numbers left exactly as they are.
765 * The 'h' indicates the first number displayed is hours, the 'm' indicates the second number
766 * displayed is minutes, and the 's' indicates that the third number displayed is seconds.
767 */
768 S.StartStatic(XO("Start Date and Time"), true);
769 {
771 safenew wxDatePickerCtrl(S.GetParent(), // wxWindow *parent,
772 ID_DATEPICKER_START, // wxWindowID id,
773 m_DateTime_Start); // const wxDateTime& dt = wxDefaultDateTime,
774 // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDP_DEFAULT | wxDP_SHOWCENTURY, const wxValidator& validator = wxDefaultValidator, const wxString& name = "datectrl")
775 m_pDatePickerCtrl_Start->SetRange(wxDateTime::Today(), wxInvalidDateTime); // No backdating.
776#if wxUSE_ACCESSIBILITY
777 m_pDatePickerCtrl_Start->SetAccessible( safenew DatePickerCtrlAx(m_pDatePickerCtrl_Start));
778#endif
779 S.Name(XO("Start Date"))
780 .AddWindow(m_pDatePickerCtrl_Start);
781
784 {}, 0, 44100,
785 Options{}
786 .MenuEnabled(false)
787 .Format(strFormat)
789 S.Name(XO("Start Time"))
790 .AddWindow(m_pTimeTextCtrl_Start);
791 }
792 S.EndStatic();
793
794 S.StartStatic(XO("End Date and Time"), true);
795 {
797 safenew wxDatePickerCtrl(S.GetParent(), // wxWindow *parent,
798 ID_DATEPICKER_END, // wxWindowID id,
799 m_DateTime_End); // const wxDateTime& dt = wxDefaultDateTime,
800 // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize,
801 // long style = wxDP_DEFAULT | wxDP_SHOWCENTURY,
802 // const wxValidator& validator = wxDefaultValidator,
803 // const wxString& name = "datectrl")
804 m_pDatePickerCtrl_End->SetRange(m_DateTime_Start, wxInvalidDateTime); // No backdating.
805#if wxUSE_ACCESSIBILITY
806 m_pDatePickerCtrl_End->SetAccessible( safenew DatePickerCtrlAx(m_pDatePickerCtrl_End));
807#endif
808 S.Name(XO("End Date"))
809 .AddWindow(m_pDatePickerCtrl_End);
810
813 {}, 0, 44100,
814 Options{}
815 .MenuEnabled(false)
816 .Format(strFormat)
818 S.Name(XO("End Time"))
819 .AddWindow(m_pTimeTextCtrl_End);
820 }
821 S.EndStatic();
822
823 S.StartStatic(XO("Duration"), true);
824 {
827 {}, 0, 44100,
828 Options{}
829 .MenuEnabled(false)
830 .Format(strFormat1)
831 .Value(true, m_TimeSpan_Duration.GetSeconds().ToDouble()));
832 /* i18n-hint: This string is used to configure the controls which shows the recording
833 * duration. As such it is important that only the alphabetic parts of the string
834 * are translated, with the numbers left exactly as they are.
835 * The string 'days' indicates that the first number in the control will be the number of days,
836 * then the 'h' indicates the second number displayed is hours, the 'm' indicates the third
837 * number displayed is minutes, and the 's' indicates that the fourth number displayed is
838 * seconds.
839 */
840 S.Name(XO("Duration"))
841 .AddWindow(m_pTimeTextCtrl_Duration);
842 }
843 S.EndStatic();
844 }
845 S.EndVerticalLay();
846
847 S.StartVerticalLay(true);
848 {
849 S.StartStatic(XO("Automatic Save"), true);
850 {
851 // If checked, the project will be saved when the recording is completed
852 m_pTimerAutoSaveCheckBoxCtrl = S.Id(ID_AUTOSAVE_CHECKBOX).AddCheckBox(XXO("Enable &Automatic Save?"),
853 bAutoSave);
854 S.StartMultiColumn(3, wxEXPAND);
855 {
856 TranslatableString sInitialValue;
857 auto sSaveValue = ProjectFileIO::Get(mProject).GetFileName();
858 if (!sSaveValue.empty()) {
859 m_fnAutoSaveFile.Assign(sSaveValue);
860 sInitialValue = XO("Current Project");
861 }
862 S.AddPrompt(XXO("Save Project As:"));
864 S.GetParent(), ID_AUTOSAVEPATH_TEXT,
865 XO("Save Project As:"), sInitialValue);
867 S.AddWindow(m_pTimerSavePathTextCtrl);
868 m_pTimerSavePathButtonCtrl = S.Id(ID_AUTOSAVEPATH_BUTTON).AddButton(XXO("Select..."));
869 }
870 S.EndMultiColumn();
871 }
872 S.EndStatic();
873
874 S.StartStatic(XO("Automatic Export"), true);
875 {
876 m_pTimerAutoExportCheckBoxCtrl = S.Id(ID_AUTOEXPORT_CHECKBOX).AddCheckBox(XXO("Enable Automatic &Export?"), bAutoExport);
877 S.StartMultiColumn(3, wxEXPAND);
878 {
879 S.AddPrompt(XXO("Export Project As:"));
881 S.GetParent(), ID_AUTOEXPORTPATH_TEXT,
882 XO("Export Project As:"), {});
884 S.AddWindow(m_pTimerExportPathTextCtrl);
885 m_pTimerExportPathButtonCtrl = S.Id(ID_AUTOEXPORTPATH_BUTTON).AddButton(XXO("Select..."));
886 }
887 S.EndMultiColumn();
888 }
889 S.EndStatic();
890
891 S.StartStatic(XO("Options"), true);
892 {
893
894 S.StartMultiColumn(1, wxEXPAND);
895 {
896 S.SetStretchyCol( 0 );
897 m_pTimerAfterCompleteChoiceCtrl = S.AddChoice(XXO("After Recording completes:"),
898 {
899 XO("Do nothing") ,
900 XO("Exit Audacity") ,
901 #ifdef __WINDOWS__
902 XO("Restart system") ,
903 XO("Shutdown system") ,
904 #endif
905 },
906 iPostTimerRecordAction
907 );
908 }
909 S.EndMultiColumn();
910 }
911 S.EndStatic();
912
913 }
914 S.EndVerticalLay();
915 }
916 S.EndMultiColumn();
917
918 // MY: Added the help button here
919 S.AddStandardButtons(eOkButton | eCancelButton | eHelpButton);
920
921 Layout();
922 Fit();
923 SetMinSize(GetSize());
924 Center();
925
928}
929
931{
932 double dTime;
933 long hr;
934 long min;
935 long sec;
936
939 hr = (long)(dTime / 3600.0);
940 min = (long)((dTime - (hr * 3600.0)) / 60.0);
941 sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
942 m_DateTime_Start.SetHour(hr);
943 m_DateTime_Start.SetMinute(min);
944 m_DateTime_Start.SetSecond(sec);
945
947 dTime = m_pTimeTextCtrl_End->GetValue();
948 hr = (long)(dTime / 3600.0);
949 min = (long)((dTime - (hr * 3600.0)) / 60.0);
950 sec = (long)(dTime - (hr * 3600.0) - (min * 60.0));
951 m_DateTime_End.SetHour(hr);
952 m_DateTime_End.SetMinute(min);
953 m_DateTime_End.SetSecond(sec);
954
956
957 // Pull the settings from the auto save/export controls and write to the pref file
960
961 // MY: Obtain the index from the choice control so we can save to the prefs file
962 int iPostRecordAction = m_pTimerAfterCompleteChoiceCtrl->GetSelection();
963
964 // Save the options back to the prefs file
965 gPrefs->Write("/TimerRecord/AutoSave", m_bAutoSaveEnabled);
966 gPrefs->Write("/TimerRecord/AutoExport", m_bAutoExportEnabled);
967 gPrefs->Write("/TimerRecord/PostAction", iPostRecordAction);
968
969 return true;
970}
971
972// Update m_TimeSpan_Duration and ctrl based on m_DateTime_Start and m_DateTime_End.
974{
976 m_pTimeTextCtrl_Duration->SetValue(m_TimeSpan_Duration.GetSeconds().ToDouble());
977}
978
979// Update m_DateTime_End and ctrls based on m_DateTime_Start and m_TimeSpan_Duration.
981{
982 //v Use remaining disk -> record time calcs from AudacityProject::OnTimer to set range?
984 //wxLogDebug( "Time start %s end %s",
985 // m_DateTime_Start.FormatISOCombined(' '),
986 // m_DateTime_End.FormatISOCombined(' ') );
987
988 // Disable the range limitation (to fix Bug 1749 and 1978)
989 // Otherwise SetVallue asserts when going back in time.
990 m_pDatePickerCtrl_End->SetRange(wxInvalidDateTime, wxInvalidDateTime);
992 // Re-enable range limitation to constrain user input.
993 m_pDatePickerCtrl_End->SetRange(m_DateTime_Start, wxInvalidDateTime); // No backdating.
994 m_pDatePickerCtrl_End->Refresh();
996}
997
999{
1000 // MY: The Waiting For Start dialog now shows what actions will occur after recording has completed
1001 auto sPostAction = Verbatim(
1002 m_pTimerAfterCompleteChoiceCtrl->GetStringSelection() );
1003
1004 // Two column layout.
1006 {
1007 XO("Waiting to start recording at:") ,
1008 XO("Recording duration:") ,
1009 XO("Scheduled to stop at:") ,
1010 {} ,
1011 XO("Automatic Save enabled:") ,
1012 XO("Automatic Export enabled:") ,
1013 XO("Action after Timer Recording:") ,
1014 },
1015 {
1017 Verbatim( m_TimeSpan_Duration.Format() ),
1019 {} ,
1020 (m_bAutoSaveEnabled ? XO("Yes") : XO("No")) ,
1021 (m_bAutoExportEnabled ? XO("Yes") : XO("No")) ,
1022 sPostAction ,
1023 },
1024 };
1025
1026 wxDateTime startWait_DateTime = wxDateTime::UNow();
1027 wxTimeSpan waitDuration = m_DateTime_Start - startWait_DateTime;
1028 TimerProgressDialog progress(waitDuration.GetMilliseconds().GetValue(),
1029 XO("Audacity Timer Record - Waiting for Start"),
1030 columns,
1032 /* i18n-hint: "in" means after a duration of time,
1033 which is shown below this string */
1034 XO("Recording will commence in:"));
1035
1036 auto updateResult = ProgressResult::Success;
1037 bool bIsRecording = false;
1038 while (updateResult == ProgressResult::Success && !bIsRecording)
1039 {
1040 updateResult = progress.UpdateProgress();
1041 using namespace std::chrono;
1042 std::this_thread::sleep_for(kTimerInterval);
1043 bIsRecording = (m_DateTime_Start <= wxDateTime::UNow());
1044 }
1045 return updateResult;
1046}
1047
1049{
1051 ->GetString(iActionIndex) );
1052
1053 /* i18n-hint: %s is one of "Do nothing", "Exit Audacity", "Restart system",
1054 or "Shutdown system", and
1055 "in" means after a duration of time, shown below this string */
1056 auto sCountdownLabel = XO("%s in:").Format( sAction );
1057
1058 // Two column layout.
1060 {
1061 XO("Timer Recording completed.") ,
1062 {} ,
1063 XO("Recording Saved:") ,
1064 XO("Recording Exported:") ,
1065 XO("Action after Timer Recording:") ,
1066 },
1067 {
1068 {} ,
1069 {} ,
1070 ((eCompletedActions & TR_ACTION_SAVED) ? XO("Yes") : XO("No")) ,
1071 ((eCompletedActions & TR_ACTION_EXPORTED) ? XO("Yes") : XO("No")) ,
1072 sAction ,
1073 },
1074 };
1075
1076
1077 wxDateTime dtNow = wxDateTime::UNow();
1078 wxTimeSpan tsWait = wxTimeSpan(0, 1, 0, 0);
1079 wxDateTime dtActionTime = dtNow.Add(tsWait);
1080
1081 TimerProgressDialog dlgAction(tsWait.GetMilliseconds().GetValue(),
1082 XO("Audacity Timer Record - Waiting"),
1083 columns,
1085 sCountdownLabel);
1086
1087 auto iUpdateResult = ProgressResult::Success;
1088 bool bIsTime = false;
1089 while (iUpdateResult == ProgressResult::Success && !bIsTime)
1090 {
1091 iUpdateResult = dlgAction.UpdateProgress();
1092 using namespace std::chrono;
1093 std::this_thread::sleep_for(kTimerInterval);
1094 bIsTime = (dtActionTime <= wxDateTime::UNow());
1095 }
1096 return iUpdateResult;
1097}
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
END_EVENT_TABLE()
int min(int a, int b)
EVT_BUTTON(wxID_NO, DependencyDialog::OnNo) EVT_BUTTON(wxID_YES
#define XXO(s)
Definition: Internat.h:44
#define XO(s)
Definition: Internat.h:31
#define _(s)
Definition: Internat.h:75
#define safenew
Definition: MemoryX.h:10
FileConfig * gPrefs
Definition: Prefs.cpp:71
@ pdlgConfirmStopCancel
@ pdlgHideStopButton
@ pdlgHideElapsedTime
@ pdlgHideCancelButton
FilePath SelectFile(FileNames::Operation op, const TranslatableString &message, const FilePath &default_path, const FilePath &default_filename, const FileExtension &default_extension, const FileTypes &fileTypes, int flags, wxWindow *parent)
Definition: SelectFile.cpp:17
@ eIsCreating
Definition: ShuttleGui.h:39
@ eOkButton
Definition: ShuttleGui.h:600
@ eCancelButton
Definition: ShuttleGui.h:601
@ eHelpButton
Definition: ShuttleGui.h:604
const int kSlowTimerInterval
@ ID_AUTOEXPORTPATH_BUTTON
@ ID_AUTOSAVEPATH_TEXT
@ ID_DATEPICKER_START
@ ID_AUTOSAVEPATH_BUTTON
@ ID_AUTOSAVE_CHECKBOX
@ ID_AUTOEXPORTPATH_TEXT
@ ID_TIMETEXT_DURATION
@ ID_TIMETEXT_END
@ ID_DATEPICKER_END
@ ID_TIMETEXT_START
@ ID_AUTOEXPORT_CHECKBOX
constexpr auto kTimerInterval
@ CONTROL_GROUP_SAVE
@ CONTROL_GROUP_EXPORT
#define TIMER_ID
static double wxDateTime_to_AudacityTime(wxDateTime &dateTime)
TimerRecordCompletedActions
@ TR_ACTION_NOTHING
@ TR_ACTION_EXPORTED
@ TR_ACTION_SAVED
@ POST_TIMER_RECORD_CLOSE
@ POST_TIMER_RECORD_CANCEL
@ POST_TIMER_RECORD_CANCEL_WAIT
@ POST_TIMER_RECORD_NOTHING
#define S(N)
Definition: ToChars.cpp:64
declares abstract base class Track, TrackList, and iterators over TrackList
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
Wrap wxMessageDialog so that caption IS translatable.
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
static AudioIO * Get()
Definition: AudioIO.cpp:140
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
FILES_API const FileType AudacityProjects
Definition: FileNames.h:72
static void ShowHelp(wxWindow *parent, const FilePath &localFileName, const URLString &remoteURL, bool bModal=false, bool alwaysDefaultBrowser=false)
Definition: HelpSystem.cpp:237
void SetValue(double newValue)
std::vector< MessageColumn > MessageTable
void Stop(bool stopStream=true)
static ProjectAudioManager & Get(AudacityProject &project)
void OnRecord(bool altAppearance)
static ProjectFileIO & Get(AudacityProject &project)
const FilePath & GetFileName() const
static ProjectFileManager & Get(AudacityProject &project)
static ProjectManager & Get(AudacityProject &project)
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:631
ProgressResult UpdateProgress()
Dialog for Timer Record, i.e., timed or long recording.
void OnAutoSaveCheckBox_Change(wxCommandEvent &event)
wxDatePickerCtrl * m_pDatePickerCtrl_Start
NumericTextCtrl * m_pTimeTextCtrl_Duration
void OnHelpButtonClick(wxCommandEvent &event)
void OnTimeText_Duration(wxCommandEvent &event)
NumericTextCtrl * m_pTimeTextCtrl_End
AudacityProject & mProject
TranslatableString GetDisplayDate(wxDateTime &dt)
wxButton * m_pTimerSavePathButtonCtrl
void EnableDisableAutoControls(bool bEnable, int iControlGoup)
wxCheckBox * m_pTimerAutoSaveCheckBoxCtrl
int ExecutePostRecordActions(bool bWasStopped)
void OnOK(wxCommandEvent &event)
void PopulateOrExchange(ShuttleGui &S)
NumericTextCtrl * m_pTimeTextCtrl_Start
wxTextCtrlWrapper * m_pTimerExportPathTextCtrl
wxDateTime m_DateTime_Start
void OnAutoExportPathButton_Click(wxCommandEvent &event)
void OnAutoExportCheckBox_Change(wxCommandEvent &event)
bool TransferDataFromWindow() override
wxChoice * m_pTimerAfterCompleteChoiceCtrl
wxFileName m_fnAutoSaveFile
void OnDatePicker_End(wxDateEvent &event)
ProgressResult WaitForStart()
wxTextCtrlWrapper * NewPathControl(wxWindow *wParent, const int iID, const TranslatableString &sCaption, const TranslatableString &sValue)
wxCheckBox * m_pTimerAutoExportCheckBoxCtrl
wxFileName m_fnAutoExportFile
void OnTimeText_Start(wxCommandEvent &event)
wxTimeSpan m_TimeSpan_Duration
wxDatePickerCtrl * m_pDatePickerCtrl_End
void OnAutoSavePathButton_Click(wxCommandEvent &event)
wxTextCtrlWrapper * m_pTimerSavePathTextCtrl
void OnTimeText_End(wxCommandEvent &event)
wxButton * m_pTimerExportPathButtonCtrl
void OnTimer(wxTimerEvent &event)
int RunWaitDialog()
Runs the wait for start dialog. Returns false if the user clicks stop.
void OnDatePicker_Start(wxDateEvent &event)
ProgressResult PreActionDelay(int iActionIndex, TimerRecordCompletedActions eCompletedActions)
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:467
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
void SetReadOnly(bool readonly=true)
ProgressResult
Definition: BasicUI.h:145
std::vector< CommandFlagOptions > & Options()
Definition: Menus.cpp:535