~ubuntu-dev/wxwidgets2.6/upstream-debian

« back to all changes in this revision

Viewing changes to wxPython/wx/lib/intctrl.py

  • Committer: Daniel T Chen
  • Date: 2006-06-26 10:15:11 UTC
  • Revision ID: crimsun@ubuntu.com-20060626101511-a4436cec4c6d9b35
ImportĀ DebianĀ 2.6.3.2.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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: intctrl.py,v 1.6 2003/12/22 19:09:52 RD Exp $
 
7
# License:     wxWindows license
 
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 Compatability 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
 
 
39
import  string
 
40
import  types
 
41
 
 
42
import  wx
 
43
 
 
44
#----------------------------------------------------------------------------
 
45
 
 
46
from sys import maxint
 
47
MAXINT = maxint     # (constants should be in upper case)
 
48
MININT = -maxint-1
 
49
 
 
50
#----------------------------------------------------------------------------
 
51
 
 
52
# Used to trap events indicating that the current
 
53
# integer value of the control has been changed.
 
54
wxEVT_COMMAND_INT_UPDATED = wx.NewEventType()
 
55
EVT_INT = wx.PyEventBinder(wxEVT_COMMAND_INT_UPDATED, 1)
 
56
 
 
57
#----------------------------------------------------------------------------
 
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:
 
65
WXK_CTRL_X = (ord('X')+1) - ord('A')
 
66
WXK_CTRL_V = (ord('V')+1) - ord('A')
 
67
 
 
68
class IntUpdatedEvent(wx.PyCommandEvent):
 
69
    def __init__(self, id, value = 0, object=None):
 
70
        wx.PyCommandEvent.__init__(self, wxEVT_COMMAND_INT_UPDATED, id)
 
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
 
 
83
class IntValidator( wx.PyValidator ):
 
84
    """
 
85
    Validator class used with IntCtrl; handles all validation of input
 
86
    prior to changing the value of the underlying wx.TextCtrl.
 
87
    """
 
88
    def __init__(self):
 
89
        wx.PyValidator.__init__(self)
 
90
        self.Bind(wx.EVT_CHAR, self.OnChar)
 
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()
 
117
        textval = wx.TextCtrl.GetValue(ctrl)
 
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
 
 
146
        if key in (wx.WXK_DELETE, wx.WXK_BACK, WXK_CTRL_X):
 
147
            if select_len:
 
148
                new_text = textval[:sel_start] + textval[sel_to:]
 
149
            elif key == wx.WXK_DELETE and pos < len(textval):
 
150
                new_text = textval[:pos] + textval[pos+1:]
 
151
            elif key == wx.WXK_BACK and pos > 0:
 
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)
 
184
 
 
185
                    if paste_text:
 
186
                        paste_value = ctrl._fromGUI(paste_text)
 
187
                    else:
 
188
                        paste_value = 0
 
189
 
 
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
 
203
 
 
204
                    paste = 1
 
205
 
 
206
                except ValueError:
 
207
                    allow_event = 0
 
208
 
 
209
 
 
210
        elif key < wx.WXK_SPACE or key > 255:
 
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:
 
274
                        if( key == wx.WXK_BACK
 
275
                            or (paste and paste_value == 0 and new_pos > 0) ):
 
276
                            new_pos = new_pos - 1
 
277
 
 
278
                        wx.CallAfter(ctrl.SetValue, new_value)
 
279
                        wx.CallAfter(ctrl.SetInsertionPoint, new_pos)
 
280
                        internally_set = 1
 
281
 
 
282
                    elif paste:
 
283
                        # Always do paste numerically, to remove
 
284
                        # leading/trailing spaces
 
285
                        wx.CallAfter(ctrl.SetValue, new_value)
 
286
                        wx.CallAfter(ctrl.SetInsertionPoint, new_pos)
 
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:
 
310
                wx.CallAfter(ctrl.SetValue, new_value)
 
311
 
 
312
            elif set_to_zero:
 
313
                # select to "empty" numeric value
 
314
                wx.CallAfter(ctrl.SetValue, new_value)
 
315
                wx.CallAfter(ctrl.SetInsertionPoint, 0)
 
316
                wx.CallAfter(ctrl.SetSelection, 0, 1)
 
317
 
 
318
            elif set_to_minus_one:
 
319
                wx.CallAfter(ctrl.SetValue, new_value)
 
320
                wx.CallAfter(ctrl.SetInsertionPoint, 1)
 
321
                wx.CallAfter(ctrl.SetSelection, 1, 2)
 
322
 
 
323
            elif not internally_set:
 
324
                event.Skip()    # allow base wxTextCtrl to finish processing
 
325
 
 
326
        elif not wx.Validator_IsSilent():
 
327
            wx.Bell()
 
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
     """
 
336
     return True # Prevent wx.Dialog from complaining.
 
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
     """
 
345
     return True # Prevent wx.Dialog from complaining.
 
346
 
 
347
 
 
348
#----------------------------------------------------------------------------
 
349
 
 
350
class IntCtrl(wx.TextCtrl):
 
351
    """
 
352
    This class provides a control that takes and returns integers as
 
353
    value, and provides bounds support and optional value limiting.
 
354
 
 
355
    IntCtrl(
 
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
 
413
        Normally None, IntCtrl uses its own validator to do value
 
414
        validation and input control.  However, a validator derived
 
415
        from IntValidator can be supplied to override the data
 
416
        transfer methods for the IntValidator class.
 
417
    """
 
418
 
 
419
    def __init__ (
 
420
                self, parent, id=-1, value = 0,
 
421
                pos = wx.DefaultPosition, size = wx.DefaultSize,
 
422
                style = 0, validator = wx.DefaultValidator,
 
423
                name = "integer",
 
424
                min=None, max=None,
 
425
                limited = 0, allow_none = 0, allow_long = 0,
 
426
                default_color = wx.BLACK, oob_color = wx.RED,
 
427
        ):
 
428
 
 
429
        # Establish attrs required for any operation on value:
 
430
        self.__min = None
 
431
        self.__max = None
 
432
        self.__limited = 0
 
433
        self.__default_color = wx.BLACK
 
434
        self.__oob_color = wx.RED
 
435
        self.__allow_none = 0
 
436
        self.__allow_long = 0
 
437
        self.__oldvalue = None
 
438
 
 
439
        if validator == wx.DefaultValidator:
 
440
            validator = IntValidator()
 
441
 
 
442
        wx.TextCtrl.__init__(
 
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:
 
447
        self.Bind(wx.EVT_TEXT, self.OnText )
 
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.
 
464
        NOTE: using wx.TextCtrl.SetValue() to change the control's
 
465
        contents from within a wx.EVT_CHAR handler can cause double
 
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(
 
473
                    IntUpdatedEvent( self.GetId(), self.GetValue(), self ) )
 
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
        """
 
485
        return self._fromGUI( wx.TextCtrl.GetValue(self) )
 
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
        """
 
496
        wx.TextCtrl.SetValue( self, self._toGUI(value) )
 
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 (
 
641
                'IntCtrl requires integer values, passed %s'% repr(value) )
 
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
 
 
697
    def SetColors(self, default_color=wx.BLACK, oob_color=wx.RED):
 
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 (
 
738
                'IntCtrl requires integer value, passed long' )
 
739
        elif type(value) not in (types.IntType, types.LongType):
 
740
            raise ValueError (
 
741
                'IntCtrl requires integer value, passed %s'% repr(value) )
 
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
 
790
        textval = wx.TextCtrl.GetValue(self)
 
791
 
 
792
        do = wx.TextDataObject()
 
793
        do.SetText(textval[sel_start:sel_to])
 
794
        wx.TheClipboard.Open()
 
795
        wx.TheClipboard.SetData(do)
 
796
        wx.TheClipboard.Close()
 
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
        """
 
813
        do = wx.TextDataObject()
 
814
        wx.TheClipboard.Open()
 
815
        success = wx.TheClipboard.GetData(do)
 
816
        wx.TheClipboard.Close()
 
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()
 
835
            text = wx.TextCtrl.GetValue( self )
 
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)
 
843
                wx.CallAfter(self.SetInsertionPoint, new_pos)
 
844
 
 
845
 
 
846
 
 
847
#===========================================================================
 
848
 
 
849
if __name__ == '__main__':
 
850
 
 
851
    import traceback
 
852
 
 
853
    class myDialog(wx.Dialog):
 
854
        def __init__(self, parent, id, title,
 
855
            pos = wx.DefaultPosition, size = wx.DefaultSize,
 
856
            style = wx.DEFAULT_DIALOG_STYLE ):
 
857
            wx.Dialog.__init__(self, parent, id, title, pos, size, style)
 
858
 
 
859
            self.int_ctrl = IntCtrl(self, wx.NewId(), size=(55,20))
 
860
            self.OK = wx.Button( self, wx.ID_OK, "OK")
 
861
            self.Cancel = wx.Button( self, wx.ID_CANCEL, "Cancel")
 
862
 
 
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 )
 
869
 
 
870
            self.SetAutoLayout( True )
 
871
            self.SetSizer( vs )
 
872
            vs.Fit( self )
 
873
            vs.SetSizeHints( self )
 
874
            self.Bind(EVT_INT, self.OnInt, self.int_ctrl)
 
875
 
 
876
        def OnInt(self, event):
 
877
            print 'int now', event.GetValue()
 
878
 
 
879
    class TestApp(wx.App):
 
880
        def OnInit(self):
 
881
            try:
 
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)
 
886
            except:
 
887
                traceback.print_exc()
 
888
                return False
 
889
            return True
 
890
 
 
891
        def OnClick(self, event):
 
892
            dlg = myDialog(self.panel, -1, "test IntCtrl")
 
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()