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