don't assert if Update(m_maximum) is called twice (replaces patch 995186)
[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 // constants
51 // ----------------------------------------------------------------------------
52
53 #define LAYOUT_MARGIN 8
54
55 // ----------------------------------------------------------------------------
56 // private functions
57 // ----------------------------------------------------------------------------
58
59 // update the label to show the given time (in seconds)
60 static void SetTimeLabel(unsigned long val, wxStaticText *label);
61
62 // ----------------------------------------------------------------------------
63 // event tables
64 // ----------------------------------------------------------------------------
65
66 BEGIN_EVENT_TABLE(wxProgressDialog, wxDialog)
67 EVT_BUTTON(wxID_CANCEL, wxProgressDialog::OnCancel)
68
69 EVT_CLOSE(wxProgressDialog::OnClose)
70 END_EVENT_TABLE()
71
72 IMPLEMENT_CLASS(wxProgressDialog, wxDialog)
73
74 // ============================================================================
75 // wxProgressDialog implementation
76 // ============================================================================
77
78 // ----------------------------------------------------------------------------
79 // wxProgressDialog creation
80 // ----------------------------------------------------------------------------
81
82 wxProgressDialog::wxProgressDialog(wxString const &title,
83 wxString const &message,
84 int maximum,
85 wxWindow *parent,
86 int style)
87 : wxDialog(parent, wxID_ANY, title),
88 m_delay(3)
89 {
90 // we may disappear at any moment, let the others know about it
91 SetExtraStyle(GetExtraStyle() | wxWS_EX_TRANSIENT);
92
93 m_windowStyle |= style;
94
95 bool hasAbortButton = (style & wxPD_CAN_ABORT) != 0;
96
97 #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
98 // we have to remove the "Close" button from the title bar then as it is
99 // confusing to have it - it doesn't work anyhow
100 //
101 // FIXME: should probably have a (extended?) window style for this
102 if ( !hasAbortButton )
103 {
104 EnableCloseButton(false);
105 }
106 #endif // wxMSW
107
108 m_state = hasAbortButton ? Continue : Uncancelable;
109 m_maximum = maximum;
110
111 #if defined(__WXMSW__) || defined(__WXPM__)
112 // we can't have values > 65,536 in the progress control under Windows, so
113 // scale everything down
114 m_factor = m_maximum / 65536 + 1;
115 m_maximum /= m_factor;
116 #endif // __WXMSW__
117
118 m_parentTop = wxGetTopLevelParent(parent);
119
120 wxClientDC dc(this);
121 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
122 long widthText;
123 dc.GetTextExtent(message, &widthText, NULL, NULL, NULL, NULL);
124
125 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
126
127 m_msg = new wxStaticText(this, wxID_ANY, message);
128 sizer->Add(m_msg, 0, wxLEFT | wxTOP, 2*LAYOUT_MARGIN);
129
130 wxSize sizeDlg,
131 sizeLabel = m_msg->GetSize();
132 sizeDlg.y = 2*LAYOUT_MARGIN + sizeLabel.y;
133
134 if ( maximum > 0 )
135 {
136 // note that we can't use wxGA_SMOOTH because it happens to
137 // cause the dialog to be modal. Have an extra
138 // style argument to wxProgressDialog, perhaps.
139 m_gauge = new wxGauge(this, wxID_ANY, m_maximum,
140 wxDefaultPosition, wxDefaultSize,
141 wxGA_HORIZONTAL);
142
143 sizer->Add(m_gauge, 0, wxLEFT | wxRIGHT | wxTOP | wxEXPAND, 2*LAYOUT_MARGIN);
144 m_gauge->SetValue(0);
145
146 wxSize sizeGauge = m_gauge->GetSize();
147 sizeDlg.y += 2*LAYOUT_MARGIN + sizeGauge.y;
148 }
149 else
150 m_gauge = (wxGauge *)NULL;
151
152 // create the estimated/remaining/total time zones if requested
153 m_elapsed = m_estimated = m_remaining = (wxStaticText*)NULL;
154 m_display_estimated = m_last_timeupdate = m_break = 0;
155 m_ctdelay = 0;
156
157 // if we are going to have at least one label, remmeber it in this var
158 wxStaticText *label = NULL;
159
160 // also count how many labels we really have
161 size_t nTimeLabels = 0;
162
163 if ( style & wxPD_ELAPSED_TIME )
164 {
165 nTimeLabels++;
166
167 label =
168 m_elapsed = CreateLabel(_("Elapsed time : "), sizer);
169 }
170
171 if ( style & wxPD_ESTIMATED_TIME )
172 {
173 nTimeLabels++;
174
175 label =
176 m_estimated = CreateLabel(_("Estimated time : "), sizer);
177 }
178
179 if ( style & wxPD_REMAINING_TIME )
180 {
181 nTimeLabels++;
182
183 label =
184 m_remaining = CreateLabel(_("Remaining time : "), sizer);
185 }
186
187 if ( nTimeLabels > 0 )
188 {
189 // set it to the current time
190 m_timeStart = wxGetCurrentTime();
191 sizeDlg.y += nTimeLabels * (label->GetSize().y + LAYOUT_MARGIN);
192 }
193
194 if ( hasAbortButton )
195 {
196 m_btnAbort = new wxButton(this, wxID_CANCEL);
197
198 // Windows dialogs usually have buttons in the lower right corner
199 #if defined(__WXMSW__) || defined(__WXPM__)
200 sizer->Add(m_btnAbort, 0, wxALIGN_RIGHT | wxALL, 2*LAYOUT_MARGIN);
201 #else // !MSW
202 sizer->Add(m_btnAbort, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM | wxTOP, 2*LAYOUT_MARGIN);
203 #endif // MSW/!MSW
204 sizeDlg.y += 2*LAYOUT_MARGIN + wxButton::GetDefaultSize().y;
205 }
206 else // no "Cancel" button
207 {
208 m_btnAbort = (wxButton *)NULL;
209 }
210
211 SetSizerAndFit(sizer);
212
213 sizeDlg.y += 2*LAYOUT_MARGIN;
214
215 // try to make the dialog not square but rectangular of reasonabel width
216 sizeDlg.x = (wxCoord)wxMax(widthText, 4*sizeDlg.y/3);
217 sizeDlg.x *= 3;
218 sizeDlg.x /= 2;
219 SetClientSize(sizeDlg);
220
221 Centre(wxCENTER_FRAME | wxBOTH);
222
223 if ( style & wxPD_APP_MODAL )
224 {
225 m_winDisabler = new wxWindowDisabler(this);
226 }
227 else
228 {
229 if ( m_parentTop )
230 m_parentTop->Disable();
231 m_winDisabler = NULL;
232 }
233
234 Show();
235 Enable();
236
237 // this one can be initialized even if the others are unknown for now
238 //
239 // NB: do it after calling Layout() to keep the labels correctly aligned
240 if ( m_elapsed )
241 {
242 SetTimeLabel(0, m_elapsed);
243 }
244
245 Update();
246 }
247
248 wxStaticText *wxProgressDialog::CreateLabel(const wxString& text,
249 wxSizer *sizer)
250 {
251 wxBoxSizer *locsizer = new wxBoxSizer(wxHORIZONTAL);
252
253 wxStaticText *dummy = new wxStaticText(this, -1, text);
254 wxStaticText *label = new wxStaticText(this, wxID_ANY, _("unknown"));
255
256 // VZ: I like the labels be centered - if the others don't mind, you may
257 // remove "#ifdef __WXMSW__" and use it for all ports
258 #if defined(__WXMSW__) || defined(__WXPM__) || defined(__WXMAC__)
259 locsizer->Add(dummy, 1, wxALIGN_RIGHT);
260 locsizer->Add(label, 1, wxALIGN_LEFT | wxLEFT, LAYOUT_MARGIN);
261 sizer->Add(locsizer, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, LAYOUT_MARGIN);
262 #else // !MSW
263 sizer->Add(locsizer, 0, wxALIGN_RIGHT | wxRIGHT | wxTOP, LAYOUT_MARGIN);
264 locsizer->Add(dummy);
265 locsizer->Add(label, 0, wxLEFT, LAYOUT_MARGIN);
266 #endif // MSW/!MSW
267
268 return label;
269 }
270
271 // ----------------------------------------------------------------------------
272 // wxProgressDialog operations
273 // ----------------------------------------------------------------------------
274
275 bool
276 wxProgressDialog::Update(int value, const wxString& newmsg)
277 {
278 wxASSERT_MSG( value == -1 || m_gauge, wxT("cannot update non existent dialog") );
279
280 #ifdef __WXMSW__
281 value /= m_factor;
282 #endif // __WXMSW__
283
284 wxASSERT_MSG( value <= m_maximum, wxT("invalid progress value") );
285
286 // fill up the gauge if value == maximum because this means that the dialog
287 // is going to close and the gauge shouldn't be partly empty in this case
288 if ( m_gauge && value <= m_maximum )
289 {
290 m_gauge->SetValue(value == m_maximum ? value : value + 1);
291 }
292
293 if ( !newmsg.IsEmpty() )
294 {
295 m_msg->SetLabel(newmsg);
296
297 wxYieldIfNeeded() ;
298 }
299
300 if ( (m_elapsed || m_remaining || m_estimated) && (value != 0) )
301 {
302 unsigned long elapsed = wxGetCurrentTime() - m_timeStart;
303 if ( m_last_timeupdate < elapsed
304 || value == m_maximum
305 )
306 {
307 m_last_timeupdate = elapsed;
308 unsigned long estimated = m_break +
309 (unsigned long)(( (double) (elapsed-m_break) * m_maximum ) / ((double)value)) ;
310 if ( estimated > m_display_estimated
311 && m_ctdelay >= 0
312 )
313 {
314 ++m_ctdelay;
315 }
316 else if ( estimated < m_display_estimated
317 && m_ctdelay <= 0
318 )
319 {
320 --m_ctdelay;
321 }
322 else
323 {
324 m_ctdelay = 0;
325 }
326 if ( m_ctdelay >= m_delay // enough confirmations for a higher value
327 || m_ctdelay <= (m_delay*-1) // enough confirmations for a lower value
328 || value == m_maximum // to stay consistent
329 || elapsed > m_display_estimated // to stay consistent
330 || ( elapsed > 0 && elapsed < 4 ) // additional updates in the beginning
331 )
332 {
333 m_display_estimated = estimated;
334 m_ctdelay = 0;
335 }
336 }
337
338 unsigned long display_remaining = m_display_estimated - elapsed;
339 if ( display_remaining < 0 )
340 {
341 display_remaining = 0;
342 }
343
344 SetTimeLabel(elapsed, m_elapsed);
345 SetTimeLabel(m_display_estimated, m_estimated);
346 SetTimeLabel(display_remaining, m_remaining);
347 }
348
349 if ( value == m_maximum )
350 {
351 if ( m_state == Finished )
352 {
353 // ignore multiple calls to Update(m_maximum): it may sometimes be
354 // troublesome to ensure that Update() is not called twice with the
355 // same value (e.g. because of the rounding errors) and if we don't
356 // return now we're going to generate asserts below
357 return true;
358 }
359
360 // so that we return true below and that out [Cancel] handler knew what
361 // to do
362 m_state = Finished;
363 if( !(GetWindowStyle() & wxPD_AUTO_HIDE) )
364 {
365 if ( m_btnAbort )
366 {
367 // tell the user what he should do...
368 m_btnAbort->SetLabel(_("Close"));
369 }
370 #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
371 else // enable the button to give the user a way to close the dlg
372 {
373 EnableCloseButton();
374 }
375 #endif // __WXMSW__
376
377 if ( !newmsg )
378 {
379 // also provide the finishing message if the application didn't
380 m_msg->SetLabel(_("Done."));
381 }
382
383 wxYieldIfNeeded() ;
384
385 (void)ShowModal();
386 }
387 else // auto hide
388 {
389 // reenable other windows before hiding this one because otherwise
390 // Windows wouldn't give the focus back to the window which had
391 // been previously focused because it would still be disabled
392 ReenableOtherWindows();
393
394 Hide();
395 }
396 }
397 else
398 {
399 // we have to yield because not only we want to update the display but
400 // also to process the clicks on the cancel button
401 wxYieldIfNeeded() ;
402 }
403
404 // update the display in case yielding above didn't do it
405 Update();
406
407 return m_state != Canceled;
408 }
409
410 void wxProgressDialog::Resume()
411 {
412 m_state = Continue;
413 m_ctdelay = m_delay; // force an update of the elapsed/estimated/remaining time
414 m_break += wxGetCurrentTime()-m_timeStop;
415
416 // it may have been disabled by OnCancel(), so enable it back to let the
417 // user interrupt us again if needed
418 m_btnAbort->Enable();
419 }
420
421 bool wxProgressDialog::Show( bool show )
422 {
423 // reenable other windows before hiding this one because otherwise
424 // Windows wouldn't give the focus back to the window which had
425 // been previously focused because it would still be disabled
426 if(!show)
427 ReenableOtherWindows();
428
429 return wxDialog::Show(show);
430 }
431
432 // ----------------------------------------------------------------------------
433 // event handlers
434 // ----------------------------------------------------------------------------
435
436 void wxProgressDialog::OnCancel(wxCommandEvent& event)
437 {
438 if ( m_state == Finished )
439 {
440 // this means that the count down is already finished and we're being
441 // shown as a modal dialog - so just let the default handler do the job
442 event.Skip();
443 }
444 else
445 {
446 // request to cancel was received, the next time Update() is called we
447 // will handle it
448 m_state = Canceled;
449
450 // update the button state immediately so that the user knows that the
451 // request has been noticed
452 m_btnAbort->Disable();
453
454 // save the time when the dialog was stopped
455 m_timeStop = wxGetCurrentTime();
456 }
457 }
458
459 void wxProgressDialog::OnClose(wxCloseEvent& event)
460 {
461 if ( m_state == Uncancelable )
462 {
463 // can't close this dialog
464 event.Veto();
465 }
466 else if ( m_state == Finished )
467 {
468 // let the default handler close the window as we already terminated
469 event.Skip();
470 }
471 else
472 {
473 // next Update() will notice it
474 m_state = Canceled;
475 m_btnAbort->Disable();
476 m_timeStop = wxGetCurrentTime();
477 }
478 }
479
480 // ----------------------------------------------------------------------------
481 // destruction
482 // ----------------------------------------------------------------------------
483
484 wxProgressDialog::~wxProgressDialog()
485 {
486 // normally this should have been already done, but just in case
487 ReenableOtherWindows();
488 }
489
490 void wxProgressDialog::ReenableOtherWindows()
491 {
492 if ( GetWindowStyle() & wxPD_APP_MODAL )
493 {
494 delete m_winDisabler;
495 m_winDisabler = (wxWindowDisabler *)NULL;
496 }
497 else
498 {
499 if ( m_parentTop )
500 m_parentTop->Enable();
501 }
502 }
503
504 // ----------------------------------------------------------------------------
505 // private functions
506 // ----------------------------------------------------------------------------
507
508 static void SetTimeLabel(unsigned long val, wxStaticText *label)
509 {
510 if ( label )
511 {
512 wxString s;
513 unsigned long hours = val / 3600;
514 unsigned long minutes = (val % 3600) / 60;
515 unsigned long seconds = val % 60;
516 s.Printf(wxT("%lu:%02lu:%02lu"), hours, minutes, seconds);
517
518 if ( s != label->GetLabel() )
519 label->SetLabel(s);
520 }
521 }
522
523 #endif // wxUSE_PROGRESSDLG