From 2715043adfcc8a262a933dc490d6a4d6ea3366be Mon Sep 17 00:00:00 2001 From: July Tikhonov Date: Tue, 20 Aug 2013 14:37:33 +0400 Subject: [PATCH] Add Tkinter variant, along with some refactoring Remove obsolete ui/common.py. --- __init__.py | 4 +++- __main__.py | 19 +++++++++++------ core.py | 21 ++++++++++++++++-- flavour/poisson.py | 7 +++--- flavour/wiener.py | 6 +++--- ui/common.py | 52 --------------------------------------------- ui/gtk.py | 34 +++++++++++------------------ ui/qt.py | 21 +++++------------- ui/tk.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 111 insertions(+), 106 deletions(-) delete mode 100644 ui/common.py create mode 100644 ui/tk.py diff --git a/__init__.py b/__init__.py index a62e50b..d72c5cd 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ -def main(*, seconds, precision=0, flavour, ui, immediate, font_size): +def main(*, seconds, precision, flavour, ui, immediate, font_size): if flavour == 'regular': from .core import TimerCore as FlavourTimerCore title = 'Regular timer' @@ -14,6 +14,8 @@ def main(*, seconds, precision=0, flavour, ui, immediate, font_size): from .ui.gtk import GtkTimerCore as UITimerCore elif ui == 'qt': from .ui.qt import QtTimerCore as UITimerCore + elif ui == 'tk': + from .ui.tk import TkTimerCore as UITimerCore else: raise ValueError(ui) class TheTimerCore(UITimerCore, FlavourTimerCore): diff --git a/__main__.py b/__main__.py index 4168e2c..7b1ff40 100644 --- a/__main__.py +++ b/__main__.py @@ -7,7 +7,7 @@ import argparse parser = argparse.ArgumentParser( - prog=PROG, description="Timer" ) + prog=PROG, description='Timer' ) parser.add_argument('seconds', type=float, metavar='DURATION', help='timer duration' ) parser.add_argument('-p', '--precision', type=int, @@ -15,10 +15,10 @@ help='print fractions of seconds' ) parser.add_argument('-i', '--immediate', action='store_true', - help='start timer immediately') - - parser.add_argument('-f', '--font-size', type=float, default=100, - help='label font size') + help='start timer immediately' ) + parser.add_argument('-f', '--font-size', type=float, + default=100, + help='label font size' ) flavour_group = parser.add_mutually_exclusive_group() flavour_group.add_argument('-P', '--poisson', @@ -36,11 +36,16 @@ ui_group.add_argument('--qt', action='store_const', dest='ui', const='qt', help='use Qt 4 (default)' ) - parser.set_defaults(ui='qt') + ui_group.add_argument('--tk', + action='store_const', dest='ui', const='tk', + help='use Qt 4 (default)' ) + parser.set_defaults(ui='tk') args = parser.parse_args() - if (args.precision < 0): + if args.precision < 0: parser.error('precision must be non-negative') + if args.precision > 0 and args.flavour == 'poisson': + parser.error('Poisson timer does not allow non-zero precision') main(**vars(args)) diff --git a/core.py b/core.py index 1900e4b..ebc932f 100644 --- a/core.py +++ b/core.py @@ -9,6 +9,8 @@ def __init__(self, seconds, precision): self.state = 'paused' + self.update_label() + def time(self): return time() @@ -19,12 +21,15 @@ def interact(self): self.start() elif self.state in {'exhausted'}: self.shutdown() + elif self.state in {'shutdown'}: + raise AssertionError def close(self): try: if self.state in {'running', 'paused'}: self.print_remained() finally: + self.state = 'shutdown' self.shutdown() def update(self): @@ -34,7 +39,8 @@ def update(self): self.stop_timeout() def update_label(self): - self.show_remained(self.get_remained()) + remained = self.get_remained() + self.set_label_text(self.format_remained(remained), finished=remained <= 0.0) def get_remained(self): if self.state in {'running'}: @@ -48,16 +54,20 @@ def get_remained(self): return self.remained if self.state in {'exhausted'}: return 0.0 + if self.state in {'shutdown'}: + raise AssertionError def start(self): assert self.state in {'paused'} assert self.remained > 0.0 self.registered_time = self.time() self.state = 'running' + self.start_timeout() def pause(self): assert self.state in {'running'} self.update_time() + self.stop_timeout() if self.remained <= 0.0: self.state = 'exhausted' else: @@ -95,5 +105,12 @@ def mainloop(self): def shutdown(self): raise NotImplementedError - def show_remained(self, remained): + def set_label_text(self, text, finished=False): + raise NotImplementedError + + def start_timeout(self): raise NotImplementedError + + def stop_timeout(self): + raise NotImplementedError + diff --git a/flavour/poisson.py b/flavour/poisson.py index 12022b6..87d581a 100644 --- a/flavour/poisson.py +++ b/flavour/poisson.py @@ -4,9 +4,10 @@ from ..core import TimerCore class PoissonTimerCore(TimerCore): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.remained = int(self.remained) + def __init__(self, seconds, precision, *args, **kwargs): + seconds = int(seconds) + assert precision == 0 + super().__init__(seconds, precision, *args, **kwargs) def update_time(self): passed_time = self.time() - self.registered_time diff --git a/flavour/wiener.py b/flavour/wiener.py index 26998d9..ecb209b 100644 --- a/flavour/wiener.py +++ b/flavour/wiener.py @@ -4,9 +4,9 @@ from ..core import TimerCore class WienerTimerCore(TimerCore): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.remained = sqrt(self.remained) + def __init__(self, seconds, *args, **kwargs): + seconds = sqrt(seconds) + super().__init__(seconds, *args, **kwargs) def get_remained(self): remained = super().get_remained() diff --git a/ui/common.py b/ui/common.py deleted file mode 100644 index d752865..0000000 --- a/ui/common.py +++ /dev/null @@ -1,52 +0,0 @@ -from math import ceil - -class CommonTimerApp: - def __init__(self, timer, precision): - self.timer = timer - self.precision = precision - self.living = True - - def remained(self): - if not self.living: - return 0.0 - remained = self.timer.remained() - if remained <= 0.0: - self.living = False - return 0.0 - return remained - - def interact(self): - if self.living: - self.toggle_timer() - else: - self.shutdown_ui() - - def toggle_timer(self): - self.timer.toggle_timer() - - def quit(self): - if self.living: - self.output_remained() - self.shutdown_ui() - - def output_remained(self): - """Print remained time on stdout.""" - remained = self.remained() - print('Remained: ' + self.format_remained(remained)) - - def format_remained(self, remained): - if self.precision == 0: - remained = ceil(remained) - minutes = remained // (60) - seconds = remained % 60 - return '{0:0=2d}:{1:0=2d}'.format(minutes, seconds) - assert self.precision >= 0 - modulo = 10**self.precision - remained = ceil(remained * modulo) - minutes = remained // (60 * modulo) - seconds = remained % (60 * modulo) // modulo - subseconds = remained % modulo - return '{0:0=2d}:{1:0=2d}.{2:0={precision}d}'.format( - minutes, seconds, subseconds, - precision=self.precision ) - diff --git a/ui/gtk.py b/ui/gtk.py index be95211..d473969 100644 --- a/ui/gtk.py +++ b/ui/gtk.py @@ -3,9 +3,7 @@ from ..core import TimerCore class GtkTimerCore(TimerCore): - def __init__(self, *args, title='timer', font_size=100, **kwargs): - super().__init__(*args, **kwargs) - + def __init__(self, *args, title, font_size, **kwargs): def close_handler(widget, event): self.close() def clicked_handler(widget): @@ -17,25 +15,22 @@ def clicked_handler(widget): self.master.add(self.label) self.label_font_size = int(1000 * font_size) - self.control_master = Gtk.Window( + self.control = Gtk.Window( title=title + ' (control)', default_width=150, default_height=150 ) - self.control_master.connect('delete-event', close_handler) + self.control.connect('delete-event', close_handler) self.button = Gtk.Button() self.button.set_label('Start/Pause') self.button.connect('clicked', clicked_handler) - self.control_master.add(self.button) + self.control.add(self.button) self.timeout_id = None - self.update_label() self.master.show_all() - self.control_master.show_all() + self.control.show_all() - def start(self): - super().start() - self.start_timeout() + super().__init__(*args, **kwargs) def start_timeout(self): assert self.timeout_id is None @@ -44,10 +39,6 @@ def timeout_call(): return True # continue timeout self.timeout_id = GObject.timeout_add(25, timeout_call) - def pause(self): - super().pause() - self.stop_timeout() - def stop_timeout(self): assert self.timeout_id is not None GObject.source_remove(self.timeout_id) @@ -59,12 +50,11 @@ def mainloop(self): def shutdown(self): Gtk.main_quit() - def show_remained(self, remained): - self.set_label_markup(self.format_remained(remained), - colour='black' if remained > 0.0 else 'red' ) - - def set_label_markup(self, label, colour='black'): + def set_label_text(self, text, finished=False): self.label.set_markup( - '{label}' - .format(label=label, colour=colour, size=self.label_font_size) ) + '{text}' + .format(text=text, + colour='black' if not finished else 'red', + size=self.label_font_size ) + ) diff --git a/ui/qt.py b/ui/qt.py index 962dd70..06a1674 100644 --- a/ui/qt.py +++ b/ui/qt.py @@ -3,9 +3,7 @@ from ..core import TimerCore class QtTimerCore(TimerCore): - def __init__(self, *args, title='timer', font_size=100, **kwargs): - super().__init__(*args, **kwargs) - + def __init__(self, *args, title, font_size, **kwargs): self.qapp = QtGui.QApplication([]) self.master = MasterWindow() @@ -36,22 +34,15 @@ def __init__(self, *args, title='timer', font_size=100, **kwargs): self.control.setLayout(control_layout) self.master.timer_callback = self.update - self.update_label() self.master.show() self.control.show() - def start(self): - super().start() - self.start_timeout() + super().__init__(*args, **kwargs) def start_timeout(self): self.timer_id = self.master.startTimer(25) - def pause(self): - super().pause() - self.stop_timeout() - def stop_timeout(self): self.master.killTimer(self.timer_id) @@ -61,11 +52,9 @@ def mainloop(self): def shutdown(self): return self.qapp.quit() - def show_remained(self, remained): - self.label.setText(self.format_remained(remained)) - if remained > 0.0: - pass - else: + def set_label_text(self, text, finished=False): + self.label.setText(text) + if finished: self.label.setStyleSheet('QLabel { color : red; }') class Window(QtGui.QWidget): diff --git a/ui/tk.py b/ui/tk.py new file mode 100644 index 0000000..bef59f5 --- /dev/null +++ b/ui/tk.py @@ -0,0 +1,53 @@ +from tkinter import Tk, Toplevel, Label, Button + +from ..core import TimerCore + +class TkTimerCore(TimerCore): + def __init__(self, *args, title, font_size, **kwargs): + def close_handler(event): + self.close() + def clicked_handler(event): + self.interact() + + self.master = Tk() + self.master.wm_title(title) + self.master.bind('', close_handler) + self.label = Label(self.master, font='Sans {}'.format(font_size)) + self.label.pack(expand=True) + + self.control = Toplevel() + self.control.wm_title(title + ' (control)') + self.control.minsize(150, 150) + self.control.bind('', close_handler) + self.button = Button(self.control, text='Start/Pause') + self.button.bind('', clicked_handler) + self.button.pack(expand=True) + + self.timeout_running = False + + super().__init__(*args, **kwargs) + + def start_timeout(self): + assert self.timeout_running is False + def timeout_call(): + self.update() + if self.timeout_running: + self.master.after(25, timeout_call) + self.timeout_running = True + timeout_call() + + def stop_timeout(self): + assert self.timeout_running is True + self.timeout_running = False + + def mainloop(self): + return self.master.mainloop() + + def shutdown(self): + self.master.quit() + + def set_label_text(self, text, finished=False): + self.label.config(text=text) + if finished: + self.label.config(fg='red') +