Define _CRT_NONSTDC_NO_WARNINGS for zlib compilation with MSVC.
[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 // Copyright: (c) 2010 wxWidgets team
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // ============================================================================
11 // Declarations
12 // ============================================================================
13
14 // ----------------------------------------------------------------------------
15 // Headers
16 // ----------------------------------------------------------------------------
17
18 // For compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20
21 #ifdef __BORLANDC__
22 #pragma hdrstop
23 #endif
24
25 #if wxUSE_PROGRESSDLG && wxUSE_THREADS
26
27 #include "wx/progdlg.h"
28
29 #ifndef WX_PRECOMP
30 #include "wx/app.h"
31 #include "wx/msgdlg.h"
32 #include "wx/stopwatch.h"
33 #include "wx/msw/private.h"
34 #endif
35
36 #include "wx/msw/private/msgdlg.h"
37 #include "wx/evtloop.h"
38
39 using namespace wxMSWMessageDialog;
40
41 #ifdef wxHAS_MSW_TASKDIALOG
42
43 // ----------------------------------------------------------------------------
44 // Constants
45 // ----------------------------------------------------------------------------
46
47 namespace
48 {
49
50 // Notification values of wxProgressDialogSharedData::m_notifications
51 const int wxSPDD_VALUE_CHANGED = 0x0001;
52 const int wxSPDD_RANGE_CHANGED = 0x0002;
53 const int wxSPDD_PBMARQUEE_CHANGED = 0x0004;
54 const int wxSPDD_TITLE_CHANGED = 0x0008;
55 const int wxSPDD_MESSAGE_CHANGED = 0x0010;
56 const int wxSPDD_EXPINFO_CHANGED = 0x0020;
57 const int wxSPDD_ENABLE_SKIP = 0x0040;
58 const int wxSPDD_ENABLE_ABORT = 0x0080;
59 const int wxSPDD_DISABLE_SKIP = 0x0100;
60 const int wxSPDD_DISABLE_ABORT = 0x0200;
61 const int wxSPDD_FINISHED = 0x0400;
62 const int wxSPDD_DESTROYED = 0x0800;
63
64 const int Id_SkipBtn = wxID_HIGHEST + 1;
65
66 } // anonymous namespace
67
68 // ============================================================================
69 // Helper classes
70 // ============================================================================
71
72 // Class used to share data between the main thread and the task dialog runner.
73 class wxProgressDialogSharedData
74 {
75 public:
76 wxProgressDialogSharedData()
77 {
78 m_hwnd = 0;
79 m_value = 0;
80 m_progressBarMarquee = false;
81 m_skipped = false;
82 m_notifications = 0;
83 m_parent = NULL;
84 }
85
86 wxCriticalSection m_cs;
87
88 wxWindow *m_parent; // Parent window only used to center us over it.
89 HWND m_hwnd; // Task dialog handler
90 long m_style; // wxProgressDialog style
91 int m_value;
92 int m_range;
93 wxString m_title;
94 wxString m_message;
95 wxString m_expandedInformation;
96 wxString m_labelCancel; // Privately used by callback.
97 unsigned long m_timeStop;
98
99 wxProgressDialog::State m_state;
100 bool m_progressBarMarquee;
101 bool m_skipped;
102
103 // Bit field that indicates fields that have been modified by the
104 // main thread so the task dialog runner knows what to update.
105 int m_notifications;
106 };
107
108 // Runner thread that takes care of displaying and updating the
109 // task dialog.
110 class wxProgressDialogTaskRunner : public wxThread
111 {
112 public:
113 wxProgressDialogTaskRunner()
114 : wxThread(wxTHREAD_JOINABLE)
115 { }
116
117 wxProgressDialogSharedData* GetSharedDataObject()
118 { return &m_sharedData; }
119
120 private:
121 wxProgressDialogSharedData m_sharedData;
122
123 virtual void* Entry();
124
125 static HRESULT CALLBACK TaskDialogCallbackProc(HWND hwnd,
126 UINT uNotification,
127 WPARAM wParam,
128 LPARAM lParam,
129 LONG_PTR dwRefData);
130 };
131
132 namespace
133 {
134
135 // A custom event loop which runs until the state of the dialog becomes
136 // "Dismissed".
137 class wxProgressDialogModalLoop : public wxEventLoop
138 {
139 public:
140 wxProgressDialogModalLoop(wxProgressDialogSharedData& data)
141 : m_data(data)
142 {
143 }
144
145 protected:
146 virtual void OnNextIteration()
147 {
148 wxCriticalSectionLocker locker(m_data.m_cs);
149
150 if ( m_data.m_state == wxProgressDialog::Dismissed )
151 Exit();
152 }
153
154 wxProgressDialogSharedData& m_data;
155
156 wxDECLARE_NO_COPY_CLASS(wxProgressDialogModalLoop);
157 };
158
159 // ============================================================================
160 // Helper functions
161 // ============================================================================
162
163 BOOL CALLBACK DisplayCloseButton(HWND hwnd, LPARAM lParam)
164 {
165 wxProgressDialogSharedData *sharedData =
166 (wxProgressDialogSharedData *) lParam;
167
168 if ( wxGetWindowText( hwnd ) == sharedData->m_labelCancel )
169 {
170 sharedData->m_labelCancel = _("Close");
171 SendMessage( hwnd, WM_SETTEXT, 0,
172 wxMSW_CONV_LPARAM(sharedData->m_labelCancel) );
173
174 return FALSE;
175 }
176
177 return TRUE;
178 }
179
180 void PerformNotificationUpdates(HWND hwnd,
181 wxProgressDialogSharedData *sharedData)
182 {
183 // Update the appropriate dialog fields.
184 if ( sharedData->m_notifications & wxSPDD_RANGE_CHANGED )
185 {
186 ::SendMessage( hwnd,
187 TDM_SET_PROGRESS_BAR_RANGE,
188 0,
189 MAKELPARAM(0, sharedData->m_range) );
190 }
191
192 if ( sharedData->m_notifications & wxSPDD_VALUE_CHANGED )
193 {
194 ::SendMessage( hwnd,
195 TDM_SET_PROGRESS_BAR_POS,
196 sharedData->m_value,
197 0 );
198 }
199
200 if ( sharedData->m_notifications & wxSPDD_PBMARQUEE_CHANGED )
201 {
202 BOOL val = sharedData->m_progressBarMarquee ? TRUE : FALSE;
203 ::SendMessage( hwnd,
204 TDM_SET_MARQUEE_PROGRESS_BAR,
205 val,
206 0 );
207 ::SendMessage( hwnd,
208 TDM_SET_PROGRESS_BAR_MARQUEE,
209 val,
210 0 );
211 }
212
213 if ( sharedData->m_notifications & wxSPDD_TITLE_CHANGED )
214 ::SetWindowText( hwnd, sharedData->m_title.t_str() );
215
216 if ( sharedData->m_notifications & wxSPDD_MESSAGE_CHANGED )
217 {
218 // Split the message in the title string and the rest if it has
219 // multiple lines.
220 wxString
221 title = sharedData->m_message,
222 body;
223
224 const size_t posNL = title.find('\n');
225 if ( posNL != wxString::npos )
226 {
227 // There can an extra new line between the first and subsequent
228 // lines to separate them as it looks better with the generic
229 // version -- but in this one, they're already separated by the use
230 // of different dialog elements, so suppress the extra new line.
231 int numNLs = 1;
232 if ( posNL < title.length() - 1 && title[posNL + 1] == '\n' )
233 numNLs++;
234
235 body.assign(title, posNL + numNLs, wxString::npos);
236 title.erase(posNL);
237 }
238 else // A single line
239 {
240 // Don't use title without the body, this doesn't make sense.
241 title.swap(body);
242 }
243
244 ::SendMessage( hwnd,
245 TDM_SET_ELEMENT_TEXT,
246 TDE_MAIN_INSTRUCTION,
247 wxMSW_CONV_LPARAM(title) );
248
249 ::SendMessage( hwnd,
250 TDM_SET_ELEMENT_TEXT,
251 TDE_CONTENT,
252 wxMSW_CONV_LPARAM(body) );
253 }
254
255 if ( sharedData->m_notifications & wxSPDD_EXPINFO_CHANGED )
256 {
257 const wxString& expandedInformation =
258 sharedData->m_expandedInformation;
259 if ( !expandedInformation.empty() )
260 {
261 ::SendMessage( hwnd,
262 TDM_SET_ELEMENT_TEXT,
263 TDE_EXPANDED_INFORMATION,
264 wxMSW_CONV_LPARAM(expandedInformation) );
265 }
266 }
267
268 if ( sharedData->m_notifications & wxSPDD_ENABLE_SKIP )
269 ::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, TRUE );
270
271 if ( sharedData->m_notifications & wxSPDD_ENABLE_ABORT )
272 ::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, TRUE );
273
274 if ( sharedData->m_notifications & wxSPDD_DISABLE_SKIP )
275 ::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE );
276
277 if ( sharedData->m_notifications & wxSPDD_DISABLE_ABORT )
278 ::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE );
279
280 // Is the progress finished?
281 if ( sharedData->m_notifications & wxSPDD_FINISHED )
282 {
283 sharedData->m_state = wxProgressDialog::Finished;
284
285 if ( !(sharedData->m_style & wxPD_AUTO_HIDE) )
286 {
287 // Change Cancel into Close and activate the button.
288 ::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE );
289 ::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, TRUE );
290 ::EnumChildWindows( hwnd, DisplayCloseButton,
291 (LPARAM) sharedData );
292 }
293 }
294 }
295
296 } // anonymous namespace
297
298 #endif // wxHAS_MSW_TASKDIALOG
299
300 // ============================================================================
301 // wxProgressDialog implementation
302 // ============================================================================
303
304 wxProgressDialog::wxProgressDialog( const wxString& title,
305 const wxString& message,
306 int maximum,
307 wxWindow *parent,
308 int style )
309 : wxGenericProgressDialog(),
310 m_taskDialogRunner(NULL),
311 m_sharedData(NULL),
312 m_message(message),
313 m_title(title)
314 {
315 #ifdef wxHAS_MSW_TASKDIALOG
316 if ( HasNativeTaskDialog() )
317 {
318 SetTopParent(parent);
319 SetPDStyle(style);
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.t_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