]>
Commit | Line | Data |
---|---|---|
1 | #---------------------------------------------------------------------------- | |
2 | # Name: wxPython.lib.intctrl.py | |
3 | # Author: Will Sadkin | |
4 | # Created: 01/16/2003 | |
5 | # Copyright: (c) 2003 by Will Sadkin | |
6 | # RCS-ID: $Id$ | |
7 | # License: wxWindows license | |
8 | #---------------------------------------------------------------------------- | |
9 | # NOTE: | |
10 | # This was written to provide a standard integer edit control for wxPython. | |
11 | # | |
12 | # wxIntCtrl permits integer (long) values to be retrieved or set via | |
13 | # .GetValue() and .SetValue(), and provides an EVT_INT() event function | |
14 | # for trapping changes to the control. | |
15 | # | |
16 | # It supports negative integers as well as the naturals, and does not | |
17 | # permit leading zeros or an empty control; attempting to delete the | |
18 | # contents of the control will result in a (selected) value of zero, | |
19 | # thus preserving a legitimate integer value, or an empty control | |
20 | # (if a value of None is allowed for the control.) Similarly, replacing the | |
21 | # contents of the control with '-' will result in a selected (absolute) | |
22 | # value of -1. | |
23 | # | |
24 | # wxIntCtrl also supports range limits, with the option of either | |
25 | # enforcing them or simply coloring the text of the control if the limits | |
26 | # are exceeded. | |
27 | #---------------------------------------------------------------------------- | |
28 | # 12/08/2003 - Jeff Grimmett (grimmtooth@softhome.net) | |
29 | # | |
30 | # o 2.5 Compatability changes | |
31 | # | |
32 | ||
33 | import string | |
34 | import types | |
35 | ||
36 | import wx | |
37 | ||
38 | #---------------------------------------------------------------------------- | |
39 | ||
40 | from sys import maxint | |
41 | MAXINT = maxint # (constants should be in upper case) | |
42 | MININT = -maxint-1 | |
43 | ||
44 | #---------------------------------------------------------------------------- | |
45 | ||
46 | # Used to trap events indicating that the current | |
47 | # integer value of the control has been changed. | |
48 | wxEVT_COMMAND_INT_UPDATED = wx.NewEventType() | |
49 | EVT_INT = wx.PyEventBinder(wxEVT_COMMAND_INT_UPDATED, 1) | |
50 | ||
51 | #---------------------------------------------------------------------------- | |
52 | ||
53 | # wxWindows' wxTextCtrl translates Composite "control key" | |
54 | # events into single events before returning them to its OnChar | |
55 | # routine. The doc says that this results in 1 for Ctrl-A, 2 for | |
56 | # Ctrl-B, etc. However, there are no wxPython or wxWindows | |
57 | # symbols for them, so I'm defining codes for Ctrl-X (cut) and | |
58 | # Ctrl-V (paste) here for readability: | |
59 | WXK_CTRL_X = (ord('X')+1) - ord('A') | |
60 | WXK_CTRL_V = (ord('V')+1) - ord('A') | |
61 | ||
62 | class wxIntUpdatedEvent(wx.PyCommandEvent): | |
63 | def __init__(self, id, value = 0, object=None): | |
64 | wx.PyCommandEvent.__init__(self, wxEVT_COMMAND_INT_UPDATED, id) | |
65 | ||
66 | self.__value = value | |
67 | self.SetEventObject(object) | |
68 | ||
69 | def GetValue(self): | |
70 | """Retrieve the value of the control at the time | |
71 | this event was generated.""" | |
72 | return self.__value | |
73 | ||
74 | ||
75 | #---------------------------------------------------------------------------- | |
76 | ||
77 | class wxIntValidator( wx.PyValidator ): | |
78 | """ | |
79 | Validator class used with wxIntCtrl; handles all validation of input | |
80 | prior to changing the value of the underlying wx.TextCtrl. | |
81 | """ | |
82 | def __init__(self): | |
83 | wx.PyValidator.__init__(self) | |
84 | self.Bind(wx.EVT_CHAR, self.OnChar) | |
85 | ||
86 | def Clone (self): | |
87 | return self.__class__() | |
88 | ||
89 | def Validate(self, window): # window here is the *parent* of the ctrl | |
90 | """ | |
91 | Because each operation on the control is vetted as it's made, | |
92 | the value of the control is always valid. | |
93 | """ | |
94 | return 1 | |
95 | ||
96 | ||
97 | def OnChar(self, event): | |
98 | """ | |
99 | Validates keystrokes to make sure the resulting value will a legal | |
100 | value. Erasing the value causes it to be set to 0, with the value | |
101 | selected, so it can be replaced. Similarly, replacing the value | |
102 | with a '-' sign causes the value to become -1, with the value | |
103 | selected. Leading zeros are removed if introduced by selection, | |
104 | and are prevented from being inserted. | |
105 | """ | |
106 | key = event.KeyCode() | |
107 | ctrl = event.GetEventObject() | |
108 | ||
109 | ||
110 | value = ctrl.GetValue() | |
111 | textval = wx.TextCtrl.GetValue(ctrl) | |
112 | allow_none = ctrl.IsNoneAllowed() | |
113 | ||
114 | pos = ctrl.GetInsertionPoint() | |
115 | sel_start, sel_to = ctrl.GetSelection() | |
116 | select_len = sel_to - sel_start | |
117 | ||
118 | # (Uncomment for debugging:) | |
119 | ## print 'keycode:', key | |
120 | ## print 'pos:', pos | |
121 | ## print 'sel_start, sel_to:', sel_start, sel_to | |
122 | ## print 'select_len:', select_len | |
123 | ## print 'textval:', textval | |
124 | ||
125 | # set defaults for processing: | |
126 | allow_event = 1 | |
127 | set_to_none = 0 | |
128 | set_to_zero = 0 | |
129 | set_to_minus_one = 0 | |
130 | paste = 0 | |
131 | internally_set = 0 | |
132 | ||
133 | new_value = value | |
134 | new_text = textval | |
135 | new_pos = pos | |
136 | ||
137 | # Validate action, and predict resulting value, so we can | |
138 | # range check the result and validate that too. | |
139 | ||
140 | if key in (wx.WXK_DELETE, wx.WXK_BACK, WXK_CTRL_X): | |
141 | if select_len: | |
142 | new_text = textval[:sel_start] + textval[sel_to:] | |
143 | elif key == wx.WXK_DELETE and pos < len(textval): | |
144 | new_text = textval[:pos] + textval[pos+1:] | |
145 | elif key == wx.WXK_BACK and pos > 0: | |
146 | new_text = textval[:pos-1] + textval[pos:] | |
147 | # (else value shouldn't change) | |
148 | ||
149 | if new_text in ('', '-'): | |
150 | # Deletion of last significant digit: | |
151 | if allow_none and new_text == '': | |
152 | new_value = None | |
153 | set_to_none = 1 | |
154 | else: | |
155 | new_value = 0 | |
156 | set_to_zero = 1 | |
157 | else: | |
158 | try: | |
159 | new_value = ctrl._fromGUI(new_text) | |
160 | except ValueError: | |
161 | allow_event = 0 | |
162 | ||
163 | ||
164 | elif key == WXK_CTRL_V: # (see comments at top of file) | |
165 | # Only allow paste if number: | |
166 | paste_text = ctrl._getClipboardContents() | |
167 | new_text = textval[:sel_start] + paste_text + textval[sel_to:] | |
168 | if new_text == '' and allow_none: | |
169 | new_value = None | |
170 | set_to_none = 1 | |
171 | else: | |
172 | try: | |
173 | # Convert the resulting strings, verifying they | |
174 | # are legal integers and will fit in proper | |
175 | # size if ctrl limited to int. (if not, | |
176 | # disallow event.) | |
177 | new_value = ctrl._fromGUI(new_text) | |
178 | ||
179 | if paste_text: | |
180 | paste_value = ctrl._fromGUI(paste_text) | |
181 | else: | |
182 | paste_value = 0 | |
183 | ||
184 | new_pos = sel_start + len(str(paste_value)) | |
185 | ||
186 | # if resulting value is 0, truncate and highlight value: | |
187 | if new_value == 0 and len(new_text) > 1: | |
188 | set_to_zero = 1 | |
189 | ||
190 | elif paste_value == 0: | |
191 | # Disallow pasting a leading zero with nothing selected: | |
192 | if( select_len == 0 | |
193 | and value is not None | |
194 | and ( (value >= 0 and pos == 0) | |
195 | or (value < 0 and pos in [0,1]) ) ): | |
196 | allow_event = 0 | |
197 | ||
198 | paste = 1 | |
199 | ||
200 | except ValueError: | |
201 | allow_event = 0 | |
202 | ||
203 | ||
204 | elif key < wx.WXK_SPACE or key > 255: | |
205 | pass # event ok | |
206 | ||
207 | ||
208 | elif chr(key) == '-': | |
209 | # Allow '-' to result in -1 if replacing entire contents: | |
210 | if( value is None | |
211 | or (value == 0 and pos == 0) | |
212 | or (select_len >= len(str(abs(value)))) ): | |
213 | new_value = -1 | |
214 | set_to_minus_one = 1 | |
215 | ||
216 | # else allow negative sign only at start, and only if | |
217 | # number isn't already zero or negative: | |
218 | elif pos != 0 or (value is not None and value < 0): | |
219 | allow_event = 0 | |
220 | else: | |
221 | new_text = '-' + textval | |
222 | new_pos = 1 | |
223 | try: | |
224 | new_value = ctrl._fromGUI(new_text) | |
225 | except ValueError: | |
226 | allow_event = 0 | |
227 | ||
228 | ||
229 | elif chr(key) in string.digits: | |
230 | # disallow inserting a leading zero with nothing selected | |
231 | if( chr(key) == '0' | |
232 | and select_len == 0 | |
233 | and value is not None | |
234 | and ( (value >= 0 and pos == 0) | |
235 | or (value < 0 and pos in [0,1]) ) ): | |
236 | allow_event = 0 | |
237 | # disallow inserting digits before the minus sign: | |
238 | elif value is not None and value < 0 and pos == 0: | |
239 | allow_event = 0 | |
240 | else: | |
241 | new_text = textval[:sel_start] + chr(key) + textval[sel_to:] | |
242 | try: | |
243 | new_value = ctrl._fromGUI(new_text) | |
244 | except ValueError: | |
245 | allow_event = 0 | |
246 | ||
247 | else: | |
248 | # not a legal char | |
249 | allow_event = 0 | |
250 | ||
251 | ||
252 | if allow_event: | |
253 | # Do range checking for new candidate value: | |
254 | if ctrl.IsLimited() and not ctrl.IsInBounds(new_value): | |
255 | allow_event = 0 | |
256 | elif new_value is not None: | |
257 | # ensure resulting text doesn't result in a leading 0: | |
258 | if not set_to_zero and not set_to_minus_one: | |
259 | if( (new_value > 0 and new_text[0] == '0') | |
260 | or (new_value < 0 and new_text[1] == '0') | |
261 | or (new_value == 0 and select_len > 1 ) ): | |
262 | ||
263 | # Allow replacement of leading chars with | |
264 | # zero, but remove the leading zero, effectively | |
265 | # making this like "remove leading digits" | |
266 | ||
267 | # Account for leading zero when positioning cursor: | |
268 | if( key == wx.WXK_BACK | |
269 | or (paste and paste_value == 0 and new_pos > 0) ): | |
270 | new_pos = new_pos - 1 | |
271 | ||
272 | wx.CallAfter(ctrl.SetValue, new_value) | |
273 | wx.CallAfter(ctrl.SetInsertionPoint, new_pos) | |
274 | internally_set = 1 | |
275 | ||
276 | elif paste: | |
277 | # Always do paste numerically, to remove | |
278 | # leading/trailing spaces | |
279 | wx.CallAfter(ctrl.SetValue, new_value) | |
280 | wx.CallAfter(ctrl.SetInsertionPoint, new_pos) | |
281 | internally_set = 1 | |
282 | ||
283 | elif (new_value == 0 and len(new_text) > 1 ): | |
284 | allow_event = 0 | |
285 | ||
286 | if allow_event: | |
287 | ctrl._colorValue(new_value) # (one way or t'other) | |
288 | ||
289 | # (Uncomment for debugging:) | |
290 | ## if allow_event: | |
291 | ## print 'new value:', new_value | |
292 | ## if paste: print 'paste' | |
293 | ## if set_to_none: print 'set_to_none' | |
294 | ## if set_to_zero: print 'set_to_zero' | |
295 | ## if set_to_minus_one: print 'set_to_minus_one' | |
296 | ## if internally_set: print 'internally_set' | |
297 | ## else: | |
298 | ## print 'new text:', new_text | |
299 | ## print 'disallowed' | |
300 | ||
301 | ||
302 | if allow_event: | |
303 | if set_to_none: | |
304 | wx.CallAfter(ctrl.SetValue, new_value) | |
305 | ||
306 | elif set_to_zero: | |
307 | # select to "empty" numeric value | |
308 | wx.CallAfter(ctrl.SetValue, new_value) | |
309 | wx.CallAfter(ctrl.SetInsertionPoint, 0) | |
310 | wx.CallAfter(ctrl.SetSelection, 0, 1) | |
311 | ||
312 | elif set_to_minus_one: | |
313 | wx.CallAfter(ctrl.SetValue, new_value) | |
314 | wx.CallAfter(ctrl.SetInsertionPoint, 1) | |
315 | wx.CallAfter(ctrl.SetSelection, 1, 2) | |
316 | ||
317 | elif not internally_set: | |
318 | event.Skip() # allow base wxTextCtrl to finish processing | |
319 | ||
320 | elif not wx.Validator_IsSilent(): | |
321 | wx.Bell() | |
322 | ||
323 | ||
324 | def TransferToWindow(self): | |
325 | """ Transfer data from validator to window. | |
326 | ||
327 | The default implementation returns False, indicating that an error | |
328 | occurred. We simply return True, as we don't do any data transfer. | |
329 | """ | |
330 | return True # Prevent wx.Dialog from complaining. | |
331 | ||
332 | ||
333 | def TransferFromWindow(self): | |
334 | """ Transfer data from window to validator. | |
335 | ||
336 | The default implementation returns False, indicating that an error | |
337 | occurred. We simply return True, as we don't do any data transfer. | |
338 | """ | |
339 | return True # Prevent wx.Dialog from complaining. | |
340 | ||
341 | ||
342 | #---------------------------------------------------------------------------- | |
343 | ||
344 | class wxIntCtrl(wx.TextCtrl): | |
345 | """ | |
346 | This class provides a control that takes and returns integers as | |
347 | value, and provides bounds support and optional value limiting. | |
348 | ||
349 | wxIntCtrl( | |
350 | parent, id = -1, | |
351 | value = 0, | |
352 | pos = wxDefaultPosition, | |
353 | size = wxDefaultSize, | |
354 | style = 0, | |
355 | validator = wxDefaultValidator, | |
356 | name = "integer", | |
357 | min = None, | |
358 | max = None, | |
359 | limited = False, | |
360 | allow_none = False, | |
361 | allow_long = False, | |
362 | default_color = wxBLACK, | |
363 | oob_color = wxRED ) | |
364 | ||
365 | value | |
366 | If no initial value is set, the default will be zero, or | |
367 | the minimum value, if specified. If an illegal string is specified, | |
368 | a ValueError will result. (You can always later set the initial | |
369 | value with SetValue() after instantiation of the control.) | |
370 | min | |
371 | The minimum value that the control should allow. This can be | |
372 | adjusted with SetMin(). If the control is not limited, any value | |
373 | below this bound will be colored with the current out-of-bounds color. | |
374 | If min < -sys.maxint-1 and the control is configured to not allow long | |
375 | values, the minimum bound will still be set to the long value, but | |
376 | the implicit bound will be -sys.maxint-1. | |
377 | max | |
378 | The maximum value that the control should allow. This can be | |
379 | adjusted with SetMax(). If the control is not limited, any value | |
380 | above this bound will be colored with the current out-of-bounds color. | |
381 | if max > sys.maxint and the control is configured to not allow long | |
382 | values, the maximum bound will still be set to the long value, but | |
383 | the implicit bound will be sys.maxint. | |
384 | ||
385 | limited | |
386 | Boolean indicating whether the control prevents values from | |
387 | exceeding the currently set minimum and maximum values (bounds). | |
388 | If False and bounds are set, out-of-bounds values will | |
389 | be colored with the current out-of-bounds color. | |
390 | ||
391 | allow_none | |
392 | Boolean indicating whether or not the control is allowed to be | |
393 | empty, representing a value of None for the control. | |
394 | ||
395 | allow_long | |
396 | Boolean indicating whether or not the control is allowed to hold | |
397 | and return a long as well as an int. | |
398 | ||
399 | default_color | |
400 | Color value used for in-bounds values of the control. | |
401 | ||
402 | oob_color | |
403 | Color value used for out-of-bounds values of the control | |
404 | when the bounds are set but the control is not limited. | |
405 | ||
406 | validator | |
407 | Normally None, wxIntCtrl uses its own validator to do value | |
408 | validation and input control. However, a validator derived | |
409 | from wxIntValidator can be supplied to override the data | |
410 | transfer methods for the wxIntValidator class. | |
411 | """ | |
412 | ||
413 | def __init__ ( | |
414 | self, parent, id=-1, value = 0, | |
415 | pos = wx.DefaultPosition, size = wx.DefaultSize, | |
416 | style = 0, validator = wx.DefaultValidator, | |
417 | name = "integer", | |
418 | min=None, max=None, | |
419 | limited = 0, allow_none = 0, allow_long = 0, | |
420 | default_color = wx.BLACK, oob_color = wx.RED, | |
421 | ): | |
422 | ||
423 | # Establish attrs required for any operation on value: | |
424 | self.__min = None | |
425 | self.__max = None | |
426 | self.__limited = 0 | |
427 | self.__default_color = wx.BLACK | |
428 | self.__oob_color = wx.RED | |
429 | self.__allow_none = 0 | |
430 | self.__allow_long = 0 | |
431 | self.__oldvalue = None | |
432 | ||
433 | if validator == wx.DefaultValidator: | |
434 | validator = wxIntValidator() | |
435 | ||
436 | wx.TextCtrl.__init__( | |
437 | self, parent, id, self._toGUI(0), | |
438 | pos, size, style, validator, name ) | |
439 | ||
440 | # The following lets us set out our "integer update" events: | |
441 | self.Bind(wx.EVT_TEXT, self.OnText ) | |
442 | ||
443 | # Establish parameters, with appropriate error checking | |
444 | ||
445 | self.SetBounds(min, max) | |
446 | self.SetLimited(limited) | |
447 | self.SetColors(default_color, oob_color) | |
448 | self.SetNoneAllowed(allow_none) | |
449 | self.SetLongAllowed(allow_long) | |
450 | self.SetValue(value) | |
451 | self.__oldvalue = 0 | |
452 | ||
453 | ||
454 | def OnText( self, event ): | |
455 | """ | |
456 | Handles an event indicating that the text control's value | |
457 | has changed, and issue EVT_INT event. | |
458 | NOTE: using wx.TextCtrl.SetValue() to change the control's | |
459 | contents from within a wx.EVT_CHAR handler can cause double | |
460 | text events. So we check for actual changes to the text | |
461 | before passing the events on. | |
462 | """ | |
463 | value = self.GetValue() | |
464 | if value != self.__oldvalue: | |
465 | try: | |
466 | self.GetEventHandler().ProcessEvent( | |
467 | wxIntUpdatedEvent( self.GetId(), self.GetValue(), self ) ) | |
468 | except ValueError: | |
469 | return | |
470 | # let normal processing of the text continue | |
471 | event.Skip() | |
472 | self.__oldvalue = value # record for next event | |
473 | ||
474 | ||
475 | def GetValue(self): | |
476 | """ | |
477 | Returns the current integer (long) value of the control. | |
478 | """ | |
479 | return self._fromGUI( wx.TextCtrl.GetValue(self) ) | |
480 | ||
481 | def SetValue(self, value): | |
482 | """ | |
483 | Sets the value of the control to the integer value specified. | |
484 | The resulting actual value of the control may be altered to | |
485 | conform with the bounds set on the control if limited, | |
486 | or colored if not limited but the value is out-of-bounds. | |
487 | A ValueError exception will be raised if an invalid value | |
488 | is specified. | |
489 | """ | |
490 | wx.TextCtrl.SetValue( self, self._toGUI(value) ) | |
491 | self._colorValue() | |
492 | ||
493 | ||
494 | def SetMin(self, min=None): | |
495 | """ | |
496 | Sets the minimum value of the control. If a value of None | |
497 | is provided, then the control will have no explicit minimum value. | |
498 | If the value specified is greater than the current maximum value, | |
499 | then the function returns 0 and the minimum will not change from | |
500 | its current setting. On success, the function returns 1. | |
501 | ||
502 | If successful and the current value is lower than the new lower | |
503 | bound, if the control is limited, the value will be automatically | |
504 | adjusted to the new minimum value; if not limited, the value in the | |
505 | control will be colored with the current out-of-bounds color. | |
506 | ||
507 | If min > -sys.maxint-1 and the control is configured to not allow longs, | |
508 | the function will return 0, and the min will not be set. | |
509 | """ | |
510 | if( self.__max is None | |
511 | or min is None | |
512 | or (self.__max is not None and self.__max >= min) ): | |
513 | self.__min = min | |
514 | ||
515 | if self.IsLimited() and min is not None and self.GetValue() < min: | |
516 | self.SetValue(min) | |
517 | else: | |
518 | self._colorValue() | |
519 | return 1 | |
520 | else: | |
521 | return 0 | |
522 | ||
523 | ||
524 | def GetMin(self): | |
525 | """ | |
526 | Gets the minimum value of the control. It will return the current | |
527 | minimum integer, or None if not specified. | |
528 | """ | |
529 | return self.__min | |
530 | ||
531 | ||
532 | def SetMax(self, max=None): | |
533 | """ | |
534 | Sets the maximum value of the control. If a value of None | |
535 | is provided, then the control will have no explicit maximum value. | |
536 | If the value specified is less than the current minimum value, then | |
537 | the function returns 0 and the maximum will not change from its | |
538 | current setting. On success, the function returns 1. | |
539 | ||
540 | If successful and the current value is greater than the new upper | |
541 | bound, if the control is limited the value will be automatically | |
542 | adjusted to this maximum value; if not limited, the value in the | |
543 | control will be colored with the current out-of-bounds color. | |
544 | ||
545 | If max > sys.maxint and the control is configured to not allow longs, | |
546 | the function will return 0, and the max will not be set. | |
547 | """ | |
548 | if( self.__min is None | |
549 | or max is None | |
550 | or (self.__min is not None and self.__min <= max) ): | |
551 | self.__max = max | |
552 | ||
553 | if self.IsLimited() and max is not None and self.GetValue() > max: | |
554 | self.SetValue(max) | |
555 | else: | |
556 | self._colorValue() | |
557 | return 1 | |
558 | else: | |
559 | return 0 | |
560 | ||
561 | ||
562 | def GetMax(self): | |
563 | """ | |
564 | Gets the maximum value of the control. It will return the current | |
565 | maximum integer, or None if not specified. | |
566 | """ | |
567 | return self.__max | |
568 | ||
569 | ||
570 | def SetBounds(self, min=None, max=None): | |
571 | """ | |
572 | This function is a convenience function for setting the min and max | |
573 | values at the same time. The function only applies the maximum bound | |
574 | if setting the minimum bound is successful, and returns True | |
575 | only if both operations succeed. | |
576 | NOTE: leaving out an argument will remove the corresponding bound. | |
577 | """ | |
578 | ret = self.SetMin(min) | |
579 | return ret and self.SetMax(max) | |
580 | ||
581 | ||
582 | def GetBounds(self): | |
583 | """ | |
584 | This function returns a two-tuple (min,max), indicating the | |
585 | current bounds of the control. Each value can be None if | |
586 | that bound is not set. | |
587 | """ | |
588 | return (self.__min, self.__max) | |
589 | ||
590 | ||
591 | def SetLimited(self, limited): | |
592 | """ | |
593 | If called with a value of True, this function will cause the control | |
594 | to limit the value to fall within the bounds currently specified. | |
595 | If the control's value currently exceeds the bounds, it will then | |
596 | be limited accordingly. | |
597 | ||
598 | If called with a value of 0, this function will disable value | |
599 | limiting, but coloring of out-of-bounds values will still take | |
600 | place if bounds have been set for the control. | |
601 | """ | |
602 | self.__limited = limited | |
603 | if limited: | |
604 | min = self.GetMin() | |
605 | max = self.GetMax() | |
606 | if not min is None and self.GetValue() < min: | |
607 | self.SetValue(min) | |
608 | elif not max is None and self.GetValue() > max: | |
609 | self.SetValue(max) | |
610 | else: | |
611 | self._colorValue() | |
612 | ||
613 | ||
614 | def IsLimited(self): | |
615 | """ | |
616 | Returns True if the control is currently limiting the | |
617 | value to fall within the current bounds. | |
618 | """ | |
619 | return self.__limited | |
620 | ||
621 | ||
622 | def IsInBounds(self, value=None): | |
623 | """ | |
624 | Returns True if no value is specified and the current value | |
625 | of the control falls within the current bounds. This function can | |
626 | also be called with a value to see if that value would fall within | |
627 | the current bounds of the given control. | |
628 | """ | |
629 | if value is None: | |
630 | value = self.GetValue() | |
631 | ||
632 | if( not (value is None and self.IsNoneAllowed()) | |
633 | and type(value) not in (types.IntType, types.LongType) ): | |
634 | raise ValueError ( | |
635 | 'wxIntCtrl requires integer values, passed %s'% repr(value) ) | |
636 | ||
637 | min = self.GetMin() | |
638 | max = self.GetMax() | |
639 | if min is None: min = value | |
640 | if max is None: max = value | |
641 | ||
642 | # if bounds set, and value is None, return False | |
643 | if value == None and (min is not None or max is not None): | |
644 | return 0 | |
645 | else: | |
646 | return min <= value <= max | |
647 | ||
648 | ||
649 | def SetNoneAllowed(self, allow_none): | |
650 | """ | |
651 | Change the behavior of the validation code, allowing control | |
652 | to have a value of None or not, as appropriate. If the value | |
653 | of the control is currently None, and allow_none is 0, the | |
654 | value of the control will be set to the minimum value of the | |
655 | control, or 0 if no lower bound is set. | |
656 | """ | |
657 | self.__allow_none = allow_none | |
658 | if not allow_none and self.GetValue() is None: | |
659 | min = self.GetMin() | |
660 | if min is not None: self.SetValue(min) | |
661 | else: self.SetValue(0) | |
662 | ||
663 | ||
664 | def IsNoneAllowed(self): | |
665 | return self.__allow_none | |
666 | ||
667 | ||
668 | def SetLongAllowed(self, allow_long): | |
669 | """ | |
670 | Change the behavior of the validation code, allowing control | |
671 | to have a long value or not, as appropriate. If the value | |
672 | of the control is currently long, and allow_long is 0, the | |
673 | value of the control will be adjusted to fall within the | |
674 | size of an integer type, at either the sys.maxint or -sys.maxint-1, | |
675 | for positive and negative values, respectively. | |
676 | """ | |
677 | current_value = self.GetValue() | |
678 | if not allow_long and type(current_value) is types.LongType: | |
679 | if current_value > 0: | |
680 | self.SetValue(MAXINT) | |
681 | else: | |
682 | self.SetValue(MININT) | |
683 | self.__allow_long = allow_long | |
684 | ||
685 | ||
686 | def IsLongAllowed(self): | |
687 | return self.__allow_long | |
688 | ||
689 | ||
690 | ||
691 | def SetColors(self, default_color=wx.BLACK, oob_color=wx.RED): | |
692 | """ | |
693 | Tells the control what colors to use for normal and out-of-bounds | |
694 | values. If the value currently exceeds the bounds, it will be | |
695 | recolored accordingly. | |
696 | """ | |
697 | self.__default_color = default_color | |
698 | self.__oob_color = oob_color | |
699 | self._colorValue() | |
700 | ||
701 | ||
702 | def GetColors(self): | |
703 | """ | |
704 | Returns a tuple of (default_color, oob_color), indicating | |
705 | the current color settings for the control. | |
706 | """ | |
707 | return self.__default_color, self.__oob_color | |
708 | ||
709 | ||
710 | def _colorValue(self, value=None): | |
711 | """ | |
712 | Colors text with oob_color if current value exceeds bounds | |
713 | set for control. | |
714 | """ | |
715 | if not self.IsInBounds(value): | |
716 | self.SetForegroundColour(self.__oob_color) | |
717 | else: | |
718 | self.SetForegroundColour(self.__default_color) | |
719 | self.Refresh() | |
720 | ||
721 | ||
722 | def _toGUI( self, value ): | |
723 | """ | |
724 | Conversion function used to set the value of the control; does | |
725 | type and bounds checking and raises ValueError if argument is | |
726 | not a valid value. | |
727 | """ | |
728 | if value is None and self.IsNoneAllowed(): | |
729 | return '' | |
730 | elif type(value) == types.LongType and not self.IsLongAllowed(): | |
731 | raise ValueError ( | |
732 | 'wxIntCtrl requires integer value, passed long' ) | |
733 | elif type(value) not in (types.IntType, types.LongType): | |
734 | raise ValueError ( | |
735 | 'wxIntCtrl requires integer value, passed %s'% repr(value) ) | |
736 | ||
737 | elif self.IsLimited(): | |
738 | min = self.GetMin() | |
739 | max = self.GetMax() | |
740 | if not min is None and value < min: | |
741 | raise ValueError ( | |
742 | 'value is below minimum value of control %d'% value ) | |
743 | if not max is None and value > max: | |
744 | raise ValueError ( | |
745 | 'value exceeds value of control %d'% value ) | |
746 | ||
747 | return str(value) | |
748 | ||
749 | ||
750 | def _fromGUI( self, value ): | |
751 | """ | |
752 | Conversion function used in getting the value of the control. | |
753 | """ | |
754 | ||
755 | # One or more of the underlying text control implementations | |
756 | # issue an intermediate EVT_TEXT when replacing the control's | |
757 | # value, where the intermediate value is an empty string. | |
758 | # So, to ensure consistency and to prevent spurious ValueErrors, | |
759 | # we make the following test, and react accordingly: | |
760 | # | |
761 | if value == '': | |
762 | if not self.IsNoneAllowed(): | |
763 | return 0 | |
764 | else: | |
765 | return None | |
766 | else: | |
767 | try: | |
768 | return int( value ) | |
769 | except ValueError: | |
770 | if self.IsLongAllowed(): | |
771 | return long( value ) | |
772 | else: | |
773 | raise | |
774 | ||
775 | ||
776 | def Cut( self ): | |
777 | """ | |
778 | Override the wxTextCtrl's .Cut function, with our own | |
779 | that does validation. Will result in a value of 0 | |
780 | if entire contents of control are removed. | |
781 | """ | |
782 | sel_start, sel_to = self.GetSelection() | |
783 | select_len = sel_to - sel_start | |
784 | textval = wx.TextCtrl.GetValue(self) | |
785 | ||
786 | do = wx.TextDataObject() | |
787 | do.SetText(textval[sel_start:sel_to]) | |
788 | wx.TheClipboard.Open() | |
789 | wx.TheClipboard.SetData(do) | |
790 | wx.TheClipboard.Close() | |
791 | if select_len == len(wxTextCtrl.GetValue(self)): | |
792 | if not self.IsNoneAllowed(): | |
793 | self.SetValue(0) | |
794 | self.SetInsertionPoint(0) | |
795 | self.SetSelection(0,1) | |
796 | else: | |
797 | self.SetValue(None) | |
798 | else: | |
799 | new_value = self._fromGUI(textval[:sel_start] + textval[sel_to:]) | |
800 | self.SetValue(new_value) | |
801 | ||
802 | ||
803 | def _getClipboardContents( self ): | |
804 | """ | |
805 | Subroutine for getting the current contents of the clipboard. | |
806 | """ | |
807 | do = wx.TextDataObject() | |
808 | wx.TheClipboard.Open() | |
809 | success = wx.TheClipboard.GetData(do) | |
810 | wx.TheClipboard.Close() | |
811 | ||
812 | if not success: | |
813 | return None | |
814 | else: | |
815 | # Remove leading and trailing spaces before evaluating contents | |
816 | return do.GetText().strip() | |
817 | ||
818 | ||
819 | def Paste( self ): | |
820 | """ | |
821 | Override the wxTextCtrl's .Paste function, with our own | |
822 | that does validation. Will raise ValueError if not a | |
823 | valid integerizable value. | |
824 | """ | |
825 | paste_text = self._getClipboardContents() | |
826 | if paste_text: | |
827 | # (conversion will raise ValueError if paste isn't legal) | |
828 | sel_start, sel_to = self.GetSelection() | |
829 | text = wx.TextCtrl.GetValue( self ) | |
830 | new_text = text[:sel_start] + paste_text + text[sel_to:] | |
831 | if new_text == '' and self.IsNoneAllowed(): | |
832 | self.SetValue(None) | |
833 | else: | |
834 | value = self._fromGUI(new_text) | |
835 | self.SetValue(value) | |
836 | new_pos = sel_start + len(paste_text) | |
837 | wx.CallAfter(self.SetInsertionPoint, new_pos) | |
838 | ||
839 | ||
840 | ||
841 | #=========================================================================== | |
842 | ||
843 | if __name__ == '__main__': | |
844 | ||
845 | import traceback | |
846 | ||
847 | class myDialog(wx.Dialog): | |
848 | def __init__(self, parent, id, title, | |
849 | pos = wx.DefaultPosition, size = wx.DefaultSize, | |
850 | style = wx.DEFAULT_DIALOG_STYLE ): | |
851 | wx.Dialog.__init__(self, parent, id, title, pos, size, style) | |
852 | ||
853 | self.int_ctrl = wxIntCtrl(self, wx.NewId(), size=(55,20)) | |
854 | self.OK = wx.Button( self, wx.ID_OK, "OK") | |
855 | self.Cancel = wx.Button( self, wx.ID_CANCEL, "Cancel") | |
856 | ||
857 | vs = wx.BoxSizer( wx.VERTICAL ) | |
858 | vs.Add( self.int_ctrl, 0, wx.ALIGN_CENTRE|wx.ALL, 5 ) | |
859 | hs = wx.BoxSizer( wx.HORIZONTAL ) | |
860 | hs.Add( self.OK, 0, wx.ALIGN_CENTRE|wx.ALL, 5 ) | |
861 | hs.Add( self.Cancel, 0, wx.ALIGN_CENTRE|wx.ALL, 5 ) | |
862 | vs.Add(hs, 0, wx.ALIGN_CENTRE|wx.ALL, 5 ) | |
863 | ||
864 | self.SetAutoLayout( True ) | |
865 | self.SetSizer( vs ) | |
866 | vs.Fit( self ) | |
867 | vs.SetSizeHints( self ) | |
868 | self.Bind(EVT_INT, self.OnInt, self.int_ctrl) | |
869 | ||
870 | def OnInt(self, event): | |
871 | print 'int now', event.GetValue() | |
872 | ||
873 | class TestApp(wx.App): | |
874 | def OnInit(self): | |
875 | try: | |
876 | self.frame = wx.Frame(None, -1, "Test", (20,20), (120,100) ) | |
877 | self.panel = wx.Panel(self.frame, -1) | |
878 | button = wx.Button(self.panel, 10, "Push Me", (20, 20)) | |
879 | self.Bind(wx.EVT_BUTTON, self.OnClick, button) | |
880 | except: | |
881 | traceback.print_exc() | |
882 | return False | |
883 | return True | |
884 | ||
885 | def OnClick(self, event): | |
886 | dlg = myDialog(self.panel, -1, "test wxIntCtrl") | |
887 | dlg.int_ctrl.SetValue(501) | |
888 | dlg.int_ctrl.SetInsertionPoint(1) | |
889 | dlg.int_ctrl.SetSelection(1,2) | |
890 | rc = dlg.ShowModal() | |
891 | print 'final value', dlg.int_ctrl.GetValue() | |
892 | del dlg | |
893 | self.frame.Destroy() | |
894 | ||
895 | def Show(self): | |
896 | self.frame.Show(True) | |
897 | ||
898 | try: | |
899 | app = TestApp(0) | |
900 | app.Show() | |
901 | app.MainLoop() | |
902 | except: | |
903 | traceback.print_exc() |