Block in wxMSW wxProgressDialog::Update(max) until the dialog is dismissed.
[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) && newmsg.empty() )
394 {
395 // Provide the finishing message if the application didn't.
396 m_message = _("Done.");
397 m_sharedData->m_message = m_message;
398 m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
399 }
400 } // unlock m_sharedData->m_cs
401
402 // We only get here when we need to wait for the dialog to terminate so
403 // do just this by running a custom event loop until the dialog is
404 // dismissed.
405 wxProgressDialogModalLoop loop(*m_sharedData);
406 loop.Run();
407 return true;
408 }
409 #endif // wxHAS_MSW_TASKDIALOG
410
411 return wxGenericProgressDialog::Update( value, newmsg, skip );
412 }
413
414 bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip)
415 {
416 #ifdef wxHAS_MSW_TASKDIALOG
417 if ( HasNativeProgressDialog() )
418 {
419 wxCriticalSectionLocker locker(m_sharedData->m_cs);
420
421 // Do nothing in canceled state.
422 if ( !DoNativeBeforeUpdate(skip) )
423 return false;
424
425 if ( !m_sharedData->m_progressBarMarquee )
426 {
427 m_sharedData->m_progressBarMarquee = true;
428 m_sharedData->m_notifications |= wxSPDD_PBMARQUEE_CHANGED;
429 }
430
431 if ( !newmsg.empty() )
432 {
433 m_message = newmsg;
434 m_sharedData->m_message = newmsg;
435 m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
436 }
437
438 // The value passed here doesn't matter, only elapsed time makes sense
439 // in indeterminate mode anyhow.
440 UpdateExpandedInformation(0);
441
442 return m_sharedData->m_state != Canceled;
443 }
444 #endif // wxHAS_MSW_TASKDIALOG
445
446 return wxGenericProgressDialog::Pulse( newmsg, skip );
447 }
448
449 bool wxProgressDialog::DoNativeBeforeUpdate(bool *skip)
450 {
451 #ifdef wxHAS_MSW_TASKDIALOG
452 if ( HasNativeProgressDialog() )
453 {
454 if ( m_sharedData->m_skipped )
455 {
456 if ( skip && !*skip )
457 {
458 *skip = true;
459 m_sharedData->m_skipped = false;
460 m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP;
461 }
462 }
463
464 if ( m_sharedData->m_state == Canceled )
465 m_timeStop = m_sharedData->m_timeStop;
466
467 return m_sharedData->m_state != Canceled;
468 }
469 #endif // wxHAS_MSW_TASKDIALOG
470
471 wxUnusedVar(skip);
472 wxFAIL_MSG( "unreachable" );
473
474 return false;
475 }
476
477 void wxProgressDialog::Resume()
478 {
479 wxGenericProgressDialog::Resume();
480
481 #ifdef wxHAS_MSW_TASKDIALOG
482 if ( HasNativeProgressDialog() )
483 {
484 HWND hwnd;
485
486 {
487 wxCriticalSectionLocker locker(m_sharedData->m_cs);
488 m_sharedData->m_state = m_state;
489
490 // "Skip" was disabled when "Cancel" had been clicked, so re-enable
491 // it now.
492 m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP;
493
494 if ( !UsesCloseButtonOnly(GetPDStyle()) )
495 m_sharedData->m_notifications |= wxSPDD_ENABLE_ABORT;
496
497 hwnd = m_sharedData->m_hwnd;
498 } // Unlock m_cs, we can't call any function operating on a dialog with
499 // it locked as it can result in a deadlock if the dialog callback is
500 // called by Windows.
501
502 // After resuming we need to bring the window on top of the Z-order as
503 // it could be hidden by another window shown from the main thread,
504 // e.g. a confirmation dialog asking whether the user really wants to
505 // abort.
506 //
507 // Notice that this must be done from the main thread as it owns the
508 // currently active window and attempts to do this from the task dialog
509 // thread would simply fail.
510 ::BringWindowToTop(hwnd);
511 }
512 #endif // wxHAS_MSW_TASKDIALOG
513 }
514
515 int wxProgressDialog::GetValue() const
516 {
517 #ifdef wxHAS_MSW_TASKDIALOG
518 if ( HasNativeProgressDialog() )
519 {
520 wxCriticalSectionLocker locker(m_sharedData->m_cs);
521 return m_sharedData->m_value;
522 }
523 #endif // wxHAS_MSW_TASKDIALOG
524
525 return wxGenericProgressDialog::GetValue();
526 }
527
528 wxString wxProgressDialog::GetMessage() const
529 {
530 #ifdef wxHAS_MSW_TASKDIALOG
531 if ( HasNativeProgressDialog() )
532 return m_message;
533 #endif // wxHAS_MSW_TASKDIALOG
534
535 return wxGenericProgressDialog::GetMessage();
536 }
537
538 void wxProgressDialog::SetRange(int maximum)
539 {
540 #ifdef wxHAS_MSW_TASKDIALOG
541 if ( HasNativeProgressDialog() )
542 {
543 SetMaximum(maximum);
544
545 wxCriticalSectionLocker locker(m_sharedData->m_cs);
546
547 m_sharedData->m_range = maximum;
548 m_sharedData->m_notifications |= wxSPDD_RANGE_CHANGED;
549
550 return;
551 }
552 #endif // wxHAS_MSW_TASKDIALOG
553
554 wxGenericProgressDialog::SetRange( maximum );
555 }
556
557 bool wxProgressDialog::WasSkipped() const
558 {
559 #ifdef wxHAS_MSW_TASKDIALOG
560 if ( HasNativeProgressDialog() )
561 {
562 if ( !m_sharedData )
563 {
564 // Couldn't be skipped before being shown.
565 return false;
566 }
567
568 wxCriticalSectionLocker locker(m_sharedData->m_cs);
569 return m_sharedData->m_skipped;
570 }
571 #endif // wxHAS_MSW_TASKDIALOG
572
573 return wxGenericProgressDialog::WasSkipped();
574 }
575
576 bool wxProgressDialog::WasCancelled() const
577 {
578 #ifdef wxHAS_MSW_TASKDIALOG
579 if ( HasNativeProgressDialog() )
580 {
581 wxCriticalSectionLocker locker(m_sharedData->m_cs);
582 return m_sharedData->m_state == Canceled;
583 }
584 #endif // wxHAS_MSW_TASKDIALOG
585
586 return wxGenericProgressDialog::WasCancelled();
587 }
588
589 void wxProgressDialog::SetTitle(const wxString& title)
590 {
591 #ifdef wxHAS_MSW_TASKDIALOG
592 if ( HasNativeProgressDialog() )
593 {
594 m_title = title;
595
596 if ( m_sharedData )
597 {
598 wxCriticalSectionLocker locker(m_sharedData->m_cs);
599 m_sharedData->m_title = title;
600 m_sharedData->m_notifications = wxSPDD_TITLE_CHANGED;
601 }
602 }
603 #endif // wxHAS_MSW_TASKDIALOG
604
605 wxGenericProgressDialog::SetTitle(title);
606 }
607
608 wxString wxProgressDialog::GetTitle() const
609 {
610 #ifdef wxHAS_MSW_TASKDIALOG
611 if ( HasNativeProgressDialog() )
612 return m_title;
613 #endif // wxHAS_MSW_TASKDIALOG
614
615 return wxGenericProgressDialog::GetTitle();
616 }
617
618 bool wxProgressDialog::Show(bool show)
619 {
620 #ifdef wxHAS_MSW_TASKDIALOG
621 if ( HasNativeProgressDialog() )
622 {
623 // The dialog can't be hidden at all and showing it again after it had
624 // been shown before doesn't do anything.
625 if ( !show || m_taskDialogRunner )
626 return false;
627
628 // We're showing the dialog for the first time, create the thread that
629 // will manage it.
630 m_taskDialogRunner = new wxProgressDialogTaskRunner;
631 m_sharedData = m_taskDialogRunner->GetSharedDataObject();
632
633 // Initialize shared data.
634 m_sharedData->m_title = m_title;
635 m_sharedData->m_message = m_message;
636 m_sharedData->m_range = m_maximum;
637 m_sharedData->m_state = Uncancelable;
638 m_sharedData->m_style = GetPDStyle();
639
640 if ( HasPDFlag(wxPD_CAN_ABORT) )
641 {
642 m_sharedData->m_state = Continue;
643 m_sharedData->m_labelCancel = _("Cancel");
644 }
645 else if ( !HasPDFlag(wxPD_AUTO_HIDE) )
646 {
647 m_sharedData->m_labelCancel = _("Close");
648 }
649
650 if ( HasPDFlag(wxPD_ELAPSED_TIME |
651 wxPD_ESTIMATED_TIME |
652 wxPD_REMAINING_TIME) )
653 {
654 // Use a non-empty string just to have the collapsible pane shown.
655 m_sharedData->m_expandedInformation = " ";
656 }
657
658 // Do launch the thread.
659 if ( m_taskDialogRunner->Create() != wxTHREAD_NO_ERROR )
660 {
661 wxLogError( "Unable to create thread!" );
662 return false;
663 }
664
665 if ( m_taskDialogRunner->Run() != wxTHREAD_NO_ERROR )
666 {
667 wxLogError( "Unable to start thread!" );
668 return false;
669 }
670
671 // Do not show the underlying dialog.
672 return false;
673 }
674 #endif // wxHAS_MSW_TASKDIALOG
675
676 return wxGenericProgressDialog::Show( show );
677 }
678
679 bool wxProgressDialog::HasNativeProgressDialog() const
680 {
681 #ifdef wxHAS_MSW_TASKDIALOG
682 // Native task dialog, if available, can't be used without any buttons so
683 // we fall back to the generic one if none of "Skip", "Cancel" and "Close"
684 // buttons is used.
685 return HasNativeTaskDialog()
686 && (HasPDFlag(wxPD_CAN_SKIP | wxPD_CAN_ABORT) ||
687 !HasPDFlag(wxPD_AUTO_HIDE));
688 #else // !wxHAS_MSW_TASKDIALOG
689 // This shouldn't be even called in !wxHAS_MSW_TASKDIALOG case but as we
690 // still must define the function as returning something, return false.
691 return false;
692 #endif // wxHAS_MSW_TASKDIALOG/!wxHAS_MSW_TASKDIALOG
693 }
694
695 void wxProgressDialog::UpdateExpandedInformation(int value)
696 {
697 #ifdef wxHAS_MSW_TASKDIALOG
698 unsigned long elapsedTime;
699 unsigned long estimatedTime;
700 unsigned long remainingTime;
701 UpdateTimeEstimates(value, elapsedTime, estimatedTime, remainingTime);
702
703 int realEstimatedTime = estimatedTime,
704 realRemainingTime = remainingTime;
705 if ( m_sharedData->m_progressBarMarquee )
706 {
707 // In indeterminate mode we don't have any estimation neither for the
708 // remaining nor for estimated time.
709 realEstimatedTime =
710 realRemainingTime = -1;
711 }
712
713 wxString expandedInformation;
714
715 // Calculate the three different timing values.
716 if ( HasPDFlag(wxPD_ELAPSED_TIME) )
717 {
718 expandedInformation << GetElapsedLabel()
719 << " "
720 << GetFormattedTime(elapsedTime);
721 }
722
723 if ( HasPDFlag(wxPD_ESTIMATED_TIME) )
724 {
725 if ( !expandedInformation.empty() )
726 expandedInformation += "\n";
727
728 expandedInformation << GetEstimatedLabel()
729 << " "
730 << GetFormattedTime(realEstimatedTime);
731 }
732
733 if ( HasPDFlag(wxPD_REMAINING_TIME) )
734 {
735 if ( !expandedInformation.empty() )
736 expandedInformation += "\n";
737
738 expandedInformation << GetRemainingLabel()
739 << " "
740 << GetFormattedTime(realRemainingTime);
741 }
742
743 // Update with new timing information.
744 if ( expandedInformation != m_sharedData->m_expandedInformation )
745 {
746 m_sharedData->m_expandedInformation = expandedInformation;
747 m_sharedData->m_notifications |= wxSPDD_EXPINFO_CHANGED;
748 }
749 #else // !wxHAS_MSW_TASKDIALOG
750 wxUnusedVar(value);
751 #endif // wxHAS_MSW_TASKDIALOG/!wxHAS_MSW_TASKDIALOG
752 }
753
754 // ----------------------------------------------------------------------------
755 // wxProgressDialogTaskRunner and related methods
756 // ----------------------------------------------------------------------------
757
758 #ifdef wxHAS_MSW_TASKDIALOG
759
760 void* wxProgressDialogTaskRunner::Entry()
761 {
762 WinStruct<TASKDIALOGCONFIG> tdc;
763 wxMSWTaskDialogConfig wxTdc;
764
765 {
766 wxCriticalSectionLocker locker(m_sharedData.m_cs);
767
768 wxTdc.caption = m_sharedData.m_title.wx_str();
769 wxTdc.message = m_sharedData.m_message.wx_str();
770
771 wxTdc.MSWCommonTaskDialogInit( tdc );
772 tdc.pfCallback = TaskDialogCallbackProc;
773 tdc.lpCallbackData = (LONG_PTR) &m_sharedData;
774
775 // Undo some of the effects of MSWCommonTaskDialogInit().
776 tdc.dwFlags &= ~TDF_EXPAND_FOOTER_AREA; // Expand in content area.
777 tdc.dwCommonButtons = 0; // Don't use common buttons.
778
779 wxTdc.useCustomLabels = true;
780
781 if ( m_sharedData.m_style & wxPD_CAN_SKIP )
782 wxTdc.AddTaskDialogButton( tdc, Id_SkipBtn, 0, _("Skip") );
783
784 // Use a Cancel button when requested or use a Close button when
785 // the dialog does not automatically hide.
786 if ( (m_sharedData.m_style & wxPD_CAN_ABORT)
787 || !(m_sharedData.m_style & wxPD_AUTO_HIDE) )
788 {
789 wxTdc.AddTaskDialogButton( tdc, IDCANCEL, 0,
790 m_sharedData.m_labelCancel );
791 }
792
793 tdc.dwFlags |= TDF_CALLBACK_TIMER | TDF_SHOW_PROGRESS_BAR;
794
795 if ( !m_sharedData.m_expandedInformation.empty() )
796 {
797 tdc.pszExpandedInformation =
798 m_sharedData.m_expandedInformation.wx_str();
799 }
800 }
801
802 TaskDialogIndirect_t taskDialogIndirect = GetTaskDialogIndirectFunc();
803 if ( !taskDialogIndirect )
804 return NULL;
805
806 int msAns;
807 HRESULT hr = taskDialogIndirect(&tdc, &msAns, NULL, NULL);
808 if ( FAILED(hr) )
809 wxLogApiError( "TaskDialogIndirect", hr );
810
811 // If the main thread is waiting for us to exit inside the event loop in
812 // Update(), wake it up so that it checks our status again.
813 wxWakeUpIdle();
814
815 return NULL;
816 }
817
818 // static
819 HRESULT CALLBACK
820 wxProgressDialogTaskRunner::TaskDialogCallbackProc
821 (
822 HWND hwnd,
823 UINT uNotification,
824 WPARAM wParam,
825 LPARAM WXUNUSED(lParam),
826 LONG_PTR dwRefData
827 )
828 {
829 wxProgressDialogSharedData * const sharedData =
830 (wxProgressDialogSharedData *) dwRefData;
831
832 wxCriticalSectionLocker locker(sharedData->m_cs);
833
834 switch ( uNotification )
835 {
836 case TDN_CREATED:
837 // Store the HWND for the main thread use.
838 sharedData->m_hwnd = hwnd;
839
840 // Set the maximum value and disable Close button.
841 ::SendMessage( hwnd,
842 TDM_SET_PROGRESS_BAR_RANGE,
843 0,
844 MAKELPARAM(0, sharedData->m_range) );
845
846 if ( UsesCloseButtonOnly(sharedData->m_style) )
847 ::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE );
848 break;
849
850 case TDN_BUTTON_CLICKED:
851 switch ( wParam )
852 {
853 case Id_SkipBtn:
854 ::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
855 sharedData->m_skipped = true;
856 return TRUE;
857
858 case IDCANCEL:
859 if ( sharedData->m_state == wxProgressDialog::Finished )
860 {
861 // If the main thread is waiting for us, tell it that
862 // we're gone (and if it doesn't wait, it's harmless).
863 sharedData->m_state = wxProgressDialog::Dismissed;
864
865 // Let Windows close the dialog.
866 return FALSE;
867 }
868
869 // Close button on the window triggers an IDCANCEL press,
870 // don't allow it when it should only be possible to close
871 // a finished dialog.
872 if ( !UsesCloseButtonOnly(sharedData->m_style) )
873 {
874 wxCHECK_MSG
875 (
876 sharedData->m_state == wxProgressDialog::Continue,
877 TRUE,
878 "Dialog not in a cancelable state!"
879 );
880
881 ::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
882 ::SendMessage(hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE);
883
884 sharedData->m_timeStop = wxGetCurrentTime();
885 sharedData->m_state = wxProgressDialog::Canceled;
886 }
887
888 return TRUE;
889 }
890 break;
891
892 case TDN_TIMER:
893 PerformNotificationUpdates(hwnd, sharedData);
894
895 // End dialog in three different cases:
896 // 1. Progress finished and dialog should automatically hide.
897 // 2. The wxProgressDialog object was destructed and should
898 // automatically hide.
899 // 3. The dialog was canceled and wxProgressDialog object
900 // was destroyed.
901 bool isCanceled =
902 sharedData->m_state == wxGenericProgressDialog::Canceled;
903 bool isFinished =
904 sharedData->m_state == wxGenericProgressDialog::Finished;
905 bool wasDestroyed =
906 (sharedData->m_notifications & wxSPDD_DESTROYED) != 0;
907 bool shouldAutoHide = (sharedData->m_style & wxPD_AUTO_HIDE) != 0;
908
909 if ( (shouldAutoHide && (isFinished || wasDestroyed))
910 || (wasDestroyed && isCanceled) )
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