import string
import sys
import Tkinter
import Pmw
def _changeNumber(text, factor, increment, min, max):
value = string.atoi(text)
if factor > 0:
if max != '' and value >= max:
raise ValueError
value = (value / increment) * increment + increment
else:
if min != '' and value <= min:
raise ValueError
value = ((value - 1) / increment) * increment
if min != '' and value < min:
value = min
if max != '' and value > max:
value = max
return str(value)
def _changeReal(text, factor, increment, min, max):
value = string.atof(text)
if factor > 0:
if max != '' and value >= max:
raise ValueError
else:
if min != '' and value <= min:
raise ValueError
# Compare reals using str() to avoid problems caused by binary
# numbers being only approximations to decimal numbers.
div = int(value / increment)
text = str(value)
if text == str(div * increment) or text == str((div + 1) * increment):
value = value + factor * increment
elif factor > 0:
value = (div + 1) * increment
else:
value = div * increment
if min != '' and value < min:
value = min
if max != '' and value > max:
value = max
return str(value)
def _changeDate_formatted(format, value, factor, increment, min, max, yyyy):
jdn = datestringtojdn(value, format) + factor * increment
if min != '':
min = datestringtojdn(min, format)
if jdn < min:
jdn = min
if max != '':
max = datestringtojdn(max, format)
if jdn > max:
jdn = max
y, m, d = jdntoymd(jdn)
result = ''
for index in range(3):
if index > 0:
result = result + '/'
f = format[index]
if f == 'y':
if yyyy:
result = result + '%02d' % y
else:
result = result + '%02d' % (y % 100)
elif f == 'm':
result = result + '%02d' % m
elif f == 'd':
result = result + '%02d' % d
return result
def _changeDate_dmy(value, factor, increment, min, max):
return _changeDate_formatted('dmy', value, factor, increment, min, max, 0)
def _changeDate_mdy(value, factor, increment, min, max):
return _changeDate_formatted('mdy', value, factor, increment, min, max, 0)
def _changeDate_ymd(value, factor, increment, min, max):
return _changeDate_formatted('ymd', value, factor, increment, min, max, 0)
def _changeDate_dmy4(value, factor, increment, min, max):
return _changeDate_formatted('dmy', value, factor, increment, min, max, 1)
def _changeDate_mdy4(value, factor, increment, min, max):
return _changeDate_formatted('mdy', value, factor, increment, min, max, 1)
def _changeDate_y4md(value, factor, increment, min, max):
return _changeDate_formatted('ymd', value, factor, increment, min, max, 1)
def _changeTime24(value, factor, increment, min, max):
return _changeTimeN(value, factor, increment, min, max, 1)
_SECSPERDAY = 24 * 60 * 60
def _changeTimeN(value, factor, increment, min, max, time24 = 0):
unixTime = timestringtoseconds(value)
if factor > 0:
chunks = unixTime / increment + 1
else:
chunks = (unixTime - 1) / increment
unixTime = chunks * increment
if time24:
while unixTime < 0:
unixTime = unixTime + _SECSPERDAY
while unixTime >= _SECSPERDAY:
unixTime = unixTime - _SECSPERDAY
if min != '':
min = timestringtoseconds(min)
if unixTime < min:
unixTime = min
if max != '':
max = timestringtoseconds(max)
if unixTime > max:
unixTime = max
if unixTime < 0:
unixTime = -unixTime
sign = '-'
else:
sign = ''
secs = unixTime % 60
unixTime = unixTime / 60
mins = unixTime % 60
hours = unixTime / 60
return '%s%02d:%02d:%02d' % (sign, hours, mins, secs)
def timestringtoseconds(text):
inputList = string.split(text, ':')
if len(inputList) != 3:
raise TypeError, 'invalid value: ' + text
if inputList[0][0] == '-':
inputList[0] = inputList[0][1:]
sign = -1
else:
sign = 1
hour = string.atoi(inputList[0])
minute = string.atoi(inputList[1])
second = string.atoi(inputList[2])
return sign * (hour * 60 * 60 + minute * 60 + second)
_year_pivot = 50
_century = 2000
def setyearpivot(pivot, century = None):
global _year_pivot
_year_pivot = pivot
if century is not None:
global _century
_century = century
def datestringtojdn(text, format):
inputList = string.split(text, '/')
if len(inputList) != 3:
raise TypeError, 'invalid value: ' + text
if format not in ('dmy', 'mdy', 'ymd'):
raise TypeError, 'invalid format: ' + format
formatList = list(format)
day = string.atoi(inputList[formatList.index('d')])
month = string.atoi(inputList[formatList.index('m')])
year = string.atoi(inputList[formatList.index('y')])
if _year_pivot is not None:
if year >= 0 and year < 100:
if year <= _year_pivot:
year = year + _century
else:
year = year + _century - 100
return ymdtojdn(year, month, day)
def _cdiv(a, b):
# Return a / b as calculated by most C language implementations,
# assuming both a and b are integers.
if a * b > 0:
return a / b
else:
return -(abs(a) / abs(b))
def ymdtojdn(y, m, d, julian = -1, papal = 1):
# set Julian flag if auto set
if julian < 0:
if papal: # Pope Gregory XIII's decree
lastJulianDate = 15821004L # last day to use Julian calendar
else: # British-American usage
lastJulianDate = 17520902L # last day to use Julian calendar
julian = ((y * 100L) + m) * 100 + d <= lastJulianDate
if y < 0:
# Adjust BC year
y = y + 1
if julian:
return 367L * y - _cdiv(7 * (y + 5001L + _cdiv((m - 9), 7)), 4) + \
_cdiv(275 * m, 9) + d + 1729777L
else:
return (d - 32076L) + \
_cdiv(1461L * (y + 4800L + _cdiv((m - 14), 12)), 4) + \
_cdiv(367 * (m - 2 - _cdiv((m - 14), 12) * 12), 12) - \
_cdiv((3 * _cdiv((y + 4900L + _cdiv((m - 14), 12)), 100)), 4) + \
1 # correction by rdg
def jdntoymd(jdn, julian = -1, papal = 1):
# set Julian flag if auto set
if julian < 0:
if papal: # Pope Gregory XIII's decree
lastJulianJdn = 2299160L # last jdn to use Julian calendar
else: # British-American usage
lastJulianJdn = 2361221L # last jdn to use Julian calendar
julian = (jdn <= lastJulianJdn);
x = jdn + 68569L
if julian:
x = x + 38
daysPer400Years = 146100L
fudgedDaysPer4000Years = 1461000L + 1
else:
daysPer400Years = 146097L
fudgedDaysPer4000Years = 1460970L + 31
z = _cdiv(4 * x, daysPer400Years)
x = x - _cdiv((daysPer400Years * z + 3), 4)
y = _cdiv(4000 * (x + 1), fudgedDaysPer4000Years)
x = x - _cdiv(1461 * y, 4) + 31
m = _cdiv(80 * x, 2447)
d = x - _cdiv(2447 * m, 80)
x = _cdiv(m, 11)
m = m + 2 - 12 * x
y = 100 * (z - 49) + y + x
# Convert from longs to integers.
yy = int(y)
mm = int(m)
dd = int(d)
if yy <= 0:
# Adjust BC years.
yy = yy - 1
return (yy, mm, dd)
# hexadecimal, alphabetic, alphanumeric not implemented
_counterCommands = {
'numeric' : _changeNumber, # } integer
'integer' : _changeNumber, # } these two use the same function
'real' : _changeReal, # real number
'timeN' : _changeTimeN, # HH:MM:SS
'time24' : _changeTime24, # HH:MM:SS (wraps around at 23:59:59)
'date_dmy' : _changeDate_dmy, # DD/MM/YY
'date_mdy' : _changeDate_mdy, # MM/DD/YY
'date_ymd' : _changeDate_ymd, # YY/MM/DD
'date_dmy4' : _changeDate_dmy4, # DD/MM/YYYY
'date_mdy4' : _changeDate_mdy4, # MM/DD/YYYY
'date_y4md' : _changeDate_y4md, # YYYY/MM/DD
}
class Counter(Pmw.MegaWidget):
# Up-down counter
# A Counter is a single-line entry widget with Up and Down arrows
# which increment and decrement the value in the entry. By
# default, may be used for entering dates, times, numbers. User
# defined functions may be specified for specialised counting.
def __init__(self, parent = None, **kw):
# Define the megawidget options.
INITOPT = Pmw.INITOPT
optiondefs = (
('autorepeat', 1, INITOPT),
('buttonaspect', 1.0, INITOPT),
('datatype', 'numeric', self._datatype),
('increment', 1, None),
('initwait', 300, INITOPT),
('labelmargin', 0, INITOPT),
('labelpos', None, INITOPT),
('max', '', None),
('min', '', None),
('orient', 'horizontal', INITOPT),
('padx', 0, INITOPT),
('pady', 0, INITOPT),
('repeatrate', 50, INITOPT),
)
self.defineoptions(kw, optiondefs)
# Initialise the base class (after defining the options).
Pmw.MegaWidget.__init__(self, parent)
# Create the components.
interior = self.interior()
# If there is no label, put the arrows and the entry directly
# into the interior, otherwise create a frame for them. In
# either case the border around the arrows and the entry will
# be raised (but not around the label).
if self['labelpos'] is None:
frame = interior
else:
frame = self.createcomponent('frame',
(), None,
Tkinter.Frame, (interior,))
frame.grid(column=2, row=2, sticky='nsew')
interior.grid_columnconfigure(2, weight=1)
interior.grid_rowconfigure(2, weight=1)
frame.configure(relief = 'raised', borderwidth = 1)
# Create the down arrow.
self._downArrowBtn = self.createcomponent('downarrow',
(), 'Arrow',
Tkinter.Canvas, (frame,),
width = 16, height = 16, relief = 'raised', borderwidth = 2)
# Create the entry field.
self._counterEntry = self.createcomponent('entryfield',
(('entry', 'entryfield_entry'),), None,
Pmw.EntryField, (frame,))
# Create the up arrow.
self._upArrowBtn = self.createcomponent('uparrow',
(), 'Arrow',
Tkinter.Canvas, (frame,),
width = 16, height = 16, relief = 'raised', borderwidth = 2)
padx = self['padx']
pady = self['pady']
if self['orient'] == 'horizontal':
self._downArrowBtn.grid(column = 0, row = 0)
self._counterEntry.grid(column = 1, row = 0, sticky = 'news')
self._upArrowBtn.grid(column = 2, row = 0)
frame.grid_columnconfigure(1, weight = 1)
frame.grid_rowconfigure(0, weight = 1)
if Tkinter.TkVersion >= 4.2:
frame.grid_columnconfigure(0, pad = padx)
frame.grid_columnconfigure(2, pad = padx)
frame.grid_rowconfigure(0, pad = pady)
else:
self._upArrowBtn.grid(column = 0, row = 0)
self._counterEntry.grid(column = 0, row = 1, sticky = 'news')
self._downArrowBtn.grid(column = 0, row = 2)
frame.grid_columnconfigure(0, weight = 1)
frame.grid_rowconfigure(1, weight = 1)
if Tkinter.TkVersion >= 4.2:
frame.grid_rowconfigure(0, pad = pady)
frame.grid_rowconfigure(2, pad = pady)
frame.grid_columnconfigure(0, pad = padx)
self.createlabel(interior)
self._upArrowBtn.bind('<Configure>', self._drawUpArrow)
self._upArrowBtn.bind('<1>', self._countUp)
self._upArrowBtn.bind('<Any-ButtonRelease-1>', self._stopUp)
self._downArrowBtn.bind('<Configure>', self._drawDownArrow)
self._downArrowBtn.bind('<1>', self._countDown)
self._downArrowBtn.bind('<Any-ButtonRelease-1>', self._stopDown)
self._counterEntry.bind('<Configure>', self._resizeArrow)
entry = self._counterEntry.component('entry')
entry.bind('<Down>', lambda event, s = self: s.decrement())
entry.bind('<Up>', lambda event, s = self: s.increment())
# Initialise instance variables.
self._flag = 'stopped'
self._timerId = None
# Check keywords and initialise options.
self.initialiseoptions(Counter)
def _resizeArrow(self, event):
for btn in (self._upArrowBtn, self._downArrowBtn):
bw = (string.atoi(btn['borderwidth']) + \
string.atoi(btn['highlightthickness']))
newHeight = self._counterEntry.winfo_reqheight() - 2 * bw
newWidth = newHeight * self['buttonaspect']
btn.configure(width=newWidth, height=newHeight)
self._drawArrow(btn)
def _drawUpArrow(self, event):
self._drawArrow(self._upArrowBtn)
def _drawDownArrow(self, event):
self._drawArrow(self._downArrowBtn)
def _drawArrow(self, arrow):
arrow.delete('arrow')
fg = self._counterEntry.cget('entry_foreground')
bw = (string.atoi(arrow['borderwidth']) +
string.atoi(arrow['highlightthickness'])) / 2
h = string.atoi(arrow['height']) + 2 * bw
w = string.atoi(arrow['width']) + 2 * bw
if arrow == self._downArrowBtn:
if self['orient'] == 'horizontal':
arrow.create_polygon(
0.25 * w + bw, 0.5 * h + bw,
0.75 * w + bw, 0.25 * h + bw,
0.75 * w + bw, 0.75 * h + bw,
fill=fg, tag='arrow')
else:
arrow.create_polygon(
0.25 * w + bw, 0.25 * h + bw,
0.75 * w + bw, 0.25 * h + bw,
0.50 * w + bw, 0.75 * h + bw,
fill=fg, tag='arrow')
else:
if self['orient'] == 'horizontal':
arrow.create_polygon(
0.75 * w + bw, 0.5 * h + bw,
0.25 * w + bw, 0.25 * h + bw,
0.25 * w + bw, 0.75 * h + bw,
fill=fg, tag='arrow')
else:
arrow.create_polygon(
0.5 * w + bw, 0.25 * h + bw,
0.25 * w + bw, 0.75 * h + bw,
0.75 * w + bw, 0.75 * h + bw,
fill=fg, tag='arrow')
def _countUp(self, event):
self._upRelief = self._upArrowBtn.cget('relief')
self._upArrowBtn.configure(relief='sunken')
self._count(1, 'start')
def _countDown(self, event):
self._downRelief = self._downArrowBtn.cget('relief')
self._downArrowBtn.configure(relief='sunken')
self._count(-1, 'start')
def increment(self):
self._count(1, 'force')
def decrement(self):
self._count(-1, 'force')
def _datatype(self):
datatype = self['datatype']
if _counterCommands.has_key(datatype):
self._counterCommand = _counterCommands[datatype]
elif callable(datatype):
self._counterCommand = datatype
else:
validValues = _counterCommands.keys()
validValues.sort()
raise ValueError, ('bad datatype value "%s": must be a' +
' function or one of %s') % (datatype, validValues)
def _count(self, factor, newFlag=None):
if newFlag != 'force':
if newFlag is not None:
self._flag = newFlag
if self._flag == 'stopped':
return
value = self._counterEntry.get()
try:
value = self._counterCommand(value, factor,
self['increment'], self['min'], self['max'])
except:
sys.exc_traceback = None # Clean up object references
if newFlag != 'force':
if factor == 1:
self._upArrowBtn.configure(relief=self._upRelief)
else:
self._downArrowBtn.configure(relief=self._downRelief)
self._flag = 'stopped'
self.bell()
return
# If incrementing produces an invalid value, stop counting.
if not self._counterEntry.setentry(value):
self._flag = 'stopped'
return
if newFlag != 'force':
if self['autorepeat']:
if self._flag == 'start':
delay = self['initwait']
self._flag = 'running'
else:
delay = self['repeatrate']
self._timerId = self.after(
delay, lambda self=self, factor=factor: self._count(factor))
def _stopUp(self, event):
if self._timerId is not None:
self.after_cancel(self._timerId)
self._timerId = None
self._upArrowBtn.configure(relief=self._upRelief)
self._flag = 'stopped'
def _stopDown(self, event):
if self._timerId is not None:
self.after_cancel(self._timerId)
self._timerId = None
self._downArrowBtn.configure(relief=self._downRelief)
self._flag = 'stopped'
def destroy(self):
if self._timerId is not None:
self.after_cancel(self._timerId)
self._timerId = None
Pmw.MegaWidget.destroy(self)
Pmw.forwardmethods(Counter, Pmw.EntryField, '_counterEntry')