nutools/chrono.py

312 lines
10 KiB
Python
Raw Normal View History

2016-10-14 13:52:51 +04:00
#!/usr/bin/env python
# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
u"""Afficher un chronomètre"""
import os, sys, re, subprocess, traceback
from os import path
from datetime import date as Date, time as Time
from datetime import datetime as Datetime, timedelta as Timedelta
from types import IntType, LongType
DEFAULT_SOUND = path.join(path.dirname(__file__), 'lib', 'chrono.wav')
def win_playSound(name):
try: import winsound as s
except: return
if name is None:
s.PlaySound(None, s.SND_ASYNC)
else:
scriptdir = path.split(path.abspath(sys.argv[0]))[0]
soundfile = path.join(scriptdir, name)
s.PlaySound(soundfile, s.SND_FILENAME + s.SND_ASYNC)
def linux_playSound(name):
subprocess.call(['/usr/bin/aplay', '-Nq', name])
def playSound(name=None):
if os.name == 'nt':
return win_playSound(name)
elif sys.platform.startswith('linux'):
return linux_playSound(name)
def isnum(i):
return type(i) in (IntType, LongType)
DEFAULT_TIMEOUT = '5'
RE_DESTHOUR = re.compile(r'@(\d+)(?:[:.](\d+)(?:[:.](\d+))?)?$')
def parse_desthour(s):
mo = RE_DESTHOUR.match(s)
if mo is None: return None
h, m, s = mo.groups()
if h is None: h = 0
if m is None: m = 0
if s is None: s = 0
h, m, s = int(h), int(m), int(s)
src = Datetime.today()
srcdate = src.date(); srctime = src.time()
destdate = srcdate; desttime = Time(h, m, s)
if desttime <= srctime: destdate = destdate + Timedelta(1)
src = Datetime.combine(srcdate, srctime)
dest = Datetime.combine(destdate, desttime)
delta = dest - src
return delta.total_seconds()
RE_TIMEOUT = re.compile(r'(\d+)(?:[:.](\d+)(?:[:.](\d+))?)?$')
def parse_timeout(s):
mo = RE_TIMEOUT.match(s)
if mo is None: return None
h, m, s = mo.groups()
if m is None and s is None:
# M
m = h
h = None
elif s is None:
# M:S
s = m
m = h
h = None
else:
# H:M:S
pass
if h is None: h = 0
if m is None: m = 0
if s is None: s = 0
h, m, s = int(h), int(m), int(s)
return h * 3600 + m * 60 + s
class Chrono:
timeout = None
date_start = None
date_end = None
initial = None
started = None
def __init__(self, timeout=None, start=False):
self.set_timeout(timeout)
if start: self.start()
def __format(self, delta):
h = delta.seconds // 3600
seconds = delta.seconds % 3600
m = seconds // 60
s = seconds % 60
if h > 0: return '%02i:%02i:%02i' % (h, m, s)
else: return '%02i:%02i' % (m, s)
def __delta(self, timeout):
return Timedelta(seconds=timeout)
def set_timeout(self, timeout=None):
if timeout is not None and not isnum(timeout):
tmp = parse_desthour(str(timeout))
if tmp is None: tmp = parse_timeout(timeout)
if tmp is None: tmp = int(timeout) * 60
timeout = tmp
self.timeout = timeout
if timeout is None: self.initial = '00:00'
else: self.initial = self.__format(self.__delta(timeout))
def start(self, timeout=None):
if timeout is None: timeout = self.timeout
self.date_start = Datetime.today()
if timeout is None: self.date_end = None
else: self.date_end = self.date_start + self.__delta(timeout)
self.started = True
def stop(self):
self.started = False
def is_started(self):
return self.started
def is_end(self):
return self.started and self.date_end is not None and Datetime.today() >= self.date_end
def __repr__(self):
now = Datetime.today()
if self.date_end is None: delta = now - self.date_start
elif now > self.date_end: delta = Timedelta()
else: delta = self.date_end - now
return self.__format(delta)
def run_chronometre(timeout=None, autostart=False):
from Tkinter import Tk, Toplevel, Frame, Label, Entry, Button
import tkMessageBox
class Dialog(Toplevel):
def __init__(self, parent, title=None):
self.result = None
self.have_result = False
Toplevel.__init__(self, parent)
self.transient(parent)
if title: self.title(title)
self.parent = parent
body = Frame(self)
self.initial_focus = self.body(body)
body.pack(padx=5, pady=5)
self.buttonbox()
self.grab_set()
if not self.initial_focus: self.initial_focus = self
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
parent.winfo_rooty()+50))
self.initial_focus.focus_set()
self.wait_window(self)
def set_result(self, result):
self.result = result
self.have_result = True
def body(self, master):
pass
def buttonbox(self):
box = Frame(self)
w = Button(box, text="OK", width=10, command=self.ok, default='active')
w.pack(side='left', padx=5, pady=5)
w = Button(box, text="Annuler", width=10, command=self.cancel)
w.pack(side='left', padx=5, pady=5)
self.bind("<Return>", self.ok)
self.bind("<Escape>", self.cancel)
box.pack()
def ok(self, event=None):
if not self.validate():
self.initial_focus.focus_set()
return
self.withdraw()
self.update_idletasks()
self.apply()
self.cancel()
def cancel(self, event=None):
self.parent.focus_set()
self.destroy()
def validate(self):
return True
def apply(self):
pass
class Config(Dialog):
def body(self, master):
Label(master, text="Nb minutes", padx=20).grid(row=0)
self.entry = Entry(master)
self.entry.grid(row=0, column=1)
return self.entry
def apply(self):
value = self.entry.get()
if value == "": result = None
else: result = value
self.set_result(result)
class Application(Frame):
root = None
chrono = None
stop = None
def __init__(self, timeout=None, autostart=False, **kw):
self.chrono = Chrono(timeout)
self.stop = False
root = Tk()
root.title("Chronomètre")
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
root.bind("c", lambda event: self.do_config())
root.bind("s", lambda event: self.do_start())
root.bind("q", lambda event: self.quit())
self.root = root
kw.update(master=root)
Frame.__init__(self, **kw)
self.TIME = Label(self, width=10, height=2, text=self.chrono.initial, padx=30, pady=10, font=('Helvetica', 18, "normal"))
self.START = Button(self, text="Démarrer", command=self.do_start)
self.CONFIG = Button(self, text="Config", command=self.do_config)
self.QUIT = Button(self, text="Quitter", command=self.quit)
self.grid(column=0, row=0, sticky='nsew')
self.TIME.grid(column=0, row=0, columnspan=3, sticky='nsew')
self.START.grid(column=0, row=1, sticky='ew')
self.CONFIG.grid(column=1, row=1, sticky='ew')
self.QUIT.grid(column=2, row=1, sticky='ew')
self.columnconfigure(0, weight=2)
self.columnconfigure(1, weight=1)
self.columnconfigure(2, weight=2)
self.rowconfigure(0, weight=1)
if autostart: self.do_start()
def update_time(self):
chrono = self.chrono
self.TIME.configure(text=chrono)
if chrono.is_started():
if chrono.is_end():
playSound(DEFAULT_SOUND)
else:
self.root.after(300, self.update_time)
def do_start(self):
self.chrono.start()
self.update_time()
def do_config(self):
chrono = self.chrono
chrono.stop()
config = Config(self.root)
if config.have_result:
try:
chrono.set_timeout(config.result)
self.TIME.configure(text=chrono.initial)
except:
traceback.print_exc()
tkMessageBox.showerror("Valeur invalide", sys.exc_info()[1])
Application(timeout, autostart).mainloop()
if __name__ == '__main__':
from argparse import ArgumentParser, RawTextHelpFormatter
AP = ArgumentParser(
formatter_class=RawTextHelpFormatter,
usage=u"%(prog)s [options] [TIMEOUT]",
description=u"Afficher un chronomètre",
epilog=u"Si TIMEOUT est spécifié, par défaut le décompte démarre automatiquement."
)
AP.set_defaults(autostart=None, timeout=None)
AP.add_argument('timeout', metavar='TIMEOUT', nargs='?',
help=u"""\
(valeur vide)
chronomètre qui démarre à 0:00 et ne s'arrête pas
H:M:S (heures:minutes:secondes)
ou M:S (minutes:secondes)
ou M (minutes)
minuteur qui démarre à H:M:S et fait un décompte jusqu'à 0:00. A la fin
du décompte, une sonnerie retentit.
@H[:M[:S]]
minuteur qui fonctionne comme précédemment, sauf qu'on spécifie l'heure
d'arrivée, et que la durée est calculée automatiquement""")
AP.add_argument('-n', '--no-autostart', dest='autostart', action='store_false',
help=u"Ne pas démarrer automatiquement le décompte même si TIMEOUT est spécifié.")
AP.add_argument('-s', '--autostart', dest='autostart', action='store_true',
help=u"Forcer le démarrage automatique du décompte, même si TIMEOUT n'est pas spécifié.")
o = AP.parse_args()
autostart = o.autostart
if autostart is None: autostart = o.timeout is not None
o.autostart = autostart
timeout = o.timeout
if timeout is None: timeout = DEFAULT_TIMEOUT
elif timeout == '': timeout = None
o.timeout = timeout
run_chronometre(o.timeout, o.autostart)