diff --git a/src/TimerRecordDialog.cpp b/src/TimerRecordDialog.cpp index ed9e661c7..3216d608b 100644 --- a/src/TimerRecordDialog.cpp +++ b/src/TimerRecordDialog.cpp @@ -522,30 +522,36 @@ int TimerRecordDialog::RunWaitDialog() wxString sPostAction = m_pTimerAfterCompleteChoiceCtrl->GetString(m_pTimerAfterCompleteChoiceCtrl->GetSelection()); - // Two column layout. Line spacing must match for both columns. - // First column - wxString strMsg = _("Recording start:\n") + - _("Duration:\n") + - _("Recording end:\n\n") + - _("Automatic Save enabled:\n") + - _("Automatic Export enabled:\n") + - _("Action after Timer Recording:"); + // Two column layout. + TimerProgressDialog::MessageTable columns(2); + auto &column1 = columns[0]; + auto &column2 = columns[1]; - strMsg += ProgressDialog::ColoumnSplitMarker; + column1.push_back( _("Recording start:") ); + column2.push_back( GetDisplayDate(m_DateTime_Start) ); - // Second column - strMsg += wxString::Format(wxT("%s\n%s\n%s\n\n%s\n%s\n%s"), - GetDisplayDate(m_DateTime_Start), - m_TimeSpan_Duration.Format(), - GetDisplayDate(m_DateTime_End), - (m_bAutoSaveEnabled ? _("Yes") : _("No")), - (m_bAutoExportEnabled ? _("Yes") : _("No")), - sPostAction); + column1.push_back( _("Duration:") ); + column2.push_back( m_TimeSpan_Duration.Format() ); + + column1.push_back( _("Recording end:") ); + column2.push_back( GetDisplayDate(m_DateTime_End) ); + + column1.push_back( {} ); + column2.push_back( {} ); + + column1.push_back( _("Automatic Save enabled:") ); + column2.push_back( (m_bAutoSaveEnabled ? _("Yes") : _("No")) ); + + column1.push_back( _("Automatic Export enabled:") ); + column2.push_back( (m_bAutoExportEnabled ? _("Yes") : _("No")) ); + + column1.push_back( _("Action after Timer Recording:") ); + column2.push_back( sPostAction ); TimerProgressDialog progress(m_TimeSpan_Duration.GetMilliseconds().GetValue(), _("Audacity Timer Record Progress"), - strMsg, + columns, pdlgHideCancelButton | pdlgConfirmStopCancel); // Make sure that start and end time are updated, so we always get the full @@ -555,7 +561,7 @@ int TimerRecordDialog::RunWaitDialog() // Loop for progress display during recording. while (bIsRecording && (updateResult == ProgressResult::Success)) { - updateResult = progress.Update(); + updateResult = progress.UpdateProgress(); wxMilliSleep(kTimerInterval); bIsRecording = (wxDateTime::UNow() <= m_DateTime_End); // Call UNow() again for extra accuracy... } @@ -1007,31 +1013,37 @@ ProgressResult 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()); - // Two column layout. Line spacing must match for both columns. - // First column - wxString strMsg = _("Waiting to start recording at:\n") + - _("Recording duration:\n") + - _("Scheduled to stop at:\n\n") + - _("Automatic Save enabled:\n") + - _("Automatic Export enabled:\n") + - _("Action after Timer Recording:"); + // Two column layout. + TimerProgressDialog::MessageTable columns(2); + auto &column1 = columns[0]; + auto &column2 = columns[1]; - strMsg += ProgressDialog::ColoumnSplitMarker; + column1.push_back(_("Waiting to start recording at:")); + column2.push_back(GetDisplayDate(m_DateTime_Start)); - // Second column - strMsg += wxString::Format(wxT("%s\n%s\n%s\n\n%s\n%s\n%s"), - GetDisplayDate(m_DateTime_Start), - m_TimeSpan_Duration.Format(), - GetDisplayDate(m_DateTime_End), - (m_bAutoSaveEnabled ? _("Yes") : _("No")), - (m_bAutoExportEnabled ? _("Yes") : _("No")), - sPostAction); + column1.push_back(_("Recording duration:")); + column2.push_back(m_TimeSpan_Duration.Format()); + + column1.push_back(_("Scheduled to stop at:")); + column2.push_back(GetDisplayDate(m_DateTime_End)); + + column1.push_back( {} ); + column2.push_back( {} ); + + column1.push_back(_("Automatic Save enabled:")); + column2.push_back((m_bAutoSaveEnabled ? _("Yes") : _("No"))); + + column1.push_back(_("Automatic Export enabled:")); + column2.push_back((m_bAutoExportEnabled ? _("Yes") : _("No"))); + + column1.push_back(_("Action after Timer Recording:")); + column2.push_back(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, + columns, pdlgHideStopButton | pdlgConfirmStopCancel | pdlgHideElapsedTime, _("Recording will commence in:")); @@ -1039,7 +1051,7 @@ ProgressResult TimerRecordDialog::WaitForStart() bool bIsRecording = false; while (updateResult == ProgressResult::Success && !bIsRecording) { - updateResult = progress.Update(); + updateResult = progress.UpdateProgress(); wxMilliSleep(10); bIsRecording = (m_DateTime_Start <= wxDateTime::UNow()); } @@ -1052,20 +1064,25 @@ ProgressResult TimerRecordDialog::PreActionDelay(int iActionIndex, TimerRecordCo wxString sCountdownLabel; sCountdownLabel.Printf("%s in:", sAction); - // Two column layout. Line spacing must match for both columns. - // First column - wxString strMsg = _("Timer Recording completed.\n\n") + - _("Recording Saved:\n") + - _("Recording Exported:\n") + - _("Action after Timer Recording:"); + // Two column layout. + TimerProgressDialog::MessageTable columns(2); + auto &column1 = columns[0]; + auto &column2 = columns[1]; - strMsg += ProgressDialog::ColoumnSplitMarker; + column1.push_back(_("Timer Recording completed.")); + column2.push_back( {} ); - // Second column - strMsg += wxString::Format(wxT("\n\n%s\n%s\n%s"), - ((eCompletedActions & TR_ACTION_SAVED) ? _("Yes") : _("No")), - ((eCompletedActions & TR_ACTION_EXPORTED) ? _("Yes") : _("No")), - sAction); + column1.push_back( {} ); + column2.push_back( {} ); + + column1.push_back(_("Recording Saved:")); + column2.push_back(((eCompletedActions & TR_ACTION_SAVED) ? _("Yes") : _("No"))); + + column1.push_back(_("Recording Exported:")); + column2.push_back(((eCompletedActions & TR_ACTION_EXPORTED) ? _("Yes") : _("No"))); + + column1.push_back(_("Action after Timer Recording:")); + column2.push_back(sAction); wxDateTime dtNow = wxDateTime::UNow(); wxTimeSpan tsWait = wxTimeSpan(0, 1, 0, 0); @@ -1073,7 +1090,7 @@ ProgressResult TimerRecordDialog::PreActionDelay(int iActionIndex, TimerRecordCo TimerProgressDialog dlgAction(tsWait.GetMilliseconds().GetValue(), _("Audacity Timer Record - Waiting"), - strMsg, + columns, pdlgHideStopButton | pdlgHideElapsedTime, sCountdownLabel); @@ -1081,7 +1098,7 @@ ProgressResult TimerRecordDialog::PreActionDelay(int iActionIndex, TimerRecordCo bool bIsTime = false; while (iUpdateResult == ProgressResult::Success && !bIsTime) { - iUpdateResult = dlgAction.Update(); + iUpdateResult = dlgAction.UpdateProgress(); wxMilliSleep(10); bIsTime = (dtActionTime <= wxDateTime::UNow()); } diff --git a/src/widgets/ProgressDialog.cpp b/src/widgets/ProgressDialog.cpp index 5469058a4..854c5799f 100644 --- a/src/widgets/ProgressDialog.cpp +++ b/src/widgets/ProgressDialog.cpp @@ -1010,6 +1010,17 @@ ProgressDialog::ProgressDialog(const wxString & title, Create(title, message, flags, sRemainingLabelText); } +ProgressDialog::ProgressDialog(const wxString & title, + const MessageTable &columns, + int flags /* = pdlgDefaultFlags */, + const wxString & sRemainingLabelText /* = wxEmptyString */) +: wxDialogWrapper() +{ + Init(); + + Create(title, columns, flags, sRemainingLabelText); +} + // // Destructor // @@ -1071,13 +1082,18 @@ void ProgressDialog::Init() } // Add a NEW text column each time this is called. -void ProgressDialog::AddMessageAsColumn(wxBoxSizer * pSizer, const wxString & sText, bool bFirstColumn) { +void ProgressDialog::AddMessageAsColumn(wxBoxSizer * pSizer, + const MessageColumn & column, + bool bFirstColumn) { // Assuming that we don't want empty columns, bail out if there is no text. - if (sText.IsEmpty()) - { + if (column.empty()) return; - } + + // Join strings + auto sText = column[0]; + std::for_each( column.begin() + 1, column.end(), + [&](const wxString &text) { sText += wxT("\n") + text; }); // Create a statictext object and add to the sizer wxStaticText* oText = safenew wxStaticText(this, @@ -1089,7 +1105,7 @@ void ProgressDialog::AddMessageAsColumn(wxBoxSizer * pSizer, const wxString & sT oText->SetName(sText); // fix for bug 577 (NVDA/Narrator screen readers do not read static text in dialogs) // If this is the first column then set the mMessage pointer so non-TimerRecord usages - // will still work correctly + // will still work correctly in SetMessage() if (bFirstColumn) { mMessage = oText; } @@ -1101,6 +1117,25 @@ bool ProgressDialog::Create(const wxString & title, const wxString & message /* = wxEmptyString */, int flags /* = pdlgDefaultFlags */, const wxString & sRemainingLabelText /* = wxEmptyString */) +{ + MessageTable columns(1); + columns.back().push_back(message); + auto result = Create(title, columns, flags, sRemainingLabelText); + + if (result) { + // Record some values used in case of change of message + // TODO: make the following work in case of message tables + wxClientDC dc(this); + dc.GetMultiLineTextExtent(message, &mLastW, &mLastH); + } + + return result; +} + +bool ProgressDialog::Create(const wxString & title, + const MessageTable & columns, + int flags /* = pdlgDefaultFlags */, + const wxString & sRemainingLabelText /* = wxEmptyString */) { wxWindow *parent = GetParentForModalDialog(NULL, 0); @@ -1126,15 +1161,19 @@ bool ProgressDialog::Create(const wxString & title, { wxWindow *window; - wxArrayString arMessages(wxSplit(message, ProgressDialog::ColoumnSplitMarker)); // There may be more than one column, so create a BoxSizer container auto uColSizer = std::make_unique(wxHORIZONTAL); auto colSizer = uColSizer.get(); - for (size_t column = 0; column < arMessages.GetCount(); column++) { - bool bFirstCol = (column == 0); - AddMessageAsColumn(colSizer, arMessages[column], bFirstCol); + // TODO: this setting-up of a grid of text in a sizer might be worth + // extracting as a utility for building other dialogs. + { + bool bFirstCol = true; + for (const auto &column : columns) { + AddMessageAsColumn(colSizer, column, bFirstCol); + bFirstCol = false; + } } // and put message column(s) into a main vertical sizer. @@ -1225,13 +1264,6 @@ bool ProgressDialog::Create(const wxString & title, } Layout(); - wxClientDC dc(this); - dc.GetMultiLineTextExtent(message, &mLastW, &mLastH); - - // 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); - Centre(wxCENTER_FRAME | wxBOTH); mStartTime = wxGetLocalTimeMillis().GetValue(); @@ -1459,6 +1491,7 @@ void ProgressDialog::SetMessage(const wxString & message) bool sizeUpdated = false; wxSize ds = GetClientSize(); + // TODO: make the following work in case of message tables if (w > mLastW) { ds.x += (w - mLastW); @@ -1591,15 +1624,15 @@ bool ProgressDialog::ConfirmAction(const wxString & sPrompt, TimerProgressDialog::TimerProgressDialog(const wxLongLong_t duration, const wxString & title, - const wxString & message /* = wxEmptyString */, + const MessageTable & columns, int flags /* = pdlgDefaultFlags */, const wxString & sRemainingLabelText /* = wxEmptyString */) -: ProgressDialog(title, message, flags, sRemainingLabelText) +: ProgressDialog(title, columns, flags, sRemainingLabelText) { mDuration = duration; } -ProgressResult TimerProgressDialog::Update(const wxString & message /*= wxEmptyString*/) +ProgressResult TimerProgressDialog::UpdateProgress() { if (mCancel) { @@ -1625,8 +1658,6 @@ ProgressResult TimerProgressDialog::Update(const wxString & message /*= wxEmptyS mIsTransparent = false; } - SetMessage(message); - wxLongLong_t remains = mStartTime + mDuration - now; int nGaugeValue = (1000 * elapsed) / mDuration; // range = [0,1000] diff --git a/src/widgets/ProgressDialog.h b/src/widgets/ProgressDialog.h index fece6128a..990c79a16 100644 --- a/src/widgets/ProgressDialog.h +++ b/src/widgets/ProgressDialog.h @@ -21,6 +21,7 @@ #include "../Audacity.h" #include "../MemoryX.h" +#include #include #include #include @@ -56,18 +57,42 @@ class AUDACITY_DLL_API ProgressDialog /* not final */ : public wxDialogWrapper { public: ProgressDialog(); + + // Display a simple message. ProgressDialog(const wxString & title, const wxString & message = wxEmptyString, int flags = pdlgDefaultFlags, const wxString & sRemainingLabelText = wxEmptyString); + + using MessageColumn = std::vector< wxString >; + using MessageTable = std::vector< MessageColumn >; + +protected: + // Display a table of messages. + // Each member of the table is a column of messages. + // Each member of a column is a string that should have no newlines, + // and each column should have the same number of elements, to make + // proper correspondences, but this is not checked. + ProgressDialog(const wxString & title, + const MessageTable & columns, + int flags = pdlgDefaultFlags, + const wxString & sRemainingLabelText = wxEmptyString); + +public: virtual ~ProgressDialog(); - // NEW virtual? It doesn't override wxDialog - virtual bool Create(const wxString & title, - const wxString & message = wxEmptyString, - int flags = pdlgDefaultFlags, - const wxString & sRemainingLabelText = wxEmptyString); + bool Create(const wxString & title, + const wxString & message = wxEmptyString, + int flags = pdlgDefaultFlags, + const wxString & sRemainingLabelText = wxEmptyString); +protected: + bool Create(const wxString & title, + const MessageTable & columns, + int flags = pdlgDefaultFlags, + const wxString & sRemainingLabelText = wxEmptyString); + +public: ProgressResult Update(int value, const wxString & message = wxEmptyString); ProgressResult Update(double current, const wxString & message = wxEmptyString); ProgressResult Update(double current, double total, const wxString & message = wxEmptyString); @@ -77,9 +102,6 @@ public: ProgressResult Update(int current, int total, const wxString & message = wxEmptyString); void SetMessage(const wxString & message); - // 'ETB' character to indicate a NEW column in the message text. - static const wxChar ColoumnSplitMarker = (char)23; - protected: wxWindow *mHadFocus; @@ -113,7 +135,8 @@ private: const wxString & sTitle, int iButtonID = -1); - void AddMessageAsColumn(wxBoxSizer * pSizer, const wxString & sText, bool bFirstColumn); + void AddMessageAsColumn(wxBoxSizer * pSizer, + const MessageColumn &column, bool bFirstColumn); private: // This guarantees we have an active event loop...possible during OnInit() @@ -121,9 +144,9 @@ private: std::unique_ptr mDisable; - wxStaticText *mMessage; - int mLastW; - int mLastH; + wxStaticText *mMessage{} ; + int mLastW{ 0 }; + int mLastH{ 0 }; DECLARE_EVENT_TABLE() }; @@ -132,14 +155,21 @@ class AUDACITY_DLL_API TimerProgressDialog final : public ProgressDialog { public: TimerProgressDialog(const wxLongLong_t duration, - const wxString & title, - const wxString & message = wxEmptyString, + const wxString &title, + const MessageTable & columns, int flags = pdlgDefaultFlags, const wxString & sRemainingLabelText = wxEmptyString); - ProgressResult Update(const wxString & message = wxEmptyString); + + // Oh no, there is an inherited nullary "Update" in wxDialog! + // Choose another name then... + ProgressResult UpdateProgress(); protected: wxLongLong_t mDuration; + + // Disallow direct use of the inherited overloads of Update because it + // doesn't support changes of message + using ProgressDialog::Update; }; #endif