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