Generate wxEVT_COMMAND_TEXT_ENTER events for generic wxSpinCtrl.
[wxWidgets.git] / src / generic / spinctlg.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/spinctlg.cpp
3 // Purpose: implements wxSpinCtrl as a composite control
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 29.01.01
7 // RCS-ID: $Id$
8 // Copyright: (c) 2001 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
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 #ifndef WX_PRECOMP
28 #include "wx/textctrl.h"
29 #endif //WX_PRECOMP
30
31 #include "wx/spinctrl.h"
32 #include "wx/tooltip.h"
33
34 #if wxUSE_SPINCTRL
35
36 IMPLEMENT_DYNAMIC_CLASS(wxSpinDoubleEvent, wxNotifyEvent)
37
38 // There are port-specific versions for the wxSpinCtrl, so exclude the
39 // contents of this file in those cases
40 #if !defined(wxHAS_NATIVE_SPINCTRL) || !defined(wxHAS_NATIVE_SPINCTRLDOUBLE)
41
42 #include "wx/spinbutt.h"
43
44 #if wxUSE_SPINBTN
45
46 // ----------------------------------------------------------------------------
47 // constants
48 // ----------------------------------------------------------------------------
49
50 // The margin between the text control and the spin: the value here is the same
51 // as the margin between the spin button and its "buddy" text control in wxMSW
52 // so the generic control looks similarly to the native one there, we might
53 // need to use different value for the other platforms (and maybe even
54 // determine it dynamically?).
55 static const wxCoord MARGIN = 1;
56
57 #define SPINCTRLBUT_MAX 32000 // large to avoid wrap around trouble
58
59 // ----------------------------------------------------------------------------
60 // wxSpinCtrlTextGeneric: text control used by spin control
61 // ----------------------------------------------------------------------------
62
63 class wxSpinCtrlTextGeneric : public wxTextCtrl
64 {
65 public:
66 wxSpinCtrlTextGeneric(wxSpinCtrlGenericBase *spin, const wxString& value, long style=0)
67 : wxTextCtrl(spin->GetParent(), wxID_ANY, value, wxDefaultPosition, wxDefaultSize,
68 style & (wxALIGN_MASK | wxTE_PROCESS_ENTER))
69 {
70 m_spin = spin;
71
72 // remove the default minsize, the spinctrl will have one instead
73 SetSizeHints(wxDefaultCoord, wxDefaultCoord);
74 }
75
76 virtual ~wxSpinCtrlTextGeneric()
77 {
78 // MSW sends extra kill focus event on destroy
79 if (m_spin)
80 m_spin->m_textCtrl = NULL;
81
82 m_spin = NULL;
83 }
84
85 void OnChar( wxKeyEvent &event )
86 {
87 if ( !m_spin->ProcessWindowEvent(event) )
88 event.Skip();
89 }
90
91 void OnTextEnter(wxCommandEvent& event)
92 {
93 // We need to forward this event to the spin control itself as it's
94 // supposed to generate it if wxTE_PROCESS_ENTER is used with it.
95 wxCommandEvent eventCopy(event);
96 eventCopy.SetEventObject(m_spin);
97 eventCopy.SetId(m_spin->GetId());
98 m_spin->ProcessWindowEvent(eventCopy);
99 }
100
101 void OnKillFocus(wxFocusEvent& event)
102 {
103 if (m_spin)
104 m_spin->ProcessWindowEvent(event);
105
106 event.Skip();
107 }
108
109 wxSpinCtrlGenericBase *m_spin;
110
111 private:
112 DECLARE_EVENT_TABLE()
113 };
114
115 BEGIN_EVENT_TABLE(wxSpinCtrlTextGeneric, wxTextCtrl)
116 EVT_CHAR(wxSpinCtrlTextGeneric::OnChar)
117 EVT_TEXT_ENTER(wxID_ANY, wxSpinCtrlTextGeneric::OnTextEnter)
118
119 EVT_KILL_FOCUS(wxSpinCtrlTextGeneric::OnKillFocus)
120 END_EVENT_TABLE()
121
122 // ----------------------------------------------------------------------------
123 // wxSpinCtrlButtonGeneric: spin button used by spin control
124 // ----------------------------------------------------------------------------
125
126 class wxSpinCtrlButtonGeneric : public wxSpinButton
127 {
128 public:
129 wxSpinCtrlButtonGeneric(wxSpinCtrlGenericBase *spin, int style)
130 : wxSpinButton(spin->GetParent(), wxID_ANY, wxDefaultPosition,
131 wxDefaultSize, style | wxSP_VERTICAL)
132 {
133 m_spin = spin;
134
135 SetRange(-SPINCTRLBUT_MAX, SPINCTRLBUT_MAX);
136
137 // remove the default minsize, the spinctrl will have one instead
138 SetSizeHints(wxDefaultCoord, wxDefaultCoord);
139 }
140
141 void OnSpinButton(wxSpinEvent& event)
142 {
143 if (m_spin)
144 m_spin->OnSpinButton(event);
145 }
146
147 wxSpinCtrlGenericBase *m_spin;
148
149 private:
150 DECLARE_EVENT_TABLE()
151 };
152
153 BEGIN_EVENT_TABLE(wxSpinCtrlButtonGeneric, wxSpinButton)
154 EVT_SPIN_UP( wxID_ANY, wxSpinCtrlButtonGeneric::OnSpinButton)
155 EVT_SPIN_DOWN(wxID_ANY, wxSpinCtrlButtonGeneric::OnSpinButton)
156 END_EVENT_TABLE()
157
158 // ============================================================================
159 // wxSpinCtrlGenericBase
160 // ============================================================================
161
162 // ----------------------------------------------------------------------------
163 // wxSpinCtrlGenericBase creation
164 // ----------------------------------------------------------------------------
165
166 void wxSpinCtrlGenericBase::Init()
167 {
168 m_value = 0;
169 m_min = 0;
170 m_max = 100;
171 m_increment = 1;
172 m_snap_to_ticks = false;
173
174 m_spin_value = 0;
175
176 m_textCtrl = NULL;
177 m_spinButton = NULL;
178 }
179
180 bool wxSpinCtrlGenericBase::Create(wxWindow *parent,
181 wxWindowID id,
182 const wxString& value,
183 const wxPoint& pos, const wxSize& size,
184 long style,
185 double min, double max, double initial,
186 double increment,
187 const wxString& name)
188 {
189 // don't use borders for this control itself, it wouldn't look good with
190 // the text control borders (but we might want to use style border bits to
191 // select the text control style)
192 if ( !wxControl::Create(parent, id, wxDefaultPosition, wxDefaultSize,
193 (style & ~wxBORDER_MASK) | wxBORDER_NONE,
194 wxDefaultValidator, name) )
195 {
196 return false;
197 }
198
199 m_value = initial;
200 m_min = min;
201 m_max = max;
202 m_increment = increment;
203
204 m_textCtrl = new wxSpinCtrlTextGeneric(this, value, style);
205 m_spinButton = new wxSpinCtrlButtonGeneric(this, style);
206 #if wxUSE_TOOLTIPS
207 m_textCtrl->SetToolTip(GetToolTipText());
208 m_spinButton->SetToolTip(GetToolTipText());
209 #endif // wxUSE_TOOLTIPS
210
211 m_spin_value = m_spinButton->GetValue();
212
213 // the string value overrides the numeric one (for backwards compatibility
214 // reasons and also because it is simpler to satisfy the string value which
215 // comes much sooner in the list of arguments and leave the initial
216 // parameter unspecified)
217 if ( !value.empty() )
218 {
219 double d;
220 if ( DoTextToValue(value, &d) )
221 {
222 m_value = d;
223 m_textCtrl->SetValue(DoValueToText(m_value));
224 }
225 }
226
227 SetInitialSize(size);
228 Move(pos);
229
230 // have to disable this window to avoid interfering it with message
231 // processing to the text and the button... but pretend it is enabled to
232 // make IsEnabled() return true
233 wxControl::Enable(false); // don't use non virtual Disable() here!
234 m_isEnabled = true;
235
236 // we don't even need to show this window itself - and not doing it avoids
237 // that it overwrites the text control
238 wxControl::Show(false);
239 m_isShown = true;
240 return true;
241 }
242
243 wxSpinCtrlGenericBase::~wxSpinCtrlGenericBase()
244 {
245 // delete the controls now, don't leave them alive even though they would
246 // still be eventually deleted by our parent - but it will be too late, the
247 // user code expects them to be gone now
248
249 if (m_textCtrl)
250 {
251 // null this since MSW sends KILL_FOCUS on deletion, see ~wxSpinCtrlTextGeneric
252 wxDynamicCast(m_textCtrl, wxSpinCtrlTextGeneric)->m_spin = NULL;
253
254 wxSpinCtrlTextGeneric *text = (wxSpinCtrlTextGeneric*)m_textCtrl;
255 m_textCtrl = NULL;
256 delete text;
257 }
258
259 wxDELETE(m_spinButton);
260 }
261
262 // ----------------------------------------------------------------------------
263 // geometry
264 // ----------------------------------------------------------------------------
265
266 wxSize wxSpinCtrlGenericBase::DoGetBestSize() const
267 {
268 wxSize sizeBtn = m_spinButton->GetBestSize(),
269 sizeText = m_textCtrl->GetBestSize();
270
271 return wxSize(sizeBtn.x + sizeText.x + MARGIN, sizeText.y);
272 }
273
274 void wxSpinCtrlGenericBase::DoMoveWindow(int x, int y, int width, int height)
275 {
276 wxControl::DoMoveWindow(x, y, width, height);
277
278 // position the subcontrols inside the client area
279 wxSize sizeBtn = m_spinButton->GetSize();
280
281 wxCoord wText = width - sizeBtn.x - MARGIN;
282 m_textCtrl->SetSize(x, y, wText, height);
283 m_spinButton->SetSize(x + wText + MARGIN, y, wxDefaultCoord, height);
284 }
285
286 // ----------------------------------------------------------------------------
287 // operations forwarded to the subcontrols
288 // ----------------------------------------------------------------------------
289
290 void wxSpinCtrlGenericBase::SetFocus()
291 {
292 if ( m_textCtrl )
293 m_textCtrl->SetFocus();
294 }
295
296 #ifdef __WXMSW__
297
298 void wxSpinCtrlGenericBase::DoEnable(bool enable)
299 {
300 // We never enable this control itself, it must stay disabled to avoid
301 // interfering with the siblings event handling (see e.g. #12045 for the
302 // kind of problems which arise otherwise).
303 if ( !enable )
304 wxSpinCtrlBase::DoEnable(enable);
305 }
306
307 #endif // __WXMSW__
308
309 bool wxSpinCtrlGenericBase::Enable(bool enable)
310 {
311 if ( !wxSpinCtrlBase::Enable(enable) )
312 return false;
313
314 m_spinButton->Enable(enable);
315 m_textCtrl->Enable(enable);
316
317 return true;
318 }
319
320 bool wxSpinCtrlGenericBase::Show(bool show)
321 {
322 if ( !wxControl::Show(show) )
323 return false;
324
325 // under GTK Show() is called the first time before we are fully
326 // constructed
327 if ( m_spinButton )
328 {
329 m_spinButton->Show(show);
330 m_textCtrl->Show(show);
331 }
332
333 return true;
334 }
335
336 #if wxUSE_TOOLTIPS
337 void wxSpinCtrlGenericBase::DoSetToolTip(wxToolTip *tip)
338 {
339 // Notice that we must check for the subcontrols not being NULL (as they
340 // could be if we were created with the default ctor and this is called
341 // before Create() for some reason) and that we can't call SetToolTip(tip)
342 // because this would take ownership of the wxToolTip object (twice).
343 if ( m_textCtrl )
344 {
345 if ( tip )
346 m_textCtrl->SetToolTip(tip->GetTip());
347 else
348 m_textCtrl->SetToolTip(NULL);
349 }
350
351 if ( m_spinButton )
352 {
353 if( tip )
354 m_spinButton->SetToolTip(tip->GetTip());
355 else
356 m_spinButton->SetToolTip(NULL);
357 }
358
359 wxWindowBase::DoSetToolTip(tip);
360 }
361 #endif // wxUSE_TOOLTIPS
362
363 // ----------------------------------------------------------------------------
364 // Handle sub controls events
365 // ----------------------------------------------------------------------------
366
367 BEGIN_EVENT_TABLE(wxSpinCtrlGenericBase, wxSpinCtrlBase)
368 EVT_CHAR(wxSpinCtrlGenericBase::OnTextChar)
369 EVT_KILL_FOCUS(wxSpinCtrlGenericBase::OnTextLostFocus)
370 END_EVENT_TABLE()
371
372 void wxSpinCtrlGenericBase::OnSpinButton(wxSpinEvent& event)
373 {
374 event.Skip();
375
376 // Sync the textctrl since the user expects that the button will modify
377 // what they see in the textctrl.
378 SyncSpinToText();
379
380 int spin_value = event.GetPosition();
381 double step = (event.GetEventType() == wxEVT_SCROLL_LINEUP) ? 1 : -1;
382
383 // Use the spinbutton's acceleration, if any, but not if wrapping around
384 if (((spin_value >= 0) && (m_spin_value >= 0)) || ((spin_value <= 0) && (m_spin_value <= 0)))
385 step *= abs(spin_value - m_spin_value);
386
387 double value = AdjustToFitInRange(m_value + step*m_increment);
388
389 // Ignore the edges when it wraps since the up/down event may be opposite
390 // They are in GTK and Mac
391 if (abs(spin_value - m_spin_value) > SPINCTRLBUT_MAX)
392 {
393 m_spin_value = spin_value;
394 return;
395 }
396
397 m_spin_value = spin_value;
398
399 if ( DoSetValue(value) )
400 DoSendEvent();
401 }
402
403 void wxSpinCtrlGenericBase::OnTextLostFocus(wxFocusEvent& event)
404 {
405 SyncSpinToText();
406 DoSendEvent();
407
408 event.Skip();
409 }
410
411 void wxSpinCtrlGenericBase::OnTextChar(wxKeyEvent& event)
412 {
413 if ( !HasFlag(wxSP_ARROW_KEYS) )
414 {
415 event.Skip();
416 return;
417 }
418
419 double value = m_value;
420 switch ( event.GetKeyCode() )
421 {
422 case WXK_UP :
423 value += m_increment;
424 break;
425
426 case WXK_DOWN :
427 value -= m_increment;
428 break;
429
430 case WXK_PAGEUP :
431 value += m_increment * 10.0;
432 break;
433
434 case WXK_PAGEDOWN :
435 value -= m_increment * 10.0;
436 break;
437
438 default:
439 event.Skip();
440 return;
441 }
442
443 value = AdjustToFitInRange(value);
444
445 SyncSpinToText();
446
447 if ( DoSetValue(value) )
448 DoSendEvent();
449 }
450
451 // ----------------------------------------------------------------------------
452 // Textctrl functions
453 // ----------------------------------------------------------------------------
454
455 bool wxSpinCtrlGenericBase::SyncSpinToText()
456 {
457 if ( !m_textCtrl || !m_textCtrl->IsModified() )
458 return false;
459
460 double textValue;
461 if ( DoTextToValue(m_textCtrl->GetValue(), &textValue) )
462 {
463 if (textValue > m_max)
464 textValue = m_max;
465 else if (textValue < m_min)
466 textValue = m_min;
467 }
468 else // text contents is not a valid number at all
469 {
470 // replace its contents with the last valid value
471 textValue = m_value;
472 }
473
474 // we must always set the value here, even if it's equal to m_value, as
475 // otherwise we could be left with an out of range value when leaving the
476 // text control and the current value is already m_max for example
477 return DoSetValue(textValue);
478 }
479
480 // ----------------------------------------------------------------------------
481 // changing value and range
482 // ----------------------------------------------------------------------------
483
484 void wxSpinCtrlGenericBase::SetValue(const wxString& text)
485 {
486 wxCHECK_RET( m_textCtrl, wxT("invalid call to wxSpinCtrl::SetValue") );
487
488 double val;
489 if ( DoTextToValue(text, &val) && InRange(val) )
490 {
491 DoSetValue(val);
492 }
493 else // not a number at all or out of range
494 {
495 m_textCtrl->SetValue(text);
496 m_textCtrl->SetSelection(0, -1);
497 m_textCtrl->SetInsertionPointEnd();
498 }
499 }
500
501 bool wxSpinCtrlGenericBase::DoSetValue(double val)
502 {
503 wxCHECK_MSG( m_textCtrl, false, wxT("invalid call to wxSpinCtrl::SetValue") );
504
505 if (!InRange(val))
506 return false;
507
508 if ( m_snap_to_ticks && (m_increment != 0) )
509 {
510 double snap_value = val / m_increment;
511
512 if (wxFinite(snap_value)) // FIXME what to do about a failure?
513 {
514 if ((snap_value - floor(snap_value)) < (ceil(snap_value) - snap_value))
515 val = floor(snap_value) * m_increment;
516 else
517 val = ceil(snap_value) * m_increment;
518 }
519 }
520
521 wxString str(DoValueToText(val));
522
523 if ((val != m_value) || (str != m_textCtrl->GetValue()))
524 {
525 if ( !DoTextToValue(str, &m_value ) ) // wysiwyg for textctrl
526 m_value = val;
527 m_textCtrl->SetValue( str );
528 m_textCtrl->SetInsertionPointEnd();
529 m_textCtrl->DiscardEdits();
530 return true;
531 }
532
533 return false;
534 }
535
536 double wxSpinCtrlGenericBase::AdjustToFitInRange(double value) const
537 {
538 if (value < m_min)
539 value = HasFlag(wxSP_WRAP) ? m_max : m_min;
540 if (value > m_max)
541 value = HasFlag(wxSP_WRAP) ? m_min : m_max;
542
543 return value;
544 }
545
546 void wxSpinCtrlGenericBase::DoSetRange(double min, double max)
547 {
548 m_min = min;
549 m_max = max;
550 }
551
552 void wxSpinCtrlGenericBase::DoSetIncrement(double inc)
553 {
554 m_increment = inc;
555 }
556
557 void wxSpinCtrlGenericBase::SetSnapToTicks(bool snap_to_ticks)
558 {
559 m_snap_to_ticks = snap_to_ticks;
560 DoSetValue(m_value);
561 }
562
563 void wxSpinCtrlGenericBase::SetSelection(long from, long to)
564 {
565 wxCHECK_RET( m_textCtrl, wxT("invalid call to wxSpinCtrl::SetSelection") );
566
567 m_textCtrl->SetSelection(from, to);
568 }
569
570 #ifndef wxHAS_NATIVE_SPINCTRL
571
572 //-----------------------------------------------------------------------------
573 // wxSpinCtrl
574 //-----------------------------------------------------------------------------
575
576 bool wxSpinCtrl::SetBase(int base)
577 {
578 // Currently we only support base 10 and 16. We could add support for base
579 // 8 quite easily but wxMSW doesn't support it natively so don't bother.
580 if ( base != 10 && base != 16 )
581 return false;
582
583 if ( base == m_base )
584 return true;
585
586 // Update the current control contents to show in the new base: be careful
587 // to call DoTextToValue() before changing the base...
588 double val;
589 const bool hasValidVal = DoTextToValue(m_textCtrl->GetValue(), &val);
590
591 m_base = base;
592
593 // ... but DoValueToText() after doing it.
594 if ( hasValidVal )
595 m_textCtrl->SetValue(DoValueToText(val));
596
597 return true;
598 }
599
600 void wxSpinCtrl::DoSendEvent()
601 {
602 wxSpinEvent event( wxEVT_COMMAND_SPINCTRL_UPDATED, GetId());
603 event.SetEventObject( this );
604 event.SetPosition((int)(m_value + 0.5)); // FIXME should be SetValue
605 event.SetString(m_textCtrl->GetValue());
606 GetEventHandler()->ProcessEvent( event );
607 }
608
609 bool wxSpinCtrl::DoTextToValue(const wxString& text, double *val)
610 {
611 long lval;
612 if ( !text.ToLong(&lval, GetBase()) )
613 return false;
614
615 *val = static_cast<double>(lval);
616
617 return true;
618 }
619
620 wxString wxSpinCtrl::DoValueToText(double val)
621 {
622 switch ( GetBase() )
623 {
624 case 16:
625 return wxPrivate::wxSpinCtrlFormatAsHex(static_cast<long>(val),
626 GetMax());
627
628 default:
629 wxFAIL_MSG( wxS("Unsupported spin control base") );
630 // Fall through
631
632 case 10:
633 return wxString::Format("%ld", static_cast<long>(val));
634 }
635 }
636
637 #endif // !wxHAS_NATIVE_SPINCTRL
638
639 //-----------------------------------------------------------------------------
640 // wxSpinCtrlDouble
641 //-----------------------------------------------------------------------------
642
643 IMPLEMENT_DYNAMIC_CLASS(wxSpinCtrlDouble, wxSpinCtrlGenericBase)
644
645 void wxSpinCtrlDouble::DoSendEvent()
646 {
647 wxSpinDoubleEvent event( wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, GetId());
648 event.SetEventObject( this );
649 event.SetValue(m_value);
650 event.SetString(m_textCtrl->GetValue());
651 GetEventHandler()->ProcessEvent( event );
652 }
653
654 bool wxSpinCtrlDouble::DoTextToValue(const wxString& text, double *val)
655 {
656 return text.ToDouble(val);
657 }
658
659 wxString wxSpinCtrlDouble::DoValueToText(double val)
660 {
661 return wxString::Format(m_format, val);
662 }
663
664 void wxSpinCtrlDouble::SetDigits(unsigned digits)
665 {
666 wxCHECK_RET( digits <= 20, "too many digits for wxSpinCtrlDouble" );
667
668 if ( digits == m_digits )
669 return;
670
671 m_digits = digits;
672
673 m_format.Printf(wxT("%%0.%ulf"), digits);
674
675 DoSetValue(m_value);
676 }
677
678 #endif // wxUSE_SPINBTN
679
680 #endif // !wxPort-with-native-spinctrl
681
682 #endif // wxUSE_SPINCTRL