1# Authors: Joe VanAndel, Greg McFarlane and Daniel Michelson
2
3import string
4import sys
5import time
6import tkinter
7import Pmw
8import collections
9
10class FullTimeCounter(Pmw.MegaWidget):
11    """Up-down counter
12
13    A TimeCounter is a single-line entry widget with Up and Down arrows
14    which increment and decrement the Time value in the entry.
15    """
16
17    def __init__(self, parent = None, **kw):
18
19        # Define the megawidget options.
20        INITOPT = Pmw.INITOPT
21        optiondefs = (
22            ('autorepeat',    1,    INITOPT),
23            ('buttonaspect',  1.0,  INITOPT),
24            ('initwait',      300,  INITOPT),
25            ('labelmargin',   0,    INITOPT),
26            ('labelpos',      None, INITOPT),
27            ('max',           '',   self._max),
28            ('min',           '',   self._min),
29            ('padx',          0,    INITOPT),
30            ('pady',          0,    INITOPT),
31            ('repeatrate',    50,   INITOPT),
32            ('value',         '',   INITOPT),
33        )
34        self.defineoptions(kw, optiondefs)
35
36        # Initialise the base class (after defining the options).
37        Pmw.MegaWidget.__init__(self, parent)
38
39        self.arrowDirection = {}
40        self._flag = 'stopped'
41        self._timerId = None
42
43        self._createComponents()
44
45        value = self['value']
46        if value is None or value == '':
47            now = time.time()
48            value = time.strftime('%Y:%m:%d:%H:%M',time.gmtime(now))
49        self._setTimeFromStr(value)
50
51        # Check keywords and initialise options.
52        self.initialiseoptions()
53
54    def _createComponents(self):
55
56        # Create the components.
57        interior = self.interior()
58
59        # If there is no label, put the arrows and the entry directly
60        # into the interior, otherwise create a frame for them.  In
61        # either case the border around the arrows and the entry will
62        # be raised (but not around the label).
63        if self['labelpos'] is None:
64            frame = interior
65        else:
66            frame = self.createcomponent('frame',
67                    (), None,
68                    tkinter.Frame, (interior,))
69            frame.grid(column=2, row=2, sticky='nsew')
70            interior.grid_columnconfigure(2, weight=1)
71            interior.grid_rowconfigure(2, weight=1)
72
73        frame.configure(relief = 'raised', borderwidth = 1)
74
75        # Create the down arrow buttons.
76
77        # Create the year down arrow.
78        self._downYearArrowBtn = self.createcomponent('downyeararrow',
79                (), 'Arrow',
80                tkinter.Canvas, (frame,),
81                width = 16, height = 16, relief = 'raised', borderwidth = 2)
82        self.arrowDirection[self._downYearArrowBtn] = 0
83        self._downYearArrowBtn.grid(column = 0, row = 2)
84
85        # Create the month down arrow.
86        self._downMonthArrowBtn = self.createcomponent('downmontharrow',
87                (), 'Arrow',
88                tkinter.Canvas, (frame,),
89                width = 16, height = 16, relief = 'raised', borderwidth = 2)
90        self.arrowDirection[self._downMonthArrowBtn] = 0
91        self._downMonthArrowBtn.grid(column = 1, row = 2)
92
93        # Create the day down arrow.
94        self._downDayArrowBtn = self.createcomponent('downdayarrow',
95                (), 'Arrow',
96                tkinter.Canvas, (frame,),
97                width = 16, height = 16, relief = 'raised', borderwidth = 2)
98        self.arrowDirection[self._downDayArrowBtn] = 0
99        self._downDayArrowBtn.grid(column = 2, row = 2)
100
101        # Create the hour down arrow.
102        self._downHourArrowBtn = self.createcomponent('downhourarrow',
103                (), 'Arrow',
104                tkinter.Canvas, (frame,),
105                width = 16, height = 16, relief = 'raised', borderwidth = 2)
106        self.arrowDirection[self._downHourArrowBtn] = 0
107        self._downHourArrowBtn.grid(column = 3, row = 2)
108
109        # Create the minute down arrow.
110        self._downMinuteArrowBtn = self.createcomponent('downminutearrow',
111                (), 'Arrow',
112                tkinter.Canvas, (frame,),
113                width = 16, height = 16, relief = 'raised', borderwidth = 2)
114        self.arrowDirection[self._downMinuteArrowBtn] = 0
115        self._downMinuteArrowBtn.grid(column = 4, row = 2)
116
117        # Create the entry fields.
118
119        # Create the year entry field.
120        self._yearCounterEntry = self.createcomponent('yearentryfield',
121                (('yearentry', 'yearentryfield_entry'),), None,
122                Pmw.EntryField, (frame,), validate='integer', entry_width = 4)
123        self._yearCounterEntry.grid(column = 0, row = 1, sticky = 'news')
124
125        # Create the month entry field.
126        self._monthCounterEntry = self.createcomponent('monthentryfield',
127                (('monthentry', 'monthentryfield_entry'),), None,
128                Pmw.EntryField, (frame,), validate='integer', entry_width = 2)
129        self._monthCounterEntry.grid(column = 1, row = 1, sticky = 'news')
130
131        # Create the day entry field.
132        self._dayCounterEntry = self.createcomponent('dayentryfield',
133                (('dayentry', 'dayentryfield_entry'),), None,
134                Pmw.EntryField, (frame,), validate='integer', entry_width = 2)
135        self._dayCounterEntry.grid(column = 2, row = 1, sticky = 'news')
136
137        # Create the hour entry field.
138        self._hourCounterEntry = self.createcomponent('hourentryfield',
139                (('hourentry', 'hourentryfield_entry'),), None,
140                Pmw.EntryField, (frame,), validate='integer', entry_width = 2)
141        self._hourCounterEntry.grid(column = 3, row = 1, sticky = 'news')
142
143        # Create the minute entry field.
144        self._minuteCounterEntry = self.createcomponent('minuteentryfield',
145                (('minuteentry', 'minuteentryfield_entry'),), None,
146                Pmw.EntryField, (frame,), validate='integer', entry_width = 2)
147        self._minuteCounterEntry.grid(column = 4, row = 1, sticky = 'news')
148
149        # Create the up arrow buttons.
150
151        # Create the year up arrow.
152        self._upYearArrowBtn = self.createcomponent('upyeararrow',
153                (), 'Arrow',
154                tkinter.Canvas, (frame,),
155                width = 16, height = 16, relief = 'raised', borderwidth = 2)
156        self.arrowDirection[self._upYearArrowBtn] = 1
157        self._upYearArrowBtn.grid(column = 0, row = 0)
158
159        # Create the month up arrow.
160        self._upMonthArrowBtn = self.createcomponent('upmontharrow',
161                (), 'Arrow',
162                tkinter.Canvas, (frame,),
163                width = 16, height = 16, relief = 'raised', borderwidth = 2)
164        self.arrowDirection[self._upMonthArrowBtn] = 1
165        self._upMonthArrowBtn.grid(column = 1, row = 0)
166
167        # Create the day up arrow.
168        self._upDayArrowBtn = self.createcomponent('updayarrow',
169                (), 'Arrow',
170                tkinter.Canvas, (frame,),
171                width = 16, height = 16, relief = 'raised', borderwidth = 2)
172        self.arrowDirection[self._upDayArrowBtn] = 1
173        self._upDayArrowBtn.grid(column = 2, row = 0)
174
175        # Create the hour up arrow.
176        self._upHourArrowBtn = self.createcomponent('uphourarrow',
177                (), 'Arrow',
178                tkinter.Canvas, (frame,),
179                width = 16, height = 16, relief = 'raised', borderwidth = 2)
180        self.arrowDirection[self._upHourArrowBtn] = 1
181        self._upHourArrowBtn.grid(column = 3, row = 0)
182
183        # Create the minute up arrow.
184        self._upMinuteArrowBtn = self.createcomponent('upminutearrow',
185                (), 'Arrow',
186                tkinter.Canvas, (frame,),
187                width = 16, height = 16, relief = 'raised', borderwidth = 2)
188        self.arrowDirection[self._upMinuteArrowBtn] = 1
189        self._upMinuteArrowBtn.grid(column = 4, row = 0)
190
191        # Make it resize nicely.
192        padx = self['padx']
193        pady = self['pady']
194        for col in range(5): # YY, MM, DD, HH, mm
195            frame.grid_columnconfigure(col, weight = 1, pad = padx)
196        frame.grid_rowconfigure(0, pad = pady)
197        frame.grid_rowconfigure(2, pad = pady)
198
199        frame.grid_rowconfigure(1, weight = 1)
200
201        # Create the label.
202        self.createlabel(interior)
203
204        # Set bindings.
205
206        # Up year
207        self._upYearArrowBtn.bind('<Configure>',
208                lambda  event, s=self,button=self._upYearArrowBtn:
209                s._drawArrow(button, 1))
210        self._upYearArrowBtn.bind('<1>',
211                lambda event, s=self,button=self._upYearArrowBtn:
212                s._countUp(button))
213        self._upYearArrowBtn.bind('<Any-ButtonRelease-1>',
214                lambda event, s=self, button=self._upYearArrowBtn:
215                s._stopUpDown(button))
216
217        # Up month
218        self._upMonthArrowBtn.bind('<Configure>',
219                lambda  event, s=self,button=self._upMonthArrowBtn:
220                s._drawArrow(button, 1))
221        self._upMonthArrowBtn.bind('<1>',
222                lambda event, s=self,button=self._upMonthArrowBtn:
223                s._countUp(button))
224        self._upMonthArrowBtn.bind('<Any-ButtonRelease-1>',
225                lambda event, s=self, button=self._upMonthArrowBtn:
226                s._stopUpDown(button))
227
228        # Up day
229        self._upDayArrowBtn.bind('<Configure>',
230                lambda  event, s=self,button=self._upDayArrowBtn:
231                s._drawArrow(button, 1))
232        self._upDayArrowBtn.bind('<1>',
233                lambda event, s=self,button=self._upDayArrowBtn:
234                s._countUp(button))
235        self._upDayArrowBtn.bind('<Any-ButtonRelease-1>',
236                lambda event, s=self, button=self._upDayArrowBtn:
237                s._stopUpDown(button))
238
239        # Up hour
240        self._upHourArrowBtn.bind('<Configure>',
241                lambda  event, s=self,button=self._upHourArrowBtn:
242                s._drawArrow(button, 1))
243        self._upHourArrowBtn.bind('<1>',
244                lambda event, s=self,button=self._upHourArrowBtn:
245                s._countUp(button))
246        self._upHourArrowBtn.bind('<Any-ButtonRelease-1>',
247                lambda event, s=self, button=self._upHourArrowBtn:
248                s._stopUpDown(button))
249
250        # Up minute
251        self._upMinuteArrowBtn.bind('<Configure>',
252                lambda  event, s=self,button=self._upMinuteArrowBtn:
253                s._drawArrow(button, 1))
254        self._upMinuteArrowBtn.bind('<1>',
255                lambda event, s=self,button=self._upMinuteArrowBtn:
256                s._countUp(button))
257        self._upMinuteArrowBtn.bind('<Any-ButtonRelease-1>',
258                lambda event, s=self, button=self._upMinuteArrowBtn:
259                s._stopUpDown(button))
260
261
262        # Down year
263        self._downYearArrowBtn.bind('<Configure>',
264                lambda  event, s=self,button=self._downYearArrowBtn:
265                s._drawArrow(button, 0))
266        self._downYearArrowBtn.bind('<1>',
267                lambda event, s=self,button=self._downYearArrowBtn:
268                s._countDown(button))
269        self._downYearArrowBtn.bind('<Any-ButtonRelease-1>',
270                lambda event, s=self, button=self._downYearArrowBtn:
271                s._stopUpDown(button))
272
273        # Down month
274        self._downMonthArrowBtn.bind('<Configure>',
275                lambda  event, s=self,button=self._downMonthArrowBtn:
276                s._drawArrow(button, 0))
277        self._downMonthArrowBtn.bind('<1>',
278                lambda event, s=self,button=self._downMonthArrowBtn:
279                s._countDown(button))
280        self._downMonthArrowBtn.bind('<Any-ButtonRelease-1>',
281                lambda event, s=self, button=self._downMonthArrowBtn:
282                s._stopUpDown(button))
283
284        # Down day
285        self._downDayArrowBtn.bind('<Configure>',
286                lambda  event, s=self,button=self._downDayArrowBtn:
287                s._drawArrow(button, 0))
288        self._downDayArrowBtn.bind('<1>',
289                lambda event, s=self,button=self._downDayArrowBtn:
290                s._countDown(button))
291        self._downDayArrowBtn.bind('<Any-ButtonRelease-1>',
292                lambda event, s=self, button=self._downDayArrowBtn:
293                s._stopUpDown(button))
294
295        # Down hour
296        self._downHourArrowBtn.bind('<Configure>',
297                lambda  event, s=self,button=self._downHourArrowBtn:
298                s._drawArrow(button, 0))
299        self._downHourArrowBtn.bind('<1>',
300                lambda event, s=self,button=self._downHourArrowBtn:
301                s._countDown(button))
302        self._downHourArrowBtn.bind('<Any-ButtonRelease-1>',
303                lambda event, s=self, button=self._downHourArrowBtn:
304                s._stopUpDown(button))
305
306        # Down minute
307        self._downMinuteArrowBtn.bind('<Configure>',
308                lambda  event, s=self,button=self._downMinuteArrowBtn:
309                s._drawArrow(button, 0))
310        self._downMinuteArrowBtn.bind('<1>',
311                lambda event, s=self,button=self._downMinuteArrowBtn: s._countDown(button))
312        self._downMinuteArrowBtn.bind('<Any-ButtonRelease-1>',
313                lambda event, s=self, button=self._downMinuteArrowBtn:
314                s._stopUpDown(button))
315
316
317        self._yearCounterEntry.bind('<Return>', self.invoke)
318        self._monthCounterEntry.bind('<Return>', self.invoke)
319        self._dayCounterEntry.bind('<Return>', self.invoke)
320        self._hourCounterEntry.bind('<Return>', self.invoke)
321        self._minuteCounterEntry.bind('<Return>', self.invoke)
322
323        self._yearCounterEntry.bind('<Configure>', self._resizeArrow)
324        self._monthCounterEntry.bind('<Configure>', self._resizeArrow)
325        self._dayCounterEntry.bind('<Configure>', self._resizeArrow)
326        self._hourCounterEntry.bind('<Configure>', self._resizeArrow)
327        self._minuteCounterEntry.bind('<Configure>', self._resizeArrow)
328
329    def _drawArrow(self, arrow, direction):
330        arrow.delete('arrow')
331
332        fg = self._yearCounterEntry.cget('entry_foreground')
333
334        bw = (string.atoi(arrow['borderwidth']) +
335                string.atoi(arrow['highlightthickness'])) / 2
336        h = string.atoi(arrow['height']) + 2 * bw
337        w =  string.atoi(arrow['width']) + 2 * bw
338
339        if direction == 0:
340            # down arrow
341            arrow.create_polygon(
342                0.25 * w + bw, 0.25 * h + bw,
343                0.50 * w + bw, 0.75 * h + bw,
344                0.75 * w + bw, 0.25 * h + bw,
345                fill=fg, tag='arrow')
346        else:
347            arrow.create_polygon(
348                0.25 * w + bw, 0.75 * h + bw,
349                0.50 * w + bw, 0.25 * h + bw,
350                0.75 * w + bw, 0.75 * h + bw,
351                fill=fg, tag='arrow')
352
353    def _resizeArrow(self, event = None):
354        for btn in (self._upYearArrowBtn, self._upMonthArrowBtn,
355                    self._upDayArrowBtn, self._upHourArrowBtn,
356                    self._upMinuteArrowBtn, self._downYearArrowBtn,
357                    self._downMonthArrowBtn, self._downDayArrowBtn,
358                    self._downHourArrowBtn, self._downMinuteArrowBtn):
359            bw = (string.atoi(btn['borderwidth']) + \
360                    string.atoi(btn['highlightthickness']))
361            newHeight = self._yearCounterEntry.winfo_reqheight() - 2 * bw
362            newWidth = newHeight * self['buttonaspect']
363            btn.configure(width=newWidth, height=newHeight)
364            self._drawArrow(btn, self.arrowDirection[btn])
365
366    def _min(self):
367        self._minVal = None
368
369    def _max(self):
370        self._maxVal = None
371
372    def _setTimeFromStr(self, str):
373        list = string.split(str, ':')
374        if len(list) != 5:
375            raise ValueError('invalid value: ' + str)
376
377        self._year = string.atoi(list[0])
378        self._month = string.atoi(list[1])
379        self._day = string.atoi(list[2])
380        self._hour = string.atoi(list[3])
381        self._minute = string.atoi(list[4])
382
383        self._setHMS()
384
385    def getstring(self):
386        return '%04d:%02d:%02d:%02d:%02d' % (self._year, self._month,
387                                             self._day, self._hour,
388                                             self._minute)
389
390    def getint(self):
391        pass
392
393    def _countUp(self, button):
394        self._relief = self._upYearArrowBtn.cget('relief')
395        button.configure(relief='sunken')
396        if button == self._upYearArrowBtn: datetype = "year"
397        elif button == self._upMonthArrowBtn: datetype = "month"
398        elif button == self._upDayArrowBtn: datetype = "day"
399        elif button == self._upHourArrowBtn: datetype = "hour"
400        elif button == self._upMinuteArrowBtn: datetype = "minute"
401        self._count(1, datetype, 'start')
402
403    def _countDown(self, button):
404        self._relief = self._downYearArrowBtn.cget('relief')
405        button.configure(relief='sunken')
406        if button == self._downYearArrowBtn: datetype = "year"
407        elif button == self._downMonthArrowBtn: datetype = "month"
408        elif button == self._downDayArrowBtn: datetype = "day"
409        elif button == self._downHourArrowBtn: datetype = "hour"
410        elif button == self._downMinuteArrowBtn: datetype = "minute"
411        self._count(-1, datetype, 'start')
412
413    def _count(self, factor, datetype, newFlag=None):
414        if newFlag != 'force':
415            if newFlag is not None:
416                self._flag = newFlag
417
418            if self._flag == 'stopped':
419                return
420
421        if datetype == "year": self._year = self._year + factor
422        elif datetype == "month": self._month = self._month + factor
423        elif datetype == "day": self._day = self._day + factor
424        elif datetype == "hour": self._hour = self._hour + factor
425        elif datetype == "minute": self._minute = self._minute + factor
426        secs = time.mktime((self._year, self._month, self._day, self._hour,
427                           self._minute, 0, 0, 0, -1))
428        tt = time.localtime(secs) # NOT gmtime!
429
430        self._year = tt[0]
431        self._month = tt[1]
432        self._day = tt[2]
433        self._hour = tt[3]
434        self._minute = tt[4]
435        self._setHMS()
436
437        if newFlag != 'force':
438            if self['autorepeat']:
439                if self._flag == 'start':
440                    delay = self['initwait']
441                    self._flag = 'running'
442                else:
443                    delay = self['repeatrate']
444                self._timerId = self.after(
445                    delay, lambda self=self, factor=factor, datetype=datetype:
446                      self._count(factor, datetype, 'running'))
447
448    def _setHMS(self):
449        self._yearCounterEntry.setentry('%04d' % self._year)
450        self._monthCounterEntry.setentry('%02d' % self._month)
451        self._dayCounterEntry.setentry('%02d' % self._day)
452        self._hourCounterEntry.setentry('%02d' % self._hour)
453        self._minuteCounterEntry.setentry('%02d' % self._minute)
454
455    def _stopUpDown(self, button):
456        if self._timerId is not None:
457            self.after_cancel(self._timerId)
458            self._timerId = None
459        button.configure(relief=self._relief)
460        self._flag = 'stopped'
461
462    def invoke(self, event = None):
463        cmd = self['command']
464        if isinstance(cmd, collections.Callable):
465            cmd()
466
467    def destroy(self):
468        if self._timerId is not None:
469            self.after_cancel(self._timerId)
470            self._timerId = None
471        Pmw.MegaWidget.destroy(self)
472
473if __name__=="__main__":
474
475    def showString():
476        stringVal = _time.getstring()
477        print(stringVal)
478
479    root = tkinter.Tk()
480    Pmw.initialise(root)
481    root.title('FullTimeCounter')
482
483    exitButton = tkinter.Button(root, text = 'Exit', command = root.destroy)
484    exitButton.pack(side = 'bottom')
485
486    _time = FullTimeCounter(root,
487            labelpos = 'n',
488            label_text = 'YYYY:MM:DD:HH:mm')
489    _time.pack(fill = 'both', expand = 1, padx=10, pady=5)
490
491    button = tkinter.Button(root, text = 'Show', command = showString)
492    button.pack()
493    root.mainloop()
494