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