X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/b00403b401b75f95b6984cf5d0f6d71492f0a1f9..d5947fad64ffb5bd695620fc82e5d4f20d51fb47:/src/msw/progdlg.cpp?ds=inline diff --git a/src/msw/progdlg.cpp b/src/msw/progdlg.cpp index 425e2a8cf8..3f051ac95e 100644 --- a/src/msw/progdlg.cpp +++ b/src/msw/progdlg.cpp @@ -27,6 +27,7 @@ #include "wx/msw/private/msgdlg.h" #include "wx/progdlg.h" +#include "wx/evtloop.h" using namespace wxMSWMessageDialog; @@ -86,7 +87,7 @@ public: wxString m_labelCancel; // Privately used by callback. unsigned long m_timeStop; - wxGenericProgressDialog::ProgressDialogState m_state; + wxProgressDialog::State m_state; bool m_progressBarMarquee; bool m_skipped; @@ -100,16 +101,14 @@ public: class wxProgressDialogTaskRunner : public wxThread { public: - wxProgressDialogTaskRunner(wxWindow* parent) - : wxThread(wxTHREAD_JOINABLE), - m_parent(parent) + wxProgressDialogTaskRunner() + : wxThread(wxTHREAD_JOINABLE) { } wxProgressDialogSharedData* GetSharedDataObject() { return &m_sharedData; } private: - wxWindow* m_parent; wxProgressDialogSharedData m_sharedData; virtual void* Entry(); @@ -121,16 +120,43 @@ private: LONG_PTR dwRefData); }; +namespace +{ + +// A custom event loop which runs until the state of the dialog becomes +// "Dismissed". +class wxProgressDialogModalLoop : public wxEventLoop +{ +public: + wxProgressDialogModalLoop(wxProgressDialogSharedData& data) + : m_data(data) + { + } + +protected: + virtual void OnNextIteration() + { + wxCriticalSectionLocker locker(m_data.m_cs); + + if ( m_data.m_state == wxProgressDialog::Dismissed ) + Exit(); + } + + wxProgressDialogSharedData& m_data; + + wxDECLARE_NO_COPY_CLASS(wxProgressDialogModalLoop); +}; + // ============================================================================ // Helper functions // ============================================================================ -namespace -{ - +// This function returns true if the progress dialog with the given style +// (combination of wxPD_XXX constants) needs the "Close" button and this button +// only, i.e. not a "Cancel" one. bool UsesCloseButtonOnly(long style) { - return !((style & wxPD_CAN_ABORT) || (style & wxPD_AUTO_HIDE)); + return !(style & (wxPD_CAN_ABORT | wxPD_AUTO_HIDE)); } BOOL CALLBACK DisplayCloseButton(HWND hwnd, LPARAM lParam) @@ -248,7 +274,7 @@ void PerformNotificationUpdates(HWND hwnd, // Is the progress finished? if ( sharedData->m_notifications & wxSPDD_FINISHED ) { - sharedData->m_state = wxGenericProgressDialog::Finished; + sharedData->m_state = wxProgressDialog::Finished; if ( !(sharedData->m_style & wxPD_AUTO_HIDE) ) { @@ -274,7 +300,7 @@ wxProgressDialog::wxProgressDialog( const wxString& title, int maximum, wxWindow *parent, int style ) - : wxGenericProgressDialog(parent, maximum, style), + : wxGenericProgressDialog(parent, style), m_taskDialogRunner(NULL), m_sharedData(NULL), m_message(message), @@ -283,6 +309,8 @@ wxProgressDialog::wxProgressDialog( const wxString& title, #ifdef wxHAS_MSW_TASKDIALOG if ( HasNativeProgressDialog() ) { + SetMaximum(maximum); + Show(); DisableOtherWindows(); @@ -321,53 +349,65 @@ bool wxProgressDialog::Update(int value, const wxString& newmsg, bool *skip) #ifdef wxHAS_MSW_TASKDIALOG if ( HasNativeProgressDialog() ) { - wxCriticalSectionLocker locker(m_sharedData->m_cs); + { + wxCriticalSectionLocker locker(m_sharedData->m_cs); - // Do nothing in canceled state. - if ( !DoNativeBeforeUpdate(skip) ) - return false; + // Do nothing in canceled state. + if ( !DoNativeBeforeUpdate(skip) ) + return false; - value /= m_factor; + value /= m_factor; - wxASSERT_MSG( value <= m_maximum, wxT("invalid progress value") ); + wxASSERT_MSG( value <= m_maximum, wxT("invalid progress value") ); - m_sharedData->m_value = value; - m_sharedData->m_notifications |= wxSPDD_VALUE_CHANGED; + m_sharedData->m_value = value; + m_sharedData->m_notifications |= wxSPDD_VALUE_CHANGED; - if ( !newmsg.empty() ) - { - m_message = newmsg; - m_sharedData->m_message = newmsg; - m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED; - } + if ( !newmsg.empty() ) + { + m_message = newmsg; + m_sharedData->m_message = newmsg; + m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED; + } - if ( m_sharedData->m_progressBarMarquee ) - { - m_sharedData->m_progressBarMarquee = false; - m_sharedData->m_notifications |= wxSPDD_PBMARQUEE_CHANGED; - } + if ( m_sharedData->m_progressBarMarquee ) + { + m_sharedData->m_progressBarMarquee = false; + m_sharedData->m_notifications |= wxSPDD_PBMARQUEE_CHANGED; + } - UpdateExpandedInformation( value ); + UpdateExpandedInformation( value ); + + // If we didn't just reach the finish, all we have to do is to + // return true if the dialog wasn't cancelled and false otherwise. + if ( value != m_maximum || m_state == Finished ) + return m_sharedData->m_state != Canceled; - // Has the progress bar finished? - if ( value == m_maximum ) - { - if ( m_state == Finished ) - return true; + // On finishing, the dialog without wxPD_AUTO_HIDE style becomes a + // modal one meaning that we must block here until the user + // dismisses it. m_state = Finished; m_sharedData->m_state = Finished; m_sharedData->m_notifications |= wxSPDD_FINISHED; - if( !HasFlag(wxPD_AUTO_HIDE) && newmsg.empty() ) + if ( HasPDFlag(wxPD_AUTO_HIDE) ) + return true; + + if ( newmsg.empty() ) { // Provide the finishing message if the application didn't. m_message = _("Done."); m_sharedData->m_message = m_message; m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED; } - } - - return m_sharedData->m_state != Canceled; + } // unlock m_sharedData->m_cs + + // We only get here when we need to wait for the dialog to terminate so + // do just this by running a custom event loop until the dialog is + // dismissed. + wxProgressDialogModalLoop loop(*m_sharedData); + loop.Run(); + return true; } #endif // wxHAS_MSW_TASKDIALOG @@ -454,7 +494,7 @@ void wxProgressDialog::Resume() // it now. m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP; - if ( !UsesCloseButtonOnly(m_windowStyle) ) + if ( !UsesCloseButtonOnly(GetPDStyle()) ) m_sharedData->m_notifications |= wxSPDD_ENABLE_ABORT; hwnd = m_sharedData->m_hwnd; @@ -500,17 +540,21 @@ wxString wxProgressDialog::GetMessage() const void wxProgressDialog::SetRange(int maximum) { - wxGenericProgressDialog::SetRange( maximum ); - #ifdef wxHAS_MSW_TASKDIALOG if ( HasNativeProgressDialog() ) { + SetMaximum(maximum); + wxCriticalSectionLocker locker(m_sharedData->m_cs); m_sharedData->m_range = maximum; m_sharedData->m_notifications |= wxSPDD_RANGE_CHANGED; + + return; } #endif // wxHAS_MSW_TASKDIALOG + + wxGenericProgressDialog::SetRange( maximum ); } bool wxProgressDialog::WasSkipped() const @@ -586,7 +630,7 @@ bool wxProgressDialog::Show(bool show) // We're showing the dialog for the first time, create the thread that // will manage it. - m_taskDialogRunner = new wxProgressDialogTaskRunner(GetParent()); + m_taskDialogRunner = new wxProgressDialogTaskRunner; m_sharedData = m_taskDialogRunner->GetSharedDataObject(); // Initialize shared data. @@ -594,21 +638,21 @@ bool wxProgressDialog::Show(bool show) m_sharedData->m_message = m_message; m_sharedData->m_range = m_maximum; m_sharedData->m_state = Uncancelable; - m_sharedData->m_style = m_windowStyle; + m_sharedData->m_style = GetPDStyle(); - if ( HasFlag(wxPD_CAN_ABORT) ) + if ( HasPDFlag(wxPD_CAN_ABORT) ) { m_sharedData->m_state = Continue; m_sharedData->m_labelCancel = _("Cancel"); } - else if ( !HasFlag(wxPD_AUTO_HIDE) ) + else if ( !HasPDFlag(wxPD_AUTO_HIDE) ) { m_sharedData->m_labelCancel = _("Close"); } - if ( m_windowStyle & (wxPD_ELAPSED_TIME - | wxPD_ESTIMATED_TIME - | wxPD_REMAINING_TIME) ) + if ( HasPDFlag(wxPD_ELAPSED_TIME | + wxPD_ESTIMATED_TIME | + wxPD_REMAINING_TIME) ) { // Use a non-empty string just to have the collapsible pane shown. m_sharedData->m_expandedInformation = " "; @@ -627,10 +671,6 @@ bool wxProgressDialog::Show(bool show) return false; } - if ( !HasFlag(wxPD_APP_MODAL) ) - GetParent()->Disable(); - //else: otherwise all windows will be disabled by m_taskDialogRunner - // Do not show the underlying dialog. return false; } @@ -642,12 +682,12 @@ bool wxProgressDialog::Show(bool show) bool wxProgressDialog::HasNativeProgressDialog() const { #ifdef wxHAS_MSW_TASKDIALOG - // For a native implementation task dialogs are required, which - // also require at least one button to be present so the flags needs - // to be checked as well to see if this is the case. + // Native task dialog, if available, can't be used without any buttons so + // we fall back to the generic one if none of "Skip", "Cancel" and "Close" + // buttons is used. return HasNativeTaskDialog() - && ((m_windowStyle & (wxPD_CAN_SKIP | wxPD_CAN_ABORT)) - || !(m_windowStyle & wxPD_AUTO_HIDE)); + && (HasPDFlag(wxPD_CAN_SKIP | wxPD_CAN_ABORT) || + !HasPDFlag(wxPD_AUTO_HIDE)); #else // !wxHAS_MSW_TASKDIALOG // This shouldn't be even called in !wxHAS_MSW_TASKDIALOG case but as we // still must define the function as returning something, return false. @@ -676,14 +716,14 @@ void wxProgressDialog::UpdateExpandedInformation(int value) wxString expandedInformation; // Calculate the three different timing values. - if ( m_windowStyle & wxPD_ELAPSED_TIME ) + if ( HasPDFlag(wxPD_ELAPSED_TIME) ) { expandedInformation << GetElapsedLabel() << " " << GetFormattedTime(elapsedTime); } - if ( m_windowStyle & wxPD_ESTIMATED_TIME ) + if ( HasPDFlag(wxPD_ESTIMATED_TIME) ) { if ( !expandedInformation.empty() ) expandedInformation += "\n"; @@ -693,7 +733,7 @@ void wxProgressDialog::UpdateExpandedInformation(int value) << GetFormattedTime(realEstimatedTime); } - if ( m_windowStyle & wxPD_REMAINING_TIME ) + if ( HasPDFlag(wxPD_REMAINING_TIME) ) { if ( !expandedInformation.empty() ) expandedInformation += "\n"; @@ -753,9 +793,7 @@ void* wxProgressDialogTaskRunner::Entry() m_sharedData.m_labelCancel ); } - tdc.dwFlags |= TDF_CALLBACK_TIMER - | TDF_SHOW_PROGRESS_BAR - | TDF_SHOW_MARQUEE_PROGRESS_BAR; + tdc.dwFlags |= TDF_CALLBACK_TIMER | TDF_SHOW_PROGRESS_BAR; if ( !m_sharedData.m_expandedInformation.empty() ) { @@ -773,6 +811,10 @@ void* wxProgressDialogTaskRunner::Entry() if ( FAILED(hr) ) wxLogApiError( "TaskDialogIndirect", hr ); + // If the main thread is waiting for us to exit inside the event loop in + // Update(), wake it up so that it checks our status again. + wxWakeUpIdle(); + return NULL; } @@ -817,9 +859,13 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc return TRUE; case IDCANCEL: - if ( sharedData->m_state - == wxGenericProgressDialog::Finished ) + if ( sharedData->m_state == wxProgressDialog::Finished ) { + // If the main thread is waiting for us, tell it that + // we're gone (and if it doesn't wait, it's harmless). + sharedData->m_state = wxProgressDialog::Dismissed; + + // Let Windows close the dialog. return FALSE; } @@ -828,16 +874,18 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc // a finished dialog. if ( !UsesCloseButtonOnly(sharedData->m_style) ) { - wxCHECK_MSG( sharedData->m_state == - wxGenericProgressDialog::Continue, - TRUE, - "Dialog not in a cancelable state!"); + wxCHECK_MSG + ( + sharedData->m_state == wxProgressDialog::Continue, + TRUE, + "Dialog not in a cancelable state!" + ); ::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE); ::SendMessage(hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE); sharedData->m_timeStop = wxGetCurrentTime(); - sharedData->m_state = wxGenericProgressDialog::Canceled; + sharedData->m_state = wxProgressDialog::Canceled; } return TRUE; @@ -847,22 +895,19 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc case TDN_TIMER: PerformNotificationUpdates(hwnd, sharedData); - // End dialog in three different cases: - // 1. Progress finished and dialog should automatically hide. - // 2. The wxProgressDialog object was destructed and should - // automatically hide. - // 3. The dialog was canceled and wxProgressDialog object - // was destroyed. - bool isCanceled = - sharedData->m_state == wxGenericProgressDialog::Canceled; - bool isFinished = - sharedData->m_state == wxGenericProgressDialog::Finished; - bool wasDestroyed = - (sharedData->m_notifications & wxSPDD_DESTROYED) != 0; - bool shouldAutoHide = (sharedData->m_style & wxPD_AUTO_HIDE) != 0; - - if ( (shouldAutoHide && (isFinished || wasDestroyed)) - || (wasDestroyed && isCanceled) ) + /* + Decide whether we should end the dialog. This is done if either + the dialog object itself was destroyed or if the progress + finished and we were configured to hide automatically without + waiting for the user to dismiss us. + + Notice that we do not close the dialog if it was cancelled + because it's up to the user code in the main thread to decide + whether it really wants to cancel the dialog. + */ + if ( (sharedData->m_notifications & wxSPDD_DESTROYED) || + (sharedData->m_state == wxProgressDialog::Finished && + sharedData->m_style & wxPD_AUTO_HIDE) ) { ::EndDialog( hwnd, IDCLOSE ); }