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