use sizers instead of constraints in wxProgressDialog (patch 980364)
[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 {
89 // we may disappear at any moment, let the others know about it
90 SetExtraStyle(GetExtraStyle() | wxWS_EX_TRANSIENT);
91
92 m_windowStyle |= style;
93
94 bool hasAbortButton = (style & wxPD_CAN_ABORT) != 0;
95
96 #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
97 // we have to remove the "Close" button from the title bar then as it is
98 // confusing to have it - it doesn't work anyhow
99 //
100 // FIXME: should probably have a (extended?) window style for this
101 if ( !hasAbortButton )
102 {
103 EnableCloseButton(false);
104 }
105 #endif // wxMSW
106
107 m_state = hasAbortButton ? Continue : Uncancelable;
108 m_maximum = maximum;
109
110 #if defined(__WXMSW__) || defined(__WXPM__)
111 // we can't have values > 65,536 in the progress control under Windows, so
112 // scale everything down
113 m_factor = m_maximum / 65536 + 1;
114 m_maximum /= m_factor;
115 #endif // __WXMSW__
116
117 m_parentTop = wxGetTopLevelParent(parent);
118
119 wxClientDC dc(this);
120 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
121 long widthText;
122 dc.GetTextExtent(message, &widthText, NULL, NULL, NULL, NULL);
123
124 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
125
126 m_msg = new wxStaticText(this, wxID_ANY, message);
127 sizer->Add(m_msg, 0, wxLEFT | wxTOP, 2*LAYOUT_MARGIN);
128
129 wxSize sizeDlg,
130 sizeLabel = m_msg->GetSize();
131 sizeDlg.y = 2*LAYOUT_MARGIN + sizeLabel.y;
132
133 wxWindow *lastWindow = m_msg;
134
135 if ( maximum > 0 )
136 {
137 // note that we can't use wxGA_SMOOTH because it happens to
138 // cause the dialog to be modal. Have an extra
139 // style argument to wxProgressDialog, perhaps.
140 m_gauge = new wxGauge(this, wxID_ANY, m_maximum,
141 wxDefaultPosition, wxDefaultSize,
142 wxGA_HORIZONTAL);
143
144 sizer->Add(m_gauge, 0, wxLEFT | wxRIGHT | wxTOP | wxEXPAND, 2*LAYOUT_MARGIN);
145 m_gauge->SetValue(0);
146 lastWindow = m_gauge;
147
148 wxSize sizeGauge = m_gauge->GetSize();
149 sizeDlg.y += 2*LAYOUT_MARGIN + sizeGauge.y;
150 }
151 else
152 m_gauge = (wxGauge *)NULL;
153
154 // create the estimated/remaining/total time zones if requested
155 m_elapsed = m_estimated = m_remaining = (wxStaticText*)NULL;
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, _("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 unsigned long estimated = (unsigned long)(( (double) elapsed * m_maximum ) / ((double)value)) ;
304 unsigned long remaining = estimated - elapsed;
305
306 SetTimeLabel(elapsed, m_elapsed);
307 SetTimeLabel(estimated, m_estimated);
308 SetTimeLabel(remaining, m_remaining);
309 }
310
311 if ( value == m_maximum )
312 {
313 // so that we return true below and that out [Cancel] handler knew what
314 // to do
315 m_state = Finished;
316 if( !(GetWindowStyle() & wxPD_AUTO_HIDE) )
317 {
318 if ( m_btnAbort )
319 {
320 // tell the user what he should do...
321 m_btnAbort->SetLabel(_("Close"));
322 }
323 #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
324 else // enable the button to give the user a way to close the dlg
325 {
326 EnableCloseButton();
327 }
328 #endif // __WXMSW__
329
330 if ( !newmsg )
331 {
332 // also provide the finishing message if the application didn't
333 m_msg->SetLabel(_("Done."));
334 }
335
336 wxYieldIfNeeded() ;
337
338 (void)ShowModal();
339 }
340 else // auto hide
341 {
342 // reenable other windows before hiding this one because otherwise
343 // Windows wouldn't give the focus back to the window which had
344 // been previously focused because it would still be disabled
345 ReenableOtherWindows();
346
347 Hide();
348 }
349 }
350 else
351 {
352 // we have to yield because not only we want to update the display but
353 // also to process the clicks on the cancel button
354 wxYieldIfNeeded() ;
355 }
356
357 // update the display in case yielding above didn't do it
358 Update();
359
360 return m_state != Canceled;
361 }
362
363 void wxProgressDialog::Resume()
364 {
365 m_state = Continue;
366
367 // it may have been disabled by OnCancel(), so enable it back to let the
368 // user interrupt us again if needed
369 m_btnAbort->Enable();
370 }
371
372 bool wxProgressDialog::Show( bool show )
373 {
374 // reenable other windows before hiding this one because otherwise
375 // Windows wouldn't give the focus back to the window which had
376 // been previously focused because it would still be disabled
377 if(!show)
378 ReenableOtherWindows();
379
380 return wxDialog::Show(show);
381 }
382
383 // ----------------------------------------------------------------------------
384 // event handlers
385 // ----------------------------------------------------------------------------
386
387 void wxProgressDialog::OnCancel(wxCommandEvent& event)
388 {
389 if ( m_state == Finished )
390 {
391 // this means that the count down is already finished and we're being
392 // shown as a modal dialog - so just let the default handler do the job
393 event.Skip();
394 }
395 else
396 {
397 // request to cancel was received, the next time Update() is called we
398 // will handle it
399 m_state = Canceled;
400
401 // update the button state immediately so that the user knows that the
402 // request has been noticed
403 m_btnAbort->Disable();
404 }
405 }
406
407 void wxProgressDialog::OnClose(wxCloseEvent& event)
408 {
409 if ( m_state == Uncancelable )
410 {
411 // can't close this dialog
412 event.Veto();
413 }
414 else if ( m_state == Finished )
415 {
416 // let the default handler close the window as we already terminated
417 event.Skip();
418 }
419 else
420 {
421 // next Update() will notice it
422 m_state = Canceled;
423 }
424 }
425
426 // ----------------------------------------------------------------------------
427 // destruction
428 // ----------------------------------------------------------------------------
429
430 wxProgressDialog::~wxProgressDialog()
431 {
432 // normally this should have been already done, but just in case
433 ReenableOtherWindows();
434 }
435
436 void wxProgressDialog::ReenableOtherWindows()
437 {
438 if ( GetWindowStyle() & wxPD_APP_MODAL )
439 {
440 delete m_winDisabler;
441 m_winDisabler = (wxWindowDisabler *)NULL;
442 }
443 else
444 {
445 if ( m_parentTop )
446 m_parentTop->Enable();
447 }
448 }
449
450 // ----------------------------------------------------------------------------
451 // private functions
452 // ----------------------------------------------------------------------------
453
454 static void SetTimeLabel(unsigned long val, wxStaticText *label)
455 {
456 if ( label )
457 {
458 wxString s;
459 unsigned long hours = val / 3600;
460 unsigned long minutes = (val % 3600) / 60;
461 unsigned long seconds = val % 60;
462 s.Printf(wxT("%lu:%02lu:%02lu"), hours, minutes, seconds);
463
464 if ( s != label->GetLabel() )
465 label->SetLabel(s);
466 }
467 }
468
469 #endif // wxUSE_PROGRESSDLG