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