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