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