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