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