diff --git a/src/Menus.cpp b/src/Menus.cpp index cec678249..3642801c9 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -6316,8 +6316,7 @@ void AudacityProject::OnTimerRecord() // it is now safer to disable Timer Recording when there is more than // one open project. if (GetOpenProjectCount() > 1) { - wxMessageBox(_("Timer Recording cannot be used with more than one open project.\n\n\ - Please close any additional projects and try again."), + wxMessageBox(_("Timer Recording cannot be used with more than one open project.\n\nPlease close any additional projects and try again."), _("Timer Recording"), wxICON_INFORMATION | wxOK); return; diff --git a/src/Project.cpp b/src/Project.cpp index 492317772..e5466976c 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -5254,3 +5254,36 @@ bool AudacityProject::ProjectHasTracks() { bool bHasTracks = (iter2.First() != NULL); return bHasTracks; } + +// MY: This routine will give an estimate of how many +// minutes of recording time we have available. +// This is called from TimerRecordDialog::OnOK() to allow +// the user to resolve a potential disk space issue before +// Timer Recording starts. +// The calculations made are based on the user's current +// preferences. +int AudacityProject::GetEstimatedRecordingMinsLeftOnDisk() { + + // Obtain the current settings + sampleFormat oCaptureFormat = (sampleFormat) + gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample); + long lCaptureChannels; + gPrefs->Read(wxT("/AudioIO/RecordChannels"), &lCaptureChannels, 2L); + + // Find out how much free space we have on disk + wxLongLong lFreeSpace = mDirManager->GetFreeDiskSpace(); + if (lFreeSpace < 0) { + return 0; + } + + // Calculate the remaining time + double dRecTime = 0.0; + dRecTime = lFreeSpace.GetHi() * 4294967296.0 + lFreeSpace.GetLo(); + dRecTime /= SAMPLE_SIZE_DISK(oCaptureFormat); + dRecTime /= lCaptureChannels; + dRecTime /= GetRate(); + + // Convert to minutes before returning + int iRecMins = (int)(dRecTime / 60.0); + return iRecMins; +} diff --git a/src/Project.h b/src/Project.h index 4ec80706e..a3fcbe720 100644 --- a/src/Project.h +++ b/src/Project.h @@ -283,6 +283,9 @@ class AUDACITY_DLL_API AudacityProject final : public wxFrame, bool ProjectHasTracks(); + // Routine to estimate how many minutes of recording time are left on disk + int GetEstimatedRecordingMinsLeftOnDisk(); + #include "Menus.h" CommandManager *GetCommandManager() { return &mCommandManager; } diff --git a/src/TimerRecordDialog.cpp b/src/TimerRecordDialog.cpp index 0f1546ce9..6e2d172cb 100644 --- a/src/TimerRecordDialog.cpp +++ b/src/TimerRecordDialog.cpp @@ -371,6 +371,33 @@ void TimerRecordDialog::OnOK(wxCommandEvent& WXUNUSED(event)) } } + // MY: Estimate here if we have enough disk space to + // complete this Timer Recording. + // If we dont think there is enough space then ask the user + // if they want to continue. + // We don't stop the user from starting the recording + // as its possible that they plan to free up some + // space before the recording begins + AudacityProject* pProject = GetActiveProject(); + + // How many minutes do we have left on the recording? + int iMinsLeft = pProject->GetEstimatedRecordingMinsLeftOnDisk(); + + // How many minutes will this recording require? + int iMinsRecording = m_TimeSpan_Duration.GetMinutes(); + + // Do we have enough space? + if (iMinsRecording >= iMinsLeft) { + wxMessageDialog dlgMessage(NULL, + _("You may not have enough free disk space to complete this timer recording, based on your current settings.\n\nDo you wish to continue?"), + _("Timer Recording Disk Space Warning"), + wxYES_NO | wxNO_DEFAULT | wxICON_WARNING); + if (dlgMessage.ShowModal() != wxID_YES) { + // User decided not to continue - bail out! + return; + } + } + m_timer.Stop(); // Don't need to keep updating m_DateTime_Start to prevent backdating. this->EndModal(wxID_OK); wxLongLong duration = m_TimeSpan_Duration.GetSeconds(); @@ -465,17 +492,26 @@ int TimerRecordDialog::RunWaitDialog() pProject->OnRecord(); bool bIsRecording = true; - wxString strMsg = - _("Recording start") + (wxString)wxT(":\t\t") - + GetDisplayDate(m_DateTime_Start) + wxT("\n") + _("Recording end") - + wxT(":\t\t") + GetDisplayDate(m_DateTime_End) + wxT("\n") - + _("Duration") + wxT(":\t\t") + m_TimeSpan_Duration.Format(); + wxString sPostAction = m_pTimerAfterCompleteChoiceCtrl->GetString(m_pTimerAfterCompleteChoiceCtrl->GetSelection()); + wxString strMsg; + strMsg.Printf(_("Recording start:\t\t\t%s\n") + + _("Recording end:\t\t\t%s\n") + + _("Duration:\t\t\t%s\n\n") + + _("Automatic Save Enabled:\t\t%s\n") + + _("Automatic Export Enabled:\t\t%s\n") + + _("Post Timer Recording Action:\t%s"), + GetDisplayDate(m_DateTime_Start).c_str(), + m_TimeSpan_Duration.Format(), + GetDisplayDate(m_DateTime_End).c_str(), + (m_bAutoSaveEnabled ? _("Yes") : _("No")), + (m_bAutoExportEnabled ? _("Yes") : _("No")), + sPostAction); TimerProgressDialog progress(m_TimeSpan_Duration.GetMilliseconds().GetValue(), _("Audacity Timer Record Progress"), strMsg, - pdlgHideCancelButton); + pdlgHideCancelButton | pdlgConfirmStopCancel); // Make sure that start and end time are updated, so we always get the full // duration, even if there's some delay getting here. @@ -593,11 +629,20 @@ int TimerRecordDialog::ExecutePostRecordActions(bool bWasStopped) { // MY: Lets do some actions that only apply to Exit/Restart/Shutdown if (iPostRecordAction >= POST_TIMER_RECORD_CLOSE) { do { + + // Set the flags as appropriate based on what we have done + wxUint32 eActionFlags = TR_ACTION_NOTHING; + if (m_bAutoSaveEnabled && bSaveOK) { + eActionFlags |= TR_ACTION_SAVED; + } + if (m_bAutoExportEnabled && bExportOK) { + eActionFlags |= TR_ACTION_EXPORTED; + } + // Lets show a warning dialog telling the user what is about to happen. // If the user no longer wants to carry out this action then they can click // Cancel and we will do POST_TIMER_RECORD_NOTHING instead. - int iDelayOutcome = PreActionDelay(iPostRecordAction, (m_bAutoSaveEnabled && bSaveOK), - (m_bAutoExportEnabled && bExportOK)); + int iDelayOutcome = PreActionDelay(iPostRecordAction, (TimerRecordCompletedActions)eActionFlags); if (iDelayOutcome != eProgressSuccess) { // Cancel the action! iPostRecordAction = POST_TIMER_RECORD_NOTHING; @@ -912,18 +957,32 @@ void TimerRecordDialog::UpdateEnd() int TimerRecordDialog::WaitForStart() { + // MY: The Waiting For Start dialog now shows what actions will occur after recording has completed + wxString sPostAction = m_pTimerAfterCompleteChoiceCtrl->GetString(m_pTimerAfterCompleteChoiceCtrl->GetSelection()); + + /* i18n-hint: Time specifications like "Sunday 28th October 2007 15:16:17 GMT" + * but hopefully translated by wxwidgets will be inserted into this */ wxString strMsg; - /* i18n-hint: A time specification like "Sunday 28th October 2007 15:16:17 GMT" - * but hopefully translated by wxwidgets will be inserted into this */ - strMsg.Printf(_("Waiting to start recording at %s.\n"), - GetDisplayDate(m_DateTime_Start).c_str()); + strMsg.Printf(_("Waiting to start recording at:\t%s\n") + + _("Recording duration:\t\t%s\n") + + _("Scheduled to stop at:\t\t%s\n\n") + + _("Automatic Save Enabled:\t\t%s\n") + + _("Automatic Export Enabled:\t\t%s\n") + + _("Post Timer Recording Action:\t%s"), + GetDisplayDate(m_DateTime_Start).c_str(), + m_TimeSpan_Duration.Format(), + GetDisplayDate(m_DateTime_End).c_str(), + (m_bAutoSaveEnabled ? _("Yes") : _("No")), + (m_bAutoExportEnabled ? _("Yes") : _("No")), + sPostAction); + wxDateTime startWait_DateTime = wxDateTime::UNow(); wxTimeSpan waitDuration = m_DateTime_Start - startWait_DateTime; - TimerProgressDialog - progress(waitDuration.GetMilliseconds().GetValue(), - _("Audacity Timer Record - Waiting for Start"), - strMsg, - pdlgHideStopButton); + TimerProgressDialog progress(waitDuration.GetMilliseconds().GetValue(), + _("Audacity Timer Record - Waiting for Start"), + strMsg, + pdlgHideStopButton | pdlgConfirmStopCancel | pdlgHideElapsedTime, + _("Recording will commence in:")); int updateResult = eProgressSuccess; bool bIsRecording = false; @@ -936,28 +995,21 @@ int TimerRecordDialog::WaitForStart() return updateResult; } -// TODO: Rather than two flags, an enum with the possibilities would be better. -int TimerRecordDialog::PreActionDelay(int iActionIndex, bool bSaved, bool bExported) +int TimerRecordDialog::PreActionDelay(int iActionIndex, TimerRecordCompletedActions eCompletedActions) { - wxString sMessage; wxString sAction = m_pTimerAfterCompleteChoiceCtrl->GetString(iActionIndex); - wxString sDone = ""; - if (bSaved && bExported) { - sDone = _("Saved and Exported"); - } - else if (bSaved) { - sDone = _("Saved"); - } - else if (bExported) { - sDone = _("Exported"); - } - // TODO: The wording will sound better if there are complete messages for - // the will-occur-shortly messages. - /* i18n-hint: The first %s will be a translation of 'Saved', 'Exported' or - * 'Saved and Exported'. The second %s will be 'Exit Audacity' - * 'Restart System' or 'Shutdown System' */ - sMessage.Printf(_("Timer Recording completed: Recording has been %s.\n\n'%s' will occur shortly...\n"), - sDone, sAction); + wxString sCountdownLabel; + sCountdownLabel.Printf("%s in:", sAction); + + // Build a clearer message... + wxString sMessage; + sMessage.Printf(_("Timer Recording Completed.\n\n") + + _("Recording Saved:\t\t\t%s\n") + + _("Recording Exported:\t\t%s\n") + + _("Post Timer Recording Action:\t%s"), + ((eCompletedActions & TR_ACTION_SAVED) ? _("Yes") : _("No")), + ((eCompletedActions & TR_ACTION_EXPORTED) ? _("Yes") : _("No")), + sAction); wxDateTime dtNow = wxDateTime::UNow(); wxTimeSpan tsWait = wxTimeSpan(0, 1, 0, 0); @@ -966,7 +1018,8 @@ int TimerRecordDialog::PreActionDelay(int iActionIndex, bool bSaved, bool bExpor TimerProgressDialog dlgAction(tsWait.GetMilliseconds().GetValue(), _("Audacity Timer Record - Waiting"), sMessage, - pdlgHideStopButton); + pdlgHideStopButton | pdlgHideElapsedTime, + sCountdownLabel); int iUpdateResult = eProgressSuccess; bool bIsTime = false; diff --git a/src/TimerRecordDialog.h b/src/TimerRecordDialog.h index 6dbd8f2c3..715c20eaf 100644 --- a/src/TimerRecordDialog.h +++ b/src/TimerRecordDialog.h @@ -30,6 +30,12 @@ class NumericTextCtrl; class ShuttleGui; class TimerRecordPathCtrl; +enum TimerRecordCompletedActions { + TR_ACTION_NOTHING = 0x00000000, + TR_ACTION_SAVED = 0x00000001, + TR_ACTION_EXPORTED = 0x00000002 +}; + class TimerRecordPathCtrl final : public wxTextCtrl { // MY: Class that inherits from the wxTextCtrl class. @@ -93,7 +99,7 @@ private: int ExecutePostRecordActions(bool bWasStopped); - int PreActionDelay(int iActionIndex, bool bSaved, bool bExported); + int PreActionDelay(int iActionIndex, TimerRecordCompletedActions eCompletedActions); private: wxDateTime m_DateTime_Start; diff --git a/src/widgets/ProgressDialog.cpp b/src/widgets/ProgressDialog.cpp index 64c0bb2b3..d05cc4654 100644 --- a/src/widgets/ProgressDialog.cpp +++ b/src/widgets/ProgressDialog.cpp @@ -996,13 +996,14 @@ ProgressDialog::ProgressDialog() } ProgressDialog::ProgressDialog(const wxString & title, - const wxString & message, - int flags) + const wxString & message /* = wxEmptyString*/, + int flags /* = pdlgDefaultFlags */, + const wxString & sRemainingLabelText /* = wxEmptyString */) : wxDialog() { Init(); - Create(title, message, flags); + Create(title, message, flags, sRemainingLabelText); } // @@ -1070,11 +1071,17 @@ void ProgressDialog::Init() } bool ProgressDialog::Create(const wxString & title, - const wxString & message, - int flags) + const wxString & message /* = wxEmptyString */, + int flags /* = pdlgDefaultFlags */, + const wxString & sRemainingLabelText /* = wxEmptyString */) { wxWindow *parent = GetParentForModalDialog(NULL, 0); + // Set this boolean to indicate if we are using the "Elapsed" labels + m_bShowElapsedTime = !(flags & pdlgHideElapsedTime); + // Set this boolean to indicate if we confirm the Cancel/Stop actions + m_bConfirmAction = (flags & pdlgConfirmStopCancel); + bool success = wxDialog::Create(parent, wxID_ANY, title, @@ -1125,33 +1132,45 @@ bool ProgressDialog::Create(const wxString & title, // { auto ug = std::make_unique(2, 2, 10, 10); + // MY: Only one row if we are not going to show the elapsed time + if (m_bShowElapsedTime == false) { + ug = std::make_unique(1, 2, 10, 10); + } g = ug.get(); - w = safenew wxStaticText(this, - wxID_ANY, - _("Elapsed Time:"), - wxDefaultPosition, - wxDefaultSize, - wxALIGN_RIGHT); - w->SetName(w->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - g->Add(w, 0, wxALIGN_RIGHT); + if (m_bShowElapsedTime) { + w = safenew wxStaticText(this, + wxID_ANY, + _("Elapsed Time:"), + wxDefaultPosition, + wxDefaultSize, + wxALIGN_RIGHT); + w->SetName(w->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) + g->Add(w, 0, wxALIGN_RIGHT); - mElapsed = safenew wxStaticText(this, - wxID_ANY, - wxT("00:00:00"), - wxDefaultPosition, - wxDefaultSize, - wxALIGN_LEFT); - mElapsed->SetName(mElapsed->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - g->Add(mElapsed, 0, wxALIGN_LEFT); - ds.y += mElapsed->GetSize().y + 10; + mElapsed = safenew wxStaticText(this, + wxID_ANY, + wxT("00:00:00"), + wxDefaultPosition, + wxDefaultSize, + wxALIGN_LEFT); + mElapsed->SetName(mElapsed->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) + g->Add(mElapsed, 0, wxALIGN_LEFT); + ds.y += mElapsed->GetSize().y + 10; + } + + // Customised "Remaining" label text + wxString sRemainingText = sRemainingLabelText; + if (sRemainingText == wxEmptyString) { + sRemainingText = _("Remaining Time:"); + } // // // w = safenew wxStaticText(this, wxID_ANY, - _("Remaining Time:"), + sRemainingText, wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); @@ -1180,12 +1199,14 @@ bool ProgressDialog::Create(const wxString & title, { w = safenew wxButton(this, wxID_OK, _("Stop")); h->Add(w, 0, wxRIGHT, 10); + m_btnStop = w; } if (!(flags & pdlgHideCancelButton)) { w = safenew wxButton(this, wxID_CANCEL, _("Cancel")); h->Add(w, 0, wxRIGHT, 10); + m_btnCancel = w; } v->Add(uh.release(), 0, wxALIGN_RIGHT | wxRIGHT | wxBOTTOM, 10); @@ -1201,6 +1222,10 @@ bool ProgressDialog::Create(const wxString & title, wxClientDC dc(this); dc.GetMultiLineTextExtent(message, &mLastW, &mLastH); + // MY: Add a little bit more width when we have TABs to stop words wrapping + int iTabFreq = wxMax((message.Freq('\t') - 1), 0); + mLastW = mLastW + (iTabFreq * 8); + #if defined(__WXMAC__) mMessage->SetMinSize(wxSize(mLastW, mLastH)); #endif @@ -1297,12 +1322,14 @@ int ProgressDialog::Update(int value, const wxString & message) // Only update if a full second has passed or track progress is complete if ((now - mLastUpdate > 1000) || (value == 1000)) { - wxTimeSpan tsElapsed(0, 0, 0, elapsed); - wxTimeSpan tsRemains(0, 0, 0, remains); + if (m_bShowElapsedTime) { + wxTimeSpan tsElapsed(0, 0, 0, elapsed); + mElapsed->SetLabel(tsElapsed.Format(wxT("%H:%M:%S"))); + mElapsed->SetName(mElapsed->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) + mElapsed->Update(); + } - mElapsed->SetLabel(tsElapsed.Format(wxT("%H:%M:%S"))); - mElapsed->SetName(mElapsed->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) - mElapsed->Update(); + wxTimeSpan tsRemains(0, 0, 0, remains); mRemaining->SetLabel(tsRemains.Format(wxT("%H:%M:%S"))); mRemaining->SetName(mRemaining->GetLabel()); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) mRemaining->Update(); @@ -1477,12 +1504,36 @@ bool ProgressDialog::SearchForWindow(const wxWindowList & list, const wxWindow * void ProgressDialog::OnCancel(wxCommandEvent & WXUNUSED(event)) { + if (m_bConfirmAction) { + wxString sPrompt = _("Are you sure you wish to cancel?"); + wxMessageDialog dlgMessage(this, + sPrompt, + _("Confirm Cancel"), + wxYES_NO | wxICON_QUESTION | wxNO_DEFAULT | wxSTAY_ON_TOP); + int iAction = dlgMessage.ShowModal(); + if (iAction != wxID_YES) { + m_btnCancel->SetFocus(); + return; + } + } FindWindowById(wxID_CANCEL, this)->Disable(); mCancel = true; } void ProgressDialog::OnStop(wxCommandEvent & WXUNUSED(event)) { + if (m_bConfirmAction) { + wxString sPrompt = _("Are you sure you wish to stop?"); + wxMessageDialog dlgMessage(this, + sPrompt, + _("Confirm Stop"), + wxYES_NO | wxICON_QUESTION | wxNO_DEFAULT | wxSTAY_ON_TOP); + int iAction = dlgMessage.ShowModal(); + if (iAction != wxID_YES) { + m_btnStop->SetFocus(); + return; + } + } FindWindowById(wxID_OK, this)->Disable(); mCancel = false; mStop = true; @@ -1490,6 +1541,17 @@ void ProgressDialog::OnStop(wxCommandEvent & WXUNUSED(event)) void ProgressDialog::OnCloseWindow(wxCloseEvent & WXUNUSED(event)) { + if (m_bConfirmAction) { + wxString sPrompt = _("Are you sure you wish to close?"); + wxMessageDialog dlgMessage(this, + sPrompt, + _("Confirm Close"), + wxYES_NO | wxICON_QUESTION | wxNO_DEFAULT | wxSTAY_ON_TOP); + int iAction = dlgMessage.ShowModal(); + if (iAction != wxID_YES) { + return; + } + } mCancel = true; } @@ -1525,10 +1587,11 @@ void ProgressDialog::Beep() const } TimerProgressDialog::TimerProgressDialog(const wxLongLong_t duration, - const wxString & title, - const wxString & message /*= wxEmptyString*/, - int flags /*= pdlgEmptyFlags*/) -: ProgressDialog(title, message, flags) + const wxString & title, + const wxString & message /* = wxEmptyString */, + int flags /* = pdlgDefaultFlags */, + const wxString & sRemainingLabelText /* = wxEmptyString */) +: ProgressDialog(title, message, flags, sRemainingLabelText) { mDuration = duration; } @@ -1587,11 +1650,13 @@ int TimerProgressDialog::Update(const wxString & message /*= wxEmptyString*/) // Only update if a full second has passed. if (now - mLastUpdate > 1000) { - wxTimeSpan tsElapsed(0, 0, 0, elapsed); - wxTimeSpan tsRemains(0, 0, 0, remains); + if (m_bShowElapsedTime) { + wxTimeSpan tsElapsed(0, 0, 0, elapsed); + mElapsed->SetLabel(tsElapsed.Format(wxT("%H:%M:%S"))); + mElapsed->Update(); + } - mElapsed->SetLabel(tsElapsed.Format(wxT("%H:%M:%S"))); - mElapsed->Update(); + wxTimeSpan tsRemains(0, 0, 0, remains); mRemaining->SetLabel(tsRemains.Format(wxT("%H:%M:%S"))); mRemaining->Update(); diff --git a/src/widgets/ProgressDialog.h b/src/widgets/ProgressDialog.h index 5a61e4590..d590c6a67 100644 --- a/src/widgets/ProgressDialog.h +++ b/src/widgets/ProgressDialog.h @@ -26,6 +26,7 @@ #include #include #include +#include enum { @@ -40,6 +41,8 @@ enum ProgressDialogFlags pdlgEmptyFlags = 0x00000000, pdlgHideStopButton = 0x00000001, pdlgHideCancelButton = 0x00000002, + pdlgHideElapsedTime = 0x00000004, + pdlgConfirmStopCancel = 0x00000008, pdlgDefaultFlags = pdlgEmptyFlags }; @@ -52,11 +55,17 @@ class AUDACITY_DLL_API ProgressDialog /* not final */ : public wxDialog { public: ProgressDialog(); - ProgressDialog(const wxString & title, const wxString & message = wxEmptyString, int flags = pdlgDefaultFlags); + ProgressDialog(const wxString & title, + const wxString & message = wxEmptyString, + int flags = pdlgDefaultFlags, + const wxString & sRemainingLabelText = wxEmptyString); virtual ~ProgressDialog(); // NEW virtual? It doesn't override wxDialog - virtual bool Create(const wxString & title, const wxString & message = wxEmptyString, int flags = pdlgDefaultFlags); + virtual bool Create(const wxString & title, + const wxString & message = wxEmptyString, + int flags = pdlgDefaultFlags, + const wxString & sRemainingLabelText = wxEmptyString); int Update(int value, const wxString & message = wxEmptyString); int Update(double current, const wxString & message = wxEmptyString); @@ -83,6 +92,14 @@ protected: bool mIsTransparent; + // MY: Booleans to hold the flag values + bool m_bShowElapsedTime = true; + bool m_bConfirmAction = false; + + // MY: Declare the buttons so we can se the focus on them later + wxWindow *m_btnStop; + wxWindow *m_btnCancel; + private: void Init(); bool SearchForWindow(const wxWindowList & list, const wxWindow *searchfor) const; @@ -110,7 +127,8 @@ public: TimerProgressDialog(const wxLongLong_t duration, const wxString & title, const wxString & message = wxEmptyString, - int flags = pdlgDefaultFlags); + int flags = pdlgDefaultFlags, + const wxString & sRemainingLabelText = wxEmptyString); int Update(const wxString & message = wxEmptyString); protected: