don't use dynamic handlers for standard buttons as this prevents static event handler...
[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_ANY, wxDialogBase::OnButton)
103
104 EVT_CLOSE(wxDialogBase::OnCloseWindow)
105
106 EVT_CHAR_HOOK(wxDialogBase::OnCharHook)
107
108 WX_EVENT_TABLE_CONTROL_CONTAINER(wxDialogBase)
109 END_EVENT_TABLE()
110
111 WX_DELEGATE_TO_CONTROL_CONTAINER(wxDialogBase, wxTopLevelWindow)
112
113 void wxDialogBase::Init()
114 {
115 m_returnCode = 0;
116 m_affirmativeId = wxID_OK;
117 m_escapeId = wxID_ANY;
118
119 // the dialogs have this flag on by default to prevent the events from the
120 // dialog controls from reaching the parent frame which is usually
121 // undesirable and can lead to unexpected and hard to find bugs
122 SetExtraStyle(GetExtraStyle() | wxWS_EX_BLOCK_EVENTS);
123
124 m_container.SetContainerWindow(this);
125 }
126
127 #if wxUSE_STATTEXT
128
129 void wxTextWrapper::Wrap(wxWindow *win, const wxString& text, int widthMax)
130 {
131 const wxChar *lastSpace = NULL;
132 wxString line;
133
134 const wxChar *lineStart = text.c_str();
135 for ( const wxChar *p = lineStart; ; p++ )
136 {
137 if ( IsStartOfNewLine() )
138 {
139 OnNewLine();
140
141 lastSpace = NULL;
142 line.clear();
143 lineStart = p;
144 }
145
146 if ( *p == _T('\n') || *p == _T('\0') )
147 {
148 DoOutputLine(line);
149
150 if ( *p == _T('\0') )
151 break;
152 }
153 else // not EOL
154 {
155 if ( *p == _T(' ') )
156 lastSpace = p;
157
158 line += *p;
159
160 if ( widthMax >= 0 && lastSpace )
161 {
162 int width;
163 win->GetTextExtent(line, &width, NULL);
164
165 if ( width > widthMax )
166 {
167 // remove the last word from this line
168 line.erase(lastSpace - lineStart, p + 1 - lineStart);
169 DoOutputLine(line);
170
171 // go back to the last word of this line which we didn't
172 // output yet
173 p = lastSpace;
174 }
175 }
176 //else: no wrapping at all or impossible to wrap
177 }
178 }
179 }
180
181 class wxTextSizerWrapper : public wxTextWrapper
182 {
183 public:
184 wxTextSizerWrapper(wxWindow *win)
185 {
186 m_win = win;
187 m_hLine = 0;
188 }
189
190 wxSizer *CreateSizer(const wxString& text, int widthMax)
191 {
192 m_sizer = new wxBoxSizer(wxVERTICAL);
193 Wrap(m_win, text, widthMax);
194 return m_sizer;
195 }
196
197 protected:
198 virtual void OnOutputLine(const wxString& line)
199 {
200 if ( !line.empty() )
201 {
202 m_sizer->Add(new wxStaticText(m_win, wxID_ANY, line));
203 }
204 else // empty line, no need to create a control for it
205 {
206 if ( !m_hLine )
207 m_hLine = m_win->GetCharHeight();
208
209 m_sizer->Add(5, m_hLine);
210 }
211 }
212
213 private:
214 wxWindow *m_win;
215 wxSizer *m_sizer;
216 int m_hLine;
217 };
218
219 wxSizer *wxDialogBase::CreateTextSizer(const wxString& message)
220 {
221 // I admit that this is complete bogus, but it makes
222 // message boxes work for pda screens temporarily..
223 int widthMax = -1;
224 const bool is_pda = wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA;
225 if (is_pda)
226 {
227 widthMax = wxSystemSettings::GetMetric( wxSYS_SCREEN_X ) - 25;
228 }
229
230 // '&' is used as accel mnemonic prefix in the wxWidgets controls but in
231 // the static messages created by CreateTextSizer() (used by wxMessageBox,
232 // for example), we don't want this special meaning, so we need to quote it
233 wxString text(message);
234 text.Replace(_T("&"), _T("&&"));
235
236 wxTextSizerWrapper wrapper(this);
237
238 return wrapper.CreateSizer(text, widthMax);
239 }
240
241 class wxLabelWrapper : public wxTextWrapper
242 {
243 public:
244 void WrapLabel(wxWindow *text, int widthMax)
245 {
246 m_text.clear();
247 Wrap(text, text->GetLabel(), widthMax);
248 text->SetLabel(m_text);
249 }
250
251 protected:
252 virtual void OnOutputLine(const wxString& line)
253 {
254 m_text += line;
255 }
256
257 virtual void OnNewLine()
258 {
259 m_text += _T('\n');
260 }
261
262 private:
263 wxString m_text;
264 };
265
266 // NB: don't "factor out" the scope operator, SGI MIPSpro 7.3 (but not 7.4)
267 // gets confused if it doesn't immediately follow the class name
268 void
269 #if defined(__WXGTK__) && !defined(__WXUNIVERSAL__)
270 wxStaticText::
271 #else
272 wxStaticTextBase::
273 #endif
274 Wrap(int width)
275 {
276 wxLabelWrapper wrapper;
277 wrapper.WrapLabel(this, width);
278 }
279
280 #endif // wxUSE_STATTEXT
281
282 wxSizer *wxDialogBase::CreateButtonSizer( long flags, bool separated, wxCoord distance )
283 {
284 #ifdef __SMARTPHONE__
285 wxUnusedVar(separated);
286 wxUnusedVar(distance);
287
288 wxDialog* dialog = (wxDialog*) this;
289 if (flags & wxOK){
290 dialog->SetLeftMenu(wxID_OK);
291 }
292
293 if (flags & wxCANCEL){
294 dialog->SetRightMenu(wxID_CANCEL);
295 }
296
297 if (flags & wxYES){
298 dialog->SetLeftMenu(wxID_YES);
299 }
300
301 if (flags & wxNO){
302 dialog->SetLeftMenu(wxID_NO);
303 }
304 wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
305 return sizer;
306
307 #else // !__SMARTPHONE__
308
309 #ifdef __POCKETPC__
310 // PocketPC guidelines recommend for Ok/Cancel dialogs to use
311 // OK button located inside caption bar and implement Cancel functionality
312 // through Undo outside dialog. As native behaviour this will be default
313 // here but can be easily replaced with real wxButtons
314 // with "wince.dialog.real-ok-cancel" option set to 1
315 if ( ((flags & ~(wxCANCEL|wxNO_DEFAULT))== wxOK) &&
316 (wxSystemOptions::GetOptionInt(wxT("wince.dialog.real-ok-cancel"))==0)
317 )
318 {
319 wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
320 return sizer;
321 }
322 #endif // __POCKETPC__
323
324 #if wxUSE_BUTTON
325
326 wxSizer* buttonSizer = CreateStdDialogButtonSizer( flags );
327
328 // Mac Human Interface Guidelines recommend not to use static lines as grouping elements
329 #if wxUSE_STATLINE && !defined(__WXMAC__)
330 if(!separated)
331 return buttonSizer;
332
333 wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
334 topsizer->Add( new wxStaticLine( this, wxID_ANY ), 0, wxEXPAND | wxBOTTOM, distance );
335 topsizer->Add( buttonSizer, 0, wxEXPAND );
336 return topsizer;
337
338 #else // !wxUSE_STATLINE
339
340 wxUnusedVar(separated);
341 wxUnusedVar(distance);
342 return buttonSizer;
343
344 #endif // wxUSE_STATLINE/!wxUSE_STATLINE
345
346 #else // !wxUSE_BUTTON
347
348 wxUnusedVar(separated);
349 wxUnusedVar(distance);
350 wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
351 return sizer;
352
353 #endif // wxUSE_BUTTON/!wxUSE_BUTTON
354
355 #endif // __SMARTPHONE__/!__SMARTPHONE__
356 }
357
358 #if wxUSE_BUTTON
359
360 wxStdDialogButtonSizer *wxDialogBase::CreateStdDialogButtonSizer( long flags )
361 {
362 wxStdDialogButtonSizer *sizer = new wxStdDialogButtonSizer();
363
364 wxButton *ok = NULL;
365 wxButton *yes = NULL;
366 wxButton *no = NULL;
367
368 if (flags & wxOK){
369 ok = new wxButton(this, wxID_OK);
370 sizer->AddButton(ok);
371 }
372
373 if (flags & wxCANCEL){
374 wxButton *cancel = new wxButton(this, wxID_CANCEL);
375 sizer->AddButton(cancel);
376 }
377
378 if (flags & wxYES){
379 yes = new wxButton(this, wxID_YES);
380 sizer->AddButton(yes);
381 }
382
383 if (flags & wxNO){
384 no = new wxButton(this, wxID_NO);
385 sizer->AddButton(no);
386 }
387
388 if (flags & wxHELP){
389 wxButton *help = new wxButton(this, wxID_HELP);
390 sizer->AddButton(help);
391 }
392
393 if (flags & wxNO_DEFAULT)
394 {
395 if (no)
396 {
397 no->SetDefault();
398 no->SetFocus();
399 }
400 }
401 else
402 {
403 if (ok)
404 {
405 ok->SetDefault();
406 ok->SetFocus();
407 }
408 else if (yes)
409 {
410 yes->SetDefault();
411 yes->SetFocus();
412 }
413 }
414
415 if (flags & wxOK)
416 SetAffirmativeId(wxID_OK);
417 else if (flags & wxYES)
418 SetAffirmativeId(wxID_YES);
419
420 sizer->Realize();
421
422 return sizer;
423 }
424
425 #endif // wxUSE_BUTTON
426
427 // ----------------------------------------------------------------------------
428 // standard buttons handling
429 // ----------------------------------------------------------------------------
430
431 void wxDialogBase::EndDialog(int rc)
432 {
433 if ( IsModal() )
434 EndModal(rc);
435 else
436 Hide();
437 }
438
439 void wxDialogBase::AcceptAndClose()
440 {
441 if ( Validate() && TransferDataFromWindow() )
442 {
443 EndDialog(m_affirmativeId);
444 }
445 }
446
447 void wxDialogBase::SetAffirmativeId(int affirmativeId)
448 {
449 m_affirmativeId = affirmativeId;
450 }
451
452 void wxDialogBase::SetEscapeId(int escapeId)
453 {
454 m_escapeId = escapeId;
455 }
456
457 bool wxDialogBase::EmulateButtonClickIfPresent(int id)
458 {
459 wxButton *btn = wxDynamicCast(FindWindow(id), wxButton);
460
461 if ( !btn || !btn->IsEnabled() || !btn->IsShown() )
462 return false;
463
464 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, id);
465 event.SetEventObject(btn);
466 btn->GetEventHandler()->ProcessEvent(event);
467
468 return true;
469 }
470
471 bool wxDialogBase::IsEscapeKey(const wxKeyEvent& event)
472 {
473 // for most platforms, Esc key is used to close the dialogs
474 return event.GetKeyCode() == WXK_ESCAPE &&
475 event.GetModifiers() == wxMOD_NONE;
476 }
477
478 void wxDialogBase::OnCharHook(wxKeyEvent& event)
479 {
480 if ( event.GetKeyCode() == WXK_ESCAPE )
481 {
482 int idCancel = GetEscapeId();
483 switch ( idCancel )
484 {
485 case wxID_NONE:
486 // don't handle Esc specially at all
487 break;
488
489 case wxID_ANY:
490 // this value is special: it means translate Esc to wxID_CANCEL
491 // but if there is no such button, then fall back to wxID_OK
492 if ( EmulateButtonClickIfPresent(wxID_CANCEL) )
493 return;
494 idCancel = GetAffirmativeId();
495 // fall through
496
497 default:
498 // translate Esc to button press for the button with given id
499 if ( EmulateButtonClickIfPresent(idCancel) )
500 return;
501 }
502 }
503
504 event.Skip();
505 }
506
507 void wxDialogBase::OnButton(wxCommandEvent& event)
508 {
509 const int id = event.GetId();
510 if ( id == GetAffirmativeId() )
511 {
512 AcceptAndClose();
513 }
514 else if ( id == wxID_APPLY )
515 {
516 if ( Validate() )
517 TransferDataFromWindow();
518
519 // TODO: disable the Apply button until things change again
520 }
521 else if ( id == GetEscapeId() ||
522 (id == wxID_CANCEL && GetEscapeId() == wxID_ANY) )
523 {
524 EndDialog(wxID_CANCEL);
525 }
526 else // not a standard button
527 {
528 event.Skip();
529 }
530 }
531
532 // ----------------------------------------------------------------------------
533 // other event handlers
534 // ----------------------------------------------------------------------------
535
536 void wxDialogBase::OnCloseWindow(wxCloseEvent& WXUNUSED(event))
537 {
538 // We'll send a Cancel message by default, which may close the dialog.
539 // Check for looping if the Cancel event handler calls Close().
540
541 // Note that if a cancel button and handler aren't present in the dialog,
542 // nothing will happen when you close the dialog via the window manager, or
543 // via Close(). We wouldn't want to destroy the dialog by default, since
544 // the dialog may have been created on the stack. However, this does mean
545 // that calling dialog->Close() won't delete the dialog unless the handler
546 // for wxID_CANCEL does so. So use Destroy() if you want to be sure to
547 // destroy the dialog. The default OnCancel (above) simply ends a modal
548 // dialog, and hides a modeless dialog.
549
550 // VZ: this is horrible and MT-unsafe. Can't we reuse some of these global
551 // lists here? don't dare to change it now, but should be done later!
552 static wxList closing;
553
554 if ( closing.Member(this) )
555 return;
556
557 closing.Append(this);
558
559 wxCommandEvent cancelEvent(wxEVT_COMMAND_BUTTON_CLICKED, wxID_CANCEL);
560 cancelEvent.SetEventObject( this );
561 GetEventHandler()->ProcessEvent(cancelEvent); // This may close the dialog
562
563 closing.DeleteObject(this);
564 }
565
566 void wxDialogBase::OnSysColourChanged(wxSysColourChangedEvent& WXUNUSED(event))
567 {
568 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE));
569 Refresh();
570 }