Throttler.pm dependencies
[hband-tools.git] / xgui-tools / xstopper
blob11f54663a92d3c6235f27ba0f0c3826add6ea9bb
1 #!/usr/bin/env python2.7
2 # -*- coding: utf-8 -*-
4 import os
5 import sys
6 import signal
7 import gtk
8 import gobject
9 import glib
10 import pango
11 import gettext
12 import re
13 import time
14 from datetime import datetime, timedelta
15 from dateutil.relativedelta import relativedelta
16 import math
19 def win_main_show():
20         pass
23 def act_quit():
24         if len(running_timers()) > 0:
25                 pass
26         else:
27                 gtk.main_quit()
28         return True
31 def add_key_binding(widget, keyname, callback):
32         accelgroup = gtk.AccelGroup()
33         key, modifier = gtk.accelerator_parse(keyname)
34         accelgroup.connect_group(key, modifier, gtk.ACCEL_VISIBLE, callback)
35         widget.add_accel_group(accelgroup)
38 class StockButton(gtk.Button):
39         def __init__(self, label=None, stock=None, use_underline=True, icon_size=None, tooltip=None):
40                 if stock is not None and stock in gtk.stock_list_ids():
41                         stock_tmp = stock
42                 else:
43                         stock_tmp = gtk.STOCK_ABOUT
44                 super(self.__class__, self).__init__(stock=stock_tmp, use_underline=use_underline)
45                 if label is not None:
46                         self.set_markup(label)
47                 if stock is None:
48                         self.set_icon('')
49                 elif stock not in gtk.stock_list_ids():
50                         self.set_icon(stock)
51                 if icon_size is not None:
52                         self.set_icon(stock, icon_size)
53                 if tooltip is not None:
54                         self.set_tooltip_text(tooltip)
55         def __get_children(self):
56                 align = self.get_children()[0]
57                 hbox = align.get_children()[0]
58                 return hbox.get_children()
59         def set_label(self, label):
60                 x, lbl = self.__get_children()
61                 lbl.set_label(label)
62         def set_markup(self, label):
63                 x, lbl = self.__get_children()
64                 lbl.set_markup(label)
65         def set_icon(self, icon, size=gtk.ICON_SIZE_BUTTON):
66                 img, x = self.__get_children()
67                 if type(icon) == str:
68                         if icon == '':
69                                 img.props.visible = False
70                         else:
71                                 img.set_from_icon_name(icon, size)
72                                 img.props.visible = True
73                 else:
74                         img.set_from_pixbuf(icon)
75                         img.props.visible = True
79 class Clock(gtk.Label):
80         name = "Clock"
81         
82         def __init__(self):
83                 super(gtk.Label, self).__init__()
84                 self._format = '%H:%M:%S'
85                 self._running = False
86                 self._draining = False
87                 self._update()
88         
89         @property
90         def is_running(self):
91                 return self._running
92         
93         def _next_update_interval_ms(self):
94                 now = time.time()
95                 nextsecond = 1 - (now - int(now))
96                 return int(nextsecond * 1000)
97         
98         def update_display(self):
99                 face = time.strftime(self._format, time.localtime(time.time()))
100                 self.set_markup("<span size='32000'><b>" + face + "</b></span>")
101         
102         def _update(self):
103                 if not self._draining:
104                         self.update_display()
105                         glib.timeout_add(self._next_update_interval_ms(), self._update, priority=glib.PRIORITY_DEFAULT_IDLE)
106                 return False
107         
108         def drain(self):
109                 self._draining = True
112 class AlarmClock(gtk.HBox, Clock):
113         name = "Alarm"
114         
115         __gsignals__ = {
116                 'turned-on': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, ()),
117                 'turned-off': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, ()),
118                 'alarm': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, ()),
119         }
120         
121         def __init__(self):
122                 self.face = AdjustableClock()
123                 self.face.hours.range = (0, 23)
124                 super(gtk.HBox, self).__init__()
125                 super(Clock, self).__init__()
126                 self.pack_start(self.face, expand=False, fill=False)
127                 now = datetime.now()
128                 self.face.set(now.hour, now.minute, now.second)
129                 self.show_all()
130         
131         def turn_on(self):
132                 now = datetime.now()
133                 h, m, s = self.face.components
134                 set_datetime = now.replace(hour=h, minute=m, second=s)
135                 if set_datetime < now:
136                         set_datetime += timedelta(days=1)
137                 self.set_time = int(set_datetime.strftime('%s'))
138                 self.face.lock()
139                 self.alarmer = Alarmer()
140                 self._running = True
141                 self.emit('turned-on')
142         
143         def turn_off(self):
144                 self.face.format_labels(None, None, None)
145                 self.face.unlock()
146                 self._running = False
147                 self.emit('turned-off')
148         
149         def update_display(self):
150                 if self._running:
151                         now = time.time()
152                         if self.set_time <= now:
153                                 if not self.alarmer.alarmed:
154                                         self.emit('alarm')
155                                         self.alarmer.text = "It's passed %02d:%02d:%02d !!" % self.face.components
156                                         self.alarmer.alarm()
157                                         self.turn_on()
158                                 self.face.format_labels(None, None, None, foreground='red')
161 class Stopper(Clock):
162         name = "Stopper"
163         
164         __gsignals__ = {
165                 'started': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, ()),
166                 'paused': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, ()),
167         }
168         
169         def __init__(self):
170                 self._start_time = 0
171                 self._behind_interval = 0
172                 self._running = False
173                 self._last_paused_time = 0
174                 super(self.__class__, self).__init__()
175         
176         def run(self):
177                 if not self._running:
178                         now = time.time()
179                         if self._start_time == 0:
180                                 self._start_time = now
181                         if self._last_paused_time > 0:
182                                 self._behind_interval += now - self._last_paused_time
183                         self._running = True
184                         self.emit('started')
185                         self._update()
186         
187         def pause(self):
188                 if self._running:
189                         self._last_paused_time = time.time()
190                         self._running = False
191                         self.emit('paused')
192                         self._update()
193         
194         def _next_update_interval_ms(self):
195                 return 10
196         
197         def update_display(self):
198                 if self._start_time > 0:
199                         if self._running:
200                                 display_time = time.time() - self._start_time - self._behind_interval
201                         else:
202                                 display_time = self._last_paused_time - self._start_time - self._behind_interval
203                         hours = display_time / 3600
204                         face = '%s%02d:%02d.%02d' % (
205                                 ('%dh ' % hours) if hours >= 1 else '', 
206                                 display_time / 60 % 60, 
207                                 display_time % 60, 
208                                 (display_time - int(display_time)) * 100,
209                         )
210                 else:
211                         face = '00:00.00'
212                 self.set_markup("<span size='32000'><b>" + face + "</b></span>")
215 class SpinLabel(gtk.EventBox):
216         def __init__(self, start_value):
217                 self.format = '%d'
218                 self._value = start_value
219                 self._locked = False
220                 self.range = (0, 59)
221                 self.label = gtk.Label()
222                 super(self.__class__, self).__init__()
223                 self.add(self.label)
224                 self.connect('scroll-event', self._event_scroll)
225                 self.connect('motion-notify-event', self._event_drag)
226                 self.motion_last_pos = 0
227                 self.drag_sensitivity = 30
228                 self.show_all()
229                 self._update_label()
230         def __str__(self):
231                 return str(self._value)
232         def __int__(self):
233                 return self._value
234         @property
235         def value(self):
236                 return self._value
237         @value.setter
238         def value(self, x):
239                 x = int(x)
240                 if x > self.range[1]: x = self.range[0]
241                 if x < self.range[0]: x = self.range[1]
242                 self._value = x
243                 self._update_label()
244         def lock(self):
245                 self._locked = True
246         def unlock(self):
247                 self._locked = False
248         def _event_scroll(self, X, event):
249                 if self._locked: return
250                 delta = +1 if event.direction == gtk.gdk.SCROLL_UP else -1
251                 self.value += delta
252         def _event_drag(self, X, event):
253                 if self._locked: return
254                 diff = event.y - self.motion_last_pos
255                 if abs(diff) > self.drag_sensitivity:
256                         delta = +1 if diff < 0 else -1
257                         self.motion_last_pos = event.y
258                         self.value += delta
259         def _update_label(self):
260                 self.label.set_markup(self.format % self._value)
263 class AdjustableClock(gtk.HBox):
264         def __init__(self, color=None):
265                 self.hours = SpinLabel(0)
266                 self.minutes = SpinLabel(0)
267                 self.seconds = SpinLabel(0)
268                 self.format_labels('%d:', '%02d:', '%02d')
269                 super(gtk.HBox, self).__init__()
270                 self.pack_start(self.hours, expand=False, fill=False)
271                 self.pack_start(self.minutes, expand=False, fill=False)
272                 self.pack_start(self.seconds, expand=False, fill=False)
273         def format_labels(self, fmt_h, fmt_m, fmt_s, **pango_attrs):
274                 span_attr = ''.join([" %s='%s'" % (attr.replace('_', '-'), val) for attr, val in pango_attrs.iteritems()])
275                 fmt = "<span size='32000'%s><b>%%s</b></span>" % (span_attr,)
276                 if fmt_h is not None: self.fmt_h = fmt_h
277                 if fmt_m is not None: self.fmt_m = fmt_m
278                 if fmt_s is not None: self.fmt_s = fmt_s
279                 self.hours.format = fmt % self.fmt_h
280                 self.minutes.format = fmt % self.fmt_m
281                 self.seconds.format = fmt % self.fmt_s
282                 self.hours.value = self.hours.value
283                 self.minutes.value = self.minutes.value
284                 self.seconds.value = self.seconds.value
285         @property
286         def components(self):
287                 return int(self.hours), int(self.minutes), int(self.seconds)
288         def lock(self):
289                 for comp in self.hours, self.minutes, self.seconds:
290                         comp.lock()
291         def unlock(self):
292                 for comp in self.hours, self.minutes, self.seconds:
293                         comp.unlock()
294         def set(self, h, m, s):
295                 self.hours.value = h
296                 self.minutes.value = m
297                 self.seconds.value = s
300 class KitchenTimer(gtk.HBox, Clock):
301         name = "Timer"
302         
303         __gsignals__ = {
304                 'started': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, ()),
305                 'stopped': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, ()),
306                 'due': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, ()),
307         }
308         
309         def __init__(self):
310                 self.face = AdjustableClock()
311                 self.alarmer = Alarmer()
312                 super(gtk.HBox, self).__init__()
313                 super(Clock, self).__init__()
314                 self.pack_start(self.face, expand=False, fill=False)
315                 self.show_all()
316         
317         @property
318         def scheduled_time(self):
319                 h, m, s = self.face.components
320                 return h * 3600 + m * 60 + s
321         
322         def start(self):
323                 if not self._running and not self.alarmer.alarmed:
324                         self.scheduled_time_components = self.face.components
325                         self.face.lock()
326                         self._future_time = time.time() + self.scheduled_time
327                         self._running = True
328                         self.emit('started')
329         
330         def stop(self):
331                 self.face.format_labels(None, None, None)
332                 self._running = False
333                 self.emit('stopped')
334         
335         def update_display(self):
336                 if self._running:
337                         display_time = self._future_time - time.time()
338                         if display_time <= 0:
339                                 if not self.alarmer.alarmed:
340                                         self.emit('due')
341                                         self.alarmer.text = "Your %d:%02d:%02d timer is due!" % self.scheduled_time_components
342                                         self.alarmer.alarm()
343                                 self.face.format_labels('-%d:', None, None, foreground='red')
344                         self.face.set(abs(display_time) / 3600, abs(display_time) / 60 % 60, abs(display_time) % 60)
347 class Alarmer(object):
348         def __init__(self):
349                 self.alarmed = False
350                 self.title = 'Alarm'
351                 self.text = 'alarm!'
352         
353         def alarm(self):
354                 dialog = gtk.MessageDialog(flags=gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_OK, message_format=self.title)
355                 dialog.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLBAR)
356                 dialog.set_keep_above(True)
357                 dialog.set_skip_taskbar_hint(False)
358                 dialog.set_icon_name('alarm-clock')
359                 dialog.get_image().set_from_icon_name('alarm-clock', gtk.ICON_SIZE_DIALOG)
360                 dialog.set_title(self.title)
361                 dialog.format_secondary_text(self.text)
362                 btn_ok = dialog.get_widget_for_response(gtk.RESPONSE_OK)
363                 btn_ok.connect('clicked', lambda *X: dialog.destroy())
364                 dialog.show_all()
365                 win_main.present()
366                 dialog.present()
367                 self.alarmed = True
370 class ClockUI(Clock):
371         @property
372         def clock(self):
373                 return self
376 class StopperUI(gtk.HBox):
377         name = Stopper.name
378         
379         def __init__(self, *pargs, **kwargs):
380                 super(gtk.HBox, self).__init__(*pargs, **kwargs)
381                 self.clock = Stopper()
382                 self.btn_start = StockButton(stock=gtk.STOCK_MEDIA_PLAY, label='')
383                 self.btn_pause = StockButton(stock=gtk.STOCK_MEDIA_PAUSE, label='')
384                 self.btn_start.connect('clicked', lambda *X: self.clock.run())
385                 self.btn_pause.connect('clicked', lambda *X: self.clock.pause())
386                 self.pack_start(self.clock, expand=True, fill=True)
387                 self.pack_start(self.btn_start, expand=False, fill=False)
388                 self.pack_start(self.btn_pause, expand=False, fill=False)
389                 
390                 self.clock.connect('started', lambda *X: self.manage_button_states())
391                 self.clock.connect('paused', lambda *X: self.manage_button_states())
392                 self.manage_button_states()
393         
394         def manage_button_states(self):
395                 running = self.clock.is_running
396                 self.btn_start.set_sensitive(not running)
397                 self.btn_pause.set_sensitive(running)
398         
400 class KitchenTimerUI(gtk.HBox):
401         name = KitchenTimer.name
402         
403         def __init__(self, *pargs, **kwargs):
404                 super(gtk.HBox, self).__init__(*pargs, **kwargs)
405                 self.clock = KitchenTimer()
406                 self.btn_start = StockButton(stock=gtk.STOCK_MEDIA_PLAY, label='')
407                 self.btn_start.connect('clicked', lambda *X: self.clock.start())
408                 self.btn_stop = StockButton(stock=gtk.STOCK_MEDIA_STOP, label='')
409                 self.btn_stop.connect('clicked', lambda *X: self.clock.stop())
410                 self.pack_start(self.clock, expand=True, fill=True)
411                 self.pack_start(self.btn_start, expand=False, fill=False)
412                 self.pack_start(self.btn_stop, expand=False, fill=False)
413                 
414                 self.clock.connect('started', lambda *X: self.manage_button_states())
415                 self.clock.connect('stopped', lambda *X: self.manage_button_states())
416                 self.manage_button_states()
417         
418         def manage_button_states(self):
419                 running = self.clock.is_running
420                 self.btn_start.set_sensitive(not running)
421                 self.btn_stop.set_sensitive(running)
422         
424 class AlarmClockUI(gtk.HBox):
425         name = AlarmClock.name
426         
427         def __init__(self, *pargs, **kwargs):
428                 super(gtk.HBox, self).__init__(*pargs, **kwargs)
429                 self.clock = AlarmClock()
430                 self.btn_set = gtk.ToggleButton()
431                 img = gtk.Image()
432                 img.set_from_icon_name('alarm-clock', size=gtk.ICON_SIZE_BUTTON)
433                 self.btn_set.set_image(img)
434                 self.btn_set.connect('clicked', lambda event: self.clock.turn_on() if self.btn_set.get_active() else self.clock.turn_off())
435                 self.pack_start(self.clock, expand=True, fill=True)
436                 self.pack_start(self.btn_set, expand=False, fill=False)
437                 self.show_all()
440 class MultiClassInstantiatorBox(gtk.HBox):
441         def __init__(self, classes):
442                 super(self.__class__, self).__init__(homogeneous=True, spacing=2)
443                 self._items = []
444                 for cls in classes:
445                         column = gtk.VBox()
446                         add_widget_button = StockButton(stock=gtk.STOCK_ADD, label=cls.name)
447                         add_widget_button.connect('clicked', lambda X, column, cls: self.new_item(column, cls), column, cls)
448                         column.pack_start(add_widget_button, expand=False, fill=False)
449                         self.pack_start(column, expand=False, fill=False)
450                         self.new_item(column, cls)
451                 self.show_all()
452         
453         def new_item(self, column, cls):
454                 item = cls()
455                 self._items.append(item)
456                 removable_box = gtk.HBox()
457                 removable_box.inner_widget = item
458                 button_remove = StockButton(stock=gtk.STOCK_REMOVE, label='')
459                 button_remove.connect('clicked', lambda X, removable_box: self.remove_item(removable_box), removable_box)
460                 removable_box.pack_start(item, expand=True, fill=True)
461                 removable_box.pack_start(button_remove, expand=False, fill=False)
462                 column.pack_start(removable_box, expand=False, fill=False)
463                 column.show_all()
464         
465         @property
466         def items(self):
467                 return self._items
468         
469         def remove_item(self, removable_box):
470                 item = removable_box.inner_widget
471                 if not item.clock.is_running:
472                         self.items.remove(item)
473                         item.clock.drain()
474                         item.destroy()
475                         removable_box.destroy()
478 def running_timers():
479         return [item for item in multibox.items if item.clock.is_running]
482 win_main = gtk.Window(gtk.WINDOW_TOPLEVEL)
483 box0 = gtk.VBox()
485 win_main.set_size_request(800, -1)
486 win_main.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_TOOLBAR)
487 win_main.set_keep_above(True)
488 win_main.set_icon_name('alarm-clock')
490 start_evt = win_main.connect('map-event', lambda w,e: (win_main.disconnect(start_evt), win_main_show()))
491 win_main.connect('delete-event', lambda w,e: act_quit())
492 add_key_binding(win_main, '<Ctrl><Shift>Q', lambda *x: act_quit())
494 box0.pack_start(ClockUI(), expand=False, fill=True)
495 multibox = MultiClassInstantiatorBox(classes=(StopperUI, KitchenTimerUI, AlarmClockUI))
496 box0.pack_start(multibox, expand=True, fill=True)
497 win_main.add(box0)
498 win_main.show_all()
500 gtk.main()