X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/a5655d37db9baabce654849fd66173f95f74e230..4f082fb3a0685321b174f8b7beb8ea357da227a2:/src/msw/progdlg.cpp diff --git a/src/msw/progdlg.cpp b/src/msw/progdlg.cpp index 1e8bd48d6a..50c3f5b974 100644 --- a/src/msw/progdlg.cpp +++ b/src/msw/progdlg.cpp @@ -25,9 +25,18 @@ #if wxUSE_PROGRESSDLG && wxUSE_THREADS -#include "wx/msw/private/msgdlg.h" #include "wx/progdlg.h" +#ifndef WX_PRECOMP + #include "wx/app.h" + #include "wx/msgdlg.h" + #include "wx/stopwatch.h" + #include "wx/msw/private.h" +#endif + +#include "wx/msw/private/msgdlg.h" +#include "wx/evtloop.h" + using namespace wxMSWMessageDialog; #ifdef wxHAS_MSW_TASKDIALOG @@ -72,10 +81,12 @@ public: m_progressBarMarquee = false; m_skipped = false; m_notifications = 0; + m_parent = NULL; } wxCriticalSection m_cs; + wxWindow *m_parent; // Parent window only used to center us over it. HWND m_hwnd; // Task dialog handler long m_style; // wxProgressDialog style int m_value; @@ -86,7 +97,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 +111,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,17 +130,36 @@ private: LONG_PTR dwRefData); }; -// ============================================================================ -// Helper functions -// ============================================================================ - namespace { -bool UsesCloseButtonOnly(long style) +// A custom event loop which runs until the state of the dialog becomes +// "Dismissed". +class wxProgressDialogModalLoop : public wxEventLoop { - return !((style & wxPD_CAN_ABORT) || (style & wxPD_AUTO_HIDE)); -} +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 +// ============================================================================ BOOL CALLBACK DisplayCloseButton(HWND hwnd, LPARAM lParam) { @@ -142,7 +170,7 @@ BOOL CALLBACK DisplayCloseButton(HWND hwnd, LPARAM lParam) { sharedData->m_labelCancel = _("Close"); SendMessage( hwnd, WM_SETTEXT, 0, - (LPARAM) sharedData->m_labelCancel.wx_str() ); + wxMSW_CONV_LPARAM(sharedData->m_labelCancel) ); return FALSE; } @@ -184,7 +212,7 @@ void PerformNotificationUpdates(HWND hwnd, } if ( sharedData->m_notifications & wxSPDD_TITLE_CHANGED ) - ::SetWindowText( hwnd, sharedData->m_title.wx_str() ); + ::SetWindowText( hwnd, sharedData->m_title.t_str() ); if ( sharedData->m_notifications & wxSPDD_MESSAGE_CHANGED ) { @@ -208,16 +236,21 @@ void PerformNotificationUpdates(HWND hwnd, body.assign(title, posNL + numNLs, wxString::npos); title.erase(posNL); } + else // A single line + { + // Don't use title without the body, this doesn't make sense. + title.swap(body); + } ::SendMessage( hwnd, TDM_SET_ELEMENT_TEXT, TDE_MAIN_INSTRUCTION, - (LPARAM) title.wx_str() ); + wxMSW_CONV_LPARAM(title) ); ::SendMessage( hwnd, TDM_SET_ELEMENT_TEXT, TDE_CONTENT, - (LPARAM) body.wx_str() ); + wxMSW_CONV_LPARAM(body) ); } if ( sharedData->m_notifications & wxSPDD_EXPINFO_CHANGED ) @@ -229,7 +262,7 @@ void PerformNotificationUpdates(HWND hwnd, ::SendMessage( hwnd, TDM_SET_ELEMENT_TEXT, TDE_EXPANDED_INFORMATION, - (LPARAM) expandedInformation.wx_str() ); + wxMSW_CONV_LPARAM(expandedInformation) ); } } @@ -248,7 +281,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,15 +307,19 @@ wxProgressDialog::wxProgressDialog( const wxString& title, int maximum, wxWindow *parent, int style ) - : wxGenericProgressDialog(parent, maximum, style), + : wxGenericProgressDialog(), m_taskDialogRunner(NULL), m_sharedData(NULL), m_message(message), m_title(title) { #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) { + SetTopParent(parent); + SetPDStyle(style); + SetMaximum(maximum); + Show(); DisableOtherWindows(); @@ -319,55 +356,67 @@ wxProgressDialog::~wxProgressDialog() bool wxProgressDialog::Update(int value, const wxString& newmsg, bool *skip) { #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) { - 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 @@ -377,7 +426,7 @@ bool wxProgressDialog::Update(int value, const wxString& newmsg, bool *skip) bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip) { #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) { wxCriticalSectionLocker locker(m_sharedData->m_cs); @@ -412,7 +461,7 @@ bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip) bool wxProgressDialog::DoNativeBeforeUpdate(bool *skip) { #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) { if ( m_sharedData->m_skipped ) { @@ -442,7 +491,7 @@ void wxProgressDialog::Resume() wxGenericProgressDialog::Resume(); #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) { HWND hwnd; @@ -454,7 +503,8 @@ void wxProgressDialog::Resume() // it now. m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP; - if ( !UsesCloseButtonOnly(m_windowStyle) ) + // Also re-enable "Cancel" itself + if ( HasPDFlag(wxPD_CAN_ABORT) ) m_sharedData->m_notifications |= wxSPDD_ENABLE_ABORT; hwnd = m_sharedData->m_hwnd; @@ -475,10 +525,27 @@ void wxProgressDialog::Resume() #endif // wxHAS_MSW_TASKDIALOG } +WXWidget wxProgressDialog::GetHandle() const +{ +#ifdef wxHAS_MSW_TASKDIALOG + if ( HasNativeTaskDialog() ) + { + HWND hwnd; + { + wxCriticalSectionLocker locker(m_sharedData->m_cs); + m_sharedData->m_state = m_state; + hwnd = m_sharedData->m_hwnd; + } + return hwnd; + } +#endif + return wxGenericProgressDialog::GetHandle(); +} + int wxProgressDialog::GetValue() const { #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) { wxCriticalSectionLocker locker(m_sharedData->m_cs); return m_sharedData->m_value; @@ -491,7 +558,7 @@ int wxProgressDialog::GetValue() const wxString wxProgressDialog::GetMessage() const { #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) return m_message; #endif // wxHAS_MSW_TASKDIALOG @@ -500,23 +567,27 @@ wxString wxProgressDialog::GetMessage() const void wxProgressDialog::SetRange(int maximum) { - wxGenericProgressDialog::SetRange( maximum ); - #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) { + 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 { #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) { if ( !m_sharedData ) { @@ -535,7 +606,7 @@ bool wxProgressDialog::WasSkipped() const bool wxProgressDialog::WasCancelled() const { #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) { wxCriticalSectionLocker locker(m_sharedData->m_cs); return m_sharedData->m_state == Canceled; @@ -548,7 +619,7 @@ bool wxProgressDialog::WasCancelled() const void wxProgressDialog::SetTitle(const wxString& title) { #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) { m_title = title; @@ -567,7 +638,7 @@ void wxProgressDialog::SetTitle(const wxString& title) wxString wxProgressDialog::GetTitle() const { #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) return m_title; #endif // wxHAS_MSW_TASKDIALOG @@ -577,7 +648,7 @@ wxString wxProgressDialog::GetTitle() const bool wxProgressDialog::Show(bool show) { #ifdef wxHAS_MSW_TASKDIALOG - if ( HasNativeProgressDialog() ) + if ( HasNativeTaskDialog() ) { // The dialog can't be hidden at all and showing it again after it had // been shown before doesn't do anything. @@ -586,7 +657,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 +665,24 @@ 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(); + m_sharedData->m_parent = GetTopParent(); - 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 // Dialog can't be cancelled. { + // We still must have at least a single button in the dialog so + // just don't call it "Cancel" in this case. 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,21 +701,6 @@ bool wxProgressDialog::Show(bool show) return false; } - if ( !HasFlag(wxPD_APP_MODAL) ) - { - wxWindow * const parent = GetTopParent(); - if ( parent ) - { - parent->Disable(); - } - else - { - wxFAIL_MSG( "Progress dialog must have a valid parent if " - "wxPD_APP_MODAL is not used." ); - } - } - //else: otherwise all windows will be disabled by m_taskDialogRunner - // Do not show the underlying dialog. return false; } @@ -650,22 +709,6 @@ bool wxProgressDialog::Show(bool show) return wxGenericProgressDialog::Show( 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. - return HasNativeTaskDialog() - && ((m_windowStyle & (wxPD_CAN_SKIP | wxPD_CAN_ABORT)) - || !(m_windowStyle & 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. - return false; -#endif // wxHAS_MSW_TASKDIALOG/!wxHAS_MSW_TASKDIALOG -} - void wxProgressDialog::UpdateExpandedInformation(int value) { #ifdef wxHAS_MSW_TASKDIALOG @@ -687,14 +730,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"; @@ -704,7 +747,7 @@ void wxProgressDialog::UpdateExpandedInformation(int value) << GetFormattedTime(realEstimatedTime); } - if ( m_windowStyle & wxPD_REMAINING_TIME ) + if ( HasPDFlag(wxPD_REMAINING_TIME) ) { if ( !expandedInformation.empty() ) expandedInformation += "\n"; @@ -742,6 +785,11 @@ void* wxProgressDialogTaskRunner::Entry() wxTdc.caption = m_sharedData.m_title.wx_str(); wxTdc.message = m_sharedData.m_message.wx_str(); + // MSWCommonTaskDialogInit() will add an IDCANCEL button but we need to + // give it the correct label. + wxTdc.btnOKLabel = m_sharedData.m_labelCancel; + wxTdc.useCustomLabels = true; + wxTdc.MSWCommonTaskDialogInit( tdc ); tdc.pfCallback = TaskDialogCallbackProc; tdc.lpCallbackData = (LONG_PTR) &m_sharedData; @@ -750,28 +798,15 @@ void* wxProgressDialogTaskRunner::Entry() tdc.dwFlags &= ~TDF_EXPAND_FOOTER_AREA; // Expand in content area. tdc.dwCommonButtons = 0; // Don't use common buttons. - wxTdc.useCustomLabels = true; - if ( m_sharedData.m_style & wxPD_CAN_SKIP ) wxTdc.AddTaskDialogButton( tdc, Id_SkipBtn, 0, _("Skip") ); - // Use a Cancel button when requested or use a Close button when - // the dialog does not automatically hide. - if ( (m_sharedData.m_style & wxPD_CAN_ABORT) - || !(m_sharedData.m_style & wxPD_AUTO_HIDE) ) - { - wxTdc.AddTaskDialogButton( tdc, IDCANCEL, 0, - 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() ) { tdc.pszExpandedInformation = - m_sharedData.m_expandedInformation.wx_str(); + m_sharedData.m_expandedInformation.t_str(); } } @@ -784,6 +819,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; } @@ -815,7 +854,30 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc 0, MAKELPARAM(0, sharedData->m_range) ); - if ( UsesCloseButtonOnly(sharedData->m_style) ) + // We always create this task dialog with NULL parent because our + // parent in wx sense is a window created from a different thread + // and so can't be used as our real parent. However we still center + // this window on the parent one as the task dialogs do with their + // real parent usually. + if ( sharedData->m_parent ) + { + wxRect rect(wxRectFromRECT(wxGetWindowRect(hwnd))); + rect = rect.CentreIn(sharedData->m_parent->GetRect()); + ::SetWindowPos(hwnd, + NULL, + rect.x, + rect.y, + -1, + -1, + SWP_NOACTIVATE | + SWP_NOOWNERZORDER | + SWP_NOSIZE | + SWP_NOZORDER); + } + + // If we can't be aborted, the "Close" button will only be enabled + // when the progress ends (and not even then with wxPD_AUTO_HIDE). + if ( !(sharedData->m_style & wxPD_CAN_ABORT) ) ::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE ); break; @@ -828,27 +890,33 @@ 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; } // Close button on the window triggers an IDCANCEL press, // don't allow it when it should only be possible to close // a finished dialog. - if ( !UsesCloseButtonOnly(sharedData->m_style) ) + if ( sharedData->m_style & wxPD_CAN_ABORT ) { - 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; @@ -858,22 +926,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 ); }