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