from functools import partial from gettext import gettext as _ from gi.repository import Gio, GLib, Gdk, Gtk, Handy from gi.repository import GLib from gi.repository.GdkPixbuf import Pixbuf import os import ntpath import threading from datetime import datetime, timedelta from timetrack.activity import Activity from timetrack.activity import ActivityWidget from timetrack.activity import ActivitySummary from timetrack.activity import ActivitySummaryWidget from timetrack.edit import EditDialog class MainWindow(Gtk.ApplicationWindow): builder = NotImplemented application = NotImplemented current_activity = NotImplemented activity_list = NotImplemented activity_model = NotImplemented main_stack = NotImplemented report_stack = NotImplemented report_stack_switcher = NotImplemented reportbar = NotImplemented timer = NotImplemented quit_dialog = NotImplemented headerbar = NotImplemented start_button = NotImplemented stop_button = NotImplemented button_stack = NotImplemented activity_stack = NotImplemented activity_entry = NotImplemented activity_label = NotImplemented comments = None start_date = None activity_id = None report_day_list = NotImplemented report_week_list = NotImplemented report_month_list = NotImplemented report_day_model = NotImplemented report_week_model = NotImplemented report_month_model = NotImplemented report_left = NotImplemented report_right = NotImplemented report_label = NotImplemented report_calendar = NotImplemented report_total = NotImplemented disable_calendar_change = False report_day_date = datetime.now() report_week_date = datetime.now() report_month_date = datetime.now() report_total_day = 0 report_total_week = 0 report_total_month = 0 new_action = NotImplemented edit_action = NotImplemented remove_action = NotImplemented current_name = NotImplemented current_hour = NotImplemented current_minute = NotImplemented current_day = NotImplemented def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.logging_manager = kwargs['application'].get_logger() self.assemble_window() self.set_size_request(-1, 500) def assemble_window(self): self.builder = Gtk.Builder() self.builder.add_from_resource("/net/danigm/timetrack/main_window.ui") self.create_headerbar() self.create_container() self.custom_css() def create_headerbar(self): self.headerbar = self.builder.get_object("headerbar") self.set_titlebar(self.headerbar) self.button_stack = self.builder.get_object('button-stack') self.activity_stack = self.builder.get_object('activity-stack') self.start_button = self.builder.get_object('start-button') self.stop_button = self.builder.get_object('stop-button') self.activity_entry = self.builder.get_object('activity-entry') self.activity_label = self.builder.get_object('activity-label') self.activity_entry_buffer = self.activity_entry.get_buffer() self.activity_entry.connect("activate", self.startstop_activity) self.activity_entry_buffer.connect("inserted-text", self.check_enable_start) self.activity_entry_buffer.connect("deleted-text", self.check_enable_start) if Gio.Application.get_default().development_mode is True: context = self.get_style_context() #context.add_class("devel") # TODO: Add comments def create_container(self): self.main_stack = self.builder.get_object('main-stack') self.timer = self.builder.get_object('timer') self.activity_list = self.builder.get_object('activity-list') self.activity_model = Gio.ListStore() self.activity_list.bind_model(self.activity_model, self.create_activity_widget) self.activity_list.connect("selected-rows-changed", self.update_actions, None) self.create_current_popup() self.create_report() self.add(self.main_stack) self.show_all() def create_current_popup(self): self.current_name = self.builder.get_object('current-name') self.current_hour = self.builder.get_object('current-hour') self.current_minute = self.builder.get_object('current-minute') self.current_day = self.builder.get_object('current-day') self.current_timer = self.builder.get_object('current-timer') self.current_comments = self.builder.get_object('current-comments') self.current_name.bind_property('text', self.activity_label, 'label') self.timer.bind_property('label', self.current_timer, 'label') self.current_name.connect('changed', self.change_current) self.current_hour.connect('changed', self.change_current) self.current_minute.connect('changed', self.change_current) self.current_day.connect('day-selected', self.change_current) self.current_comments.get_buffer().connect('changed', self.change_current) def create_report(self): self.report_stack = self.builder.get_object('report-stack') self.report_stack.connect("notify::visible-child", self.report_changed) self.reportbar = self.builder.get_object("reportbar") self.report_left = self.builder.get_object('report-left') self.report_right = self.builder.get_object('report-right') self.report_label = self.builder.get_object('report-date') self.report_calendar = self.builder.get_object('report-calendar') self.report_calendar.connect("day-selected", self.report_change_day) self.report_total = self.builder.get_object('report-total') self.report_day_list = self.builder.get_object('report-day-list') self.report_week_list = self.builder.get_object('report-week-list') self.report_month_list = self.builder.get_object('report-month-list') self.report_day_model = Gio.ListStore() self.report_week_model = Gio.ListStore() self.report_month_model = Gio.ListStore() self.report_day_list.bind_model(self.report_day_model, self.create_activity_summary_widget) self.report_week_list.bind_model(self.report_week_model, self.create_activity_summary_widget) self.report_month_list.bind_model(self.report_month_model, self.create_activity_summary_widget) self.report_filter = self.builder.get_object('report-filter') self.report_filter.connect("activate", self.filter_report) def set_headerbar(self): self.set_titlebar(self.headerbar) def get_headerbar(self): return self.headerbar def custom_css(self): screen = Gdk.Screen.get_default() css_provider = Gtk.CssProvider() css_provider_resource = Gio.File.new_for_uri( "resource:///net/danigm/timetrack/timetrack.css") css_provider.load_from_file(css_provider_resource) context = Gtk.StyleContext() context.add_provider_for_screen( screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) def add_actions(self): start_action = Gio.SimpleAction.new("activity.start", None) start_action.connect("activate", self.startstop_activity) start_action.set_enabled(False) self.start_action = start_action self.application.add_action(start_action) stop_action = Gio.SimpleAction.new("activity.stop", None) stop_action.connect("activate", self.startstop_activity) self.application.add_action(stop_action) action = Gio.SimpleAction.new("report", None) action.connect("activate", self.show_report) self.application.add_action(action) action = Gio.SimpleAction.new("list", None) action.connect("activate", self.show_list) self.application.add_action(action) # new action = Gio.SimpleAction.new("new", None) action.connect("activate", self.new_activity) self.new_action = action self.application.add_action(action) # edit action = Gio.SimpleAction.new("edit", None) action.connect("activate", self.edit_activity) action.set_enabled(False) self.edit_action = action self.application.add_action(action) # remove action = Gio.SimpleAction.new("remove", None) action.connect("activate", self.remove_activity) action.set_enabled(False) self.remove_action = action self.application.add_action(action) # report actions action = Gio.SimpleAction.new("report-left", None) action.connect("activate", self.report_go_left) self.application.add_action(action) action = Gio.SimpleAction.new("report-right", None) action.connect("activate", self.report_go_right) self.application.add_action(action) action = Gio.SimpleAction.new("report-today", None) action.connect("activate", self.report_go_today) self.application.add_action(action) action = Gio.SimpleAction.new("report-day", None) action.connect("activate", partial(self.report_show, "day")) self.application.add_action(action) action = Gio.SimpleAction.new("report-week", None) action.connect("activate", partial(self.report_show, "week")) self.application.add_action(action) action = Gio.SimpleAction.new("report-month", None) action.connect("activate", partial(self.report_show, "month")) self.application.add_action(action) action = Gio.SimpleAction.new("report-filter", None) action.connect("activate", self.filter) self.application.add_action(action) def filter(self, *args, **kwargs): self.show_report() self.report_filter.grab_focus() def report_show(self, name, *args, **kwargs): self.show_report() self.report_stack.set_visible_child_name(name) def show_report(self, *args, **kwargs): self.main_stack.set_visible_child_name("report") self.set_titlebar(self.reportbar) self.fill_report() def fill_report(self): self.report_label.set_text(self.report_day_date.strftime("%d/%M/%Y")) self.fill_report_day() self.fill_report_week() self.fill_report_month() self.report_changed() def fill_report_day(self): now = self.report_day_date s = datetime(now.year, now.month, now.day) e = s + timedelta(days=1) r = self.fill_report_generic(s, e, self.report_day_model) self.report_total_day = r def fill_report_week(self): now = self.report_week_date s = datetime(now.year, now.month, now.day) s -= timedelta(days=s.isoweekday() - 1) e = s + timedelta(days=7) r = self.fill_report_generic(s, e, self.report_week_model) self.report_total_week = r def fill_report_month(self): now = self.report_month_date s = datetime(now.year, now.month, 1) y = now.year m = now.month + 1 if m > 12: m = 1 y += 1 elif m < 1: m = 12 y -= 1 e = datetime(y, m, 1) r = self.fill_report_generic(s, e, self.report_month_model) self.report_total_month = r def fill_report_generic(self, start, end, model): total = 0 model.remove_all() filter = self.report_filter.get_text() query = self.application.db.report(start=start, end=end, activity=filter) for (act, seconds) in query: total += seconds model.append(ActivitySummary(name=act, seconds=seconds)) return total def show_list(self, *args, **kwargs): self.main_stack.set_visible_child_name("main") self.set_titlebar(self.headerbar) self.activity_entry.grab_focus() def startstop_activity(self, *args, **kwargs): if self.activity_entry_buffer.get_length() > 0: if self.start_date: self.stop_activity() return self.activity_stack.set_visible_child_name("working") self.button_stack.set_visible_child_name("working") self.start_date = datetime.now() self.comments = "" activity = self.activity_entry.get_text() self.activity_label.set_text(activity) GLib.timeout_add(1000, self.update_timer, None) activity = Activity(id=None, name=activity, start=self.start_date, stop=None) self.update_current_popup(activity) self.activity_id = self.application.db.store(activity) def check_enable_start(self, *args, **kwargs): self.start_action.set_enabled( self.activity_entry_buffer.get_length() > 0) def stop_activity(self, *args, **kwargs): self.activity_stack.set_visible_child_name("waiting") self.button_stack.set_visible_child_name("waiting") self.timer.set_text("00:00:00") name = self.activity_label.get_text() start = self.start_date stop = datetime.now() activity = Activity(id=self.activity_id, name=name, start=start, stop=stop, comments=self.comments) self.application.db.set_stop(self.activity_id, stop, activity.seconds) self.insert_activity(activity) self.comments = "" self.start_date = None self.activity_id = None def update_timer(self, *args, **kwargs): if not self.start_date: return False now = datetime.now() diff = int((now - self.start_date).total_seconds()) timepass = self.seconds_to_time(diff) self.timer.set_text(timepass) return True def load_last(self): for act in self.application.db.get_last(): self.insert_activity(act) def seconds_to_time(self, seconds): m = seconds // 60 s = seconds % 60 h = m // 60 m = m % 60 return "{:02d}:{:02d}:{:02d}".format(h, m, s) def update_actions(self, *args, **kwargs): if self.activity_list.get_selected_rows(): self.edit_action.set_enabled(True) self.remove_action.set_enabled(True) else: self.edit_action.set_enabled(False) self.remove_action.set_enabled(False) def edit_activity(self, *args, **kwargs): selected = self.activity_list.get_selected_row() index = selected.get_index() act = self.activity_model.get_item(index) dialog = EditDialog(self.application.db, self) result = dialog.open(act.id) if result: self.activity_model.remove(index) self.insert_activity(result) def new_activity(self, *args, **kwargs): dialog = EditDialog(self.application.db, self) act = dialog.open() if act: self.insert_activity(act) def remove_activity(self, *args, **kwargs): selected = self.activity_list.get_selected_row() index = selected.get_index() act = self.activity_model.get_item(index) aid = act.id confirm = Gtk.MessageDialog(text=_("Are you sure?"), buttons=Gtk.ButtonsType.YES_NO) confirm.set_transient_for(self) confirm.set_attached_to(self) confirm.set_destroy_with_parent(True) confirm.set_type_hint(Gdk.WindowTypeHint.DIALOG) confirm.set_modal(True) resp = confirm.run() if resp == Gtk.ResponseType.YES: self.application.db.delete(aid) self.activity_model.remove(index) confirm.close() def report_go_left(self, *args, **kwargs): selected = self.report_stack.get_visible_child_name() if selected == 'day': self.report_day_date -= timedelta(days=1) elif selected == 'week': self.report_week_date -= timedelta(days=7) elif selected == 'month': date = self.report_month_date y, m = date.year, date.month m = m - 1 if m < 1: m = 12 y -= 1 self.report_month_date = datetime(y, m, 1) self.fill_report() def report_go_right(self, *args, **kwargs): selected = self.report_stack.get_visible_child_name() if selected == 'day': self.report_day_date += timedelta(days=1) elif selected == 'week': self.report_week_date += timedelta(days=7) elif selected == 'month': date = self.report_month_date y, m = date.year, date.month m = m + 1 if m > 12: m = 1 y += 1 self.report_month_date = datetime(y, m, 1) self.fill_report() def report_go_today(self, *args, **kwargs): selected = self.report_stack.get_visible_child_name() if selected == 'day': self.report_day_date = datetime.now() elif selected == 'week': self.report_week_date = datetime.now() elif selected == 'month': n = datetime.now() self.report_month_date = datetime(n.year, n.month, 1) self.fill_report() def report_changed(self, *args, **kwargs): self.disable_calendar_change = True self.report_calendar.clear_marks() text = "" total = 0 selected = self.report_stack.get_visible_child_name() if selected == 'day': text = self.report_day_date.strftime("%d/%m/%Y") self.report_calendar.select_month(self.report_day_date.month - 1, self.report_day_date.year) self.report_calendar.select_day(self.report_day_date.day) total = self.report_total_day elif selected == 'week': now = self.report_week_date s = datetime(now.year, now.month, now.day) s -= timedelta(days=s.isoweekday() - 1) self.report_week_date = s e = s + timedelta(days=6) text = s.strftime("%d/%m") + "-" + e.strftime("%d/%m") self.report_calendar.select_month(s.month - 1, s.year) self.report_calendar.select_day(s.day) while s <= e: self.report_calendar.mark_day(s.day) s += timedelta(days=1) total = self.report_total_week elif selected == 'month': now = self.report_month_date s = datetime(now.year, now.month, 1) self.report_month_date = now text = s.strftime("%m/%Y") self.report_calendar.select_month(s.month - 1, s.year) self.report_calendar.select_day(s.day) m = s.month while s.month == m: self.report_calendar.mark_day(s.day) s += timedelta(days=1) total = self.report_total_month self.report_label.set_text(text) self.report_total.set_text(self.seconds_to_time(total)) self.disable_calendar_change = False def report_change_day(self, *args, **kwargs): if self.disable_calendar_change: return new_date = self.report_calendar.get_date() d = datetime(new_date.year, new_date.month + 1, new_date.day) selected = self.report_stack.get_visible_child_name() if selected == 'day': self.report_day_date = d elif selected == 'week': self.report_week_date = d elif selected == 'month': self.report_month_date = datetime(d.year, d.month, 1) self.fill_report() def filter_report(self, *args, **kwargs): self.fill_report() def insert_activity(self, activity): self.activity_model.insert_sorted(activity, lambda a, b: a.start < b.start) def create_activity_widget(self, item): return ActivityWidget(item) def create_activity_summary_widget(self, item): return ActivitySummaryWidget(item) def update_current_popup(self, activity): self.current_name.set_text(activity.name) self.current_hour.set_value(activity.start.hour) self.current_minute.set_value(activity.start.minute) self.current_day.select_month(activity.start.month - 1, activity.start.year) self.current_day.select_day(activity.start.day) self.current_comments.get_buffer().set_text(activity.comments) def change_current(self, *args, **kwargs): if not self.activity_id or not self.start_date: return new_date = self.current_day.get_date() name = self.current_name.get_text() comments_buf = self.current_comments.get_buffer() comments_start = comments_buf.get_start_iter() comments_end = comments_buf.get_end_iter() self.comments = comments_buf.get_text(comments_start, comments_end, False) self.start_date = datetime(new_date.year, new_date.month + 1, new_date.day, int(self.current_hour.get_value()), int(self.current_minute.get_value())) activity = Activity(id=self.activity_id, name=name, start=self.start_date, stop=None, comments=self.comments) self.application.db.update(activity)