diff --git a/src/AboutDialog.cpp b/src/AboutDialog.cpp index b84da0b6e..d7f46ccc7 100644 --- a/src/AboutDialog.cpp +++ b/src/AboutDialog.cpp @@ -120,6 +120,7 @@ void AboutDialog::CreateCreditsList() AddCredit(wxString(wxT("Jun Wan, ")) + _("developer"), roleContributor); AddCredit(wxString(wxT("Daniel Winzen, ")) + _("developer"), roleContributor); AddCredit(wxString(wxT("Tom Woodhams, ")) + _("developer"), roleContributor); + AddCredit(wxString(wxT("Mark Young, ")) + _("developer"), roleContributor); AddCredit(wxString(wxT("Wing Yu, ")) + _("developer"), roleContributor); // Translators diff --git a/src/Menus.cpp b/src/Menus.cpp index e60fe4d7d..ebae67322 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -144,6 +144,18 @@ enum { kAlignTogether }; +// Post Timer Recording Actions +// Ensure this matches the enum in TimerRecordDialog.cpp +enum { + POST_TIMER_RECORD_STOPPED = -3, + POST_TIMER_RECORD_CANCEL_WAIT, + POST_TIMER_RECORD_CANCEL, + POST_TIMER_RECORD_NOTHING, + POST_TIMER_RECORD_CLOSE, + POST_TIMER_RECORD_RESTART, + POST_TIMER_RECORD_SHUTDOWN +}; + // Define functor subclasses that dispatch to the correct call sequence on // member functions of AudacityProject @@ -6308,24 +6320,79 @@ void AudacityProject::OnNewTimeTrack() void AudacityProject::OnTimerRecord() { + // MY: Due to improvements in how Timer Recording saves and/or exports + // 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."), + _("Timer Recording"), + wxICON_INFORMATION | wxOK); + return; + } + + // MY: If the project has unsaved changes then we no longer allow access + // to Timer Recording. This decision has been taken as the safest approach + // preventing issues surrounding "dirty" projects when Automatic Save/Export + // is used in Timer Recording. + if ((GetUndoManager()->UnsavedChanges()) && (ProjectHasTracks() || mEmptyCanBeDirty)) { + wxMessageBox(_("Timer Recording cannot be used while you have unsaved changes.\n\nPlease save or close this project and try again."), + _("Timer Recording"), + wxICON_INFORMATION | wxOK); + return; + } + // We use this variable to display "Current Project" in the Timer Recording save project field + bool bProjectSaved = IsProjectSaved(); + //we break the prompting and waiting dialogs into two sections //because they both give the user a chance to click cancel //and therefore remove the newly inserted track. - TimerRecordDialog dialog(this /* parent */ ); + TimerRecordDialog dialog(this, bProjectSaved); /* parent, project saved? */ int modalResult = dialog.ShowModal(); if (modalResult == wxID_CANCEL) { // Cancelled before recording - don't need to do anyting. } - else if(!dialog.RunWaitDialog()) + else { - //RunWaitDialog() shows the "wait for start" as well as "recording" dialog - //if it returned false it means the user cancelled while the recording, so throw out the fresh track. - //However, we can't undo it here because the PushState() is called in TrackPanel::OnTimer(), - //which is blocked by this function. - //so instead we mark a flag to undo it there. - mTimerRecordCanceled = true; + int iTimerRecordingOutcome = dialog.RunWaitDialog(); + switch (iTimerRecordingOutcome) { + case POST_TIMER_RECORD_CANCEL_WAIT: + // Canceled on the wait dialog + if (GetUndoManager()->UndoAvailable()) { + // MY: We need to roll back what changes we have made here + OnUndo(); + } + break; + case POST_TIMER_RECORD_CANCEL: + // RunWaitDialog() shows the "wait for start" as well as "recording" dialog + // if it returned POST_TIMER_RECORD_CANCEL it means the user cancelled while the recording, so throw out the fresh track. + // However, we can't undo it here because the PushState() is called in TrackPanel::OnTimer(), + // which is blocked by this function. + // so instead we mark a flag to undo it there. + mTimerRecordCanceled = true; + break; + case POST_TIMER_RECORD_NOTHING: + // No action required + break; + case POST_TIMER_RECORD_CLOSE: + // Quit Audacity + exit(0); + break; + case POST_TIMER_RECORD_RESTART: + // Restart System +#ifdef __WINDOWS__ + system("shutdown /r /f /t 30"); +#endif + break; + case POST_TIMER_RECORD_SHUTDOWN: + // Shutdown System +#ifdef __WINDOWS__ + system("shutdown /s /f /t 30"); +#endif + break; + } } } diff --git a/src/Project.cpp b/src/Project.cpp index c30b24f91..4d4daf3fb 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -2168,10 +2168,8 @@ void AudacityProject::OnCloseWindow(wxCloseEvent & event) gAudioIO->StopStream(); } - // These two lines test for an 'empty' project. - // of course it could still have a history at this stage. - TrackListIterator iter2(mTracks); - bool bHasTracks = (iter2.First() != NULL); + // MY: Use routine here so other processes can make same check + bool bHasTracks = ProjectHasTracks(); // We may not bother to prompt the user to save, if the // project is now empty. @@ -5193,3 +5191,66 @@ void AudacityProject::ReleaseKeyboard(wxWindow * /* handler */) return; } + +bool AudacityProject::ExportFromTimerRecording(wxFileName fnFile, int iFormat, int iSubFormat, int iFilterIndex) +{ + Exporter e; + + wxGetApp().SetMissingAliasedFileWarningShouldShow(true); + return e.ProcessFromTimerRecording(this, false, 0.0, mTracks->GetEndTime(), fnFile, iFormat, iSubFormat, iFilterIndex); +} + +int AudacityProject::GetOpenProjectCount() { + return gAudacityProjects.Count(); +} + +bool AudacityProject::IsProjectSaved() { + wxString sProjectName = mDirManager->GetProjectName(); + return (sProjectName != wxT("")); +} + +bool AudacityProject::SaveFromTimerRecording(wxFileName fnFile) { + // MY: Will save the project to a new location a-la Save As + // and then tidy up after itself. + + wxString sNewFileName = fnFile.GetFullPath(); + + // MY: To allow SaveAs from Timer Recording we need to check what + // the value of mFileName is befoer we change it. + wxString sOldFilename = ""; + if (IsProjectSaved()) { + sOldFilename = mFileName; + } + + // MY: If the project file already exists then bail out + // and send populate the message string (pointer) so + // we can tell the user what went wrong. + if (wxFileExists(sNewFileName)) { + return false; + } + + mFileName = sNewFileName; + SetProjectTitle(); + + bool bSuccess = Save(false, true, false); + + if (bSuccess) { + wxGetApp().AddFileToHistory(mFileName); + } else + { + // Reset file name on error + mFileName = sOldFilename; + SetProjectTitle(); + } + + return bSuccess; +} + +// MY: Does the project have any tracks? +bool AudacityProject::ProjectHasTracks() { + // These two lines test for an 'empty' project. + // of course it could still have a history at this stage. + TrackListIterator iter2(mTracks); + bool bHasTracks = (iter2.First() != NULL); + return bHasTracks; +} diff --git a/src/Project.h b/src/Project.h index a27a09cc8..4ec80706e 100644 --- a/src/Project.h +++ b/src/Project.h @@ -275,6 +275,14 @@ class AUDACITY_DLL_API AudacityProject final : public wxFrame, */ wxDialog *GetMissingAliasFileDialog(); + // Timer Record Auto Save/Export Routines + bool SaveFromTimerRecording(wxFileName fnFile); + bool ExportFromTimerRecording(wxFileName fnFile, int iFormat, int iSubFormat, int iFilterIndex); + int GetOpenProjectCount(); + bool IsProjectSaved(); + + bool ProjectHasTracks(); + #include "Menus.h" CommandManager *GetCommandManager() { return &mCommandManager; } diff --git a/src/ShuttleGui.cpp b/src/ShuttleGui.cpp index aaf12d59a..8cde5e2d3 100644 --- a/src/ShuttleGui.cpp +++ b/src/ShuttleGui.cpp @@ -487,7 +487,6 @@ wxSpinCtrl * ShuttleGuiBase::AddSpinCtrl(const wxString &Prompt, int Value, int return pSpinCtrl; } - wxTextCtrl * ShuttleGuiBase::AddTextBox(const wxString &Caption, const wxString &Value, const int nChars) { UseUpId(); diff --git a/src/ShuttleGui.h b/src/ShuttleGui.h index cb6f240a3..f8ef12b67 100644 --- a/src/ShuttleGui.h +++ b/src/ShuttleGui.h @@ -70,7 +70,6 @@ class Shuttle; class WrappedType; - class AUDACITY_DLL_API ShuttleGuiBase /* not final */ { public: diff --git a/src/TimerRecordDialog.cpp b/src/TimerRecordDialog.cpp index 96790744f..758b0c823 100644 --- a/src/TimerRecordDialog.cpp +++ b/src/TimerRecordDialog.cpp @@ -20,6 +20,7 @@ #include "Audacity.h" #include "TimerRecordDialog.h" +#include "FileNames.h" #include <wx/defs.h> #include <wx/datetime.h> @@ -44,7 +45,30 @@ enum { // control IDs ID_TIMETEXT_START, ID_DATEPICKER_END, ID_TIMETEXT_END, - ID_TIMETEXT_DURATION + ID_TIMETEXT_DURATION, + ID_AUTOSAVEPATH_BUTTON, + ID_AUTOSAVEPATH_TEXT, + ID_AUTOEXPORTPATH_BUTTON, + ID_AUTOEXPORTPATH_TEXT, + ID_AUTOSAVE_CHECKBOX, + ID_AUTOEXPORT_CHECKBOX +}; + +enum { + CONTROL_GROUP_SAVE, + CONTROL_GROUP_EXPORT +}; + +// Post Timer Recording Actions +// Ensure this matches the enum in Menus.cpp +enum { + POST_TIMER_RECORD_STOPPED = -3, + POST_TIMER_RECORD_CANCEL_WAIT, + POST_TIMER_RECORD_CANCEL, + POST_TIMER_RECORD_NOTHING, + POST_TIMER_RECORD_CLOSE, + POST_TIMER_RECORD_RESTART, + POST_TIMER_RECORD_SHUTDOWN }; const int kTimerInterval = 50; // ms @@ -66,9 +90,16 @@ BEGIN_EVENT_TABLE(TimerRecordDialog, wxDialog) EVT_BUTTON(wxID_OK, TimerRecordDialog::OnOK) EVT_TIMER(TIMER_ID, TimerRecordDialog::OnTimer) + + EVT_BUTTON(ID_AUTOSAVEPATH_BUTTON, TimerRecordDialog::OnAutoSavePathButton_Click) + EVT_BUTTON(ID_AUTOEXPORTPATH_BUTTON, TimerRecordDialog::OnAutoExportPathButton_Click) + + EVT_CHECKBOX(ID_AUTOSAVE_CHECKBOX, TimerRecordDialog::OnAutoSaveCheckBox_Change) + EVT_CHECKBOX(ID_AUTOEXPORT_CHECKBOX, TimerRecordDialog::OnAutoExportCheckBox_Change) + END_EVENT_TABLE() -TimerRecordDialog::TimerRecordDialog(wxWindow* parent) +TimerRecordDialog::TimerRecordDialog(wxWindow* parent, bool bAlreadySaved) : wxDialog(parent, -1, _("Audacity Timer Record"), wxDefaultPosition, wxDefaultSize, wxCAPTION) { @@ -88,6 +119,9 @@ TimerRecordDialog::TimerRecordDialog(wxWindow* parent) m_pTimeTextCtrl_Duration = NULL; + // Do we allow the user to change the Automatic Save file? + m_bProjectAlreadySaved = bAlreadySaved; + ShuttleGui S(this, eIsCreating); this->PopulateOrExchange(S); @@ -98,6 +132,10 @@ TimerRecordDialog::TimerRecordDialog(wxWindow* parent) m_timer.SetOwner(this, TIMER_ID); m_timer.Start(kTimerInterval); + + // Do we need to tidy up when the timer recording has been completed? + m_bProjectCleanupRequired = !(this->HaveFilesToRecover()); + } TimerRecordDialog::~TimerRecordDialog() @@ -210,6 +248,65 @@ void TimerRecordDialog::OnTimeText_Duration(wxCommandEvent& WXUNUSED(event)) this->UpdateEnd(); // Keep Start constant and update End for changed Duration. } +// New events for timer recording automation +void TimerRecordDialog::OnAutoSavePathButton_Click(wxCommandEvent& WXUNUSED(event)) +{ + wxString fName = FileSelector(_T("Save Timer Recording As"), + m_fnAutoSaveFile.GetPath(), + m_fnAutoSaveFile.GetFullName(), + wxT("aup"), + _("Audacity projects") + wxT(" (*.aup)|*.aup"), + wxFD_SAVE | wxRESIZE_BORDER, + this); + + if (fName == wxT("")) + return; + + // MY: If project already exits then abort - we do not allow users to overwrite an existing project + if (wxFileExists(fName)) { + wxMessageDialog m( + NULL, + _("The selected file name could not be used\nfor Timer Recording because it would overwrite another project.\nPlease try again and select an original name."), + _("Error Saving Timer Recording Project"), + wxOK|wxICON_ERROR); + m.ShowModal(); + return; + } + + // MY: Set this boolean to false so we now do a SaveAs at the end of the recording + m_bProjectAlreadySaved = false; + + m_fnAutoSaveFile = fName; + m_fnAutoSaveFile.SetExt(wxT("aup")); + this->UpdateTextBoxControls(); +} + +void TimerRecordDialog::OnAutoExportPathButton_Click(wxCommandEvent& WXUNUSED(event)) +{ + AudacityProject* pProject = GetActiveProject(); + Exporter eExporter; + + // Call the Exporter to set the options required + if (eExporter.SetAutoExportOptions(pProject)) { + // Populate the options so that we can destroy this instance of the Exporter + m_fnAutoExportFile = eExporter.GetAutoExportFileName(); + m_iAutoExportFormat = eExporter.GetAutoExportFormat(); + m_iAutoExportSubFormat = eExporter.GetAutoExportSubFormat(); + m_iAutoExportFilterIndex = eExporter.GetAutoExportFilterIndex(); + + // Update the text controls + this->UpdateTextBoxControls(); + } +} + +void TimerRecordDialog::OnAutoSaveCheckBox_Change(wxCommandEvent& WXUNUSED(event)) { + EnableDisableAutoControls(m_pTimerAutoSaveCheckBoxCtrl->GetValue(), CONTROL_GROUP_SAVE); +} + +void TimerRecordDialog::OnAutoExportCheckBox_Change(wxCommandEvent& WXUNUSED(event)) { + EnableDisableAutoControls(m_pTimerAutoExportCheckBoxCtrl->GetValue(), CONTROL_GROUP_EXPORT); +} + void TimerRecordDialog::OnOK(wxCommandEvent& WXUNUSED(event)) { this->TransferDataFromWindow(); @@ -220,6 +317,23 @@ void TimerRecordDialog::OnOK(wxCommandEvent& WXUNUSED(event)) return; } + // Validate that we have a Save and/or Export path setup if the appropriate check box is ticked + wxString sTemp = m_fnAutoSaveFile.GetFullPath(); + if (m_pTimerAutoSaveCheckBoxCtrl->IsChecked()) { + if (!m_fnAutoSaveFile.IsOk() || m_fnAutoSaveFile.IsDir()) { + wxMessageBox(_("Automatic Save path is invalid."), + _("Error in Automatic Save"), wxICON_EXCLAMATION | wxOK); + return; + } + } + if (m_pTimerAutoExportCheckBoxCtrl->IsChecked()) { + if (!m_fnAutoExportFile.IsOk() || m_fnAutoExportFile.IsDir()) { + wxMessageBox(_("Automatic Export path is invalid."), + _("Error in Automatic Export"), wxICON_EXCLAMATION | wxOK); + 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(); @@ -228,24 +342,88 @@ void TimerRecordDialog::OnOK(wxCommandEvent& WXUNUSED(event)) gPrefs->Flush(); } -///Runs the wait for start dialog. Returns false if the user clicks stop while we are recording -///so that the high -// <ANSWER-ME: so that the "high" what does what?> -bool TimerRecordDialog::RunWaitDialog() +void TimerRecordDialog::EnableDisableAutoControls(bool bEnable, int iControlGoup) { + + if (iControlGoup == CONTROL_GROUP_EXPORT) { + m_pTimerExportPathTextCtrl->Enable( bEnable ); + m_pTimerExportPathButtonCtrl->Enable( bEnable); + } else if (iControlGoup == CONTROL_GROUP_SAVE) { + m_pTimerSavePathTextCtrl->Enable( bEnable); + m_pTimerSavePathButtonCtrl->Enable(bEnable ); + } + + // Enable or disable the Choice box - if there is no Save or Export then this will be disabled + if (m_pTimerAutoSaveCheckBoxCtrl->GetValue() || m_pTimerAutoExportCheckBoxCtrl->GetValue()) { + m_pTimerAfterCompleteChoiceCtrl->Enable(); + } else { + m_pTimerAfterCompleteChoiceCtrl->SetSelection(POST_TIMER_RECORD_NOTHING); + m_pTimerAfterCompleteChoiceCtrl->Disable(); + } +} + +void TimerRecordDialog::UpdateTextBoxControls() { + // Will update the text box controls + m_pTimerSavePathTextCtrl->SetValue(m_fnAutoSaveFile.GetFullPath()); + m_pTimerExportPathTextCtrl->SetValue(m_fnAutoExportFile.GetFullPath()); + + // MY: Ensure we still display "Current Project" if this has already been saved + if (m_bProjectAlreadySaved) { + m_pTimerSavePathTextCtrl->SetValue(_("Current Project")); + } +} + +// Copied from AutoRecovery.cpp - for use with Timer Recording Improvements +bool TimerRecordDialog::HaveFilesToRecover() +{ + wxDir dir(FileNames::AutoSaveDir()); + if (!dir.IsOpened()) { + wxMessageBox(_("Could not enumerate files in auto save directory."), + _("Error"), wxICON_STOP); + return false; + } + + wxString filename; + bool c = dir.GetFirst(&filename, wxT("*.autosave"), wxDIR_FILES); + + return c; +} + +bool TimerRecordDialog::RemoveAllAutoSaveFiles() +{ + wxArrayString files; + wxDir::GetAllFiles(FileNames::AutoSaveDir(), &files, + wxT("*.autosave"), wxDIR_FILES); + + for (unsigned int i = 0; i < files.GetCount(); i++) + { + if (!wxRemoveFile(files[i])) + { + // I don't think this error message is actually useful. + // -dmazzoni + //wxMessageBox(wxT("Could not remove auto save file: " + files[i]), + // _("Error"), wxICON_STOP); + return false; + } + } + + return true; +} + +/// Runs the wait for start dialog. Returns -1 if the user clicks stop while we are recording +/// or if the post recording actions fail. +int TimerRecordDialog::RunWaitDialog() { AudacityProject* pProject = GetActiveProject(); + int updateResult = eProgressSuccess; if (m_DateTime_Start > wxDateTime::UNow()) updateResult = this->WaitForStart(); - if (updateResult != eProgressSuccess) - { + if (updateResult != eProgressSuccess) { // Don't proceed, but don't treat it as canceled recording. User just canceled waiting. - return true; - } - else - { + return POST_TIMER_RECORD_CANCEL_WAIT; + } else { // Record for specified time. pProject->OnRecord(); bool bIsRecording = true; @@ -268,10 +446,9 @@ bool TimerRecordDialog::RunWaitDialog() this->OnTimer(dummyTimerEvent); // Loop for progress display during recording. - while (bIsRecording && (updateResult == eProgressSuccess)) - { - wxMilliSleep(kTimerInterval); + while (bIsRecording && (updateResult == eProgressSuccess)) { updateResult = progress.Update(); + wxMilliSleep(kTimerInterval); bIsRecording = (wxDateTime::UNow() <= m_DateTime_End); // Call UNow() again for extra accuracy... } } @@ -282,14 +459,131 @@ bool TimerRecordDialog::RunWaitDialog() // Let the caller handle cancellation or failure from recording progress. if (updateResult == eProgressCancelled || updateResult == eProgressFailed) - return false; + return POST_TIMER_RECORD_CANCEL; - // Success, so let's automatically save it, for safety's sake. - // If user hadn't saved it before, they'll see the Save As dialog. - // If user had saved it before, it will safely be saved, automatically. - pProject->Save(); + return ExecutePostRecordActions((updateResult == eProgressStopped)); +} - return true; +int TimerRecordDialog::ExecutePostRecordActions(bool bWasStopped) { + // MY: We no longer automatically (and silently) call ->Save() when the + // timer recording is completed. We can now Save and/or Export depending + // on the options selected by the user. + // Once completed, we can also close Audacity, restart the system or + // shutdown the system. + // If there was any error with the auto save or export then we will not do + // the actions requested and instead present an error mesasge to the user. + // Finally, if there is no post-record action selected then we output + // a dialog detailing what has been carried out instead. + + AudacityProject* pProject = GetActiveProject(); + + bool bSaveOK = false; + bool bExportOK = false; + int iPostRecordAction = m_pTimerAfterCompleteChoiceCtrl->GetSelection(); + int iOverriddenAction = iPostRecordAction; + bool bErrorOverride = false; + + // Do Automatic Save? + if (m_bAutoSaveEnabled) { + + // MY: If this project has already been saved then simply execute a Save here + if (m_bProjectAlreadySaved) { + bSaveOK = pProject->Save(); + } else { + bSaveOK = pProject->SaveFromTimerRecording(m_fnAutoSaveFile); + } + } + + // Do Automatic Export? + if (m_bAutoExportEnabled) { + bExportOK = pProject->ExportFromTimerRecording(m_fnAutoExportFile, m_iAutoExportFormat, + m_iAutoExportSubFormat, m_iAutoExportFilterIndex); + } + + // Check if we need to override the post recording action + bErrorOverride = ((m_bAutoSaveEnabled && !bSaveOK) || (m_bAutoExportEnabled && !bExportOK)); + if (bErrorOverride || bWasStopped) { + iPostRecordAction = POST_TIMER_RECORD_NOTHING; + } + + if (iPostRecordAction == POST_TIMER_RECORD_NOTHING) { + // If there is no post-record action then we can show a message indicating what has been done + + wxString sMessage = (bWasStopped ? _("Timer Recording Stopped.") : + _("Timer Recording Completed.")); + + if (m_bAutoSaveEnabled) { + if (bSaveOK) { + sMessage.Printf("%s\n\nRecording saved: %s", + sMessage, m_fnAutoSaveFile.GetFullPath()); + } else { + sMessage.Printf("%s\n\nError saving recording.", sMessage); + } + } + if (m_bAutoExportEnabled) { + if (bExportOK) { + sMessage.Printf("%s\n\nRecording exported: %s", + sMessage, m_fnAutoExportFile.GetFullPath()); + } else { + sMessage.Printf("%s\n\nError exporting recording.", sMessage); + } + } + + if (bErrorOverride) { + + if ((iOverriddenAction != iPostRecordAction) && + (iOverriddenAction != POST_TIMER_RECORD_NOTHING)) { + // Inform the user that we have overridden the selected action + sMessage.Printf("%s\n\n'%s' has been canceled due to the error(s) noted above.", + sMessage, + m_pTimerAfterCompleteChoiceCtrl->GetString(iOverriddenAction)); + } + + // Show Error Message Box + wxMessageBox(sMessage, _("Error"), wxICON_EXCLAMATION | wxOK); + } else { + + if (bWasStopped && (iOverriddenAction != POST_TIMER_RECORD_NOTHING)) { + sMessage.Printf("%s\n\n'%s' has been cancelled as the recording was stopped.", + sMessage, + m_pTimerAfterCompleteChoiceCtrl->GetString(iOverriddenAction)); + } + + wxMessageBox(sMessage, _("Timer Recording"), wxICON_INFORMATION | wxOK); + } + } + + // MY: Lets do some actions that only apply to Exit/Restart/Shutdown + if (iPostRecordAction >= POST_TIMER_RECORD_CLOSE) { + do { + // 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)); + if (iDelayOutcome != eProgressSuccess) { + // Cancel the action! + iPostRecordAction = POST_TIMER_RECORD_NOTHING; + // Set this to true to avoid any chance of the temp files being deleted + bErrorOverride = true; + break; + } + + // If we have simply recorded, exported and then plan to Exit/Restart/Shutdown + // then we will have a temporary project setup. Let's get rid of that! + if (m_bAutoExportEnabled && !m_bAutoSaveEnabled) { + DirManager::CleanTempDir(); + } + } while (false); + } + + // Do we need to cleanup the orphaned temporary project? + if (m_bProjectCleanupRequired && !bErrorOverride) { + RemoveAllAutoSaveFiles(); + } + + // Return the action as required + return iPostRecordAction; } wxString TimerRecordDialog::GetDisplayDate( wxDateTime & dt ) @@ -341,84 +635,166 @@ wxPrintf(wxT("%s\n"), dt.Format().c_str()); return dt.FormatDate() + wxT(" ") + dt.FormatTime(); } +TimerRecordPathCtrl * TimerRecordDialog::NewPathControl(wxWindow *wParent, const int iID, + const wxString &sCaption, const wxString &sValue) +{ + TimerRecordPathCtrl * pTextCtrl; + pTextCtrl = safenew TimerRecordPathCtrl(wParent, iID, sValue); + pTextCtrl->SetName(sCaption); + return pTextCtrl; +} + void TimerRecordDialog::PopulateOrExchange(ShuttleGui& S) { + bool bAutoSave = gPrefs->ReadBool("/TimerRecord/AutoSave", false); + bool bAutoExport = gPrefs->ReadBool("/TimerRecord/AutoExport", false); + int iPostTimerRecordAction = gPrefs->ReadLong("/TimerRecord/PostAction", 0); + S.SetBorder(5); - S.StartVerticalLay(true); + S.StartMultiColumn(2, wxCENTER); { - /* i18n-hint: This string is used to configure the controls for times when the recording is - * started and stopped. As such it is important that only the alphabetic parts of the string - * are translated, with the numbers left exactly as they are. - * The 'h' indicates the first number displayed is hours, the 'm' indicates the second number - * displayed is minutes, and the 's' indicates that the third number displayed is seconds. - */ - wxString strFormat = _("099 h 060 m 060 s"); - S.StartStatic(_("Start Date and Time"), true); + S.StartVerticalLay(true); { - m_pDatePickerCtrl_Start = - safenew wxDatePickerCtrl(this, // wxWindow *parent, - ID_DATEPICKER_START, // wxWindowID id, - m_DateTime_Start); // const wxDateTime& dt = wxDefaultDateTime, - // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDP_DEFAULT | wxDP_SHOWCENTURY, const wxValidator& validator = wxDefaultValidator, const wxString& name = "datectrl") - m_pDatePickerCtrl_Start->SetName(_("Start Date")); - m_pDatePickerCtrl_Start->SetRange(wxDateTime::Today(), wxInvalidDateTime); // No backdating. - S.AddWindow(m_pDatePickerCtrl_Start); + /* i18n-hint: This string is used to configure the controls for times when the recording is + * started and stopped. As such it is important that only the alphabetic parts of the string + * are translated, with the numbers left exactly as they are. + * The 'h' indicates the first number displayed is hours, the 'm' indicates the second number + * displayed is minutes, and the 's' indicates that the third number displayed is seconds. + */ + wxString strFormat = _("099 h 060 m 060 s"); + S.StartStatic(_("Start Date and Time"), true); + { + m_pDatePickerCtrl_Start = + new wxDatePickerCtrl(this, // wxWindow *parent, + ID_DATEPICKER_START, // wxWindowID id, + m_DateTime_Start); // const wxDateTime& dt = wxDefaultDateTime, + // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDP_DEFAULT | wxDP_SHOWCENTURY, const wxValidator& validator = wxDefaultValidator, const wxString& name = "datectrl") + m_pDatePickerCtrl_Start->SetName(_("Start Date")); + m_pDatePickerCtrl_Start->SetRange(wxDateTime::Today(), wxInvalidDateTime); // No backdating. + S.AddWindow(m_pDatePickerCtrl_Start); - m_pTimeTextCtrl_Start = safenew NumericTextCtrl( - NumericConverter::TIME, this, ID_TIMETEXT_START); - m_pTimeTextCtrl_Start->SetName(_("Start Time")); - m_pTimeTextCtrl_Start->SetFormatString(strFormat); - m_pTimeTextCtrl_Start-> - SetValue(wxDateTime_to_AudacityTime(m_DateTime_Start)); - S.AddWindow(m_pTimeTextCtrl_Start); - m_pTimeTextCtrl_Start->EnableMenu(false); + m_pTimeTextCtrl_Start = new NumericTextCtrl( + NumericConverter::TIME, this, ID_TIMETEXT_START); + m_pTimeTextCtrl_Start->SetName(_("Start Time")); + m_pTimeTextCtrl_Start->SetFormatString(strFormat); + m_pTimeTextCtrl_Start-> + SetValue(wxDateTime_to_AudacityTime(m_DateTime_Start)); + S.AddWindow(m_pTimeTextCtrl_Start); + m_pTimeTextCtrl_Start->EnableMenu(false); + } + S.EndStatic(); + + S.StartStatic(_("End Date and Time"), true); + { + m_pDatePickerCtrl_End = + new wxDatePickerCtrl(this, // wxWindow *parent, + ID_DATEPICKER_END, // wxWindowID id, + m_DateTime_End); // const wxDateTime& dt = wxDefaultDateTime, + // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDP_DEFAULT | wxDP_SHOWCENTURY, const wxValidator& validator = wxDefaultValidator, const wxString& name = "datectrl") + m_pDatePickerCtrl_End->SetRange(m_DateTime_Start, wxInvalidDateTime); // No backdating. + m_pDatePickerCtrl_End->SetName(_("End Date")); + S.AddWindow(m_pDatePickerCtrl_End); + + m_pTimeTextCtrl_End = new NumericTextCtrl( + NumericConverter::TIME, this, ID_TIMETEXT_END); + m_pTimeTextCtrl_End->SetName(_("End Time")); + m_pTimeTextCtrl_End->SetFormatString(strFormat); + m_pTimeTextCtrl_End->SetValue(wxDateTime_to_AudacityTime(m_DateTime_End)); + S.AddWindow(m_pTimeTextCtrl_End); + m_pTimeTextCtrl_End->EnableMenu(false); + } + S.EndStatic(); + + S.StartStatic(_("Duration"), true); + { + /* i18n-hint: This string is used to configure the controls which shows the recording + * duration. As such it is important that only the alphabetic parts of the string + * are translated, with the numbers left exactly as they are. + * The string 'days' indicates that the first number in the control will be the number of days, + * then the 'h' indicates the second number displayed is hours, the 'm' indicates the third + * number displayed is minutes, and the 's' indicates that the fourth number displayed is + * seconds. + */ + wxString strFormat1 = _("099 days 024 h 060 m 060 s"); + m_pTimeTextCtrl_Duration = new NumericTextCtrl(NumericConverter::TIME, this, ID_TIMETEXT_DURATION); + m_pTimeTextCtrl_Duration->SetName(_("Duration")); + m_pTimeTextCtrl_Duration->SetFormatString(strFormat1); + m_pTimeTextCtrl_Duration->SetValue(m_TimeSpan_Duration.GetSeconds().ToDouble()); + S.AddWindow(m_pTimeTextCtrl_Duration); + m_pTimeTextCtrl_Duration->EnableMenu(false); + } + S.EndStatic(); } - S.EndStatic(); + S.EndVerticalLay(); - S.StartStatic(_("End Date and Time"), true); + S.StartVerticalLay(true); { - m_pDatePickerCtrl_End = - safenew wxDatePickerCtrl(this, // wxWindow *parent, - ID_DATEPICKER_END, // wxWindowID id, - m_DateTime_End); // const wxDateTime& dt = wxDefaultDateTime, - // const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDP_DEFAULT | wxDP_SHOWCENTURY, const wxValidator& validator = wxDefaultValidator, const wxString& name = "datectrl") - m_pDatePickerCtrl_End->SetRange(m_DateTime_Start, wxInvalidDateTime); // No backdating. - m_pDatePickerCtrl_End->SetName(_("End Date")); - S.AddWindow(m_pDatePickerCtrl_End); + S.StartStatic(_("Automatic Save"), true); + { + // If checked, the project will be saved when the recording is completed + m_pTimerAutoSaveCheckBoxCtrl = S.Id(ID_AUTOSAVE_CHECKBOX).AddCheckBox(_("Enable &Automatic Save?"), + (bAutoSave ? "true" : "false")); + S.StartMultiColumn(3, wxEXPAND); + { + wxString sInitialValue = wxT(""); + AudacityProject* pProject = GetActiveProject(); + wxString sSaveValue = pProject->GetFileName(); + if (sSaveValue != wxEmptyString) { + m_fnAutoSaveFile.Assign(sSaveValue); + sInitialValue = _("Current Project"); + } + S.AddPrompt(_("Save Project As:")); + m_pTimerSavePathTextCtrl = NewPathControl(this, ID_AUTOSAVEPATH_TEXT, _("Save Project As:"), sInitialValue); + m_pTimerSavePathTextCtrl->SetEditable(false); + S.AddWindow(m_pTimerSavePathTextCtrl); + m_pTimerSavePathButtonCtrl = S.Id(ID_AUTOSAVEPATH_BUTTON).AddButton(_("Select...")); + } + S.EndMultiColumn(); + } + S.EndStatic(); - m_pTimeTextCtrl_End = safenew NumericTextCtrl( - NumericConverter::TIME, this, ID_TIMETEXT_END); - m_pTimeTextCtrl_End->SetName(_("End Time")); - m_pTimeTextCtrl_End->SetFormatString(strFormat); - m_pTimeTextCtrl_End->SetValue(wxDateTime_to_AudacityTime(m_DateTime_End)); - S.AddWindow(m_pTimeTextCtrl_End); - m_pTimeTextCtrl_End->EnableMenu(false); - } - S.EndStatic(); + S.StartStatic(_("Automatic Export"), true); + { + m_pTimerAutoExportCheckBoxCtrl = S.Id(ID_AUTOEXPORT_CHECKBOX).AddCheckBox(_("Enable Automatic &Export?"), (bAutoExport ? "true" : "false")); + S.StartMultiColumn(3, wxEXPAND); + { + S.AddPrompt(_("Export Project As:")); + m_pTimerExportPathTextCtrl = NewPathControl(this, ID_AUTOEXPORTPATH_TEXT, _("Export Project As:"), _("")); + m_pTimerExportPathTextCtrl->SetEditable(false); + S.AddWindow(m_pTimerExportPathTextCtrl); + m_pTimerExportPathButtonCtrl = S.Id(ID_AUTOEXPORTPATH_BUTTON).AddButton(_("Select...")); + } + S.EndMultiColumn(); + } + S.EndStatic(); + + S.StartStatic(_("Options"), true); + { + + wxArrayString arrayOptions; + arrayOptions.Add(_("Do Nothing")); + arrayOptions.Add(_("Exit Audacity")); + arrayOptions.Add(_("Restart System")); + arrayOptions.Add(_("Shutdown System")); + + m_sTimerAfterCompleteOptionsArray.Add(arrayOptions.Item(0)); + m_sTimerAfterCompleteOptionsArray.Add(arrayOptions.Item(1)); +#ifdef __WINDOWS__ + m_sTimerAfterCompleteOptionsArray.Add(arrayOptions.Item(2)); + m_sTimerAfterCompleteOptionsArray.Add(arrayOptions.Item(3)); +#endif + m_sTimerAfterCompleteOption = arrayOptions.Item(iPostTimerRecordAction); + + m_pTimerAfterCompleteChoiceCtrl = S.AddChoice(_("After Recording Completes:"), + m_sTimerAfterCompleteOption, + &m_sTimerAfterCompleteOptionsArray); + } + S.EndStatic(); - S.StartStatic(_("Duration"), true); - { - /* i18n-hint: This string is used to configure the controls which shows the recording - * duration. As such it is important that only the alphabetic parts of the string - * are translated, with the numbers left exactly as they are. - * The string 'days' indicates that the first number in the control will be the number of days, - * then the 'h' indicates the second number displayed is hours, the 'm' indicates the third - * number displayed is minutes, and the 's' indicates that the fourth number displayed is - * seconds. - */ - wxString strFormat1 = _("099 days 024 h 060 m 060 s"); - m_pTimeTextCtrl_Duration = safenew NumericTextCtrl( - NumericConverter::TIME, this, ID_TIMETEXT_DURATION); - m_pTimeTextCtrl_Duration->SetName(_("Duration")); - m_pTimeTextCtrl_Duration->SetFormatString(strFormat1); - m_pTimeTextCtrl_Duration-> - SetValue(m_TimeSpan_Duration.GetSeconds().ToDouble()); - S.AddWindow(m_pTimeTextCtrl_Duration); - m_pTimeTextCtrl_Duration->EnableMenu(false); } - S.EndStatic(); + S.EndVerticalLay(); } - S.EndVerticalLay(); + S.EndMultiColumn(); S.AddStandardButtons(); @@ -426,6 +802,9 @@ void TimerRecordDialog::PopulateOrExchange(ShuttleGui& S) Fit(); SetMinSize(GetSize()); Center(); + + EnableDisableAutoControls(bAutoSave, CONTROL_GROUP_SAVE); + EnableDisableAutoControls(bAutoExport, CONTROL_GROUP_EXPORT); } bool TimerRecordDialog::TransferDataFromWindow() @@ -455,6 +834,18 @@ bool TimerRecordDialog::TransferDataFromWindow() m_TimeSpan_Duration = m_DateTime_End - m_DateTime_Start; + // Pull the settings from the auto save/export controls and write to the pref file + m_bAutoSaveEnabled = m_pTimerAutoSaveCheckBoxCtrl->GetValue(); + m_bAutoExportEnabled = m_pTimerAutoExportCheckBoxCtrl->GetValue(); + + // MY: Obtain the index from the choice control so we can save to the prefs file + int iPostRecordAction = m_pTimerAfterCompleteChoiceCtrl->GetSelection(); + + // Save the options back to the prefs file + gPrefs->Write("/TimerRecord/AutoSave", m_bAutoSaveEnabled); + gPrefs->Write("/TimerRecord/AutoExport", m_bAutoExportEnabled); + gPrefs->Write("/TimerRecord/PostAction", iPostRecordAction); + return true; } @@ -495,9 +886,52 @@ int TimerRecordDialog::WaitForStart() bool bIsRecording = false; while (updateResult == eProgressSuccess && !bIsRecording) { - wxMilliSleep(10); updateResult = progress.Update(); + wxMilliSleep(10); bIsRecording = (m_DateTime_Start <= wxDateTime::UNow()); } return updateResult; } + +// TODO: Rather than two flags, an enum with the possibilities would be better. +int TimerRecordDialog::PreActionDelay(int iActionIndex, bool bSaved, bool bExported) +{ + 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); + + wxDateTime dtNow = wxDateTime::UNow(); + wxTimeSpan tsWait = wxTimeSpan(0, 1, 0, 0); + wxDateTime dtActionTime = dtNow.Add(tsWait); + + TimerProgressDialog dlgAction(tsWait.GetMilliseconds().GetValue(), + _("Audacity Timer Record - Waiting"), + sMessage, + pdlgHideStopButton); + + int iUpdateResult = eProgressSuccess; + bool bIsTime = false; + while (iUpdateResult == eProgressSuccess && !bIsTime) + { + iUpdateResult = dlgAction.Update(); + wxMilliSleep(10); + bIsTime = (dtActionTime <= wxDateTime::UNow()); + } + return iUpdateResult; +} diff --git a/src/TimerRecordDialog.h b/src/TimerRecordDialog.h index 71cb8506f..6dbd8f2c3 100644 --- a/src/TimerRecordDialog.h +++ b/src/TimerRecordDialog.h @@ -17,24 +17,46 @@ #define __AUDACITY_TIMERRECORD_DIALOG__ #include <wx/dialog.h> +#include <wx/textctrl.h> #include <wx/datectrl.h> #include <wx/calctrl.h> #include <wx/timer.h> +#include <wx/checkbox.h> +#include "export/Export.h" class wxTimerEvent; class NumericTextCtrl; class ShuttleGui; +class TimerRecordPathCtrl; + +class TimerRecordPathCtrl final : public wxTextCtrl +{ + // MY: Class that inherits from the wxTextCtrl class. + // We override AcceptsFocusFromKeyboard in order to add + // the text controls to the Tab Order. +public: + TimerRecordPathCtrl(wxWindow * parent, wxWindowID id, const wxString &value + = wxEmptyString, const wxPoint &pos = wxDefaultPosition, const wxSize & + size = wxDefaultSize, long style = 0, const wxValidator & validator = + wxDefaultValidator, const wxString & name = wxTextCtrlNameStr) + :wxTextCtrl(parent, id, value, pos, size, style, validator, name) {}; + ~TimerRecordPathCtrl() {}; + + virtual bool AcceptsFocusFromKeyboard() const override { + return true; + } +}; class TimerRecordDialog final : public wxDialog { public: - TimerRecordDialog(wxWindow* parent); + TimerRecordDialog(wxWindow* parent, bool bAlreadySaved); ~TimerRecordDialog(); void OnTimer(wxTimerEvent& event); ///Runs the wait for start dialog. Returns false if the user clicks stop. - bool RunWaitDialog(); + int RunWaitDialog(); private: void OnDatePicker_Start(wxDateEvent& event); @@ -54,6 +76,25 @@ private: void UpdateEnd(); // Update m_DateTime_End and ctrls based on m_DateTime_Start and m_TimeSpan_Duration. int WaitForStart(); + // Timer Recording Automation Control Events + void OnAutoSavePathButton_Click(wxCommandEvent& event); + void OnAutoExportPathButton_Click(wxCommandEvent& event); + void OnAutoSaveCheckBox_Change(wxCommandEvent& event); + void OnAutoExportCheckBox_Change(wxCommandEvent& event); + // Timer Recording Automation Routines + void EnableDisableAutoControls(bool bEnable, int iControlGoup); + void UpdateTextBoxControls(); + // Tidy up after Timer Recording + bool HaveFilesToRecover(); + bool RemoveAllAutoSaveFiles(); + + // Add Path Controls to Form + TimerRecordPathCtrl *NewPathControl(wxWindow *wParent, const int iID, const wxString &sCaption, const wxString &sValue); + + int ExecutePostRecordActions(bool bWasStopped); + + int PreActionDelay(int iActionIndex, bool bSaved, bool bExported); + private: wxDateTime m_DateTime_Start; wxDateTime m_DateTime_End; @@ -70,6 +111,34 @@ private: wxTimer m_timer; + // Controls for Auto Save/Export + wxCheckBox *m_pTimerAutoSaveCheckBoxCtrl; + TimerRecordPathCtrl *m_pTimerSavePathTextCtrl; + wxButton *m_pTimerSavePathButtonCtrl; + wxCheckBox *m_pTimerAutoExportCheckBoxCtrl; + TimerRecordPathCtrl *m_pTimerExportPathTextCtrl; + wxButton *m_pTimerExportPathButtonCtrl; + + // After Timer Record Options Choice + wxChoice *m_pTimerAfterCompleteChoiceCtrl; + + // After Timer Record do we need to clean up? + bool m_bProjectCleanupRequired; + + // Variables for the Auto Save/Export + bool m_bAutoSaveEnabled; + wxFileName m_fnAutoSaveFile; + bool m_bAutoExportEnabled; + wxFileName m_fnAutoExportFile; + int m_iAutoExportFormat; + int m_iAutoExportSubFormat; + int m_iAutoExportFilterIndex; + bool m_bProjectAlreadySaved; + + // Variables for After Timer Recording Option + wxString m_sTimerAfterCompleteOption; + wxArrayString m_sTimerAfterCompleteOptionsArray; + DECLARE_EVENT_TABLE(); }; diff --git a/src/export/Export.cpp b/src/export/Export.cpp index 7c4860de9..eca012ce1 100644 --- a/src/export/Export.cpp +++ b/src/export/Export.cpp @@ -640,9 +640,9 @@ bool Exporter::GetFilename() !mFilename.FileExists()) { // Warn and return to the dialog wxMessageBox(_("You are attempting to overwrite an aliased file that is missing.\n\ -The file cannot be written because the path is needed to restore the original audio to the project.\n\ -Choose File > Check Dependencies to view the locations of all missing files.\n\ -If you still wish to export, please choose a different filename or folder.")); + The file cannot be written because the path is needed to restore the original audio to the project.\n\ + Choose File > Check Dependencies to view the locations of all missing files.\n\ + If you still wish to export, please choose a different filename or folder.")); overwritingMissingAlias = true; } } @@ -886,6 +886,88 @@ void Exporter::OnFilterChanged(wxFileCtrlEvent & evt) mBook->ChangeSelection(index); } +bool Exporter::ProcessFromTimerRecording(AudacityProject *project, + bool selectedOnly, + double t0, + double t1, + wxFileName fnFile, + int iFormat, + int iSubFormat, + int iFilterIndex) +{ + // Save parms + mProject = project; + mSelectedOnly = selectedOnly; + mT0 = t0; + mT1 = t1; + + // Auto Export Parameters + mFilename = fnFile; + mFormat = iFormat; + mSubFormat = iSubFormat; + mFilterIndex = iFilterIndex; + + // Gather track information + if (!ExamineTracks()) { + return false; + } + + // Check for down mixing + if (!CheckMix()) { + return false; + } + + // Ensure filename doesn't interfere with project files. + if (!CheckFilename()) { + return false; + } + + // Export the tracks + bool success = ExportTracks(); + + // Get rid of mixerspec + if (mMixerSpec) { + delete mMixerSpec; + mMixerSpec = NULL; + } + + return success; +} + +int Exporter::GetAutoExportFormat() { + return mFormat; +} + +int Exporter::GetAutoExportSubFormat() { + return mSubFormat; +} + +int Exporter::GetAutoExportFilterIndex() { + return mFormat; +} + +wxFileName Exporter::GetAutoExportFileName() { + return mFilename; +} + +bool Exporter::SetAutoExportOptions(AudacityProject *project) { + mFormat = -1; + mProject = project; + + if( GetFilename()==false ) + return false; + + // Let user edit MetaData + if (mPlugins[mFormat]->GetCanMetaData(mSubFormat)) { + if (!(project->DoEditMetadata(_("Edit Metadata Tags for Export"), + _("Exported Tags"), mProject->GetShowId3Dialog()))) { + return false; + } + } + + return true; +} + //---------------------------------------------------------------------------- // ExportMixerPanel //---------------------------------------------------------------------------- diff --git a/src/export/Export.h b/src/export/Export.h index 9bd50b1be..7964ca8d9 100644 --- a/src/export/Export.h +++ b/src/export/Export.h @@ -153,6 +153,21 @@ public: const ExportPluginArray GetPlugins(); + // Auto Export from Timer Recording + bool ProcessFromTimerRecording(AudacityProject *project, + bool selectedOnly, + double t0, + double t1, + wxFileName fnFile, + int iFormat, + int iSubFormat, + int iFilterIndex); + bool SetAutoExportOptions(AudacityProject *project); + int GetAutoExportFormat(); + int GetAutoExportSubFormat(); + int GetAutoExportFilterIndex(); + wxFileName GetAutoExportFileName(); + private: bool ExamineTracks(); diff --git a/src/widgets/ProgressDialog.cpp b/src/widgets/ProgressDialog.cpp index 73b2f1f79..64c0bb2b3 100644 --- a/src/widgets/ProgressDialog.cpp +++ b/src/widgets/ProgressDialog.cpp @@ -1569,7 +1569,14 @@ int TimerProgressDialog::Update(const wxString & message /*= wxEmptyString*/) // From testing, it's never shown bigger than 1009, but // give it a little extra, to 1010. // wxASSERT((nGaugeValue >= 0) && (nGaugeValue <= 1000)); // This ought to work. - wxASSERT((nGaugeValue >= 0) && (nGaugeValue <= 1010)); + // wxASSERT((nGaugeValue >= 0) && (nGaugeValue <= 1010)); + // + // stf. Update was being called after wxMilliSleep(<ms>), which could be up to <ms> + // beyond the completion time. My gusess is that the microsleep in RunWaitDialog was originally 10 ms + // (same as other uses of Update) but was updated to kTimerInterval = 50 ms, thus triggering + // the Assert (Bug 1367). By calling Update() before sleeping then I think nGaugeValue <= 1000 should work. + wxASSERT((nGaugeValue >= 0) && (nGaugeValue <= 1000)); + if (nGaugeValue != mLastValue) { mGauge->SetValue(nGaugeValue);