]> git.saurik.com Git - wxWidgets.git/blame - src/msw/progdlg.cpp
Fix wxRect::SetRightTop() which set the wrong corner.
[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
28#include "wx/msw/private/msgdlg.h"
29#include "wx/progdlg.h"
cc31a982 30#include "wx/evtloop.h"
c31d9c7f
VZ
31
32using namespace wxMSWMessageDialog;
33
34#ifdef wxHAS_MSW_TASKDIALOG
35
36// ----------------------------------------------------------------------------
37// Constants
38// ----------------------------------------------------------------------------
39
40namespace
41{
42
43// Notification values of wxProgressDialogSharedData::m_notifications
44const int wxSPDD_VALUE_CHANGED = 0x0001;
45const int wxSPDD_RANGE_CHANGED = 0x0002;
46const int wxSPDD_PBMARQUEE_CHANGED = 0x0004;
47const int wxSPDD_TITLE_CHANGED = 0x0008;
48const int wxSPDD_MESSAGE_CHANGED = 0x0010;
49const int wxSPDD_EXPINFO_CHANGED = 0x0020;
50const int wxSPDD_ENABLE_SKIP = 0x0040;
51const int wxSPDD_ENABLE_ABORT = 0x0080;
52const int wxSPDD_DISABLE_SKIP = 0x0100;
53const int wxSPDD_DISABLE_ABORT = 0x0200;
54const int wxSPDD_FINISHED = 0x0400;
55const int wxSPDD_DESTROYED = 0x0800;
56
57const 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.
66class wxProgressDialogSharedData
67{
68public:
69 wxProgressDialogSharedData()
70 {
71 m_hwnd = 0;
72 m_value = 0;
73 m_progressBarMarquee = false;
74 m_skipped = false;
75 m_notifications = 0;
a4984245 76 m_parent = NULL;
c31d9c7f
VZ
77 }
78
79 wxCriticalSection m_cs;
80
a4984245 81 wxWindow *m_parent; // Parent window only used to center us over it.
c31d9c7f
VZ
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
4f4d6f44 92 wxProgressDialog::State m_state;
c31d9c7f
VZ
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.
103class wxProgressDialogTaskRunner : public wxThread
104{
105public:
8297e64b
VZ
106 wxProgressDialogTaskRunner()
107 : wxThread(wxTHREAD_JOINABLE)
c31d9c7f
VZ
108 { }
109
110 wxProgressDialogSharedData* GetSharedDataObject()
111 { return &m_sharedData; }
112
113private:
c31d9c7f
VZ
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
cc31a982
VZ
125namespace
126{
127
128// A custom event loop which runs until the state of the dialog becomes
129// "Dismissed".
130class wxProgressDialogModalLoop : public wxEventLoop
131{
132public:
133 wxProgressDialogModalLoop(wxProgressDialogSharedData& data)
134 : m_data(data)
135 {
136 }
137
138protected:
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
c31d9c7f
VZ
152// ============================================================================
153// Helper functions
154// ============================================================================
155
c31d9c7f
VZ
156BOOL 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
173void 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 {
979901c8 271 sharedData->m_state = wxProgressDialog::Finished;
c31d9c7f
VZ
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
292wxProgressDialog::wxProgressDialog( const wxString& title,
293 const wxString& message,
294 int maximum,
295 wxWindow *parent,
296 int style )
2de77c6a 297 : wxGenericProgressDialog(parent, style),
c31d9c7f
VZ
298 m_taskDialogRunner(NULL),
299 m_sharedData(NULL),
300 m_message(message),
301 m_title(title)
302{
303#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 304 if ( HasNativeTaskDialog() )
c31d9c7f 305 {
2de77c6a
VZ
306 SetMaximum(maximum);
307
c31d9c7f
VZ
308 Show();
309 DisableOtherWindows();
310
311 return;
312 }
313#endif // wxHAS_MSW_TASKDIALOG
314
315 Create(title, message, maximum, parent, style);
316}
317
318wxProgressDialog::~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
341bool wxProgressDialog::Update(int value, const wxString& newmsg, bool *skip)
342{
343#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 344 if ( HasNativeTaskDialog() )
c31d9c7f 345 {
cc31a982
VZ
346 {
347 wxCriticalSectionLocker locker(m_sharedData->m_cs);
c31d9c7f 348
cc31a982
VZ
349 // Do nothing in canceled state.
350 if ( !DoNativeBeforeUpdate(skip) )
351 return false;
c31d9c7f 352
cc31a982 353 value /= m_factor;
c31d9c7f 354
cc31a982 355 wxASSERT_MSG( value <= m_maximum, wxT("invalid progress value") );
c31d9c7f 356
cc31a982
VZ
357 m_sharedData->m_value = value;
358 m_sharedData->m_notifications |= wxSPDD_VALUE_CHANGED;
c31d9c7f 359
cc31a982
VZ
360 if ( !newmsg.empty() )
361 {
362 m_message = newmsg;
363 m_sharedData->m_message = newmsg;
364 m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
365 }
c31d9c7f 366
cc31a982
VZ
367 if ( m_sharedData->m_progressBarMarquee )
368 {
369 m_sharedData->m_progressBarMarquee = false;
370 m_sharedData->m_notifications |= wxSPDD_PBMARQUEE_CHANGED;
371 }
c31d9c7f 372
cc31a982
VZ
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;
c31d9c7f 379
c31d9c7f 380
cc31a982
VZ
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.
c31d9c7f
VZ
384 m_state = Finished;
385 m_sharedData->m_state = Finished;
386 m_sharedData->m_notifications |= wxSPDD_FINISHED;
4be95bef
VZ
387 if ( HasPDFlag(wxPD_AUTO_HIDE) )
388 return true;
389
390 if ( newmsg.empty() )
c31d9c7f
VZ
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 }
cc31a982
VZ
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;
c31d9c7f
VZ
405 }
406#endif // wxHAS_MSW_TASKDIALOG
407
408 return wxGenericProgressDialog::Update( value, newmsg, skip );
409}
410
411bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip)
412{
413#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 414 if ( HasNativeTaskDialog() )
c31d9c7f
VZ
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
446bool wxProgressDialog::DoNativeBeforeUpdate(bool *skip)
447{
448#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 449 if ( HasNativeTaskDialog() )
c31d9c7f
VZ
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
474void wxProgressDialog::Resume()
475{
476 wxGenericProgressDialog::Resume();
477
478#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 479 if ( HasNativeTaskDialog() )
c31d9c7f
VZ
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
76c4b149
VZ
491 // Also re-enable "Cancel" itself
492 if ( HasPDFlag(wxPD_CAN_ABORT) )
c31d9c7f
VZ
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
513int wxProgressDialog::GetValue() const
514{
515#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 516 if ( HasNativeTaskDialog() )
c31d9c7f
VZ
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
526wxString wxProgressDialog::GetMessage() const
527{
528#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 529 if ( HasNativeTaskDialog() )
c31d9c7f
VZ
530 return m_message;
531#endif // wxHAS_MSW_TASKDIALOG
532
533 return wxGenericProgressDialog::GetMessage();
534}
535
536void wxProgressDialog::SetRange(int maximum)
537{
c31d9c7f 538#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 539 if ( HasNativeTaskDialog() )
c31d9c7f 540 {
2de77c6a
VZ
541 SetMaximum(maximum);
542
c31d9c7f
VZ
543 wxCriticalSectionLocker locker(m_sharedData->m_cs);
544
545 m_sharedData->m_range = maximum;
546 m_sharedData->m_notifications |= wxSPDD_RANGE_CHANGED;
2de77c6a
VZ
547
548 return;
c31d9c7f
VZ
549 }
550#endif // wxHAS_MSW_TASKDIALOG
2de77c6a
VZ
551
552 wxGenericProgressDialog::SetRange( maximum );
c31d9c7f
VZ
553}
554
555bool wxProgressDialog::WasSkipped() const
556{
557#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 558 if ( HasNativeTaskDialog() )
c31d9c7f
VZ
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
574bool wxProgressDialog::WasCancelled() const
575{
576#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 577 if ( HasNativeTaskDialog() )
c31d9c7f
VZ
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
587void wxProgressDialog::SetTitle(const wxString& title)
588{
589#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 590 if ( HasNativeTaskDialog() )
c31d9c7f
VZ
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
b00403b4 603 wxGenericProgressDialog::SetTitle(title);
c31d9c7f
VZ
604}
605
606wxString wxProgressDialog::GetTitle() const
607{
608#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 609 if ( HasNativeTaskDialog() )
c31d9c7f
VZ
610 return m_title;
611#endif // wxHAS_MSW_TASKDIALOG
612
613 return wxGenericProgressDialog::GetTitle();
614}
615
616bool wxProgressDialog::Show(bool show)
617{
618#ifdef wxHAS_MSW_TASKDIALOG
76c4b149 619 if ( HasNativeTaskDialog() )
c31d9c7f
VZ
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.
8297e64b 628 m_taskDialogRunner = new wxProgressDialogTaskRunner;
c31d9c7f
VZ
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;
e77570de 636 m_sharedData->m_style = GetPDStyle();
a4984245 637 m_sharedData->m_parent = GetTopParent();
c31d9c7f 638
e77570de 639 if ( HasPDFlag(wxPD_CAN_ABORT) )
c31d9c7f
VZ
640 {
641 m_sharedData->m_state = Continue;
642 m_sharedData->m_labelCancel = _("Cancel");
643 }
76c4b149 644 else // Dialog can't be cancelled.
c31d9c7f 645 {
76c4b149
VZ
646 // We still must have at least a single button in the dialog so
647 // just don't call it "Cancel" in this case.
c31d9c7f
VZ
648 m_sharedData->m_labelCancel = _("Close");
649 }
650
e77570de
VZ
651 if ( HasPDFlag(wxPD_ELAPSED_TIME |
652 wxPD_ESTIMATED_TIME |
653 wxPD_REMAINING_TIME) )
c31d9c7f
VZ
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
c31d9c7f
VZ
672 // Do not show the underlying dialog.
673 return false;
674 }
675#endif // wxHAS_MSW_TASKDIALOG
676
677 return wxGenericProgressDialog::Show( show );
678}
679
c31d9c7f
VZ
680void 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.
e77570de 701 if ( HasPDFlag(wxPD_ELAPSED_TIME) )
c31d9c7f
VZ
702 {
703 expandedInformation << GetElapsedLabel()
704 << " "
705 << GetFormattedTime(elapsedTime);
706 }
707
e77570de 708 if ( HasPDFlag(wxPD_ESTIMATED_TIME) )
c31d9c7f
VZ
709 {
710 if ( !expandedInformation.empty() )
711 expandedInformation += "\n";
712
713 expandedInformation << GetEstimatedLabel()
714 << " "
715 << GetFormattedTime(realEstimatedTime);
716 }
717
e77570de 718 if ( HasPDFlag(wxPD_REMAINING_TIME) )
c31d9c7f
VZ
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
745void* 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.
76c4b149
VZ
771 wxTdc.AddTaskDialogButton( tdc, IDCANCEL, 0,
772 m_sharedData.m_labelCancel );
c31d9c7f 773
928da15b 774 tdc.dwFlags |= TDF_CALLBACK_TIMER | TDF_SHOW_PROGRESS_BAR;
c31d9c7f
VZ
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
cc31a982
VZ
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
c31d9c7f
VZ
796 return NULL;
797}
798
799// static
800HRESULT CALLBACK
801wxProgressDialogTaskRunner::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
a4984245
VZ
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
76c4b149
VZ
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) )
c31d9c7f
VZ
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:
979901c8 863 if ( sharedData->m_state == wxProgressDialog::Finished )
c31d9c7f 864 {
cc31a982
VZ
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.
c31d9c7f
VZ
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.
76c4b149 876 if ( sharedData->m_style & wxPD_CAN_ABORT )
c31d9c7f 877 {
979901c8
VZ
878 wxCHECK_MSG
879 (
880 sharedData->m_state == wxProgressDialog::Continue,
881 TRUE,
882 "Dialog not in a cancelable state!"
883 );
c31d9c7f
VZ
884
885 ::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
886 ::SendMessage(hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE);
887
888 sharedData->m_timeStop = wxGetCurrentTime();
979901c8 889 sharedData->m_state = wxProgressDialog::Canceled;
c31d9c7f
VZ
890 }
891
892 return TRUE;
893 }
894 break;
895
896 case TDN_TIMER:
897 PerformNotificationUpdates(hwnd, sharedData);
898
21a5e7e0
VZ
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) )
c31d9c7f
VZ
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