historical/toontown-classic.git/panda/Pmw/Pmw_1_3/lib/PmwEntryField.py

459 lines
13 KiB
Python
Raw Normal View History

2024-01-16 11:20:27 -06:00
# Based on iwidgets2.2.0/entryfield.itk code.
import re
import string
import types
import Tkinter
import Pmw
# Possible return values of validation functions.
OK = 1
ERROR = 0
PARTIAL = -1
class EntryField(Pmw.MegaWidget):
_classBindingsDefinedFor = 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),
('modifiedcommand', None, None),
('sticky', 'ew', INITOPT),
('validate', None, self._validate),
('extravalidators', {}, None),
('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=self['sticky'])
if self['value'] != '':
self.__setEntry(self['value'])
interior.grid_columnconfigure(2, weight=1)
interior.grid_rowconfigure(2, weight=1)
self.createlabel(interior)
# Initialise instance variables.
self.normalBackground = None
self._previousText = None
# Initialise instance.
_registerEntryField(self._entryFieldEntry, self)
# Establish the special class bindings if not already done.
# Also create bindings if the Tkinter default interpreter has
# changed. Use Tkinter._default_root to create class
# bindings, so that a reference to root is created by
# bind_class rather than a reference to self, which would
# prevent object cleanup.
if EntryField._classBindingsDefinedFor != Tkinter._default_root:
tagList = self._entryFieldEntry.bindtags()
root = Tkinter._default_root
allSequences = {}
for tag in tagList:
sequences = root.bind_class(tag)
if type(sequences) is types.StringType:
# In old versions of Tkinter, bind_class returns a string
sequences = root.tk.splitlist(sequences)
for sequence in sequences:
allSequences[sequence] = None
for sequence in allSequences.keys():
root.bind_class('EntryFieldPre', sequence, _preProcess)
root.bind_class('EntryFieldPost', sequence, _postProcess)
EntryField._classBindingsDefinedFor = root
self._entryFieldEntry.bindtags(('EntryFieldPre',) +
self._entryFieldEntry.bindtags() + ('EntryFieldPost',))
self._entryFieldEntry.bind('<Return>', self._executeCommand)
# Check keywords and initialise options.
self.initialiseoptions()
def destroy(self):
_deregisterEntryField(self._entryFieldEntry)
Pmw.MegaWidget.destroy(self)
def _getValidatorFunc(self, validator, index):
# Search the extra and standard validator lists for the
# given 'validator'. If 'validator' is an alias, then
# continue the search using the alias. Make sure that
# self-referencial aliases do not cause infinite loops.
extraValidators = self['extravalidators']
traversedValidators = []
while 1:
traversedValidators.append(validator)
if extraValidators.has_key(validator):
validator = extraValidators[validator][index]
elif _standardValidators.has_key(validator):
validator = _standardValidators[validator][index]
else:
return validator
if validator in traversedValidators:
return validator
def _validate(self):
dict = {
'validator' : None,
'min' : None,
'max' : None,
'minstrict' : 1,
'maxstrict' : 1,
}
opt = self['validate']
if type(opt) is types.DictionaryType:
dict.update(opt)
else:
dict['validator'] = opt
# Look up validator maps and replace 'validator' field with
# the corresponding function.
validator = dict['validator']
valFunction = self._getValidatorFunc(validator, 0)
self._checkValidateFunction(valFunction, 'validate', validator)
dict['validator'] = valFunction
# Look up validator maps and replace 'stringtovalue' field
# with the corresponding function.
if dict.has_key('stringtovalue'):
stringtovalue = dict['stringtovalue']
strFunction = self._getValidatorFunc(stringtovalue, 1)
self._checkValidateFunction(
strFunction, 'stringtovalue', stringtovalue)
else:
strFunction = self._getValidatorFunc(validator, 1)
if strFunction == validator:
strFunction = len
dict['stringtovalue'] = strFunction
self._validationInfo = dict
args = dict.copy()
del args['validator']
del args['min']
del args['max']
del args['minstrict']
del args['maxstrict']
del args['stringtovalue']
self._validationArgs = args
self._previousText = None
if type(dict['min']) == types.StringType and strFunction is not None:
dict['min'] = apply(strFunction, (dict['min'],), args)
if type(dict['max']) == types.StringType and strFunction is not None:
dict['max'] = apply(strFunction, (dict['max'],), args)
self._checkValidity()
def _checkValidateFunction(self, function, option, validator):
# Raise an error if 'function' is not a function or None.
if function is not None and not callable(function):
extraValidators = self['extravalidators']
extra = extraValidators.keys()
extra.sort()
extra = tuple(extra)
standard = _standardValidators.keys()
standard.sort()
standard = tuple(standard)
msg = 'bad %s value "%s": must be a function or one of ' \
'the standard validators %s or extra validators %s'
raise ValueError, msg % (option, validator, standard, extra)
def _executeCommand(self, event = None):
cmd = self['command']
if callable(cmd):
if event is None:
# Return result of command for invoke() method.
return cmd()
else:
cmd()
def _preProcess(self):
self._previousText = self._entryFieldEntry.get()
self._previousICursor = self._entryFieldEntry.index('insert')
self._previousXview = self._entryFieldEntry.index('@0')
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()
if self.hulldestroyed():
# The invalidcommand called by _checkValidity() destroyed us.
return valid
cmd = self['modifiedcommand']
if callable(cmd) and previousText != self._entryFieldEntry.get():
cmd()
return valid
def checkentry(self):
# If there is a variable specified by the entry_textvariable
# option, checkentry() should be called after the set() method
# of the variable is called.
self._previousText = None
return self._postProcess()
def _getValidity(self):
text = self._entryFieldEntry.get()
dict = self._validationInfo
args = self._validationArgs
if dict['validator'] is not None:
status = apply(dict['validator'], (text,), args)
if status != OK:
return status
# Check for out of (min, max) range.
if dict['stringtovalue'] is not None:
min = dict['min']
max = dict['max']
if min is None and max is None:
return OK
val = apply(dict['stringtovalue'], (text,), args)
if min is not None and val < min:
if dict['minstrict']:
return ERROR
else:
return PARTIAL
if max is not None and val > max:
if dict['maxstrict']:
return ERROR
else:
return PARTIAL
return OK
def _checkValidity(self):
valid = self._getValidity()
oldValidity = valid
if valid == ERROR:
# The entry is invalid.
cmd = self['invalidcommand']
if callable(cmd):
cmd()
if self.hulldestroyed():
# The invalidcommand destroyed us.
return oldValidity
# Restore the entry to its previous value.
if self._previousText is not None:
self.__setEntry(self._previousText)
self._entryFieldEntry.icursor(self._previousICursor)
self._entryFieldEntry.xview(self._previousXview)
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 self.hulldestroyed():
# The validator or stringtovalue commands called by
# _checkValidity() destroyed us.
return oldValidity
if valid == OK:
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):
return self._executeCommand()
def valid(self):
return self._valid == OK
def clear(self):
self.setentry('')
def __setEntry(self, text):
oldState = str(self._entryFieldEntry.cget('state'))
if oldState != 'normal':
self._entryFieldEntry.configure(state='normal')
self._entryFieldEntry.delete(0, 'end')
self._entryFieldEntry.insert(0, text)
if oldState != 'normal':
self._entryFieldEntry.configure(state=oldState)
def setentry(self, text):
self._preProcess()
self.__setEntry(text)
return self._postProcess()
def getvalue(self):
return self._entryFieldEntry.get()
def setvalue(self, text):
return self.setentry(text)
Pmw.forwardmethods(EntryField, Tkinter.Entry, '_entryFieldEntry')
# ======================================================================
# Entry field validation functions
_numericregex = re.compile('^[0-9]*$')
_alphabeticregex = re.compile('^[a-z]*$', re.IGNORECASE)
_alphanumericregex = re.compile('^[0-9a-z]*$', re.IGNORECASE)
def numericvalidator(text):
if text == '':
return PARTIAL
else:
if _numericregex.match(text) is None:
return ERROR
else:
return OK
def integervalidator(text):
if text in ('', '-', '+'):
return PARTIAL
try:
string.atol(text)
return OK
except ValueError:
return ERROR
def alphabeticvalidator(text):
if _alphabeticregex.match(text) is None:
return ERROR
else:
return OK
def alphanumericvalidator(text):
if _alphanumericregex.match(text) is None:
return ERROR
else:
return OK
def hexadecimalvalidator(text):
if text in ('', '0x', '0X', '+', '+0x', '+0X', '-', '-0x', '-0X'):
return PARTIAL
try:
string.atol(text, 16)
return OK
except ValueError:
return ERROR
def realvalidator(text, separator = '.'):
if separator != '.':
if string.find(text, '.') >= 0:
return ERROR
index = string.find(text, separator)
if index >= 0:
text = text[:index] + '.' + text[index + 1:]
try:
string.atof(text)
return OK
except ValueError:
# Check if the string could be made valid by appending a digit
# eg ('-', '+', '.', '-.', '+.', '1.23e', '1E-').
if len(text) == 0:
return PARTIAL
if text[-1] in string.digits:
return ERROR
try:
string.atof(text + '0')
return PARTIAL
except ValueError:
return ERROR
def timevalidator(text, separator = ':'):
try:
Pmw.timestringtoseconds(text, separator)
return OK
except ValueError:
if len(text) > 0 and text[0] in ('+', '-'):
text = text[1:]
if re.search('[^0-9' + separator + ']', text) is not None:
return ERROR
return PARTIAL
def datevalidator(text, format = 'ymd', separator = '/'):
try:
Pmw.datestringtojdn(text, format, separator)
return OK
except ValueError:
if re.search('[^0-9' + separator + ']', text) is not None:
return ERROR
return PARTIAL
_standardValidators = {
'numeric' : (numericvalidator, string.atol),
'integer' : (integervalidator, string.atol),
'hexadecimal' : (hexadecimalvalidator, lambda s: string.atol(s, 16)),
'real' : (realvalidator, Pmw.stringtoreal),
'alphabetic' : (alphabeticvalidator, len),
'alphanumeric' : (alphanumericvalidator, len),
'time' : (timevalidator, Pmw.timestringtoseconds),
'date' : (datevalidator, Pmw.datestringtojdn),
}
_entryCache = {}
def _registerEntryField(entry, entryField):
# Register an EntryField widget for an Entry widget
_entryCache[entry] = entryField
def _deregisterEntryField(entry):
# Deregister an Entry widget
del _entryCache[entry]
def _preProcess(event):
# Forward preprocess events for an Entry to it's EntryField
_entryCache[event.widget]._preProcess()
def _postProcess(event):
# Forward postprocess events for an Entry to it's EntryField
# The function specified by the 'command' option may have destroyed
# the megawidget in a binding earlier in bindtags, so need to check.
if _entryCache.has_key(event.widget):
_entryCache[event.widget]._postProcess()