def set_facts(self, facts): totals = defaultdict(lambda: defaultdict(dt.timedelta)) for fact in facts: for key in ('category', 'activity'): totals[key][getattr(fact, key)] += fact.delta for tag in fact.tags: totals["tag"][tag] += fact.delta for key, group in totals.items(): totals[key] = sorted(group.items(), key=lambda x: x[1], reverse=True) self.totals = totals self.activities_chart.set_values(totals['activity']) self.categories_chart.set_values(totals['category']) self.tag_chart.set_values(totals['tag']) self.stacked_bar.set_items([(cat, delta.total_seconds() / 60.0) for cat, delta in totals['category']]) grand_total = sum(delta.total_seconds() / 60 for __, delta in totals['activity']) self.category_totals.markup = "<b>Total: </b>%s; " % stuff.format_duration( grand_total) self.category_totals.markup += ", ".join( "<b>%s:</b> %s" % (stuff.escape_pango(cat), stuff.format_duration(hours)) for cat, hours in totals['category'])
def update_label(self): if self.last_activity and self.last_activity.end_time is None: delta = dt.datetime.now() - self.last_activity.start_time duration = delta.seconds / 60 label = "%s %s" % (self.last_activity.activity, stuff.format_duration(duration, False)) self.button.set_text(self.last_activity.activity, stuff.format_duration(duration, False)) else: label = "%s" % _(u"No activity") self.button.set_text(label, None)
def update_label(self): '''Override for menu items sensitivity and to update the menu''' if self.project.last_activity: # Let's see if activity is an attribute and cache the result. # This is only required for backwards compatibility if self._activity_as_attribute == None: self._activity_as_attribute = hasattr(self.project.last_activity, 'activity') if self._activity_as_attribute: start_time = self.project.last_activity.start_time end_time = self.project.last_activity.end_time last_activity_name = self.project.last_activity.activity else: start_time = self.project.last_activity['start_time'] end_time = self.project.last_activity['end_time'] last_activity_name = self.project.last_activity['name'] self.project.load_day() facts = self.project.todays_facts today_duration = 0 if facts: for fact in facts: today_duration += 24 * 60 * fact.delta.days + fact.delta.seconds / 60 # if self.duration: # today_duration += self.duration today_duration = "%s:%02d" % (today_duration/60, today_duration%60) if self.project.last_activity and end_time is None: self._set_activity_status(1) delta = dt.datetime.now() - start_time duration = delta.seconds / 60 label = "%s %s" % (last_activity_name, stuff.format_duration(duration, False)) self.set_activity_text(last_activity_name, stuff.format_duration(duration, False)) indicator_label = "%s %s / %sh" % (self._clamp_text(self.activity, length=self._label_length, with_ellipsis=False), self.duration, today_duration) else: self._set_activity_status(0) label = "%s" % _(u"New activity") self.set_activity_text(label, None) indicator_label = self._get_no_activity_label(today_duration) # Update the indicator label, if needed if self._show_label: self.indicator.set_label(indicator_label) else: self.indicator.set_label("") # Update the menu or the new activity text won't show up self.refresh_menu()
def update_label(self): '''Override for menu items sensitivity and to update the menu''' if self.project.last_activity: # Let's see if activity is an attribute and cache the result. # This is only required for backwards compatibility if self._activity_as_attribute == None: self._activity_as_attribute = hasattr( self.project.last_activity, 'activity') if self._activity_as_attribute: start_time = self.project.last_activity.start_time end_time = self.project.last_activity.end_time last_activity_name = self.project.last_activity.activity else: start_time = self.project.last_activity['start_time'] end_time = self.project.last_activity['end_time'] last_activity_name = self.project.last_activity['name'] self.project.load_day() facts = self.project.todays_facts today_duration = 0 if facts: for fact in facts: today_duration += 24 * 60 * fact.delta.days + fact.delta.seconds / 60 # if self.duration: # today_duration += self.duration today_duration = "%s:%02d" % (today_duration / 60, today_duration % 60) if self.project.last_activity and end_time is None: self._set_activity_status(1) delta = dt.datetime.now() - start_time duration = delta.seconds / 60 label = "%s %s" % (last_activity_name, stuff.format_duration(duration, False)) self.set_activity_text(last_activity_name, stuff.format_duration(duration, False)) indicator_label = "%s %s / %sh" % (self._clamp_text( self.activity, length=self._label_length, with_ellipsis=False), self.duration, today_duration) else: self._set_activity_status(0) label = "%s" % _(u"New activity") self.set_activity_text(label, None) indicator_label = self._get_no_activity_label(today_duration) # Update the indicator label, if needed if self._show_label: self.indicator.set_label(indicator_label) else: self.indicator.set_label("") # Update the menu or the new activity text won't show up self.refresh_menu()
def _draw(self, context, opacity, matrix): g = graphics.Graphics(context) g.save_context() g.translate(self.x, self.y) for i, (label, value) in enumerate(self.values): g.set_color("#333") duration_str = stuff.format_duration(value, human=False) markup = stuff.escape_pango('{}, {}'.format(label, duration_str)) self.layout.set_markup(markup) label_w, label_h = self.layout.get_pixel_size() bar_start_x = 150 # pixels margin = 10 # pixels y = int(i * label_h * 1.5) g.move_to(bar_start_x - margin - label_w, y) pangocairo.show_layout(context, self.layout) if self._max > dt.timedelta(0): w = ceil((self.alloc_w - bar_start_x) * value.total_seconds() / self._max.total_seconds()) else: w = 1 g.rectangle(bar_start_x, y, int(w), int(label_h)) g.fill("#999") g.restore_context()
def fact_dict(fact_data, with_date): fact = {} if with_date: fmt = '%Y-%m-%d %H:%M' else: fmt = '%H:%M' fact['start'] = fact_data.start_time.strftime(fmt) if fact_data.end_time: fact['end'] = fact_data.end_time.strftime(fmt) else: end_date = stuff.hamster_now() fact['end'] = '' fact['duration'] = stuff.format_duration(fact_data.delta) fact['activity'] = fact_data.activity fact['category'] = fact_data.category if fact_data.tags: fact['tags'] = ' '.join('#%s' % tag for tag in fact_data.tags) else: fact['tags'] = '' fact['description'] = fact_data.description return fact
def set_last_activity(self): activity = self.last_activity #sets all the labels and everything as necessary self.get_widget("stop_tracking").set_sensitive(activity != None) if activity: self.get_widget("switch_activity").show() self.get_widget("start_tracking").hide() delta = dt.datetime.now() - activity.start_time duration = delta.seconds / 60 if activity.category != _("Unsorted"): self.get_widget("last_activity_name").set_text("%s - %s" % (activity.activity, activity.category)) else: self.get_widget("last_activity_name").set_text(activity.activity) self.get_widget("last_activity_duration").set_text(stuff.format_duration(duration) or _("Just started")) self.get_widget("last_activity_description").set_text(activity.description or "") self.get_widget("activity_info_box").show() self.tag_box.draw(activity.tags) else: self.get_widget("switch_activity").hide() self.get_widget("start_tracking").show() self.get_widget("last_activity_name").set_text(_("No activity")) self.get_widget("activity_info_box").hide() self.tag_box.draw([])
def check_user(self): """check if we need to notify user perhaps""" if not self.notification or self.notify_interval <= 0 or self.notify_interval >= 121: return now = dt.datetime.now() message = None # update duration of current task if self.last_activity: delta = now - self.last_activity.start_time duration = delta.seconds / 60 if duration and duration % self.notify_interval == 0: message = _(u"Working on <b>%s</b>") % self.last_activity.name self.get_widget("last_activity_duration").set_text(stuff.format_duration(duration) or _("Just started")) if not self.last_activity and self.notify_on_idle: #if we have no last activity, let's just calculate duration from 00:00 if (now.minute + now.hour *60) % self.notify_interval == 0: message = _(u"No activity") if message: self.notification.update(_("Time Tracker"), message, "hamster-applet") self.notification.show()
def _write_fact(self, fact): # no having end time is fine end_time_str, end_time_iso_str = "", "" if fact.end_time: end_time_str = fact.end_time.strftime('%H:%M') end_time_iso_str = fact.end_time.isoformat() category = "" if fact.category != _("Unsorted"): #do not print "unsorted" in list category = fact.category data = dict( date=fact.date.strftime( # date column format for each row in HTML report # Using python datetime formatting syntax. See: # http://docs.python.org/library/time.html#time.strftime C_("html report", "%b %d, %Y")), date_iso=fact.date.isoformat(), activity=fact.activity, category=category, tags=", ".join(fact.tags), start=fact.start_time.strftime('%H:%M'), start_iso=fact.start_time.isoformat(), end=end_time_str, end_iso=end_time_iso_str, duration=stuff.format_duration(fact.delta) or "", duration_minutes="%d" % (stuff.duration_minutes(fact.delta)), duration_decimal="%.2f" % (stuff.duration_minutes(fact.delta) / 60.0), description=fact.description or "") self.fact_rows.append( Template(self.fact_row_template).safe_substitute(data))
def _draw(self, context, opacity, matrix): g = graphics.Graphics(context) g.save_context() g.translate(self.x, self.y) # arbitrary 3/4 total width for label, 1/4 for histogram hist_width = self.alloc_w // 4; margin = 10 # pixels label_width = self.alloc_w - hist_width - margin self.layout.set_width(label_width * pango.SCALE) label_h = self.label_height bar_start_x = label_width + margin for i, (label, value) in enumerate(self.values): g.set_color("#333") duration_str = stuff.format_duration(value, human=False) markup_label = stuff.escape_pango(str(label)) markup_duration = stuff.escape_pango(duration_str) self.layout.set_markup("{}, <i>{}</i>".format(markup_label, markup_duration)) y = int(i * label_h * 1.5) g.move_to(0, y) pangocairo.show_layout(context, self.layout) if self._max > dt.timedelta(0): w = ceil(hist_width * value.total_seconds() / self._max.total_seconds()) else: w = 1 g.rectangle(bar_start_x, y, int(w), int(label_h)) g.fill("#999") g.restore_context()
def _write_fact(self, fact): # no having end time is fine end_time_str, end_time_iso_str = "", "" if fact.end_time: end_time_str = fact.end_time.strftime('%H:%M') end_time_iso_str = fact.end_time.isoformat() category = "" if fact.category != _("Unsorted"): #do not print "unsorted" in list category = fact.category data = dict( date = fact.date.strftime( # date column format for each row in HTML report # Using python datetime formatting syntax. See: # http://docs.python.org/library/time.html#time.strftime C_("html report","%b %d, %Y")), date_iso = fact.date.isoformat(), activity = fact.activity, category = category, tags = fact.tags, start = fact.start_time.strftime('%H:%M'), start_iso = fact.start_time.isoformat(), end = end_time_str, end_iso = end_time_iso_str, duration = stuff.format_duration(fact.delta) or "", duration_minutes = "%d" % (stuff.duration_minutes(fact.delta)), duration_decimal = "%.2f" % (stuff.duration_minutes(fact.delta) / 60.0), description = fact.description or "" ) self.fact_rows.append(Template(self.fact_row_template).safe_substitute(data))
def current(self, *args): """prints current activity. kinda minimal right now""" facts = self.storage.get_todays_facts() if facts and not facts[-1].end_time: print("{} {}".format( str(facts[-1]).strip(), stuff.format_duration(facts[-1].delta, human=False))) else: print((_("No activity")))
def __init__(self, width, fact, color, **kwargs): graphics.Sprite.__init__(self, **kwargs) self.width = width self.height = 27 self.natural_height = 27 self.fact = fact self.color = color self.interactive = True self.mouse_cursor = gdk.CursorType.XTERM self.fact_labels = graphics.Sprite() self.start_label = graphics.Label("", color="#333", size=11, x=10, y=5, interactive=True, mouse_cursor=gdk.CursorType.XTERM) self.start_label.text = "%s - " % fact.start_time.strftime("%H:%M") self.fact_labels.add_child(self.start_label) self.end_label = graphics.Label("", color="#333", size=11, x=65, y=5, interactive=True, mouse_cursor=gdk.CursorType.XTERM) if fact.end_time: self.end_label.text = fact.end_time.strftime("%H:%M") self.fact_labels.add_child(self.end_label) self.activity_label = graphics.Label(fact.activity, color="#333", size=11, x=120, y=5, interactive=True, mouse_cursor=gdk.CursorType.XTERM) self.fact_labels.add_child(self.activity_label) self.category_label = graphics.Label("", color="#333", size=9, y=7, interactive=True, mouse_cursor=gdk.CursorType.XTERM) self.category_label.text = stuff.escape_pango(" - %s" % fact.category) self.category_label.x = self.activity_label.x + self.activity_label.width self.fact_labels.add_child(self.category_label) self.duration_label = graphics.Label(stuff.format_duration(fact.delta), size=11, color="#333", interactive=True, mouse_cursor=gdk.CursorType.XTERM) self.duration_label.x = self.width - self.duration_label.width - 5 self.duration_label.y = 5 self.fact_labels.add_child(self.duration_label) self.add_child(self.fact_labels) self.edit_links = graphics.Sprite(x=10, y = 110, opacity=0) self.delete_link = graphics.Label("Delete", size=11, color="#555", interactive=True) self.save_link = graphics.Label("Save", size=11, x=390, color="#555", interactive=True) self.cancel_link = graphics.Label("Cancel", size=11, x=440, color="#555", interactive=True) self.edit_links.add_child(self.delete_link, self.save_link, self.cancel_link) self.add_child(self.edit_links) for sprite in self.fact_labels.sprites: sprite.connect("on-click", self.on_sprite_click) self.connect("on-render", self.on_render) self.connect("on-click", self.on_click)
def update_text(self): today = self.storage.get_todays_facts() if today and today[-1].end_time is None: fact = today[-1] self.set_tooltip("%s - %s" % (fact.activity, fact.category)) self.set_badge(stuff.format_duration(fact.delta, human=False)) else: self.set_tooltip(_("No activity")) self.reset_badge()
def update_text(self): today = self.storage.get_todays_facts() if today and today[-1].end_time is None: fact = today[-1] self.iface.SetText("%s - %s" % (fact.activity, fact.category)) self.iface.SetBadgeText(stuff.format_duration(fact.delta, human=False)) else: self.iface.SetText(_("No activity")) self.iface.ResetBadgeText()
def show(self, g, colors, fact=None, is_selected=False): """Display the fact row. If fact is given, the fact attribute is updated. """ g.save_context() if fact is not None: # before the selection highlight, to get the correct height self.set_fact(fact) color, bg = colors["normal"], colors["normal_bg"] if is_selected: color, bg = colors["selected"], colors["selected_bg"] g.fill_area(0, 0, self.width, self.height, bg) g.translate(self.row_margin_H, self.row_margin_V) g.set_color(color) self.time_label.show(g) self.activity_label.show(g) if self.fact.category: g.save_context() category_color = graphics.ColorUtils.mix(bg, color, 0.57) g.set_color(category_color) x = self.activity_label.x + self.activity_label.layout.get_pixel_size( )[0] self.category_label.show(g, x=x, y=self.category_offset_V) g.restore_context() if self.fact.description or self.fact.tags: g.save_context() g.translate(self.activity_label.x, self.activity_label.height + 3) if self.fact.tags: self._show_tags(g, color, bg) tag_height = (self.tag_label.height + self.tag_inner_margin_V * 2 + self.tag_row_margin_V * 2) g.translate(0, tag_height) if self.fact.description: self.description_label.show(g) g.restore_context() self.duration_label.show(g, stuff.format_duration(self.fact.delta), x=self.width - 105) g.restore_context()
def show(self, g, colors, fact, current=False): g.save_context() color, bg = colors["normal"], colors["normal_bg"] if current: color, bg = colors["selected"], colors["selected_bg"] g.fill_area(0, 0, self.width, self.height(fact), bg) g.translate(5, 2) time_label = fact.start_time.strftime("%H:%M -") if fact.end_time: time_label += fact.end_time.strftime(" %H:%M") g.set_color(color) self.time_label.show(g, time_label) self.activity_label.show(g, stuff.escape_pango(fact.activity)) if fact.category: g.save_context() g.set_color(color if current else "#999") x = self.activity_label.x + self.activity_label.layout.get_pixel_size( )[0] self.category_label.show(g, " - %s" % stuff.escape_pango(fact.category), x=x, y=2) g.restore_context() if fact.description or fact.tags: g.save_context() g.translate(self.activity_label.x, self.activity_label.height + 3) if fact.tags: self._show_tags(g, fact.tags, color, bg) g.translate(0, self.tag_label.height + 5) if fact.description: self.description_label.show( g, "<small>%s</small>" % stuff.escape_pango(fact.description)) g.restore_context() self.duration_label.show(g, stuff.format_duration(fact.delta), x=self.width - 105) g.restore_context()
def show(self, g, colors, fact, current=False): g.save_context() color, bg = colors["normal"], colors["normal_bg"] if current: color, bg = colors["selected"], colors["selected_bg"] g.fill_area(0, 0, self.width, self.height(fact), bg) g.translate(5, 2) time_label = fact.start_time.strftime("%H:%M -") if fact.end_time: time_label += fact.end_time.strftime(" %H:%M") g.set_color(color) self.time_label.show(g, time_label) self.activity_label.show(g, stuff.escape_pango(fact.activity)) if fact.category: g.save_context() g.set_color(color if current else "#999") x = self.activity_label.x + self.activity_label.layout.get_pixel_size()[0] self.category_label.show(g, " - %s" % stuff.escape_pango(fact.category), x=x, y=2) g.restore_context() if fact.description or fact.tags: g.save_context() g.translate(self.activity_label.x, self.activity_label.height + 3) if fact.tags: self._show_tags(g, fact.tags, color, bg) g.translate(0, self.tag_label.height + 5) if fact.description: self.description_label.show(g, "<small>%s</small>" % stuff.escape_pango(fact.description)) g.restore_context() self.duration_label.show(g, stuff.format_duration(fact.delta), x=self.width - 105) g.restore_context()
def set_facts(self, facts): totals = defaultdict(lambda: defaultdict(dt.timedelta)) for fact in facts: for key in ('category', 'activity'): totals[key][getattr(fact, key)] += fact.delta for tag in fact.tags: totals["tag"][tag] += fact.delta for key, group in totals.items(): totals[key] = sorted(group.items(), key=lambda x: x[1], reverse=True) self.totals = totals self.activities_chart.set_values(totals['activity']) self.categories_chart.set_values(totals['category']) self.tag_chart.set_values(totals['tag']) self.stacked_bar.set_items([(cat, delta.total_seconds() / 60.0) for cat, delta in totals['category']]) self.category_totals.markup = ", ".join("<b>%s:</b> %s" % ( stuff.escape_pango(cat), stuff.format_duration(hours)) \ for cat, hours in totals['category'])
def show_popup(self): if not self._parent_click_watcher: self._parent_click_watcher = self.get_toplevel().connect( "button-press-event", self._on_focus_out_event) # we will be adding things, need datetime i_time_0 = dt.datetime.combine(self.start_date or dt.date.today(), self.start_time or dt.time()) if self.start_time is None: # full 24 hours i_time = i_time_0 interval = dt.timedelta(minutes=15) end_time = i_time_0 + dt.timedelta(days=1) else: # from start time to start time + 12 hours interval = dt.timedelta(minutes=15) i_time = i_time_0 + interval end_time = i_time_0 + dt.timedelta(hours=12) time = self.figure_time(self.get_text()) focus_time = dt.datetime.combine(dt.date.today(), time) if time else None hours = gtk.ListStore(str) i, focus_row = 0, None while i_time < end_time: row_text = self._format_time(i_time) if self.start_time is not None: delta_text = format_duration(i_time - i_time_0) row_text += " (%s)" % delta_text hours.append([row_text]) if focus_time and i_time <= focus_time < i_time + interval: focus_row = i i_time += interval i += 1 self.time_tree.set_model(hours) #focus on row if focus_row != None: selection = self.time_tree.get_selection() selection.select_path(focus_row) self.time_tree.scroll_to_cell(focus_row, use_align=True, row_align=0.4) #move popup under the widget alloc = self.get_allocation() w = alloc.width self.time_tree.set_size_request(w, alloc.height * 5) window = self.get_parent_window() dmmy, x, y = window.get_origin() self.popup.move(x + alloc.x, y + alloc.y + alloc.height) self.popup.resize(*self.time_tree.get_size_request()) self.popup.show_all()
def update_suggestions(self, text=""): """ * from previous activity | set time | minutes ago | start now * to ongoing | set time * activity * [@category] * #tags, #tags, #tags * we will leave description for later all our magic is space separated, strictly, start-end can be just dash phases: [start_time] | [-end_time] | activity | [@category] | [#tag] """ now = dt.datetime.now() text = text.lstrip() time_re = re.compile("^([0-1]?[0-9]|[2][0-3]):([0-5][0-9])$") time_range_re = re.compile("^([0-1]?[0-9]|[2][0-3]):([0-5][0-9])-([0-1]?[0-9]|[2][0-3]):([0-5][0-9])$") delta_re = re.compile("^-[0-9]{1,3}$") # when the time is filled, we need to make sure that the chunks parse correctly delta_fragment_re = re.compile("^-[0-9]{0,3}$") templates = { "start_time": "", "start_delta": ("start activity -n minutes ago", "-"), } # need to set the start_time template before prev_fact = self.todays_facts[-1] if self.todays_facts else None if prev_fact and prev_fact.end_time: templates["start_time"] = ("from previous activity %s ago" % stuff.format_duration(now - prev_fact.end_time), prev_fact.end_time.strftime("%H:%M ")) variants = [] fact = Fact(text) # figure out what we are looking for # time -> activity[@category] -> tags -> description # presence of each next attribute means that we are not looking for the previous one # we still might be looking for the current one though looking_for = "start_time" fields = ["start_time", "end_time", "activity", "category", "tags", "description", "done"] for field in reversed(fields): if getattr(fact, field, None): looking_for = field if text[-1] == " ": looking_for = fields[fields.index(field)+1] break fragments = [f for f in re.split("[\s|#]", text)] current_fragment = fragments[-1] if fragments else "" if not text.strip(): variants = [templates[name] for name in ("start_time", "start_delta") if templates[name]] elif looking_for == "start_time" and text == "-": if len(current_fragment) > 1: # avoid blank "-" templates["start_delta"] = ("%s minutes ago" % (-int(current_fragment)), current_fragment) variants.append(templates["start_delta"]) res = [] for (description, variant) in variants: res.append(DataRow(variant, description=description)) # regular activity if (looking_for in ("start_time", "end_time") and not looks_like_time(text.split(" ")[-1])) or \ looking_for in ("activity", "category"): search = extract_search(text) matches = [] for match, score in self.suggestions: if search in match: if match.startswith(search): score += 10**8 # boost beginnings matches.append((match, score)) matches = sorted(matches, key=lambda x: x[1], reverse=True)[:7] # need to limit these guys, sorry for match, score in matches: label = (fact.start_time or now).strftime("%H:%M") if fact.end_time: label += fact.end_time.strftime("-%H:%M") markup_label = label + " " + (stuff.escape_pango(match).replace(search, "<b>%s</b>" % search) if search else match) label += " " + match res.append(DataRow(markup_label, match, label)) if not res: # in case of nothing to show, add preview so that the user doesn't # think they are lost label = (fact.start_time or now).strftime("%H:%M") if fact.end_time: label += fact.end_time.strftime("-%H:%M") if fact.activity: label += " " + fact.activity if fact.category: label += "@" + fact.category if fact.tags: label += " #" + " #".join(fact.tags) res.append(DataRow(stuff.escape_pango(label), description="Start tracking")) self.complete_tree.set_rows(res)
def __init__(self, width, fact, color, **kwargs): graphics.Sprite.__init__(self, **kwargs) self.width = width self.height = 27 self.natural_height = 27 self.fact = fact self.color = color self.interactive = True self.mouse_cursor = gdk.CursorType.XTERM self.fact_labels = graphics.Sprite() self.start_label = graphics.Label("", color="#333", size=11, x=10, y=5, interactive=True, mouse_cursor=gdk.CursorType.XTERM) self.start_label.text = "%s - " % fact.start_time.strftime("%H:%M") self.fact_labels.add_child(self.start_label) self.end_label = graphics.Label("", color="#333", size=11, x=65, y=5, interactive=True, mouse_cursor=gdk.CursorType.XTERM) if fact.end_time: self.end_label.text = fact.end_time.strftime("%H:%M") self.fact_labels.add_child(self.end_label) self.activity_label = graphics.Label(fact.activity, color="#333", size=11, x=120, y=5, interactive=True, mouse_cursor=gdk.CursorType.XTERM) self.fact_labels.add_child(self.activity_label) self.category_label = graphics.Label("", color="#333", size=9, y=7, interactive=True, mouse_cursor=gdk.CursorType.XTERM) self.category_label.text = stuff.escape_pango(" - %s" % fact.category) self.category_label.x = self.activity_label.x + self.activity_label.width self.fact_labels.add_child(self.category_label) self.duration_label = graphics.Label(stuff.format_duration(fact.delta), size=11, color="#333", interactive=True, mouse_cursor=gdk.CursorType.XTERM) self.duration_label.x = self.width - self.duration_label.width - 5 self.duration_label.y = 5 self.fact_labels.add_child(self.duration_label) self.add_child(self.fact_labels) self.edit_links = graphics.Sprite(x=10, y=110, opacity=0) self.delete_link = graphics.Label("Delete", size=11, color="#555", interactive=True) self.save_link = graphics.Label("Save", size=11, x=390, color="#555", interactive=True) self.cancel_link = graphics.Label("Cancel", size=11, x=440, color="#555", interactive=True) self.edit_links.add_child(self.delete_link, self.save_link, self.cancel_link) self.add_child(self.edit_links) for sprite in self.fact_labels.sprites: sprite.connect("on-click", self.on_sprite_click) self.connect("on-render", self.on_render) self.connect("on-click", self.on_click)
def set_last_activity(self): activity = self.last_activity #sets all the labels and everything as necessary self.get_widget("stop_tracking").set_sensitive(activity != None) arbitrary_issue_id = self.get_widget("arbitrary_issue_id_entry").get_text() active_activity = self.get_widget("time_activity_combo").get_active() if activity: self.get_widget("switch_activity").show() self.get_widget("start_tracking").hide() # If the Redmine integration is enabled, show the Redmine frame and set insensitivity of combos if conf.get("redmine_integration_enabled"): self.get_widget("redmine_frame").show() self.get_widget("issue_combo").set_sensitive(False) self.get_widget("time_activity_combo").set_sensitive(False) self.get_widget("arbitrary_issue_id_entry").set_sensitive(False) if arbitrary_issue_id != None: self.get_widget("arbitrary_issue_id_entry").set_text(arbitrary_issue_id) self.get_widget("time_activity_combo").set_active(active_activity) delta = dt.datetime.now() - activity.start_time duration = delta.seconds // 60 if activity.category != _("Unsorted"): if isinstance(activity, RedmineFact): self.get_widget("last_activity_name").set_text("%s %s - %s" %(activity.activity, activity.redmine_tag(), activity.category)) else: self.get_widget("last_activity_name").set_text("%s - %s" %(activity.activity, activity.category)) else: if isinstance(activity, RedmineFact): self.get_widget("last_activity_name").set_text("%s %s"%(activity.activity, activity.redmine_tag())) else: self.get_widget("last_activity_name").set_text(activity.activity) self.get_widget("last_activity_duration").set_text(stuff.format_duration(duration) or _("Just started")) self.get_widget("last_activity_description").set_text(activity.description or "") self.get_widget("activity_info_box").show() self.tag_box.draw(activity.tags) else: self.get_widget("switch_activity").hide() self.get_widget("start_tracking").show() self.get_widget("last_activity_name").set_text(_("No activity")) self.get_widget("activity_info_box").hide() self.tag_box.draw([]) # If the Redmine integration is enabled, show the Redmine frame and set up the combos (if there is no selection), making sure they are sensitive if conf.get("redmine_integration_enabled"): self.get_widget("redmine_frame").show() self.get_widget("issue_combo").set_sensitive(True) self.get_widget("time_activity_combo").set_sensitive(True) if self.get_widget("issue_combo").get_active() == -1 or self.get_widget("issue_combo").get_active() == 0: self.fill_issues_combo() self.get_widget("arbitrary_issue_id_entry").set_sensitive(True) if self.get_widget("time_activity_combo").get_active() == -1 or self.get_widget("time_activity_combo").get_active() == 0: self.fill_time_activities_combo() if arbitrary_issue_id != None: self.get_widget("arbitrary_issue_id_entry").set_text(arbitrary_issue_id) self.get_widget("time_activity_combo").set_active(active_activity)
def show_popup(self): if not self._parent_click_watcher: self._parent_click_watcher = self.get_toplevel().connect( "button-press-event", self._on_focus_out_event) # will be going either 24 hours or from start time to start time + 12 hours start_time = dt.datetime.combine( dt.date.today(), self.start_time) # we will be adding things i_time = start_time # we will be adding things if self.start_time: end_time = i_time + dt.timedelta(hours=12) i_time += dt.timedelta(minutes=15) else: end_time = i_time + dt.timedelta(days=1) focus_time = dt.datetime.combine(dt.date.today(), self.figure_time(self.get_text())) hours = gtk.ListStore(gobject.TYPE_STRING) i, focus_row = 0, None while i_time < end_time: row_text = self._format_time(i_time) if self.start_time: delta = (i_time - start_time).seconds / 60 delta_text = format_duration(delta) row_text += " (%s)" % delta_text hours.append([row_text]) if focus_time and i_time <= focus_time <= i_time + \ dt.timedelta(minutes = 30): focus_row = i if self.start_time: i_time += dt.timedelta(minutes=15) else: i_time += dt.timedelta(minutes=30) i += 1 self.time_tree.set_model(hours) #focus on row if focus_row != None: selection = self.time_tree.get_selection() selection.select_path(focus_row) self.time_tree.scroll_to_cell(focus_row, use_align=True, row_align=0.4) #move popup under the widget alloc = self.get_allocation() w = alloc.width if self.start_time: w = w * 2 self.time_tree.set_size_request(w, alloc.height * 5) window = self.get_parent_window() dmmy, x, y = window.get_origin() self.popup.move(x + alloc.x, y + alloc.y + alloc.height) self.popup.resize(*self.time_tree.get_size_request()) self.popup.show_all()
def update_suggestions(self, text=""): """ * from previous activity | set time | minutes ago | start now * to ongoing | set time * activity * [@category] * #tags, #tags, #tags * we will leave description for later all our magic is space separated, strictly, start-end can be just dash phases: [start_time] | [-end_time] | activity | [@category] | [#tag] """ res = [] fact = Fact(text) now = dt.datetime.now() # figure out what we are looking for # time -> activity[@category] -> tags -> description # presence of an attribute means that we are not looking for the previous one # we still might be looking for the current one though looking_for = "start_time" fields = ["start_time", "end_time", "activity", "category", "tags", "description", "done"] for field in reversed(fields): if getattr(fact, field, None): looking_for = field if text[-1] == " ": looking_for = fields[fields.index(field)+1] break fragments = [f for f in re.split("[\s|#]", text)] current_fragment = fragments[-1] if fragments else "" search = extract_search(text) matches = [] for match, score in self.suggestions: if search in match: if match.startswith(search): score += 10**8 # boost beginnings matches.append((match, score)) # need to limit these guys, sorry matches = sorted(matches, key=lambda x: x[1], reverse=True)[:7] for match, score in matches: label = (fact.start_time or now).strftime("%H:%M") if fact.end_time: label += fact.end_time.strftime("-%H:%M") markup_label = label + " " + (stuff.escape_pango(match).replace(search, "<b>%s</b>" % search) if search else match) label += " " + match res.append(DataRow(markup_label, match, label)) # list of tuples (description, variant) variants = [] if self.original_fact: # editing an existing fact variant_fact = None if self.original_fact.end_time is None: description = "stop now" variant_fact = self.original_fact.copy() variant_fact.end_time = now elif self.original_fact == self.todays_facts[-1]: # that one is too dangerous, except for the last entry description = "keep up" # Do not use Fact(..., end_time=None): it would be a no-op variant_fact = self.original_fact.copy() variant_fact.end_time = None if variant_fact: variant = variant_fact.serialized(prepend_date=False) variants.append((description, variant)) else: # brand new fact description = "start now" variant = now.strftime("%H:%M ") variants.append((description, variant)) prev_fact = self.todays_facts[-1] if self.todays_facts else None if prev_fact and prev_fact.end_time: since = stuff.format_duration(now - prev_fact.end_time) description = "from previous activity, %s ago" % since variant = prev_fact.end_time.strftime("%H:%M ") variants.append((description, variant)) description = "start activity -n minutes ago (1 or 3 digits allowed)" variant = "-" variants.append((description, variant)) text = text.strip() if text: description = "clear" variant = "" variants.append((description, variant)) for (description, variant) in variants: res.append(DataRow(variant, description=description)) self.complete_tree.set_rows(res)
def set_last_activity(self): activity = self.last_activity #sets all the labels and everything as necessary self.get_widget("stop_tracking").set_sensitive(activity != None) arbitrary_issue_id = self.get_widget( "arbitrary_issue_id_entry").get_text() active_activity = self.get_widget("time_activity_combo").get_active() if activity: self.get_widget("switch_activity").show() self.get_widget("start_tracking").hide() # If the Redmine integration is enabled, show the Redmine frame and set insensitivity of combos if conf.get("redmine_integration_enabled"): self.get_widget("redmine_frame").show() self.get_widget("issue_combo").set_sensitive(False) self.get_widget("time_activity_combo").set_sensitive(False) self.get_widget("arbitrary_issue_id_entry").set_sensitive( False) if arbitrary_issue_id != None: self.get_widget("arbitrary_issue_id_entry").set_text( arbitrary_issue_id) self.get_widget("time_activity_combo").set_active( active_activity) delta = dt.datetime.now() - activity.start_time duration = delta.seconds // 60 if activity.category != _("Unsorted"): if isinstance(activity, RedmineFact): self.get_widget("last_activity_name").set_text( "%s %s - %s" % (activity.activity, activity.redmine_tag(), activity.category)) else: self.get_widget("last_activity_name").set_text( "%s - %s" % (activity.activity, activity.category)) else: if isinstance(activity, RedmineFact): self.get_widget("last_activity_name").set_text( "%s %s" % (activity.activity, activity.redmine_tag())) else: self.get_widget("last_activity_name").set_text( activity.activity) self.get_widget("last_activity_duration").set_text( stuff.format_duration(duration) or _("Just started")) self.get_widget("last_activity_description").set_text( activity.description or "") self.get_widget("activity_info_box").show() self.tag_box.draw(activity.tags) else: self.get_widget("switch_activity").hide() self.get_widget("start_tracking").show() self.get_widget("last_activity_name").set_text(_("No activity")) self.get_widget("activity_info_box").hide() self.tag_box.draw([]) # If the Redmine integration is enabled, show the Redmine frame and set up the combos (if there is no selection), making sure they are sensitive if conf.get("redmine_integration_enabled"): self.get_widget("redmine_frame").show() self.get_widget("issue_combo").set_sensitive(True) self.get_widget("time_activity_combo").set_sensitive(True) if self.get_widget("issue_combo").get_active( ) == -1 or self.get_widget("issue_combo").get_active() == 0: self.fill_issues_combo() self.get_widget("arbitrary_issue_id_entry").set_sensitive( True) if self.get_widget("time_activity_combo").get_active( ) == -1 or self.get_widget( "time_activity_combo").get_active() == 0: self.fill_time_activities_combo() if arbitrary_issue_id != None: self.get_widget("arbitrary_issue_id_entry").set_text( arbitrary_issue_id) self.get_widget("time_activity_combo").set_active( active_activity)
def show_popup(self): if not self._parent_click_watcher: self._parent_click_watcher = self.get_toplevel().connect("button-press-event", self._on_focus_out_event) # will be going either 24 hours or from start time to start time + 12 hours start_time = dt.datetime.combine(dt.date.today(), self.start_time) # we will be adding things i_time = start_time # we will be adding things if self.start_time: end_time = i_time + dt.timedelta(hours = 12) i_time += dt.timedelta(minutes = 15) else: end_time = i_time + dt.timedelta(days = 1) focus_time = dt.datetime.combine(dt.date.today(), self.figure_time(self.get_text())) hours = gtk.ListStore(gobject.TYPE_STRING) i, focus_row = 0, None while i_time < end_time: row_text = self._format_time(i_time) if self.start_time: delta = (i_time - start_time).seconds / 60 delta_text = format_duration(delta) row_text += " (%s)" % delta_text hours.append([row_text]) if focus_time and i_time <= focus_time <= i_time + \ dt.timedelta(minutes = 30): focus_row = i if self.start_time: i_time += dt.timedelta(minutes = 15) else: i_time += dt.timedelta(minutes = 30) i += 1 self.time_tree.set_model(hours) #focus on row if focus_row != None: selection = self.time_tree.get_selection() selection.select_path(focus_row) self.time_tree.scroll_to_cell(focus_row, use_align = True, row_align = 0.4) #move popup under the widget alloc = self.get_allocation() w = alloc.width if self.start_time: w = w * 2 self.time_tree.set_size_request(w, alloc.height * 5) window = self.get_parent_window() dmmy, x, y= window.get_origin() self.popup.move(x + alloc.x,y + alloc.y + alloc.height) self.popup.resize(*self.time_tree.get_size_request()) self.popup.show_all()
def _list(self, start_time, end_time, search=""): """Print a listing of activities""" facts = self.storage.get_facts(start_time, end_time, search) headers = { 'activity': _("Activity"), 'category': _("Category"), 'tags': _("Tags"), 'description': _("Description"), 'start': _("Start"), 'end': _("End"), 'duration': _("Duration") } # print date if it is not the same day print_with_date = start_time.date() != end_time.date() cols = 'start', 'end', 'duration', 'activity', 'category' widths = dict([(col, len(headers[col])) for col in cols]) for fact in facts: fact = fact_dict(fact, print_with_date) for col in cols: widths[col] = max(widths[col], len(fact[col])) cols = [ "{{{col}: <{len}}}".format(col=col, len=widths[col]) for col in cols ] fact_line = " | ".join(cols) row_width = sum(val + 3 for val in list(widths.values())) print() print(fact_line.format(**headers)) print("-" * min(row_width, 80)) by_cat = {} for fact in facts: cat = fact.category or _("Unsorted") by_cat.setdefault(cat, dt.timedelta(0)) by_cat[cat] += fact.delta pretty_fact = fact_dict(fact, print_with_date) print(fact_line.format(**pretty_fact)) if pretty_fact['description']: for line in word_wrap(pretty_fact['description'], 76): print(" {}".format(line)) if pretty_fact['tags']: for line in word_wrap(pretty_fact['tags'], 76): print(" {}".format(line)) print("-" * min(row_width, 80)) cats = [] total_duration = dt.timedelta() for cat, duration in sorted(by_cat.items(), key=lambda x: x[1], reverse=True): cats.append("{}: {}".format(cat, stuff.format_duration(duration))) total_duration += duration for line in word_wrap(", ".join(cats), 80): print(line) print("Total: ", stuff.format_duration(total_duration)) print()