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