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