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