# Authors: Joe VanAndel and Greg McFarlane

import string
import sys
import time
import Tkinter
import Pmw

class TimeCounter(Pmw.MegaWidget):
    """Up-down counter

    A TimeCounter is a single-line entry widget with Up and Down arrows
    which increment and decrement the Time value in the entry.  
    """

    def __init__(self, parent = None, **kw):

	# Define the megawidget options.
	INITOPT = Pmw.INITOPT
	optiondefs = (
	    ('autorepeat',    1,    INITOPT),
	    ('buttonaspect',  1.0,  INITOPT),
	    ('initwait',      300,  INITOPT),
	    ('labelmargin',   0,    INITOPT),
	    ('labelpos',      None, INITOPT),
	    ('max',           '',   self._max),
	    ('min',           '',   self._min),
	    ('padx',          0,    INITOPT),
	    ('pady',          0,    INITOPT),
	    ('repeatrate',    50,   INITOPT),
	    ('value',         '',   INITOPT),
	)
	self.defineoptions(kw, optiondefs)

	# Initialise the base class (after defining the options).
	Pmw.MegaWidget.__init__(self, parent)

    	self.arrowDirection = {}
	self._flag = 'stopped'
	self._timerId = None

	self._createComponents()

	value = self['value']
	if value is None or value == '':
	    now = time.time()
	    value = time.strftime('%H:%M:%S',time.gmtime(now))
    	self._setTimeFromStr(value)

	# Check keywords and initialise options.
	self.initialiseoptions(TimeCounter)

    def _createComponents(self):

	# 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 buttons.

	# Create the hour down arrow.
	self._downHourArrowBtn = self.createcomponent('downhourarrow',
		(), 'Arrow',
		Tkinter.Canvas, (frame,),
		width = 16, height = 16, relief = 'raised', borderwidth = 2)
    	self.arrowDirection[self._downHourArrowBtn] = 0
	self._downHourArrowBtn.grid(column = 0, row = 2)

	# Create the minute down arrow.
	self._downMinuteArrowBtn = self.createcomponent('downminutearrow',
		(), 'Arrow',
		Tkinter.Canvas, (frame,),
		width = 16, height = 16, relief = 'raised', borderwidth = 2)
    	self.arrowDirection[self._downMinuteArrowBtn] = 0
	self._downMinuteArrowBtn.grid(column = 1, row = 2)

	# Create the second down arrow.
	self._downSecondArrowBtn = self.createcomponent('downsecondarrow',
		(), 'Arrow',
		Tkinter.Canvas, (frame,),
		width = 16, height = 16, relief = 'raised', borderwidth = 2)
    	self.arrowDirection[self._downSecondArrowBtn] = 0
	self._downSecondArrowBtn.grid(column = 2, row = 2)

	# Create the entry fields.

	# Create the hour entry field.
	self._hourCounterEntry = self.createcomponent('hourentryfield',
		(('hourentry', 'hourentryfield_entry'),), None,
		Pmw.EntryField, (frame,), validate='integer', entry_width = 2)
	self._hourCounterEntry.grid(column = 0, row = 1, sticky = 'news')

	# Create the minute entry field.
	self._minuteCounterEntry = self.createcomponent('minuteentryfield',
		(('minuteentry', 'minuteentryfield_entry'),), None,
		Pmw.EntryField, (frame,), validate='integer', entry_width = 2)
	self._minuteCounterEntry.grid(column = 1, row = 1, sticky = 'news')

	# Create the second entry field.
	self._secondCounterEntry = self.createcomponent('secondentryfield',
		(('secondentry', 'secondentryfield_entry'),), None,
		Pmw.EntryField, (frame,), validate='integer', entry_width = 2)
	self._secondCounterEntry.grid(column = 2, row = 1, sticky = 'news')

	# Create the up arrow buttons.

	# Create the hour up arrow.
	self._upHourArrowBtn = self.createcomponent('uphourarrow',
		(), 'Arrow',
		Tkinter.Canvas, (frame,),
		width = 16, height = 16, relief = 'raised', borderwidth = 2)
    	self.arrowDirection[self._upHourArrowBtn] = 1
	self._upHourArrowBtn.grid(column = 0, row = 0)

	# Create the minute up arrow.
	self._upMinuteArrowBtn = self.createcomponent('upminutearrow',
		(), 'Arrow',
		Tkinter.Canvas, (frame,),
		width = 16, height = 16, relief = 'raised', borderwidth = 2)
    	self.arrowDirection[self._upMinuteArrowBtn] = 1
	self._upMinuteArrowBtn.grid(column = 1, row = 0)

	# Create the second up arrow.
	self._upSecondArrowBtn = self.createcomponent('upsecondarrow',
		(), 'Arrow',
		Tkinter.Canvas, (frame,),
		width = 16, height = 16, relief = 'raised', borderwidth = 2)
    	self.arrowDirection[self._upSecondArrowBtn] = 1
	self._upSecondArrowBtn.grid(column = 2, row = 0)

	# Make it resize nicely.
	padx = self['padx']
	pady = self['pady']
	for col in range(3):
	    frame.grid_columnconfigure(col, weight = 1, pad = padx)
	frame.grid_rowconfigure(0, pad = pady)
	frame.grid_rowconfigure(2, pad = pady)

	frame.grid_rowconfigure(1, weight = 1)

	# Create the label.
	self.createlabel(interior)

	# Set bindings.

	# Up hour
	self._upHourArrowBtn.bind('<Configure>', 
		lambda  event, s=self,button=self._upHourArrowBtn: 
		s._drawArrow(button, 1))

	self._upHourArrowBtn.bind('<1>', 
    	    	lambda event, s=self,button=self._upHourArrowBtn: 
		s._countUp(button, 3600))

	self._upHourArrowBtn.bind('<Any-ButtonRelease-1>', 
		lambda event, s=self, button=self._upHourArrowBtn:
		s._stopUpDown(button))

	# Up minute
	self._upMinuteArrowBtn.bind('<Configure>', 
		lambda  event, s=self,button=self._upMinuteArrowBtn: 
		s._drawArrow(button, 1))
	    

	self._upMinuteArrowBtn.bind('<1>', 
    	    	lambda event, s=self,button=self._upMinuteArrowBtn: 
		s._countUp(button, 60))

	self._upMinuteArrowBtn.bind('<Any-ButtonRelease-1>', 
		lambda event, s=self, button=self._upMinuteArrowBtn:
		s._stopUpDown(button))

	# Up second
	self._upSecondArrowBtn.bind('<Configure>', 
		lambda  event, s=self,button=self._upSecondArrowBtn: 
		s._drawArrow(button, 1))
	    

	self._upSecondArrowBtn.bind('<1>', 
    	    	lambda event, s=self,button=self._upSecondArrowBtn: 
		s._countUp(button, 1))

	self._upSecondArrowBtn.bind('<Any-ButtonRelease-1>', 
		lambda event, s=self, button=self._upSecondArrowBtn:
		s._stopUpDown(button))

	# Down hour
	self._downHourArrowBtn.bind('<Configure>', 
		lambda  event, s=self,button=self._downHourArrowBtn: 
		s._drawArrow(button, 0))

	self._downHourArrowBtn.bind('<1>', 
    	    	lambda event, s=self,button=self._downHourArrowBtn: 
		s._countDown(button, 3600))
	self._downHourArrowBtn.bind('<Any-ButtonRelease-1>', 
		lambda event, s=self, button=self._downHourArrowBtn:
		s._stopUpDown(button))


	# Down minute
	self._downMinuteArrowBtn.bind('<Configure>', 
		lambda  event, s=self,button=self._downMinuteArrowBtn: 
		s._drawArrow(button, 0))

	self._downMinuteArrowBtn.bind('<1>', 
    	    	lambda event, s=self,button=self._downMinuteArrowBtn: s._countDown(button, 60))
	self._downMinuteArrowBtn.bind('<Any-ButtonRelease-1>', 
		lambda event, s=self, button=self._downMinuteArrowBtn:
		s._stopUpDown(button))

	# Down second
	self._downSecondArrowBtn.bind('<Configure>', 
		lambda  event, s=self,button=self._downSecondArrowBtn: 
		s._drawArrow(button, 0))

	self._downSecondArrowBtn.bind('<1>', 
    	    	lambda event, s=self, button=self._downSecondArrowBtn: 
		s._countDown(button,1))
	self._downSecondArrowBtn.bind('<Any-ButtonRelease-1>', 
		lambda event, s=self, button=self._downSecondArrowBtn:
		s._stopUpDown(button))

	self._hourCounterEntry.bind('<Return>', self.invoke)
	self._minuteCounterEntry.bind('<Return>', self.invoke)
	self._secondCounterEntry.bind('<Return>', self.invoke)

	self._hourCounterEntry.bind('<Configure>', self._resizeArrow)
	self._minuteCounterEntry.bind('<Configure>', self._resizeArrow)
	self._secondCounterEntry.bind('<Configure>', self._resizeArrow)

    def _drawArrow(self, arrow, direction):
	arrow.delete('arrow')

	fg = self._hourCounterEntry.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 direction == 0:
    	     # down arrow
	     arrow.create_polygon(
		 0.25 * w + bw, 0.25 * h + bw,
	         0.50 * w + bw, 0.75 * h + bw,
	         0.75 * w + bw, 0.25 * h + bw,
		 fill=fg, tag='arrow')
	else:
	     arrow.create_polygon(
	         0.25 * w + bw, 0.75 * h + bw,
		 0.50 * w + bw, 0.25 * h + bw,
	         0.75 * w + bw, 0.75 * h + bw,
		 fill=fg, tag='arrow')

    def _resizeArrow(self, event = None):
	for btn in (self._upHourArrowBtn, self._upMinuteArrowBtn,
		self._upSecondArrowBtn,
		self._downHourArrowBtn,
		self._downMinuteArrowBtn, self._downSecondArrowBtn):
	    bw = (string.atoi(btn['borderwidth']) + \
		    string.atoi(btn['highlightthickness']))
	    newHeight = self._hourCounterEntry.winfo_reqheight() - 2 * bw
	    newWidth = newHeight * self['buttonaspect']
	    btn.configure(width=newWidth, height=newHeight)
	    self._drawArrow(btn, self.arrowDirection[btn])

    def _min(self):
	min = self['min']
        if min == '':
	    self._minVal = 0
	else:
	    self._minVal = Pmw.timestringtoseconds(min)

    def _max(self):
	max = self['max']
	if max != '':
	    self._maxVal = Pmw.timestringtoseconds(max)
	else:
	    self._maxVal = None


    def _setTimeFromStr(self, str):
        list = string.split(str, ':')
	if len(list) != 3:
	    raise TypeError, 'invalid value: ' + str

	self._hour = string.atoi(list[0])
	self._minute = string.atoi(list[1])
	self._second = string.atoi(list[2]) 

    	self._setHMS()

    def getstring(self):
    	return '%02d:%02d:%02d' % (self._hour, self._minute, self._second)

    def getint(self):
    	return self._hour * 3600 + self._minute * 60 + self._second

    def _countUp(self, button, increment):
	self._relief = self._upHourArrowBtn.cget('relief')
	button.configure(relief='sunken')
	self._count(1, 'start', increment)

    def _countDown(self, button, increment):

	self._relief = self._downHourArrowBtn.cget('relief')
	button.configure(relief='sunken')
	self._count(-1, 'start', increment)

    def increment(self):
	self._count(1, 'force')

    def decrement(self):
	self._count(-1, 'force')

    def _count(self, factor, newFlag=None,increment=1):
	if newFlag != 'force':
	  if newFlag is not None:
	    self._flag = newFlag

	  if self._flag == 'stopped':
	    return

	value = (string.atoi(self._hourCounterEntry.get()) *3600) + \
	      (string.atoi(self._minuteCounterEntry.get()) *60) + \
	      string.atoi(self._secondCounterEntry.get()) + \
	      factor * increment
	min = self._minVal
	max = self._maxVal
	if value < min:
	  value = min
	if max is not None and value > max:
	  value = max

	self._hour = value /3600
	self._minute = (value - (self._hour*3600)) / 60
	self._second = value - (self._hour*3600) - (self._minute*60)
	self._setHMS()

	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,increment=increment: 
		  self._count(factor,'running', increment))

    def _setHMS(self):
        self._hourCounterEntry.setentry('%02d' % self._hour)
        self._minuteCounterEntry.setentry('%02d' % self._minute)
        self._secondCounterEntry.setentry('%02d' % self._second)

    def _stopUpDown(self, button):
        if self._timerId is not None:
            self.after_cancel(self._timerId)
	    self._timerId = None
        button.configure(relief=self._relief)
        self._flag = 'stopped'

    def invoke(self, event = None):
        cmd = self['command']
        if callable(cmd):
	    cmd()

    def destroy(self):
        if self._timerId is not None:
            self.after_cancel(self._timerId)
	    self._timerId = None
        Pmw.MegaWidget.destroy(self)