connect the affirmative/cancel button handlers to new ids, not old ones
[wxWidgets.git] / src / common / dlgcmn.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/dlgcmn.cpp
3 // Purpose: common (to all ports) wxDialog functions
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 28.06.99
7 // RCS-ID: $Id$
8 // Copyright: (c) Vadim Zeitlin
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22
23 #ifdef __BORLANDC__
24 #pragma hdrstop
25 #endif
26
27 #include "wx/dialog.h"
28
29 #ifndef WX_PRECOMP
30 #include "wx/button.h"
31 #include "wx/dcclient.h"
32 #include "wx/intl.h"
33 #include "wx/settings.h"
34 #include "wx/stattext.h"
35 #include "wx/sizer.h"
36 #include "wx/containr.h"
37 #endif
38
39 #include "wx/statline.h"
40 #include "wx/sysopt.h"
41
42 #if wxUSE_STATTEXT
43
44 // ----------------------------------------------------------------------------
45 // wxTextWrapper
46 // ----------------------------------------------------------------------------
47
48 // this class is used to wrap the text on word boundary: wrapping is done by
49 // calling OnStartLine() and OnOutputLine() functions
50 class wxTextWrapper
51 {
52 public:
53 wxTextWrapper() { m_eol = false; }
54
55 // win is used for getting the font, text is the text to wrap, width is the
56 // max line width or -1 to disable wrapping
57 void Wrap(wxWindow *win, const wxString& text, int widthMax);
58
59 // we don't need it, but just to avoid compiler warnings
60 virtual ~wxTextWrapper() { }
61
62 protected:
63 // line may be empty
64 virtual void OnOutputLine(const wxString& line) = 0;
65
66 // called at the start of every new line (except the very first one)
67 virtual void OnNewLine() { }
68
69 private:
70 // call OnOutputLine() and set m_eol to true
71 void DoOutputLine(const wxString& line)
72 {
73 OnOutputLine(line);
74
75 m_eol = true;
76 }
77
78 // this function is a destructive inspector: when it returns true it also
79 // resets the flag to false so calling it again woulnd't return true any
80 // more
81 bool IsStartOfNewLine()
82 {
83 if ( !m_eol )
84 return false;
85
86 m_eol = false;
87
88 return true;
89 }
90
91
92 bool m_eol;
93 };
94
95 #endif // wxUSE_STATTEXT
96
97 // ----------------------------------------------------------------------------
98 // wxDialogBase
99 // ----------------------------------------------------------------------------
100
101 BEGIN_EVENT_TABLE(wxDialogBase, wxTopLevelWindow)
102 EVT_BUTTON(wxID_OK, wxDialogBase::OnAffirmativeButton)
103 EVT_BUTTON(wxID_APPLY, wxDialogBase::OnApply)
104 EVT_BUTTON(wxID_CANCEL, wxDialogBase::OnCancelButton)
105
106 EVT_CLOSE(wxDialogBase::OnCloseWindow)
107
108 EVT_CHAR_HOOK(wxDialogBase::OnCharHook)
109
110 WX_EVENT_TABLE_CONTROL_CONTAINER(wxDialogBase)
111 END_EVENT_TABLE()
112
113 WX_DELEGATE_TO_CONTROL_CONTAINER(wxDialogBase, wxTopLevelWindow)
114
115 void wxDialogBase::Init()
116 {
117 m_returnCode = 0;
118 m_affirmativeId = wxID_OK;
119 m_escapeId = wxID_ANY;
120
121 // the dialogs have this flag on by default to prevent the events from the
122 // dialog controls from reaching the parent frame which is usually
123 // undesirable and can lead to unexpected and hard to find bugs
124 SetExtraStyle(GetExtraStyle() | wxWS_EX_BLOCK_EVENTS);
125
126 m_container.SetContainerWindow(this);
127 }
128
129 #if wxUSE_STATTEXT
130
131 void wxTextWrapper::Wrap(wxWindow *win, const wxString& text, int widthMax)
132 {
133 const wxChar *lastSpace = NULL;
134 wxString line;
135
136 const wxChar *lineStart = text.c_str();
137 for ( const wxChar *p = lineStart; ; p++ )
138 {
139 if ( IsStartOfNewLine() )
140 {
141 OnNewLine();
142
143 lastSpace = NULL;
144 line.clear();
145 lineStart = p;
146 }
147
148 if ( *p == _T('\n') || *p == _T('\0') )
149 {
150 DoOutputLine(line);
151
152 if ( *p == _T('\0') )
153 break;
154 }
155 else // not EOL
156 {
157 if ( *p == _T(' ') )
158 lastSpace = p;
159
160 line += *p;
161
162 if ( widthMax >= 0 && lastSpace )
163 {
164 int width;
165 win->GetTextExtent(line, &width, NULL);
166
167 if ( width > widthMax )
168 {
169 // remove the last word from this line
170 line.erase(lastSpace - lineStart, p + 1 - lineStart);
171 DoOutputLine(line);
172
173 // go back to the last word of this line which we didn't
174 // output yet
175 p = lastSpace;
176 }
177 }
178 //else: no wrapping at all or impossible to wrap
179 }
180 }
181 }
182
183 class wxTextSizerWrapper : public wxTextWrapper
184 {
185 public:
186 wxTextSizerWrapper(wxWindow *win)
187 {
188 m_win = win;
189 m_hLine = 0;
190 }
191
192 wxSizer *CreateSizer(const wxString& text, int widthMax)
193 {
194 m_sizer = new wxBoxSizer(wxVERTICAL);
195 Wrap(m_win, text, widthMax);
196 return m_sizer;
197 }
198
199 protected:
200 virtual void OnOutputLine(const wxString& line)
201 {
202 if ( !line.empty() )
203 {
204 m_sizer->Add(new wxStaticText(m_win, wxID_ANY, line));
205 }
206 else // empty line, no need to create a control for it
207 {
208 if ( !m_hLine )
209 m_hLine = m_win->GetCharHeight();
210
211 m_sizer->Add(5, m_hLine);
212 }
213 }
214
215 private:
216 wxWindow *m_win;
217 wxSizer *m_sizer;
218 int m_hLine;
219 };
220
221 wxSizer *wxDialogBase::CreateTextSizer(const wxString& message)
222 {
223 // I admit that this is complete bogus, but it makes
224 // message boxes work for pda screens temporarily..
225 int widthMax = -1;
226 const bool is_pda = wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA;
227 if (is_pda)
228 {
229 widthMax = wxSystemSettings::GetMetric( wxSYS_SCREEN_X ) - 25;
230 }
231
232 // '&' is used as accel mnemonic prefix in the wxWidgets controls but in
233 // the static messages created by CreateTextSizer() (used by wxMessageBox,
234 // for example), we don't want this special meaning, so we need to quote it
235 wxString text(message);
236 text.Replace(_T("&"), _T("&&"));
237
238 wxTextSizerWrapper wrapper(this);
239
240 return wrapper.CreateSizer(text, widthMax);
241 }
242
243 class wxLabelWrapper : public wxTextWrapper
244 {
245 public:
246 void WrapLabel(wxWindow *text, int widthMax)
247 {
248 m_text.clear();
249 Wrap(text, text->GetLabel(), widthMax);
250 text->SetLabel(m_text);
251 }
252
253 protected:
254 virtual void OnOutputLine(const wxString& line)
255 {
256 m_text += line;
257 }
258
259 virtual void OnNewLine()
260 {
261 m_text += _T('\n');
262 }
263
264 private:
265 wxString m_text;
266 };
267
268 // NB: don't "factor out" the scope operator, SGI MIPSpro 7.3 (but not 7.4)
269 // gets confused if it doesn't immediately follow the class name
270 void
271 #if defined(__WXGTK__) && !defined(__WXUNIVERSAL__)
272 wxStaticText::
273 #else
274 wxStaticTextBase::
275 #endif
276 Wrap(int width)
277 {
278 wxLabelWrapper wrapper;
279 wrapper.WrapLabel(this, width);
280 }
281
282 #endif // wxUSE_STATTEXT
283
284 wxSizer *wxDialogBase::CreateButtonSizer( long flags, bool separated, wxCoord distance )
285 {
286 #ifdef __SMARTPHONE__
287 wxUnusedVar(separated);
288 wxUnusedVar(distance);
289
290 wxDialog* dialog = (wxDialog*) this;
291 if (flags & wxOK){
292 dialog->SetLeftMenu(wxID_OK);
293 }
294
295 if (flags & wxCANCEL){
296 dialog->SetRightMenu(wxID_CANCEL);
297 }
298
299 if (flags & wxYES){
300 dialog->SetLeftMenu(wxID_YES);
301 }
302
303 if (flags & wxNO){
304 dialog->SetLeftMenu(wxID_NO);
305 }
306 wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
307 return sizer;
308
309 #else // !__SMARTPHONE__
310
311 #ifdef __POCKETPC__
312 // PocketPC guidelines recommend for Ok/Cancel dialogs to use
313 // OK button located inside caption bar and implement Cancel functionality
314 // through Undo outside dialog. As native behaviour this will be default
315 // here but can be easily replaced with real wxButtons
316 // with "wince.dialog.real-ok-cancel" option set to 1
317 if ( ((flags & ~(wxCANCEL|wxNO_DEFAULT))== wxOK) &&
318 (wxSystemOptions::GetOptionInt(wxT("wince.dialog.real-ok-cancel"))==0)
319 )
320 {
321 wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
322 return sizer;
323 }
324 #endif // __POCKETPC__
325
326 #if wxUSE_BUTTON
327
328 wxSizer* buttonSizer = CreateStdDialogButtonSizer( flags );
329
330 // Mac Human Interface Guidelines recommend not to use static lines as grouping elements
331 #if wxUSE_STATLINE && !defined(__WXMAC__)
332 if(!separated)
333 return buttonSizer;
334
335 wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
336 topsizer->Add( new wxStaticLine( this, wxID_ANY ), 0, wxEXPAND | wxBOTTOM, distance );
337 topsizer->Add( buttonSizer, 0, wxEXPAND );
338 return topsizer;
339
340 #else // !wxUSE_STATLINE
341
342 wxUnusedVar(separated);
343 wxUnusedVar(distance);
344 return buttonSizer;
345
346 #endif // wxUSE_STATLINE/!wxUSE_STATLINE
347
348 #else // !wxUSE_BUTTON
349
350 wxUnusedVar(separated);
351 wxUnusedVar(distance);
352 wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
353 return sizer;
354
355 #endif // wxUSE_BUTTON/!wxUSE_BUTTON
356
357 #endif // __SMARTPHONE__/!__SMARTPHONE__
358 }
359
360 #if wxUSE_BUTTON
361
362 wxStdDialogButtonSizer *wxDialogBase::CreateStdDialogButtonSizer( long flags )
363 {
364 wxStdDialogButtonSizer *sizer = new wxStdDialogButtonSizer();
365
366 wxButton *ok = NULL;
367 wxButton *yes = NULL;
368 wxButton *no = NULL;
369
370 if (flags & wxOK){
371 ok = new wxButton(this, wxID_OK);
372 sizer->AddButton(ok);
373 }
374
375 if (flags & wxCANCEL){
376 wxButton *cancel = new wxButton(this, wxID_CANCEL);
377 sizer->AddButton(cancel);
378 }
379
380 if (flags & wxYES){
381 yes = new wxButton(this, wxID_YES);
382 sizer->AddButton(yes);
383 }
384
385 if (flags & wxNO){
386 no = new wxButton(this, wxID_NO);
387 sizer->AddButton(no);
388 }
389
390 if (flags & wxHELP){
391 wxButton *help = new wxButton(this, wxID_HELP);
392 sizer->AddButton(help);
393 }
394
395 if (flags & wxNO_DEFAULT)
396 {
397 if (no)
398 {
399 no->SetDefault();
400 no->SetFocus();
401 }
402 }
403 else
404 {
405 if (ok)
406 {
407 ok->SetDefault();
408 ok->SetFocus();
409 }
410 else if (yes)
411 {
412 yes->SetDefault();
413 yes->SetFocus();
414 }
415 }
416
417 if (flags & wxOK)
418 SetAffirmativeId(wxID_OK);
419 else if (flags & wxYES)
420 SetAffirmativeId(wxID_YES);
421
422 sizer->Realize();
423
424 return sizer;
425 }
426
427 #endif // wxUSE_BUTTON
428
429 // ----------------------------------------------------------------------------
430 // escape/affirmatives button handling
431 // ----------------------------------------------------------------------------
432
433 void wxDialogBase::AcceptAndClose()
434 {
435 if ( Validate() && TransferDataFromWindow() )
436 {
437 EndDialog(wxID_OK);
438 }
439 }
440
441 void wxDialogBase::SetAffirmativeId(int affirmativeId)
442 {
443 if ( affirmativeId == m_affirmativeId )
444 return;
445
446 // disconnect the handler for the old affirmative button
447 if ( m_affirmativeId != wxID_NONE && m_affirmativeId != wxID_OK )
448 {
449 if ( !Disconnect
450 (
451 m_affirmativeId,
452 wxEVT_COMMAND_BUTTON_CLICKED,
453 wxCommandEventHandler(wxDialogBase::OnAffirmativeButton)
454 ) )
455 {
456 wxFAIL_MSG( _T("failed to disconnect old ok handler") );
457 }
458 }
459 //else: wxID_OK is always handled
460
461 m_affirmativeId = affirmativeId;
462
463 // connect the handler to the new button
464 if ( m_affirmativeId != wxID_NONE )
465 {
466 Connect(m_affirmativeId,
467 wxEVT_COMMAND_BUTTON_CLICKED,
468 wxCommandEventHandler(wxDialogBase::OnAffirmativeButton));
469 }
470 //else: no affirmative button
471 }
472
473 void wxDialogBase::SetEscapeId(int escapeId)
474 {
475 if ( escapeId == m_escapeId )
476 return;
477
478 if ( m_escapeId != wxID_ANY &&
479 m_escapeId != wxID_CANCEL &&
480 m_escapeId != wxID_ANY )
481 {
482 if ( !Disconnect
483 (
484 m_escapeId,
485 wxEVT_COMMAND_BUTTON_CLICKED,
486 wxCommandEventHandler(wxDialogBase::OnCancelButton)
487 ) )
488 {
489 wxFAIL_MSG( _T("failed to disconnect old cancel handler") );
490 }
491 }
492 //else: wxID_CANCEL is always handled
493
494 m_escapeId = escapeId;
495
496 // connect the handler to the new button
497 if ( m_escapeId != wxID_NONE )
498 {
499 Connect(m_escapeId,
500 wxEVT_COMMAND_BUTTON_CLICKED,
501 wxCommandEventHandler(wxDialogBase::OnCancelButton));
502 }
503 }
504
505 // ----------------------------------------------------------------------------
506 // event handling stuff
507 // ----------------------------------------------------------------------------
508
509 bool wxDialogBase::EmulateButtonClickIfPresent(int id)
510 {
511 wxButton *btn = wxDynamicCast(FindWindow(id), wxButton);
512
513 if ( !btn || !btn->IsEnabled() || !btn->IsShown() )
514 return false;
515
516 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, id);
517 event.SetEventObject(btn);
518 btn->GetEventHandler()->ProcessEvent(event);
519
520 return true;
521 }
522
523 void wxDialogBase::EndDialog(int rc)
524 {
525 if ( IsModal() )
526 EndModal(rc);
527 else
528 Hide();
529 }
530
531 bool wxDialogBase::IsEscapeKey(const wxKeyEvent& event)
532 {
533 // for most platforms, Esc key is used to close the dialogs
534 return event.GetKeyCode() == WXK_ESCAPE &&
535 event.GetModifiers() == wxMOD_NONE;
536 }
537
538 void wxDialogBase::OnCharHook(wxKeyEvent& event)
539 {
540 if ( event.GetKeyCode() == WXK_ESCAPE )
541 {
542 int idCancel = GetEscapeId();
543 switch ( idCancel )
544 {
545 case wxID_NONE:
546 // don't handle Esc specially at all
547 break;
548
549 case wxID_ANY:
550 // this value is special: it means translate Esc to wxID_CANCEL
551 // but if there is no such button, then fall back to wxID_OK
552 if ( EmulateButtonClickIfPresent(wxID_CANCEL) )
553 return;
554 idCancel = GetAffirmativeId();
555 // fall through
556
557 default:
558 // translate Esc to button press for the button with given id
559 if ( EmulateButtonClickIfPresent(idCancel) )
560 return;
561 }
562 }
563
564 event.Skip();
565 }
566
567 void wxDialogBase::OnAffirmativeButton(wxCommandEvent& WXUNUSED(event))
568 {
569 AcceptAndClose();
570 }
571
572 void wxDialogBase::OnApply(wxCommandEvent& WXUNUSED(event))
573 {
574 if ( Validate() )
575 TransferDataFromWindow();
576
577 // TODO probably need to disable the Apply button until things change again
578 }
579
580 void wxDialogBase::OnCancelButton(wxCommandEvent& WXUNUSED(event))
581 {
582 EndDialog(wxID_CANCEL);
583 }
584
585 void wxDialogBase::OnCloseWindow(wxCloseEvent& WXUNUSED(event))
586 {
587 // We'll send a Cancel message by default, which may close the dialog.
588 // Check for looping if the Cancel event handler calls Close().
589
590 // Note that if a cancel button and handler aren't present in the dialog,
591 // nothing will happen when you close the dialog via the window manager, or
592 // via Close(). We wouldn't want to destroy the dialog by default, since
593 // the dialog may have been created on the stack. However, this does mean
594 // that calling dialog->Close() won't delete the dialog unless the handler
595 // for wxID_CANCEL does so. So use Destroy() if you want to be sure to
596 // destroy the dialog. The default OnCancel (above) simply ends a modal
597 // dialog, and hides a modeless dialog.
598
599 // VZ: this is horrible and MT-unsafe. Can't we reuse some of these global
600 // lists here? don't dare to change it now, but should be done later!
601 static wxList closing;
602
603 if ( closing.Member(this) )
604 return;
605
606 closing.Append(this);
607
608 wxCommandEvent cancelEvent(wxEVT_COMMAND_BUTTON_CLICKED, wxID_CANCEL);
609 cancelEvent.SetEventObject( this );
610 GetEventHandler()->ProcessEvent(cancelEvent); // This may close the dialog
611
612 closing.DeleteObject(this);
613 }
614
615 void wxDialogBase::OnSysColourChanged(wxSysColourChangedEvent& WXUNUSED(event))
616 {
617 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
618 Refresh();
619 }