#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
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;
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;
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)
{
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,
// 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) )
{
int maximum,
wxWindow *parent,
int style )
- : wxGenericProgressDialog(parent, maximum, style),
+ : wxGenericProgressDialog(parent, style),
m_taskDialogRunner(NULL),
m_sharedData(NULL),
m_message(message),
m_title(title)
{
#ifdef wxHAS_MSW_TASKDIALOG
- if ( HasNativeProgressDialog() )
+ if ( HasNativeTaskDialog() )
{
+ SetMaximum(maximum);
+
Show();
DisableOtherWindows();
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
bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip)
{
#ifdef wxHAS_MSW_TASKDIALOG
- if ( HasNativeProgressDialog() )
+ if ( HasNativeTaskDialog() )
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
bool wxProgressDialog::DoNativeBeforeUpdate(bool *skip)
{
#ifdef wxHAS_MSW_TASKDIALOG
- if ( HasNativeProgressDialog() )
+ if ( HasNativeTaskDialog() )
{
if ( m_sharedData->m_skipped )
{
wxGenericProgressDialog::Resume();
#ifdef wxHAS_MSW_TASKDIALOG
- if ( HasNativeProgressDialog() )
+ if ( HasNativeTaskDialog() )
{
HWND hwnd;
// 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;
#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;
wxString wxProgressDialog::GetMessage() const
{
#ifdef wxHAS_MSW_TASKDIALOG
- if ( HasNativeProgressDialog() )
+ if ( HasNativeTaskDialog() )
return m_message;
#endif // wxHAS_MSW_TASKDIALOG
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 )
{
bool wxProgressDialog::WasCancelled() const
{
#ifdef wxHAS_MSW_TASKDIALOG
- if ( HasNativeProgressDialog() )
+ if ( HasNativeTaskDialog() )
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
return m_sharedData->m_state == Canceled;
void wxProgressDialog::SetTitle(const wxString& title)
{
#ifdef wxHAS_MSW_TASKDIALOG
- if ( HasNativeProgressDialog() )
+ if ( HasNativeTaskDialog() )
{
m_title = title;
wxString wxProgressDialog::GetTitle() const
{
#ifdef wxHAS_MSW_TASKDIALOG
- if ( HasNativeProgressDialog() )
+ if ( HasNativeTaskDialog() )
return m_title;
#endif // wxHAS_MSW_TASKDIALOG
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.
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 = " ";
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;
}
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
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";
<< GetFormattedTime(realEstimatedTime);
}
- if ( m_windowStyle & wxPD_REMAINING_TIME )
+ if ( HasPDFlag(wxPD_REMAINING_TIME) )
{
if ( !expandedInformation.empty() )
expandedInformation += "\n";
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;
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() )
{
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;
}
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;
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;
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 );
}