Corrected border drawing to avoid clipping
[wxWidgets.git] / src / common / valnum.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/valnum.cpp
3 // Purpose: Numeric validator classes implementation
4 // Author: Vadim Zeitlin based on the submission of Fulvio Senore
5 // Created: 2010-11-06
6 // Copyright: (c) 2010 wxWidgets team
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // ============================================================================
11 // Declarations
12 // ============================================================================
13
14 // ----------------------------------------------------------------------------
15 // headers
16 // ----------------------------------------------------------------------------
17
18 // For compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20
21 #ifdef __BORLANDC__
22 #pragma hdrstop
23 #endif
24
25 #if wxUSE_VALIDATORS && wxUSE_TEXTCTRL
26
27 #ifndef WX_PRECOMP
28 #include "wx/textctrl.h"
29 #include "wx/combobox.h"
30 #endif
31
32 #include "wx/valnum.h"
33 #include "wx/numformatter.h"
34
35 // ============================================================================
36 // wxNumValidatorBase implementation
37 // ============================================================================
38
39 BEGIN_EVENT_TABLE(wxNumValidatorBase, wxValidator)
40 EVT_CHAR(wxNumValidatorBase::OnChar)
41 EVT_KILL_FOCUS(wxNumValidatorBase::OnKillFocus)
42 END_EVENT_TABLE()
43
44 int wxNumValidatorBase::GetFormatFlags() const
45 {
46 int flags = wxNumberFormatter::Style_None;
47 if ( m_style & wxNUM_VAL_THOUSANDS_SEPARATOR )
48 flags |= wxNumberFormatter::Style_WithThousandsSep;
49 if ( m_style & wxNUM_VAL_NO_TRAILING_ZEROES )
50 flags |= wxNumberFormatter::Style_NoTrailingZeroes;
51
52 return flags;
53 }
54
55 wxTextEntry *wxNumValidatorBase::GetTextEntry() const
56 {
57 #if wxUSE_TEXTCTRL
58 if ( wxTextCtrl *text = wxDynamicCast(m_validatorWindow, wxTextCtrl) )
59 return text;
60 #endif // wxUSE_TEXTCTRL
61
62 #if wxUSE_COMBOBOX
63 if ( wxComboBox *combo = wxDynamicCast(m_validatorWindow, wxComboBox) )
64 return combo;
65 #endif // wxUSE_COMBOBOX
66
67 wxFAIL_MSG("Can only be used with wxTextCtrl or wxComboBox");
68
69 return NULL;
70 }
71
72 void
73 wxNumValidatorBase::GetCurrentValueAndInsertionPoint(wxString& val,
74 int& pos) const
75 {
76 wxTextEntry * const control = GetTextEntry();
77 if ( !control )
78 return;
79
80 val = control->GetValue();
81 pos = control->GetInsertionPoint();
82
83 long selFrom, selTo;
84 control->GetSelection(&selFrom, &selTo);
85
86 const long selLen = selTo - selFrom;
87 if ( selLen )
88 {
89 // Remove selected text because pressing a key would make it disappear.
90 val.erase(selFrom, selLen);
91
92 // And adjust the insertion point to have correct position in the new
93 // string.
94 if ( pos > selFrom )
95 {
96 if ( pos >= selTo )
97 pos -= selLen;
98 else
99 pos = selFrom;
100 }
101 }
102 }
103
104 bool wxNumValidatorBase::IsMinusOk(const wxString& val, int pos) const
105 {
106 // Minus is only ever accepted in the beginning of the string.
107 if ( pos != 0 )
108 return false;
109
110 // And then only if there is no existing minus sign there.
111 if ( !val.empty() && val[0] == '-' )
112 return false;
113
114 return true;
115 }
116
117 void wxNumValidatorBase::OnChar(wxKeyEvent& event)
118 {
119 // By default we just validate this key so don't prevent the normal
120 // handling from taking place.
121 event.Skip();
122
123 if ( !m_validatorWindow )
124 return;
125
126 #if wxUSE_UNICODE
127 const int ch = event.GetUnicodeKey();
128 if ( ch == WXK_NONE )
129 {
130 // It's a character without any Unicode equivalent at all, e.g. cursor
131 // arrow or function key, we never filter those.
132 return;
133 }
134 #else // !wxUSE_UNICODE
135 const int ch = event.GetKeyCode();
136 if ( ch > WXK_DELETE )
137 {
138 // Not a character neither.
139 return;
140 }
141 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
142
143 if ( ch < WXK_SPACE || ch == WXK_DELETE )
144 {
145 // Allow ASCII control characters and Delete.
146 return;
147 }
148
149 // Check if this character is allowed in the current state.
150 wxString val;
151 int pos;
152 GetCurrentValueAndInsertionPoint(val, pos);
153
154 if ( !IsCharOk(val, pos, ch) )
155 {
156 if ( !wxValidator::IsSilent() )
157 wxBell();
158
159 // Do not skip the event in this case, stop handling it here.
160 event.Skip(false);
161 }
162 }
163
164 void wxNumValidatorBase::OnKillFocus(wxFocusEvent& event)
165 {
166 wxTextEntry * const control = GetTextEntry();
167 if ( !control )
168 return;
169
170 // When we change the control value below, its "modified" status is reset
171 // so we need to explicitly keep it marked as modified if it was so in the
172 // first place.
173 //
174 // Notice that only wxTextCtrl (and not wxTextEntry) has
175 // IsModified()/MarkDirty() methods hence the need for dynamic cast.
176 wxTextCtrl * const text = wxDynamicCast(m_validatorWindow, wxTextCtrl);
177 const bool wasModified = text ? text->IsModified() : false;
178
179 control->ChangeValue(NormalizeString(control->GetValue()));
180
181 if ( wasModified )
182 text->MarkDirty();
183
184 event.Skip();
185 }
186
187 // ============================================================================
188 // wxIntegerValidatorBase implementation
189 // ============================================================================
190
191 wxString wxIntegerValidatorBase::ToString(LongestValueType value) const
192 {
193 return wxNumberFormatter::ToString(value, GetFormatFlags());
194 }
195
196 bool
197 wxIntegerValidatorBase::FromString(const wxString& s, LongestValueType *value)
198 {
199 return wxNumberFormatter::FromString(s, value);
200 }
201
202 bool
203 wxIntegerValidatorBase::IsCharOk(const wxString& val, int pos, wxChar ch) const
204 {
205 // We may accept minus sign if we can represent negative numbers at all.
206 if ( ch == '-' )
207 {
208 // Notice that entering '-' can make our value invalid, for example if
209 // we're limited to -5..15 range and the current value is 12, then the
210 // new value would be (invalid) -12. We consider it better to let the
211 // user do this because perhaps he is going to press Delete key next to
212 // make it -2 and forcing him to delete 1 first would be unnatural.
213 //
214 // TODO: It would be nice to indicate that the current control contents
215 // is invalid (if it's indeed going to be the case) once
216 // wxValidator supports doing this non-intrusively.
217 return m_min < 0 && IsMinusOk(val, pos);
218 }
219
220 // We only accept digits here (remember that '-' is taken care of by the
221 // base class already).
222 if ( ch < '0' || ch > '9' )
223 return false;
224
225 // And the value after insertion needs to be in the defined range.
226 LongestValueType value;
227 if ( !FromString(GetValueAfterInsertingChar(val, pos, ch), &value) )
228 return false;
229
230 return IsInRange(value);
231 }
232
233 // ============================================================================
234 // wxFloatingPointValidatorBase implementation
235 // ============================================================================
236
237 wxString wxFloatingPointValidatorBase::ToString(LongestValueType value) const
238 {
239 return wxNumberFormatter::ToString(value, m_precision, GetFormatFlags());
240 }
241
242 bool
243 wxFloatingPointValidatorBase::FromString(const wxString& s,
244 LongestValueType *value)
245 {
246 return wxNumberFormatter::FromString(s, value);
247 }
248
249 bool
250 wxFloatingPointValidatorBase::IsCharOk(const wxString& val,
251 int pos,
252 wxChar ch) const
253 {
254 // We may accept minus sign if we can represent negative numbers at all.
255 if ( ch == '-' )
256 return m_min < 0 && IsMinusOk(val, pos);
257
258 const wxChar separator = wxNumberFormatter::GetDecimalSeparator();
259 if ( ch == separator )
260 {
261 if ( val.find(separator) != wxString::npos )
262 {
263 // There is already a decimal separator, can't insert another one.
264 return false;
265 }
266
267 // Prepending a separator before the minus sign isn't allowed.
268 if ( pos == 0 && !val.empty() && val[0] == '-' )
269 return false;
270
271 // Otherwise always accept it, adding a decimal separator doesn't
272 // change the number value and, in particular, can't make it invalid.
273 // OTOH the checks below might not pass because strings like "." or
274 // "-." are not valid numbers so parsing them would fail, hence we need
275 // to treat it specially here.
276 return true;
277 }
278
279 // Must be a digit then.
280 if ( ch < '0' || ch > '9' )
281 return false;
282
283 // Check whether the value we'd obtain if we accepted this key is correct.
284 const wxString newval(GetValueAfterInsertingChar(val, pos, ch));
285
286 LongestValueType value;
287 if ( !FromString(newval, &value) )
288 return false;
289
290 // Also check that it doesn't have too many decimal digits.
291 const size_t posSep = newval.find(separator);
292 if ( posSep != wxString::npos && newval.length() - posSep - 1 > m_precision )
293 return false;
294
295 // Finally check whether it is in the range.
296 return IsInRange(value);
297 }
298
299 #endif // wxUSE_VALIDATORS && wxUSE_TEXTCTRL