Commit | Line | Data |
---|---|---|
920dae64 AT |
1 | # Authors: Joe VanAndel and Greg McFarlane |
2 | ||
3 | import string | |
4 | import sys | |
5 | import time | |
6 | import Tkinter | |
7 | import Pmw | |
8 | ||
9 | class TimeCounter(Pmw.MegaWidget): | |
10 | """Up-down counter | |
11 | ||
12 | A TimeCounter is a single-line entry widget with Up and Down arrows | |
13 | which increment and decrement the Time value in the entry. | |
14 | """ | |
15 | ||
16 | def __init__(self, parent = None, **kw): | |
17 | ||
18 | # Define the megawidget options. | |
19 | INITOPT = Pmw.INITOPT | |
20 | optiondefs = ( | |
21 | ('autorepeat', 1, None), | |
22 | ('buttonaspect', 1.0, INITOPT), | |
23 | ('command', None, None), | |
24 | ('initwait', 300, None), | |
25 | ('labelmargin', 0, INITOPT), | |
26 | ('labelpos', None, INITOPT), | |
27 | ('max', None, self._max), | |
28 | ('min', None, self._min), | |
29 | ('padx', 0, INITOPT), | |
30 | ('pady', 0, INITOPT), | |
31 | ('repeatrate', 50, None), | |
32 | ('value', None, 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(kw) | |
44 | ||
45 | value = self['value'] | |
46 | if value is None: | |
47 | now = time.time() | |
48 | value = time.strftime('%H:%M:%S', time.localtime(now)) | |
49 | self.setvalue(value) | |
50 | ||
51 | # Check keywords and initialise options. | |
52 | self.initialiseoptions() | |
53 | ||
54 | def _createComponents(self, kw): | |
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 | if not kw.has_key('hull_relief'): | |
66 | frame.configure(relief = 'raised') | |
67 | if not kw.has_key('hull_borderwidth'): | |
68 | frame.configure(borderwidth = 1) | |
69 | else: | |
70 | frame = self.createcomponent('frame', | |
71 | (), None, | |
72 | Tkinter.Frame, (interior,), | |
73 | relief = 'raised', borderwidth = 1) | |
74 | frame.grid(column=2, row=2, sticky='nsew') | |
75 | interior.grid_columnconfigure(2, weight=1) | |
76 | interior.grid_rowconfigure(2, weight=1) | |
77 | ||
78 | # Create the down arrow buttons. | |
79 | ||
80 | # Create the hour down arrow. | |
81 | self._downHourArrowBtn = self.createcomponent('downhourarrow', | |
82 | (), 'Arrow', | |
83 | Tkinter.Canvas, (frame,), | |
84 | width = 16, height = 16, relief = 'raised', borderwidth = 2) | |
85 | self.arrowDirection[self._downHourArrowBtn] = 'down' | |
86 | self._downHourArrowBtn.grid(column = 0, row = 2) | |
87 | ||
88 | # Create the minute down arrow. | |
89 | self._downMinuteArrowBtn = self.createcomponent('downminutearrow', | |
90 | (), 'Arrow', | |
91 | Tkinter.Canvas, (frame,), | |
92 | width = 16, height = 16, relief = 'raised', borderwidth = 2) | |
93 | self.arrowDirection[self._downMinuteArrowBtn] = 'down' | |
94 | self._downMinuteArrowBtn.grid(column = 1, row = 2) | |
95 | ||
96 | # Create the second down arrow. | |
97 | self._downSecondArrowBtn = self.createcomponent('downsecondarrow', | |
98 | (), 'Arrow', | |
99 | Tkinter.Canvas, (frame,), | |
100 | width = 16, height = 16, relief = 'raised', borderwidth = 2) | |
101 | self.arrowDirection[self._downSecondArrowBtn] = 'down' | |
102 | self._downSecondArrowBtn.grid(column = 2, row = 2) | |
103 | ||
104 | # Create the entry fields. | |
105 | ||
106 | # Create the hour entry field. | |
107 | self._hourCounterEntry = self.createcomponent('hourentryfield', | |
108 | (('hourentry', 'hourentryfield_entry'),), None, | |
109 | Pmw.EntryField, (frame,), validate='integer', entry_width = 2) | |
110 | self._hourCounterEntry.grid(column = 0, row = 1, sticky = 'news') | |
111 | ||
112 | # Create the minute entry field. | |
113 | self._minuteCounterEntry = self.createcomponent('minuteentryfield', | |
114 | (('minuteentry', 'minuteentryfield_entry'),), None, | |
115 | Pmw.EntryField, (frame,), validate='integer', entry_width = 2) | |
116 | self._minuteCounterEntry.grid(column = 1, row = 1, sticky = 'news') | |
117 | ||
118 | # Create the second entry field. | |
119 | self._secondCounterEntry = self.createcomponent('secondentryfield', | |
120 | (('secondentry', 'secondentryfield_entry'),), None, | |
121 | Pmw.EntryField, (frame,), validate='integer', entry_width = 2) | |
122 | self._secondCounterEntry.grid(column = 2, row = 1, sticky = 'news') | |
123 | ||
124 | # Create the up arrow buttons. | |
125 | ||
126 | # Create the hour up arrow. | |
127 | self._upHourArrowBtn = self.createcomponent('uphourarrow', | |
128 | (), 'Arrow', | |
129 | Tkinter.Canvas, (frame,), | |
130 | width = 16, height = 16, relief = 'raised', borderwidth = 2) | |
131 | self.arrowDirection[self._upHourArrowBtn] = 'up' | |
132 | self._upHourArrowBtn.grid(column = 0, row = 0) | |
133 | ||
134 | # Create the minute up arrow. | |
135 | self._upMinuteArrowBtn = self.createcomponent('upminutearrow', | |
136 | (), 'Arrow', | |
137 | Tkinter.Canvas, (frame,), | |
138 | width = 16, height = 16, relief = 'raised', borderwidth = 2) | |
139 | self.arrowDirection[self._upMinuteArrowBtn] = 'up' | |
140 | self._upMinuteArrowBtn.grid(column = 1, row = 0) | |
141 | ||
142 | # Create the second up arrow. | |
143 | self._upSecondArrowBtn = self.createcomponent('upsecondarrow', | |
144 | (), 'Arrow', | |
145 | Tkinter.Canvas, (frame,), | |
146 | width = 16, height = 16, relief = 'raised', borderwidth = 2) | |
147 | self.arrowDirection[self._upSecondArrowBtn] = 'up' | |
148 | self._upSecondArrowBtn.grid(column = 2, row = 0) | |
149 | ||
150 | # Make it resize nicely. | |
151 | padx = self['padx'] | |
152 | pady = self['pady'] | |
153 | for col in range(3): | |
154 | frame.grid_columnconfigure(col, weight = 1, pad = padx) | |
155 | frame.grid_rowconfigure(0, pad = pady) | |
156 | frame.grid_rowconfigure(2, pad = pady) | |
157 | ||
158 | frame.grid_rowconfigure(1, weight = 1) | |
159 | ||
160 | # Create the label. | |
161 | self.createlabel(interior) | |
162 | ||
163 | # Set bindings. | |
164 | ||
165 | # Up hour | |
166 | self._upHourArrowBtn.bind('<Configure>', | |
167 | lambda event, s=self,button=self._upHourArrowBtn: | |
168 | s._drawArrow(button, 'up')) | |
169 | ||
170 | self._upHourArrowBtn.bind('<1>', | |
171 | lambda event, s=self,button=self._upHourArrowBtn: | |
172 | s._countUp(button, 3600)) | |
173 | ||
174 | self._upHourArrowBtn.bind('<Any-ButtonRelease-1>', | |
175 | lambda event, s=self, button=self._upHourArrowBtn: | |
176 | s._stopUpDown(button)) | |
177 | ||
178 | # Up minute | |
179 | self._upMinuteArrowBtn.bind('<Configure>', | |
180 | lambda event, s=self,button=self._upMinuteArrowBtn: | |
181 | s._drawArrow(button, 'up')) | |
182 | ||
183 | ||
184 | self._upMinuteArrowBtn.bind('<1>', | |
185 | lambda event, s=self,button=self._upMinuteArrowBtn: | |
186 | s._countUp(button, 60)) | |
187 | ||
188 | self._upMinuteArrowBtn.bind('<Any-ButtonRelease-1>', | |
189 | lambda event, s=self, button=self._upMinuteArrowBtn: | |
190 | s._stopUpDown(button)) | |
191 | ||
192 | # Up second | |
193 | self._upSecondArrowBtn.bind('<Configure>', | |
194 | lambda event, s=self,button=self._upSecondArrowBtn: | |
195 | s._drawArrow(button, 'up')) | |
196 | ||
197 | ||
198 | self._upSecondArrowBtn.bind('<1>', | |
199 | lambda event, s=self,button=self._upSecondArrowBtn: | |
200 | s._countUp(button, 1)) | |
201 | ||
202 | self._upSecondArrowBtn.bind('<Any-ButtonRelease-1>', | |
203 | lambda event, s=self, button=self._upSecondArrowBtn: | |
204 | s._stopUpDown(button)) | |
205 | ||
206 | # Down hour | |
207 | self._downHourArrowBtn.bind('<Configure>', | |
208 | lambda event, s=self,button=self._downHourArrowBtn: | |
209 | s._drawArrow(button, 'down')) | |
210 | ||
211 | self._downHourArrowBtn.bind('<1>', | |
212 | lambda event, s=self,button=self._downHourArrowBtn: | |
213 | s._countDown(button, 3600)) | |
214 | self._downHourArrowBtn.bind('<Any-ButtonRelease-1>', | |
215 | lambda event, s=self, button=self._downHourArrowBtn: | |
216 | s._stopUpDown(button)) | |
217 | ||
218 | ||
219 | # Down minute | |
220 | self._downMinuteArrowBtn.bind('<Configure>', | |
221 | lambda event, s=self,button=self._downMinuteArrowBtn: | |
222 | s._drawArrow(button, 'down')) | |
223 | ||
224 | self._downMinuteArrowBtn.bind('<1>', | |
225 | lambda event, s=self,button=self._downMinuteArrowBtn: | |
226 | s._countDown(button, 60)) | |
227 | self._downMinuteArrowBtn.bind('<Any-ButtonRelease-1>', | |
228 | lambda event, s=self, button=self._downMinuteArrowBtn: | |
229 | s._stopUpDown(button)) | |
230 | ||
231 | # Down second | |
232 | self._downSecondArrowBtn.bind('<Configure>', | |
233 | lambda event, s=self,button=self._downSecondArrowBtn: | |
234 | s._drawArrow(button, 'down')) | |
235 | ||
236 | self._downSecondArrowBtn.bind('<1>', | |
237 | lambda event, s=self, button=self._downSecondArrowBtn: | |
238 | s._countDown(button,1)) | |
239 | self._downSecondArrowBtn.bind('<Any-ButtonRelease-1>', | |
240 | lambda event, s=self, button=self._downSecondArrowBtn: | |
241 | s._stopUpDown(button)) | |
242 | ||
243 | self._hourCounterEntry.component('entry').bind( | |
244 | '<Return>', self._invoke) | |
245 | self._minuteCounterEntry.component('entry').bind( | |
246 | '<Return>', self._invoke) | |
247 | self._secondCounterEntry.component('entry').bind( | |
248 | '<Return>', self._invoke) | |
249 | ||
250 | self._hourCounterEntry.bind('<Configure>', self._resizeArrow) | |
251 | self._minuteCounterEntry.bind('<Configure>', self._resizeArrow) | |
252 | self._secondCounterEntry.bind('<Configure>', self._resizeArrow) | |
253 | ||
254 | def _drawArrow(self, arrow, direction): | |
255 | Pmw.drawarrow(arrow, self['hourentry_foreground'], direction, 'arrow') | |
256 | ||
257 | def _resizeArrow(self, event = None): | |
258 | for btn in (self._upHourArrowBtn, self._upMinuteArrowBtn, | |
259 | self._upSecondArrowBtn, | |
260 | self._downHourArrowBtn, | |
261 | self._downMinuteArrowBtn, self._downSecondArrowBtn): | |
262 | bw = (string.atoi(btn['borderwidth']) + | |
263 | string.atoi(btn['highlightthickness'])) | |
264 | newHeight = self._hourCounterEntry.winfo_reqheight() - 2 * bw | |
265 | newWidth = int(newHeight * self['buttonaspect']) | |
266 | btn.configure(width=newWidth, height=newHeight) | |
267 | self._drawArrow(btn, self.arrowDirection[btn]) | |
268 | ||
269 | def _min(self): | |
270 | min = self['min'] | |
271 | if min is None: | |
272 | self._minVal = 0 | |
273 | else: | |
274 | self._minVal = Pmw.timestringtoseconds(min) | |
275 | ||
276 | def _max(self): | |
277 | max = self['max'] | |
278 | if max is None: | |
279 | self._maxVal = None | |
280 | else: | |
281 | self._maxVal = Pmw.timestringtoseconds(max) | |
282 | ||
283 | def getvalue(self): | |
284 | return self.getstring() | |
285 | ||
286 | def setvalue(self, text): | |
287 | list = string.split(text, ':') | |
288 | if len(list) != 3: | |
289 | raise ValueError, 'invalid value: ' + text | |
290 | ||
291 | self._hour = string.atoi(list[0]) | |
292 | self._minute = string.atoi(list[1]) | |
293 | self._second = string.atoi(list[2]) | |
294 | ||
295 | self._setHMS() | |
296 | ||
297 | def getstring(self): | |
298 | return '%02d:%02d:%02d' % (self._hour, self._minute, self._second) | |
299 | ||
300 | def getint(self): | |
301 | return self._hour * 3600 + self._minute * 60 + self._second | |
302 | ||
303 | def _countUp(self, button, increment): | |
304 | self._relief = self._upHourArrowBtn.cget('relief') | |
305 | button.configure(relief='sunken') | |
306 | self._count(1, 'start', increment) | |
307 | ||
308 | def _countDown(self, button, increment): | |
309 | ||
310 | self._relief = self._downHourArrowBtn.cget('relief') | |
311 | button.configure(relief='sunken') | |
312 | self._count(-1, 'start', increment) | |
313 | ||
314 | def increment(self, seconds = 1): | |
315 | self._count(1, 'force', seconds) | |
316 | ||
317 | def decrement(self, seconds = 1): | |
318 | self._count(-1, 'force', seconds) | |
319 | ||
320 | def _count(self, factor, newFlag = None, increment = 1): | |
321 | if newFlag != 'force': | |
322 | if newFlag is not None: | |
323 | self._flag = newFlag | |
324 | ||
325 | if self._flag == 'stopped': | |
326 | return | |
327 | ||
328 | value = (string.atoi(self._hourCounterEntry.get()) *3600) + \ | |
329 | (string.atoi(self._minuteCounterEntry.get()) *60) + \ | |
330 | string.atoi(self._secondCounterEntry.get()) + \ | |
331 | factor * increment | |
332 | min = self._minVal | |
333 | max = self._maxVal | |
334 | if value < min: | |
335 | value = min | |
336 | if max is not None and value > max: | |
337 | value = max | |
338 | ||
339 | self._hour = value /3600 | |
340 | self._minute = (value - (self._hour*3600)) / 60 | |
341 | self._second = value - (self._hour*3600) - (self._minute*60) | |
342 | self._setHMS() | |
343 | ||
344 | if newFlag != 'force': | |
345 | if self['autorepeat']: | |
346 | if self._flag == 'start': | |
347 | delay = self['initwait'] | |
348 | self._flag = 'running' | |
349 | else: | |
350 | delay = self['repeatrate'] | |
351 | self._timerId = self.after( | |
352 | delay, lambda self=self, factor=factor,increment=increment: | |
353 | self._count(factor,'running', increment)) | |
354 | ||
355 | def _setHMS(self): | |
356 | self._hourCounterEntry.setentry('%02d' % self._hour) | |
357 | self._minuteCounterEntry.setentry('%02d' % self._minute) | |
358 | self._secondCounterEntry.setentry('%02d' % self._second) | |
359 | ||
360 | def _stopUpDown(self, button): | |
361 | if self._timerId is not None: | |
362 | self.after_cancel(self._timerId) | |
363 | self._timerId = None | |
364 | button.configure(relief=self._relief) | |
365 | self._flag = 'stopped' | |
366 | ||
367 | def _invoke(self, event): | |
368 | cmd = self['command'] | |
369 | if callable(cmd): | |
370 | cmd() | |
371 | ||
372 | def invoke(self): | |
373 | cmd = self['command'] | |
374 | if callable(cmd): | |
375 | return cmd() | |
376 | ||
377 | def destroy(self): | |
378 | if self._timerId is not None: | |
379 | self.after_cancel(self._timerId) | |
380 | self._timerId = None | |
381 | Pmw.MegaWidget.destroy(self) |