Merge branch 'progress-dialog-fixes'

Make native MSW wxProgressDialog much more usable and some minor
improvements to the generic version.
This commit is contained in:
Vadim Zeitlin
2017-11-17 17:58:03 +01:00
10 changed files with 545 additions and 196 deletions

View File

@@ -223,6 +223,7 @@ wxMSW:
- Fix updating radio groups when non-radio item is inserted to wxMenu. - Fix updating radio groups when non-radio item is inserted to wxMenu.
- Fix autoselecting the contents of wxTextCtrl with wxWANTS_CHARS style. - Fix autoselecting the contents of wxTextCtrl with wxWANTS_CHARS style.
- Implement SetIcon(), SetPosition(), GetPosition() for native wxProgressDialog. - Implement SetIcon(), SetPosition(), GetPosition() for native wxProgressDialog.
- Fix focus-related problems when using native wxProgressDialog.
- Fix crash when reparenting the currently focused window to another TLW. - Fix crash when reparenting the currently focused window to another TLW.
wxOSX: wxOSX:

View File

@@ -12,6 +12,7 @@
#define __PROGDLGH_G__ #define __PROGDLGH_G__
#include "wx/dialog.h" #include "wx/dialog.h"
#include "wx/weakref.h"
class WXDLLIMPEXP_FWD_CORE wxButton; class WXDLLIMPEXP_FWD_CORE wxButton;
class WXDLLIMPEXP_FWD_CORE wxEventLoop; class WXDLLIMPEXP_FWD_CORE wxEventLoop;
@@ -43,18 +44,18 @@ public:
virtual bool Update(int value, const wxString& newmsg = wxEmptyString, bool *skip = NULL); virtual bool Update(int value, const wxString& newmsg = wxEmptyString, bool *skip = NULL);
virtual bool Pulse(const wxString& newmsg = wxEmptyString, bool *skip = NULL); virtual bool Pulse(const wxString& newmsg = wxEmptyString, bool *skip = NULL);
void Resume(); virtual void Resume();
int GetValue() const; virtual int GetValue() const;
int GetRange() const; virtual int GetRange() const;
wxString GetMessage() const; virtual wxString GetMessage() const;
void SetRange(int maximum); virtual void SetRange(int maximum);
// Return whether "Cancel" or "Skip" button was pressed, always return // Return whether "Cancel" or "Skip" button was pressed, always return
// false if the corresponding button is not shown. // false if the corresponding button is not shown.
bool WasCancelled() const; virtual bool WasCancelled() const;
bool WasSkipped() const; virtual bool WasSkipped() const;
// Must provide overload to avoid hiding it (and warnings about it) // Must provide overload to avoid hiding it (and warnings about it)
virtual void Update() wxOVERRIDE { wxDialog::Update(); } virtual void Update() wxOVERRIDE { wxDialog::Update(); }
@@ -104,6 +105,9 @@ protected:
// Converts seconds to HH:mm:ss format. // Converts seconds to HH:mm:ss format.
static wxString GetFormattedTime(unsigned long timeInSec); static wxString GetFormattedTime(unsigned long timeInSec);
// Create a new event loop if there is no currently running one.
void EnsureActiveEventLoopExists();
// callback for optional abort button // callback for optional abort button
void OnCancel(wxCommandEvent&); void OnCancel(wxCommandEvent&);
@@ -120,8 +124,8 @@ protected:
// the dialog was shown // the dialog was shown
void ReenableOtherWindows(); void ReenableOtherWindows();
// Set the top level parent we store from the parent window provided when // Store the parent window as wxWindow::m_parent and also set the top level
// creating the dialog. // parent reference we store in this class itself.
void SetTopParent(wxWindow* parent); void SetTopParent(wxWindow* parent);
// return the top level parent window of this dialog (may be NULL) // return the top level parent window of this dialog (may be NULL)
@@ -183,8 +187,9 @@ private:
*m_estimated, *m_estimated,
*m_remaining; *m_remaining;
// parent top level window (may be NULL) // Reference to the parent top level window, automatically becomes NULL if
wxWindow *m_parentTop; // it it is destroyed and could be always NULL if it's not given at all.
wxWindowRef m_parentTop;
// Progress dialog styles: this is not the same as m_windowStyle because // Progress dialog styles: this is not the same as m_windowStyle because
// wxPD_XXX constants clash with the existing TLW styles so to be sure we // wxPD_XXX constants clash with the existing TLW styles so to be sure we

View File

@@ -26,17 +26,17 @@ public:
virtual bool Update(int value, const wxString& newmsg = wxEmptyString, bool *skip = NULL) wxOVERRIDE; virtual bool Update(int value, const wxString& newmsg = wxEmptyString, bool *skip = NULL) wxOVERRIDE;
virtual bool Pulse(const wxString& newmsg = wxEmptyString, bool *skip = NULL) wxOVERRIDE; virtual bool Pulse(const wxString& newmsg = wxEmptyString, bool *skip = NULL) wxOVERRIDE;
void Resume(); virtual void Resume() wxOVERRIDE;
int GetValue() const; virtual int GetValue() const wxOVERRIDE;
wxString GetMessage() const; virtual wxString GetMessage() const wxOVERRIDE;
void SetRange(int maximum); virtual void SetRange(int maximum) wxOVERRIDE;
// Return whether "Cancel" or "Skip" button was pressed, always return // Return whether "Cancel" or "Skip" button was pressed, always return
// false if the corresponding button is not shown. // false if the corresponding button is not shown.
bool WasSkipped() const; virtual bool WasSkipped() const wxOVERRIDE;
bool WasCancelled() const; virtual bool WasCancelled() const wxOVERRIDE;
virtual void SetTitle(const wxString& title) wxOVERRIDE; virtual void SetTitle(const wxString& title) wxOVERRIDE;
virtual wxString GetTitle() const wxOVERRIDE; virtual wxString GetTitle() const wxOVERRIDE;
@@ -44,6 +44,8 @@ public:
virtual void SetIcons(const wxIconBundle& icons) wxOVERRIDE; virtual void SetIcons(const wxIconBundle& icons) wxOVERRIDE;
virtual void DoMoveWindow(int x, int y, int width, int height) wxOVERRIDE; virtual void DoMoveWindow(int x, int y, int width, int height) wxOVERRIDE;
virtual void DoGetPosition(int *x, int *y) const wxOVERRIDE; virtual void DoGetPosition(int *x, int *y) const wxOVERRIDE;
virtual void DoGetSize(int *width, int *height) const wxOVERRIDE;
virtual void Fit() wxOVERRIDE;
virtual bool Show( bool show = true ) wxOVERRIDE; virtual bool Show( bool show = true ) wxOVERRIDE;
@@ -53,15 +55,24 @@ public:
virtual WXWidget GetHandle() const wxOVERRIDE; virtual WXWidget GetHandle() const wxOVERRIDE;
private: private:
// Performs common routines to Update() and Pulse(). Requires the // Common part of Update() and Pulse().
// shared object to have been entered. //
// Returns false if the user requested cancelling the dialog.
bool DoNativeBeforeUpdate(bool *skip); bool DoNativeBeforeUpdate(bool *skip);
// Dispatch the pending events to let the windows to update, just as the
// generic version does. This is done as part of DoNativeBeforeUpdate().
void DispatchEvents();
// Updates the various timing informations for both determinate // Updates the various timing informations for both determinate
// and indeterminate modes. Requires the shared object to have // and indeterminate modes. Requires the shared object to have
// been entered. // been entered.
void UpdateExpandedInformation(int value); void UpdateExpandedInformation(int value);
// Get the task dialog geometry when using the native dialog.
wxRect GetTaskDialogRect() const;
wxProgressDialogTaskRunner *m_taskDialogRunner; wxProgressDialogTaskRunner *m_taskDialogRunner;
wxProgressDialogSharedData *m_sharedData; wxProgressDialogSharedData *m_sharedData;

View File

@@ -90,6 +90,9 @@ public:
// NULL if getting the system menu failed. // NULL if getting the system menu failed.
wxMenu *MSWGetSystemMenu() const; wxMenu *MSWGetSystemMenu() const;
// Enable or disable the close button of the specified window.
static bool MSWEnableCloseButton(WXHWND hwnd, bool enable = true);
// implementation from now on // implementation from now on
// -------------------------- // --------------------------

View File

@@ -33,6 +33,26 @@
wxProgressDialog in a multi-threaded application you should be sure to use wxProgressDialog in a multi-threaded application you should be sure to use
wxThreadEvent for your inter-threads communications). wxThreadEvent for your inter-threads communications).
Although wxProgressDialog is not really modal, it should be created on the
stack, and not the heap, as other modal dialogs, e.g. use it like this:
@code
void MyFrame::SomeFunc()
{
wxProgressDialog dialog(...);
for ( int i = 0; i < 100; ++i ) {
if ( !dialog.Update(i)) {
// Cancelled by user.
break;
}
... do something time-consuming (but not too much) ...
}
}
@endcode
Note that this becomes even more important if the dialog is instantiated
during the program initialization, e.g. from wxApp::OnInit(): the dialog
must be destroyed before the main event loop is started in this case.
@beginStyleTable @beginStyleTable
@style{wxPD_APP_MODAL} @style{wxPD_APP_MODAL}
Make the progress dialog modal. If this flag is not given, it is Make the progress dialog modal. If this flag is not given, it is
@@ -192,9 +212,19 @@ public:
for the user to dismiss it, meaning that this function does not return for the user to dismiss it, meaning that this function does not return
until this happens. until this happens.
Notice that you may want to call Fit() to change the dialog size to Notice that if @a newmsg is longer than the currently shown message,
conform to the length of the new message if desired. The dialog does the dialog will be automatically made wider to account for it. However
not do this automatically. if the new message is shorter than the previous one, the dialog doesn't
shrink back to avoid constant resizes if the message is changed often.
To do this and fit the dialog to its current contents you may call
Fit() explicitly. However the native MSW implementation of this class
does make the dialog shorter if the new text has fewer lines of text
than the old one, so it is recommended to keep the number of lines of
text constant in order to avoid jarring dialog size changes. You may
also want to make the initial message, specified when creating the
dialog, wide enough to avoid having to resize the dialog later, e.g. by
appending a long string of unbreakable spaces (@c wxString(L'\u00a0',
100)) to it.
@param value @param value
The new value of the progress meter. It should be less than or equal to The new value of the progress meter. It should be less than or equal to

View File

@@ -233,6 +233,9 @@ wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
#if wxUSE_PROGRESSDLG #if wxUSE_PROGRESSDLG
EVT_MENU(DIALOGS_PROGRESS, MyFrame::ShowProgress) EVT_MENU(DIALOGS_PROGRESS, MyFrame::ShowProgress)
#ifdef wxHAS_NATIVE_PROGRESSDIALOG
EVT_MENU(DIALOGS_PROGRESS_GENERIC, MyFrame::ShowProgressGeneric)
#endif // wxHAS_NATIVE_PROGRESSDIALOG
#endif // wxUSE_PROGRESSDLG #endif // wxUSE_PROGRESSDLG
EVT_MENU(DIALOGS_APP_PROGRESS, MyFrame::ShowAppProgress) EVT_MENU(DIALOGS_APP_PROGRESS, MyFrame::ShowAppProgress)
@@ -349,7 +352,25 @@ bool MyApp::OnInit()
); );
for ( int i = 0; i <= PROGRESS_COUNT; i++ ) for ( int i = 0; i <= PROGRESS_COUNT; i++ )
{ {
if ( !dlg.Update(i) ) wxString msg;
switch ( i )
{
case 15:
msg = "And the same dialog but with a very, very, very long"
" message, just to test how it appears in this case.";
break;
case 30:
msg = "Back to brevity";
break;
case 80:
msg = "Back and adjusted";
dlg.Fit();
break;
}
if ( !dlg.Update(i, msg) )
break; break;
wxMilliSleep(50); wxMilliSleep(50);
@@ -490,6 +511,10 @@ bool MyApp::OnInit()
#if wxUSE_PROGRESSDLG #if wxUSE_PROGRESSDLG
info_menu->Append(DIALOGS_PROGRESS, wxT("Pro&gress dialog\tCtrl-G")); info_menu->Append(DIALOGS_PROGRESS, wxT("Pro&gress dialog\tCtrl-G"));
#ifdef wxHAS_NATIVE_PROGRESSDIALOG
info_menu->Append(DIALOGS_PROGRESS_GENERIC,
wxT("Generic progress dialog\tCtrl-Alt-G"));
#endif // wxHAS_NATIVE_PROGRESSDIALOG
#endif // wxUSE_PROGRESSDLG #endif // wxUSE_PROGRESSDLG
info_menu->Append(DIALOGS_APP_PROGRESS, wxT("&App progress\tShift-Ctrl-G")); info_menu->Append(DIALOGS_APP_PROGRESS, wxT("&App progress\tShift-Ctrl-G"));
@@ -2652,10 +2677,10 @@ void MyFrame::OnExit(wxCommandEvent& WXUNUSED(event) )
#if wxUSE_PROGRESSDLG #if wxUSE_PROGRESSDLG
static const int max = 100;
void MyFrame::ShowProgress( wxCommandEvent& WXUNUSED(event) ) void MyFrame::ShowProgress( wxCommandEvent& WXUNUSED(event) )
{ {
static const int max = 100;
wxProgressDialog dialog("Progress dialog example", wxProgressDialog dialog("Progress dialog example",
// "Reserve" enough space for the multiline // "Reserve" enough space for the multiline
// messages below, we'll change it anyhow // messages below, we'll change it anyhow
@@ -2673,6 +2698,30 @@ void MyFrame::ShowProgress( wxCommandEvent& WXUNUSED(event) )
wxPD_SMOOTH // - makes indeterminate mode bar on WinXP very small wxPD_SMOOTH // - makes indeterminate mode bar on WinXP very small
); );
DoShowProgress(dialog);
}
#ifdef wxHAS_NATIVE_PROGRESSDIALOG
void MyFrame::ShowProgressGeneric( wxCommandEvent& WXUNUSED(event) )
{
wxGenericProgressDialog dialog("Generic progress dialog example",
wxString(' ', 100) + "\n\n\n\n",
max,
this,
wxPD_CAN_ABORT |
wxPD_CAN_SKIP |
wxPD_APP_MODAL |
wxPD_ELAPSED_TIME |
wxPD_ESTIMATED_TIME |
wxPD_REMAINING_TIME |
wxPD_SMOOTH);
DoShowProgress(dialog);
}
#endif // wxHAS_NATIVE_PROGRESSDIALOG
void MyFrame::DoShowProgress(wxGenericProgressDialog& dialog)
{
bool cont = true; bool cont = true;
for ( int i = 0; i <= max; i++ ) for ( int i = 0; i <= max; i++ )
{ {
@@ -2721,8 +2770,8 @@ void MyFrame::ShowProgress( wxCommandEvent& WXUNUSED(event) )
{ {
i += max/4; i += max/4;
if ( i >= 100 ) if ( i >= max )
i = 99; i = max - 1;
} }
if ( !cont ) if ( !cont )
@@ -2736,7 +2785,7 @@ void MyFrame::ShowProgress( wxCommandEvent& WXUNUSED(event) )
dialog.Resume(); dialog.Resume();
} }
wxMilliSleep(200); wxMilliSleep(100);
} }
if ( !cont ) if ( !cont )
@@ -2768,7 +2817,7 @@ void MyFrame::ShowAppProgress( wxCommandEvent& WXUNUSED(event) )
{ {
progress.SetValue(i); progress.SetValue(i);
wxMilliSleep(500); wxMilliSleep(200);
} }
wxLogStatus("Progress finished"); wxLogStatus("Progress finished");

View File

@@ -452,6 +452,10 @@ public:
#if wxUSE_PROGRESSDLG #if wxUSE_PROGRESSDLG
void ShowProgress(wxCommandEvent& event); void ShowProgress(wxCommandEvent& event);
#ifdef wxHAS_NATIVE_PROGRESSDIALOG
void ShowProgressGeneric(wxCommandEvent& event);
#endif // wxHAS_NATIVE_PROGRESSDIALOG
void DoShowProgress(wxGenericProgressDialog& dialog);
#endif // wxUSE_PROGRESSDLG #endif // wxUSE_PROGRESSDLG
void ShowAppProgress(wxCommandEvent& event); void ShowAppProgress(wxCommandEvent& event);
@@ -596,6 +600,7 @@ enum
DIALOGS_ONTOP, DIALOGS_ONTOP,
DIALOGS_MODELESS_BTN, DIALOGS_MODELESS_BTN,
DIALOGS_PROGRESS, DIALOGS_PROGRESS,
DIALOGS_PROGRESS_GENERIC,
DIALOGS_APP_PROGRESS, DIALOGS_APP_PROGRESS,
DIALOGS_ABOUTDLG_SIMPLE, DIALOGS_ABOUTDLG_SIMPLE,
DIALOGS_ABOUTDLG_FANCY, DIALOGS_ABOUTDLG_FANCY,

View File

@@ -133,6 +133,7 @@ wxGenericProgressDialog::wxGenericProgressDialog(const wxString& title,
void wxGenericProgressDialog::SetTopParent(wxWindow* parent) void wxGenericProgressDialog::SetTopParent(wxWindow* parent)
{ {
m_parent = parent;
m_parentTop = GetParentForModalDialog(parent, GetWindowStyle()); m_parentTop = GetParentForModalDialog(parent, GetWindowStyle());
} }
@@ -144,13 +145,9 @@ bool wxGenericProgressDialog::Create( const wxString& title,
{ {
SetTopParent(parent); SetTopParent(parent);
m_parentTop = wxGetTopLevelParent(parent);
m_pdStyle = style; m_pdStyle = style;
wxWindow* const if (!wxDialog::Create(m_parentTop, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, GetWindowStyle()))
realParent = GetParentForModalDialog(parent, GetWindowStyle());
if (!wxDialog::Create(realParent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, GetWindowStyle()))
return false; return false;
SetMaximum(maximum); SetMaximum(maximum);
@@ -160,11 +157,7 @@ bool wxGenericProgressDialog::Create( const wxString& title,
// even if this means we have to start it ourselves (this happens most // even if this means we have to start it ourselves (this happens most
// commonly during the program initialization, e.g. for the progress // commonly during the program initialization, e.g. for the progress
// dialogs shown from overridden wxApp::OnInit()). // dialogs shown from overridden wxApp::OnInit()).
if ( !wxEventLoopBase::GetActive() ) EnsureActiveEventLoopExists();
{
m_tempEventLoop = new wxEventLoop;
wxEventLoop::SetActive(m_tempEventLoop);
}
#if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
// we have to remove the "Close" button from the title bar then as it is // we have to remove the "Close" button from the title bar then as it is
@@ -363,6 +356,15 @@ wxString wxGenericProgressDialog::GetFormattedTime(unsigned long timeInSec)
return timeAsHMS; return timeAsHMS;
} }
void wxGenericProgressDialog::EnsureActiveEventLoopExists()
{
if ( !wxEventLoopBase::GetActive() )
{
m_tempEventLoop = new wxEventLoop;
wxEventLoop::SetActive(m_tempEventLoop);
}
}
wxStaticText * wxStaticText *
wxGenericProgressDialog::CreateLabel(const wxString& text, wxSizer *sizer) wxGenericProgressDialog::CreateLabel(const wxString& text, wxSizer *sizer)
{ {
@@ -695,6 +697,21 @@ wxGenericProgressDialog::~wxGenericProgressDialog()
if ( m_tempEventLoop ) if ( m_tempEventLoop )
{ {
// If another event loop has been installed as active during the life
// time of this object, we shouldn't deactivate it, but we also can't
// delete our m_tempEventLoop in this case because it risks leaving the
// new event loop with a dangling pointer, which it will set back as
// the active loop when it exits, resulting in a crash. So we have no
// choice but to just leak this pointer then, which is, of course, bad
// and usually easily avoidable by just destroying the progress dialog
// sooner, so warn the programmer about it.
wxCHECK_RET
(
wxEventLoopBase::GetActive() == m_tempEventLoop,
"current event loop must not be changed during "
"wxGenericProgressDialog lifetime"
);
wxEventLoopBase::SetActive(NULL); wxEventLoopBase::SetActive(NULL);
delete m_tempEventLoop; delete m_tempEventLoop;
} }
@@ -765,8 +782,17 @@ void wxGenericProgressDialog::UpdateMessage(const wxString &newmsg)
{ {
if ( !newmsg.empty() && newmsg != m_msg->GetLabel() ) if ( !newmsg.empty() && newmsg != m_msg->GetLabel() )
{ {
const wxSize sizeOld = m_msg->GetSize();
m_msg->SetLabel(newmsg); m_msg->SetLabel(newmsg);
if ( m_msg->GetSize().x > sizeOld.x )
{
// Resize the dialog to fit its new, longer contents instead of
// just truncating it.
Fit();
}
// allow the window to repaint: // allow the window to repaint:
// NOTE: since we yield only for UI events with this call, there // NOTE: since we yield only for UI events with this call, there
// should be no side-effects // should be no side-effects

View File

@@ -57,7 +57,6 @@ const int wxSPDD_EXPINFO_CHANGED = 0x0020;
const int wxSPDD_ENABLE_SKIP = 0x0040; const int wxSPDD_ENABLE_SKIP = 0x0040;
const int wxSPDD_ENABLE_ABORT = 0x0080; const int wxSPDD_ENABLE_ABORT = 0x0080;
const int wxSPDD_DISABLE_SKIP = 0x0100; const int wxSPDD_DISABLE_SKIP = 0x0100;
const int wxSPDD_DISABLE_ABORT = 0x0200;
const int wxSPDD_FINISHED = 0x0400; const int wxSPDD_FINISHED = 0x0400;
const int wxSPDD_DESTROYED = 0x0800; const int wxSPDD_DESTROYED = 0x0800;
const int wxSPDD_ICON_CHANGED = 0x1000; const int wxSPDD_ICON_CHANGED = 0x1000;
@@ -81,6 +80,7 @@ public:
m_value = 0; m_value = 0;
m_progressBarMarquee = false; m_progressBarMarquee = false;
m_skipped = false; m_skipped = false;
m_msgChangeElementText = TDM_UPDATE_ELEMENT_TEXT;
m_notifications = 0; m_notifications = 0;
m_parent = NULL; m_parent = NULL;
} }
@@ -105,9 +105,48 @@ public:
bool m_progressBarMarquee; bool m_progressBarMarquee;
bool m_skipped; bool m_skipped;
// The task dialog message to use for changing the text of its elements:
// it is set to TDM_SET_ELEMENT_TEXT by Fit() to let the dialog adjust
// itself to the size of its elements during the next update, but otherwise
// TDM_UPDATE_ELEMENT_TEXT is used in order to prevent the dialog from
// performing a layout on each update, which is annoying as it can result
// in its size constantly changing.
int m_msgChangeElementText;
// Bit field that indicates fields that have been modified by the // Bit field that indicates fields that have been modified by the
// main thread so the task dialog runner knows what to update. // main thread so the task dialog runner knows what to update.
int m_notifications; int m_notifications;
// Helper function to split a single message, passed via our public API,
// into the title and the main content body used by the native dialog.
//
// Note that it uses m_message and so must be called with m_cs locked.
void SplitMessageIntoTitleAndBody(wxString& title, wxString& body) const
{
title = m_message;
const size_t posNL = title.find('\n');
if ( posNL != wxString::npos )
{
// There can an extra new line between the first and subsequent
// lines to separate them as it looks better with the generic
// version -- but in this one, they're already separated by the use
// of different dialog elements, so suppress the extra new line.
int numNLs = 1;
if ( posNL < title.length() - 1 && title[posNL + 1] == '\n' )
numNLs++;
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.
body.clear();
title.swap(body);
}
}
}; };
// Runner thread that takes care of displaying and updating the // Runner thread that takes care of displaying and updating the
@@ -182,6 +221,16 @@ BOOL CALLBACK DisplayCloseButton(HWND hwnd, LPARAM lParam)
return TRUE; return TRUE;
} }
// This function enables or disables both the cancel button in the task dialog
// and the close button in its title bar, as they perform the same function and
// so should be kept in the same state.
void EnableCloseButtons(HWND hwnd, bool enable)
{
::SendMessage(hwnd, TDM_ENABLE_BUTTON, IDCANCEL, enable ? TRUE : FALSE);
wxTopLevelWindow::MSWEnableCloseButton(hwnd, enable);
}
void PerformNotificationUpdates(HWND hwnd, void PerformNotificationUpdates(HWND hwnd,
wxProgressDialogSharedData *sharedData) wxProgressDialogSharedData *sharedData)
{ {
@@ -196,6 +245,33 @@ void PerformNotificationUpdates(HWND hwnd,
if ( sharedData->m_notifications & wxSPDD_VALUE_CHANGED ) if ( sharedData->m_notifications & wxSPDD_VALUE_CHANGED )
{ {
// Use a hack to avoid progress bar animation: we can't afford to use
// it because animating the gauge movement smoothly requires a
// constantly running message loop and while it does run in this (task
// dialog) thread, it is often blocked from proceeding by some lock
// held by the main thread which is busy doing something and may not
// dispatch events frequently enough. So, in practice, the animation
// can lag far behind the real value and results in showing a wrong
// value in the progress bar.
//
// To prevent this from happening, set the progress bar value to
// something greater than its maximal value and then move it back: in
// the current implementations of the progress bar control, moving its
// position backwards does it directly, without using the animation,
// which is exactly what we want here.
//
// Finally notice that this hack doesn't really work for the last
// value, but while we could use a nested hack and temporarily increase
// the progress bar range when the value is equal to it, it isn't
// actually necessary in practice because when we reach the end of the
// bar the dialog is either hidden immediately anyhow or the main
// thread enters a modal event loop which does dispatch events and so
// it's not a problem to have an animated transition in this particular
// case.
::SendMessage( hwnd,
TDM_SET_PROGRESS_BAR_POS,
sharedData->m_value + 1,
0 );
::SendMessage( hwnd, ::SendMessage( hwnd,
TDM_SET_PROGRESS_BAR_POS, TDM_SET_PROGRESS_BAR_POS,
sharedData->m_value, sharedData->m_value,
@@ -235,39 +311,31 @@ void PerformNotificationUpdates(HWND hwnd,
{ {
// Split the message in the title string and the rest if it has // Split the message in the title string and the rest if it has
// multiple lines. // multiple lines.
wxString wxString title, body;
title = sharedData->m_message, sharedData->SplitMessageIntoTitleAndBody(title, body);
body;
const size_t posNL = title.find('\n');
if ( posNL != wxString::npos )
{
// There can an extra new line between the first and subsequent
// lines to separate them as it looks better with the generic
// version -- but in this one, they're already separated by the use
// of different dialog elements, so suppress the extra new line.
int numNLs = 1;
if ( posNL < title.length() - 1 && title[posNL + 1] == '\n' )
numNLs++;
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, ::SendMessage( hwnd,
TDM_SET_ELEMENT_TEXT, sharedData->m_msgChangeElementText,
TDE_MAIN_INSTRUCTION, TDE_MAIN_INSTRUCTION,
wxMSW_CONV_LPARAM(title) ); wxMSW_CONV_LPARAM(title) );
::SendMessage( hwnd, ::SendMessage( hwnd,
TDM_SET_ELEMENT_TEXT, sharedData->m_msgChangeElementText,
TDE_CONTENT, TDE_CONTENT,
wxMSW_CONV_LPARAM(body) ); wxMSW_CONV_LPARAM(body) );
// After using TDM_SET_ELEMENT_TEXT once, we don't want to use it for
// the subsequent updates as it could result in dialog size changing
// unexpectedly, so reset it (which does nothing if we had already done
// it, of course, but it's not a problem).
//
// Notice that, contrary to its documentation, even using this message
// still increases the dialog size if the new text is longer (at least
// under Windows 7), but it doesn't shrink back if the text becomes
// shorter later and stays at the bigger size which is still a big gain
// as it prevents jumping back and forth between the smaller and larger
// sizes.
sharedData->m_msgChangeElementText = TDM_UPDATE_ELEMENT_TEXT;
} }
if ( sharedData->m_notifications & wxSPDD_EXPINFO_CHANGED ) if ( sharedData->m_notifications & wxSPDD_EXPINFO_CHANGED )
@@ -276,8 +344,15 @@ void PerformNotificationUpdates(HWND hwnd,
sharedData->m_expandedInformation; sharedData->m_expandedInformation;
if ( !expandedInformation.empty() ) if ( !expandedInformation.empty() )
{ {
// Here we never need to use TDM_SET_ELEMENT_TEXT as the size of
// the expanded information doesn't change drastically.
//
// Notice that TDM_UPDATE_ELEMENT_TEXT for this element only works
// when using TDF_EXPAND_FOOTER_AREA, as we do. Without this flag,
// only TDM_SET_ELEMENT_TEXT could be used as otherwise the dialog
// layout becomes completely mangled (at least under Windows 7).
::SendMessage( hwnd, ::SendMessage( hwnd,
TDM_SET_ELEMENT_TEXT, TDM_UPDATE_ELEMENT_TEXT,
TDE_EXPANDED_INFORMATION, TDE_EXPANDED_INFORMATION,
wxMSW_CONV_LPARAM(expandedInformation) ); wxMSW_CONV_LPARAM(expandedInformation) );
} }
@@ -287,14 +362,11 @@ void PerformNotificationUpdates(HWND hwnd,
::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, TRUE ); ::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, TRUE );
if ( sharedData->m_notifications & wxSPDD_ENABLE_ABORT ) if ( sharedData->m_notifications & wxSPDD_ENABLE_ABORT )
::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, TRUE ); EnableCloseButtons(hwnd, true);
if ( sharedData->m_notifications & wxSPDD_DISABLE_SKIP ) if ( sharedData->m_notifications & wxSPDD_DISABLE_SKIP )
::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE ); ::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE );
if ( sharedData->m_notifications & wxSPDD_DISABLE_ABORT )
::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE );
// Is the progress finished? // Is the progress finished?
if ( sharedData->m_notifications & wxSPDD_FINISHED ) if ( sharedData->m_notifications & wxSPDD_FINISHED )
{ {
@@ -304,7 +376,7 @@ void PerformNotificationUpdates(HWND hwnd,
{ {
// Change Cancel into Close and activate the button. // Change Cancel into Close and activate the button.
::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE ); ::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE );
::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, TRUE ); EnableCloseButtons(hwnd, true);
::EnumChildWindows( hwnd, DisplayCloseButton, ::EnumChildWindows( hwnd, DisplayCloseButton,
(LPARAM) sharedData ); (LPARAM) sharedData );
} }
@@ -337,6 +409,7 @@ wxProgressDialog::wxProgressDialog( const wxString& title,
SetPDStyle(style); SetPDStyle(style);
SetMaximum(maximum); SetMaximum(maximum);
EnsureActiveEventLoopExists();
Show(); Show();
DisableOtherWindows(); DisableOtherWindows();
@@ -359,14 +432,49 @@ wxProgressDialog::~wxProgressDialog()
m_sharedData->m_notifications |= wxSPDD_DESTROYED; m_sharedData->m_notifications |= wxSPDD_DESTROYED;
} }
m_taskDialogRunner->Wait(); // We can't use simple wxThread::Wait() here as we would deadlock because
// the task dialog thread expects this thread to process some messages
// (presumably those the task dialog sends to its parent during its
// destruction).
const WXHANDLE hThread = m_taskDialogRunner->MSWGetHandle();
for ( bool cont = true; cont; )
{
DWORD rc = ::MsgWaitForMultipleObjects
(
1, // number of objects to wait for
(HANDLE *)&hThread, // the objects
false, // wait for any objects, not all
INFINITE, // no timeout
QS_ALLINPUT | // return as soon as there are any events
QS_ALLPOSTMESSAGE
);
delete m_taskDialogRunner; switch ( rc )
{
case 0xFFFFFFFF:
// This is unexpected, but we can't do anything about it and
// probably shouldn't continue waiting as we risk doing it
// forever.
wxLogLastError("MsgWaitForMultipleObjectsEx");
cont = false;
break;
case WAIT_OBJECT_0:
// Thread has terminated.
cont = false;
break;
default:
// An event has arrive, so dispatch it.
wxEventLoop::GetActive()->Dispatch();
}
}
// Enable the windows before deleting the task dialog to ensure that we
// can regain the activation.
ReenableOtherWindows(); ReenableOtherWindows();
if ( GetTopParent() ) delete m_taskDialogRunner;
GetTopParent()->Raise();
#endif // wxHAS_MSW_TASKDIALOG #endif // wxHAS_MSW_TASKDIALOG
} }
@@ -375,21 +483,26 @@ bool wxProgressDialog::Update(int value, const wxString& newmsg, bool *skip)
#ifdef wxHAS_MSW_TASKDIALOG #ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() ) if ( HasNativeTaskDialog() )
{ {
if ( !DoNativeBeforeUpdate(skip) )
{
// Dialog was cancelled.
return false;
}
value /= m_factor;
wxASSERT_MSG( value <= m_maximum, wxT("invalid progress value") );
{ {
wxCriticalSectionLocker locker(m_sharedData->m_cs); wxCriticalSectionLocker locker(m_sharedData->m_cs);
// Do nothing in canceled state. if ( value != m_sharedData->m_value )
if ( !DoNativeBeforeUpdate(skip) ) {
return false; m_sharedData->m_value = value;
m_sharedData->m_notifications |= wxSPDD_VALUE_CHANGED;
}
value /= m_factor; if ( !newmsg.empty() && newmsg != m_message )
wxASSERT_MSG( value <= m_maximum, wxT("invalid progress value") );
m_sharedData->m_value = value;
m_sharedData->m_notifications |= wxSPDD_VALUE_CHANGED;
if ( !newmsg.empty() )
{ {
m_message = newmsg; m_message = newmsg;
m_sharedData->m_message = newmsg; m_sharedData->m_message = newmsg;
@@ -445,11 +558,13 @@ bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip)
#ifdef wxHAS_MSW_TASKDIALOG #ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() ) if ( HasNativeTaskDialog() )
{ {
wxCriticalSectionLocker locker(m_sharedData->m_cs);
// Do nothing in canceled state.
if ( !DoNativeBeforeUpdate(skip) ) if ( !DoNativeBeforeUpdate(skip) )
{
// Dialog was cancelled.
return false; return false;
}
wxCriticalSectionLocker locker(m_sharedData->m_cs);
if ( !m_sharedData->m_progressBarMarquee ) if ( !m_sharedData->m_progressBarMarquee )
{ {
@@ -457,15 +572,15 @@ bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip)
m_sharedData->m_notifications |= wxSPDD_PBMARQUEE_CHANGED; m_sharedData->m_notifications |= wxSPDD_PBMARQUEE_CHANGED;
} }
if ( !newmsg.empty() ) if ( !newmsg.empty() && newmsg != m_message )
{ {
m_message = newmsg; m_message = newmsg;
m_sharedData->m_message = newmsg; m_sharedData->m_message = newmsg;
m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED; m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
} }
// The value passed here doesn't matter, only elapsed time makes sense // Value of 0 is special and is used when we can't estimate the
// in indeterminate mode anyhow. // remaining and total times, which is exactly what we need here.
UpdateExpandedInformation(0); UpdateExpandedInformation(0);
return m_sharedData->m_state != Canceled; return m_sharedData->m_state != Canceled;
@@ -475,32 +590,49 @@ bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip)
return wxGenericProgressDialog::Pulse( newmsg, skip ); return wxGenericProgressDialog::Pulse( newmsg, skip );
} }
void wxProgressDialog::DispatchEvents()
{
#ifdef wxHAS_MSW_TASKDIALOG
// No need for HasNativeTaskDialog() check, we're only called when this is
// the case.
// We don't need to dispatch the user input events as the task dialog
// handles its own ones in its thread and we shouldn't react to any
// other user actions while the dialog is shown.
wxEventLoop::GetActive()->
YieldFor(wxEVT_CATEGORY_ALL & ~wxEVT_CATEGORY_USER_INPUT);
#else // !wxHAS_MSW_TASKDIALOG
wxFAIL_MSG( "unreachable" );
#endif // wxHAS_MSW_TASKDIALOG/!wxHAS_MSW_TASKDIALOG
}
bool wxProgressDialog::DoNativeBeforeUpdate(bool *skip) bool wxProgressDialog::DoNativeBeforeUpdate(bool *skip)
{ {
#ifdef wxHAS_MSW_TASKDIALOG #ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() ) DispatchEvents();
wxCriticalSectionLocker locker(m_sharedData->m_cs);
if ( m_sharedData->m_skipped )
{ {
if ( m_sharedData->m_skipped ) if ( skip && !*skip )
{ {
if ( skip && !*skip ) *skip = true;
{ m_sharedData->m_skipped = false;
*skip = true; m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP;
m_sharedData->m_skipped = false;
m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP;
}
} }
if ( m_sharedData->m_state == Canceled )
m_timeStop = m_sharedData->m_timeStop;
return m_sharedData->m_state != Canceled;
} }
#endif // wxHAS_MSW_TASKDIALOG
if ( m_sharedData->m_state == Canceled )
m_timeStop = m_sharedData->m_timeStop;
return m_sharedData->m_state != Canceled;
#else // !wxHAS_MSW_TASKDIALOG
wxUnusedVar(skip); wxUnusedVar(skip);
wxFAIL_MSG( "unreachable" ); wxFAIL_MSG( "unreachable" );
return false; return false;
#endif // wxHAS_MSW_TASKDIALOG/!wxHAS_MSW_TASKDIALOG
} }
void wxProgressDialog::Resume() void wxProgressDialog::Resume()
@@ -514,7 +646,7 @@ void wxProgressDialog::Resume()
{ {
wxCriticalSectionLocker locker(m_sharedData->m_cs); wxCriticalSectionLocker locker(m_sharedData->m_cs);
m_sharedData->m_state = m_state; m_sharedData->m_state = Continue;
// "Skip" was disabled when "Cancel" had been clicked, so re-enable // "Skip" was disabled when "Cancel" had been clicked, so re-enable
// it now. // it now.
@@ -542,20 +674,16 @@ void wxProgressDialog::Resume()
#endif // wxHAS_MSW_TASKDIALOG #endif // wxHAS_MSW_TASKDIALOG
} }
WXWidget wxProgressDialog::GetHandle() const WXWidget wxProgressDialog::GetHandle() const
{ {
#ifdef wxHAS_MSW_TASKDIALOG #ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() ) if ( HasNativeTaskDialog() )
{ {
HWND hwnd; wxCriticalSectionLocker locker(m_sharedData->m_cs);
{ return m_sharedData->m_hwnd;
wxCriticalSectionLocker locker(m_sharedData->m_cs);
m_sharedData->m_state = m_state;
hwnd = m_sharedData->m_hwnd;
}
return hwnd;
} }
#endif #endif // wxHAS_MSW_TASKDIALOG
return wxGenericProgressDialog::GetHandle(); return wxGenericProgressDialog::GetHandle();
} }
@@ -715,21 +843,33 @@ void wxProgressDialog::DoMoveWindow(int x, int y, int width, int height)
wxGenericProgressDialog::DoMoveWindow(x, y, width, height); wxGenericProgressDialog::DoMoveWindow(x, y, width, height);
} }
wxRect wxProgressDialog::GetTaskDialogRect() const
{
wxRect r;
#ifdef wxHAS_MSW_TASKDIALOG
if ( m_sharedData )
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
r = wxRectFromRECT(wxGetWindowRect(m_sharedData->m_hwnd));
}
#else // !wxHAS_MSW_TASKDIALOG
wxFAIL_MSG( "unreachable" );
#endif // wxHAS_MSW_TASKDIALOG/!wxHAS_MSW_TASKDIALOG
return r;
}
void wxProgressDialog::DoGetPosition(int *x, int *y) const void wxProgressDialog::DoGetPosition(int *x, int *y) const
{ {
#ifdef wxHAS_MSW_TASKDIALOG #ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() ) if ( HasNativeTaskDialog() )
{ {
wxPoint pos; const wxRect r = GetTaskDialogRect();
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
m_sharedData->m_state = m_state;
pos = m_sharedData->m_winPosition;
}
if (x) if (x)
*x = pos.x; *x = r.x;
if (y) if (y)
*y = pos.y; *y = r.y;
return; return;
} }
@@ -738,6 +878,42 @@ void wxProgressDialog::DoGetPosition(int *x, int *y) const
wxGenericProgressDialog::DoGetPosition(x, y); wxGenericProgressDialog::DoGetPosition(x, y);
} }
void wxProgressDialog::DoGetSize(int *width, int *height) const
{
#ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() )
{
const wxRect r = GetTaskDialogRect();
if ( width )
*width = r.width;
if ( height )
*height = r.height;
return;
}
#endif // wxHAS_MSW_TASKDIALOG
wxGenericProgressDialog::DoGetSize(width, height);
}
void wxProgressDialog::Fit()
{
#ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() )
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
// Force the task dialog to use this message to adjust it layout.
m_sharedData->m_msgChangeElementText = TDM_SET_ELEMENT_TEXT;
// Don't change the message, but pretend that it did change.
m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
}
#endif // wxHAS_MSW_TASKDIALOG
wxGenericProgressDialog::Fit();
}
bool wxProgressDialog::Show(bool show) bool wxProgressDialog::Show(bool show)
{ {
#ifdef wxHAS_MSW_TASKDIALOG #ifdef wxHAS_MSW_TASKDIALOG
@@ -777,8 +953,9 @@ bool wxProgressDialog::Show(bool show)
wxPD_ESTIMATED_TIME | wxPD_ESTIMATED_TIME |
wxPD_REMAINING_TIME) ) wxPD_REMAINING_TIME) )
{ {
// Use a non-empty string just to have the collapsible pane shown. // Set the expanded information field from the beginning to avoid
m_sharedData->m_expandedInformation = " "; // having to re-layout the dialog later when it changes.
UpdateExpandedInformation(0);
} }
// Do launch the thread. // Do launch the thread.
@@ -794,6 +971,16 @@ bool wxProgressDialog::Show(bool show)
return false; return false;
} }
// Wait until the dialog is shown as the program may need some time
// before it calls Update() and we want to show something to the user
// in the meanwhile.
while ( wxEventLoop::GetActive()->Dispatch() )
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
if ( m_sharedData->m_hwnd )
break;
}
// Do not show the underlying dialog. // Do not show the underlying dialog.
return false; return false;
} }
@@ -810,14 +997,15 @@ void wxProgressDialog::UpdateExpandedInformation(int value)
unsigned long remainingTime; unsigned long remainingTime;
UpdateTimeEstimates(value, elapsedTime, estimatedTime, remainingTime); UpdateTimeEstimates(value, elapsedTime, estimatedTime, remainingTime);
int realEstimatedTime = estimatedTime, // The value of 0 is special, we can't estimate anything before we have at
realRemainingTime = remainingTime; // least one update, so leave the times dependent on it indeterminate.
if ( m_sharedData->m_progressBarMarquee ) //
// This value is also used by Pulse(), as in the indeterminate mode we can
// never estimate anything.
if ( !value )
{ {
// In indeterminate mode we don't have any estimation neither for the estimatedTime =
// remaining nor for estimated time. remainingTime = static_cast<unsigned long>(-1);
realEstimatedTime =
realRemainingTime = -1;
} }
wxString expandedInformation; wxString expandedInformation;
@@ -837,7 +1025,7 @@ void wxProgressDialog::UpdateExpandedInformation(int value)
expandedInformation << GetEstimatedLabel() expandedInformation << GetEstimatedLabel()
<< " " << " "
<< GetFormattedTime(realEstimatedTime); << GetFormattedTime(estimatedTime);
} }
if ( HasPDFlag(wxPD_REMAINING_TIME) ) if ( HasPDFlag(wxPD_REMAINING_TIME) )
@@ -847,7 +1035,7 @@ void wxProgressDialog::UpdateExpandedInformation(int value)
expandedInformation << GetRemainingLabel() expandedInformation << GetRemainingLabel()
<< " " << " "
<< GetFormattedTime(realRemainingTime); << GetFormattedTime(remainingTime);
} }
// Update with new timing information. // Update with new timing information.
@@ -875,8 +1063,20 @@ void* wxProgressDialogTaskRunner::Entry()
{ {
wxCriticalSectionLocker locker(m_sharedData.m_cs); wxCriticalSectionLocker locker(m_sharedData.m_cs);
// If we have a parent, we must use it to have correct Z-order and
// icon, even if this comes at the price of attaching this thread input
// to the thread that created the parent window, i.e. the main thread.
wxTdc.parent = m_sharedData.m_parent;
wxTdc.caption = m_sharedData.m_title.wx_str(); wxTdc.caption = m_sharedData.m_title.wx_str();
wxTdc.message = m_sharedData.m_message.wx_str();
// Split the message into the title and main body text in the same way
// as it's done later in PerformNotificationUpdates() when the message
// is changed by Update() or Pulse().
m_sharedData.SplitMessageIntoTitleAndBody
(
wxTdc.message,
wxTdc.extendedMessage
);
// MSWCommonTaskDialogInit() will add an IDCANCEL button but we need to // MSWCommonTaskDialogInit() will add an IDCANCEL button but we need to
// give it the correct label. // give it the correct label.
@@ -887,10 +1087,6 @@ void* wxProgressDialogTaskRunner::Entry()
tdc.pfCallback = TaskDialogCallbackProc; tdc.pfCallback = TaskDialogCallbackProc;
tdc.lpCallbackData = (LONG_PTR) &m_sharedData; tdc.lpCallbackData = (LONG_PTR) &m_sharedData;
// Undo some of the effects of MSWCommonTaskDialogInit().
tdc.dwFlags &= ~TDF_EXPAND_FOOTER_AREA; // Expand in content area.
tdc.dwCommonButtons = 0; // Don't use common buttons.
if ( m_sharedData.m_style & wxPD_CAN_SKIP ) if ( m_sharedData.m_style & wxPD_CAN_SKIP )
wxTdc.AddTaskDialogButton( tdc, Id_SkipBtn, 0, _("Skip") ); wxTdc.AddTaskDialogButton( tdc, Id_SkipBtn, 0, _("Skip") );
@@ -900,6 +1096,13 @@ void* wxProgressDialogTaskRunner::Entry()
{ {
tdc.pszExpandedInformation = tdc.pszExpandedInformation =
m_sharedData.m_expandedInformation.t_str(); m_sharedData.m_expandedInformation.t_str();
// If we have elapsed/estimated/... times to show, show them from
// the beginning for consistency with the generic version and also
// because showing them later may be very sluggish if the main
// thread doesn't update the dialog sufficiently frequently, while
// hiding them still works reasonably well.
tdc.dwFlags |= TDF_EXPANDED_BY_DEFAULT;
} }
} }
@@ -930,6 +1133,10 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
LONG_PTR dwRefData LONG_PTR dwRefData
) )
{ {
bool endDialog = false;
// Block for shared data critical section.
{
wxProgressDialogSharedData * const sharedData = wxProgressDialogSharedData * const sharedData =
(wxProgressDialogSharedData *) dwRefData; (wxProgressDialogSharedData *) dwRefData;
@@ -941,45 +1148,20 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
// Store the HWND for the main thread use. // Store the HWND for the main thread use.
sharedData->m_hwnd = hwnd; sharedData->m_hwnd = hwnd;
// The main thread is sitting in an event dispatching loop waiting
// for this dialog to be shown, so make sure it does get an event.
wxWakeUpIdle();
// Set the maximum value and disable Close button. // Set the maximum value and disable Close button.
::SendMessage( hwnd, ::SendMessage( hwnd,
TDM_SET_PROGRESS_BAR_RANGE, TDM_SET_PROGRESS_BAR_RANGE,
0, 0,
MAKELPARAM(0, sharedData->m_range) ); MAKELPARAM(0, sharedData->m_range) );
// 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);
}
// Store current position for the main thread use
// if no position update is pending.
if ( !(sharedData->m_notifications & wxSPDD_WINDOW_MOVED) )
{
RECT r = wxGetWindowRect(hwnd);
sharedData->m_winPosition = wxPoint(r.left, r.top);
}
// If we can't be aborted, the "Close" button will only be enabled // 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). // when the progress ends (and not even then with wxPD_AUTO_HIDE).
if ( !(sharedData->m_style & wxPD_CAN_ABORT) ) if ( !(sharedData->m_style & wxPD_CAN_ABORT) )
::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE ); EnableCloseButtons(hwnd, false);
break; break;
case TDN_BUTTON_CLICKED: case TDN_BUTTON_CLICKED:
@@ -988,7 +1170,7 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
case Id_SkipBtn: case Id_SkipBtn:
::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE); ::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
sharedData->m_skipped = true; sharedData->m_skipped = true;
return TRUE; return S_FALSE;
case IDCANCEL: case IDCANCEL:
if ( sharedData->m_state == wxProgressDialog::Finished ) if ( sharedData->m_state == wxProgressDialog::Finished )
@@ -998,7 +1180,7 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
sharedData->m_state = wxProgressDialog::Dismissed; sharedData->m_state = wxProgressDialog::Dismissed;
// Let Windows close the dialog. // Let Windows close the dialog.
return FALSE; return S_OK;
} }
// Close button on the window triggers an IDCANCEL press, // Close button on the window triggers an IDCANCEL press,
@@ -1006,26 +1188,48 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
// a finished dialog. // a finished dialog.
if ( sharedData->m_style & wxPD_CAN_ABORT ) if ( sharedData->m_style & wxPD_CAN_ABORT )
{ {
wxCHECK_MSG switch ( sharedData->m_state )
( {
sharedData->m_state == wxProgressDialog::Continue, case wxProgressDialog::Canceled:
TRUE, // It can happen that we receive a second
"Dialog not in a cancelable state!" // cancel request before we had time to process
); // the first one, in which case simply do
// nothing for the subsequent one.
break;
::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE); case wxProgressDialog::Continue:
::SendMessage(hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE); ::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
EnableCloseButtons(hwnd, false);
sharedData->m_timeStop = wxGetCurrentTime(); sharedData->m_timeStop = wxGetCurrentTime();
sharedData->m_state = wxProgressDialog::Canceled; sharedData->m_state = wxProgressDialog::Canceled;
break;
// States which shouldn't be possible here:
// We shouldn't have an (enabled) cancel button at
// all then.
case wxProgressDialog::Uncancelable:
// This one was already dealt with above.
case wxProgressDialog::Finished:
// Normally it shouldn't be possible to get any
// notifications after switching to this state.
case wxProgressDialog::Dismissed:
wxFAIL_MSG( "unreachable" );
break;
}
} }
return TRUE; return S_FALSE;
} }
break; break;
case TDN_TIMER: case TDN_TIMER:
PerformNotificationUpdates(hwnd, sharedData); // Don't perform updates if nothing needs to be done.
if ( sharedData->m_notifications )
PerformNotificationUpdates(hwnd, sharedData);
/* /*
Decide whether we should end the dialog. This is done if either Decide whether we should end the dialog. This is done if either
@@ -1041,21 +1245,30 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
(sharedData->m_state == wxProgressDialog::Finished && (sharedData->m_state == wxProgressDialog::Finished &&
sharedData->m_style & wxPD_AUTO_HIDE) ) sharedData->m_style & wxPD_AUTO_HIDE) )
{ {
::EndDialog( hwnd, IDCLOSE ); // Don't call EndDialog() from here as it could deadlock
// because we are inside the shared data critical section, do
// it below after leaving it instead.
endDialog = true;
} }
sharedData->m_notifications = 0; sharedData->m_notifications = 0;
{
// Update current position for the main thread use.
RECT r = wxGetWindowRect(hwnd);
sharedData->m_winPosition = wxPoint(r.left, r.top);
}
return TRUE; if ( endDialog )
break;
return S_FALSE;
}
} // Leave shared data critical section.
if ( endDialog )
{
::EndDialog( hwnd, IDCLOSE );
return S_FALSE;
} }
// Return anything. // Return anything.
return 0; return S_OK;
} }
#endif // wxHAS_MSW_TASKDIALOG #endif // wxHAS_MSW_TASKDIALOG

View File

@@ -986,10 +986,11 @@ void wxTopLevelWindowMSW::SetIcons(const wxIconBundle& icons)
DoSelectAndSetIcon(icons, SM_CXICON, SM_CYICON, ICON_BIG); DoSelectAndSetIcon(icons, SM_CXICON, SM_CYICON, ICON_BIG);
} }
bool wxTopLevelWindowMSW::EnableCloseButton(bool enable) // static
bool wxTopLevelWindowMSW::MSWEnableCloseButton(WXHWND hwnd, bool enable)
{ {
// get system (a.k.a. window) menu // get system (a.k.a. window) menu
HMENU hmenu = GetSystemMenu(GetHwnd(), FALSE /* get it */); HMENU hmenu = GetSystemMenu(hwnd, FALSE /* get it */);
if ( !hmenu ) if ( !hmenu )
{ {
// no system menu at all -- ok if we want to remove the close button // no system menu at all -- ok if we want to remove the close button
@@ -1008,7 +1009,7 @@ bool wxTopLevelWindowMSW::EnableCloseButton(bool enable)
return false; return false;
} }
// update appearance immediately // update appearance immediately
if ( !::DrawMenuBar(GetHwnd()) ) if ( !::DrawMenuBar(hwnd) )
{ {
wxLogLastError(wxT("DrawMenuBar")); wxLogLastError(wxT("DrawMenuBar"));
} }
@@ -1016,6 +1017,11 @@ bool wxTopLevelWindowMSW::EnableCloseButton(bool enable)
return true; return true;
} }
bool wxTopLevelWindowMSW::EnableCloseButton(bool enable)
{
return MSWEnableCloseButton(GetHwnd(), enable);
}
// Window must have wxCAPTION and either wxCLOSE_BOX or wxSYSTEM_MENU for the // Window must have wxCAPTION and either wxCLOSE_BOX or wxSYSTEM_MENU for the
// button to be visible. Also check for wxMAXIMIZE_BOX because we don't want // button to be visible. Also check for wxMAXIMIZE_BOX because we don't want
// to enable a button that is excluded from the current style. // to enable a button that is excluded from the current style.