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