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