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