Fix closing logic in wxMSW native wxProgressDialog.
[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 /*
896 Decide whether we should end the dialog. This is done if either
897 the dialog object itself was destroyed or if the progress
898 finished and we were configured to hide automatically without
899 waiting for the user to dismiss us.
900
901 Notice that we do not close the dialog if it was cancelled
902 because it's up to the user code in the main thread to decide
903 whether it really wants to cancel the dialog.
904 */
905 if ( (sharedData->m_notifications & wxSPDD_DESTROYED) ||
906 (sharedData->m_state == wxProgressDialog::Finished &&
907 sharedData->m_style & wxPD_AUTO_HIDE) )
908 {
909 ::EndDialog( hwnd, IDCLOSE );
910 }
911
912 sharedData->m_notifications = 0;
913
914 return TRUE;
915 }
916
917 // Return anything.
918 return 0;
919 }
920
921 #endif // wxHAS_MSW_TASKDIALOG
922
923 #endif // wxUSE_PROGRESSDLG && wxUSE_THREADS