1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        spinctld.h
3 // Author:      John Labenski
4 // Created:     11/05/02
5 // Copyright:   John Labenski, 2002
6 // License:     wxWidgets
7 /////////////////////////////////////////////////////////////////////////////
8 
9 // For compilers that support precompilation, includes "wx/wx.h".
10 #include "wx/wxprec.h"
11 
12 #ifdef __BORLANDC__
13     #pragma hdrstop
14 #endif
15 
16 #ifndef WX_PRECOMP
17     #include "wx/valtext.h"     // for wxTextValidator
18     #include "wx/textctrl.h"
19 #endif // WX_PRECOMP
20 
21 #include "wx/wxthings/spinctld.h"
22 #include <cmath>
23 
24 #if wxCHECK_VERSION(2,5,0)
25     #include "wx/math.h"
26 #else
27     #if defined(__VISUALC__) || defined(__BORLANDC__) || defined(__WATCOMC__)
28         #include <cfloat>
29         #define wxFinite(x) _finite(x)
30     #elif defined(__GNUG__)||defined(__GNUWIN32__)||defined(__DJGPP__)|| \
31           defined(__SGI_CC__)||defined(__SUNCC__)||defined(__XLC__)|| \
32           defined(__HPUX__)||defined(__MWERKS__)
33         #define wxFinite(x) finite(x)
34     #else
35         #define wxFinite(x) ((x) == (x))
36     #endif
37 #endif // wxCHECK_VERSION(2,5,0)
38 
39 #ifdef wxFinite
40 #undef wxFinite
41 #define wxFinite(x) std::isfinite(x)
42 #endif  // wxFinite
43 
44 
45 
46 // NOTES : if the textctrl is focused and the program is ending, a killfocus
47 //         event is sent in MSW, this is why m_textCtrl is set to NULL in it's
48 //         destructor and there's so many checks for it not being NULL
49 
50 //----------------------------------------------------------------------------
51 // wxSpinCtrlDbl
52 //----------------------------------------------------------------------------
53 
54 // the textctrl used for the wxSpinCtrlDbl, needed for keypresses
55 class wxSpinCtrlDblTextCtrl : public wxTextCtrl
56 {
57 public:
58     wxSpinCtrlDblTextCtrl( wxWindow *parent, wxWindowID id,
59                            const wxString &value = wxEmptyString,
60                            const wxPoint &pos = wxDefaultPosition,
61                            const wxSize &size = wxDefaultSize,
62                            long style = 0,
63                            const wxValidator& validator = wxDefaultValidator,
64                            const wxString &name = wxTextCtrlNameStr);
65 
66     // MSW sends extra kill focus event
~wxSpinCtrlDblTextCtrl()67     virtual ~wxSpinCtrlDblTextCtrl()
68     {
69         if (m_parent) m_parent->m_textCtrl = NULL;
70         m_parent = NULL;
71     }
72 
73     wxSpinCtrlDbl *m_parent;
74 
75     void OnChar( wxKeyEvent &event );         // pass chars to wxSpinCtrlDbl
76     void OnKillFocus( wxFocusEvent &event );  // sync the spin to textctrl
77 
78 private:
79     DECLARE_EVENT_TABLE()
80 };
81 
BEGIN_EVENT_TABLE(wxSpinCtrlDblTextCtrl,wxTextCtrl)82 BEGIN_EVENT_TABLE(wxSpinCtrlDblTextCtrl,wxTextCtrl)
83 //  EVT_TEXT_ENTER( wxID_ANY, wxSpinCtrlDblTextCtrl::OnTextEnter ) // get them from spinctrldbl
84 //  EVT_TEXT( wxID_ANY, wxSpinCtrlDblTextCtrl::OnTextUpdate )      // get them from spinctrldbl
85     EVT_CHAR( wxSpinCtrlDblTextCtrl::OnChar )
86     EVT_KILL_FOCUS( wxSpinCtrlDblTextCtrl::OnKillFocus )
87 END_EVENT_TABLE()
88 
89 wxSpinCtrlDblTextCtrl::wxSpinCtrlDblTextCtrl( wxWindow *parent, wxWindowID id,
90                                               const wxString &value,
91                                               const wxPoint &pos, const wxSize &size,
92                                               long style,
93                                               const wxValidator& validator,
94                                               const wxString &name)
95                        :wxTextCtrl( parent, id, value, pos, size, style,
96                                     validator, name)
97 {
98     m_parent = (wxSpinCtrlDbl*)parent;
99 }
100 
OnChar(wxKeyEvent & event)101 void wxSpinCtrlDblTextCtrl::OnChar( wxKeyEvent &event )
102 {
103     if (m_parent) m_parent->OnChar( event );
104 }
105 
OnKillFocus(wxFocusEvent & event)106 void wxSpinCtrlDblTextCtrl::OnKillFocus( wxFocusEvent &event )
107 {
108     if (m_parent) m_parent->SyncSpinToText(true);
109     event.Skip();
110 }
111 
112 //----------------------------------------------------------------------------
113 // wxSpinCtrlDbl
114 //----------------------------------------------------------------------------
115 
IMPLEMENT_DYNAMIC_CLASS(wxSpinCtrlDbl,wxControl)116 IMPLEMENT_DYNAMIC_CLASS( wxSpinCtrlDbl, wxControl )
117 
118 BEGIN_EVENT_TABLE(wxSpinCtrlDbl,wxControl)
119     EVT_SPIN_UP   ( wxID_ANY, wxSpinCtrlDbl::OnSpinUp    )
120     EVT_SPIN_DOWN ( wxID_ANY, wxSpinCtrlDbl::OnSpinDown  )
121     EVT_TEXT_ENTER( wxID_ANY, wxSpinCtrlDbl::OnTextEnter )
122     //EVT_TEXT      ( wxID_ANY, wxSpinCtrlDbl::OnText      )
123     EVT_SET_FOCUS ( wxSpinCtrlDbl::OnFocus     )
124     EVT_KILL_FOCUS( wxSpinCtrlDbl::OnKillFocus )
125 END_EVENT_TABLE()
126 
127 void wxSpinCtrlDbl::Init()
128 {
129     m_min = 0;
130     m_max = 100;
131     m_value = 0;
132     m_default_value = 0;
133     m_increment = 1;
134     m_digits = wxSPINCTRLDBL_AUTODIGITS;
135     m_snap_ticks = false;
136     m_spinButton = NULL;
137     m_textCtrl = NULL;
138 }
139 
Create(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,long style,double min,double max,double initial,double increment,int digits,const wxString & name)140 bool wxSpinCtrlDbl::Create( wxWindow *parent, wxWindowID id,
141                             const wxString& value,
142                             const wxPoint& pos, const wxSize& size,
143                             long style,
144                             double min, double max,
145                             double initial,
146                             double increment, int digits,
147                             const wxString& name)
148 {
149     if (!wxControl::Create(parent, id, pos, size, style|wxNO_BORDER,
150                            wxDefaultValidator, name))
151         return false;
152 
153     wxControl::SetLabel(name);
154     wxControl::SetBackgroundColour(parent->GetBackgroundColour());
155     wxControl::SetForegroundColour(parent->GetForegroundColour());
156 
157     int width = size.GetWidth(), height = size.GetHeight();
158 
159     wxSize best_size( DoGetBestSize() );
160     if (width  == -1) width  = best_size.GetWidth();
161     if (height == -1) height = best_size.GetHeight();
162 
163     // Create a validator for numbers, +-, and eE for exponential
164     wxTextValidator validator(wxFILTER_INCLUDE_CHAR_LIST);
165 
166 #if wxCHECK_VERSION(2, 5, 4)
167     wxArrayString list;
168 
169     wxString valid_chars(wxT(" 0123456789+-.eE"));
170     size_t len = valid_chars.Length();
171     for (size_t i=0; i<len; i++)
172         list.Add(wxString(valid_chars.GetChar(i)));
173 
174     validator.SetIncludes(list);
175 #else
176     wxStringList list;
177 
178     wxString valid_chars(wxT(" 0123456789+-.eE"));
179     size_t len = valid_chars.Length();
180     for (size_t i=0; i<len; i++)
181         list.Add(wxString(valid_chars.GetChar(i)));
182 
183     validator.SetIncludeList(list);
184 #endif // wxCHECK_VER(2, 5, 4)
185 
186     m_spinButton = new wxSpinButton( this, id, wxPoint(0,0), wxSize(-1, height),
187                                      wxSP_ARROW_KEYS|wxSP_VERTICAL|wxSP_WRAP);
188     m_textCtrl = new wxSpinCtrlDblTextCtrl( this, id, value,
189                       wxPoint(0,0),
190                       wxSize(width-m_spinButton->GetSize().GetWidth(), height),
191                       wxTE_NOHIDESEL|wxTE_PROCESS_ENTER, validator);
192 
193     DoSetSize( pos.x, pos.y, width, height );
194 #if wxCHECK_VERSION(2,7,0)
195     SetInitialSize(wxSize(width, height));
196 #else
197     SetBestSize(wxSize(width, height));
198 #endif
199 
200     m_min = min;
201     m_max = max;
202     m_value = initial;
203     m_default_value = initial;
204     m_increment = increment;
205     SetDigits( digits );
206 
207     // set the value here without generating an event
208     if (!value.IsEmpty())
209         m_textCtrl->SetValue(value);
210     else
211         m_textCtrl->SetValue(wxString::Format(m_textFormat.c_str(), initial));
212 
213     return true;
214 }
215 
~wxSpinCtrlDbl()216 wxSpinCtrlDbl::~wxSpinCtrlDbl()
217 {
218     if (m_textCtrl) // null this since MSW sends KILL_FOCUS on deletion
219     {
220         m_textCtrl->m_parent = NULL;
221 
222         wxSpinCtrlDblTextCtrl *text = m_textCtrl;
223         m_textCtrl = NULL;
224         delete text;
225     }
226 
227     delete m_spinButton;
228     m_spinButton = NULL;
229 }
230 
231 #define wxSPINCTRLDBL_SPIN_WIDTH  15
232 #define wxSPINCTRLDBL_SPIN_HEIGHT 22
233 
DoSetSize(int x,int y,int width,int height,int sizeFlags)234 void wxSpinCtrlDbl::DoSetSize(int x, int y, int width, int height, int sizeFlags)
235 {
236     //wxPrintf(wxT("DoSetSize %d, %d %d %d %d %d\n"), GetId(), x, y, width, height, sizeFlags);
237 
238     wxSize bestSize( DoGetBestSize() );
239     if (width < 0)  width  = bestSize.GetWidth();
240     if (height < 0) height = bestSize.GetHeight();
241 
242     wxWindow::DoSetSize(x, y, width, height, sizeFlags);
243 
244     int spinwidth  = wxSPINCTRLDBL_SPIN_WIDTH;
245     int spinheight = wxSPINCTRLDBL_SPIN_HEIGHT;
246     if (m_spinButton)
247         m_spinButton->GetSize( &spinwidth, &spinheight );
248 
249 #ifdef __WIN95__   // humm... these used to be different
250     if (m_textCtrl)   m_textCtrl->SetSize( 0, 0, width - spinwidth, height );
251     if (m_spinButton) m_spinButton->SetSize( width-spinwidth-2, 0, -1, height );
252     //m_textCtrl->SetSize( -3, -3, width - spinwidth, height );   // old wxWin < 2.3.2
253     //m_spinButton->SetSize( width-spinwidth-4, -3, -1, height-1 );
254 #else
255     if (m_textCtrl)   m_textCtrl->SetSize( 0, 0, width - spinwidth, height );
256     if (m_spinButton) m_spinButton->SetSize( width-spinwidth, 0, -1, height );
257 #endif
258 }
259 
260 static wxSize s_spinctrl_bestSize(-999,-999);
261 
DoGetBestSize() const262 wxSize wxSpinCtrlDbl::DoGetBestSize() const
263 {
264     //wxPrintf(wxT("GetBestSize %d\n"), GetId());
265     if (s_spinctrl_bestSize.x == -999)
266     {
267         wxSpinCtrl spin((wxWindow*)this, wxID_ANY);
268         s_spinctrl_bestSize = spin.GetBestSize();
269         // oops something went wrong, set to reasonable value
270         if (s_spinctrl_bestSize.GetWidth()  < 20)
271             s_spinctrl_bestSize.SetWidth(95);
272         if (s_spinctrl_bestSize.GetHeight() < 10)
273             s_spinctrl_bestSize.SetHeight(wxSPINCTRLDBL_SPIN_HEIGHT);
274     }
275 
276     return s_spinctrl_bestSize;
277 }
278 
DoSendEvent()279 void wxSpinCtrlDbl::DoSendEvent()
280 {
281     wxCommandEvent event( wxEVT_COMMAND_SPINCTRL_UPDATED, GetId() );
282     event.SetEventObject( this );
283     event.SetInt( (int)(m_value+0.5) );
284     if (m_textCtrl) event.SetString( m_textCtrl->GetValue() );
285     GetEventHandler()->ProcessEvent( event );
286 }
287 
OnSpinUp(wxSpinEvent & WXUNUSED (event))288 void wxSpinCtrlDbl::OnSpinUp( wxSpinEvent &WXUNUSED(event) )
289 {
290     if (m_textCtrl && m_textCtrl->IsModified() )
291         SyncSpinToText(false);
292 
293     if ( InRange(m_value + m_increment) )
294     {
295         m_value += m_increment;
296         SetValue( m_value );
297         DoSendEvent();
298     }
299 }
300 
OnSpinDown(wxSpinEvent & WXUNUSED (event))301 void wxSpinCtrlDbl::OnSpinDown( wxSpinEvent &WXUNUSED(event) )
302 {
303     if (m_textCtrl && m_textCtrl->IsModified() )
304         SyncSpinToText(false);
305 
306     if ( InRange(m_value - m_increment) )
307     {
308         m_value -= m_increment;
309         SetValue( m_value );
310         DoSendEvent();
311     }
312 }
313 
OnTextEnter(wxCommandEvent & event)314 void wxSpinCtrlDbl::OnTextEnter( wxCommandEvent &event )
315 {
316     SyncSpinToText(true);
317     event.Skip();
318 }
319 
OnText(wxCommandEvent & event)320 void wxSpinCtrlDbl::OnText( wxCommandEvent &event )
321 {
322     //wxPrintf(wxT("Text '%s'\n"), event.GetString()); fflush(stdout);
323     event.Skip();
324 }
325 
OnChar(wxKeyEvent & event)326 void wxSpinCtrlDbl::OnChar( wxKeyEvent &event )
327 {
328     double modifier = 1.0;
329     if ( event.m_shiftDown   ) modifier  = 2.0;
330     if ( event.m_controlDown ) modifier *= 10.0;
331     if ( event.m_altDown     ) modifier *= 100.0;
332 
333     switch ( event.GetKeyCode() )
334     {
335         case WXK_UP :
336         {
337             if (m_textCtrl && m_textCtrl->IsModified()) SyncSpinToText(false);
338             SetValue( m_value + m_increment * modifier );
339             DoSendEvent();
340             break;
341         }
342         case WXK_DOWN :
343         {
344             if (m_textCtrl && m_textCtrl->IsModified()) SyncSpinToText(false);
345             SetValue( m_value - m_increment * modifier );
346             DoSendEvent();
347             break;
348         }
349         case WXK_PAGEUP :
350         {
351             if (m_textCtrl && m_textCtrl->IsModified()) SyncSpinToText(false);
352             SetValue( m_value + m_increment * 10.0 * modifier );
353             DoSendEvent();
354             break;
355         }
356         case WXK_PAGEDOWN :
357         {
358             if (m_textCtrl && m_textCtrl->IsModified()) SyncSpinToText(false);
359             SetValue( m_value - m_increment * 10.0 * modifier );
360             DoSendEvent();
361             break;
362         }
363         case WXK_SPACE :
364         {
365             SetValue(m_value);
366             event.Skip(false);
367             break;
368         }
369         case WXK_ESCAPE :
370         {
371             SetDefaultValue();
372             DoSendEvent();
373             break;
374         }
375         case WXK_TAB :
376         {
377             wxNavigationKeyEvent new_event;
378             new_event.SetEventObject( GetParent() );
379             new_event.SetDirection( !event.ShiftDown() );
380             // CTRL-TAB changes the (parent) window, i.e. switch notebook page
381             new_event.SetWindowChange( event.ControlDown() );
382             new_event.SetCurrentFocus( this );
383             GetParent()->GetEventHandler()->ProcessEvent( new_event );
384             break;
385         }
386         default : event.Skip(); break;
387     }
388 }
389 
SetValue(double value)390 void wxSpinCtrlDbl::SetValue( double value )
391 {
392     if (!m_textCtrl || !InRange(value))
393         return;
394 
395     if ( m_snap_ticks && (m_increment != 0) )
396     {
397         double snap_value = (value - m_default_value) / m_increment;
398 
399         if (wxFinite(snap_value)) // FIXME what to do about a failure?
400         {
401             if (snap_value - floor(snap_value) < ceil(snap_value) - snap_value)
402                 value = m_default_value + floor(snap_value) * m_increment;
403             else
404                 value = m_default_value + ceil(snap_value) * m_increment;
405         }
406     }
407 
408     wxString str(wxString::Format(m_textFormat.c_str(), value));
409 
410     if ((value != m_value) || (str != m_textCtrl->GetValue()))
411     {
412         m_textCtrl->SetValue( str );
413         m_textCtrl->DiscardEdits();
414         m_value = value;
415         str.ToDouble( &m_value );    // wysiwyg for textctrl
416     }
417 }
418 
SetValue(const wxString & text,bool force)419 void wxSpinCtrlDbl::SetValue( const wxString& text, bool force )
420 {
421     if (!m_textCtrl) return;
422 
423     double value;
424     if ( text.ToDouble(&value) )
425         SetValue( value );
426     else if (force)
427     {
428         m_textCtrl->SetValue( text );
429         m_textCtrl->DiscardEdits();
430     }
431 }
432 
SetRange(double min_val,double max_val)433 void wxSpinCtrlDbl::SetRange( double min_val, double max_val )
434 {
435     //wxCHECK_RET(max_val > min_val, wxT("invalid spinctrl range"));
436     m_min = min_val;
437     m_max = max_val;
438 
439     if (HasRange())
440     {
441         if (m_value > m_max)
442             SetValue(m_max);
443         else if (m_value < m_min)
444             SetValue(m_min);
445     }
446 }
447 
SetIncrement(double increment)448 void wxSpinCtrlDbl::SetIncrement( double increment )
449 {
450     m_increment = increment;
451     SetValue(m_value);
452 }
453 
SetDigits(int digits,formatType fmt)454 void wxSpinCtrlDbl::SetDigits( int digits, formatType fmt )
455 {
456     wxCHECK_RET(digits >= -1, wxT("invalid spinctrl format"));
457 
458     if ((digits == wxSPINCTRLDBL_AUTODIGITS) && (fmt != lg_fmt))
459     {
460         wxString wxstr;
461         int lastplace = -1, extra_digits = 0;
462         if (fmt == le_fmt)
463         {
464             wxstr.Printf(wxT("%le"), m_increment );
465             wxstr.LowerCase();
466             lastplace = wxstr.Find(wxT('e')) - 2;
467             long places;
468             if (wxstr.AfterFirst(wxT('e')).ToLong(&places))
469                 extra_digits = int(labs(places));
470         }
471         else if (fmt == lf_fmt)
472         {
473             wxstr.Printf(wxT("%lf"), m_increment );
474             lastplace = wxstr.Len()-1;
475         }
476 
477         int decimalplace = wxstr.Find(wxT('.'));
478 
479         int i = 0;
480 
481         for ( i=lastplace; i>decimalplace; i-- )
482         {
483             if ( wxstr.GetChar(i) != wxT('0') )
484             {
485                 m_digits = extra_digits + i-decimalplace;
486                 switch (fmt)
487                 {
488                     case le_fmt : m_textFormat.Printf(wxT("%%.%dle"), m_digits ); break;
489                     case lf_fmt :
490                     default     : m_textFormat.Printf(wxT("%%.%dlg"), m_digits ); break;
491                 }
492 
493                 SetValue(m_value);
494                 return;
495             }
496         }
497 
498         m_digits = 0;  // no digits, I guess
499     }
500     else
501         m_digits = digits;
502 
503     switch (fmt)
504     {
505         case le_fmt : m_textFormat.Printf(wxT("%%.%dle"), m_digits ); break;
506         case lg_fmt :
507         {
508             if (m_digits == -1)
509                 m_textFormat.Printf(wxT("%%lg") );
510             else
511                 m_textFormat.Printf(wxT("%%.%dlg"), m_digits );
512             break;
513         }
514         case lf_fmt :
515         default     : m_textFormat.Printf(wxT("%%.%dlf"), m_digits ); break;
516     }
517 
518     SetValue(m_value);
519 }
520 
SetFormat(const wxString & format)521 void wxSpinCtrlDbl::SetFormat( const wxString& format )
522 {
523     wxString wxstr;
524     if ( wxstr.Printf(format.c_str(), 123456.123456) > 0 )
525         m_textFormat = format;
526 
527     SetValue(m_value);
528 }
529 
SetDefaultValue(double default_value)530 void wxSpinCtrlDbl::SetDefaultValue( double default_value )
531 {
532     if ( InRange(default_value) )
533     {
534         m_default_value = default_value;
535         SetDefaultValue();
536     }
537 }
538 
SetSnapToTicks(bool forceTicks)539 void wxSpinCtrlDbl::SetSnapToTicks(bool forceTicks)
540 {
541     if (m_snap_ticks != forceTicks)
542     {
543         m_snap_ticks = forceTicks;
544         SetValue( m_value );
545     }
546 }
547 
OnFocus(wxFocusEvent & event)548 void wxSpinCtrlDbl::OnFocus( wxFocusEvent &event )
549 {
550     if (m_textCtrl)
551         m_textCtrl->SetFocus(); // this is to pass TAB navigation
552 
553     event.Skip();
554 }
555 
OnKillFocus(wxFocusEvent & event)556 void wxSpinCtrlDbl::OnKillFocus( wxFocusEvent &event )
557 {
558     SyncSpinToText(true);
559     event.Skip();
560 }
561 
SyncSpinToText(bool send_event,bool force_valid)562 void wxSpinCtrlDbl::SyncSpinToText(bool send_event, bool force_valid)
563 {
564     if (!m_textCtrl)
565         return;
566 
567     double txt_value;
568     if ( m_textCtrl->GetValue().ToDouble( &txt_value ) )
569     {
570         if ( force_valid || !HasRange() || InRange(txt_value) )
571         {
572             if (force_valid && HasRange())
573             {
574                 if (txt_value > GetMax())
575                     txt_value = GetMax();
576                 else if (txt_value < GetMin())
577                     txt_value = GetMin();
578             }
579 
580             if (m_value != txt_value)
581             {
582                 SetValue( txt_value );
583                 if (send_event) DoSendEvent();
584             }
585         }
586     }
587     else if (force_valid)
588     {
589         // textctrl is out of sync, discard and reset
590         SetValue(GetValue());
591     }
592 }
593 
SetFont(const wxFont & font)594 bool wxSpinCtrlDbl::SetFont( const wxFont &font )
595 {
596     if (!m_textCtrl) return false;
597     return m_textCtrl->SetFont( font );
598 }
GetFont() const599 wxFont wxSpinCtrlDbl::GetFont() const
600 {
601     if (!m_textCtrl) return GetFont();
602     return m_textCtrl->GetFont();
603 }
604 
SetBackgroundColour(const wxColour & colour)605 bool wxSpinCtrlDbl::SetBackgroundColour(const wxColour& colour)
606 {
607     if (!m_textCtrl) return wxControl::SetBackgroundColour(colour);
608     bool ret = false;
609     ret = m_textCtrl->SetBackgroundColour(colour);
610     m_textCtrl->Refresh(); // FIXME is this necessary in GTK/OSX
611     return ret;
612 }
GetBackgroundColour() const613 wxColour wxSpinCtrlDbl::GetBackgroundColour() const
614 {
615     if (!m_textCtrl) return wxControl::GetBackgroundColour();
616     return m_textCtrl->GetBackgroundColour();
617 }
618 
SetForegroundColour(const wxColour & colour)619 bool wxSpinCtrlDbl::SetForegroundColour(const wxColour& colour)
620 {
621     if (!m_textCtrl) return wxControl::SetForegroundColour(colour);
622     bool ret = false;
623     ret = m_textCtrl->SetForegroundColour(colour);
624     m_textCtrl->Refresh();
625     return ret;
626 }
GetForegroundColour() const627 wxColour wxSpinCtrlDbl::GetForegroundColour() const
628 {
629     if (!m_textCtrl) return wxControl::GetForegroundColour();
630     return m_textCtrl->GetForegroundColour();
631 }
632