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