import gtk import gobject from datetime import * from AboutWindow import AboutWindow from Config import * DT_FORMAT = '%Y-%m-%d %H:%M:%S' class Window: def __init__(self, progName, ds, config): self.ds = ds self.config = config self.mark = None if self.config.get('mark') is not None: self.mark = datetime.strptime(self.config.get('mark'), DT_FORMAT) self.shown_projects = {} self.taskStart = datetime.now() self.monday = \ self.taskStart.date() - timedelta(self.taskStart.weekday()) # Top-level Window creation self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title(progName) self.window.set_geometry_hints() self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy_event) self.window.connect("key-press-event", self.window_key_press_event) # Menu Bar self.menubar = gtk.MenuBar() m = gtk.Menu() mi = gtk.MenuItem('E_xit') mi.connect("activate", self.destroy_event) m.append(mi) mi = gtk.MenuItem('_File') mi.set_submenu(m) self.menubar.append(mi) mi = gtk.MenuItem('_Report') self.menubar.append(mi) m = gtk.Menu() mi = gtk.MenuItem('_About') mi.connect("activate", self.show_about_event) m.append(mi) mi = gtk.MenuItem('_Help') mi.set_submenu(m) self.menubar.append(mi) # Projects Table self.projects_present = False self.currProject = 0 self.currTask = 0 self.projects_container = gtk.VBox() self.updateProjects() # Bottom Control Bar self.status_label = gtk.Label() self.status_label.set_alignment(0.0, 0.5) self.status_label.set_padding(5, 0) adjust_button = gtk.Button('Adjust') in_button = gtk.Button('In') in_button.connect("clicked", self.in_event) out_button = gtk.Button('Out') out_button.connect("clicked", self.out_event) exit_button = gtk.Button('Exit') exit_button.connect("clicked", self.destroy_event) hbox = gtk.HBox() hbox.pack_start(self.status_label, expand = True) hbox.pack_start(adjust_button, expand = False) hbox.pack_start(in_button, expand = False) hbox.pack_start(out_button, expand = False) hbox.pack_start(exit_button, expand = False) vbox = gtk.VBox() vbox.pack_start(self.menubar, expand = False) vbox.pack_start(self.projects_container, expand = False) vbox.pack_end(hbox, expand = False) vbox.pack_end(gtk.HSeparator(), expand = False) self.update_status_event() gobject.timeout_add(1000, self.update_status_event) self.window.add(vbox) def updateProjects(self): def projTblCell(text, pnum): lbl = gtk.Label(text) e = gtk.EventBox() e.add(lbl) if pnum == self.currProject: e.set_state(gtk.STATE_SELECTED) return e if self.projects_present: self.projects_container.remove(self.projects_table) self.projects = self.ds.getProjects() self.project_hour_labels = {} self.project_labels_to_pnum = {} projects_to_show = {} for p in self.shown_projects: projects_to_show[p] = 1 project_week_hours = self.getProjectWeekHours() for p in project_week_hours: projects_to_show[p] = 1 self.tasks = self.ds.getTasks(self.currProject) self.task_hour_labels = {} self.task_labels_to_tnum = {} # Header row col_headers = ('Project', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun', 'Tot') rows = 3 + len(projects_to_show) + len(self.tasks) columns = len(col_headers) self.projects_table = gtk.Table(rows = rows, columns = columns) row = 0 for i in range(len(col_headers)): l = gtk.Label() l.set_markup('%s' % col_headers[i]) if i == 0: l.set_alignment(0.0, 0.0) l.set_size_request(60, -1) else: l.set_size_request(40, -1) self.projects_table.attach(l, i, i + 1, row, row + 1) row += 1 pnames_to_show = map(lambda x: self.projects[x], projects_to_show) pnames_to_show.sort() for pname in pnames_to_show: pnum = self.getProjectNum(pname) e = projTblCell(pname, pnum) e.get_child().set_alignment(0.0, 0.0) self.project_labels_to_pnum[e.get_child()] = pnum e.connect("button-press-event", self.project_click_event) self.projects_table.attach(e, 0, 1, row, row + 1) self.project_hour_labels[pnum] = {} for i in range(8): e = projTblCell('', pnum) self.projects_table.attach(e, i + 1, i + 2, row, row + 1) self.project_hour_labels[pnum][i] = e.get_child() e.connect("button-press-event", self.project_click_event) self.project_labels_to_pnum[e.get_child()] = pnum row += 1 # Totals row l = gtk.Label() l.set_markup('Total') l.set_alignment(0.0, 0.0) self.projects_table.attach(l, 0, 1, row, row + 1) row += 1 # Add project row self.new_project_combobox = gtk.combo_box_entry_new_text() self.new_project_combobox.set_size_request(250, -1) self.new_project_combobox.connect("key-release-event", self.project_key_press_event) project_names = self.projects.values() project_names.sort() for project in project_names: self.new_project_combobox.append_text(project) self.add_project_button = gtk.Button('Add') self.add_project_button.connect('clicked', self.add_project_event) hbox = gtk.HBox() hbox.pack_start(self.new_project_combobox, expand = False) hbox.pack_start(self.add_project_button, expand = False) self.projects_table.attach(hbox, 0, columns, row, row + 1) self.projects_table.set_row_spacings(3) row += 1 # Add Tasks information if we have a current project if self.currProject > 0: l = gtk.Label() l.set_markup('Tasks') l.set_alignment(0.0, 0.0) self.projects_table.attach(l, 0, 1, row, row + 1) row += 1 for tnum in self.tasks: l = gtk.Label(self.tasks[tnum]) l.set_alignment(0.0, 0.0) e = gtk.EventBox() e.add(l) self.task_labels_to_tnum[l] = tnum if tnum == self.currTask: e.set_state(gtk.STATE_SELECTED) e.connect("button-press-event", self.task_click_event) self.projects_table.attach(e, 0, 1, row, row + 1) self.task_hour_labels[tnum] = {} for i in range(8): e = gtk.EventBox() l = gtk.Label() e.add(l) if tnum == self.currTask: e.set_state(gtk.STATE_SELECTED) self.projects_table.attach(e, i + 1, i + 2, row, row + 1) self.task_hour_labels[tnum][i] = l e.connect("button-press-event", self.task_click_event) self.task_labels_to_tnum[l] = tnum row += 1 # Totals row l = gtk.Label() l.set_markup('Total') l.set_alignment(0.0, 0.0) self.projects_table.attach(l, 0, 1, row, row + 1) row += 1 # Add task row self.new_task_combobox = gtk.combo_box_entry_new_text() self.new_task_combobox.set_size_request(250, -1) self.new_task_combobox.connect("key-release-event", self.task_key_press_event) task_names = self.tasks.values() task_names.sort() for task in task_names: self.new_task_combobox.append_text(task) self.add_task_button = gtk.Button('Add') self.add_task_button.connect('clicked', self.add_task_event) hbox = gtk.HBox() hbox.pack_start(self.new_task_combobox, expand = False) hbox.pack_start(self.add_task_button, expand = False) self.projects_table.attach(hbox, 0, columns, row, row + 1) self.projects_table.set_row_spacings(3) row += 1 self.projects_container.pack_start(self.projects_table, expand = False) self.projects_present = True self.projects_table.show_all() self.updateProjectHours() self.updateTaskHours() def updateProjectHours(self): totals = {} for day in range(7): dt = str(self.monday + timedelta(day)) day_hours = self.ds.getProjectDailyHours(dt) for p in day_hours: if p in self.project_hour_labels: hrs = self.hoursFromSeconds(day_hours[p]) if not p in totals: totals[p] = 0 totals[p] += float(hrs) self.project_hour_labels[p][day].set_text(hrs) for p in totals: if p in self.project_hour_labels: self.project_hour_labels[p][7].set_text(str(totals[p])) def updateTaskHours(self): totals = {} for day in range(7): dt = str(self.monday + timedelta(day)) day_hours = self.ds.getDailyHours(dt) if self.currProject in day_hours: for t in day_hours[self.currProject]: if t in self.task_hour_labels: hrs = self.hoursFromSeconds( day_hours[self.currProject][t]) if not t in totals: totals[t] = 0 totals[t] += float(hrs) self.task_hour_labels[t][day].set_text(hrs) for t in totals: if t in self.task_hour_labels: self.task_hour_labels[t][7].set_text(str(totals[t])) def getProjectWeekHours(self): proj_week_hours = {} for day in range(7): dt = str(self.monday + timedelta(day)) day_hours = self.ds.getProjectDailyHours(dt) for p in day_hours: if not p in proj_week_hours: proj_week_hours[p] = 0 proj_week_hours[p] += float(day_hours[p]) return proj_week_hours def hoursFromSeconds(self, secs): if secs < 0.01 * 60 * 60: secs = 0.01 * 60 * 60; return "%.2f" % (secs / 60.0 / 60.0) def main(self): self.window.show_all() gtk.main() def getProjectNum(self, pname): for p in self.projects: if self.projects[p] == pname: return p return 0 def getTaskNum(self, tname): for t in self.tasks: if self.tasks[t] == tname: return t return 0 def add_project_event(self, button, data=None): pname = self.new_project_combobox.get_active_text() if pname in self.projects.values(): pnum = self.getProjectNum(pname) elif len(pname.strip()): pnum = self.ds.createProject(pname) else: pnum = 0 if pnum: self.shown_projects[pnum] = 1 self.project_select_event(pnum) self.updateProjects() def project_key_press_event(self, widget, event, data=None): if event.keyval == gtk.gdk.keyval_from_name("Return"): self.add_project_event(1) return True return False def project_click_event(self, ebox, event, data=None): lbl = ebox.get_child() if lbl: if event.button == 1: if lbl in self.project_labels_to_pnum: return self.project_select_event( self.project_labels_to_pnum[lbl]) elif event.button == 3: # TODO: handle right-click return True return False def project_select_event(self, pnum): if pnum: self.currProject = pnum self.currTask = 0 self.updateProjects() return True return False def add_task_event(self, button, data=None): tname = self.new_task_combobox.get_active_text() if tname in self.tasks.values(): tnum = self.getTaskNum(tname) elif len(tname.strip()): tnum = self.ds.createTask(self.currProject, tname) else: tnum = 0 if tnum: self.tasks = self.ds.getTasks(self.currProject) self.task_select_event(tnum) self.updateProjects() def task_key_press_event(self, widget, event, data=None): if event.keyval == gtk.gdk.keyval_from_name("Return"): self.add_task_event(1) return True return False def task_click_event(self, ebox, event, data=None): lbl = ebox.get_child() if lbl: if event.button == 1: if lbl in self.task_labels_to_tnum: return self.task_select_event(self.task_labels_to_tnum[lbl]) elif event.button == 3: # TODO: handle right-click return True return False def task_select_event(self, tnum): if tnum: self.updateCurrTaskHours() self.currTask = tnum self.updateProjects() return True return False def updateCurrTaskHours(self): nst = datetime.now() delta = nst - self.taskStart self.ds.addTaskHours(self.currTask, nst.date(), delta.seconds) self.taskStart = nst def delete_event(self, widget, event, data=None): return False def destroy_event(self, widget, data=None): # could be called by window or exit button # save any config changes if self.mark is not None: self.config.set('mark', self.mark.strftime(DT_FORMAT)) else: self.config.clear('mark') self.config.write() gtk.main_quit() def window_key_press_event(self, widget, event, data=None): if event.keyval == gtk.gdk.keyval_from_name("Escape"): self.destroy_event(widget, data) def show_about_event(self, widget, data=None): about_window = AboutWindow() def update_status_event(self): if self.mark is not None: now = datetime.now() td = now - self.mark days = td.days hours = td.seconds / 60 / 60 mins = (td.seconds - hours * 60 * 60) / 60 secs = (td.seconds - hours * 60 * 60 - mins * 60) markfmt = '' + \ str(int(self.mark.strftime("%I"))) + \ self.mark.strftime(":%M") + \ self.mark.strftime("%p")[0].lower() + '' if now.date() != self.mark.date(): markfmt = '' + \ self.mark.strftime("%m/%d ") + '' + markfmt elapsedfmt = '' if days != 0: elapsedfmt += '%dd ' % days if hours != 0: elapsedfmt += '%dh ' % hours if mins != 0: elapsedfmt += '%dm ' % mins elapsedfmt += '%ds' % secs self.status_label.set_markup('Mark: %s Elapsed: %s' % \ (markfmt, elapsedfmt)) else: self.status_label.set_markup('No mark set') return True def in_event(self, widget, data=None): if self.mark is None: self.mark = datetime.now() self.update_status_event() else: # TODO: error message? pass def out_event(self, widget, data=None): self.mark = None self.update_status_event() # TODO: bundle elapsed time or discard?