Don't call ::GetLayout() in wxMSW code directly.
[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 wxMSW_CONV_LPARAM(sharedData->m_labelCancel) );
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.t_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 wxMSW_CONV_LPARAM(title) );
249
250 ::SendMessage( hwnd,
251 TDM_SET_ELEMENT_TEXT,
252 TDE_CONTENT,
253 wxMSW_CONV_LPARAM(body) );
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 wxMSW_CONV_LPARAM(expandedInformation) );
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(),
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 SetTopParent(parent);
320 SetPDStyle(style);
321 SetMaximum(maximum);
322
323 Show();
324 DisableOtherWindows();
325
326 return;
327 }
328 #endif // wxHAS_MSW_TASKDIALOG
329
330 Create(title, message, maximum, parent, style);
331 }
332
333 wxProgressDialog::~wxProgressDialog()
334 {
335 #ifdef wxHAS_MSW_TASKDIALOG
336 if ( !m_taskDialogRunner )
337 return;
338
339 if ( m_sharedData )
340 {
341 wxCriticalSectionLocker locker(m_sharedData->m_cs);
342 m_sharedData->m_notifications |= wxSPDD_DESTROYED;
343 }
344
345 m_taskDialogRunner->Wait();
346
347 delete m_taskDialogRunner;
348
349 ReenableOtherWindows();
350
351 if ( GetTopParent() )
352 GetTopParent()->Raise();
353 #endif // wxHAS_MSW_TASKDIALOG
354 }
355
356 bool wxProgressDialog::Update(int value, const wxString& newmsg, bool *skip)
357 {
358 #ifdef wxHAS_MSW_TASKDIALOG
359 if ( HasNativeTaskDialog() )
360 {
361 {
362 wxCriticalSectionLocker locker(m_sharedData->m_cs);
363
364 // Do nothing in canceled state.
365 if ( !DoNativeBeforeUpdate(skip) )
366 return false;
367
368 value /= m_factor;
369
370 wxASSERT_MSG( value <= m_maximum, wxT("invalid progress value") );
371
372 m_sharedData->m_value = value;
373 m_sharedData->m_notifications |= wxSPDD_VALUE_CHANGED;
374
375 if ( !newmsg.empty() )
376 {
377 m_message = newmsg;
378 m_sharedData->m_message = newmsg;
379 m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
380 }
381
382 if ( m_sharedData->m_progressBarMarquee )
383 {
384 m_sharedData->m_progressBarMarquee = false;
385 m_sharedData->m_notifications |= wxSPDD_PBMARQUEE_CHANGED;
386 }
387
388 UpdateExpandedInformation( value );
389
390 // If we didn't just reach the finish, all we have to do is to
391 // return true if the dialog wasn't cancelled and false otherwise.
392 if ( value != m_maximum || m_state == Finished )
393 return m_sharedData->m_state != Canceled;
394
395
396 // On finishing, the dialog without wxPD_AUTO_HIDE style becomes a
397 // modal one meaning that we must block here until the user
398 // dismisses it.
399 m_state = Finished;
400 m_sharedData->m_state = Finished;
401 m_sharedData->m_notifications |= wxSPDD_FINISHED;
402 if ( HasPDFlag(wxPD_AUTO_HIDE) )
403 return true;
404
405 if ( newmsg.empty() )
406 {
407 // Provide the finishing message if the application didn't.
408 m_message = _("Done.");
409 m_sharedData->m_message = m_message;
410 m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
411 }
412 } // unlock m_sharedData->m_cs
413
414 // We only get here when we need to wait for the dialog to terminate so
415 // do just this by running a custom event loop until the dialog is
416 // dismissed.
417 wxProgressDialogModalLoop loop(*m_sharedData);
418 loop.Run();
419 return true;
420 }
421 #endif // wxHAS_MSW_TASKDIALOG
422
423 return wxGenericProgressDialog::Update( value, newmsg, skip );
424 }
425
426 bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip)
427 {
428 #ifdef wxHAS_MSW_TASKDIALOG
429 if ( HasNativeTaskDialog() )
430 {
431 wxCriticalSectionLocker locker(m_sharedData->m_cs);
432
433 // Do nothing in canceled state.
434 if ( !DoNativeBeforeUpdate(skip) )
435 return false;
436
437 if ( !m_sharedData->m_progressBarMarquee )
438 {
439 m_sharedData->m_progressBarMarquee = true;
440 m_sharedData->m_notifications |= wxSPDD_PBMARQUEE_CHANGED;
441 }
442
443 if ( !newmsg.empty() )
444 {
445 m_message = newmsg;
446 m_sharedData->m_message = newmsg;
447 m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
448 }
449
450 // The value passed here doesn't matter, only elapsed time makes sense
451 // in indeterminate mode anyhow.
452 UpdateExpandedInformation(0);
453
454 return m_sharedData->m_state != Canceled;
455 }
456 #endif // wxHAS_MSW_TASKDIALOG
457
458 return wxGenericProgressDialog::Pulse( newmsg, skip );
459 }
460
461 bool wxProgressDialog::DoNativeBeforeUpdate(bool *skip)
462 {
463 #ifdef wxHAS_MSW_TASKDIALOG
464 if ( HasNativeTaskDialog() )
465 {
466 if ( m_sharedData->m_skipped )
467 {
468 if ( skip && !*skip )
469 {
470 *skip = true;
471 m_sharedData->m_skipped = false;
472 m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP;
473 }
474 }
475
476 if ( m_sharedData->m_state == Canceled )
477 m_timeStop = m_sharedData->m_timeStop;
478
479 return m_sharedData->m_state != Canceled;
480 }
481 #endif // wxHAS_MSW_TASKDIALOG
482
483 wxUnusedVar(skip);
484 wxFAIL_MSG( "unreachable" );
485
486 return false;
487 }
488
489 void wxProgressDialog::Resume()
490 {
491 wxGenericProgressDialog::Resume();
492
493 #ifdef wxHAS_MSW_TASKDIALOG
494 if ( HasNativeTaskDialog() )
495 {
496 HWND hwnd;
497
498 {
499 wxCriticalSectionLocker locker(m_sharedData->m_cs);
500 m_sharedData->m_state = m_state;
501
502 // "Skip" was disabled when "Cancel" had been clicked, so re-enable
503 // it now.
504 m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP;
505
506 // Also re-enable "Cancel" itself
507 if ( HasPDFlag(wxPD_CAN_ABORT) )
508 m_sharedData->m_notifications |= wxSPDD_ENABLE_ABORT;
509
510 hwnd = m_sharedData->m_hwnd;
511 } // Unlock m_cs, we can't call any function operating on a dialog with
512 // it locked as it can result in a deadlock if the dialog callback is
513 // called by Windows.
514
515 // After resuming we need to bring the window on top of the Z-order as
516 // it could be hidden by another window shown from the main thread,
517 // e.g. a confirmation dialog asking whether the user really wants to
518 // abort.
519 //
520 // Notice that this must be done from the main thread as it owns the
521 // currently active window and attempts to do this from the task dialog
522 // thread would simply fail.
523 ::BringWindowToTop(hwnd);
524 }
525 #endif // wxHAS_MSW_TASKDIALOG
526 }
527
528 WXWidget wxProgressDialog::GetHandle() const
529 {
530 #ifdef wxHAS_MSW_TASKDIALOG
531 if ( HasNativeTaskDialog() )
532 {
533 HWND hwnd;
534 {
535 wxCriticalSectionLocker locker(m_sharedData->m_cs);
536 m_sharedData->m_state = m_state;
537 hwnd = m_sharedData->m_hwnd;
538 }
539 return hwnd;
540 }
541 #endif
542 return wxGenericProgressDialog::GetHandle();
543 }
544
545 int wxProgressDialog::GetValue() const
546 {
547 #ifdef wxHAS_MSW_TASKDIALOG
548 if ( HasNativeTaskDialog() )
549 {
550 wxCriticalSectionLocker locker(m_sharedData->m_cs);
551 return m_sharedData->m_value;
552 }
553 #endif // wxHAS_MSW_TASKDIALOG
554
555 return wxGenericProgressDialog::GetValue();
556 }
557
558 wxString wxProgressDialog::GetMessage() const
559 {
560 #ifdef wxHAS_MSW_TASKDIALOG
561 if ( HasNativeTaskDialog() )
562 return m_message;
563 #endif // wxHAS_MSW_TASKDIALOG
564
565 return wxGenericProgressDialog::GetMessage();
566 }
567
568 void wxProgressDialog::SetRange(int maximum)
569 {
570 #ifdef wxHAS_MSW_TASKDIALOG
571 if ( HasNativeTaskDialog() )
572 {
573 SetMaximum(maximum);
574
575 wxCriticalSectionLocker locker(m_sharedData->m_cs);
576
577 m_sharedData->m_range = maximum;
578 m_sharedData->m_notifications |= wxSPDD_RANGE_CHANGED;
579
580 return;
581 }
582 #endif // wxHAS_MSW_TASKDIALOG
583
584 wxGenericProgressDialog::SetRange( maximum );
585 }
586
587 bool wxProgressDialog::WasSkipped() const
588 {
589 #ifdef wxHAS_MSW_TASKDIALOG
590 if ( HasNativeTaskDialog() )
591 {
592 if ( !m_sharedData )
593 {
594 // Couldn't be skipped before being shown.
595 return false;
596 }
597
598 wxCriticalSectionLocker locker(m_sharedData->m_cs);
599 return m_sharedData->m_skipped;
600 }
601 #endif // wxHAS_MSW_TASKDIALOG
602
603 return wxGenericProgressDialog::WasSkipped();
604 }
605
606 bool wxProgressDialog::WasCancelled() const
607 {
608 #ifdef wxHAS_MSW_TASKDIALOG
609 if ( HasNativeTaskDialog() )
610 {
611 wxCriticalSectionLocker locker(m_sharedData->m_cs);
612 return m_sharedData->m_state == Canceled;
613 }
614 #endif // wxHAS_MSW_TASKDIALOG
615
616 return wxGenericProgressDialog::WasCancelled();
617 }
618
619 void wxProgressDialog::SetTitle(const wxString& title)
620 {
621 #ifdef wxHAS_MSW_TASKDIALOG
622 if ( HasNativeTaskDialog() )
623 {
624 m_title = title;
625
626 if ( m_sharedData )
627 {
628 wxCriticalSectionLocker locker(m_sharedData->m_cs);
629 m_sharedData->m_title = title;
630 m_sharedData->m_notifications = wxSPDD_TITLE_CHANGED;
631 }
632 }
633 #endif // wxHAS_MSW_TASKDIALOG
634
635 wxGenericProgressDialog::SetTitle(title);
636 }
637
638 wxString wxProgressDialog::GetTitle() const
639 {
640 #ifdef wxHAS_MSW_TASKDIALOG
641 if ( HasNativeTaskDialog() )
642 return m_title;
643 #endif // wxHAS_MSW_TASKDIALOG
644
645 return wxGenericProgressDialog::GetTitle();
646 }
647
648 bool wxProgressDialog::Show(bool show)
649 {
650 #ifdef wxHAS_MSW_TASKDIALOG
651 if ( HasNativeTaskDialog() )
652 {
653 // The dialog can't be hidden at all and showing it again after it had
654 // been shown before doesn't do anything.
655 if ( !show || m_taskDialogRunner )
656 return false;
657
658 // We're showing the dialog for the first time, create the thread that
659 // will manage it.
660 m_taskDialogRunner = new wxProgressDialogTaskRunner;
661 m_sharedData = m_taskDialogRunner->GetSharedDataObject();
662
663 // Initialize shared data.
664 m_sharedData->m_title = m_title;
665 m_sharedData->m_message = m_message;
666 m_sharedData->m_range = m_maximum;
667 m_sharedData->m_state = Uncancelable;
668 m_sharedData->m_style = GetPDStyle();
669 m_sharedData->m_parent = GetTopParent();
670
671 if ( HasPDFlag(wxPD_CAN_ABORT) )
672 {
673 m_sharedData->m_state = Continue;
674 m_sharedData->m_labelCancel = _("Cancel");
675 }
676 else // Dialog can't be cancelled.
677 {
678 // We still must have at least a single button in the dialog so
679 // just don't call it "Cancel" in this case.
680 m_sharedData->m_labelCancel = _("Close");
681 }
682
683 if ( HasPDFlag(wxPD_ELAPSED_TIME |
684 wxPD_ESTIMATED_TIME |
685 wxPD_REMAINING_TIME) )
686 {
687 // Use a non-empty string just to have the collapsible pane shown.
688 m_sharedData->m_expandedInformation = " ";
689 }
690
691 // Do launch the thread.
692 if ( m_taskDialogRunner->Create() != wxTHREAD_NO_ERROR )
693 {
694 wxLogError( "Unable to create thread!" );
695 return false;
696 }
697
698 if ( m_taskDialogRunner->Run() != wxTHREAD_NO_ERROR )
699 {
700 wxLogError( "Unable to start thread!" );
701 return false;
702 }
703
704 // Do not show the underlying dialog.
705 return false;
706 }
707 #endif // wxHAS_MSW_TASKDIALOG
708
709 return wxGenericProgressDialog::Show( show );
710 }
711
712 void wxProgressDialog::UpdateExpandedInformation(int value)
713 {
714 #ifdef wxHAS_MSW_TASKDIALOG
715 unsigned long elapsedTime;
716 unsigned long estimatedTime;
717 unsigned long remainingTime;
718 UpdateTimeEstimates(value, elapsedTime, estimatedTime, remainingTime);
719
720 int realEstimatedTime = estimatedTime,
721 realRemainingTime = remainingTime;
722 if ( m_sharedData->m_progressBarMarquee )
723 {
724 // In indeterminate mode we don't have any estimation neither for the
725 // remaining nor for estimated time.
726 realEstimatedTime =
727 realRemainingTime = -1;
728 }
729
730 wxString expandedInformation;
731
732 // Calculate the three different timing values.
733 if ( HasPDFlag(wxPD_ELAPSED_TIME) )
734 {
735 expandedInformation << GetElapsedLabel()
736 << " "
737 << GetFormattedTime(elapsedTime);
738 }
739
740 if ( HasPDFlag(wxPD_ESTIMATED_TIME) )
741 {
742 if ( !expandedInformation.empty() )
743 expandedInformation += "\n";
744
745 expandedInformation << GetEstimatedLabel()
746 << " "
747 << GetFormattedTime(realEstimatedTime);
748 }
749
750 if ( HasPDFlag(wxPD_REMAINING_TIME) )
751 {
752 if ( !expandedInformation.empty() )
753 expandedInformation += "\n";
754
755 expandedInformation << GetRemainingLabel()
756 << " "
757 << GetFormattedTime(realRemainingTime);
758 }
759
760 // Update with new timing information.
761 if ( expandedInformation != m_sharedData->m_expandedInformation )
762 {
763 m_sharedData->m_expandedInformation = expandedInformation;
764 m_sharedData->m_notifications |= wxSPDD_EXPINFO_CHANGED;
765 }
766 #else // !wxHAS_MSW_TASKDIALOG
767 wxUnusedVar(value);
768 #endif // wxHAS_MSW_TASKDIALOG/!wxHAS_MSW_TASKDIALOG
769 }
770
771 // ----------------------------------------------------------------------------
772 // wxProgressDialogTaskRunner and related methods
773 // ----------------------------------------------------------------------------
774
775 #ifdef wxHAS_MSW_TASKDIALOG
776
777 void* wxProgressDialogTaskRunner::Entry()
778 {
779 WinStruct<TASKDIALOGCONFIG> tdc;
780 wxMSWTaskDialogConfig wxTdc;
781
782 {
783 wxCriticalSectionLocker locker(m_sharedData.m_cs);
784
785 wxTdc.caption = m_sharedData.m_title.wx_str();
786 wxTdc.message = m_sharedData.m_message.wx_str();
787
788 // MSWCommonTaskDialogInit() will add an IDCANCEL button but we need to
789 // give it the correct label.
790 wxTdc.btnOKLabel = m_sharedData.m_labelCancel;
791 wxTdc.useCustomLabels = true;
792
793 wxTdc.MSWCommonTaskDialogInit( tdc );
794 tdc.pfCallback = TaskDialogCallbackProc;
795 tdc.lpCallbackData = (LONG_PTR) &m_sharedData;
796
797 // Undo some of the effects of MSWCommonTaskDialogInit().
798 tdc.dwFlags &= ~TDF_EXPAND_FOOTER_AREA; // Expand in content area.
799 tdc.dwCommonButtons = 0; // Don't use common buttons.
800
801 if ( m_sharedData.m_style & wxPD_CAN_SKIP )
802 wxTdc.AddTaskDialogButton( tdc, Id_SkipBtn, 0, _("Skip") );
803
804 tdc.dwFlags |= TDF_CALLBACK_TIMER | TDF_SHOW_PROGRESS_BAR;
805
806 if ( !m_sharedData.m_expandedInformation.empty() )
807 {
808 tdc.pszExpandedInformation =
809 m_sharedData.m_expandedInformation.t_str();
810 }
811 }
812
813 TaskDialogIndirect_t taskDialogIndirect = GetTaskDialogIndirectFunc();
814 if ( !taskDialogIndirect )
815 return NULL;
816
817 int msAns;
818 HRESULT hr = taskDialogIndirect(&tdc, &msAns, NULL, NULL);
819 if ( FAILED(hr) )
820 wxLogApiError( "TaskDialogIndirect", hr );
821
822 // If the main thread is waiting for us to exit inside the event loop in
823 // Update(), wake it up so that it checks our status again.
824 wxWakeUpIdle();
825
826 return NULL;
827 }
828
829 // static
830 HRESULT CALLBACK
831 wxProgressDialogTaskRunner::TaskDialogCallbackProc
832 (
833 HWND hwnd,
834 UINT uNotification,
835 WPARAM wParam,
836 LPARAM WXUNUSED(lParam),
837 LONG_PTR dwRefData
838 )
839 {
840 wxProgressDialogSharedData * const sharedData =
841 (wxProgressDialogSharedData *) dwRefData;
842
843 wxCriticalSectionLocker locker(sharedData->m_cs);
844
845 switch ( uNotification )
846 {
847 case TDN_CREATED:
848 // Store the HWND for the main thread use.
849 sharedData->m_hwnd = hwnd;
850
851 // Set the maximum value and disable Close button.
852 ::SendMessage( hwnd,
853 TDM_SET_PROGRESS_BAR_RANGE,
854 0,
855 MAKELPARAM(0, sharedData->m_range) );
856
857 // We always create this task dialog with NULL parent because our
858 // parent in wx sense is a window created from a different thread
859 // and so can't be used as our real parent. However we still center
860 // this window on the parent one as the task dialogs do with their
861 // real parent usually.
862 if ( sharedData->m_parent )
863 {
864 wxRect rect(wxRectFromRECT(wxGetWindowRect(hwnd)));
865 rect = rect.CentreIn(sharedData->m_parent->GetRect());
866 ::SetWindowPos(hwnd,
867 NULL,
868 rect.x,
869 rect.y,
870 -1,
871 -1,
872 SWP_NOACTIVATE |
873 SWP_NOOWNERZORDER |
874 SWP_NOSIZE |
875 SWP_NOZORDER);
876 }
877
878 // If we can't be aborted, the "Close" button will only be enabled
879 // when the progress ends (and not even then with wxPD_AUTO_HIDE).
880 if ( !(sharedData->m_style & wxPD_CAN_ABORT) )
881 ::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE );
882 break;
883
884 case TDN_BUTTON_CLICKED:
885 switch ( wParam )
886 {
887 case Id_SkipBtn:
888 ::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
889 sharedData->m_skipped = true;
890 return TRUE;
891
892 case IDCANCEL:
893 if ( sharedData->m_state == wxProgressDialog::Finished )
894 {
895 // If the main thread is waiting for us, tell it that
896 // we're gone (and if it doesn't wait, it's harmless).
897 sharedData->m_state = wxProgressDialog::Dismissed;
898
899 // Let Windows close the dialog.
900 return FALSE;
901 }
902
903 // Close button on the window triggers an IDCANCEL press,
904 // don't allow it when it should only be possible to close
905 // a finished dialog.
906 if ( sharedData->m_style & wxPD_CAN_ABORT )
907 {
908 wxCHECK_MSG
909 (
910 sharedData->m_state == wxProgressDialog::Continue,
911 TRUE,
912 "Dialog not in a cancelable state!"
913 );
914
915 ::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
916 ::SendMessage(hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE);
917
918 sharedData->m_timeStop = wxGetCurrentTime();
919 sharedData->m_state = wxProgressDialog::Canceled;
920 }
921
922 return TRUE;
923 }
924 break;
925
926 case TDN_TIMER:
927 PerformNotificationUpdates(hwnd, sharedData);
928
929 /*
930 Decide whether we should end the dialog. This is done if either
931 the dialog object itself was destroyed or if the progress
932 finished and we were configured to hide automatically without
933 waiting for the user to dismiss us.
934
935 Notice that we do not close the dialog if it was cancelled
936 because it's up to the user code in the main thread to decide
937 whether it really wants to cancel the dialog.
938 */
939 if ( (sharedData->m_notifications & wxSPDD_DESTROYED) ||
940 (sharedData->m_state == wxProgressDialog::Finished &&
941 sharedData->m_style & wxPD_AUTO_HIDE) )
942 {
943 ::EndDialog( hwnd, IDCLOSE );
944 }
945
946 sharedData->m_notifications = 0;
947
948 return TRUE;
949 }
950
951 // Return anything.
952 return 0;
953 }
954
955 #endif // wxHAS_MSW_TASKDIALOG
956
957 #endif // wxUSE_PROGRESSDLG && wxUSE_THREADS