|
| 1 | +# |
| 2 | +# GUI for the timer implementation |
| 3 | +# |
| 4 | +# Copyright (C) 2011 Santosh Sivaraj <[email protected]> |
| 5 | +# |
| 6 | +# This program is free software; you can redistribute it and/or modify |
| 7 | +# it under the terms of the GNU General Public License as published by |
| 8 | +# the Free Software Foundation; either version 2 of the License, or |
| 9 | +# (at your option) any later version. |
| 10 | +# |
| 11 | +# This program is distributed in the hope that it will be useful, |
| 12 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | +# GNU General Public License for more details. |
| 15 | +# |
| 16 | +# You should have received a copy of the GNU General Public License |
| 17 | +# along with this program; if not, write to the Free Software |
| 18 | +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 19 | +# 02110-1301, USA |
| 20 | +# |
| 21 | + |
| 22 | +# List of Todo items: |
| 23 | +# TODO Update the timer list when a new timer is added when there was none |
| 24 | +# TODO system tray notification when timer has expired |
| 25 | +# TODO Play sound when timer expires |
| 26 | +# TODO There is a timing issue in label update and expiry (see 153,154 in timer_timer) |
| 27 | +# TODO Remove timer thread and implement as a schedulable time event, so we don't loop and waste cycles |
| 28 | + |
| 29 | +from nagme_timer import Timer |
| 30 | +from collections import deque |
| 31 | +import pygtk |
| 32 | +pygtk.require('2.0') |
| 33 | +import gtk, gobject |
| 34 | +import time |
| 35 | +from datetime import datetime |
| 36 | +import os |
| 37 | +if os.name == 'nt': |
| 38 | + import winsound, sys |
| 39 | + |
| 40 | +class MessageDialog_nb(gtk.MessageDialog): |
| 41 | + |
| 42 | + def __init__(self, parent=None, typ=gtk.MESSAGE_INFO, |
| 43 | + buttons=gtk.BUTTONS_NONE, message_format=None): |
| 44 | + super(MessageDialog_nb, self).__init__(parent, |
| 45 | + gtk.DIALOG_DESTROY_WITH_PARENT|gtk.DIALOG_MODAL, |
| 46 | + typ, buttons, |
| 47 | + message_format) |
| 48 | + |
| 49 | + def dialog_response_cb(self, response_id, param): |
| 50 | + self.destroy() |
| 51 | + |
| 52 | + def run_nb(self): |
| 53 | + if not self.modal: |
| 54 | + self.set_modal(True) |
| 55 | + self.connect('response', self.dialog_response_cb) |
| 56 | + self.show() |
| 57 | + |
| 58 | +class tui(Timer): |
| 59 | + notify_event = ("added", "expired", "deleted") |
| 60 | + TIMER_ADDED = 0 |
| 61 | + TIMER_EXPIRED = 1 |
| 62 | + TIMER_DELETED = 2 |
| 63 | + |
| 64 | + def stop(self): |
| 65 | + return super(tui, self).stop() |
| 66 | + |
| 67 | + def delete_event(self, widget, event, data=None): |
| 68 | + if self.confirm_close: |
| 69 | + dialog = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, |
| 70 | + gtk.MESSAGE_INFO, gtk.BUTTONS_YES_NO, |
| 71 | + "Sure you wanna quit??? Click \'No\' to keep nagging you.") |
| 72 | + dialog.set_title("Exit timer?") |
| 73 | + |
| 74 | + response = dialog.run() |
| 75 | + dialog.destroy() |
| 76 | + if response == gtk.RESPONSE_YES: |
| 77 | + return False |
| 78 | + return True |
| 79 | + |
| 80 | + return False |
| 81 | + |
| 82 | + def destroy(self, widget, data=None): |
| 83 | + self.stop() |
| 84 | + gtk.main_quit() |
| 85 | + |
| 86 | + def notify(self, event, timer_text, title=None): |
| 87 | + d = {'text':timer_text, 'title':title if title else self.notify_event[event], |
| 88 | + 'type':self.notify_event[event]} |
| 89 | + self.notify_list.append(d) |
| 90 | + |
| 91 | + def get_values(self, widget, data=None): |
| 92 | + txt = self.textentry.get_text() |
| 93 | + |
| 94 | + if self.relative_time: |
| 95 | + self.add_timer_rel(self.hourspin.get_value_as_int(), |
| 96 | + self.minspin.get_value_as_int(), |
| 97 | + txt) |
| 98 | + else: |
| 99 | + self.add_timer(self.hourspin.get_value_as_int(), |
| 100 | + self.minspin.get_value_as_int(), txt) |
| 101 | + |
| 102 | + self.reset_values(widget, data) |
| 103 | + self.update_timer_list() |
| 104 | + |
| 105 | + def reset_values(self, widget, data=None): |
| 106 | + self.textentry.set_text("") |
| 107 | + |
| 108 | + if self.relative_time: |
| 109 | + self.hourspin.spin(gtk.SPIN_HOME) |
| 110 | + self.minspin.spin(gtk.SPIN_HOME) |
| 111 | + else: |
| 112 | + t = time.localtime(time.time()) |
| 113 | + self.hourspin.set_value(t[3]) |
| 114 | + self.minspin.set_value(t[4]) |
| 115 | + |
| 116 | + def toggle_window_hide(self): |
| 117 | + if self.mainwin_hidden: |
| 118 | + self.window.show() |
| 119 | + self.window.deiconify() |
| 120 | + else: self.window.hide() |
| 121 | + |
| 122 | + self.mainwin_hidden = not self.mainwin_hidden |
| 123 | + self.statusicon.set_visible(self.mainwin_hidden) |
| 124 | + |
| 125 | + def status_clicked(self, status): |
| 126 | + self.toggle_window_hide() |
| 127 | + #self.statusicon.set_tooltip("the window is visible") |
| 128 | + |
| 129 | + def timer_timer(self, pobj): |
| 130 | + while len(self.notify_list): |
| 131 | + i = self.notify_list.popleft() |
| 132 | + text = "<big><b>" + i['title'].capitalize() + "</b></big>\n\n<tt>" + i['text'] + "</tt>" |
| 133 | + |
| 134 | + if self.expander.get_expanded(): |
| 135 | + if not self.next_timer: |
| 136 | + self.expander.set_expanded(False) |
| 137 | + else: |
| 138 | + self.update_timer_list() |
| 139 | + |
| 140 | + if i['type'] == "expired": |
| 141 | + self.update_timer_list() |
| 142 | + md = MessageDialog_nb(self.window, gtk.MESSAGE_INFO, |
| 143 | + gtk.BUTTONS_OK, None) |
| 144 | + md.set_markup(text) |
| 145 | + #self.statusicon.set_tooltip(i['text']) |
| 146 | + md.run_nb() |
| 147 | + if os.name == 'nt': |
| 148 | + winsound.PlaySound("*", winsound.SND_ALIAS) |
| 149 | + |
| 150 | + self.statusbar.push(0, i['title'] + " " + i['text']) |
| 151 | + |
| 152 | + # Countdown |
| 153 | + if not self.mainwin_hidden: |
| 154 | + if self.next_timer: |
| 155 | + t = datetime.fromtimestamp(self.next_timer) - datetime.now() |
| 156 | + text = "<i>Next:</i> <b><tt>" + self.get_timer_text(self.next_timer) + "</tt></b> <i>in</i>\n<big>" + str(t).split(".")[0] + "</big>" |
| 157 | + else: |
| 158 | + text = "<b>Nothing to remind</b>" |
| 159 | + |
| 160 | + self.timer_counter.set_markup(text) |
| 161 | + |
| 162 | + return True |
| 163 | + |
| 164 | + def win_state_cb(self, widget, event): |
| 165 | + if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED: |
| 166 | + if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED: |
| 167 | + self.toggle_window_hide() |
| 168 | + |
| 169 | + def relative_time_cb(self, tb): |
| 170 | + self.relative_time = not tb.get_active() |
| 171 | + |
| 172 | + txt = self.textentry.get_text() |
| 173 | + self.reset_values(None, None) |
| 174 | + # Put back whatever text was there, reset_values resets all fields |
| 175 | + self.textentry.set_text(txt) |
| 176 | + |
| 177 | + def confirm_close_cb(self, tb): |
| 178 | + self.confirm_close = tb.get_active() |
| 179 | + |
| 180 | + def update_timer_list(self): |
| 181 | + if self.expander.get_expanded() and self.next_timer: |
| 182 | + self.liststore.clear() |
| 183 | + tl = self.get_timerlist() |
| 184 | + if tl: |
| 185 | + for t in tl: |
| 186 | + self.liststore.append(t) |
| 187 | + |
| 188 | + def update_expander(self, expander): |
| 189 | + if not self.next_timer: |
| 190 | + label = gtk.Label("You haven't added anything") |
| 191 | + expander.add(label) |
| 192 | + label.show() |
| 193 | + return; |
| 194 | + |
| 195 | + box = gtk.VBox(False, 0) |
| 196 | + self.liststore = gtk.ListStore(str, str) |
| 197 | + self.treeview = gtk.TreeView(self.liststore) |
| 198 | + # For timer text |
| 199 | + textcell = gtk.CellRendererText() |
| 200 | + self.textcol = gtk.TreeViewColumn("What?", textcell, text=0) |
| 201 | + self.textcol.set_property('resizable', True) |
| 202 | + # For time (in absolute values only) |
| 203 | + timecell = gtk.CellRendererText() |
| 204 | + self.timecol = gtk.TreeViewColumn("When?", timecell, text=1) |
| 205 | + self.timecol.set_property('resizable', True) |
| 206 | + self.treeview.append_column(self.textcol) |
| 207 | + self.treeview.append_column(self.timecol) |
| 208 | + box.pack_start(self.treeview, True, True, 0) |
| 209 | + self.treeview.show() |
| 210 | + |
| 211 | + buttonDel = gtk.Button(label=None, stock=gtk.STOCK_DELETE) |
| 212 | + buttonDel.connect("clicked", self.del_timer, None) |
| 213 | + buttonDel.show() |
| 214 | + # label.set_size_request(200, 100) |
| 215 | + box.pack_start(buttonDel, False, True, 0) |
| 216 | + expander.add(box) |
| 217 | + box.show() |
| 218 | + |
| 219 | + def del_timer(self, widget, data=None): |
| 220 | + selection = self.treeview.get_selection() |
| 221 | + model, iter = selection.get_selected() |
| 222 | + |
| 223 | + if iter: |
| 224 | + i = selection.get_selected_rows()[1][0][0] |
| 225 | + model.remove(iter) |
| 226 | + tl = self.del_timer_at_index(i) |
| 227 | + self.notify(self.TIMER_DELETED, tl[1], "Deleted") |
| 228 | + |
| 229 | + return |
| 230 | + |
| 231 | + def expanded(self, expander, parameter): |
| 232 | + if expander.get_expanded(): |
| 233 | + self.update_expander(expander) |
| 234 | + self.update_timer_list() |
| 235 | + else: |
| 236 | + if expander.child: |
| 237 | + expander.remove(expander.child) |
| 238 | + # expander.get_parent().resize(200, 1) |
| 239 | + |
| 240 | + def create_window(self): |
| 241 | + # GTK initialisation |
| 242 | + window = gtk.Window(gtk.WINDOW_TOPLEVEL) |
| 243 | + window.connect("delete_event", self.delete_event) |
| 244 | + window.connect("destroy", self.destroy) |
| 245 | + window.connect("window-state-event", self.win_state_cb) |
| 246 | + window.set_border_width(10) |
| 247 | + window.set_title("NagMe") |
| 248 | + window.set_resizable(False) |
| 249 | + |
| 250 | + self.statusicon = gtk.status_icon_new_from_stock(gtk.STOCK_GOTO_TOP) |
| 251 | + self.statusicon.connect('activate', self.status_clicked) |
| 252 | + self.statusicon.set_visible(self.mainwin_hidden) |
| 253 | + |
| 254 | + vbox = gtk.VBox(False, 5) |
| 255 | + |
| 256 | + self.statusbar = gtk.Statusbar() |
| 257 | + vbox.pack_end(self.statusbar, False, True, 0) |
| 258 | + |
| 259 | + # I need to say something |
| 260 | + label = gtk.Label("Don't forget things, just tell me, I will nag you!!\n" |
| 261 | + "What you want to be reminded of?") |
| 262 | + vbox.pack_start(label, True, True, 0) |
| 263 | + |
| 264 | + self.textentry = gtk.Entry(max = 0) |
| 265 | + vbox.pack_start(self.textentry, True, True, 0) |
| 266 | + |
| 267 | + self.ptimer = gobject.timeout_add(200, self.timer_timer, self) |
| 268 | + |
| 269 | + hbox1 = gtk.HBox(True, 5) |
| 270 | + hbox2 = gtk.HBox(True, 5) |
| 271 | + hbox3 = gtk.HBox(True, 5) |
| 272 | + hbox0 = gtk.HBox(True, 5) |
| 273 | + vbox.pack_start(hbox1, True, True, 0) |
| 274 | + vbox.pack_start(hbox2, True, True, 0) |
| 275 | + vbox.pack_start(hbox3, True, True, 0) |
| 276 | + window.add(vbox) |
| 277 | + |
| 278 | + label = gtk.Label("Hour :") |
| 279 | + label.set_alignment(0, 0.5) |
| 280 | + hbox1.pack_start(label, False, True, 0) |
| 281 | + adj = gtk.Adjustment(0, 0.0, 23.0, 1.0, 5.0, 0.0) |
| 282 | + self.hourspin = gtk.SpinButton(adj, 0, 0) |
| 283 | + self.hourspin.set_wrap(True) |
| 284 | + hbox1.pack_start(self.hourspin, False, True, 0) |
| 285 | + |
| 286 | + label = gtk.Label("Minute :") |
| 287 | + label.set_alignment(0, 0.5) |
| 288 | + hbox1.pack_start(label, False, True, 0) |
| 289 | + adj = gtk.Adjustment(0, 0.0, 59.0, 1.0, 5.0, 0.0) |
| 290 | + self.minspin = gtk.SpinButton(adj, 0, 0) |
| 291 | + self.minspin.set_wrap(True) |
| 292 | + hbox1.pack_start(self.minspin, False, True, 0) |
| 293 | + |
| 294 | + # Add a button for OK |
| 295 | + buttonOK = gtk.Button(label=None, stock=gtk.STOCK_ADD) |
| 296 | + buttonOK.connect("clicked", self.get_values, None) |
| 297 | + hbox2.pack_start(buttonOK, True, True, 0) |
| 298 | + add_tt = gtk.Tooltips() |
| 299 | + add_tt.set_tip(buttonOK, "Add timer", tip_private=None) # Tool tip |
| 300 | + |
| 301 | + # Add a button for clearing |
| 302 | + buttonClear = gtk.Button(label=None, stock=gtk.STOCK_CLEAR) |
| 303 | + buttonClear.connect_object("clicked", self.reset_values, None) |
| 304 | + hbox2.pack_start(buttonClear, True, True, 0) |
| 305 | + clear_tt = gtk.Tooltips() |
| 306 | + clear_tt.set_tip(buttonClear, "Clear Form", tip_private=None) |
| 307 | + |
| 308 | + # Close confirmation |
| 309 | + confirm_close_toggle = gtk.CheckButton("_Confirm Close?") |
| 310 | + confirm_close_toggle.connect("toggled", self.confirm_close_cb) |
| 311 | + confirm_close_toggle.set_active(self.confirm_close) |
| 312 | + hbox3.pack_start(confirm_close_toggle, True, True, 0) |
| 313 | + |
| 314 | + # Relative or absolute timing? |
| 315 | + relative_time_toggle = gtk.CheckButton("_Absolute timing?") |
| 316 | + relative_time_toggle.connect("toggled", self.relative_time_cb) |
| 317 | + hbox3.pack_start(relative_time_toggle, True, True, 0) |
| 318 | + |
| 319 | + # Make a display to list timers |
| 320 | + label_exp = gtk.Label("Timers") |
| 321 | + self.expander = gtk.Expander(None) |
| 322 | + self.expander.set_label_widget(label_exp) |
| 323 | + self.expander.connect("notify::expanded", self.expanded) |
| 324 | + vbox.pack_end(self.expander, True, True, 0) |
| 325 | + list_tt = gtk.Tooltips() |
| 326 | + list_tt.set_tip(self.expander, "Show all timers", tip_private=None) |
| 327 | + |
| 328 | + self.timer_counter = gtk.Label("Nothing to remind") |
| 329 | + self.timer_counter.set_justify(gtk.JUSTIFY_CENTER) |
| 330 | + vbox.pack_start(self.timer_counter, True, True, 0) |
| 331 | + |
| 332 | + return window |
| 333 | + |
| 334 | + def __init__(self): |
| 335 | + self.notify_list = deque([]) |
| 336 | + self.mainwin_hidden = False |
| 337 | + self.relative_time = True |
| 338 | + self.confirm_close = True |
| 339 | + |
| 340 | + self.window = self.create_window() |
| 341 | + super(tui, self).__init__(self) |
| 342 | + self.window.show_all() |
| 343 | + self.start() |
| 344 | + |
| 345 | + def main(self): |
| 346 | + gtk.main() |
| 347 | + #self.test_timer() |
| 348 | + |
| 349 | + def test_timer(self): |
| 350 | + print "Nagme test start" |
| 351 | + self.add_timer_rel(5, 30, "Five hours from now") |
| 352 | + self.add_timer_rel(1, 0, "One hour from now") |
| 353 | + self.add_timer_rel(1, 0, "Another timer at one") |
| 354 | + self.add_timer_rel(23, 0, "New timer at 23 hours from now") |
| 355 | + self.add_timer_rel(12, 30, "Timer at 13 hours from now") |
| 356 | + self.add_timer_rel(2, 1, "A little later") |
| 357 | + self.add_timer_rel(0, 0, None) |
| 358 | + self.add_timer_rel(0, 0, "Now Now") |
| 359 | + self.add_timer_rel(0, 1, "1 min from now") |
| 360 | + self.list_timers() |
| 361 | + print "peek_next: " + str(self.peek_next()) |
| 362 | + print "at_index: " + str(self.at_index(2)) |
| 363 | + print "at_index: " + str(self.at_index(9)) |
| 364 | + print "pop_next: " + str(test.pop_next()) |
| 365 | + self.list_timers() |
| 366 | + self.print_times() |
| 367 | + print "Test complete" |
0 commit comments