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