]> git.saurik.com Git - wxWidgets.git/blobdiff - src/msw/progdlg.cpp
Enable variadic macros for VC9 and later.
[wxWidgets.git] / src / msw / progdlg.cpp
index 0a016d382fe0909f2e5de288de6d9101982196d5..8191f6b08478b66554b1e4484b717b549981ceb8 100644 (file)
 
 #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;
 
@@ -119,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)
 {
@@ -206,6 +236,11 @@ 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,
@@ -246,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) )
         {
@@ -272,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();
 
@@ -317,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
 
@@ -375,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);
 
@@ -410,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  )
         {
@@ -440,7 +491,7 @@ void wxProgressDialog::Resume()
     wxGenericProgressDialog::Resume();
 
 #ifdef wxHAS_MSW_TASKDIALOG
-    if ( HasNativeProgressDialog() )
+    if ( HasNativeTaskDialog() )
     {
         HWND hwnd;
 
@@ -452,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;
@@ -473,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;
@@ -489,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
 
@@ -498,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 )
         {
@@ -533,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;
@@ -546,7 +619,7 @@ bool wxProgressDialog::WasCancelled() const
 void wxProgressDialog::SetTitle(const wxString& title)
 {
 #ifdef wxHAS_MSW_TASKDIALOG
-    if ( HasNativeProgressDialog() )
+    if ( HasNativeTaskDialog() )
     {
         m_title = title;
 
@@ -565,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
 
@@ -575,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.
@@ -592,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 = " ";
@@ -625,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;
     }
@@ -648,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
@@ -685,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";
@@ -702,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";
@@ -740,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;
@@ -748,23 +798,10 @@ 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() )
         {
@@ -782,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;
 }
 
@@ -813,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;
 
@@ -826,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;
@@ -856,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 );
             }