[hamster-applet] more extracted widgets
- From: Toms Baugis <tbaugis src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [hamster-applet] more extracted widgets
- Date: Thu, 19 Nov 2009 16:56:42 +0000 (UTC)
commit f59581d5a1289c006bd7e356465d5eb46e7f37ff
Author: Toms Bauģis <toms baugis gmail com>
Date: Thu Nov 19 16:56:29 2009 +0000
more extracted widgets
hamster/edit_activity.py | 319 +------------------------------------------
hamster/stats.py | 297 +---------------------------------------
hamster/widgets/Makefile.am | 5 +-
hamster/widgets/__init__.py | 5 +
4 files changed, 12 insertions(+), 614 deletions(-)
---
diff --git a/hamster/edit_activity.py b/hamster/edit_activity.py
index d651307..cc475c0 100644
--- a/hamster/edit_activity.py
+++ b/hamster/edit_activity.py
@@ -43,323 +43,6 @@ import cairo, pango
* hook into notifications and refresh our days if some evil neighbour edit
fact window has dared to edit facts
"""
-class Dayline(graphics.Area):
- def __init__(self):
- graphics.Area.__init__(self)
-
- self.set_events(gtk.gdk.EXPOSURE_MASK
- | gtk.gdk.LEAVE_NOTIFY_MASK
- | gtk.gdk.BUTTON_PRESS_MASK
- | gtk.gdk.BUTTON_RELEASE_MASK
- | gtk.gdk.POINTER_MOTION_MASK
- | gtk.gdk.POINTER_MOTION_HINT_MASK)
- self.connect("button_release_event", self.on_button_release)
- self.connect("motion_notify_event", self.draw_cursor)
- self.highlight_start, self.highlight_end = None, None
- self.drag_start = None
- self.move_type = ""
- self.on_time_changed = None #override this with your func to get notified when user changes date
- self.on_more_data = None #supplement with more data func that accepts single date
- self.in_progress = False
-
- self.range_start = None
- self.in_motion = False
- self.days = []
-
-
- def draw(self, day_facts, highlight = None):
- """Draw chart with given data"""
- self.facts = day_facts
- if self.facts:
- self.days.append(self.facts[0]["start_time"].date())
-
- start_time = highlight[0] - dt.timedelta(minutes = highlight[0].minute) - dt.timedelta(hours = 10)
-
- if self.range_start:
- self.range_start.target(start_time)
- self.scroll_to_range_start()
- else:
- self.range_start = graphics.Integrator(start_time, damping = 0.35, attraction = 0.5)
-
- self.highlight = highlight
-
- self.show()
-
- self.redraw_canvas()
-
-
- def on_button_release(self, area, event):
- if not self.drag_start:
- return
-
- self.drag_start, self.move_type = None, None
-
- if event.state & gtk.gdk.BUTTON1_MASK:
- self.__call_parent_time_changed()
-
- def set_in_progress(self, in_progress):
- self.in_progress = in_progress
-
- def __call_parent_time_changed(self):
- #now calculate back from pixels into minutes
- start_time = self.highlight[0]
- end_time = self.highlight[1]
-
- if self.on_time_changed:
- self.on_time_changed(start_time, end_time)
-
- def get_time(self, pixels):
- minutes = self.get_value_at_pos(x = pixels)
- return self.range_start.value + dt.timedelta(minutes = minutes)
-
- def scroll_to_range_start(self):
- if not self.in_motion:
- self.in_motion = True
- gobject.timeout_add(1000 / 30, self.animate_scale)
-
-
- def animate_scale(self):
- moving = self.range_start.update() > 5
-
-
- # check if maybe we are approaching day boundaries and should ask for
- # more data!
- if self.on_more_data:
- now = self.range_start.value
- date_plus = (now + dt.timedelta(hours = 12 + 2*4 + 1)).date()
- date_minus = (now - dt.timedelta(hours=1)).date()
-
- if date_minus != now.date() and date_minus not in self.days:
- self.facts += self.on_more_data(date_minus)
- self.days.append(date_minus)
- elif date_plus != now.date() and date_plus not in self.days:
- self.facts += self.on_more_data(date_plus)
- self.days.append(date_plus)
-
-
- self.redraw_canvas()
- if moving:
- return True
- else:
- self.in_motion = False
- return False
-
-
-
- def draw_cursor(self, area, event):
- if event.is_hint:
- x, y, state = event.window.get_pointer()
- else:
- x = event.x
- y = event.y
- state = event.state
-
- mouse_down = state & gtk.gdk.BUTTON1_MASK
-
- #print x, self.highlight_start, self.highlight_end
- if self.highlight_start != None:
- start_drag = 10 > (self.highlight_start - x) > -1
-
- end_drag = 10 > (x - self.highlight_end) > -1
-
- if start_drag and end_drag:
- start_drag = abs(x - self.highlight_start) < abs(x - self.highlight_end)
-
- in_between = self.highlight_start <= x <= self.highlight_end
- scale = True
-
- if self.in_progress:
- end_drag = False
- in_between = False
-
- if mouse_down and not self.drag_start:
- self.drag_start = x
- if start_drag:
- self.move_type = "start"
- elif end_drag:
- self.move_type = "end"
- elif in_between:
- self.move_type = "move"
- self.drag_start = x - self.highlight_start
- elif scale:
- self.move_type = "scale_drag"
- self.drag_start_time = self.range_start.value
-
-
- if mouse_down and self.drag_start:
- start, end = 0, 0
- if self.move_type and self.move_type != "scale_drag":
- if self.move_type == "start":
- if 0 <= x <= self.width:
- start = x
- end = self.highlight_end
- elif self.move_type == "end":
- if 0 <= x <= self.width:
- start = self.highlight_start
- end = x
- elif self.move_type == "move":
- width = self.highlight_end - self.highlight_start
- start = x - self.drag_start
- start = max(0, min(start, self.width))
-
- end = start + width
- if end > self.width:
- end = self.width
- start = end - width
-
- if end - start > 1:
- self.highlight = (self.get_time(start), self.get_time(end))
- self.redraw_canvas()
-
- self.__call_parent_time_changed()
- else:
- self.range_start.target(self.drag_start_time +
- dt.timedelta(minutes = self.get_value_at_pos(x = self.drag_start) - self.get_value_at_pos(x = x)))
- self.scroll_to_range_start()
-
-
-
- if start_drag:
- area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_SIDE))
- elif end_drag:
- area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.RIGHT_SIDE))
- elif in_between:
- area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
- else:
- area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
-
-
- def _minutes_from_start(self, date):
- delta = (date - self.range_start.value)
- return delta.days * 24 * 60 + delta.seconds / 60
-
- def _render(self):
- context = self.context
- #TODO - use system colors and fonts
-
- context.set_line_width(1)
-
- #we will buffer 4 hours to both sides so partial labels also appear
- range_end = self.range_start.value + dt.timedelta(hours = 12 + 2 * 4)
- self.graph_x = -self.width / 3 #so x moves one third out of screen
- self.set_value_range(x_min = 0, x_max = 12 * 60)
-
- minutes = self._minutes_from_start(range_end)
-
-
-
- graph_y = 4
- graph_height = self.height - 10
- graph_y2 = graph_y + graph_height
-
-
- # graph area
- self.fill_area(0, graph_y - 1, self.width, graph_height, (1,1,1))
-
- #bars
- for fact in self.facts:
- start_minutes = self._minutes_from_start(fact["start_time"])
-
- if fact["end_time"]:
- end_minutes = self._minutes_from_start(fact["end_time"])
- else:
- if fact["start_time"].date() > dt.date.today() - dt.timedelta(days=1):
- end_minutes = self._minutes_from_start(dt.datetime.now())
- else:
- end_minutes = start_minutes
-
- if self.get_pixel(end_minutes) > 0 and \
- self.get_pixel(start_minutes) < self.width:
- context.set_source_rgba(0.86, 0.86, 0.86, 0.5)
-
- context.rectangle(round(self.get_pixel(start_minutes)),
- graph_y,
- round(self.get_pixel(end_minutes) - self.get_pixel(start_minutes)),
- graph_height - 1)
- context.fill()
- context.stroke()
-
- context.set_source_rgba(0.86, 0.86, 0.86, 1)
- self.move_to(start_minutes, graph_y)
- self.line_to(start_minutes, graph_y2)
- self.move_to(end_minutes, graph_y)
- self.line_to(end_minutes, graph_y2)
- context.stroke()
-
-
-
- #time scale
- context.set_source_rgb(0, 0, 0)
- self.layout.set_width(-1)
- for i in range(minutes):
- label_time = (self.range_start.value + dt.timedelta(minutes=i))
-
- if label_time.minute == 0:
- context.set_source_rgb(0.8, 0.8, 0.8)
- self.move_to(i, graph_y2 - 15)
- self.line_to(i, graph_y2)
- context.stroke()
- elif label_time.minute % 15 == 0:
- context.set_source_rgb(0.8, 0.8, 0.8)
- self.move_to(i, graph_y2 - 5)
- self.line_to(i, graph_y2)
- context.stroke()
-
-
-
- if label_time.minute == 0 and label_time.hour % 2 == 0:
- if label_time.hour == 0:
- context.set_source_rgb(0.8, 0.8, 0.8)
- self.move_to(i, graph_y)
- self.line_to(i, graph_y2)
- label_minutes = label_time.strftime("%b %d")
- else:
- label_minutes = label_time.strftime("%H<small><sup>%M</sup></small>")
-
- context.set_source_rgb(0.4, 0.4, 0.4)
- self.layout.set_markup(label_minutes)
- label_w, label_h = self.layout.get_pixel_size()
-
- context.move_to(self.get_pixel(i) + 2, graph_y2 - label_h - 8)
-
- context.show_layout(self.layout)
- context.stroke()
-
- #highlight rectangle
- if self.highlight:
- self.highlight_start = self.get_pixel(self._minutes_from_start(self.highlight[0]))
- self.highlight_end = self.get_pixel(self._minutes_from_start(self.highlight[1]))
-
- #TODO - make a proper range check here
- if self.highlight_end > 0 and self.highlight_start < self.width:
- rgb = colorsys.hls_to_rgb(.6, .7, .5)
-
-
- self.fill_area(self.highlight_start, graph_y,
- self.highlight_end - self.highlight_start, graph_height,
- (rgb[0], rgb[1], rgb[2], 0.5))
- context.stroke()
-
- context.set_source_rgb(*rgb)
- self.context.move_to(self.highlight_start, graph_y)
- self.context.line_to(self.highlight_start, graph_y + graph_height)
- self.context.move_to(self.highlight_end, graph_y)
- self.context.line_to(self.highlight_end, graph_y + graph_height)
- context.stroke()
-
- #and now put a frame around the whole thing
- context.set_source_rgb(0.7, 0.7, 0.7)
- context.rectangle(0, graph_y-1, self.width - 1, graph_height)
- context.stroke()
-
- if self.move_type == "move" and (self.highlight_start == 0 or self.highlight_end == self.width):
- if self.highlight_start == 0:
- self.range_start.target(self.range_start.value - dt.timedelta(minutes=30))
- if self.highlight_end == self.width:
- self.range_start.target(self.range_start.value + dt.timedelta(minutes=30))
- self.scroll_to_range_start()
-
-
class CustomFactController:
def __init__(self, parent = None, fact_date = None, fact_id = None):
@@ -434,7 +117,7 @@ class CustomFactController:
self.set_dropdown()
self.refresh_menu()
- self.dayline = Dayline()
+ self.dayline = widgets.DayLine()
self.dayline.on_time_changed = self.update_time
self.dayline.on_more_data = runtime.storage.get_facts
self._gui.get_object("day_preview").add(self.dayline)
diff --git a/hamster/stats.py b/hamster/stats.py
index dfc449e..b4216cc 100644
--- a/hamster/stats.py
+++ b/hamster/stats.py
@@ -44,299 +44,6 @@ import calendar
import time
from hamster.i18n import C_
-class ReportChooserDialog(gtk.Dialog):
- __gsignals__ = {
- # format, path, start_date, end_date
- 'report-chosen': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
- (gobject.TYPE_STRING, gobject.TYPE_STRING,
- gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
- gobject.TYPE_PYOBJECT)),
- 'report-chooser-closed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
- }
- def __init__(self):
- gtk.Dialog.__init__(self)
- ui = stuff.load_ui_file("stats.ui")
- self.dialog = ui.get_object('save_report_dialog')
-
- self.dialog.set_action(gtk.FILE_CHOOSER_ACTION_SAVE)
- self.dialog.set_current_folder(os.path.expanduser("~"))
-
- self.filters = {}
-
- filter = gtk.FileFilter()
- filter.set_name(_("HTML Report"))
- filter.add_mime_type("text/html")
- filter.add_pattern("*.html")
- filter.add_pattern("*.htm")
- self.filters[filter] = "html"
- self.dialog.add_filter(filter)
-
- filter = gtk.FileFilter()
- filter.set_name(_("Tab-Separated Values (TSV)"))
- filter.add_mime_type("text/plain")
- filter.add_pattern("*.tsv")
- filter.add_pattern("*.txt")
- self.filters[filter] = "tsv"
- self.dialog.add_filter(filter)
-
- filter = gtk.FileFilter()
- filter.set_name(_("XML"))
- filter.add_mime_type("text/xml")
- filter.add_pattern("*.xml")
- self.filters[filter] = "xml"
- self.dialog.add_filter(filter)
-
- filter = gtk.FileFilter()
- filter.set_name(_("iCal"))
- filter.add_mime_type("text/calendar")
- filter.add_pattern("*.ics")
- self.filters[filter] = "ical"
- self.dialog.add_filter(filter)
-
- filter = gtk.FileFilter()
- filter.set_name("All files")
- filter.add_pattern("*")
- self.dialog.add_filter(filter)
-
- self.start_date = widgets.DateInput()
- ui.get_object('from_date_box').add(self.start_date)
- self.end_date = widgets.DateInput()
- ui.get_object('to_date_box').add(self.end_date)
-
- self.category_box = ui.get_object('category_box')
-
- ui.get_object('save_button').connect("clicked", self.on_save_button_clicked)
- ui.get_object('cancel_button').connect("clicked", self.on_cancel_button_clicked)
-
-
- def show(self, start_date, end_date):
- #set suggested name to something readable, replace backslashes with dots
- #so the name is valid in linux
- filename = "Time track %s - %s." % (start_date.strftime("%x").replace("/", "."),
- end_date.strftime("%x").replace("/", "."))
- self.dialog.set_current_name(filename)
-
- self.start_date.set_date(start_date)
- self.end_date.set_date(end_date)
-
- #add unsorted category
- button_all = gtk.CheckButton(C_("categories", "All").encode("utf-8"))
- button_all.value = None
- button_all.set_active(True)
-
- def on_category_all_clicked(checkbox):
- active = checkbox.get_active()
- for checkbox in self.category_box.get_children():
- checkbox.set_active(active)
-
- button_all.connect("clicked", on_category_all_clicked)
- self.category_box.attach(button_all, 0, 1, 0, 1)
-
- categories = runtime.storage.get_category_list()
- col, row = 0, 0
- for category in categories:
- col +=1
- if col % 4 == 0:
- col = 0
- row +=1
-
- button = gtk.CheckButton(category['name'].encode("utf-8"))
- button.value = category['id']
- button.set_active(True)
- self.category_box.attach(button, col, col+1, row, row+1)
-
-
-
- response = self.dialog.show_all()
-
- def present(self):
- self.dialog.present()
-
- def on_save_button_clicked(self, widget):
- path, format = None, None
-
- format = "html"
- if self.dialog.get_filter() in self.filters:
- format = self.filters[self.dialog.get_filter()]
- path = self.dialog.get_filename()
-
- # append correct extension if it is missing
- # TODO - proper way would be to change extension on filter change
- # only pointer in web is http://www.mail-archive.com/pygtk daa com au/msg08740.html
- if path.endswith(".%s" % format) == False:
- path = "%s.%s" % (path.rstrip("."), format)
-
- categories = []
- for button in self.category_box.get_children():
- if button.get_active():
- categories.append(button.value)
-
- if None in categories:
- categories = None # nothing is everything
-
- # format, path, start_date, end_date
- self.emit("report-chosen", format, path,
- self.start_date.get_date().date(),
- self.end_date.get_date().date(),
- categories)
- self.dialog.destroy()
-
-
- def on_cancel_button_clicked(self, widget):
- self.emit("report-chooser-closed")
- self.dialog.destroy()
-
-class TimeLine(graphics.Area):
- MODE_YEAR = 0
- MODE_MONTH = 1
- MODE_WEEK = 1
- MODE_DAY = 3
- def __init__(self):
- graphics.Area.__init__(self)
- self.start_date, self.end_date = None, None
- self.draw_mode = None
- self.max_hours = None
-
-
- def draw(self, facts):
- import itertools
- self.facts = {}
- for date, date_facts in itertools.groupby(facts, lambda x: x["start_time"].date()):
- date_facts = list(date_facts)
- self.facts[date] = date_facts
- self.max_hours = max(self.max_hours,
- sum([fact["delta"].seconds / 60 / float(60) +
- fact["delta"].days * 24 for fact in date_facts]))
-
- start_date = facts[0]["start_time"].date()
- end_date = facts[-1]["start_time"].date()
-
- self.draw_mode = self.MODE_YEAR
- self.start_date = start_date.replace(month=1, day=1)
- self.end_date = end_date.replace(month=12, day=31)
-
-
- """
- #TODO - for now we have only the year mode
- if start_date.year != end_date.year or start_date.month != end_date.month:
- self.draw_mode = self.MODE_YEAR
- self.start_date = start_date.replace(month=1, day=1)
- self.end_date = end_date.replace(month=12, day=31)
- elif start_date.strftime("%W") != end_date.strftime("%W"):
- self.draw_mode = self.MODE_MONTH
- self.start_date = start_date.replace(day=1)
- self.end_date = end_date.replace(date =
- calendar.monthrange(self.end_date.year,
- self.end_date.month)[1])
- elif start_date != end_date:
- self.draw_mode = self.MODE_WEEK
- else:
- self.draw_mode = self.MODE_DAY
- """
-
- self.redraw_canvas()
-
-
- def _render(self):
- import calendar
-
- if self.draw_mode != self.MODE_YEAR:
- return
-
- self.fill_area(0, 0, self.width, self.height, (0.975,0.975,0.975))
- self.set_color((100,100,100))
-
- self.set_value_range(x_min = 1, x_max = (self.end_date - self.start_date).days)
- month_label_fits = True
- for month in range(1, 13):
- self.layout.set_text(calendar.month_abbr[month])
- label_w, label_h = self.layout.get_pixel_size()
- if label_w * 2 > self.x_factor * 30:
- month_label_fits = False
- break
-
-
- ticker_date = self.start_date
-
- year_pos = 0
-
- for year in range(self.start_date.year, self.end_date.year + 1):
- #due to how things lay over, we are putting labels on backwards, so that they don't overlap
-
- self.context.set_line_width(1)
- for month in range(1, 13):
- for day in range(1, calendar.monthrange(year, month)[1] + 1):
- ticker_pos = year_pos + ticker_date.timetuple().tm_yday
-
- #if ticker_date.weekday() in [0, 6]:
- # self.fill_area(ticker_pos * self.x_factor + 1, 20, self.x_factor, self.height - 20, (240, 240, 240))
- # self.context.stroke()
-
-
- if self.x_factor > 5:
- self.move_to(ticker_pos, self.height - 20)
- self.line_to(ticker_pos, self.height)
-
- self.layout.set_text(ticker_date.strftime("%d"))
- label_w, label_h = self.layout.get_pixel_size()
-
- if label_w < self.x_factor / 1.2: #if label fits
- self.context.move_to(self.get_pixel(ticker_pos) + 2,
- self.height - 20)
- self.context.show_layout(self.layout)
-
- self.context.stroke()
-
- #now facts
- facts_today = self.facts.get(ticker_date, [])
- if facts_today:
- total_length = dt.timedelta()
- for fact in facts_today:
- total_length += fact["delta"]
- total_length = total_length.seconds / 60 / 60.0 + total_length.days * 24
- total_length = total_length / float(self.max_hours) * self.height - 16
-
- self.fill_area(round(ticker_pos * self.x_factor),
- round(self.height - total_length),
- round(self.x_factor),
- round(total_length),
- (190,190,190))
-
-
-
-
- ticker_date += dt.timedelta(1)
-
-
-
- if month_label_fits:
- #roll back a little
- month_pos = ticker_pos - calendar.monthrange(year, month)[1] + 1
-
- self.move_to(month_pos, 0)
- #self.line_to(month_pos, 20)
-
- self.layout.set_text(dt.date(year, month, 1).strftime("%b"))
-
- self.move_to(month_pos, 0)
- self.context.show_layout(self.layout)
-
-
-
-
-
- self.layout.set_text("%d" % year)
- label_w, label_h = self.layout.get_pixel_size()
-
- self.move_to(year_pos + 2 / self.x_factor, month_label_fits * label_h * 1.2)
-
- self.context.show_layout(self.layout)
-
- self.context.stroke()
-
- year_pos = ticker_pos #save current state for next year
-
-
class StatsViewer(object):
def __init__(self, parent = None):
@@ -423,7 +130,7 @@ class StatsViewer(object):
self._gui.connect_signals(self)
self.fact_tree.grab_focus()
- self.timeline = TimeLine()
+ self.timeline = widgets.TimeLine()
self.get_widget("explore_everything").add(self.timeline)
self.get_widget("explore_everything").show_all()
@@ -1335,7 +1042,7 @@ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent))
def on_report_button_clicked(self, widget):
if not self.report_chooser:
- self.report_chooser = ReportChooserDialog()
+ self.report_chooser = widgets.ReportChooserDialog()
self.report_chooser.connect("report-chosen", self.on_report_chosen)
self.report_chooser.connect("report-chooser-closed",
self.on_report_chooser_closed)
diff --git a/hamster/widgets/Makefile.am b/hamster/widgets/Makefile.am
index d422ee0..11a663e 100644
--- a/hamster/widgets/Makefile.am
+++ b/hamster/widgets/Makefile.am
@@ -3,7 +3,10 @@ hamster_PYTHON = \
__init__.py \
activityentry.py \
timeinput.py \
- dateinput.py
+ dateinput.py \
+ dayline.py \
+ timeline.py \
+ reportchooserdialog.py \
clean-local:
rm -rf *.pyc *.pyo
diff --git a/hamster/widgets/__init__.py b/hamster/widgets/__init__.py
index b927f2d..644633c 100644
--- a/hamster/widgets/__init__.py
+++ b/hamster/widgets/__init__.py
@@ -25,6 +25,11 @@ from activityentry import ActivityEntry
from dateinput import DateInput
from timeinput import TimeInput
+from timeline import TimeLine
+from dayline import DayLine
+
+from reportchooserdialog import ReportChooserDialog
+
# handy wrappers
def add_hint(entry, hint):
entry.hint = hint
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]