1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/msw/progdlg.cpp
3 // Purpose:     wxProgressDialog
4 // Author:      Rickard Westerlund
5 // Created:     2010-07-22
6 // Copyright:   (c) 2010 wxWidgets team
7 // Licence:     wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9 
10 // ============================================================================
11 // Declarations
12 // ============================================================================
13 
14 // ----------------------------------------------------------------------------
15 // Headers
16 // ----------------------------------------------------------------------------
17 
18 // For compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20 
21 #ifdef __BORLANDC__
22     #pragma hdrstop
23 #endif
24 
25 #if wxUSE_PROGRESSDLG && wxUSE_THREADS
26 
27 #include "wx/progdlg.h"
28 
29 #ifndef WX_PRECOMP
30     #include "wx/app.h"
31     #include "wx/msgdlg.h"
32     #include "wx/stopwatch.h"
33     #include "wx/msw/private.h"
34 #endif
35 
36 #include "wx/msw/private/msgdlg.h"
37 #include "wx/evtloop.h"
38 
39 using namespace wxMSWMessageDialog;
40 
41 #ifdef wxHAS_MSW_TASKDIALOG
42 
43 // ----------------------------------------------------------------------------
44 // Constants
45 // ----------------------------------------------------------------------------
46 
47 namespace
48 {
49 
50 // Notification values of wxProgressDialogSharedData::m_notifications
51 const int wxSPDD_VALUE_CHANGED     = 0x0001;
52 const int wxSPDD_RANGE_CHANGED     = 0x0002;
53 const int wxSPDD_PBMARQUEE_CHANGED = 0x0004;
54 const int wxSPDD_TITLE_CHANGED     = 0x0008;
55 const int wxSPDD_MESSAGE_CHANGED   = 0x0010;
56 const int wxSPDD_EXPINFO_CHANGED   = 0x0020;
57 const int wxSPDD_ENABLE_SKIP       = 0x0040;
58 const int wxSPDD_ENABLE_ABORT      = 0x0080;
59 const int wxSPDD_DISABLE_SKIP      = 0x0100;
60 const int wxSPDD_DISABLE_ABORT     = 0x0200;
61 const int wxSPDD_FINISHED          = 0x0400;
62 const int wxSPDD_DESTROYED         = 0x0800;
63 
64 const int Id_SkipBtn = wxID_HIGHEST + 1;
65 
66 } // anonymous namespace
67 
68 // ============================================================================
69 // Helper classes
70 // ============================================================================
71 
72 // Class used to share data between the main thread and the task dialog runner.
73 class wxProgressDialogSharedData
74 {
75 public:
wxProgressDialogSharedData()76     wxProgressDialogSharedData()
77     {
78         m_hwnd = 0;
79         m_value = 0;
80         m_progressBarMarquee = false;
81         m_skipped = false;
82         m_notifications = 0;
83         m_parent = NULL;
84     }
85 
86     wxCriticalSection m_cs;
87 
88     wxWindow *m_parent;     // Parent window only used to center us over it.
89     HWND m_hwnd;            // Task dialog handler
90     long m_style;           // wxProgressDialog style
91     int m_value;
92     int m_range;
93     wxString m_title;
94     wxString m_message;
95     wxString m_expandedInformation;
96     wxString m_labelCancel; // Privately used by callback.
97     unsigned long m_timeStop;
98 
99     wxProgressDialog::State m_state;
100     bool m_progressBarMarquee;
101     bool m_skipped;
102 
103     // Bit field that indicates fields that have been modified by the
104     // main thread so the task dialog runner knows what to update.
105     int m_notifications;
106 };
107 
108 // Runner thread that takes care of displaying and updating the
109 // task dialog.
110 class wxProgressDialogTaskRunner : public wxThread
111 {
112 public:
wxProgressDialogTaskRunner()113     wxProgressDialogTaskRunner()
114         : wxThread(wxTHREAD_JOINABLE)
115         { }
116 
GetSharedDataObject()117     wxProgressDialogSharedData* GetSharedDataObject()
118         { return &m_sharedData; }
119 
120 private:
121     wxProgressDialogSharedData m_sharedData;
122 
123     virtual void* Entry();
124 
125     static HRESULT CALLBACK TaskDialogCallbackProc(HWND hwnd,
126                                                    UINT uNotification,
127                                                    WPARAM wParam,
128                                                    LPARAM lParam,
129                                                    LONG_PTR dwRefData);
130 };
131 
132 namespace
133 {
134 
135 // A custom event loop which runs until the state of the dialog becomes
136 // "Dismissed".
137 class wxProgressDialogModalLoop : public wxEventLoop
138 {
139 public:
wxProgressDialogModalLoop(wxProgressDialogSharedData & data)140     wxProgressDialogModalLoop(wxProgressDialogSharedData& data)
141         : m_data(data)
142     {
143     }
144 
145 protected:
OnNextIteration()146     virtual void OnNextIteration()
147     {
148         wxCriticalSectionLocker locker(m_data.m_cs);
149 
150         if ( m_data.m_state == wxProgressDialog::Dismissed )
151             Exit();
152     }
153 
154     wxProgressDialogSharedData& m_data;
155 
156     wxDECLARE_NO_COPY_CLASS(wxProgressDialogModalLoop);
157 };
158 
159 // ============================================================================
160 // Helper functions
161 // ============================================================================
162 
DisplayCloseButton(HWND hwnd,LPARAM lParam)163 BOOL CALLBACK DisplayCloseButton(HWND hwnd, LPARAM lParam)
164 {
165     wxProgressDialogSharedData *sharedData =
166         (wxProgressDialogSharedData *) lParam;
167 
168     if ( wxGetWindowText( hwnd ) == sharedData->m_labelCancel )
169     {
170         sharedData->m_labelCancel = _("Close");
171         SendMessage( hwnd, WM_SETTEXT, 0,
172                      wxMSW_CONV_LPARAM(sharedData->m_labelCancel) );
173 
174         return FALSE;
175     }
176 
177     return TRUE;
178 }
179 
PerformNotificationUpdates(HWND hwnd,wxProgressDialogSharedData * sharedData)180 void PerformNotificationUpdates(HWND hwnd,
181                                 wxProgressDialogSharedData *sharedData)
182 {
183     // Update the appropriate dialog fields.
184     if ( sharedData->m_notifications & wxSPDD_RANGE_CHANGED )
185     {
186         ::SendMessage( hwnd,
187                        TDM_SET_PROGRESS_BAR_RANGE,
188                        0,
189                        MAKELPARAM(0, sharedData->m_range) );
190     }
191 
192     if ( sharedData->m_notifications & wxSPDD_VALUE_CHANGED )
193     {
194         ::SendMessage( hwnd,
195                        TDM_SET_PROGRESS_BAR_POS,
196                        sharedData->m_value,
197                        0 );
198     }
199 
200     if ( sharedData->m_notifications & wxSPDD_PBMARQUEE_CHANGED )
201     {
202         BOOL val = sharedData->m_progressBarMarquee ? TRUE : FALSE;
203         ::SendMessage( hwnd,
204                        TDM_SET_MARQUEE_PROGRESS_BAR,
205                        val,
206                        0 );
207         ::SendMessage( hwnd,
208                        TDM_SET_PROGRESS_BAR_MARQUEE,
209                        val,
210                        0 );
211     }
212 
213     if ( sharedData->m_notifications & wxSPDD_TITLE_CHANGED )
214         ::SetWindowText( hwnd, sharedData->m_title.t_str() );
215 
216     if ( sharedData->m_notifications & wxSPDD_MESSAGE_CHANGED )
217     {
218         // Split the message in the title string and the rest if it has
219         // multiple lines.
220         wxString
221             title = sharedData->m_message,
222             body;
223 
224         const size_t posNL = title.find('\n');
225         if ( posNL != wxString::npos )
226         {
227             // There can an extra new line between the first and subsequent
228             // lines to separate them as it looks better with the generic
229             // version -- but in this one, they're already separated by the use
230             // of different dialog elements, so suppress the extra new line.
231             int numNLs = 1;
232             if ( posNL < title.length() - 1 && title[posNL + 1] == '\n' )
233                 numNLs++;
234 
235             body.assign(title, posNL + numNLs, wxString::npos);
236             title.erase(posNL);
237         }
238         else // A single line
239         {
240             // Don't use title without the body, this doesn't make sense.
241             title.swap(body);
242         }
243 
244         ::SendMessage( hwnd,
245                        TDM_SET_ELEMENT_TEXT,
246                        TDE_MAIN_INSTRUCTION,
247                        wxMSW_CONV_LPARAM(title) );
248 
249         ::SendMessage( hwnd,
250                        TDM_SET_ELEMENT_TEXT,
251                        TDE_CONTENT,
252                        wxMSW_CONV_LPARAM(body) );
253     }
254 
255     if ( sharedData->m_notifications & wxSPDD_EXPINFO_CHANGED )
256     {
257         const wxString& expandedInformation =
258             sharedData->m_expandedInformation;
259         if ( !expandedInformation.empty() )
260         {
261             ::SendMessage( hwnd,
262                            TDM_SET_ELEMENT_TEXT,
263                            TDE_EXPANDED_INFORMATION,
264                            wxMSW_CONV_LPARAM(expandedInformation) );
265         }
266     }
267 
268     if ( sharedData->m_notifications & wxSPDD_ENABLE_SKIP )
269         ::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, TRUE );
270 
271     if ( sharedData->m_notifications & wxSPDD_ENABLE_ABORT )
272         ::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, TRUE );
273 
274     if ( sharedData->m_notifications & wxSPDD_DISABLE_SKIP )
275         ::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE );
276 
277     if ( sharedData->m_notifications & wxSPDD_DISABLE_ABORT )
278         ::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE );
279 
280     // Is the progress finished?
281     if ( sharedData->m_notifications & wxSPDD_FINISHED )
282     {
283         sharedData->m_state = wxProgressDialog::Finished;
284 
285         if ( !(sharedData->m_style & wxPD_AUTO_HIDE) )
286         {
287             // Change Cancel into Close and activate the button.
288             ::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE );
289             ::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, TRUE );
290             ::EnumChildWindows( hwnd, DisplayCloseButton,
291                                 (LPARAM) sharedData );
292         }
293     }
294 }
295 
296 } // anonymous namespace
297 
298 #endif // wxHAS_MSW_TASKDIALOG
299 
300 // ============================================================================
301 // wxProgressDialog implementation
302 // ============================================================================
303 
wxProgressDialog(const wxString & title,const wxString & message,int maximum,wxWindow * parent,int style)304 wxProgressDialog::wxProgressDialog( const wxString& title,
305                                     const wxString& message,
306                                     int maximum,
307                                     wxWindow *parent,
308                                     int style )
309     : wxGenericProgressDialog(),
310       m_taskDialogRunner(NULL),
311       m_sharedData(NULL),
312       m_message(message),
313       m_title(title)
314 {
315 #ifdef wxHAS_MSW_TASKDIALOG
316     if ( HasNativeTaskDialog() )
317     {
318         SetTopParent(parent);
319         SetPDStyle(style);
320         SetMaximum(maximum);
321 
322         Show();
323         DisableOtherWindows();
324 
325         return;
326     }
327 #endif // wxHAS_MSW_TASKDIALOG
328 
329     Create(title, message, maximum, parent, style);
330 }
331 
~wxProgressDialog()332 wxProgressDialog::~wxProgressDialog()
333 {
334 #ifdef wxHAS_MSW_TASKDIALOG
335     if ( !m_taskDialogRunner )
336         return;
337 
338     if ( m_sharedData )
339     {
340         wxCriticalSectionLocker locker(m_sharedData->m_cs);
341         m_sharedData->m_notifications |= wxSPDD_DESTROYED;
342     }
343 
344     m_taskDialogRunner->Wait();
345 
346     delete m_taskDialogRunner;
347 
348     ReenableOtherWindows();
349 
350     if ( GetTopParent() )
351         GetTopParent()->Raise();
352 #endif // wxHAS_MSW_TASKDIALOG
353 }
354 
Update(int value,const wxString & newmsg,bool * skip)355 bool wxProgressDialog::Update(int value, const wxString& newmsg, bool *skip)
356 {
357 #ifdef wxHAS_MSW_TASKDIALOG
358     if ( HasNativeTaskDialog() )
359     {
360         {
361             wxCriticalSectionLocker locker(m_sharedData->m_cs);
362 
363             // Do nothing in canceled state.
364             if ( !DoNativeBeforeUpdate(skip) )
365                 return false;
366 
367             value /= m_factor;
368 
369             wxASSERT_MSG( value <= m_maximum, wxT("invalid progress value") );
370 
371             m_sharedData->m_value = value;
372             m_sharedData->m_notifications |= wxSPDD_VALUE_CHANGED;
373 
374             if ( !newmsg.empty() )
375             {
376                 m_message = newmsg;
377                 m_sharedData->m_message = newmsg;
378                 m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
379             }
380 
381             if ( m_sharedData->m_progressBarMarquee )
382             {
383                 m_sharedData->m_progressBarMarquee = false;
384                 m_sharedData->m_notifications |= wxSPDD_PBMARQUEE_CHANGED;
385             }
386 
387             UpdateExpandedInformation( value );
388 
389             // If we didn't just reach the finish, all we have to do is to
390             // return true if the dialog wasn't cancelled and false otherwise.
391             if ( value != m_maximum || m_state == Finished )
392                 return m_sharedData->m_state != Canceled;
393 
394 
395             // On finishing, the dialog without wxPD_AUTO_HIDE style becomes a
396             // modal one meaning that we must block here until the user
397             // dismisses it.
398             m_state = Finished;
399             m_sharedData->m_state = Finished;
400             m_sharedData->m_notifications |= wxSPDD_FINISHED;
401             if ( HasPDFlag(wxPD_AUTO_HIDE) )
402                 return true;
403 
404             if ( newmsg.empty() )
405             {
406                 // Provide the finishing message if the application didn't.
407                 m_message = _("Done.");
408                 m_sharedData->m_message = m_message;
409                 m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
410             }
411         } // unlock m_sharedData->m_cs
412 
413         // We only get here when we need to wait for the dialog to terminate so
414         // do just this by running a custom event loop until the dialog is
415         // dismissed.
416         wxProgressDialogModalLoop loop(*m_sharedData);
417         loop.Run();
418         return true;
419     }
420 #endif // wxHAS_MSW_TASKDIALOG
421 
422     return wxGenericProgressDialog::Update( value, newmsg, skip );
423 }
424 
Pulse(const wxString & newmsg,bool * skip)425 bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip)
426 {
427 #ifdef wxHAS_MSW_TASKDIALOG
428     if ( HasNativeTaskDialog() )
429     {
430         wxCriticalSectionLocker locker(m_sharedData->m_cs);
431 
432         // Do nothing in canceled state.
433         if ( !DoNativeBeforeUpdate(skip) )
434             return false;
435 
436         if ( !m_sharedData->m_progressBarMarquee )
437         {
438             m_sharedData->m_progressBarMarquee = true;
439             m_sharedData->m_notifications |= wxSPDD_PBMARQUEE_CHANGED;
440         }
441 
442         if ( !newmsg.empty() )
443         {
444             m_message = newmsg;
445             m_sharedData->m_message = newmsg;
446             m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
447         }
448 
449         // The value passed here doesn't matter, only elapsed time makes sense
450         // in indeterminate mode anyhow.
451         UpdateExpandedInformation(0);
452 
453         return m_sharedData->m_state != Canceled;
454     }
455 #endif // wxHAS_MSW_TASKDIALOG
456 
457     return wxGenericProgressDialog::Pulse( newmsg, skip );
458 }
459 
DoNativeBeforeUpdate(bool * skip)460 bool wxProgressDialog::DoNativeBeforeUpdate(bool *skip)
461 {
462 #ifdef wxHAS_MSW_TASKDIALOG
463     if ( HasNativeTaskDialog() )
464     {
465         if ( m_sharedData->m_skipped  )
466         {
467             if ( skip && !*skip )
468             {
469                 *skip = true;
470                 m_sharedData->m_skipped = false;
471                 m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP;
472             }
473         }
474 
475         if ( m_sharedData->m_state == Canceled )
476             m_timeStop = m_sharedData->m_timeStop;
477 
478         return m_sharedData->m_state != Canceled;
479     }
480 #endif // wxHAS_MSW_TASKDIALOG
481 
482     wxUnusedVar(skip);
483     wxFAIL_MSG( "unreachable" );
484 
485     return false;
486 }
487 
Resume()488 void wxProgressDialog::Resume()
489 {
490     wxGenericProgressDialog::Resume();
491 
492 #ifdef wxHAS_MSW_TASKDIALOG
493     if ( HasNativeTaskDialog() )
494     {
495         HWND hwnd;
496 
497         {
498             wxCriticalSectionLocker locker(m_sharedData->m_cs);
499             m_sharedData->m_state = m_state;
500 
501             // "Skip" was disabled when "Cancel" had been clicked, so re-enable
502             // it now.
503             m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP;
504 
505             // Also re-enable "Cancel" itself
506             if ( HasPDFlag(wxPD_CAN_ABORT) )
507                 m_sharedData->m_notifications |= wxSPDD_ENABLE_ABORT;
508 
509             hwnd = m_sharedData->m_hwnd;
510         } // Unlock m_cs, we can't call any function operating on a dialog with
511           // it locked as it can result in a deadlock if the dialog callback is
512           // called by Windows.
513 
514         // After resuming we need to bring the window on top of the Z-order as
515         // it could be hidden by another window shown from the main thread,
516         // e.g. a confirmation dialog asking whether the user really wants to
517         // abort.
518         //
519         // Notice that this must be done from the main thread as it owns the
520         // currently active window and attempts to do this from the task dialog
521         // thread would simply fail.
522         ::BringWindowToTop(hwnd);
523     }
524 #endif // wxHAS_MSW_TASKDIALOG
525 }
526 
GetHandle() const527 WXWidget wxProgressDialog::GetHandle() const
528 {
529 #ifdef wxHAS_MSW_TASKDIALOG
530     if ( HasNativeTaskDialog() )
531     {
532         HWND hwnd;
533         {
534             wxCriticalSectionLocker locker(m_sharedData->m_cs);
535             m_sharedData->m_state = m_state;
536             hwnd = m_sharedData->m_hwnd;
537         }
538         return hwnd;
539     }
540 #endif
541     return wxGenericProgressDialog::GetHandle();
542 }
543 
GetValue() const544 int wxProgressDialog::GetValue() const
545 {
546 #ifdef wxHAS_MSW_TASKDIALOG
547     if ( HasNativeTaskDialog() )
548     {
549         wxCriticalSectionLocker locker(m_sharedData->m_cs);
550         return m_sharedData->m_value;
551     }
552 #endif // wxHAS_MSW_TASKDIALOG
553 
554     return wxGenericProgressDialog::GetValue();
555 }
556 
GetMessage() const557 wxString wxProgressDialog::GetMessage() const
558 {
559 #ifdef wxHAS_MSW_TASKDIALOG
560     if ( HasNativeTaskDialog() )
561         return m_message;
562 #endif // wxHAS_MSW_TASKDIALOG
563 
564     return wxGenericProgressDialog::GetMessage();
565 }
566 
SetRange(int maximum)567 void wxProgressDialog::SetRange(int maximum)
568 {
569 #ifdef wxHAS_MSW_TASKDIALOG
570     if ( HasNativeTaskDialog() )
571     {
572         SetMaximum(maximum);
573 
574         wxCriticalSectionLocker locker(m_sharedData->m_cs);
575 
576         m_sharedData->m_range = maximum;
577         m_sharedData->m_notifications |= wxSPDD_RANGE_CHANGED;
578 
579         return;
580     }
581 #endif // wxHAS_MSW_TASKDIALOG
582 
583     wxGenericProgressDialog::SetRange( maximum );
584 }
585 
WasSkipped() const586 bool wxProgressDialog::WasSkipped() const
587 {
588 #ifdef wxHAS_MSW_TASKDIALOG
589     if ( HasNativeTaskDialog() )
590     {
591         if ( !m_sharedData )
592         {
593             // Couldn't be skipped before being shown.
594             return false;
595         }
596 
597         wxCriticalSectionLocker locker(m_sharedData->m_cs);
598         return m_sharedData->m_skipped;
599     }
600 #endif // wxHAS_MSW_TASKDIALOG
601 
602     return wxGenericProgressDialog::WasSkipped();
603 }
604 
WasCancelled() const605 bool wxProgressDialog::WasCancelled() const
606 {
607 #ifdef wxHAS_MSW_TASKDIALOG
608     if ( HasNativeTaskDialog() )
609     {
610         wxCriticalSectionLocker locker(m_sharedData->m_cs);
611         return m_sharedData->m_state == Canceled;
612     }
613 #endif // wxHAS_MSW_TASKDIALOG
614 
615     return wxGenericProgressDialog::WasCancelled();
616 }
617 
SetTitle(const wxString & title)618 void wxProgressDialog::SetTitle(const wxString& title)
619 {
620 #ifdef wxHAS_MSW_TASKDIALOG
621     if ( HasNativeTaskDialog() )
622     {
623         m_title = title;
624 
625         if ( m_sharedData )
626         {
627             wxCriticalSectionLocker locker(m_sharedData->m_cs);
628             m_sharedData->m_title = title;
629             m_sharedData->m_notifications = wxSPDD_TITLE_CHANGED;
630         }
631     }
632 #endif // wxHAS_MSW_TASKDIALOG
633 
634     wxGenericProgressDialog::SetTitle(title);
635 }
636 
GetTitle() const637 wxString wxProgressDialog::GetTitle() const
638 {
639 #ifdef wxHAS_MSW_TASKDIALOG
640     if ( HasNativeTaskDialog() )
641         return m_title;
642 #endif // wxHAS_MSW_TASKDIALOG
643 
644     return wxGenericProgressDialog::GetTitle();
645 }
646 
Show(bool show)647 bool wxProgressDialog::Show(bool show)
648 {
649 #ifdef wxHAS_MSW_TASKDIALOG
650     if ( HasNativeTaskDialog() )
651     {
652         // The dialog can't be hidden at all and showing it again after it had
653         // been shown before doesn't do anything.
654         if ( !show || m_taskDialogRunner )
655             return false;
656 
657         // We're showing the dialog for the first time, create the thread that
658         // will manage it.
659         m_taskDialogRunner = new wxProgressDialogTaskRunner;
660         m_sharedData = m_taskDialogRunner->GetSharedDataObject();
661 
662         // Initialize shared data.
663         m_sharedData->m_title = m_title;
664         m_sharedData->m_message = m_message;
665         m_sharedData->m_range = m_maximum;
666         m_sharedData->m_state = Uncancelable;
667         m_sharedData->m_style = GetPDStyle();
668         m_sharedData->m_parent = GetTopParent();
669 
670         if ( HasPDFlag(wxPD_CAN_ABORT) )
671         {
672             m_sharedData->m_state = Continue;
673             m_sharedData->m_labelCancel = _("Cancel");
674         }
675         else // Dialog can't be cancelled.
676         {
677             // We still must have at least a single button in the dialog so
678             // just don't call it "Cancel" in this case.
679             m_sharedData->m_labelCancel = _("Close");
680         }
681 
682         if ( HasPDFlag(wxPD_ELAPSED_TIME |
683                          wxPD_ESTIMATED_TIME |
684                             wxPD_REMAINING_TIME) )
685         {
686             // Use a non-empty string just to have the collapsible pane shown.
687             m_sharedData->m_expandedInformation = " ";
688         }
689 
690         // Do launch the thread.
691         if ( m_taskDialogRunner->Create() != wxTHREAD_NO_ERROR )
692         {
693             wxLogError( "Unable to create thread!" );
694             return false;
695         }
696 
697         if ( m_taskDialogRunner->Run() != wxTHREAD_NO_ERROR )
698         {
699             wxLogError( "Unable to start thread!" );
700             return false;
701         }
702 
703         // Do not show the underlying dialog.
704         return false;
705     }
706 #endif // wxHAS_MSW_TASKDIALOG
707 
708     return wxGenericProgressDialog::Show( show );
709 }
710 
UpdateExpandedInformation(int value)711 void wxProgressDialog::UpdateExpandedInformation(int value)
712 {
713 #ifdef wxHAS_MSW_TASKDIALOG
714     unsigned long elapsedTime;
715     unsigned long estimatedTime;
716     unsigned long remainingTime;
717     UpdateTimeEstimates(value, elapsedTime, estimatedTime, remainingTime);
718 
719     int realEstimatedTime = estimatedTime,
720         realRemainingTime = remainingTime;
721     if ( m_sharedData->m_progressBarMarquee )
722     {
723         // In indeterminate mode we don't have any estimation neither for the
724         // remaining nor for estimated time.
725         realEstimatedTime =
726         realRemainingTime = -1;
727     }
728 
729     wxString expandedInformation;
730 
731     // Calculate the three different timing values.
732     if ( HasPDFlag(wxPD_ELAPSED_TIME) )
733     {
734         expandedInformation << GetElapsedLabel()
735                             << " "
736                             << GetFormattedTime(elapsedTime);
737     }
738 
739     if ( HasPDFlag(wxPD_ESTIMATED_TIME) )
740     {
741         if ( !expandedInformation.empty() )
742             expandedInformation += "\n";
743 
744         expandedInformation << GetEstimatedLabel()
745                             << " "
746                             << GetFormattedTime(realEstimatedTime);
747     }
748 
749     if ( HasPDFlag(wxPD_REMAINING_TIME) )
750     {
751         if ( !expandedInformation.empty() )
752             expandedInformation += "\n";
753 
754         expandedInformation << GetRemainingLabel()
755                             << " "
756                             << GetFormattedTime(realRemainingTime);
757     }
758 
759     // Update with new timing information.
760     if ( expandedInformation != m_sharedData->m_expandedInformation )
761     {
762         m_sharedData->m_expandedInformation = expandedInformation;
763         m_sharedData->m_notifications |= wxSPDD_EXPINFO_CHANGED;
764     }
765 #else // !wxHAS_MSW_TASKDIALOG
766     wxUnusedVar(value);
767 #endif // wxHAS_MSW_TASKDIALOG/!wxHAS_MSW_TASKDIALOG
768 }
769 
770 // ----------------------------------------------------------------------------
771 // wxProgressDialogTaskRunner and related methods
772 // ----------------------------------------------------------------------------
773 
774 #ifdef wxHAS_MSW_TASKDIALOG
775 
Entry()776 void* wxProgressDialogTaskRunner::Entry()
777 {
778     WinStruct<TASKDIALOGCONFIG> tdc;
779     wxMSWTaskDialogConfig wxTdc;
780 
781     {
782         wxCriticalSectionLocker locker(m_sharedData.m_cs);
783 
784         wxTdc.caption = m_sharedData.m_title.wx_str();
785         wxTdc.message = m_sharedData.m_message.wx_str();
786 
787         // MSWCommonTaskDialogInit() will add an IDCANCEL button but we need to
788         // give it the correct label.
789         wxTdc.btnOKLabel = m_sharedData.m_labelCancel;
790         wxTdc.useCustomLabels = true;
791 
792         wxTdc.MSWCommonTaskDialogInit( tdc );
793         tdc.pfCallback = TaskDialogCallbackProc;
794         tdc.lpCallbackData = (LONG_PTR) &m_sharedData;
795 
796         // Undo some of the effects of MSWCommonTaskDialogInit().
797         tdc.dwFlags &= ~TDF_EXPAND_FOOTER_AREA; // Expand in content area.
798         tdc.dwCommonButtons = 0; // Don't use common buttons.
799 
800         if ( m_sharedData.m_style & wxPD_CAN_SKIP )
801             wxTdc.AddTaskDialogButton( tdc, Id_SkipBtn, 0, _("Skip") );
802 
803         tdc.dwFlags |= TDF_CALLBACK_TIMER | TDF_SHOW_PROGRESS_BAR;
804 
805         if ( !m_sharedData.m_expandedInformation.empty() )
806         {
807             tdc.pszExpandedInformation =
808                 m_sharedData.m_expandedInformation.t_str();
809         }
810     }
811 
812     TaskDialogIndirect_t taskDialogIndirect = GetTaskDialogIndirectFunc();
813     if ( !taskDialogIndirect )
814         return NULL;
815 
816     int msAns;
817     HRESULT hr = taskDialogIndirect(&tdc, &msAns, NULL, NULL);
818     if ( FAILED(hr) )
819         wxLogApiError( "TaskDialogIndirect", hr );
820 
821     // If the main thread is waiting for us to exit inside the event loop in
822     // Update(), wake it up so that it checks our status again.
823     wxWakeUpIdle();
824 
825     return NULL;
826 }
827 
828 // static
829 HRESULT CALLBACK
TaskDialogCallbackProc(HWND hwnd,UINT uNotification,WPARAM wParam,LPARAM WXUNUSED (lParam),LONG_PTR dwRefData)830 wxProgressDialogTaskRunner::TaskDialogCallbackProc
831                             (
832                                 HWND hwnd,
833                                 UINT uNotification,
834                                 WPARAM wParam,
835                                 LPARAM WXUNUSED(lParam),
836                                 LONG_PTR dwRefData
837                             )
838 {
839     wxProgressDialogSharedData * const sharedData =
840         (wxProgressDialogSharedData *) dwRefData;
841 
842     wxCriticalSectionLocker locker(sharedData->m_cs);
843 
844     switch ( uNotification )
845     {
846         case TDN_CREATED:
847             // Store the HWND for the main thread use.
848             sharedData->m_hwnd = hwnd;
849 
850             // Set the maximum value and disable Close button.
851             ::SendMessage( hwnd,
852                            TDM_SET_PROGRESS_BAR_RANGE,
853                            0,
854                            MAKELPARAM(0, sharedData->m_range) );
855 
856             // We always create this task dialog with NULL parent because our
857             // parent in wx sense is a window created from a different thread
858             // and so can't be used as our real parent. However we still center
859             // this window on the parent one as the task dialogs do with their
860             // real parent usually.
861             if ( sharedData->m_parent )
862             {
863                 wxRect rect(wxRectFromRECT(wxGetWindowRect(hwnd)));
864                 rect = rect.CentreIn(sharedData->m_parent->GetRect());
865                 ::SetWindowPos(hwnd,
866                                NULL,
867                                rect.x,
868                                rect.y,
869                                -1,
870                                -1,
871                                SWP_NOACTIVATE |
872                                SWP_NOOWNERZORDER |
873                                SWP_NOSIZE |
874                                SWP_NOZORDER);
875             }
876 
877             // If we can't be aborted, the "Close" button will only be enabled
878             // when the progress ends (and not even then with wxPD_AUTO_HIDE).
879             if ( !(sharedData->m_style & wxPD_CAN_ABORT) )
880                 ::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE );
881             break;
882 
883         case TDN_BUTTON_CLICKED:
884             switch ( wParam )
885             {
886                 case Id_SkipBtn:
887                     ::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
888                     sharedData->m_skipped = true;
889                     return TRUE;
890 
891                 case IDCANCEL:
892                     if ( sharedData->m_state == wxProgressDialog::Finished )
893                     {
894                         // If the main thread is waiting for us, tell it that
895                         // we're gone (and if it doesn't wait, it's harmless).
896                         sharedData->m_state = wxProgressDialog::Dismissed;
897 
898                         // Let Windows close the dialog.
899                         return FALSE;
900                     }
901 
902                     // Close button on the window triggers an IDCANCEL press,
903                     // don't allow it when it should only be possible to close
904                     // a finished dialog.
905                     if ( sharedData->m_style & wxPD_CAN_ABORT )
906                     {
907                         wxCHECK_MSG
908                         (
909                             sharedData->m_state == wxProgressDialog::Continue,
910                             TRUE,
911                             "Dialog not in a cancelable state!"
912                         );
913 
914                         ::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
915                         ::SendMessage(hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE);
916 
917                         sharedData->m_timeStop = wxGetCurrentTime();
918                         sharedData->m_state = wxProgressDialog::Canceled;
919                     }
920 
921                     return TRUE;
922             }
923             break;
924 
925         case TDN_TIMER:
926             PerformNotificationUpdates(hwnd, sharedData);
927 
928             /*
929                 Decide whether we should end the dialog. This is done if either
930                 the dialog object itself was destroyed or if the progress
931                 finished and we were configured to hide automatically without
932                 waiting for the user to dismiss us.
933 
934                 Notice that we do not close the dialog if it was cancelled
935                 because it's up to the user code in the main thread to decide
936                 whether it really wants to cancel the dialog.
937              */
938             if ( (sharedData->m_notifications & wxSPDD_DESTROYED) ||
939                     (sharedData->m_state == wxProgressDialog::Finished &&
940                         sharedData->m_style & wxPD_AUTO_HIDE) )
941             {
942                 ::EndDialog( hwnd, IDCLOSE );
943             }
944 
945             sharedData->m_notifications = 0;
946 
947             return TRUE;
948     }
949 
950     // Return anything.
951     return 0;
952 }
953 
954 #endif // wxHAS_MSW_TASKDIALOG
955 
956 #endif // wxUSE_PROGRESSDLG && wxUSE_THREADS
957