Removed all duplicated and complicated #ifdefs for enabling Skip and Abort in progres...
[wxWidgets.git] / src / generic / progdlgg.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: progdlgg.h
3 // Purpose: wxProgressDialog class
4 // Author: Karsten Ballüder
5 // Modified by:
6 // Created: 09.05.1999
7 // RCS-ID: $Id$
8 // Copyright: (c) Karsten Ballüder
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
21 #pragma implementation "progdlgg.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #if wxUSE_PROGRESSDLG
32
33 #ifndef WX_PRECOMP
34 #include "wx/utils.h"
35 #include "wx/frame.h"
36 #include "wx/button.h"
37 #include "wx/stattext.h"
38 #include "wx/sizer.h"
39 #include "wx/event.h"
40 #include "wx/gauge.h"
41 #include "wx/intl.h"
42 #include "wx/settings.h"
43 #include "wx/dcclient.h"
44 #include "wx/timer.h"
45 #endif
46
47 #include "wx/generic/progdlgg.h"
48
49 // ---------------------------------------------------------------------------
50 // macros
51 // ---------------------------------------------------------------------------
52
53 /* Macro for avoiding #ifdefs when value have to be different depending on size of
54 device we display on - take it from something like wxDesktopPolicy in the future
55 */
56
57 #if defined(__SMARTPHONE__)
58 #define wxLARGESMALL(large,small) small
59 #else
60 #define wxLARGESMALL(large,small) large
61 #endif
62
63 // ----------------------------------------------------------------------------
64 // constants
65 // ----------------------------------------------------------------------------
66
67 #define LAYOUT_MARGIN wxLARGESMALL(8,2)
68
69 static const int wxID_SKIP = 32000; // whatever
70
71 // ----------------------------------------------------------------------------
72 // private functions
73 // ----------------------------------------------------------------------------
74
75 // update the label to show the given time (in seconds)
76 static void SetTimeLabel(unsigned long val, wxStaticText *label);
77
78 // ----------------------------------------------------------------------------
79 // event tables
80 // ----------------------------------------------------------------------------
81
82 BEGIN_EVENT_TABLE(wxProgressDialog, wxDialog)
83 EVT_BUTTON(wxID_CANCEL, wxProgressDialog::OnCancel)
84 EVT_BUTTON(wxID_SKIP, wxProgressDialog::OnSkip)
85
86 EVT_CLOSE(wxProgressDialog::OnClose)
87 END_EVENT_TABLE()
88
89 IMPLEMENT_CLASS(wxProgressDialog, wxDialog)
90
91 // ============================================================================
92 // wxProgressDialog implementation
93 // ============================================================================
94
95 // ----------------------------------------------------------------------------
96 // wxProgressDialog creation
97 // ----------------------------------------------------------------------------
98
99 wxProgressDialog::wxProgressDialog(wxString const &title,
100 wxString const &message,
101 int maximum,
102 wxWindow *parent,
103 int style)
104 : wxDialog(parent, wxID_ANY, title),
105 m_skip(false),
106 m_delay(3),
107 m_hasAbortButton(false),
108 m_hasSkipButton(false)
109 {
110 // we may disappear at any moment, let the others know about it
111 SetExtraStyle(GetExtraStyle() | wxWS_EX_TRANSIENT);
112 m_windowStyle |= style;
113
114 m_hasAbortButton = (style & wxPD_CAN_ABORT) != 0;
115 m_hasSkipButton = (style & wxPD_CAN_SKIP) != 0;
116
117 #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
118 // we have to remove the "Close" button from the title bar then as it is
119 // confusing to have it - it doesn't work anyhow
120 //
121 // FIXME: should probably have a (extended?) window style for this
122 if ( !m_hasAbortButton )
123 {
124 EnableCloseButton(false);
125 }
126 #endif // wxMSW
127
128 #if defined(__SMARTPHONE__)
129 SetLeftMenu();
130 #endif
131
132 m_state = m_hasAbortButton ? Continue : Uncancelable;
133 m_maximum = maximum;
134
135 #if defined(__WXMSW__) || defined(__WXPM__)
136 // we can't have values > 65,536 in the progress control under Windows, so
137 // scale everything down
138 m_factor = m_maximum / 65536 + 1;
139 m_maximum /= m_factor;
140 #endif // __WXMSW__
141
142 m_parentTop = wxGetTopLevelParent(parent);
143
144 wxClientDC dc(this);
145 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
146 long widthText;
147 dc.GetTextExtent(message, &widthText, NULL, NULL, NULL, NULL);
148
149 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
150
151 m_msg = new wxStaticText(this, wxID_ANY, message);
152 sizer->Add(m_msg, 0, wxLEFT | wxTOP, 2*LAYOUT_MARGIN);
153
154 wxSize sizeDlg,
155 sizeLabel = m_msg->GetSize();
156 sizeDlg.y = 2*LAYOUT_MARGIN + sizeLabel.y;
157
158 if ( maximum > 0 )
159 {
160 int gauge_style = wxGA_HORIZONTAL;
161 if ( ( style & wxPD_SMOOTH ) == wxPD_SMOOTH )
162 gauge_style |= wxGA_SMOOTH;
163 m_gauge = new wxGauge(this, wxID_ANY, m_maximum,
164 wxDefaultPosition, wxDefaultSize,
165 gauge_style );
166
167 sizer->Add(m_gauge, 0, wxLEFT | wxRIGHT | wxTOP | wxEXPAND, 2*LAYOUT_MARGIN);
168 m_gauge->SetValue(0);
169
170 wxSize sizeGauge = m_gauge->GetSize();
171 sizeDlg.y += 2*LAYOUT_MARGIN + sizeGauge.y;
172 }
173 else
174 m_gauge = (wxGauge *)NULL;
175
176 // create the estimated/remaining/total time zones if requested
177 m_elapsed = m_estimated = m_remaining = (wxStaticText*)NULL;
178 m_display_estimated = m_last_timeupdate = m_break = 0;
179 m_ctdelay = 0;
180
181 // if we are going to have at least one label, remmeber it in this var
182 wxStaticText *label = NULL;
183
184 // also count how many labels we really have
185 size_t nTimeLabels = 0;
186
187 if ( style & wxPD_ELAPSED_TIME )
188 {
189 nTimeLabels++;
190
191 label =
192 m_elapsed = CreateLabel(_("Elapsed time : "), sizer);
193 }
194
195 if ( style & wxPD_ESTIMATED_TIME )
196 {
197 nTimeLabels++;
198
199 label =
200 m_estimated = CreateLabel(_("Estimated time : "), sizer);
201 }
202
203 if ( style & wxPD_REMAINING_TIME )
204 {
205 nTimeLabels++;
206
207 label =
208 m_remaining = CreateLabel(_("Remaining time : "), sizer);
209 }
210
211 if ( nTimeLabels > 0 )
212 {
213 // set it to the current time
214 m_timeStart = wxGetCurrentTime();
215 sizeDlg.y += nTimeLabels * (label->GetSize().y + LAYOUT_MARGIN);
216 }
217
218 #if defined(__SMARTPHONE__)
219 if ( m_hasSkipButton )
220 SetRightMenu(wxID_SKIP, _("Skip"));
221 if ( m_hasAbortButton )
222 SetLeftMenu(wxID_CANCEL);
223 #else
224 m_btnAbort = m_btnSkip = (wxButton *)NULL;
225 bool sizeDlgModified = false;
226 wxBoxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
227
228 const int sizerFlags =
229 #if defined(__WXMSW__) || defined(__WXPM__)
230 wxALIGN_RIGHT | wxALL
231 #else // !MSW
232 wxALIGN_CENTER_HORIZONTAL | wxBOTTOM | wxTOP
233 #endif // MSW/!MSW
234 ;
235
236 if ( m_hasSkipButton )
237 {
238 m_btnSkip = new wxButton(this, wxID_SKIP, _("Skip"));
239
240 // Windows dialogs usually have buttons in the lower right corner
241 buttonSizer->Add(m_btnSkip, 0, sizerFlags, LAYOUT_MARGIN);
242 sizeDlg.y += 2*LAYOUT_MARGIN + wxButton::GetDefaultSize().y;
243 sizeDlgModified = true;
244 }
245
246 if ( m_hasAbortButton )
247 {
248 m_btnAbort = new wxButton(this, wxID_CANCEL);
249
250 // Windows dialogs usually have buttons in the lower right corner
251 buttonSizer->Add(m_btnAbort, 0, sizerFlags, LAYOUT_MARGIN);
252 if(!sizeDlgModified)
253 sizeDlg.y += 2*LAYOUT_MARGIN + wxButton::GetDefaultSize().y;
254 }
255
256 sizer->Add(buttonSizer, 0, sizerFlags, LAYOUT_MARGIN );
257 #endif // __SMARTPHONE__/!__SMARTPHONE__
258
259 SetSizerAndFit(sizer);
260
261 sizeDlg.y += 2*LAYOUT_MARGIN;
262
263 // try to make the dialog not square but rectangular of reasonabel width
264 sizeDlg.x = (wxCoord)wxMax(widthText, 4*sizeDlg.y/3);
265 sizeDlg.x *= 3;
266 sizeDlg.x /= 2;
267 SetClientSize(sizeDlg);
268
269 Centre(wxCENTER_FRAME | wxBOTH);
270
271 if ( style & wxPD_APP_MODAL )
272 {
273 m_winDisabler = new wxWindowDisabler(this);
274 }
275 else
276 {
277 if ( m_parentTop )
278 m_parentTop->Disable();
279 m_winDisabler = NULL;
280 }
281
282 Show();
283 Enable();
284
285 // this one can be initialized even if the others are unknown for now
286 //
287 // NB: do it after calling Layout() to keep the labels correctly aligned
288 if ( m_elapsed )
289 {
290 SetTimeLabel(0, m_elapsed);
291 }
292
293 Update();
294 }
295
296 wxStaticText *wxProgressDialog::CreateLabel(const wxString& text,
297 wxSizer *sizer)
298 {
299 wxBoxSizer *locsizer = new wxBoxSizer(wxLARGESMALL(wxHORIZONTAL,wxVERTICAL));
300
301 wxStaticText *dummy = new wxStaticText(this, wxID_ANY, text);
302 wxStaticText *label = new wxStaticText(this, wxID_ANY, _("unknown"));
303
304 // select placement most native or nice on target GUI
305 #if defined(__SMARTPHONE__)
306 // label and time to the left in two rows
307 locsizer->Add(dummy, 1, wxALIGN_LEFT);
308 locsizer->Add(label, 1, wxALIGN_LEFT);
309 sizer->Add(locsizer, 0, wxALIGN_LEFT | wxTOP | wxLEFT, LAYOUT_MARGIN);
310 #elif defined(__WXMSW__) || defined(__WXPM__) || defined(__WXMAC__)
311 // label and time centered in one row
312 locsizer->Add(dummy, 1, wxLARGESMALL(wxALIGN_RIGHT,wxALIGN_LEFT));
313 locsizer->Add(label, 1, wxALIGN_LEFT | wxLEFT, LAYOUT_MARGIN);
314 sizer->Add(locsizer, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, LAYOUT_MARGIN);
315 #else
316 // label and time to the right in one row
317 sizer->Add(locsizer, 0, wxALIGN_RIGHT | wxRIGHT | wxTOP, LAYOUT_MARGIN);
318 locsizer->Add(dummy);
319 locsizer->Add(label, 0, wxLEFT, LAYOUT_MARGIN);
320 #endif
321
322 return label;
323 }
324
325 // ----------------------------------------------------------------------------
326 // wxProgressDialog operations
327 // ----------------------------------------------------------------------------
328
329 bool
330 wxProgressDialog::Update(int value, const wxString& newmsg, bool *skip)
331 {
332 wxASSERT_MSG( value == -1 || m_gauge, wxT("cannot update non existent dialog") );
333
334 #ifdef __WXMSW__
335 value /= m_factor;
336 #endif // __WXMSW__
337
338 wxASSERT_MSG( value <= m_maximum, wxT("invalid progress value") );
339
340 // fill up the gauge if value == maximum because this means that the dialog
341 // is going to close and the gauge shouldn't be partly empty in this case
342 if ( m_gauge && value <= m_maximum )
343 {
344 m_gauge->SetValue(value == m_maximum ? value : value + 1);
345 }
346
347 if ( !newmsg.empty() && newmsg != m_msg->GetLabel() )
348 {
349 m_msg->SetLabel(newmsg);
350
351 wxYieldIfNeeded() ;
352 }
353
354 if ( (m_elapsed || m_remaining || m_estimated) && (value != 0) )
355 {
356 unsigned long elapsed = wxGetCurrentTime() - m_timeStart;
357 if ( m_last_timeupdate < elapsed
358 || value == m_maximum
359 )
360 {
361 m_last_timeupdate = elapsed;
362 unsigned long estimated = m_break +
363 (unsigned long)(( (double) (elapsed-m_break) * m_maximum ) / ((double)value)) ;
364 if ( estimated > m_display_estimated
365 && m_ctdelay >= 0
366 )
367 {
368 ++m_ctdelay;
369 }
370 else if ( estimated < m_display_estimated
371 && m_ctdelay <= 0
372 )
373 {
374 --m_ctdelay;
375 }
376 else
377 {
378 m_ctdelay = 0;
379 }
380 if ( m_ctdelay >= m_delay // enough confirmations for a higher value
381 || m_ctdelay <= (m_delay*-1) // enough confirmations for a lower value
382 || value == m_maximum // to stay consistent
383 || elapsed > m_display_estimated // to stay consistent
384 || ( elapsed > 0 && elapsed < 4 ) // additional updates in the beginning
385 )
386 {
387 m_display_estimated = estimated;
388 m_ctdelay = 0;
389 }
390 }
391
392 long display_remaining = m_display_estimated - elapsed;
393 if ( display_remaining < 0 )
394 {
395 display_remaining = 0;
396 }
397
398 SetTimeLabel(elapsed, m_elapsed);
399 SetTimeLabel(m_display_estimated, m_estimated);
400 SetTimeLabel(display_remaining, m_remaining);
401 }
402
403 if ( value == m_maximum )
404 {
405 if ( m_state == Finished )
406 {
407 // ignore multiple calls to Update(m_maximum): it may sometimes be
408 // troublesome to ensure that Update() is not called twice with the
409 // same value (e.g. because of the rounding errors) and if we don't
410 // return now we're going to generate asserts below
411 return true;
412 }
413
414 // so that we return true below and that out [Cancel] handler knew what
415 // to do
416 m_state = Finished;
417 if( !(GetWindowStyle() & wxPD_AUTO_HIDE) )
418 {
419 EnableClose();
420 DisableSkip();
421 #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
422 EnableCloseButton();
423 #endif // __WXMSW__
424
425 if ( !newmsg.empty() )
426 {
427 // also provide the finishing message if the application didn't
428 m_msg->SetLabel(_("Done."));
429 }
430
431 wxYieldIfNeeded() ;
432
433 (void)ShowModal();
434 }
435 else // auto hide
436 {
437 // reenable other windows before hiding this one because otherwise
438 // Windows wouldn't give the focus back to the window which had
439 // been previously focused because it would still be disabled
440 ReenableOtherWindows();
441
442 Hide();
443 }
444 }
445 else
446 {
447 // we have to yield because not only we want to update the display but
448 // also to process the clicks on the cancel and skip buttons
449 wxYieldIfNeeded() ;
450
451 if ( (m_skip) && (skip != NULL) && (*skip == false) )
452 {
453 *skip = true;
454 m_skip = false;
455 EnableSkip();
456 }
457 }
458
459 // update the display in case yielding above didn't do it
460 Update();
461
462 return m_state != Canceled;
463 }
464
465 void wxProgressDialog::Resume()
466 {
467 m_state = Continue;
468 m_ctdelay = m_delay; // force an update of the elapsed/estimated/remaining time
469 m_break += wxGetCurrentTime()-m_timeStop;
470
471 EnableAbort();
472 EnableSkip();
473 m_skip = false;
474 }
475
476 bool wxProgressDialog::Show( bool show )
477 {
478 // reenable other windows before hiding this one because otherwise
479 // Windows wouldn't give the focus back to the window which had
480 // been previously focused because it would still be disabled
481 if(!show)
482 ReenableOtherWindows();
483
484 return wxDialog::Show(show);
485 }
486
487 // ----------------------------------------------------------------------------
488 // event handlers
489 // ----------------------------------------------------------------------------
490
491 void wxProgressDialog::OnCancel(wxCommandEvent& event)
492 {
493 if ( m_state == Finished )
494 {
495 // this means that the count down is already finished and we're being
496 // shown as a modal dialog - so just let the default handler do the job
497 event.Skip();
498 }
499 else
500 {
501 // request to cancel was received, the next time Update() is called we
502 // will handle it
503 m_state = Canceled;
504
505 // update the buttons state immediately so that the user knows that the
506 // request has been noticed
507 DisableAbort();
508 DisableSkip();
509
510 // save the time when the dialog was stopped
511 m_timeStop = wxGetCurrentTime();
512 }
513 }
514
515 void wxProgressDialog::OnSkip(wxCommandEvent& WXUNUSED(event))
516 {
517 DisableSkip();
518 m_skip = true;
519 }
520
521 void wxProgressDialog::OnClose(wxCloseEvent& event)
522 {
523 if ( m_state == Uncancelable )
524 {
525 // can't close this dialog
526 event.Veto();
527 }
528 else if ( m_state == Finished )
529 {
530 // let the default handler close the window as we already terminated
531 event.Skip();
532 }
533 else
534 {
535 // next Update() will notice it
536 m_state = Canceled;
537 DisableAbort();
538 DisableSkip();
539
540 m_timeStop = wxGetCurrentTime();
541 }
542 }
543
544 // ----------------------------------------------------------------------------
545 // destruction
546 // ----------------------------------------------------------------------------
547
548 wxProgressDialog::~wxProgressDialog()
549 {
550 // normally this should have been already done, but just in case
551 ReenableOtherWindows();
552 }
553
554 void wxProgressDialog::ReenableOtherWindows()
555 {
556 if ( GetWindowStyle() & wxPD_APP_MODAL )
557 {
558 delete m_winDisabler;
559 m_winDisabler = (wxWindowDisabler *)NULL;
560 }
561 else
562 {
563 if ( m_parentTop )
564 m_parentTop->Enable();
565 }
566 }
567
568 // ----------------------------------------------------------------------------
569 // private functions
570 // ----------------------------------------------------------------------------
571
572 static void SetTimeLabel(unsigned long val, wxStaticText *label)
573 {
574 if ( label )
575 {
576 wxString s;
577 unsigned long hours = val / 3600;
578 unsigned long minutes = (val % 3600) / 60;
579 unsigned long seconds = val % 60;
580 s.Printf(wxT("%lu:%02lu:%02lu"), hours, minutes, seconds);
581
582 if ( s != label->GetLabel() )
583 label->SetLabel(s);
584 }
585 }
586
587 void wxProgressDialog::EnableSkip(bool enable)
588 {
589 if(m_hasSkipButton)
590 {
591 #ifdef __SMARTPHONE__
592 if(enable)
593 SetRightMenu(wxID_SKIP, _("Skip"));
594 else
595 SetRightMenu();
596 #else
597 if(m_btnSkip)
598 m_btnSkip->Enable(enable);
599 #endif
600 }
601 }
602
603 void wxProgressDialog::EnableAbort(bool enable)
604 {
605 if(m_hasAbortButton)
606 {
607 #ifdef __SMARTPHONE__
608 if(enable)
609 SetLeftMenu(wxID_CANCEL, _("Cancel"));
610 else
611 SetLeftMenu();
612 #else
613 if(m_btnAbort)
614 m_btnAbort->Enable(enable);
615 #endif
616 }
617 }
618
619 void wxProgressDialog::EnableClose()
620 {
621 if(m_hasAbortButton)
622 {
623 #ifdef __SMARTPHONE__
624 SetLeftMenu(wxID_CANCEL, _("Close"));
625 #else
626 if(m_btnAbort)
627 {
628 m_btnAbort->Enable();
629 m_btnAbort->SetLabel(_("Close"));
630 }
631 #endif
632 }
633 }
634
635 #endif // wxUSE_PROGRESSDLG