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