#!/usr/bin/python
"""
Tom's Clock
Tom Dignan - http://tomdignan.com - <tom.dignan@gmail.com>
Last Modified: Mon Feb 14 04:02:31 EST 2011
Release: 0.02-dev
"""
# TODO: start modularizing the code. refactor.
import sys
from PyQt4 import QtGui, QtCore
class TCConfig(object):
worktimer_geometry = QtCore.QRect(0,0, 500, 80)
worktimer_title = 'Tom\'s Clock'
worktimer_toggle_geometry = QtCore.QRect(10,10, 60, 25)
worktimer_toggle_start_label = 'Start'
worktimer_toggle_start_tooltip = 'Start the timer'
worktimer_toggle_pause_label = 'Pause'
worktimer_toggle_pause_tooltip = 'Pause the timer'
worktimer_button_clear_geometry = QtCore.QRect(10,35, 60, 25)
worktimer_button_clear_label = 'Clear'
worktimer_button_clear_tooltip = 'Clear the timer'
worktimer_timer_hours_geometry = QtCore.QRect(80, 10, 100, 50)
worktimer_timer_minutes_geometry = QtCore.QRect(180, 10, 100, 50)
worktimer_timer_seconds_geometry = QtCore.QRect(280, 10, 100, 50)
worktimer_timer_milliseconds_geometry = QtCore.QRect(380, 10, 100, 50)
worktimer_timer_update_interval = 100 # 1/10 sec
MSEC, SECOND, MINUTE, HOUR = range(4)
class TCToggleButton(object):
"""
A Toggle switch class. Has-a QtPushButton, has 1...n possible states.
"""
def __init__(self, parent, geometry, labels, tooltips, callbacks):
#TODO: add exceptions for bad parameters.
self.labels = labels
self.tooltips = tooltips
self.callbacks = callbacks
self.state = 0
self.last_state = len(labels)
self.button = QtGui.QPushButton(parent)
self.button.setGeometry(geometry)
self.button.setText(self.labels[self.state])
self.button.setToolTip(self.tooltips[self.state])
parent.connect(self.button, QtCore.SIGNAL('clicked()'), self.toggle)
def toggle(self):
self.callbacks[self.state]()
self.state += 1
if self.state == self.last_state:
self.state = 0
self.button.setText(self.labels[self.state])
self.button.setToolTip(self.tooltips[self.state])
class TCStopwatch(QtCore.QTime):
"""
Subclassed the QTime class to make a class that is more suitable for timers.
QTime is more for displaying the current time. In QTime, setHMS() will reset
the time to the value of choice, but after calling start, the time is set
to the current time. Also minute(), second(), hour(), and msec() do not return
elapsed time.
In this class, the QTime class is modified to be a stopwatch, rather than a
wall-clock.
"""
def __init__(self):
QtCore.QTime.__init__(self)
self._is_paused = None
self.clear()
def real_elapsed(self):
"""
Returns the 'real' elapsed time. The time exclusive of the time
spent in a paused state.
"""
return self.elapsed() - self.offset
def msec(self):
return self.real_elapsed() % 1000
def second(self):
return self.real_elapsed() / 1000 % 60
def minute(self):
return self.real_elapsed() / 1000 / 60 % 60
def hour(self):
return self.real_elapsed() / 1000 / 60 / 60
def pause(self):
"""
The timer is not actually paused. The last time is saved and then
when the timer is restarted the saved time is loaded.
"""
self.lasttime = self.elapsed()
self._is_paused = True
def restart(self):
"""
'restart' the timer.
"""
self._is_paused = False
self.offset += self.elapsed() - self.lasttime
def is_paused(self):
return self._is_paused
def clear(self):
"""
Zeros out the timer.
"""
self.lasttime = None
self.offset = 0
self.setHMS(0,0,0,0)
self._is_paused = False
class TCTimer(object):
"""
A timer class. Has QLCDNumbers for display and a QTime for keeping time.
Updates the QLCDNumber displays with a QTimer thread. Starts at milliseconds,
and adds another counter for each geometry parameter supplied in the geometry [].
"""
def __init__(self, parent, geometry):
self.msec_elapsed = 0
self.lcds = []
for rect in geometry:
lcd = QtGui.QLCDNumber(parent)
lcd.setGeometry(rect)
self.lcds.append(lcd)
self.stopwatch = TCStopwatch()
self.updater = QtCore.QTimer(parent)
parent.connect(self.updater, QtCore.SIGNAL('timeout()'), self.update)
def start(self):
"""
Starts keeping time, and fires off a timer to keep the GUI updated.
"""
# If clear was the last button to be pushed, then we need to unset
# that status here, since they just pushed start. No need to check
# what it is, it becomes false at this point no matter what.
# This is to prevent the clear button from becoming an auxiliary
# start button.
self._clear_called_last = False
if self.stopwatch.is_paused():
self.stopwatch.restart()
else:
self.stopwatch.start()
self.updater.start(TCConfig.worktimer_timer_update_interval)
def pause(self):
"""
Pauses the timer.
"""
self.updater.stop()
self.stopwatch.pause()
def is_paused(self):
"""
Wrapper
"""
return self.stopwatch.is_paused()
def clear(self):
"""
Resets the timer to zeros.
"""
self.stopwatch.clear()
self.lcds[MSEC].display(0)
self.lcds[SECOND].display(0)
self.lcds[MINUTE].display(0)
self.lcds[HOUR].display(0)
# unset on the next start() call.
self._clear_called_last = True
def clear_was_last(self):
return self._clear_called_last
def update(self):
"""
Updates the display with the current elapsed time
"""
# When updating msec, we use the update interval as a means
# of only displaying the digits required. This is accomplished
# by truncating insignificant digits with integer division by the
# update interval. Then, it is re-multiplied. Insignifcant
# digits are now zeros.
self.lcds[MSEC].display((self.stopwatch.msec() /
TCConfig.worktimer_timer_update_interval)
* TCConfig.worktimer_timer_update_interval)
self.lcds[SECOND].display(self.stopwatch.second())
self.lcds[MINUTE].display(self.stopwatch.minute())
self.lcds[HOUR].display(self.stopwatch.hour())
class TCWindow(QtGui.QWidget):
"""
Main window of Tom's Clock
"""
def __init__(self, parent=None):
# For some reason, pyqt4 seems to be missing these hints.
Window = 0x00000001
CustomizeWindowHint = 0x02000000
# window setup
QtGui.QWidget.__init__(self, parent)
self.setGeometry(TCConfig.worktimer_geometry)
self.setWindowTitle(TCConfig.worktimer_title)
# hide the titlebar
# Commented out for alpha release
#self.setWindowFlags(QtCore.Qt.WindowFlags(Window | CustomizeWindowHint))
# Layout is absolute. This should allow for maximum skinnability.
self.my_timer = TCTimer(self, [
TCConfig.worktimer_timer_milliseconds_geometry,
TCConfig.worktimer_timer_seconds_geometry,
TCConfig.worktimer_timer_minutes_geometry,
TCConfig.worktimer_timer_hours_geometry
])
# add the toggle button (must happen after my_timer instantiation)
self.my_toggle = TCToggleButton(
self, TCConfig.worktimer_toggle_geometry,
[TCConfig.worktimer_toggle_start_label,
TCConfig.worktimer_toggle_pause_label],
[TCConfig.worktimer_toggle_start_tooltip,
TCConfig.worktimer_toggle_pause_tooltip],
[self.my_timer.start, self.my_timer.pause]
)
# add the clear button
self.my_clear = QtGui.QPushButton(self)
self.my_clear.setGeometry(TCConfig.worktimer_button_clear_geometry)
self.my_clear.setText(TCConfig.worktimer_button_clear_label)
self.my_clear.setToolTip(TCConfig.worktimer_button_clear_tooltip)
self.connect(self.my_clear, QtCore.SIGNAL('clicked()'), self.my_on_clear)
def my_on_clear(self):
if (not self.my_timer.clear_was_last() and
not self.my_timer.is_paused()):
self.my_toggle.toggle()
self.my_timer.clear()
def main():
"""
This is the main entry point for Tom's Clock.
"""
app = QtGui.QApplication(sys.argv)
worktimer = TCWindow()
worktimer.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()