# 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')