# Based on iwidgets2.2.0/entryfield.itk code. import regex import types import Tkinter import Pmw # Entry field validation functions def _numeric(text): if text == '': return -1 else: return _getRegex('^[0-9]*$').match(text) != -1 def _integer(text): if text in ('', '-', '+'): return -1 else: return _getRegex('^[-+]?\([0-9]*\)$').match(text) != -1 def _alphabetic(text): return _getRegex('^[a-z]*$', 1).match(text) != -1 def _alphanumeric(text): return _getRegex('^[0-9a-z]*$', 1).match(text) != -1 def _hexadecimal(text): if text in ('', '0x', '0X'): return -1 else: return _getRegex('^\(0x\)?\([0-9a-f]*\)$', 1).match(text) != -1 def _real(text): if text in ('', '-', '+', '.', '-.', '+.',): return -1 elif _getRegex('^[^e]*[0-9]').search(text) == -1: return 0 else: return _getRegex( '^[-+]?[0-9]*\.?[0-9]*\([eE][-+]?[0-9]*\)?$').match(text) != -1 _from0to23 = '\([0-9]\|[0-1][0-9]\|2[0-3]\)' _from0to60 = '\([0-9]\|[0-5][0-9]\)' _time24Reg = '^' + _from0to23 + ':' + _from0to60 + ':' + _from0to60 + '$' def _time24(text): if _getRegex(_time24Reg).match(text) != -1: return 1 else: return -1 _timeNReg = '^[-+]?[0-9]+:' + _from0to60 + ':' + _from0to60 + '$' def _timeN(text): if _getRegex(_timeNReg).match(text) != -1: return 1 else: return -1 _from1to31 = '\(0?[1-9]\|[1-2][0-9]\|3[01]\)' _from1to12 = '\(0?[1-9]\|1[0-2]\)' _digits = '[0-9]+' _date_dmyReg = '^' + _from1to31 + '/' + _from1to12 + '/' + _digits + '$' _date_mdyReg = '^' + _from1to12 + '/' + _from1to31 + '/' + _digits + '$' _date_ymdReg = '^' + _digits + '/' + _from1to12 + '/' + _from1to31 + '$' def _date_dmy(text): if _getRegex(_date_dmyReg).match(text) != -1: return 1 else: return -1 def _date_mdy(text): if _getRegex(_date_mdyReg).match(text) != -1: return 1 else: return -1 def _date_ymd(text): if _getRegex(_date_ymdReg).match(text) != -1: return 1 else: return -1 _validators = { 'numeric' : _numeric, # integer >= 0 'integer' : _integer, # any integer 'hexadecimal' : _hexadecimal, # hex number (optionally with leading 0x) 'real' : _real, # number with or without a decimal point 'alphabetic' : _alphabetic, # letters a-zA-Z 'alphanumeric' : _alphanumeric, # letters a-zA-Z and digits 'timeN' : _timeN, # HH:MM:SS 'time24' : _time24, # HH:MM:SS (between 00:00:00 and 23:59:59) 'date_dmy' : _date_dmy, # DD/MM/YY 'date_mdy' : _date_mdy, # MM/DD/YY 'date_ymd' : _date_ymd, # YY/MM/DD } _regexCache = {} def _getRegex(pattern, ignoreCase = 0): global _regexCache item = pattern, ignoreCase if not _regexCache.has_key(item): if ignoreCase: _regexCache[item] = regex.compile(pattern, regex.casefold) else: _regexCache[item] = regex.compile(pattern) return _regexCache[item] _entryCache = {} def _registerEntryField(entry, entryField): # Register an EntryField widget for an Entry widget _entryCache[entry] = entryField def _preProcess(event, *args): # Forward preprocess events for an Entry to it's EntryField _entryCache[event.widget]._preProcess() def _postProcess(event, *args): # Forward postprocess events for an Entry to it's EntryField _entryCache[event.widget]._postProcess() class EntryField(Pmw.MegaWidget): _classBindingsDefined = 0 def __init__(self, parent = None, **kw): # Define the megawidget options. INITOPT = Pmw.INITOPT optiondefs = ( ('command', None, None), ('errorbackground', 'pink', None), ('invalidcommand', self.bell, None), ('labelmargin', 0, INITOPT), ('labelpos', None, INITOPT), ('maxwidth', 0, self._maxwidth), ('modifiedcommand', None, None), ('validate', None, self._validate), ('value', '', INITOPT), ) self.defineoptions(kw, optiondefs) # Initialise the base class (after defining the options). Pmw.MegaWidget.__init__(self, parent) # Create the components. interior = self.interior() self._entryFieldEntry = self.createcomponent('entry', (), None, Tkinter.Entry, (interior,)) self._entryFieldEntry.grid(column=2, row=2, sticky='nsew') self._entryFieldEntry.insert(0, self['value']) interior.grid_columnconfigure(2, weight=1) interior.grid_rowconfigure(2, weight=1) self.createlabel(interior) # Initialise instance variables. self.normalBackground = None self._validator = None self._previousText = None # Initialise instance. _registerEntryField(self._entryFieldEntry, self) # establish the special class bindings if not already done if not EntryField._classBindingsDefined: tagList = self._entryFieldEntry.bindtags() allSequences = {} for tag in tagList: # bind_class returns a string, not a tuple tk = self._hull.tk sequences = tk.splitlist(self.bind_class(tag)) for sequence in sequences: allSequences[sequence] = None for sequence in allSequences.keys(): self.bind_class('EntryFieldPre', sequence, _preProcess) self.bind_class('EntryFieldPost', sequence, _postProcess) EntryField._classBindingsDefined = 1 self._entryFieldEntry.bindtags(('EntryFieldPre',) + self._entryFieldEntry.bindtags() + ('EntryFieldPost',)) self._entryFieldEntry.bind('<Return>', self._executeCommand) # Check keywords and initialise options. self.initialiseoptions(EntryField) def _maxwidth(self): maxwidth = self['maxwidth'] if type(maxwidth) != types.IntType or maxwidth < 0: raise ValueError, '"maxwidth" option should be non-negative integer' self._previousText = None self._checkValidity() def _validate(self): val = self['validate'] if _validators.has_key(val): self._validator = _validators[val] elif callable(val): self._validator = val elif not val: self._validator = None else: validValues = _validators.keys() validValues.sort() raise ValueError, \ 'bad validate value "%s": must be a function or one of %s' \ % (val, validValues) self._previousText = None self._checkValidity() def _executeCommand(self, event): cmd = self['command'] if callable(cmd): # Let command return 'break'. This is useful if the # EntryField is in a Dialog and hitting Return in the # EntryField should not invoke the Dialog's default # button. return cmd() def _preProcess(self): self._previousText = self._entryFieldEntry.get() self._previousICursor = self._entryFieldEntry.index('insert') if self._entryFieldEntry.selection_present(): self._previousSel= (self._entryFieldEntry.index('sel.first'), self._entryFieldEntry.index('sel.last')) else: self._previousSel = None def _postProcess(self): # No need to check if text has not changed. previousText = self._previousText if previousText == self._entryFieldEntry.get(): return self.valid() valid = self._checkValidity() cmd = self['modifiedcommand'] if callable(cmd) and previousText != self._entryFieldEntry.get(): cmd() return valid def _getValidity(self): input = self._entryFieldEntry.get() if self['maxwidth'] and len(input) > self['maxwidth']: return 0 elif self._validator: return self._validator(input) else: return 1 def _checkValidity(self): valid = self._getValidity() oldValidity = valid if valid == 0: # The entry is invalid. cmd = self['invalidcommand'] if callable(cmd): cmd() # Restore the entry to its previous value. if self._previousText is not None: self.__setEntry(self._previousText) self._entryFieldEntry.icursor(self._previousICursor) if self._previousSel is not None: self._entryFieldEntry.selection_range(self._previousSel[0], self._previousSel[1]) # Check if the saved text is valid as well. valid = self._getValidity() self._valid = valid if valid == 1: if self.normalBackground is not None: self._entryFieldEntry.configure( background = self.normalBackground) self.normalBackground = None else: if self.normalBackground is None: self.normalBackground = self._entryFieldEntry.cget('background') self._entryFieldEntry.configure( background = self['errorbackground']) return oldValidity def invoke(self): cmd = self['command'] if callable(cmd): cmd() def valid(self): return self._valid == 1 def clear(self): self._entryFieldEntry.delete(0, 'end') def __setEntry(self, text): disabled = (self._entryFieldEntry.cget('state') == 'disabled') if disabled: self._entryFieldEntry.configure(state='normal') self._entryFieldEntry.delete(0, 'end') self._entryFieldEntry.insert(0, text) if disabled: self._entryFieldEntry.configure(state='disabled') def setentry(self, text): self._preProcess() self.__setEntry(text) return self._postProcess() Pmw.forwardmethods(EntryField, Tkinter.Entry, '_entryFieldEntry')