]> git.saurik.com Git - wxWidgets.git/blob - src/generic/wizard.cpp
fix for wxFrame's last focus bug (finally?)
[wxWidgets.git] / src / generic / wizard.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: generic/wizard.cpp
3 // Purpose: generic implementation of wxWizard class
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 15.08.99
7 // RCS-ID: $Id$
8 // Copyright: (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation ".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 #ifndef WX_PRECOMP
32 #include "wx/dynarray.h"
33 #include "wx/intl.h"
34 #include "wx/statbmp.h"
35 #include "wx/button.h"
36 #endif //WX_PRECOMP
37
38 #include "wx/statline.h"
39
40 #include "wx/wizard.h"
41
42 // ----------------------------------------------------------------------------
43 // simple types
44 // ----------------------------------------------------------------------------
45
46 WX_DEFINE_ARRAY(wxPanel *, wxArrayPages);
47
48 // ----------------------------------------------------------------------------
49 // event tables and such
50 // ----------------------------------------------------------------------------
51
52 DEFINE_EVENT_TYPE(wxEVT_WIZARD_PAGE_CHANGED)
53 DEFINE_EVENT_TYPE(wxEVT_WIZARD_PAGE_CHANGING)
54 DEFINE_EVENT_TYPE(wxEVT_WIZARD_CANCEL)
55
56 BEGIN_EVENT_TABLE(wxWizard, wxDialog)
57 EVT_BUTTON(wxID_CANCEL, wxWizard::OnCancel)
58 EVT_BUTTON(-1, wxWizard::OnBackOrNext)
59 END_EVENT_TABLE()
60
61 IMPLEMENT_DYNAMIC_CLASS(wxWizard, wxDialog)
62 IMPLEMENT_ABSTRACT_CLASS(wxWizardPage, wxPanel)
63 IMPLEMENT_DYNAMIC_CLASS(wxWizardPageSimple, wxWizardPage)
64 IMPLEMENT_DYNAMIC_CLASS(wxWizardEvent, wxNotifyEvent)
65
66 // ============================================================================
67 // implementation
68 // ============================================================================
69
70 // ----------------------------------------------------------------------------
71 // wxWizardPage
72 // ----------------------------------------------------------------------------
73
74 wxWizardPage::wxWizardPage(wxWizard *parent, const wxBitmap& bitmap)
75 : wxPanel(parent), m_bitmap(bitmap)
76 {
77 // initially the page is hidden, it's shown only when it becomes current
78 Hide();
79 }
80
81 // ----------------------------------------------------------------------------
82 // wxWizardPageSimple
83 // ----------------------------------------------------------------------------
84
85 wxWizardPage *wxWizardPageSimple::GetPrev() const
86 {
87 return m_prev;
88 }
89
90 wxWizardPage *wxWizardPageSimple::GetNext() const
91 {
92 return m_next;
93 }
94 // ----------------------------------------------------------------------------
95 // generic wxWizard implementation
96 // ----------------------------------------------------------------------------
97
98 wxWizard::wxWizard(wxWindow *parent,
99 int id,
100 const wxString& title,
101 const wxBitmap& bitmap,
102 const wxPoint& pos)
103 : m_posWizard(pos), m_bitmap(bitmap)
104 {
105 // just create the dialog itself here, the controls will be created in
106 // DoCreateControls() called later when we know our final size
107 m_page = (wxWizardPage *)NULL;
108 m_btnPrev = m_btnNext = NULL;
109 m_statbmp = NULL;
110
111 (void)wxDialog::Create(parent, id, title, pos);
112 }
113
114 void wxWizard::DoCreateControls()
115 {
116 // do nothing if the controls were already created
117 if ( WasCreated() )
118 return;
119
120 // constants defining the dialog layout
121 // ------------------------------------
122
123 // these constants define the position of the upper left corner of the
124 // bitmap or the page in the wizard
125 static const int X_MARGIN = 10;
126 static const int Y_MARGIN = 10;
127
128 // margin between the bitmap and the panel
129 static const int BITMAP_X_MARGIN = 15;
130
131 // margin between the bitmap and the static line
132 static const int BITMAP_Y_MARGIN = 15;
133
134 // margin between the static line and the buttons
135 static const int SEPARATOR_LINE_MARGIN = 15;
136
137 // margin between "Next >" and "Cancel" buttons
138 static const int BUTTON_MARGIN = 10;
139
140 // default width and height of the page
141 static const int DEFAULT_PAGE_WIDTH = 270;
142 static const int DEFAULT_PAGE_HEIGHT = 290;
143
144 // create controls
145 // ---------------
146
147 wxSize sizeBtn = wxButton::GetDefaultSize();
148
149 // the global dialog layout is: a row of buttons at the bottom (aligned to
150 // the right), the static line above them, the bitmap (if any) on the left
151 // of the upper part of the dialog and the panel in the remaining space
152 m_x = X_MARGIN;
153 m_y = Y_MARGIN;
154
155 int defaultHeight;
156 if ( m_bitmap.Ok() )
157 {
158 m_statbmp = new wxStaticBitmap(this, -1, m_bitmap, wxPoint(m_x, m_y));
159
160 m_x += m_bitmap.GetWidth() + BITMAP_X_MARGIN;
161
162 defaultHeight = m_bitmap.GetHeight();
163 }
164 else
165 {
166 m_statbmp = (wxStaticBitmap *)NULL;
167
168 defaultHeight = DEFAULT_PAGE_HEIGHT;
169 }
170
171 // use default size if none given and also make sure that the dialog is
172 // not less than the default size
173 m_height = m_sizePage.y == -1 ? defaultHeight : m_sizePage.y;
174 m_width = m_sizePage.x == -1 ? DEFAULT_PAGE_WIDTH : m_sizePage.x;
175 if ( m_height < defaultHeight )
176 m_height = defaultHeight;
177 if ( m_width < DEFAULT_PAGE_WIDTH )
178 m_width = DEFAULT_PAGE_WIDTH;
179
180 int x = X_MARGIN;
181 int y = m_y + m_height + BITMAP_Y_MARGIN;
182
183 #if wxUSE_STATLINE
184 (void)new wxStaticLine(this, -1, wxPoint(x, y),
185 wxSize(m_x + m_width - x, 2));
186 #endif // wxUSE_STATLINE
187
188 x = m_x + m_width - 3*sizeBtn.x - BUTTON_MARGIN;
189 y += SEPARATOR_LINE_MARGIN;
190 m_btnPrev = new wxButton(this, wxID_BACKWARD, _("< &Back"), wxPoint(x, y), sizeBtn);
191
192 x += sizeBtn.x;
193 m_btnNext = new wxButton(this, wxID_FORWARD, _("&Next >"), wxPoint(x, y), sizeBtn);
194
195 x += sizeBtn.x + BUTTON_MARGIN;
196 (void)new wxButton(this, wxID_CANCEL, _("Cancel"), wxPoint(x, y), sizeBtn);
197
198 // position and size the dialog
199 // ----------------------------
200
201 SetClientSize(m_x + m_width + X_MARGIN,
202 m_y + m_height + BITMAP_Y_MARGIN +
203 SEPARATOR_LINE_MARGIN + sizeBtn.y + Y_MARGIN);
204
205 if ( m_posWizard == wxDefaultPosition )
206 {
207 CentreOnScreen();
208 }
209 }
210
211 void wxWizard::SetPageSize(const wxSize& size)
212 {
213 // otherwise it will have no effect now as it's too late...
214 wxASSERT_MSG( !WasCreated(), _T("should be called before RunWizard()!") );
215
216 m_sizePage = size;
217 }
218
219 bool wxWizard::ShowPage(wxWizardPage *page, bool goingForward)
220 {
221 wxASSERT_MSG( page != m_page, wxT("this is useless") );
222
223 // we'll use this to decide whether we have to change the label of this
224 // button or not (initially the label is "Next")
225 bool btnLabelWasNext = TRUE;
226
227 // and this tells us whether we already had the default bitmap before
228 bool bmpWasDefault = TRUE;
229
230 if ( m_page )
231 {
232 // send the event to the old page
233 wxWizardEvent event(wxEVT_WIZARD_PAGE_CHANGING, GetId(), goingForward);
234 if ( m_page->GetEventHandler()->ProcessEvent(event) &&
235 !event.IsAllowed() )
236 {
237 // vetoed by the page
238 return FALSE;
239 }
240
241 m_page->Hide();
242
243 btnLabelWasNext = m_page->GetNext() != (wxWizardPage *)NULL;
244 bmpWasDefault = !m_page->GetBitmap().Ok();
245 }
246
247 // set the new one
248 m_page = page;
249
250 // is this the end?
251 if ( !m_page )
252 {
253 // terminate successfully
254 EndModal(wxID_OK);
255
256 return TRUE;
257 }
258
259 // send the event to the new page now
260 wxWizardEvent event(wxEVT_WIZARD_PAGE_CHANGED, GetId(), goingForward);
261 (void)m_page->GetEventHandler()->ProcessEvent(event);
262
263 // position and show the new page
264 (void)m_page->TransferDataToWindow();
265 m_page->SetSize(m_x, m_y, m_width, m_height);
266 m_page->Show();
267
268 // change the bitmap if necessary (and if we have it at all)
269 bool bmpIsDefault = !m_page->GetBitmap().Ok();
270 if ( m_statbmp && (bmpIsDefault != bmpWasDefault) )
271 {
272 wxBitmap bmp;
273 if ( bmpIsDefault )
274 bmp = m_bitmap;
275 else
276 bmp = m_page->GetBitmap();
277 m_statbmp->SetBitmap(bmp);
278 }
279
280 // and update the buttons state
281 m_btnPrev->Enable(m_page->GetPrev() != (wxWizardPage *)NULL);
282
283 bool hasNext = m_page->GetNext() != (wxWizardPage *)NULL;
284 if ( btnLabelWasNext != hasNext )
285 {
286 // need to update
287 if (btnLabelWasNext)
288 m_btnNext->SetLabel(_("&Finish"));
289 else
290 m_btnNext->SetLabel(_("&Next >"));
291 }
292 // nothing to do: the label was already correct
293
294 return TRUE;
295 }
296
297 bool wxWizard::RunWizard(wxWizardPage *firstPage)
298 {
299 wxCHECK_MSG( firstPage, FALSE, wxT("can't run empty wizard") );
300
301 DoCreateControls();
302
303 // can't return FALSE here because there is no old page
304 (void)ShowPage(firstPage, TRUE /* forward */);
305
306 return ShowModal() == wxID_OK;
307 }
308
309 wxWizardPage *wxWizard::GetCurrentPage() const
310 {
311 return m_page;
312 }
313
314 wxSize wxWizard::GetPageSize() const
315 {
316 // make sure that the controls are created because otherwise m_width and
317 // m_height would be both still -1
318 wxConstCast(this, wxWizard)->DoCreateControls();
319
320 return wxSize(m_width, m_height);
321 }
322
323 void wxWizard::OnCancel(wxCommandEvent& WXUNUSED(event))
324 {
325 // this function probably can never be called when we don't have an active
326 // page, but a small extra check won't hurt
327 wxWindow *win = m_page ? (wxWindow *)m_page : (wxWindow *)this;
328
329 wxWizardEvent event(wxEVT_WIZARD_CANCEL, GetId());
330 if ( !win->GetEventHandler()->ProcessEvent(event) || event.IsAllowed() )
331 {
332 // no objections - close the dialog
333 EndModal(wxID_CANCEL);
334 }
335 //else: request to Cancel ignored
336 }
337
338 void wxWizard::OnBackOrNext(wxCommandEvent& event)
339 {
340 wxASSERT_MSG( (event.GetEventObject() == m_btnNext) ||
341 (event.GetEventObject() == m_btnPrev),
342 wxT("unknown button") );
343
344 // ask the current page first: notice that we do it before calling
345 // GetNext/Prev() because the data transfered from the controls of the page
346 // may change the value returned by these methods
347 if ( m_page && !m_page->TransferDataFromWindow() )
348 {
349 // the page data is incorrect, don't do anything
350 return;
351 }
352
353 bool forward = event.GetEventObject() == m_btnNext;
354
355 wxWizardPage *page;
356 if ( forward )
357 {
358 page = m_page->GetNext();
359 }
360 else // back
361 {
362 page = m_page->GetPrev();
363
364 wxASSERT_MSG( page, wxT("\"<Back\" button should have been disabled") );
365 }
366
367 // just pass to the new page (or may be not - but we don't care here)
368 (void)ShowPage(page, forward);
369 }
370
371 // ----------------------------------------------------------------------------
372 // our public interface
373 // ----------------------------------------------------------------------------
374
375 /* static */
376 wxWizard *wxWizardBase::Create(wxWindow *parent,
377 int id,
378 const wxString& title,
379 const wxBitmap& bitmap,
380 const wxPoint& pos,
381 const wxSize& WXUNUSED(size))
382 {
383 return new wxWizard(parent, id, title, bitmap, pos);
384 }
385
386 // ----------------------------------------------------------------------------
387 // wxWizardEvent
388 // ----------------------------------------------------------------------------
389
390 wxWizardEvent::wxWizardEvent(wxEventType type, int id, bool direction)
391 : wxNotifyEvent(type, id)
392 {
393 m_direction = direction;
394 }
395