All pastes #2053776 Raw Edit

Unnamed

public text v1 · immutable
#2053776 ·published 2011-05-04 11:44 UTC
rendered paste body
#!/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()