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