[hamster-applet] fixed trailing space
- From: Toms Baugis <tbaugis src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [hamster-applet] fixed trailing space
- Date: Mon, 11 Jan 2010 22:06:48 +0000 (UTC)
commit 3c68355c5d60e3893b588d87b36d9f1a41e1da90
Author: Toms Bauģis <toms baugis gmail com>
Date: Mon Jan 11 22:06:39 2010 +0000
fixed trailing space
hamster/KeyBinder.py | 20 ++--
hamster/about.py | 10 +-
hamster/applet.py | 108 +++++++-------
hamster/charting.py | 178 +++++++++++-----------
hamster/configuration.py | 46 +++---
hamster/db.py | 250 ++++++++++++++++----------------
hamster/edit_activity.py | 58 ++++----
hamster/eds.py | 2 +-
hamster/graphics.py | 61 ++++----
hamster/hamster-applet.py | 26 ++--
hamster/hamsterdbus.py | 14 +-
hamster/idle.py | 6 +-
hamster/preferences.py | 98 ++++++------
hamster/pytweener.py | 120 ++++++++--------
hamster/reports.py | 68 +++++-----
hamster/stats.py | 66 ++++----
hamster/stats_overview.py | 38 +++---
hamster/stats_reports.py | 22 ++--
hamster/stats_stats.py | 82 +++++-----
hamster/storage.py | 18 +-
hamster/stuff.py | 58 ++++----
hamster/widgets/__init__.py | 16 +-
hamster/widgets/activityentry.py | 72 +++++-----
hamster/widgets/dateinput.py | 30 ++--
hamster/widgets/dayline.py | 80 +++++-----
hamster/widgets/facttree.py | 29 ++--
hamster/widgets/reportchooserdialog.py | 14 +-
hamster/widgets/tags.py | 110 +++++++-------
hamster/widgets/timeinput.py | 64 ++++----
hamster/widgets/timeline.py | 78 +++++-----
tests/charting_test.py | 4 +-
tests/hamsterdbus_test.py | 32 ++--
tests/stuff_test.py | 4 +-
33 files changed, 940 insertions(+), 942 deletions(-)
---
diff --git a/hamster/KeyBinder.py b/hamster/KeyBinder.py
index 9c8a730..6545d9d 100644
--- a/hamster/KeyBinder.py
+++ b/hamster/KeyBinder.py
@@ -26,19 +26,19 @@ from configuration import GconfStore, runtime
class Keybinder(object):
def __init__(self):
self.config = GconfStore()
-
+
self.bound = False
self.prevbinding = None
-
+
self.key_combination = self.config.get_keybinding()
if self.key_combination is None:
# This is for uninstalled cases, the real default is in the schema
self.key_combination = "<Super>H"
-
+
runtime.dispatcher.add_handler("gconf_keybinding_changed", self.on_keybinding_changed)
-
+
self.bind()
-
+
def on_keybinding_changed(self, event, new_binding = None):
self.prevbinding = self.key_combination
self.key_combination = new_binding
@@ -46,14 +46,14 @@ class Keybinder(object):
def on_keybinding_activated(self):
runtime.dispatcher.dispatch('keybinding_activated')
-
+
def get_key_combination(self):
return self.key_combination
-
+
def bind(self):
if self.bound:
self.unbind()
-
+
try:
print 'Binding shortcut %s to popup hamster' % self.key_combination
keybinder.tomboy_keybinder_bind(self.key_combination, self.on_keybinding_activated)
@@ -61,9 +61,9 @@ class Keybinder(object):
except KeyError:
# if the requested keybinding conflicts with an existing one, a KeyError will be thrown
self.bound = False
-
+
#self.emit('changed', self.bound) TODO - revert to previous hotkey
-
+
def unbind(self):
try:
print 'Unbinding shortcut %s to popup hamster' % self.prevbinding
diff --git a/hamster/about.py b/hamster/about.py
index 4eaff13..4609664 100644
--- a/hamster/about.py
+++ b/hamster/about.py
@@ -46,20 +46,20 @@ class About(object):
"title": _("About Time Tracker"),
"wrap-license": True
}
-
+
about.set_authors(["Toms Bauģis <toms baugis gmail com>",
"Patryk Zawadzki <patrys pld-linux org>",
"PÄ?teris Caune <cuu508 gmail com>",
"Juanje Ojeda <jojeda emergya es>"])
about.set_artists(["Kalle Persson <kalle kallepersson se>"])
-
+
about.set_translator_credits(_("translator-credits"))
-
+
for prop, val in infos.items():
about.set_property(prop, val)
-
+
about.set_logo_icon_name("hamster-applet")
-
+
about.connect("response", lambda self, *args: self.destroy())
about.show_all()
diff --git a/hamster/applet.py b/hamster/applet.py
index be44601..3688391 100755
--- a/hamster/applet.py
+++ b/hamster/applet.py
@@ -49,14 +49,14 @@ try:
PYNOTIFY = True
except:
PYNOTIFY = False
-
+
class Notifier(object):
def __init__(self, attach):
self._icon = gtk.STOCK_DIALOG_QUESTION
self._attach = attach
# Title of reminder notification
self.summary = _("Time Tracker")
-
+
if not pynotify.is_initted():
pynotify.init('Hamster Applet')
@@ -67,9 +67,9 @@ class Notifier(object):
#translators: this is edit activity action in the notifier bubble
notify.add_action("edit", _("Edit"), edit_cb)
#translators: this is switch activity action in the notifier bubble
- notify.add_action("switch", _("Switch"), switch_cb)
+ notify.add_action("switch", _("Switch"), switch_cb)
notify.show()
-
+
def msg_low(self, message):
notify = pynotify.Notification(self.summary, message, self._icon, self._attach)
notify.set_urgency(pynotify.URGENCY_LOW)
@@ -81,7 +81,7 @@ class PanelButton(gtk.ToggleButton):
gtk.ToggleButton.__init__(self)
self.set_relief(gtk.RELIEF_NONE)
self.set_border_width(0)
-
+
self.label = gtk.Label()
self.label.set_justify(gtk.JUSTIFY_CENTER)
@@ -90,7 +90,7 @@ class PanelButton(gtk.ToggleButton):
self.connect('button_press_event', self.on_button_press)
self.add(self.label)
-
+
self.activity, self.duration = None, None
self.prev_size = 0
@@ -100,7 +100,7 @@ class PanelButton(gtk.ToggleButton):
GtkWidget::focus-line-width=0
GtkWidget::focus-padding=0
}
-
+
widget "*.hamster-applet-button" style "hamster-applet-button-style"
""");
gtk.Widget.set_name (self, "hamster-applet-button");
@@ -117,7 +117,7 @@ class PanelButton(gtk.ToggleButton):
self.activity = activity
self.duration = duration
self.reformat_label()
-
+
def reformat_label(self):
label = self.activity
if self.duration:
@@ -125,7 +125,7 @@ class PanelButton(gtk.ToggleButton):
label = "%s\n%s" % (self.activity, self.duration)
else:
label = "%s %s" % (self.activity, self.duration)
-
+
label = '<span gravity="south">%s</span>' % label
self.label.set_markup("") #clear - seems to fix the warning
self.label.set_markup(label)
@@ -139,7 +139,7 @@ class PanelButton(gtk.ToggleButton):
return False
popup_dir = self.get_parent().get_orient()
-
+
orient_vertical = popup_dir in [gnomeapplet.ORIENT_LEFT] or \
popup_dir in [gnomeapplet.ORIENT_RIGHT]
@@ -164,7 +164,7 @@ class PanelButton(gtk.ToggleButton):
available_size = self.get_allocation().width
else:
available_size = self.get_allocation().height
-
+
return required_size <= available_size
def on_label_style_set(self, widget, something):
@@ -175,7 +175,7 @@ class PanelButton(gtk.ToggleButton):
return
self.popup_dir = self.get_parent().get_orient()
-
+
orient_vertical = True
new_size = allocation.width
if self.popup_dir in [gnomeapplet.ORIENT_LEFT]:
@@ -186,7 +186,7 @@ class PanelButton(gtk.ToggleButton):
new_angle = 0
orient_vertical = False
new_size = allocation.height
-
+
if new_angle != self.label.get_angle():
self.label.set_angle(new_angle)
@@ -212,7 +212,7 @@ class HamsterApplet(object):
self.open_fact_editors = []
self.config = GconfStore()
-
+
self.button = PanelButton()
self.button.connect('toggled', self.on_toggle)
self.applet.add(self.button)
@@ -246,7 +246,7 @@ class HamsterApplet(object):
self.treeview.connect("key-press-event", self.on_todays_keys)
self.treeview.connect("edit-clicked", self._open_edit_activity)
self.treeview.connect("row-activated", self.on_today_row_activated)
-
+
self.get_widget("today_box").add(self.treeview)
# DBus Setup
@@ -261,7 +261,7 @@ class HamsterApplet(object):
except dbus.DBusException, e:
logging.error("Can't init dbus: %s" % e)
-
+
self.day_start = self.config.get_day_start()
# Load today's data, activities and set label
@@ -292,9 +292,9 @@ class HamsterApplet(object):
runtime.dispatcher.add_handler('gconf_notify_on_idle_changed', self.on_notify_on_idle_changed)
self.notify_on_idle = self.config.get_notify_on_idle()
-
+
runtime.dispatcher.add_handler('gconf_on_day_start_changed', self.on_day_start_changed)
-
+
# init nagging timeout
if PYNOTIFY:
self.notify = Notifier(self.button)
@@ -304,10 +304,10 @@ class HamsterApplet(object):
"""UI functions"""
def refresh_hamster(self):
- """refresh hamster every x secs - load today, check last activity etc."""
+ """refresh hamster every x secs - load today, check last activity etc."""
#if we the day view is visible - update day's durations
- if self.button.get_active():
+ if self.button.get_active():
self.load_day()
self.update_label()
@@ -325,12 +325,12 @@ class HamsterApplet(object):
else:
label = "%s" % _(u"No activity")
self.button.set_text(label, None)
-
+
def check_user(self):
if not self.notify_interval: #no interval means "never"
return
-
+
now = dt.datetime.now()
if self.last_activity:
delta = now - self.last_activity['start_time']
@@ -349,7 +349,7 @@ class HamsterApplet(object):
dialogs.edit.show(self.applet, activity_id = self.last_activity['id'])
def switch_cb(self, n, action):
- self.__show_toggle(None, not self.button.get_active())
+ self.__show_toggle(None, not self.button.get_active())
def load_day(self):
@@ -366,14 +366,14 @@ class HamsterApplet(object):
self.last_activity = facts[-1]
else:
self.last_activity = None
-
+
if len(facts) > 10:
self._gui.get_object("today_box").set_size_request(-1, 250)
self._gui.get_object("today_box").set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
else:
self._gui.get_object("today_box").set_size_request(-1, -1)
self._gui.get_object("today_box").set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER)
-
+
by_category = {}
for fact in facts:
duration = 24 * 60 * fact["delta"].days + fact["delta"].seconds / 60
@@ -381,13 +381,13 @@ class HamsterApplet(object):
by_category.setdefault(fact['category'], 0) + duration
self.treeview.add_fact(fact)
-
+
if not facts:
self._gui.get_object("today_box").hide()
self._gui.get_object("fact_totals").set_text(_("No records today"))
else:
self._gui.get_object("today_box").show()
-
+
total_strings = []
for category in by_category:
# listing of today's categories and time spent in them
@@ -395,27 +395,27 @@ class HamsterApplet(object):
total_strings.append(_("%(category)s: %(duration)s") % \
({'category': category,
#duration in main drop-down per category in hours
- 'duration': _("%sh") % duration
+ 'duration': _("%sh") % duration
}))
total_string = ", ".join(total_strings)
self._gui.get_object("fact_totals").set_text(total_string)
-
+
self.set_last_activity()
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
-
+
self.get_widget("last_activity_duration").set_text(stuff.format_duration(duration) or _("Just started"))
self.get_widget("last_activity_name").set_text(activity['name'])
if activity['category'] != _("Unsorted"):
@@ -443,21 +443,21 @@ class HamsterApplet(object):
(cur, col) = self.treeview.get_cursor()
runtime.storage.remove_fact(model[iter][0])
- if next_row:
+ if next_row:
self.treeview.set_cursor(cur)
def __update_fact(self):
"""dbus controller current fact updating"""
last_activity_id = 0
-
+
if not self.last_activity:
self.dbusController.TrackingStopped()
else:
last_activity_id = self.last_activity['id']
self.dbusController.FactUpdated(last_activity_id)
-
+
def __show_toggle(self, event, is_active):
"""main window display and positioning"""
self.button.set_active(is_active)
@@ -471,7 +471,7 @@ class HamsterApplet(object):
label_geom = self.button.get_allocation()
window_geom = self.window.get_allocation()
-
+
x, y = self.button.get_pos()
self.popup_dir = self.applet.get_orient()
@@ -495,14 +495,14 @@ class HamsterApplet(object):
self.new_name.set_text("");
self.new_tags.set_text("");
- gobject.idle_add(self._delayed_display)
-
+ gobject.idle_add(self._delayed_display)
+
def _delayed_display(self):
"""show window only when gtk has become idle. otherwise we get
mixed results. TODO - this looks like a hack though"""
self.window.present()
self.new_name.grab_focus()
-
+
"""events"""
def on_toggle(self, widget):
@@ -513,17 +513,17 @@ class HamsterApplet(object):
if (event.keyval == gtk.keysyms.Delete):
self.delete_selected()
return True
-
+
return False
-
+
def _open_edit_activity(self, row, fact):
"""opens activity editor for selected row"""
dialogs.edit.show(self.applet, fact_id = fact["id"])
-
+
def on_today_row_activated(self, tree, path, column):
selection = tree.get_selection()
(model, iter) = selection.get_selected()
-
+
fact = model[iter][6]
if fact:
activity = fact['name']
@@ -533,21 +533,21 @@ class HamsterApplet(object):
tags = fact["tags"]
if fact["description"]:
tags.append(fact["description"])
-
+
runtime.storage.add_fact(activity, ", ".join(tags))
runtime.dispatcher.dispatch('panel_visible', False)
-
-
+
+
def on_windows_keys(self, tree, event_key):
if (event_key.keyval == gtk.keysyms.Escape
- or (event_key.keyval == gtk.keysyms.w
+ or (event_key.keyval == gtk.keysyms.w
and event_key.state & gtk.gdk.CONTROL_MASK)):
if self.new_name.popup.get_property("visible") == False \
and self.new_tags.popup.get_property("visible") == False:
runtime.dispatcher.dispatch('panel_visible', False)
return True
return False
-
+
"""button events"""
def on_overview(self, menu_item):
runtime.dispatcher.dispatch('panel_visible', False)
@@ -566,13 +566,13 @@ class HamsterApplet(object):
runtime.dispatcher.dispatch('panel_visible', False)
dialogs.prefs.show(self.applet)
-
+
"""signals"""
def after_activity_update(self, widget, renames):
self.new_name.refresh_activities()
self.load_day()
self.update_label()
-
+
def after_fact_update(self, event, date):
self.load_day()
self.update_label()
@@ -587,7 +587,7 @@ class HamsterApplet(object):
self.refresh_hamster()
elif self.timeout_enabled and self.last_activity and \
self.last_activity['end_time'] is None:
-
+
runtime.storage.touch_fact(self.last_activity,
end_time = self.dbusIdleListener.getIdleFrom())
@@ -599,7 +599,7 @@ class HamsterApplet(object):
"""global shortcuts"""
def on_keybinding_activated(self, event, data):
self.__show_toggle(None, not self.button.get_active())
-
+
def on_timeout_enabled_changed(self, event, enabled):
self.timeout_enabled = enabled
@@ -629,6 +629,6 @@ class HamsterApplet(object):
def show(self):
self.window.show_all()
-
+
def get_widget(self, name):
return self._gui.get_object(name)
diff --git a/hamster/charting.py b/hamster/charting.py
index 7522dbe..cdbf11a 100644
--- a/hamster/charting.py
+++ b/hamster/charting.py
@@ -75,16 +75,16 @@ def get_limits(set, stack_subfactors = True):
min_value = max(row, min_value)
return min_value, max_value
-
+
class Bar(object):
def __init__(self, value, size = 0):
self.value = value
self.size = size
-
+
def __repr__(self):
return str((self.value, self.size))
-
+
class Chart(graphics.Area):
"""Chart constructor. Optional arguments:
@@ -146,34 +146,34 @@ class Chart(graphics.Area):
self.keys = []
self.data = None
self.stack_keys = []
-
+
self.key_colors = {} # key:color dictionary. if key's missing will grab basecolor
self.stack_key_colors = {} # key:color dictionary. if key's missing will grab basecolor
-
+
# use these to mark area where the "real" drawing is going on
self.graph_x, self.graph_y = 0, 0
self.graph_width, self.graph_height = None, None
-
+
self.mouse_bar = None
if self.interactive:
self.connect("mouse-over", self.on_mouse_over)
self.connect("button-release", self.on_clicked)
-
+
self.bars_selected = []
-
-
+
+
def on_mouse_over(self, area, region):
if region:
self.mouse_bar = int(region[0])
else:
self.mouse_bar = None
-
+
self.redraw_canvas()
-
+
def on_clicked(self, area, bar):
self.emit("bar-clicked", self.mouse_bar)
-
+
def select_bar(self, index):
pass
@@ -181,15 +181,15 @@ class Chart(graphics.Area):
# returns color darkened by it's index
# the approach reduces contrast by each step
base_color = self.bar_base_color or (220, 220, 220)
-
+
base_hls = colorsys.rgb_to_hls(*base_color)
-
+
step = (base_hls[1] - 30) / 10 #will go from base down to 20 and max 22 steps
-
+
return colorsys.hls_to_rgb(base_hls[0],
base_hls[1] - step * index,
base_hls[2])
-
+
def draw_bar(self, x, y, w, h, color = None):
""" draws a simple bar"""
@@ -219,15 +219,15 @@ class Chart(graphics.Area):
def on_expose(self):
- # fill whole area
+ # fill whole area
if self.background:
self.fill_area(0, 0, self.width, self.height, self.background)
-
+
def _update_targets(self):
# calculates new factors and then updates existing set
max_value = float(self.max_value) or 1 # avoid division by zero
-
+
self.bars = size_list(self.bars, self.data)
#need function to go recursive
@@ -244,7 +244,7 @@ class Chart(graphics.Area):
self.tweener.addTween(bars[i], size = bars[i].value / float(max_value))
return bars
-
+
retarget(self.bars, self.data)
@@ -255,9 +255,9 @@ class Chart(graphics.Area):
self.layout.set_text(label)
label_w, label_h = self.layout.get_pixel_size()
max_extent = max(label_w + 5, max_extent)
-
+
return max_extent
-
+
def draw(self):
logging.error("OMG OMG, not implemented!!!")
@@ -269,7 +269,7 @@ class BarChart(Chart):
if not self.data:
return
- context = self.context
+ context = self.context
context.set_line_width(1)
@@ -281,7 +281,7 @@ class BarChart(Chart):
grid_stride = int(self.max_value * self.grid_stride)
else:
grid_stride = int(self.grid_stride)
-
+
scale_labels = [self.value_format % i
for i in range(grid_stride, int(self.max_value), grid_stride)]
self.legend_width = legend_width = self.legend_width or self.longest_label(scale_labels)
@@ -320,7 +320,7 @@ class BarChart(Chart):
bar_width = min(self.graph_width / float(len(self.keys)), self.max_bar_width)
for i, key in enumerate(self.keys):
exes[key] = (x + self.graph_x, round(bar_width - 1))
-
+
x = x + round(bar_width)
bar_width = min(self.max_bar_width,
(self.graph_width - x) / float(max(1, len(self.keys) - i - 1)))
@@ -332,20 +332,20 @@ class BarChart(Chart):
label_w, label_h = self.layout.get_pixel_size()
intended_x = exes[key][0] + (exes[key][1] - label_w) / 2
-
+
if not prev_label_end or intended_x > prev_label_end:
self.context.move_to(intended_x, self.graph_height + 4)
context.show_layout(self.layout)
-
+
prev_label_end = intended_x + label_w + 3
-
+
bar_start = 0
base_color = self.bar_base_color or (220, 220, 220)
-
+
if self.stack_keys:
remaining_fractions, remaining_pixels = 1, max_bar_size
-
+
for j, stack_bar in enumerate(bar):
if stack_bar.size > 0:
bar_size = round(remaining_pixels * (stack_bar.size / remaining_fractions))
@@ -353,7 +353,7 @@ class BarChart(Chart):
remaining_pixels -= bar_size
bar_start += bar_size
-
+
last_color = self.stack_key_colors.get(self.stack_keys[j]) or self.get_bar_color(j)
self.draw_bar(exes[key][0],
self.graph_height - bar_start,
@@ -377,17 +377,17 @@ class BarChart(Chart):
total_value = sum(data[i])
else:
total_value = data[i]
-
+
self.layout.set_width(-1)
self.layout.set_text(self.value_format % total_value)
label_w, label_h = self.layout.get_pixel_size()
-
+
if bar_start > label_h + 2:
label_y = self.graph_y + self.graph_height - bar_start + 5
else:
label_y = self.graph_y + self.graph_height - bar_start - label_h + 5
-
+
context.move_to(self.exes[key][0] + (self.exes[key][1] - label_w) / 2.0,
label_y)
@@ -395,7 +395,7 @@ class BarChart(Chart):
if colorsys.rgb_to_hls(*graphics.Colors.rgb(last_color))[1] < 150:
self.set_color(graphics.Colors.almost_white)
else:
- self.set_color(graphics.Colors.aluminium[5])
+ self.set_color(graphics.Colors.aluminium[5])
context.show_layout(self.layout)
@@ -408,7 +408,7 @@ class BarChart(Chart):
grid_stride = int(self.max_value * self.grid_stride)
else:
grid_stride = int(self.grid_stride)
-
+
context.set_line_width(1)
for i in range(grid_stride, int(self.max_value), grid_stride):
y = round(max_bar_size * (i / self.max_value)) + 0.5
@@ -430,16 +430,16 @@ class BarChart(Chart):
if self.show_stack_labels:
#put series keys
self.set_color(graphics.Colors.aluminium[5]);
-
+
y = self.graph_height
label_y = None
- # if labels are at end, then we need show them for the last bar!
+ # if labels are at end, then we need show them for the last bar!
if self.labels_at_end:
factors = self.bars[-1]
else:
factors = self.bars[0]
-
+
if isinstance(factors, Bar):
factors = [factors]
@@ -449,28 +449,28 @@ class BarChart(Chart):
self.layout.set_alignment(pango.ALIGN_LEFT)
else:
self.layout.set_alignment(pango.ALIGN_RIGHT)
-
+
for j in range(len(factors)):
factor = factors[j].size
bar_size = factor * max_bar_size
-
+
if round(bar_size) > 0 and self.stack_keys:
label = "%s" % self.stack_keys[j]
-
+
self.layout.set_text(label)
label_w, label_h = self.layout.get_pixel_size()
-
+
y -= bar_size
intended_position = round(y + (bar_size - label_h) / 2)
-
+
if label_y:
label_y = min(intended_position, label_y - label_h)
else:
label_y = intended_position
-
+
if self.labels_at_end:
- label_x = self.graph_x + self.graph_width
+ label_x = self.graph_x + self.graph_width
line_x1 = self.graph_x + self.graph_width - 1
line_x2 = self.graph_x + self.graph_width - 6
else:
@@ -498,13 +498,13 @@ class HorizontalBarChart(Chart):
context = self.context
rowcount, keys = len(self.keys), self.keys
-
+
# push graph to the right, so it doesn't overlap
legend_width = self.legend_width or self.longest_label(keys)
-
+
self.graph_x = legend_width
self.graph_x += 8 #add another 8 pixes of padding
-
+
self.graph_width = self.width - self.graph_x
self.graph_y, self.graph_height = 0, self.height
@@ -512,7 +512,7 @@ class HorizontalBarChart(Chart):
if self.chart_background:
self.fill_area(self.graph_x, self.graph_y, self.graph_width, self.graph_height, self.chart_background)
-
+
if not self.data: # go home if we have nothing
return
@@ -521,7 +521,7 @@ class HorizontalBarChart(Chart):
bar_width = min(self.graph_height / float(len(self.keys)), self.max_bar_width)
for i, key in enumerate(self.keys):
positions[key] = (y + self.graph_y, round(bar_width - 1))
-
+
y = y + round(bar_width)
bar_width = min(self.max_bar_width,
(self.graph_height - y) / float(max(1, len(self.keys) - i - 1)))
@@ -531,14 +531,14 @@ class HorizontalBarChart(Chart):
self.layout.set_alignment(pango.ALIGN_RIGHT)
self.layout.set_ellipsize(pango.ELLIPSIZE_END)
-
-
+
+
context.set_line_width(1)
# bars and labels
self.layout.set_width(legend_width * pango.SCALE)
-
+
for i, label in enumerate(keys):
if self.interactive:
@@ -551,8 +551,8 @@ class HorizontalBarChart(Chart):
self.layout.set_width(legend_width * pango.SCALE)
self.layout.set_text(label)
label_w, label_h = self.layout.get_pixel_size()
-
- self.set_color(graphics.Colors.aluminium[5])
+
+ self.set_color(graphics.Colors.aluminium[5])
context.move_to(0, positions[label][0] + (positions[label][1] - label_h) / 2)
context.show_layout(self.layout)
@@ -564,7 +564,7 @@ class HorizontalBarChart(Chart):
bar_start = 0
remaining_fractions, remaining_pixels = 1, max_bar_size
-
+
for j, stack_bar in enumerate(self.bars[i]):
if stack_bar.size > 0:
bar_size = round(remaining_pixels * (stack_bar.size / remaining_fractions))
@@ -600,7 +600,7 @@ class HorizontalBarChart(Chart):
total_value = sum(self.data[i])
else:
total_value = self.data[i]
-
+
self.layout.set_width(-1)
self.layout.set_text(self.value_format % total_value)
label_w, label_h = self.layout.get_pixel_size()
@@ -608,7 +608,7 @@ class HorizontalBarChart(Chart):
vertical_padding = max((positions[label][1] - label_h) / 2.0, 1)
if bar_start - vertical_padding < label_w:
label_x = self.graph_x + bar_start + vertical_padding
- self.set_color(graphics.Colors.aluminium[5])
+ self.set_color(graphics.Colors.aluminium[5])
else:
if i in self.bars_selected:
self.set_color(self.get_style().fg[gtk.STATE_SELECTED].to_string())
@@ -617,10 +617,10 @@ class HorizontalBarChart(Chart):
if colorsys.rgb_to_hls(*graphics.Colors.rgb(last_color))[1] < 150:
self.set_color(graphics.Colors.almost_white)
else:
- self.set_color(graphics.Colors.aluminium[5])
-
+ self.set_color(graphics.Colors.aluminium[5])
+
label_x = self.graph_x + bar_start - label_w - vertical_padding
-
+
context.move_to(label_x, positions[label][0] + (positions[label][1] - label_h) / 2.0)
context.show_layout(self.layout)
@@ -635,39 +635,39 @@ class HorizontalDayChart(Chart):
def __init__(self, *args, **kwargs):
Chart.__init__(self, *args, **kwargs)
self.start_time, self.end_time = None, None
-
+
def plot_day(self, keys, data, start_time = None, end_time = None):
self.keys, self.data = keys, data
self.start_time, self.end_time = start_time, end_time
self.show()
self.redraw_canvas()
-
+
def on_expose(self):
context = self.context
-
+
Chart.on_expose(self)
rowcount, keys = len(self.keys), self.keys
-
+
start_hour = 0
if self.start_time:
start_hour = self.start_time
- end_hour = 24 * 60
+ end_hour = 24 * 60
if self.end_time:
end_hour = self.end_time
-
-
+
+
# push graph to the right, so it doesn't overlap
legend_width = self.legend_width or self.longest_label(keys)
self.graph_x = legend_width
self.graph_x += 8 #add another 8 pixes of padding
-
+
self.graph_width = self.width - self.graph_x
-
+
#on the botttom leave some space for label
self.layout.set_text("1234567890:")
label_w, label_h = self.layout.get_pixel_size()
-
+
self.graph_y, self.graph_height = 0, self.height - label_h - 4
@@ -677,32 +677,32 @@ class HorizontalDayChart(Chart):
if not self.data: #if we have nothing, let's go home
return
-
+
positions = {}
y = 0
bar_width = min(self.graph_height / float(len(self.keys)), self.max_bar_width)
for i, key in enumerate(self.keys):
positions[key] = (y + self.graph_y, round(bar_width - 1))
-
+
y = y + round(bar_width)
bar_width = min(self.max_bar_width,
(self.graph_height - y) / float(max(1, len(self.keys) - i - 1)))
-
+
max_bar_size = self.graph_width - 15
self.layout.set_alignment(pango.ALIGN_RIGHT)
self.layout.set_ellipsize(pango.ELLIPSIZE_END)
-
+
# bars and labels
self.layout.set_width(legend_width * pango.SCALE)
factor = max_bar_size / float(end_hour - start_hour)
for i, label in enumerate(keys):
- self.set_color(graphics.Colors.aluminium[5])
-
+ self.set_color(graphics.Colors.aluminium[5])
+
self.layout.set_text(label)
label_w, label_h = self.layout.get_pixel_size()
@@ -713,11 +713,11 @@ class HorizontalDayChart(Chart):
if isinstance(self.data[i], list) == False:
self.data[i] = [self.data[i]]
-
+
for row in self.data[i]:
bar_x = round((row[0]- start_hour) * factor)
bar_size = round((row[1] - start_hour) * factor - bar_x)
-
+
self.draw_bar(round(self.graph_x + bar_x),
positions[label][0],
bar_size,
@@ -733,7 +733,7 @@ class HorizontalDayChart(Chart):
last_position = positions[keys[-1]]
for i in range(start_hour + 60, end_hour, pace):
x = round((i - start_hour) * factor)
-
+
minutes = i % (24 * 60)
self.layout.set_markup(dt.time(minutes / 60, minutes % 60).strftime("%H<small><sup>%M</sup></small>"))
@@ -744,13 +744,13 @@ class HorizontalDayChart(Chart):
self.set_color(graphics.Colors.aluminium[4])
context.show_layout(self.layout)
-
+
self.set_color((255, 255, 255))
self.context.move_to(round(self.graph_x + x) + 0.5, self.graph_y)
self.context.line_to(round(self.graph_x + x) + 0.5,
last_position[0] + last_position[1])
-
+
context.stroke()
@@ -761,12 +761,12 @@ class BasicWindow:
window.set_title("Hamster Charting")
window.set_size_request(400, 300)
window.connect("delete_event", lambda *args: gtk.main_quit())
-
+
self.stacked = BarChart(background = "#fafafa",
bar_base_color = (220, 220, 220),
legend_width = 20,
show_stack_labels = True)
-
+
box = gtk.VBox()
box.pack_start(self.stacked)
@@ -777,7 +777,7 @@ class BasicWindow:
import random
self.data = [[random.randint(0, 10) for j in range(len(self.stacks))] for i in range(len(self.series))]
-
+
color_buttons = gtk.HBox()
color_buttons.set_spacing(4)
@@ -787,13 +787,13 @@ class BasicWindow:
color_buttons.pack_start(button)
box.pack_start(color_buttons, False)
-
+
window.add(box)
window.show_all()
self.plot()
-
-
+
+
def plot(self):
self.stacked.stack_key_colors = self.stack_colors
self.stacked.plot(self.series, self.data, self.stacks)
diff --git a/hamster/configuration.py b/hamster/configuration.py
index b2d0618..2eb594e 100644
--- a/hamster/configuration.py
+++ b/hamster/configuration.py
@@ -44,7 +44,7 @@ class RuntimeStore(Singleton):
data_dir = ""
dispatcher = None
storage = None
-
+
def __init__(self):
gettext.install("hamster-applet", unicode = True)
@@ -61,7 +61,7 @@ class RuntimeStore(Singleton):
# figure out the correct database file
old_db_file = os.path.expanduser("~/.gnome2/hamster-applet/hamster.db")
new_db_file = os.path.join(xdg_data_home, "hamster-applet", "hamster.db")
-
+
if os.path.exists(old_db_file):
db_path, _ = os.path.split(os.path.realpath(new_db_file))
if not os.path.exists(db_path):
@@ -73,7 +73,7 @@ class RuntimeStore(Singleton):
logging.info("Have two database %s and %s" % (new_db_file, old_db_file))
else:
os.rename(old_db_file, new_db_file)
-
+
self.database_path = new_db_file
@@ -84,12 +84,12 @@ class RuntimeStore(Singleton):
if gio_file.query_info(gio.FILE_ATTRIBUTE_ETAG_VALUE).get_etag() == self.last_etag:
# ours
return
-
+
logging.info("DB file has been modified externally. Calling all stations")
self.storage.dispatch_overwrite()
-
+
self.database_file = gio.File(self.database_path)
self.db_monitor = self.database_file.monitor_file()
self.db_monitor.connect("changed", on_db_file_change)
@@ -113,14 +113,14 @@ class OneWindow(object):
def __init__(self, get_dialog_class):
self.dialogs = {}
self.get_dialog_class = get_dialog_class
-
+
def on_dialog_destroy(self, params):
del self.dialogs[params]
#self.dialogs[params] = None
def show(self, parent = None, **kwargs):
params = str(sorted(kwargs.items())) #this is not too safe but will work for most cases
-
+
if params in self.dialogs:
self.dialogs[params].window.present()
else:
@@ -129,12 +129,12 @@ class OneWindow(object):
dialog.window.set_transient_for(parent.get_toplevel())
else:
dialog = self.get_dialog_class()(**kwargs)
-
+
# to make things simple, we hope that the target has defined self.window
dialog.window.connect("destroy",
lambda window, params: self.on_dialog_destroy(params),
params)
-
+
self.dialogs[params] = dialog
class Dialogs(Singleton):
@@ -166,31 +166,31 @@ class Dialogs(Singleton):
return PreferencesEditor
self.prefs = OneWindow(get_prefs_class)
-dialogs = Dialogs()
+dialogs = Dialogs()
class GconfStore(Singleton):
"""
- Handles storing to and retrieving values from GConf
+ Handles storing to and retrieving values from GConf
"""
# GConf directory for deskbar in window mode and shared settings
GCONF_DIR = "/apps/hamster-applet/general"
-
+
# GConf key for global keybinding
GCONF_KEYBINDING = GCONF_DIR + "/keybinding"
GCONF_ENABLE_TIMEOUT = GCONF_DIR + "/enable_timeout"
- GCONF_STOP_ON_SHUTDOWN = GCONF_DIR + "/stop_on_shutdown"
- GCONF_NOTIFY_INTERVAL = GCONF_DIR + "/notify_interval"
+ GCONF_STOP_ON_SHUTDOWN = GCONF_DIR + "/stop_on_shutdown"
+ GCONF_NOTIFY_INTERVAL = GCONF_DIR + "/notify_interval"
GCONF_NOTIFY_ON_IDLE = GCONF_DIR + "/notify_on_idle"
GCONF_DAY_START = GCONF_DIR + "/day_start"
__instance = None
-
+
def __init__(self):
super(GconfStore, self).__init__()
self._client = gconf.client_get_default()
self.__connect_notifications()
-
+
def __connect_notifications(self):
self._client.add_dir(self.GCONF_DIR, gconf.CLIENT_PRELOAD_RECURSIVE)
self._client.notify_add(self.GCONF_KEYBINDING, lambda x, y, z, a: runtime.dispatcher.dispatch("gconf_keybinding_changed", z.value.get_string()))
@@ -200,16 +200,16 @@ class GconfStore(Singleton):
self._client.notify_add(self.GCONF_NOTIFY_ON_IDLE, lambda x, y, z, a: runtime.dispatcher.dispatch("gconf_notify_on_idle_changed", z.value.get_bool()))
self._client.notify_add(self.GCONF_DAY_START, lambda x, y, z, a: runtime.dispatcher.dispatch("gconf_on_day_start_changed", z.value.get_int()))
-
+
def get_keybinding(self):
return self._client.get_string(self.GCONF_KEYBINDING) or ""
-
+
def get_timeout_enabled(self):
return self._client.get_bool(self.GCONF_ENABLE_TIMEOUT) or False
def get_stop_on_shutdown(self):
return self._client.get_bool(self.GCONF_STOP_ON_SHUTDOWN) or False
-
+
def get_notify_interval(self):
return self._client.get_int(self.GCONF_NOTIFY_INTERVAL) or 27
@@ -220,16 +220,16 @@ class GconfStore(Singleton):
minutes = self._client.get_int(self.GCONF_DAY_START) or 5*60 + 30
return dt.time(minutes / 60, minutes % 60)
- #------------------------
+ #------------------------
def set_keybinding(self, binding):
self._client.set_string(self.GCONF_KEYBINDING, binding)
-
+
def set_timeout_enabled(self, enabled):
self._client.set_bool(self.GCONF_ENABLE_TIMEOUT, enabled)
-
+
def set_stop_on_shutdown(self, enabled):
self._client.set_bool(self.GCONF_STOP_ON_SHUTDOWN, enabled)
-
+
def set_notify_interval(self, interval):
return self._client.set_int(self.GCONF_NOTIFY_INTERVAL, interval)
diff --git a/hamster/db.py b/hamster/db.py
index b686f28..d1edd83 100644
--- a/hamster/db.py
+++ b/hamster/db.py
@@ -85,36 +85,36 @@ class Storage(storage.Storage):
__setup.complete = False
-
+
#tags, here we come!
def __get_tags(self, autocomplete = None):
query = "select * from tags"
if autocomplete:
query += " where autocomplete='true'"
-
+
query += " order by name"
return self.fetchall(query)
-
+
def __get_tag_ids(self, tags):
"""look up tags by their name. create if not found"""
-
+
# filter descriptions out, just in case they have wandered in here
tags = [tag.lower() for tag in tags if tag.startswith("!") == False and len(tag.split(" ")) < 3]
-
+
db_tags = self.fetchall("select * from tags where name in (%s)"
% ",".join(["?"] * len(tags)), tags) # bit of magic here - using sqlites bind variables
-
+
changes = False
-
+
# check if any of tags needs ressurection
set_complete = [str(tag["id"]) for tag in db_tags if tag["autocomplete"] == "false"]
if set_complete:
changes = True
self.execute("update tags set autocomplete='true' where id in (%s)" % ", ".join(set_complete))
-
-
+
+
found_tags = [tag["name"] for tag in db_tags]
-
+
add = set(tags) - set(found_tags)
if add:
statement = "insert into tags(name) values(?)"
@@ -131,7 +131,7 @@ class Storage(storage.Storage):
#first we will create new ones
tags, changes = self.__get_tag_ids(tags)
tags = [tag["id"] for tag in tags]
-
+
#now we will find which ones are gone from the list
query = """
SELECT b.id as id, count(a.fact_id) as occurences
@@ -149,7 +149,7 @@ class Storage(storage.Storage):
if to_delete:
self.execute("delete from tags where id in (%s)" % ", ".join(to_delete))
- if to_uncomplete:
+ if to_uncomplete:
self.execute("update tags set autocomplete='false' where id in (%s)" % ", ".join(to_uncomplete))
@@ -160,10 +160,10 @@ class Storage(storage.Storage):
# first check if we don't have an activity with same name before us
activity = self.fetchone("select name from activities where id = ?", (id, ))
existing_id = self.__get_activity_by_name(activity['name'], category_id)
-
+
if id == existing_id: # we are already there, go home
return False
-
+
if existing_id: #ooh, we have something here!
# first move all facts that belong to movable activity to the new one
update = """
@@ -171,26 +171,26 @@ class Storage(storage.Storage):
SET activity_id = ?
WHERE activity_id = ?
"""
-
+
self.execute(update, (existing_id, id))
-
+
# and now get rid of our friend
self.__remove_activity(id)
-
- else: #just moving
+
+ else: #just moving
query = "SELECT max(activity_order) + 1 FROM activities WHERE category_id = ?"
max_order = self.fetchone(query, (category_id, ))[0] or 1
-
+
statement = """
- UPDATE activities
+ UPDATE activities
SET category_id = ?, activity_order = ?
WHERE id = ?
"""
-
+
self.execute(statement, (category_id, max_order, id))
-
+
return True
-
+
def __add_category(self, name):
new_rec = self.fetchone("select max(id) +1, max(category_order) + 1 from categories")
@@ -210,50 +210,50 @@ class Storage(storage.Storage):
SET name = ?
WHERE id = ?
"""
- self.execute(update, (name, id))
-
+ self.execute(update, (name, id))
+
def __move_activity(self, source_id, target_order, insert_after = True):
statement = "UPDATE activities SET activity_order = activity_order + 1"
-
+
if insert_after:
statement += " WHERE activity_order > ?"
else:
statement += " WHERE activity_order >= ?"
self.execute(statement, (target_order, ))
-
+
statement = "UPDATE activities SET activity_order = ? WHERE id = ?"
-
+
if insert_after:
self.execute(statement, (target_order + 1, source_id))
else:
self.execute(statement, (target_order, source_id))
-
-
-
+
+
+
def __get_activity_by_name(self, name, category_id = None):
"""get most recent, preferably not deleted activity by it's name"""
-
+
if category_id:
query = """
- SELECT id, deleted from activities
+ SELECT id, deleted from activities
WHERE lower(name) = lower(?)
AND category_id = ?
ORDER BY deleted, id desc
LIMIT 1
"""
-
+
res = self.fetchone(query, (name, category_id))
else:
query = """
- SELECT id, deleted from activities
+ SELECT id, deleted from activities
WHERE lower(name) = lower(?)
ORDER BY deleted, id desc
LIMIT 1
"""
res = self.fetchone(query, (name, ))
-
+
if res:
# if the activity was marked as deleted, ressurect on first call
# and put in the unsorted category
@@ -264,13 +264,13 @@ class Storage(storage.Storage):
WHERE id = ?
"""
self.execute(update, (res['id'], ))
-
+
return res['id']
-
+
return None
def __get_category_by_name(self, name):
- """returns category by it's name"""
+ """returns category by it's name"""
query = """
SELECT id from categories
@@ -278,12 +278,12 @@ class Storage(storage.Storage):
ORDER BY id desc
LIMIT 1
"""
-
+
res = self.fetchone(query, (name, ))
-
+
if res:
return res['id']
-
+
return None
def __get_fact(self, id):
@@ -309,11 +309,11 @@ class Storage(storage.Storage):
def __group_tags(self, facts):
"""put the fact back together and move all the unique tags to an array"""
if not facts: return facts #be it None or whatever
-
+
grouped_facts = []
for fact_id, fact_tags in itertools.groupby(facts, lambda f: f["id"]):
fact_tags = list(fact_tags)
-
+
# first one is as good as the last one
grouped_fact = fact_tags[0]
@@ -322,7 +322,7 @@ class Storage(storage.Storage):
keys = ["id", "start_time", "end_time", "description", "name",
"activity_id", "category", "tag"]
grouped_fact = dict([(key, grouped_fact[key]) for key in keys])
-
+
grouped_fact["tags"] = [ft["tag"] for ft in fact_tags if ft["tag"]]
grouped_facts.append(grouped_fact)
return grouped_facts
@@ -334,7 +334,7 @@ class Storage(storage.Storage):
today = (dt.datetime.now() - dt.timedelta(hours = day_start.hour,
minutes = day_start.minute)).date()
facts = self.__get_facts(today)
-
+
last_activity = None
if facts and facts[-1]["end_time"] == None:
last_activity = facts[-1]
@@ -375,14 +375,14 @@ class Storage(storage.Storage):
start_time,
start_time + dt.timedelta(seconds = 60 * 60 * 12)))
- end_time = None
+ end_time = None
if fact:
if fact["end_time"] and start_time > fact["start_time"]:
#we are in middle of a fact - truncate it to our start
self.execute("UPDATE facts SET end_time=? WHERE id=?",
(start_time, fact["id"]))
-
+
# hamster is second-aware, but the edit dialog naturally is not
# so when an ongoing task is being edited, the seconds get truncated
# and the start time will be before previous task's end time.
@@ -393,7 +393,7 @@ class Storage(storage.Storage):
end_time = fact["start_time"]
return end_time
-
+
def __solve_overlaps(self, start_time, end_time):
"""finds facts that happen in given interval and shifts them to
make room for new fact"""
@@ -401,7 +401,7 @@ class Storage(storage.Storage):
# this function is destructive - can't go with a wildcard
if not end_time or not start_time:
return
-
+
# activities that we are overlapping.
# second OR clause is for elimination - |new fact--|---old-fact--|--new fact|
query = """
@@ -411,19 +411,19 @@ class Storage(storage.Storage):
LEFT JOIN categories c on b.category_id = c.id
WHERE ((start_time < ? and end_time > ?)
OR (start_time < ? and end_time > ?))
-
+
OR ((start_time < ? and start_time > ?)
OR (end_time < ? and end_time > ?))
ORDER BY start_time
"""
conflicts = self.fetchall(query, (start_time, start_time, end_time, end_time,
end_time, start_time, end_time, start_time))
-
+
for fact in conflicts:
# split - truncate until beginning of new entry and create new activity for end
if fact["start_time"] < start_time < fact["end_time"] and \
fact["start_time"] < end_time < fact["end_time"]:
-
+
logging.info("splitting %s" % fact["name"])
self.execute("""UPDATE facts
SET end_time = ?
@@ -447,13 +447,13 @@ class Storage(storage.Storage):
start_time < fact["end_time"] < end_time:
logging.info("eliminating %s" % fact["name"])
self.__remove_fact(fact["id"])
-
+
# overlap start
elif start_time < fact["start_time"] < end_time:
logging.info("Overlapping start of %s" % fact["name"])
self.execute("UPDATE facts SET start_time=? WHERE id=?",
(end_time, fact["id"]))
-
+
# overlap end
elif start_time < fact["end_time"] < end_time:
logging.info("Overlapping end of %s" % fact["name"])
@@ -463,40 +463,40 @@ class Storage(storage.Storage):
def __add_fact(self, activity_name, tags, start_time = None,
end_time = None, category_name = None, description = None):
-
+
activity = stuff.parse_activity_input(activity_name)
-
+
tags = [tag.strip() for tag in tags.split(",") if tag.strip()] # split by comma
descriptions = [tag for tag in tags if len(tag.split(" ")) > 2 or tag.startswith("!")] #extract description
tags = list(set(tags) - set(descriptions)) #remove any found descriptions from tag list
-
+
# TODO - untangle descriptions - allow just one place where to enter them
activity.description = ", ".join(descriptions) # somebody will file bug on "why tags can't be seven words"
tags = self.get_tag_ids(tags) #this will create any missing tags too
-
+
if category_name:
activity.category_name = category_name
if description:
activity.description = description #override
-
+
start_time = activity.start_time or start_time or datetime.datetime.now()
-
+
if start_time > datetime.datetime.now():
return None #no facts in future, please
-
+
start_time = start_time.replace(microsecond = 0)
end_time = activity.end_time or end_time
if end_time:
end_time = end_time.replace(microsecond = 0)
-
+
# now check if maybe there is also a category
category_id = None
if activity.category_name:
category_id = self.__get_category_by_name(activity.category_name)
if not category_id:
category_id = self.__add_category(activity.category_name)
-
+
# try to find activity
activity_id = self.__get_activity_by_name(activity.activity_name,
category_id)
@@ -506,7 +506,7 @@ class Storage(storage.Storage):
# if we are working on +/- current day - check the last_activity
-
+
if (dt.datetime.now() - start_time <= dt.timedelta(days=1)):
previous = self.__get_last_activity()
@@ -516,7 +516,7 @@ class Storage(storage.Storage):
and previous["tags"] == sorted([tag["name"] for tag in tags]) \
and previous["description"] == (description or ""):
return previous
-
+
# otherwise, if not tags nor description is added (extra data
# see if maybe it is too short to qualify as an activity
if not previous["tags"] and not previous["description"] \
@@ -524,7 +524,7 @@ class Storage(storage.Storage):
self.__remove_fact(previous['id'])
start_time = previous['start_time']
else:
- # otherwise stop
+ # otherwise stop
update = """
UPDATE facts
SET end_time = ?
@@ -548,12 +548,12 @@ class Storage(storage.Storage):
self.execute(insert, (activity_id, start_time, end_time, activity.description))
fact_id = self.fetchone("select max(id) as max_id from facts")['max_id']
-
+
#now link tags
insert = ["insert into fact_tags(fact_id, tag_id) values(?, ?)"] * len(tags)
params = [(fact_id, tag["id"]) for tag in tags]
self.execute(insert, params)
-
+
return self.__get_fact(fact_id)
@@ -578,7 +578,7 @@ class Storage(storage.Storage):
# we will be looking in activity names, descriptions, categories and tags
# comma will be treated as OR
# space will be treated as AND or possible join
-
+
# split by comma and then by space and remove all extra spaces
or_bits = [[term.strip().lower().replace("'", "''") #striping removing case sensitivity and escaping quotes in term
@@ -590,10 +590,10 @@ class Storage(storage.Storage):
or lower(b.name) = '%(term)s'
or lower(c.name) = '%(term)s'
or lower(e.name) = '%(term)s' )""" % dict(term = term)
-
+
if or_bits:
search_query = "1<>1 " # will be building OR chain, so start with a false
-
+
for and_bits in or_bits:
if len(and_bits) == 1:
and_query = all_fields(and_bits[0])
@@ -605,30 +605,30 @@ class Storage(storage.Storage):
and_query += "and (%s and %s) or %s" % (all_fields(bit1),
all_fields(bit2),
all_fields("%s %s" % (bit1, bit2)))
-
+
search_query = "%s or (%s) " % (search_query, and_query)
-
+
query = "%s and (%s)" % (query, search_query)
-
-
+
+
query += " ORDER BY a.start_time, e.name"
end_date = end_date or date
from configuration import GconfStore
day_start = GconfStore().get_day_start()
-
+
split_time = day_start
datetime_from = dt.datetime.combine(date, split_time)
datetime_to = dt.datetime.combine(end_date, split_time) + dt.timedelta(days = 1)
-
+
facts = self.fetchall(query, (_("Unsorted"),
datetime_from,
datetime_to))
-
+
#first let's put all tags in an array
facts = self.__group_tags(facts)
-
+
res = []
for fact in facts:
# heuristics to assign tasks to proper days
@@ -701,15 +701,15 @@ class Storage(storage.Storage):
WHERE category_id = ?
AND deleted is null
"""
-
+
# unsorted entries we sort by name - others by ID
if category_id == -1:
query += "ORDER BY lower(a.name)"
else:
query += "ORDER BY a.activity_order"
-
+
activities = self.fetchall(query, (category_id, ))
-
+
else:
query = """
SELECT a.*, b.name as category
@@ -719,7 +719,7 @@ class Storage(storage.Storage):
ORDER BY lower(a.name)
"""
activities = self.fetchall(query)
-
+
return activities
def __get_autocomplete_activities(self):
@@ -734,17 +734,17 @@ class Storage(storage.Storage):
ORDER BY lower(a.name)
"""
activities = self.fetchall(query)
-
+
return activities
def __remove_activity(self, id):
""" check if we have any facts with this activity and behave accordingly
if there are facts - sets activity to deleted = True
else, just remove it"""
-
+
query = "select count(*) as count from facts where activity_id = ?"
bound_facts = self.fetchone(query, (id,))['count']
-
+
if bound_facts > 0:
self.execute("UPDATE activities SET deleted = 1 WHERE id = ?", (id,))
else:
@@ -752,13 +752,13 @@ class Storage(storage.Storage):
def __remove_category(self, id):
"""move all activities to unsorted and remove category"""
-
+
update = "update activities set category_id = -1 where category_id = ?"
self.execute(update, (id, ))
-
+
self.execute("delete from categories where id = ?", (id, ))
-
-
+
+
def __swap_activities(self, id1, priority1, id2, priority2):
""" swaps nearby activities """
# TODO - 2 selects and 2 updates is wrong we could live without selects
@@ -771,7 +771,7 @@ class Storage(storage.Storage):
activity_id = self.__get_activity_by_name(name, category_id)
if activity_id:
return activity_id
-
+
#now do the create bit
category_id = category_id or -1
new_rec = self.fetchone("select max(id) + 1 , max(activity_order) + 1 from activities")
@@ -841,57 +841,57 @@ class Storage(storage.Storage):
con = self.__con or self.connection
cur = self.__cur or con.cursor()
-
+
if isinstance(statement, list) == False: #we kind of think that we will get list of instructions
statement = [statement]
params = [params]
-
+
if isinstance(statement, list):
for i in range(len(statement)):
logging.debug("%s %s" % (statement[i], params[i]))
-
+
res = cur.execute(statement[i], params[i])
if not self.__con:
con.commit()
cur.close()
runtime.register_modification()
-
-
+
+
def start_transaction(self):
# will give some hints to execute not to close or commit anything
self.__con = self.connection
self.__cur = self.__con.cursor()
-
+
def end_transaction(self):
self.__con.commit()
self.__cur.close()
self.__con = None
from configuration import runtime
runtime.register_modification()
-
+
def run_fixtures(self):
self.start_transaction()
-
+
# defaults
work_category = {"name": _("Work"),
"entries": [_("Reading news"),
_("Checking stocks"),
_("Super secret project X"),
_("World domination")]}
-
+
nonwork_category = {"name": _("Day-to-day"),
"entries": [_("Lunch"),
_("Watering flowers"),
_("Doing handstands")]}
-
+
"""upgrade DB to hamster version"""
version = self.fetchone("SELECT version FROM version")["version"]
current_version = 6
if version < 2:
"""moving from fact_date, fact_time to start_time, end_time"""
-
+
self.execute("""
CREATE TABLE facts_new
(id integer primary key,
@@ -899,7 +899,7 @@ class Storage(storage.Storage):
start_time varchar2(12),
end_time varchar2(12))
""")
-
+
self.execute("""
INSERT INTO facts_new
(id, activity_id, start_time)
@@ -921,7 +921,7 @@ class Storage(storage.Storage):
ORDER BY start_time
""")
prev_fact = None
-
+
for fact in facts:
if prev_fact:
if prev_fact['start_date'] == fact['start_date']:
@@ -930,7 +930,7 @@ class Storage(storage.Storage):
else:
#otherwise that's the last entry of the day - remove it
self.execute("DELETE FROM facts WHERE id = ?", (prev_fact["id"],))
-
+
prev_fact = fact
#it was kind of silly not to have datetimes in first place
@@ -942,7 +942,7 @@ class Storage(storage.Storage):
start_time timestamp,
end_time timestamp)
""")
-
+
self.execute("""
INSERT INTO facts_new
(id, activity_id, start_time, end_time)
@@ -989,22 +989,22 @@ class Storage(storage.Storage):
FROM activities
WHERE deleted is null and work=1;
""")['work_activities']
-
+
if work_activities > 0:
self.execute("""
INSERT INTO categories
(id, name, category_order)
VALUES (2, ?, 1);
""", (work_category["name"],))
-
+
# now add category field to activities, before starting the move
self.execute(""" ALTER TABLE activities
ADD COLUMN category_id integer;
""")
-
-
+
+
# starting the move
-
+
# first remove all deleted activities with no instances in facts
self.execute("""
DELETE FROM activities
@@ -1012,25 +1012,25 @@ class Storage(storage.Storage):
AND id not in(select activity_id from facts);
""")
-
+
# moving work / non-work to appropriate categories
# exploit false/true = 0/1 thing
self.execute(""" UPDATE activities
SET category_id = work + 1
WHERE deleted is null
""")
-
- #finally, set category to -1 where there is none
+
+ #finally, set category to -1 where there is none
self.execute(""" UPDATE activities
SET category_id = -1
WHERE category_id is null
""")
-
+
# drop work column and forget value of deleted
# previously deleted records are now unsorted ones
# user will be able to mark them as deleted again, in which case
# they won't appear in autocomplete, or in categories
- # ressurection happens, when user enters the exact same name
+ # ressurection happens, when user enters the exact same name
self.execute("""
CREATE TABLE activities_new (id integer primary key,
name varchar2(500),
@@ -1038,7 +1038,7 @@ class Storage(storage.Storage):
deleted integer,
category_id integer);
""")
-
+
self.execute("""
INSERT INTO activities_new
(id, name, activity_order, category_id)
@@ -1048,7 +1048,7 @@ class Storage(storage.Storage):
self.execute("DROP TABLE activities")
self.execute("ALTER TABLE activities_new RENAME TO activities")
-
+
if version < 5:
self.execute("ALTER TABLE facts add column description varchar2")
@@ -1067,21 +1067,21 @@ class Storage(storage.Storage):
self.execute("CREATE INDEX idx_fact_tags_fact ON fact_tags(fact_id)")
self.execute("CREATE INDEX idx_fact_tags_tag ON fact_tags(tag_id)")
- # at the happy end, update version number
+ # at the happy end, update version number
if version < current_version:
#lock down current version
- self.execute("UPDATE version SET version = %d" % current_version)
-
+ self.execute("UPDATE version SET version = %d" % current_version)
+
"""we start with an empty database and then populate with default
values. This way defaults can be localized!"""
-
+
category_count = self.fetchone("select count(*) from categories")[0]
-
+
if category_count == 0:
work_cat_id = self.__add_category(work_category["name"])
for entry in work_category["entries"]:
self.__add_activity(entry, work_cat_id)
-
+
nonwork_cat_id = self.__add_category(nonwork_category["name"])
for entry in nonwork_category["entries"]:
self.__add_activity(entry, nonwork_cat_id)
diff --git a/hamster/edit_activity.py b/hamster/edit_activity.py
index ab3f799..bd361c7 100644
--- a/hamster/edit_activity.py
+++ b/hamster/edit_activity.py
@@ -36,31 +36,31 @@ class CustomFactController:
self.parent, self.fact_id = parent, fact_id
start_date, end_date = None, None
-
+
#TODO - should somehow hint that time is not welcome here
self.new_name = widgets.ActivityEntry()
self.get_widget("activity_box").add(self.new_name)
-
+
self.new_tags = widgets.TagsEntry()
self.get_widget("tags_box").add(self.new_tags)
-
+
if fact_id:
fact = runtime.storage.get_fact(fact_id)
label = fact['name']
if fact['category'] != _("Unsorted"):
label += "@%s" % fact['category']
-
+
self.new_name.set_text(label)
-
+
if fact['description']:
fact['tags'].append(fact['description']) #same edit field
self.new_tags.set_text(", ".join(fact['tags']))
-
-
+
+
start_date = fact["start_time"]
end_date = fact["end_time"]
-
+
self.get_widget("save_button").set_label("gtk-save")
self.window.set_title(_("Update activity"))
@@ -99,7 +99,7 @@ class CustomFactController:
self.start_time = widgets.TimeInput(start_date)
self.get_widget("start_time_placeholder").add(self.start_time)
-
+
self.end_time = widgets.TimeInput(end_date, start_date)
self.get_widget("end_time_placeholder").add(self.end_time)
self.set_end_date_label(end_date)
@@ -117,7 +117,7 @@ class CustomFactController:
self.new_name.connect("changed", self.on_new_name_changed)
self.end_time.connect("time-entered", self.on_end_time_entered)
self._gui.connect_signals(self)
-
+
self.window.show_all()
def update_time(self, start_time, end_time):
@@ -126,7 +126,7 @@ class CustomFactController:
self.end_time.set_time(end_time)
self.set_end_date_label(end_time)
-
+
def draw_preview(self, date, highlight = None):
day_facts = runtime.storage.get_facts(date)
self.dayline.draw(day_facts, highlight)
@@ -153,20 +153,20 @@ class CustomFactController:
time, date = end_time, end_date
else:
time, date = start_time, start_date
-
+
if time and date:
return dt.datetime.combine(date, time.time())
else:
return None
-
+
def on_save_button_clicked(self, button):
activity = self.new_name.get_text().decode("utf-8")
-
+
if not activity:
return False
tags = self.new_tags.get_text().decode('utf8', 'replace')
-
+
start_time = self._get_datetime("start")
if self.get_widget("in_progress").get_active():
@@ -182,17 +182,17 @@ class CustomFactController:
# hide panel only on add - on update user will want to see changes
- if not self.fact_id:
+ if not self.fact_id:
runtime.dispatcher.dispatch('panel_visible', False)
-
+
self.close_window()
-
+
def on_activity_list_key_pressed(self, entry, event):
#treating tab as keydown to be able to cycle through available values
if event.keyval == gtk.keysyms.Tab:
event.keyval = gtk.keysyms.Down
return False
-
+
def on_in_progress_toggled(self, check):
sensitive = not check.get_active()
self.end_time.set_sensitive(sensitive)
@@ -203,7 +203,7 @@ class CustomFactController:
def on_cancel_clicked(self, button):
self.close_window()
-
+
def on_new_name_changed(self, combo):
self.validate_fields()
@@ -219,13 +219,13 @@ class CustomFactController:
self.end_time.set_start_time(start_time)
self.validate_fields()
self.end_time.grab_focus()
-
+
def on_end_time_entered(self, widget):
self.validate_fields()
-
+
def set_end_date_label(self, some_date):
self.get_widget("end_date_label").set_text(some_date.strftime("%x"))
-
+
def validate_fields(self, widget = None):
activity_text = self.new_name.get_text().decode('utf8', 'replace')
start_time = self._get_datetime("start")
@@ -236,8 +236,8 @@ class CustomFactController:
# make sure we are within 24 hours of start time
end_time -= dt.timedelta(days=(end_time - start_time).days)
-
- self.draw_preview(start_time.date(), [start_time, end_time])
+
+ self.draw_preview(start_time.date(), [start_time, end_time])
else:
self.draw_preview(dt.datetime.today().date(), [dt.datetime.now(),
dt.datetime.now()])
@@ -252,9 +252,9 @@ class CustomFactController:
def on_window_key_pressed(self, tree, event_key):
if (event_key.keyval == gtk.keysyms.Escape
- or (event_key.keyval == gtk.keysyms.w
+ or (event_key.keyval == gtk.keysyms.w
and event_key.state & gtk.gdk.CONTROL_MASK)):
-
+
if self.start_date.popup.get_property("visible") or \
self.start_time.popup.get_property("visible") or \
self.end_time.popup.get_property("visible") or \
@@ -262,10 +262,10 @@ class CustomFactController:
self.new_tags.popup.get_property("visible"):
return False
- self.close_window()
+ self.close_window()
def on_close(self, widget, event):
- self.close_window()
+ self.close_window()
def close_window(self):
self.window.destroy()
diff --git a/hamster/eds.py b/hamster/eds.py
index 4a51abe..dc04015 100644
--- a/hamster/eds.py
+++ b/hamster/eds.py
@@ -31,7 +31,7 @@ except:
def get_eds_tasks():
if EDS_AVAILABLE == False:
return []
-
+
try:
sources = ecal.list_task_sources()
tasks = []
diff --git a/hamster/graphics.py b/hamster/graphics.py
index 0186800..46bd545 100644
--- a/hamster/graphics.py
+++ b/hamster/graphics.py
@@ -44,7 +44,7 @@ class Colors(object):
color = [c / 255.0 for c in color]
return color
-
+
@staticmethod
def rgb(color):
return [c * 255 for c in Colors.color(color)]
@@ -80,12 +80,12 @@ class Area(gtk.DrawingArea):
self.context, self.layout = None, None
self.width, self.height = None, None
self.__prev_mouse_regions = None
-
+
self.tweener = pytweener.Tweener(0.4, pytweener.Easing.Cubic.easeInOut)
self.framerate = 80
self.last_frame_time = None
self.__animating = False
-
+
self.mouse_drag = (None, None)
def on_expose(self):
@@ -99,15 +99,15 @@ class Area(gtk.DrawingArea):
self.__animating = True
self.last_frame_time = dt.datetime.now()
gobject.timeout_add(1000 / self.framerate, self.__interpolate)
-
+
""" animation bits """
def __interpolate(self):
self.__animating = self.tweener.hasTweens()
if not self.window: #will wait until window comes
return self.__animating
-
-
+
+
time_since_start = (dt.datetime.now() - self.last_frame_time).microseconds / 1000000.0
self.tweener.update(time_since_start)
@@ -122,10 +122,10 @@ class Area(gtk.DrawingArea):
if easing: params["tweenType"] = easing # if none will fallback to tweener default
if callback: params["onCompleteFunction"] = callback
self.tweener.addTween(object, **params)
-
+
if instant:
self.redraw_canvas()
-
+
""" drawing on canvas bits """
def draw_rect(self, x, y, w, h, corner_radius = 0):
@@ -139,7 +139,7 @@ class Area(gtk.DrawingArea):
x2, y2 = x + w, y + h
half_corner = corner_radius / 2
-
+
self.context.move_to(x + corner_radius, y);
self.context.line_to(x2 - corner_radius, y);
# top-right
@@ -170,7 +170,7 @@ class Area(gtk.DrawingArea):
if color:
self.set_color(color, opacity)
self.context.rectangle(x, y, w, h)
-
+
def fill_area(self, x, y, w, h, color, opacity = 0):
self.rectangle(x, y, w, h, color, opacity)
self.context.fill()
@@ -179,7 +179,7 @@ class Area(gtk.DrawingArea):
# sets text and returns width and height of the layout
self.layout.set_text(text)
return self.layout.get_pixel_size()
-
+
def set_color(self, color, opacity = None):
color = Colors.color(color) #parse whatever we have there into a normalized triplet
@@ -212,7 +212,7 @@ class Area(gtk.DrawingArea):
self.layout.set_font_description(default_font)
alloc = self.get_allocation() #x, y, width, height
self.width, self.height = alloc.width, alloc.height
-
+
self.mouse_regions = [] #reset since these can move in each redraw
self.on_expose()
@@ -225,7 +225,7 @@ class Area(gtk.DrawingArea):
x = event.x
y = event.y
state = event.state
-
+
self.emit("mouse-move", (x, y), state)
if not self.mouse_regions:
@@ -235,7 +235,7 @@ class Area(gtk.DrawingArea):
for region in self.mouse_regions:
if region[0] < x < region[2] and region[1] < y < region[3]:
mouse_regions.append(region[4])
-
+
if mouse_regions:
area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
else:
@@ -272,7 +272,7 @@ class Area(gtk.DrawingArea):
x = event.x
y = event.y
state = event.state
-
+
drag_distance = 5
if self.mouse_drag and (self.mouse_drag[0] - x) ** 2 + (self.mouse_drag[1] - y) ** 2 < drag_distance ** 2:
#if the drag is less than the drag distance, then we have a click
@@ -281,7 +281,7 @@ class Area(gtk.DrawingArea):
if not self.mouse_regions:
return
-
+
mouse_regions = []
for region in self.mouse_regions:
if region[0] < x < region[2] and region[1] < y < region[3]:
@@ -299,31 +299,31 @@ class SampleArea(Area):
self.rect_width, self.rect_height = 90, 90
self.text_y = -100
-
-
+
+
def on_expose(self):
# on expose is called when we are ready to draw
-
+
# fill_area is just a shortcut function
# feel free to use self.context. move_to, line_to and others
self.font_size = 32
self.layout.set_text("Hello, World!")
-
+
self.draw_rect(round(self.rect_x),
round(self.rect_y),
self.rect_width,
self.rect_height,
10)
-
+
self.set_color("#ff00ff")
self.context.fill()
self.context.move_to((self.width - self.layout.get_pixel_size()[0]) / 2,
self.text_y)
-
+
self.set_color("#333")
self.context.show_layout(self.layout)
-
+
class BasicWindow:
def __init__(self):
@@ -331,20 +331,20 @@ class BasicWindow:
window.set_title("Graphics Module")
window.set_size_request(300, 300)
window.connect("delete_event", lambda *args: gtk.main_quit())
-
+
self.graphic = SampleArea()
-
+
box = gtk.VBox()
box.pack_start(self.graphic)
-
+
button = gtk.Button("Hello")
button.connect("clicked", self.on_go_clicked)
box.add_with_properties(button, "expand", False)
-
+
window.add(box)
window.show_all()
-
+
# drop the hello on init
self.graphic.animate(self.graphic,
dict(text_y = 120),
@@ -354,13 +354,13 @@ class BasicWindow:
def on_go_clicked(self, widget):
import random
-
+
# set x and y to random position within the drawing area
x = round(min(random.random() * self.graphic.width,
self.graphic.width - self.graphic.rect_width))
y = round(min(random.random() * self.graphic.height,
self.graphic.height - self.graphic.rect_height))
-
+
# here we call the animate function with parameters we would like to change
# the easing functions outside graphics module can be accessed via
# graphics.Easing
@@ -373,4 +373,3 @@ class BasicWindow:
if __name__ == "__main__":
example = BasicWindow()
gtk.main()
-
\ No newline at end of file
diff --git a/hamster/hamster-applet.py b/hamster/hamster-applet.py
index d44231d..59194fb 100755
--- a/hamster/hamster-applet.py
+++ b/hamster/hamster-applet.py
@@ -32,7 +32,7 @@ def applet_factory(applet, iid):
applet.connect("destroy", on_destroy)
applet.set_applet_flags(gnomeapplet.EXPAND_MINOR)
- from hamster.applet import HamsterApplet
+ from hamster.applet import HamsterApplet
hamster_applet = HamsterApplet(applet)
applet.show_all()
@@ -43,13 +43,13 @@ def applet_factory(applet, iid):
def on_destroy(event):
from hamster.configuration import GconfStore, runtime
config = GconfStore()
-
+
# handle config option to stop tracking on shutdown
if config.get_stop_on_shutdown():
last_activity = runtime.storage.get_last_activity()
if last_activity and last_activity['end_time'] is None:
runtime.storage.touch_fact(last_activity)
-
+
if gtk.main_level():
gtk.main_quit()
@@ -69,7 +69,7 @@ if __name__ == "__main__":
dest="debug",
default=False,
help="set log level to debug")
-
+
#these two come from bonobo
parser.add_option("--oaf-activate-iid")
parser.add_option("--oaf-ior-fd")
@@ -96,22 +96,22 @@ if __name__ == "__main__":
logging.info("Running from source folder, modifying PYTHONPATH")
sys.path.insert(0, os.path.join(name, "hamster", "keybinder", ".libs"))
sys.path.insert(0, name)
-
+
# Now the path is set, import our applet
from hamster import defs
from hamster.configuration import runtime, dialogs
-
+
# Setup i18n
- locale_dir = os.path.abspath(os.path.join(defs.DATA_DIR, "locale"))
+ locale_dir = os.path.abspath(os.path.join(defs.DATA_DIR, "locale"))
for module in (gettext, locale):
module.bindtextdomain('hamster-applet', locale_dir)
module.textdomain('hamster-applet')
-
+
if hasattr(module, 'bind_textdomain_codeset'):
module.bind_textdomain_codeset('hamster-applet','UTF-8')
-
+
gtk.window_set_default_icon_name("hamster-applet")
-
+
if options.start_window or options.standalone:
gobject.set_application_name("hamster-applet")
if (options.start_window or "").startswith("over"):
@@ -127,16 +127,16 @@ if __name__ == "__main__":
else: #default to main applet
gnome.init(defs.PACKAGE, defs.VERSION)
-
+
app = gtk.Window(gtk.WINDOW_TOPLEVEL)
app.set_title(_(u"Time Tracker"))
-
+
applet = gnomeapplet.Applet()
applet_factory(applet, None)
applet.reparent(app)
app.show_all()
- gtk.main()
+ gtk.main()
else:
gnomeapplet.bonobo_factory(
"OAFIID:Hamster_Applet_Factory",
diff --git a/hamster/hamsterdbus.py b/hamster/hamsterdbus.py
index eed86a8..26c8f29 100644
--- a/hamster/hamsterdbus.py
+++ b/hamster/hamsterdbus.py
@@ -48,7 +48,7 @@ class HamsterDbusController(dbus.service.Object):
@staticmethod
def to_dbus_fact(fact):
- """Perform the conversion between fact database query and
+ """Perform the conversion between fact database query and
dbus supported data types
"""
if not fact:
@@ -57,17 +57,17 @@ class HamsterDbusController(dbus.service.Object):
fact = dict(fact)
for key in fact.keys():
fact[key] = fact[key] or 0
-
+
# make sure we return correct type where strings are expected
if not fact[key] and key in ('name', 'category', 'description'):
fact[key] = ''
-
+
# convert times to gmtime
if isinstance(fact[key], datetime.datetime):
fact[key] = timegm(fact[key].timetuple())
-
+
return fact
-
+
@dbus.service.method(HAMSTER_URI, out_signature='a{sv}')
def GetCurrentFact(self):
@@ -169,10 +169,10 @@ class HamsterDbusController(dbus.service.Object):
Parameters:
s activity: Activity name with optional category and/or description
in the form 'activity_name[ category_name][,description]'
- Activity and matching category will be refered or created
+ Activity and matching category will be refered or created
on the fly.
u start_time: Seconds since epoch (timestamp). Use 0 for 'now'
- u end_time: Seconds since epoch (timestamp).
+ u end_time: Seconds since epoch (timestamp).
Use 0 for 'in progress task'
"""
#TODO: Assert start > end ?
diff --git a/hamster/idle.py b/hamster/idle.py
index 0808731..1fbeefd 100644
--- a/hamster/idle.py
+++ b/hamster/idle.py
@@ -84,7 +84,7 @@ class DbusIdleListener(object):
idle_state = message.get_args_list()[0]
if idle_state:
self.idle_from = dt.datetime.now()
-
+
# from gnome screensaver 2.24 to 2.28 they have switched
# configuration keys and signal types.
# luckily we can determine key by signal type
@@ -121,9 +121,9 @@ class DbusIdleListener(object):
# ActiveChanged and SessionIdle signals
logging.debug("Screen Lock Requested")
self.screen_locked = True
-
+
return
-
+
def getIdleFrom(self):
if not self.idle_from:
diff --git a/hamster/preferences.py b/hamster/preferences.py
index 3fcca83..d234ba1 100755
--- a/hamster/preferences.py
+++ b/hamster/preferences.py
@@ -51,7 +51,7 @@ class CategoryStore(gtk.ListStore):
self.append([category['id'],
category['name'],
category['category_order']])
-
+
self.unsorted_category = self.append([-1, _("Unsorted"), 999]) # all activities without category
@@ -63,12 +63,12 @@ class ActivityStore(gtk.ListStore):
def load(self, category_id):
""" Loads activity list from database, ordered by
activity_order """
-
+
self.clear()
if category_id is None:
return
-
+
activity_list = runtime.storage.get_activities(category_id)
for activity in activity_list:
@@ -90,8 +90,8 @@ class PreferencesEditor:
('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_APP, 0),
]
-
-
+
+
def __init__(self, parent = None):
self.parent = parent
self._gui = stuff.load_ui_file("preferences.ui")
@@ -169,10 +169,10 @@ class PreferencesEditor:
#select first category
selection = self.category_tree.get_selection()
selection.select_path((0,))
-
+
self.prev_selected_activity = None
self.prev_selected_category = None
-
+
# disable notification thing if pynotify is not available
try:
import pynotify
@@ -190,10 +190,10 @@ class PreferencesEditor:
self.get_widget("notify_on_idle").set_active(self.config.get_notify_on_idle())
self.day_start.set_time(self.config.get_day_start())
-
+
self.tags = [tag["name"] for tag in runtime.storage.get_tags(autocomplete=True)]
self.get_widget("autocomplete_tags").set_text(", ".join(self.tags))
-
+
def on_autocomplete_tags_view_focus_out_event(self, view, event):
buf = self.get_widget("autocomplete_tags")
@@ -202,10 +202,10 @@ class PreferencesEditor:
if updated_tags == self.tags:
return
-
+
runtime.storage.update_autocomplete_tags(updated_tags)
-
-
+
+
def drag_data_get_data(self, treeview, context, selection, target_id,
etime):
treeselection = treeview.get_selection()
@@ -220,7 +220,7 @@ class PreferencesEditor:
if row[0] == id:
self.activity_tree.set_cursor((i, ))
i += 1
-
+
def select_category(self, id):
model = self.category_tree.get_model()
i = 0
@@ -228,7 +228,7 @@ class PreferencesEditor:
if row[0] == id:
self.category_tree.set_cursor((i, ))
i += 1
-
+
def on_activity_list_drag_motion(self, treeview, drag_context, x, y, eventtime):
self.prev_selected_activity = None
try:
@@ -266,7 +266,7 @@ class PreferencesEditor:
else:
treeview.enable_model_drag_dest([drop_no], gtk.gdk.ACTION_MOVE)
-
+
def drag_data_received_data(self, treeview, context, x, y, selection,
info, etime):
model = treeview.get_model()
@@ -293,7 +293,7 @@ class PreferencesEditor:
self.activity_store.load(self._get_selected_category())
self.select_activity(int(data))
-
+
return
def on_category_drop(self, treeview, context, x, y, selection,
@@ -301,12 +301,12 @@ class PreferencesEditor:
model = self.category_tree.get_model()
data = selection.data
drop_info = treeview.get_dest_row_at_pos(x, y)
-
+
if drop_info:
path, position = drop_info
iter = model.get_iter(path)
changed = runtime.storage.change_category(int(data), model[iter][0])
-
+
context.finish(changed, True, etime)
else:
context.finish(False, True, etime)
@@ -354,7 +354,7 @@ class PreferencesEditor:
def activity_name_edited_cb(self, cell, path, new_text, model):
id = model[path][0]
category_id = model[path][2]
-
+
#look for dupes
activities = runtime.storage.get_activities(category_id)
for activity in activities:
@@ -363,15 +363,15 @@ class PreferencesEditor:
self.activity_store.remove(model.get_iter(path))
self.select_activity(activity['id'])
return False
-
-
+
+
if id == -1: #new activity -> add
model[path][0] = runtime.storage.add_activity(new_text.decode("utf-8"), category_id)
else: #existing activity -> update
runtime.storage.update_activity(id, new_text.decode("utf-8"), category_id)
model[path][1] = new_text
return True
-
+
def category_changed_cb(self, selection, model):
""" enables and disables action buttons depending on selected item """
@@ -384,7 +384,7 @@ class PreferencesEditor:
id = model[iter][0]
self.activity_store.load(model[iter][0])
-
+
#start with nothing
self.get_widget('activity_up').set_sensitive(False)
self.get_widget('activity_down').set_sensitive(False)
@@ -401,12 +401,12 @@ class PreferencesEditor:
return model[iter][0]
else:
return None
-
+
def activity_changed(self, selection, model):
""" enables and disables action buttons depending on selected item """
(model, iter) = selection.get_selected()
-
+
# treat any selected case
unsorted_selected = self._get_selected_category() == -1
self.get_widget('activity_up').set_sensitive(False)
@@ -414,7 +414,7 @@ class PreferencesEditor:
self.get_widget('activity_edit').set_sensitive(iter != None)
self.get_widget('activity_remove').set_sensitive(iter != None)
-
+
if iter != None and not unsorted_selected:
first_item = model.get_path(iter) == (0,)
self.get_widget('activity_up').set_sensitive(not first_item)
@@ -438,7 +438,7 @@ class PreferencesEditor:
removable_id = model[iter][0]
model.remove(iter)
return removable_id
-
+
def unsorted_painter(self, column, cell, model, iter):
cell_id = model.get_value(iter, 0)
cell_text = model.get_value(iter, 1)
@@ -447,12 +447,12 @@ class PreferencesEditor:
cell.set_property('markup', text)
else:
cell.set_property('text', cell_text)
-
+
return
def on_activity_list_button_pressed(self, tree, event):
self.activityCell.set_property("editable", False)
-
+
def on_activity_list_button_released(self, tree, event):
if event.button == 1 and tree.get_path_at_pos(int(event.x), int(event.y)):
@@ -462,12 +462,12 @@ class PreferencesEditor:
if self.prev_selected_activity == path:
self.activityCell.set_property("editable", True)
tree.set_cursor(path, focus_column = self.activityColumn, start_editing = True)
-
+
self.prev_selected_activity = path
-
+
def on_category_list_button_pressed(self, tree, event):
self.activityCell.set_property("editable", False)
-
+
def on_category_list_button_released(self, tree, event):
if event.button == 1 and tree.get_path_at_pos(int(event.x), int(event.y)):
# Get treeview path.
@@ -479,10 +479,10 @@ class PreferencesEditor:
tree.set_cursor(path, focus_column = self.categoryColumn, start_editing = True)
else:
self.categoryCell.set_property("editable", False)
-
+
self.prev_selected_category = path
-
+
def on_activity_remove_clicked(self, button):
self.remove_current_activity()
@@ -494,7 +494,7 @@ class PreferencesEditor:
(model, iter) = selection.get_selected()
path = model.get_path(iter)[0]
self.activity_tree.set_cursor(path, focus_column = self.activityColumn, start_editing = True)
-
+
"""keyboard events"""
@@ -521,7 +521,7 @@ class PreferencesEditor:
def on_category_remove_clicked(self, button):
self.remove_current_category()
-
+
def on_category_edit_clicked(self, button):
self.categoryCell.set_property("editable", True)
@@ -529,14 +529,14 @@ class PreferencesEditor:
(model, iter) = selection.get_selected()
path = model.get_path(iter)[0]
self.category_tree.set_cursor(path, focus_column = self.categoryColumn, start_editing = True)
-
-
+
+
def on_category_list_key_pressed(self, tree, event_key):
key = event_key.keyval
-
+
if self._get_selected_category() == -1:
return #ignoring unsorted category
-
+
selection = tree.get_selection()
(model, iter) = selection.get_selected()
@@ -573,12 +573,12 @@ class PreferencesEditor:
self.categoryCell.set_property("editable", False)
return
- self.close_window()
+ self.close_window()
"""button events"""
def on_category_add_clicked(self, button):
""" appends row, jumps to it and allows user to input name """
-
+
new_category = self.category_store.insert_before(self.category_store.unsorted_category,
[-2, _(u"New category"), -1])
@@ -592,7 +592,7 @@ class PreferencesEditor:
def on_activity_add_clicked(self, button):
""" appends row, jumps to it and allows user to input name """
category_id = self._get_selected_category()
-
+
new_activity = self.activity_store.append([-1, _(u"New activity"), category_id, -1])
(model, iter) = self.selection.get_selected()
@@ -632,8 +632,8 @@ class PreferencesEditor:
self.close_window()
def on_close(self, widget, event):
- self.close_window()
-
+ self.close_window()
+
def close_window(self):
if not self.parent:
gtk.main_quit()
@@ -657,14 +657,14 @@ class PreferencesEditor:
else:
# notify interval slider value label
label = _(u"Never")
-
+
return label
-
+
def on_notify_interval_value_changed(self, scale):
value = int(scale.get_value())
self.config.set_notify_interval(value)
self.get_widget("notify_on_idle").set_sensitive(value <= 120)
-
+
def on_keybinding_changed(self, textbox):
self.config.set_keybinding(textbox.get_text().decode('utf8', 'replace'))
@@ -677,4 +677,4 @@ class PreferencesEditor:
def on_preferences_window_destroy(self, window):
self.window = None
-
+
diff --git a/hamster/pytweener.py b/hamster/pytweener.py
index 575dd31..c8c3876 100644
--- a/hamster/pytweener.py
+++ b/hamster/pytweener.py
@@ -17,28 +17,28 @@ class Tweener(object):
self.currentTweens = {}
self.defaultTweenType = tween or Easing.Cubic.easeInOut
self.defaultDuration = default_duration or 1.0
-
+
def hasTweens(self):
return len(self.currentTweens) > 0
-
-
+
+
def addTween(self, obj, **kwargs):
""" addTween( object, **kwargs) -> tweenObject or False
-
+
Example:
tweener.addTween( myRocket, throttle=50, setThrust=400, tweenTime=5.0, tweenType=tweener.OUT_QUAD )
-
+
You must first specify an object, and at least one property or function with a corresponding
change value. The tween will throw an error if you specify an attribute the object does
not possess. Also the data types of the change and the initial value of the tweened item
must match. If you specify a 'set' -type function, the tweener will attempt to get the
- starting value by call the corresponding 'get' function on the object. If you specify a
- property, the tweener will read the current state as the starting value. You add both
+ starting value by call the corresponding 'get' function on the object. If you specify a
+ property, the tweener will read the current state as the starting value. You add both
functions and property changes to the same tween.
-
+
in addition to any properties you specify on the object, these keywords do additional
setup of the tween.
-
+
tweenTime = the duration of the motion
tweenType = one of the predefined tweening equations or your own function
onComplete = specify a function to call on completion of the tween
@@ -48,37 +48,37 @@ class Tweener(object):
if "tweenTime" in kwargs:
t_time = kwargs.pop("tweenTime")
else: t_time = self.defaultDuration
-
+
if "tweenType" in kwargs:
t_type = kwargs.pop("tweenType")
else: t_type = self.defaultTweenType
-
+
if "onComplete" in kwargs:
t_completeFunc = kwargs.pop("onComplete")
else: t_completeFunc = None
-
+
if "onUpdate" in kwargs:
t_updateFunc = kwargs.pop("onUpdate")
else: t_updateFunc = None
-
+
if "tweenDelay" in kwargs:
t_delay = kwargs.pop("tweenDelay")
else: t_delay = 0
-
+
tw = Tween( obj, t_time, t_type, t_completeFunc, t_updateFunc, t_delay, **kwargs )
- if tw:
+ if tw:
tweenlist = self.currentTweens.setdefault(obj, [])
tweenlist.append(tw)
return tw
-
+
def removeTween(self, tweenObj):
tweenObj.complete = True
-
+
def getTweensAffectingObject(self, obj):
"""Get a list of all tweens acting on the specified object
Useful for manipulating tweens on the fly"""
return self.currentTweens.get(obj, [])
-
+
def killTweensOf(self, obj):
"""Stop tweening an object, without completing the motion
or firing the completeFunction"""
@@ -87,14 +87,14 @@ class Tweener(object):
except:
pass
-
+
def finish(self):
#go to last frame for all tweens
for obj in self.currentTweens:
for t in self.currentTweens[obj]:
t.update(t.duration)
self.currentTweens = {}
-
+
def update(self, timeSinceLastFrame):
for obj in self.currentTweens.keys():
# updating tweens from last to first and deleting while at it
@@ -103,25 +103,25 @@ class Tweener(object):
t.update(timeSinceLastFrame)
if t.complete:
del self.currentTweens[obj][i]
-
+
if not self.currentTweens[obj]:
del self.currentTweens[obj]
-
+
class Tween(object):
__slots__ = ['duration', 'delay', 'target', 'tween', 'tweenables', 'delta',
'target', 'ease', 'tweenables', 'delta', 'completeFunction',
'updateFunction', 'complete', 'paused']
-
+
def __init__(self, obj, duration, easing, on_complete, on_update, delay, **kwargs):
"""Tween object use Tweener.addTween( ... ) to create"""
self.duration = duration
self.delay = delay
self.target = obj
self.ease = easing
-
+
# list of (property, start_value, end_value)
self.tweenables = [(k, self.target.__dict__[k], v) for k, v in kwargs.items()]
-
+
self.delta = 0
self.completeFunction = on_complete
self.updateFunction = on_update
@@ -135,20 +135,20 @@ class Tween(object):
or tween.pause() which pauses indefinitely."""
self.paused = True
self.delay = numSeconds
-
+
def resume( self ):
"""Resume from pause"""
if self.paused:
self.paused=False
-
+
def update(self, ptime):
"""Update tween with the time since the last frame
if there is an update callback, it is always called
whether the tween is running or paused"""
-
+
if self.complete:
return
-
+
if self.paused:
if self.delay > 0:
self.delay = max( 0, self.delay - ptime )
@@ -158,20 +158,20 @@ class Tween(object):
if self.updateFunction:
self.updateFunction()
return
-
+
self.delta = self.delta + ptime
if self.delta > self.duration:
self.delta = self.duration
-
+
for prop, start_value, end_value in self.tweenables:
self.target.__dict__[prop] = self.ease(self.delta, start_value, end_value - start_value, self.duration)
-
+
if self.delta == self.duration:
self.complete = True
if self.completeFunction:
self.completeFunction()
-
+
if self.updateFunction:
self.updateFunction()
@@ -181,7 +181,7 @@ There certainly is room for improvement, but wanted to keep the readability to s
================================================================================
Easing Equations
- (c) 2003 Robert Penner, all rights reserved.
+ (c) 2003 Robert Penner, all rights reserved.
This work is subject to the terms in
http://www.robertpenner.com/easing_terms_of_use.html.
================================================================================
@@ -231,7 +231,7 @@ class Easing(object):
def easeInOut (t, b, c, d, s = 1.70158):
t = t / (d * 0.5)
s = s * 1.525
-
+
if t < 1:
return c * 0.5 * (t * t * ((s + 1) * t - s)) + b
@@ -266,7 +266,7 @@ class Easing(object):
return Easing.Bounce.easeOut (t * 2 -d, 0, c, d) * .5 + c*.5 + b
-
+
class Circ(object):
@staticmethod
def easeIn (t, b, c, d):
@@ -283,7 +283,7 @@ class Easing(object):
t = t / (d * 0.5)
if t < 1:
return -c * 0.5 * (math.sqrt(1 - t * t) - 1) + b
-
+
t = t - 2
return c*0.5 * (math.sqrt(1 - t * t) + 1) + b
@@ -304,7 +304,7 @@ class Easing(object):
t = t / (d * 0.5)
if t < 1:
return c * 0.5 * t * t * t + b
-
+
t = t - 2
return c * 0.5 * (t * t * t + 2) + b
@@ -314,9 +314,9 @@ class Easing(object):
def easeIn (t, b, c, d, a = 0, p = 0):
if t==0: return b
- t = t / d
+ t = t / d
if t == 1: return b+c
-
+
if not p: p = d * .3;
if not a or a < abs(c):
@@ -324,18 +324,18 @@ class Easing(object):
s = p / 4
else:
s = p / (2 * math.pi) * math.asin(c / a)
-
- t = t - 1
+
+ t = t - 1
return - (a * math.pow(2, 10 * t) * math.sin((t*d-s) * (2 * math.pi) / p)) + b
@staticmethod
def easeOut (t, b, c, d, a = 0, p = 0):
if t == 0: return b
-
+
t = t / d
if (t == 1): return b + c
-
+
if not p: p = d * .3;
if not a or a < abs(c):
@@ -343,17 +343,17 @@ class Easing(object):
s = p / 4
else:
s = p / (2 * math.pi) * math.asin(c / a)
-
+
return a * math.pow(2,-10 * t) * math.sin((t * d - s) * (2 * math.pi) / p) + c + b
@staticmethod
def easeInOut (t, b, c, d, a = 0, p = 0):
if t == 0: return b
-
+
t = t / (d * 0.5)
if t == 2: return b + c
-
+
if not p: p = d * (.3 * 1.5)
if not a or a < abs(c):
@@ -361,11 +361,11 @@ class Easing(object):
s = p / 4
else:
s = p / (2 * math.pi) * math.asin(c / a)
-
+
if (t < 1):
t = t - 1
return -.5 * (a * math.pow(2, 10 * t) * math.sin((t * d - s) * (2 * math.pi) / p)) + b
-
+
t = t - 1
return a * math.pow(2, -10 * t) * math.sin((t * d - s) * (2 * math.pi) / p) * .5 + c + b
@@ -393,10 +393,10 @@ class Easing(object):
return b+c
t = t / (d * 0.5)
-
+
if t < 1:
return c * 0.5 * math.pow(2, 10 * (t - 1)) + b
-
+
return c * 0.5 * (-math.pow(2, -10 * (t - 1)) + 2) + b
@@ -434,7 +434,7 @@ class Easing(object):
t = t / (d * 0.5)
if t < 1:
return c * 0.5 * t * t + b
-
+
t = t - 1
return -c * 0.5 * (t * (t - 2) - 1) + b
@@ -455,11 +455,11 @@ class Easing(object):
t = t / (d * 0.5)
if t < 1:
return c * 0.5 * t * t * t * t + b
-
+
t = t - 2
return -c * 0.5 * (t * t * t * t - 2) + b
-
+
class Quint(object):
@staticmethod
def easeIn (t, b, c, d):
@@ -476,7 +476,7 @@ class Easing(object):
t = t / (d * 0.5)
if t < 1:
return c * 0.5 * t * t * t * t * t + b
-
+
t = t - 2
return c * 0.5 * (t * t * t * t * t + 2) + b
@@ -506,10 +506,10 @@ class Easing(object):
@staticmethod
def easeInOut(t, b, c, d):
t = t / (d * 0.5)
-
+
if t < 1:
return c * 0.5 * t * t * t * t * t + b
-
+
t = t - 2
return c * 0.5 * (t * t * t * t * t + 2) + b
@@ -532,7 +532,7 @@ if __name__ == "__main__":
total = dt.datetime.now()
-
+
t = dt.datetime.now()
for i, o in enumerate(objects):
tweener.addTween(o, a = i, b = i, c = i, tweenTime = 1.0)
@@ -545,7 +545,7 @@ if __name__ == "__main__":
print "update", dt.datetime.now() - t
t = dt.datetime.now()
-
+
for i in range(10):
for i, o in enumerate(objects):
tweener.killTweensOf(o)
@@ -553,5 +553,5 @@ if __name__ == "__main__":
print "kill-add", dt.datetime.now() - t
print "total", dt.datetime.now() - total
-
+
diff --git a/hamster/reports.py b/hamster/reports.py
index cd740b1..58a900a 100644
--- a/hamster/reports.py
+++ b/hamster/reports.py
@@ -27,7 +27,7 @@ from hamster.i18n import C_
def simple(facts, start_date, end_date, format, path):
report_path = stuff.locale_from_utf8(path)
-
+
if format == "tsv":
writer = TSVWriter(report_path)
elif format == "xml":
@@ -45,52 +45,52 @@ class ReportWriter(object):
def __init__(self, path, datetime_format = "%Y-%m-%d %H:%M:%S"):
self.file = open(path, "w")
self.datetime_format = datetime_format
-
+
def write_report(self, facts):
try:
for fact in facts:
fact["name"]= fact["name"].encode('utf-8')
fact["description"] = (fact["description"] or u"").encode('utf-8')
fact["category"] = (fact["category"] or _("Unsorted")).encode('utf-8')
-
+
if self.datetime_format:
fact["start_time"] = fact["start_time"].strftime(self.datetime_format)
-
+
if fact["end_time"]:
fact["end_time"] = fact["end_time"].strftime(self.datetime_format)
else:
- fact["end_time"] = ""
+ fact["end_time"] = ""
fact["delta"] = fact["delta"].seconds / 60 + fact["delta"].days * 24 * 60
-
+
self._write_fact(self.file, fact)
-
+
self._finish(self.file, facts)
finally:
self.file.close()
-
+
def _start(self, file, facts):
raise NotImplementedError
-
+
def _write_fact(self, file, fact):
raise NotImplementedError
-
+
def _finish(self, file, facts):
raise NotImplementedError
-
+
class ICalWriter(ReportWriter):
"""a lame ical writer, could not be bothered with finding a library"""
def __init__(self, path):
ReportWriter.__init__(self, path, datetime_format = "%Y%m%dT%H%M%S")
self.file.write("BEGIN:VCALENDAR\nVERSION:1.0\n")
-
+
def _write_fact(self, file, fact):
#for now we will skip ongoing facts
if not fact["end_time"]: return
if fact["category"] == _("Unsorted"):
fact["category"] = None
-
+
self.file.write("""BEGIN:VEVENT
CATEGORIES:%(category)s
DTSTART:%(start_time)s
@@ -99,7 +99,7 @@ SUMMARY:%(name)s
DESCRIPTION:%(description)s
END:VEVENT
""" % fact)
-
+
def _finish(self, file, facts):
self.file.write("END:VCALENDAR\n")
@@ -133,7 +133,7 @@ class XMLWriter(ReportWriter):
ReportWriter.__init__(self, path)
self.doc = Document()
self.activity_list = self.doc.createElement("activities")
-
+
def _write_fact(self, file, fact):
activity = self.doc.createElement("activity")
activity.setAttribute("name", fact["name"])
@@ -143,9 +143,9 @@ class XMLWriter(ReportWriter):
activity.setAttribute("category", fact["category"])
activity.setAttribute("description", fact["description"])
self.activity_list.appendChild(activity)
-
+
def _finish(self, file, facts):
- self.doc.appendChild(self.activity_list)
+ self.doc.appendChild(self.activity_list)
file.write(self.doc.toxml())
@@ -162,7 +162,7 @@ class HTMLWriter(ReportWriter):
self.title = _(u"Overview for %(start_B)s %(start_d)s â?? %(end_B)s %(end_d)s, %(end_Y)s") % dates_dict
else:
self.title = _(u"Overview for %(start_B)s %(start_d)s â?? %(end_d)s, %(end_Y)s") % dates_dict
-
+
if start_date == end_date:
self.title = _(u"Overview for %(start_B)s %(start_d)s, %(start_Y)s") % dates_dict
@@ -170,7 +170,7 @@ class HTMLWriter(ReportWriter):
self.even_row = True
"""TODO bring template to external file or write to PDF"""
-
+
self.file.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
@@ -184,7 +184,7 @@ class HTMLWriter(ReportWriter):
font-size: 12px;
padding: 12px;
color: #303030;
-
+
}
h1 {
border-bottom: 2px solid #303030;
@@ -202,7 +202,7 @@ class HTMLWriter(ReportWriter):
text-align: center;
padding-bottom: 6px;
}
-
+
.smallCell {
text-align: center;
width: 100px;
@@ -212,14 +212,14 @@ class HTMLWriter(ReportWriter):
.largeCell {
text-align: left;
padding: 3px 3px 3px 5px;
- }
+ }
.row0 {
background-color: #EAE8E3;
}
.row1 {
background-color: #ffffff;
- }
+ }
</style>
</head>
@@ -235,10 +235,10 @@ class HTMLWriter(ReportWriter):
<th class="smallCell">""" + _("Duration") + """</th>
<th class="largeCell">""" + _("Description") + """</th>
</tr>""")
-
+
def _write_fact(self, report, fact):
end_time = fact["end_time"]
-
+
# ongoing task in current day
end_time_str = ""
if end_time:
@@ -248,8 +248,8 @@ class HTMLWriter(ReportWriter):
if fact["category"] != _("Unsorted"): #do not print "unsorted" in list
category = fact["category"]
- description = fact["description"] or ""
-
+ description = fact["description"] or ""
+
# fact date column in HTML report
report.write("""<tr class="row%d">
<td class="smallCell">%s</td>
@@ -267,23 +267,23 @@ class HTMLWriter(ReportWriter):
# http://docs.python.org/library/time.html#time.strftime
C_("html report","%b %d, %Y")),
fact["name"],
- category,
+ category,
fact["start_time"].strftime('%H:%M'),
end_time_str,
stuff.format_duration(fact["delta"]) or "",
description))
- self.even_row = not self.even_row
+ self.even_row = not self.even_row
# save data for summary table
if fact["delta"]:
id_string = "<td class=\"smallCell\">%s</td><td class=\"largeCell\">%s</td>" % (fact["category"], fact["name"])
self.sum_time[id_string] = self.sum_time.get(id_string, 0) + fact["delta"]
-
+
def _finish(self, report, facts):
report.write("</table>")
-
+
# summary table
report.write("\n<h2>%s</h2>\n" % _("Totals"))
report.write("""<table>
@@ -297,11 +297,11 @@ class HTMLWriter(ReportWriter):
for key in sorted(self.sum_time.keys()):
report.write(" <tr class=\"row%d\">%s<td class=\"smallCell\">%s</td></tr>\n" % (int(even_row), key, stuff.format_duration(self.sum_time[key])))
tot_time += self.sum_time[key]
-
+
even_row = not even_row
report.write(" <tr><th colspan=\"2\" style=\"text-align:right;\">" + _("Total Time") + ":</th><th>%s</th></tr>\n" % (stuff.format_duration(tot_time)))
report.write("</table>\n")
-
+
report.write("</body>\n</html>")
-
+
diff --git a/hamster/stats.py b/hamster/stats.py
index ec65a1b..1e833b0 100644
--- a/hamster/stats.py
+++ b/hamster/stats.py
@@ -43,7 +43,7 @@ class StatsViewer(object):
self.parent = parent# determine if app should shut down on close
self._gui = stuff.load_ui_file("stats.ui")
self.report_chooser = None
-
+
self.facts = None
self.window = self.get_widget("tabs_window")
@@ -56,17 +56,17 @@ class StatsViewer(object):
#set to monday
self.start_date = self.view_date - dt.timedelta(self.view_date.weekday() + 1)
-
+
# look if we need to start on sunday or monday
self.start_date = self.start_date + dt.timedelta(stuff.locale_first_weekday())
-
+
# see if we have not gotten carried away too much in all these calculations
if (self.view_date - self.start_date) == dt.timedelta(7):
self.start_date += dt.timedelta(7)
-
+
self.end_date = self.start_date + dt.timedelta(6)
-
+
self.overview = OverviewBox()
self.get_widget("overview_tab").add(self.overview)
self.fact_tree = self.overview.fact_tree # TODO - this is upside down, should maybe get the overview tab over here
@@ -74,7 +74,7 @@ class StatsViewer(object):
self.reports = ReportsBox()
self.get_widget("reports_tab").add(self.reports)
-
+
self.range_combo = gtk.combo_box_new_text()
self.range_combo.append_text(_("Week"))
self.range_combo.append_text(_("Month"))
@@ -84,9 +84,9 @@ class StatsViewer(object):
self.range_combo.append_text("All")
self.range_combo.set_active(0)
self.range_combo.connect("changed", self.on_range_combo_changed)
-
-
-
+
+
+
self.get_widget("range_pick").add(self.range_combo)
@@ -113,10 +113,10 @@ class StatsViewer(object):
def search(self):
if self.start_date > self.end_date: # make sure the end is always after beginning
self.start_date, self.end_date = self.end_date, self.start_date
-
+
self.start_date_input.set_date(self.start_date)
self.end_date_input.set_date(self.end_date)
-
+
search_terms = self.get_widget("search").get_text().decode("utf8", "replace")
self.facts = runtime.storage.get_facts(self.start_date, self.end_date, search_terms)
@@ -138,7 +138,7 @@ class StatsViewer(object):
start_date, end_date = self.start_date, self.end_date
dates_dict = stuff.dateDict(start_date, "start_")
dates_dict.update(stuff.dateDict(end_date, "end_"))
-
+
if start_date == end_date:
# date format for overview label when only single day is visible
# Using python datetime formatting syntax. See:
@@ -187,21 +187,21 @@ class StatsViewer(object):
def on_search_activate(self, widget):
self.search()
-
+
def on_report_button_clicked(self, widget):
def on_report_chosen(widget, format, path):
self.report_chooser = None
reports.simple(self.facts, self.start_date, self.end_date, format, path)
-
+
if format == ("html"):
webbrowser.open_new("file://%s" % path)
else:
gtk.show_uri(gtk.gdk.Screen(),
"file://%s" % os.path.split(path)[0], 0L)
-
+
def on_report_chooser_closed(widget):
self.report_chooser = None
-
+
if not self.report_chooser:
self.report_chooser = widgets.ReportChooserDialog()
self.report_chooser.connect("report-chosen", on_report_chosen)
@@ -218,7 +218,7 @@ class StatsViewer(object):
self.get_widget("preset_range").hide()
self.get_widget("range_box").hide()
-
+
if idx == 2: # date range
self.get_widget("range_box").show()
else:
@@ -227,16 +227,16 @@ class StatsViewer(object):
self.start_date = self.start_date + dt.timedelta(stuff.locale_first_weekday())
self.end_date = self.start_date + dt.timedelta(6)
self.get_widget("preset_range").show()
-
+
elif idx == 1: #month
self.start_date = self.view_date - dt.timedelta(self.view_date.day - 1) #set to beginning of month
first_weekday, days_in_month = calendar.monthrange(self.view_date.year, self.view_date.month)
self.end_date = self.start_date + dt.timedelta(days_in_month - 1)
self.get_widget("preset_range").show()
-
-
+
+
self.search()
-
+
def on_start_date_entered(self, input):
self.start_date = input.get_date().date()
self.view_date = self.start_date
@@ -247,12 +247,12 @@ class StatsViewer(object):
self.search()
def _chosen_range(self):
- return self.range_combo.get_active()
+ return self.range_combo.get_active()
def on_prev_clicked(self, button):
if self._chosen_range() == 0: # week
self.start_date -= dt.timedelta(7)
- self.end_date -= dt.timedelta(7)
+ self.end_date -= dt.timedelta(7)
elif self._chosen_range() == 1: # month
self.end_date = self.start_date - dt.timedelta(1)
first_weekday, days_in_month = calendar.monthrange(self.end_date.year, self.end_date.month)
@@ -264,12 +264,12 @@ class StatsViewer(object):
def on_next_clicked(self, button):
if self._chosen_range() == 0: # week
self.start_date += dt.timedelta(7)
- self.end_date += dt.timedelta(7)
+ self.end_date += dt.timedelta(7)
elif self._chosen_range() == 1: # month
self.start_date = self.end_date + dt.timedelta(1)
first_weekday, days_in_month = calendar.monthrange(self.start_date.year, self.start_date.month)
self.end_date = self.start_date + dt.timedelta(days_in_month - 1)
-
+
self.view_date = self.start_date
self.search()
@@ -281,14 +281,14 @@ class StatsViewer(object):
self.start_date = self.view_date - dt.timedelta(self.view_date.weekday() + 1)
self.start_date = self.start_date + dt.timedelta(stuff.locale_first_weekday())
self.end_date = self.start_date + dt.timedelta(6)
-
+
elif self._chosen_range() == 1: # month
self.start_date = self.view_date - dt.timedelta(self.view_date.day - 1) #set to beginning of month
first_weekday, days_in_month = calendar.monthrange(self.view_date.year, self.view_date.month)
self.end_date = self.start_date + dt.timedelta(days_in_month - 1)
-
+
self.search()
-
+
def get_widget(self, name):
""" skip one variable (huh) """
return self._gui.get_object(name)
@@ -327,22 +327,22 @@ class StatsViewer(object):
def on_close(self, widget, event):
runtime.dispatcher.del_handler('activity_updated', self.after_activity_update)
runtime.dispatcher.del_handler('day_updated', self.after_fact_update)
- self.close_window()
+ self.close_window()
def on_window_key_pressed(self, tree, event_key):
if (event_key.keyval == gtk.keysyms.Escape
- or (event_key.keyval == gtk.keysyms.w
+ or (event_key.keyval == gtk.keysyms.w
and event_key.state & gtk.gdk.CONTROL_MASK)):
self.close_window()
-
-
+
+
def close_window(self):
if not self.parent:
gtk.main_quit()
else:
self.window.destroy()
return False
-
+
def show(self):
self.window.show()
diff --git a/hamster/stats_overview.py b/hamster/stats_overview.py
index 70af8bd..6da77c4 100644
--- a/hamster/stats_overview.py
+++ b/hamster/stats_overview.py
@@ -45,19 +45,19 @@ class OverviewBox(gtk.VBox):
def __init__(self):
gtk.VBox.__init__(self)
self.set_border_width(6)
-
+
scroll = gtk.ScrolledWindow()
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
scroll.set_shadow_type(gtk.SHADOW_IN)
-
+
self.start_date, self.end_date = None, None
self.facts = []
-
+
self.fact_tree = widgets.FactTree()
self.fact_tree.connect("row-activated", self.on_facts_row_activated)
self.fact_tree.connect("key-press-event", self.on_facts_keys)
self.fact_tree.connect("edit_clicked", lambda tree, fact: self.on_edit_clicked(fact))
-
+
scroll.add(self.fact_tree)
self.add(scroll)
@@ -72,11 +72,11 @@ class OverviewBox(gtk.VBox):
def fill_facts_tree(self):
self.fact_tree.detach_model()
self.fact_tree.clear()
-
+
#create list of all required dates
dates = [(self.start_date + dt.timedelta(i), [])
for i in range((self.end_date - self.start_date).days + 1)]
-
+
#update with facts for the day
for date, facts in groupby(self.facts, lambda fact: fact["date"]):
dates[dates.index((date, []))] = (date, list(facts))
@@ -143,7 +143,7 @@ class OverviewBox(gtk.VBox):
selection = tree.get_selection()
(model, iter) = selection.get_selected()
custom_fact = dialogs.edit.show(self, fact_id = model[iter][0])
-
+
def on_facts_keys(self, tree, event):
if (event.keyval == gtk.keysyms.Delete):
self.delete_selected()
@@ -157,13 +157,13 @@ class OverviewBox(gtk.VBox):
elif event.keyval == gtk.keysyms.v and event.state & gtk.gdk.CONTROL_MASK:
self.check_clipboard()
return True
-
+
return False
def check_clipboard(self):
clipboard = gtk.Clipboard()
clipboard.request_text(self.on_clipboard_text)
-
+
def on_clipboard_text(self, clipboard, text, data):
# first check that we have a date selected
selection = self.fact_tree.get_selection()
@@ -177,23 +177,23 @@ class OverviewBox(gtk.VBox):
int(selected_date[2]))
if not selected_date:
return
-
+
res = stuff.parse_activity_input(text)
if res.start_time is None or res.end_time is None:
return
-
+
start_time = res.start_time.replace(year = selected_date.year,
month = selected_date.month,
day = selected_date.day)
end_time = res.end_time.replace(year = selected_date.year,
month = selected_date.month,
day = selected_date.day)
-
+
activity_name = res.activity_name
if res.category_name:
activity_name += "@%s" % res.category_name
-
+
if res.description:
activity_name += ", %s" % res.description
@@ -202,10 +202,10 @@ class OverviewBox(gtk.VBox):
# TODO - set cursor to the pasted entry when done
# TODO - revisit parsing of selected date
added_fact = runtime.storage.add_fact(activity_name, start_time, end_time)
-
+
if __name__ == "__main__":
- gtk.window_set_default_icon_name("hamster-applet")
+ gtk.window_set_default_icon_name("hamster-applet")
window = gtk.Window()
window.set_title("Hamster - reports")
window.set_size_request(800, 600)
@@ -213,11 +213,11 @@ if __name__ == "__main__":
window.add(overview)
window.connect("delete_event", lambda *args: gtk.main_quit())
window.show_all()
-
- start_date = dt.date.today() - dt.timedelta(days=30)
+
+ start_date = dt.date.today() - dt.timedelta(days=30)
end_date = dt.date.today()
facts = runtime.storage.get_facts(start_date, end_date)
overview.search(start_date, end_date, facts)
-
- gtk.main()
+
+ gtk.main()
diff --git a/hamster/stats_reports.py b/hamster/stats_reports.py
index 1e166ca..3028d49 100644
--- a/hamster/stats_reports.py
+++ b/hamster/stats_reports.py
@@ -62,9 +62,9 @@ class ReportsBox(gtk.VBox):
self.category_chart.connect("bar-clicked", self.on_category_clicked)
self.selected_categories = []
self.category_sums = None
-
+
self.get_widget("totals_by_category").add(self.category_chart);
-
+
self.activity_chart = charting.HorizontalBarChart(background = self.background,
max_bar_width = 20,
legend_width = x_offset,
@@ -89,7 +89,7 @@ class ReportsBox(gtk.VBox):
self._gui.connect_signals(self)
-
+
self.config = GconfStore()
runtime.dispatcher.add_handler('gconf_on_day_start_changed', self.on_day_start_changed)
@@ -132,7 +132,7 @@ class ReportsBox(gtk.VBox):
self.category_sums, self.activity_sums, self.tag_sums = [], [], []
self.selected_categories, self.selected_activities, self.selected_tags = [], [], []
self.category_chart.bars_selected, self.activity_chart.bars_selected, self.tag_chart.bars_selected = [], [], []
-
+
self.start_date = start_date
self.end_date = end_date
@@ -154,7 +154,7 @@ class ReportsBox(gtk.VBox):
import copy
facts = copy.deepcopy(self.facts)
-
+
for fact in facts:
if self.selected_categories and fact["category"] not in self.selected_categories:
fact["delta"] = dt.timedelta()
@@ -207,7 +207,7 @@ class ReportsBox(gtk.VBox):
for sum in tag_sums:
tag_sums[sum] = tag_sums[sum] / 60 / 60.0
- if tag_sums:
+ if tag_sums:
if self.tag_sums:
tag_sums = [(key, tag_sums[key]) for key in self.tag_sums[0]]
else:
@@ -244,14 +244,14 @@ class ReportsBox(gtk.VBox):
def on_day_start_changed(self, event, new_minutes):
self.do_graph()
-
+
def on_statistics_button_clicked(self, button):
dialogs.stats.show(self)
if __name__ == "__main__":
- gtk.window_set_default_icon_name("hamster-applet")
+ gtk.window_set_default_icon_name("hamster-applet")
window = gtk.Window()
window.set_title("Hamster - reports")
window.set_size_request(800, 600)
@@ -260,11 +260,11 @@ if __name__ == "__main__":
window.connect("delete_event", lambda *args: gtk.main_quit())
window.show_all()
- start_date = dt.date.today() - dt.timedelta(days=30)
+ start_date = dt.date.today() - dt.timedelta(days=30)
end_date = dt.date.today()
facts = runtime.storage.get_facts(start_date, end_date)
reports.search(start_date, end_date, facts)
-
- gtk.main()
+
+ gtk.main()
diff --git a/hamster/stats_stats.py b/hamster/stats_stats.py
index ddf80d3..26ee349 100644
--- a/hamster/stats_stats.py
+++ b/hamster/stats_stats.py
@@ -39,7 +39,7 @@ class TimelineBackground(widgets.TimeLine):
def __init__(self):
widgets.TimeLine.__init__(self)
self.bar_color = (220, 220, 220)
-
+
def on_expose(self):
self.fill_area(0, 0, self.width, self.height, (0.975, 0.975, 0.975))
widgets.TimeLine.on_expose(self)
@@ -53,7 +53,7 @@ class StatsViewer(object):
self.parent = parent# determine if app should shut down on close
-
+
self.background = (0.975, 0.975, 0.975)
self.get_widget("explore_frame").modify_bg(gtk.STATE_NORMAL,
gtk.gdk.Color(*[int(b*65536.0) for b in self.background]))
@@ -75,21 +75,21 @@ class StatsViewer(object):
def init_stats(self):
self.stat_facts = runtime.storage.get_facts(dt.date(1970, 1, 1), dt.date.today())
-
+
if not self.stat_facts or self.stat_facts[-1]["start_time"].year == self.stat_facts[0]["start_time"].year:
self.get_widget("explore_controls").hide()
else:
by_year = stuff.totals(self.stat_facts,
lambda fact: fact["start_time"].year,
lambda fact: 1)
-
+
year_box = self.get_widget("year_box")
class YearButton(gtk.ToggleButton):
def __init__(self, label, year, on_clicked):
gtk.ToggleButton.__init__(self, label)
self.year = year
self.connect("clicked", on_clicked)
-
+
all_button = YearButton(C_("years", "All").encode("utf-8"),
None,
self.on_year_changed)
@@ -97,11 +97,11 @@ class StatsViewer(object):
self.bubbling = True # TODO figure out how to properly work with togglebuttons as radiobuttons
all_button.set_active(True)
self.bubbling = False # TODO figure out how to properly work with togglebuttons as radiobuttons
-
+
years = sorted(by_year.keys())
for year in years:
year_box.pack_start(YearButton(str(year), year, self.on_year_changed))
-
+
year_box.show_all()
self.chart_category_totals = charting.HorizontalBarChart(value_format = "%.1f",
@@ -125,7 +125,7 @@ class StatsViewer(object):
max_bar_width = 20,
legend_width = 70)
self.get_widget("explore_weekday_starts_ends").add(self.chart_weekday_starts_ends)
-
+
self.chart_category_starts_ends = charting.HorizontalDayChart(bars_beveled = False,
animate = False,
background = self.background,
@@ -141,11 +141,11 @@ class StatsViewer(object):
self.background = background
self.text = ""
self.fontsize = fontsize
-
+
def set_text(self, text):
self.text = text
self.redraw_canvas()
-
+
def on_expose(self):
if self.background:
self.fill_area(0, 0, self.width, self.height, self.background)
@@ -153,14 +153,14 @@ class StatsViewer(object):
default_font = pango.FontDescription(gtk.Style().font_desc.to_string())
default_font.set_size(self.fontsize * pango.SCALE)
self.layout.set_font_description(default_font)
-
+
#self.context.set_source_rgb(0,0,0)
self.layout.set_markup(self.text)
self.layout.set_width((self.width) * pango.SCALE)
self.context.move_to(0,0)
self.set_color(charting.graphics.Colors.aluminium[5])
-
+
self.context.show_layout(self.layout)
self.explore_summary = CairoText(self.background)
@@ -202,21 +202,21 @@ A week of usage would be nice!"""))
category_keys = sorted(categories.keys())
categories = [categories[key] for key in category_keys]
self.chart_category_totals.plot(category_keys, categories)
-
+
# Totals by weekday
weekdays = stuff.totals(facts,
lambda fact: (fact["start_time"].weekday(),
fact["start_time"].strftime("%a")),
lambda fact: fact['delta'].seconds / 60 / 60.0)
-
- weekday_keys = sorted(weekdays.keys(), key = lambda x: x[0]) #sort
+
+ weekday_keys = sorted(weekdays.keys(), key = lambda x: x[0]) #sort
weekdays = [weekdays[key] for key in weekday_keys] #get values in the order
weekday_keys = [key[1] for key in weekday_keys] #now remove the weekday and keep just the abbreviated one
self.chart_weekday_totals.plot(weekday_keys, weekdays)
split_minutes = 5 * 60 + 30 #the mystical hamster midnight
-
+
# starts and ends by weekday
by_weekday = {}
for date, date_facts in groupby(facts, lambda fact: fact["start_time"].date()):
@@ -224,7 +224,7 @@ A week of usage would be nice!"""))
weekday = (date_facts[0]["start_time"].weekday(),
date_facts[0]["start_time"].strftime("%a"))
by_weekday.setdefault(weekday, [])
-
+
start_times, end_times = [], []
for fact in date_facts:
start_time = fact["start_time"].time()
@@ -232,15 +232,15 @@ A week of usage would be nice!"""))
if fact["end_time"]:
end_time = fact["end_time"].time()
end_time = end_time.hour * 60 + end_time.minute
-
+
if start_time < split_minutes:
start_time += 24 * 60
if end_time < start_time:
end_time += 24 * 60
-
+
start_times.append(start_time)
end_times.append(end_time)
- if start_times and end_times:
+ if start_times and end_times:
by_weekday[weekday].append((min(start_times), max(end_times)))
@@ -256,16 +256,16 @@ A week of usage would be nice!"""))
weekdays = [by_weekday[key] for key in weekday_keys]
weekday_keys = [key[1] for key in weekday_keys] # get rid of the weekday number as int
-
+
# starts and ends by category
by_category = {}
for date, date_facts in groupby(facts, lambda fact: fact["start_time"].date()):
date_facts = sorted(list(date_facts), key = lambda x: x["category"])
-
+
for category, category_facts in groupby(date_facts, lambda x: x["category"]):
category_facts = list(category_facts)
by_category.setdefault(category, [])
-
+
start_times, end_times = [], []
for fact in category_facts:
start_time = fact["start_time"]
@@ -273,7 +273,7 @@ A week of usage would be nice!"""))
if fact["end_time"]:
end_time = fact["end_time"].time()
end_time = end_time.hour * 60 + end_time.minute
-
+
if start_time < split_minutes:
start_time += 24 * 60
if end_time < start_time:
@@ -282,7 +282,7 @@ A week of usage would be nice!"""))
start_times.append(start_time)
end_times.append(end_time)
- if start_times and end_times:
+ if start_times and end_times:
by_category[category].append((min(start_times), max(end_times)))
for cat in by_category:
@@ -307,7 +307,7 @@ A week of usage would be nice!"""))
#now the factoids!
summary = ""
- # first record
+ # first record
if not year:
# date format for the first record if the year has not been selected
# Using python datetime formatting syntax. See:
@@ -321,12 +321,12 @@ A week of usage would be nice!"""))
summary += _("First activity was recorded on %s.") % \
("<b>%s</b>" % first_date)
-
+
# total time tracked
total_delta = dt.timedelta(days=0)
for fact in facts:
total_delta += fact["delta"]
-
+
if total_delta.days > 1:
human_years_str = ngettext("%(num)s year",
"%(num)s years",
@@ -343,7 +343,7 @@ A week of usage would be nice!"""))
"human_years": human_years_str,
"working_days": ("<b>%d</b>" % (total_delta.days * 3)), # 8 should be pretty much an average working day
"working_years": working_years_str }
-
+
# longest fact
max_fact = None
@@ -356,10 +356,10 @@ A week of usage would be nice!"""))
# Using python datetime formatting syntax. See:
# http://docs.python.org/library/time.html#time.strftime
C_("date of the longest activity", "%b %d, %Y"))
-
+
num_hours = max_fact["delta"].seconds / 60 / 60.0 + max_fact["delta"].days * 24
hours = "<b>%.1f</b>" % (num_hours)
-
+
summary += "\n" + ngettext("Longest continuous work happened on \
%(date)s and was %(hours)s hour.",
"Longest continuous work happened on \
@@ -375,14 +375,14 @@ A week of usage would be nice!"""))
early_start, early_end = dt.time(5,0), dt.time(9,0)
late_start, late_end = dt.time(20,0), dt.time(5,0)
-
-
+
+
fact_count = len(facts)
def percent(condition):
matches = [fact for fact in facts if condition(fact)]
return round(len(matches) / float(fact_count) * 100)
-
-
+
+
early_percent = percent(lambda fact: early_start < fact["start_time"].time() < early_end)
late_percent = percent(lambda fact: fact["start_time"].time() > late_start or fact["start_time"].time() < late_end)
short_percent = percent(lambda fact: fact["delta"] <= dt.timedelta(seconds = 60 * 15))
@@ -402,16 +402,16 @@ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent))
self.explore_summary.set_text(summary)
-
+
def on_year_changed(self, button):
if self.bubbling: return
-
+
for child in button.parent.get_children():
if child != button and child.get_active():
self.bubbling = True
child.set_active(False)
self.bubbling = False
-
+
self.stats(button.year)
@@ -430,8 +430,8 @@ than 15 minutes you seem to be a busy bee." % ("<b>%d</b>" % short_percent))
else:
self.window.destroy()
return False
-
+
if __name__ == "__main__":
- stats_viewer = StatsViewer()
- gtk.main()
+ stats_viewer = StatsViewer()
+ gtk.main()
diff --git a/hamster/storage.py b/hamster/storage.py
index e4fe975..ebd0124 100644
--- a/hamster/storage.py
+++ b/hamster/storage.py
@@ -31,14 +31,14 @@ class Storage(object):
def dispatch(self, event, data):
self.parent.dispatch(event, data)
- def dispatch_overwrite(self):
+ def dispatch_overwrite(self):
self.dispatch('new_tags_added', ())
self.dispatch('day_updated', ())
self.dispatch('activity_updated', ())
def get_tags(self, autocomplete = None):
return self.__get_tags(autocomplete)
-
+
def get_tag_ids(self, tags):
tags, new_added = self.__get_tag_ids(tags)
if new_added:
@@ -85,25 +85,25 @@ class Storage(object):
def update_fact(self, fact_id, activity_name, tags, start_time, end_time):
now = datetime.datetime.now()
self.start_transaction()
-
+
fact = self.get_fact(fact_id)
if fact:
self.__remove_fact(fact_id)
-
+
result = self.__add_fact(activity_name, tags, start_time, end_time)
self.end_transaction()
-
+
if result:
self.dispatch('day_updated', result['start_time'])
return result
def get_activities(self, category_id = None):
return self.__get_activities(category_id = category_id)
-
+
def get_autocomplete_activities(self):
return self.__get_autocomplete_activities()
-
+
def get_last_activity(self):
return self.__get_last_activity()
@@ -111,7 +111,7 @@ class Storage(object):
result = self.__remove_activity(id)
self.dispatch('activity_updated', ())
return result
-
+
def remove_category(self, id):
self.__remove_category(id)
self.dispatch('activity_updated', ())
@@ -134,7 +134,7 @@ class Storage(object):
def update_activity(self, id, name, category_id):
self.__update_activity(id, name, category_id)
self.dispatch('activity_updated', ())
-
+
def add_activity(self, name, category_id = -1):
new_id = self.__add_activity(name, category_id)
self.dispatch('activity_updated', ())
diff --git a/hamster/stuff.py b/hamster/stuff.py
index f2e1f22..b3d15cc 100644
--- a/hamster/stuff.py
+++ b/hamster/stuff.py
@@ -36,20 +36,20 @@ import os
def format_duration(minutes, human = True):
"""formats duration in a human readable format.
accepts either minutes or timedelta"""
-
+
if isinstance(minutes, dt.timedelta):
minutes = duration_minutes(minutes)
-
+
if not minutes:
if human:
return ""
else:
return "00:00"
-
+
hours = minutes / 60
minutes = minutes % 60
formatted_duration = ""
-
+
if human:
if minutes % 60 == 0:
# duration in round hours
@@ -62,8 +62,8 @@ def format_duration(minutes, human = True):
formatted_duration += _("%dh %dmin") % (hours, minutes % 60)
else:
formatted_duration += "%02d:%02d" % (hours, minutes)
-
-
+
+
return formatted_duration
def duration_minutes(duration):
@@ -75,11 +75,11 @@ def load_ui_file(name):
from configuration import runtime
ui = gtk.Builder()
ui.add_from_file(os.path.join(runtime.data_dir, name))
- return ui
+ return ui
def zero_hour(date):
return dt.datetime.combine(date.date(), dt.time(0,0))
-
+
# it seems that python or something has bug of sorts, that breaks stuff for
# japanese locale, so we have this locale from and to ut8 magic in some places
# see bug 562298
@@ -111,14 +111,14 @@ def locale_first_weekday():
first_weekday = int(beginning.strftime("%w"))
except:
logging.warn("WARNING - Failed to get first weekday from locale")
-
+
return first_weekday
-
+
class CategoryCell(gtk.CellRendererText):
def __init__(self):
- gtk.CellRendererText.__init__(self)
+ gtk.CellRendererText.__init__(self)
self.set_property('alignment', pango.ALIGN_RIGHT)
-
+
insensitive_color = gtk.Label().style.fg[gtk.STATE_INSENSITIVE]
self.set_property('foreground-gdk', insensitive_color)
self.set_property('scale', pango.SCALE_SMALL)
@@ -128,7 +128,7 @@ class CategoryCell(gtk.CellRendererText):
insensitive_color = gtk.Label().style.fg[gtk.STATE_INSENSITIVE].to_string()
def format_activity(name, category, description, pad_description = False):
"returns pango markup for activity with category and description"
- text = name
+ text = name
if category and category != _("Unsorted"):
text += """ - <span color="%s" size="x-small">%s</span>""" % (insensitive_color, category)
@@ -138,9 +138,9 @@ def format_activity(name, category, description, pad_description = False):
text += " " * 23
text += """<span style="italic" size="small">%s</span>""" % description
-
+
return text
-
+
def totals(iter, keyfunc, sumfunc):
"""groups items by field described in keyfunc and counts totals using value
@@ -160,7 +160,7 @@ def totals(iter, keyfunc, sumfunc):
def dateDict(date, prefix = ""):
"""converts date into dictionary, having prefix for all the keys"""
res = {}
-
+
res[prefix+"a"] = date.strftime("%a")
res[prefix+"A"] = date.strftime("%A")
res[prefix+"b"] = date.strftime("%b")
@@ -182,7 +182,7 @@ def dateDict(date, prefix = ""):
res[prefix+"y"] = date.strftime("%y")
res[prefix+"Y"] = date.strftime("%Y")
res[prefix+"Z"] = date.strftime("%Z")
-
+
for i, value in res.items():
res[i] = locale_to_utf8(value)
@@ -191,7 +191,7 @@ def dateDict(date, prefix = ""):
def escape_pango(text):
if not text:
return text
-
+
text = text.replace ("&", "&")
text = text.replace("<", "<")
text = text.replace(">", ">")
@@ -200,14 +200,14 @@ def escape_pango(text):
def figure_time(str_time):
if not str_time or not str_time.strip():
return None
-
+
# strip everything non-numeric and consider hours to be first number
# and minutes - second number
numbers = re.split("\D", str_time)
numbers = filter(lambda x: x!="", numbers)
-
+
hours, minutes = None, None
-
+
if len(numbers) == 1 and len(numbers[0]) == 4:
hours, minutes = int(numbers[0][:2]), int(numbers[0][2:])
else:
@@ -215,7 +215,7 @@ def figure_time(str_time):
hours = int(numbers[0])
if len(numbers) >= 2:
minutes = int(numbers[1])
-
+
if (hours is None or minutes is None) or hours > 24 or minutes > 60:
return None #no can do
@@ -233,10 +233,10 @@ def parse_activity_input(text):
self.end_time = None
self.description = None
self.tags = []
-
-
+
+
res = InputParseResult()
-
+
input_parts = text.split(" ")
if len(input_parts) > 1 and re.match('^-?\d', input_parts[0]): #look for time only if there is more
potential_time = text.split(" ")[0]
@@ -245,20 +245,20 @@ def parse_activity_input(text):
#if starts with minus, treat as minus delta minutes
res.start_time = dt.datetime.now() + dt.timedelta(minutes =
int(potential_time))
-
+
else:
if potential_time.find("-") > 0:
potential_time, potential_end_time = potential_time.split("-", 2)
res.end_time = figure_time(potential_end_time)
-
+
res.start_time = figure_time(potential_time)
-
+
#remove parts that worked
if res.start_time and potential_end_time and not res.end_time:
res.start_time = None #scramble
elif res.start_time:
text = text[text.find(" ")+1:]
-
+
if text.find("@") > 0:
text, res.category_name = text.split("@", 1)
res.category_name = res.category_name.strip()
diff --git a/hamster/widgets/__init__.py b/hamster/widgets/__init__.py
index 0a82f24..63d7f55 100644
--- a/hamster/widgets/__init__.py
+++ b/hamster/widgets/__init__.py
@@ -40,26 +40,26 @@ from facttree import FactTree
# handy wrappers
def add_hint(entry, hint):
- entry.hint = hint
-
+ entry.hint = hint
+
def override_get_text(self):
#override get text so it does not return true when hint is in!
if self.real_get_text() == self.hint:
return ""
else:
return self.real_get_text()
-
+
def _set_hint(self, widget, event):
if self.get_text(): # do not mess with user entered text
- return
+ return
self.modify_text(gtk.STATE_NORMAL, gtk.gdk.Color("gray"))
hint_font = pango.FontDescription(gtk.Style().font_desc.to_string())
hint_font.set_style(pango.STYLE_ITALIC)
self.modify_font(hint_font)
-
+
self.set_text(self.hint)
-
+
def _set_normal(self, widget, event):
self.modify_text(gtk.STATE_NORMAL, gtk.Style().fg[gtk.STATE_NORMAL])
hint_font = pango.FontDescription(gtk.Style().font_desc.to_string())
@@ -67,7 +67,7 @@ def add_hint(entry, hint):
if self.real_get_text() == self.hint:
self.set_text("")
-
+
def _on_changed(self, widget):
if self.real_get_text() == "" and self.is_focus() == False:
self._set_hint(widget, None)
@@ -80,7 +80,7 @@ def add_hint(entry, hint):
entry._on_changed = instancemethod(_on_changed, entry, gtk.Entry)
entry.real_get_text = entry.get_text
entry.get_text = instancemethod(override_get_text, entry, gtk.Entry)
-
+
entry.connect('focus-in-event', entry._set_normal)
entry.connect('focus-out-event', entry._set_hint)
entry.connect('changed', entry._on_changed)
diff --git a/hamster/widgets/activityentry.py b/hamster/widgets/activityentry.py
index c1a5ece..aceeb9e 100644
--- a/hamster/widgets/activityentry.py
+++ b/hamster/widgets/activityentry.py
@@ -38,9 +38,9 @@ class ActivityEntry(gtk.Entry):
self.categories = None
self.filter = None
self.max_results = 10 # limit popup size to 10 results
-
+
self.popup = gtk.Window(type = gtk.WINDOW_POPUP)
-
+
box = gtk.ScrolledWindow()
box.set_shadow_type(gtk.SHADOW_IN)
box.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
@@ -53,11 +53,11 @@ class ActivityEntry(gtk.Entry):
time_cell = gtk.CellRendererPixbuf()
time_cell.set_property("icon-name", "appointment-new")
time_cell.set_property("cell-background", bgcolor)
-
+
self.time_icon_column = gtk.TreeViewColumn("",
time_cell)
self.tree.append_column(self.time_icon_column)
-
+
time_cell = gtk.CellRendererText()
time_cell.set_property("scale", 0.8)
time_cell.set_property("cell-background", bgcolor)
@@ -73,7 +73,7 @@ class ActivityEntry(gtk.Entry):
text=1)
self.activity_column.set_expand(True)
self.tree.append_column(self.activity_column)
-
+
self.category_column = gtk.TreeViewColumn("Category",
stuff.CategoryCell(),
text=2)
@@ -85,7 +85,7 @@ class ActivityEntry(gtk.Entry):
box.add(self.tree)
self.popup.add(box)
-
+
self.connect("button-press-event", self._on_button_press_event)
self.connect("key-press-event", self._on_key_press_event)
self.connect("key-release-event", self._on_key_release_event)
@@ -106,7 +106,7 @@ class ActivityEntry(gtk.Entry):
self.hide_popup()
return
- activity = stuff.parse_activity_input(self.filter)
+ activity = stuff.parse_activity_input(self.filter)
time = ''
if activity.start_time:
time = activity.start_time.strftime("%H:%M")
@@ -115,11 +115,11 @@ class ActivityEntry(gtk.Entry):
self.time_icon_column.set_visible(activity.start_time != None and self.filter.find("@") == -1)
self.time_column.set_visible(activity.start_time != None and self.filter.find("@") == -1)
-
+
self.category_column.set_visible(self.filter.find("@") == -1)
-
-
+
+
#move popup under the widget
alloc = self.get_allocation()
x, y = self.get_parent_window().get_origin()
@@ -127,36 +127,36 @@ class ActivityEntry(gtk.Entry):
self.popup.move(x + alloc.x,y + alloc.y + alloc.height)
w = alloc.width
-
+
#TODO - this is clearly unreliable as we calculate tree row size based on our gtk entry
self.tree.parent.set_size_request(w,(alloc.height-6) * min([result_count, self.max_results]))
self.popup.resize(w, (alloc.height-6) * min([result_count, self.max_results]))
-
+
self.popup.show_all()
-
+
def complete_inline(self):
model = self.tree.get_model()
activity = stuff.parse_activity_input(self.filter)
subject = self.get_text()
-
+
if not subject or model.iter_n_children(None) == 0:
return
-
+
prefix_length = 0
-
+
labels = [row[0] for row in model]
shortest = min([len(label) for label in labels])
first = labels[0] #since we are looking for common prefix, we do not care which label we use for comparisons
-
+
for i in range(len(subject), shortest):
letter_matching = all([label[i]==first[i] for label in labels])
-
+
if not letter_matching:
break
-
+
prefix_length +=1
-
+
if prefix_length:
prefix = first[len(subject):len(subject)+prefix_length]
self.set_text("%s%s" % (self.filter, prefix))
@@ -172,29 +172,29 @@ class ActivityEntry(gtk.Entry):
cursor = self.get_selection_bounds()[0]
else:
cursor = self.get_position()
-
+
if self.activities and self.categories and self.filter == self.get_text().decode('utf8', 'replace')[:cursor]:
return #same thing, no need to repopulate
self.activities = self.activities or runtime.storage.get_autocomplete_activities()
self.categories = self.categories or runtime.storage.get_category_list()
-
+
self.filter = self.get_text().decode('utf8', 'replace')[:cursor]
-
+
input_activity = stuff.parse_activity_input(self.filter)
-
+
time = ''
if input_activity.start_time:
time = input_activity.start_time.strftime("%H:%M")
if input_activity.end_time:
time += "-%s" % input_activity.end_time.strftime("%H:%M")
-
-
+
+
store = self.tree.get_model()
if not store:
store = gtk.ListStore(str, str, str, str)
- self.tree.set_model(store)
+ self.tree.set_model(store)
store.clear()
if self.filter.find("@") > 0:
@@ -213,18 +213,18 @@ class ActivityEntry(gtk.Entry):
if time: #as we also support deltas, for the time we will grab anything up to first space
fillable = "%s %s" % (self.filter.split(" ", 1)[0], fillable)
-
+
store.append([fillable, activity['name'], activity['category'], time])
def after_activity_update(self, widget, event):
self.refresh_activities()
-
+
def _on_focus_out_event(self, widget, event):
self.hide_popup()
def _on_text_changed(self, widget):
self.news = True
-
+
def _on_button_press_event(self, button, event):
self.populate_suggestions()
@@ -250,22 +250,22 @@ class ActivityEntry(gtk.Entry):
else:
self.populate_suggestions()
self.show_popup()
-
+
if event.keyval not in (gtk.keysyms.Delete, gtk.keysyms.BackSpace):
self.complete_inline()
-
+
def _on_key_press_event(self, entry, event):
if event.keyval in (gtk.keysyms.Up, gtk.keysyms.Down):
cursor = self.tree.get_cursor()
-
+
if not cursor or not cursor[0]:
self.tree.set_cursor(0)
return True
-
+
i = cursor[0][0]
if event.keyval == gtk.keysyms.Up:
@@ -275,13 +275,13 @@ class ActivityEntry(gtk.Entry):
# keep it in the sane borders
i = min(max(i, 0), len(self.tree.get_model()) - 1)
-
+
self.tree.set_cursor(i)
self.tree.scroll_to_cell(i, use_align = True, row_align = 0.4)
return True
else:
return False
-
+
def _on_tree_button_press_event(self, tree, event):
model, iter = tree.get_selection().get_selected()
value = model.get_value(iter, 0)
diff --git a/hamster/widgets/dateinput.py b/hamster/widgets/dateinput.py
index 46ea201..362674b 100644
--- a/hamster/widgets/dateinput.py
+++ b/hamster/widgets/dateinput.py
@@ -33,7 +33,7 @@ class DateInput(gtk.Entry):
def __init__(self, date = None):
gtk.Entry.__init__(self)
-
+
self.set_width_chars(12) #12 is enough for 12-oct-2009, which is verbose
self.date = date
if date:
@@ -86,21 +86,21 @@ class DateInput(gtk.Entry):
def _on_text_changed(self, widget):
self.news = True
-
+
def __on_day_selected_double_click(self, calendar):
self.prev_cal_day = None
self._on_day_selected(calendar) #forward
-
+
def _on_cal_button_press_event(self, calendar, event):
self.prev_cal_day = calendar.get_date()[2]
def _on_day_selected(self, calendar):
if self.popup.get_property("visible") == False:
return
-
+
if self.prev_cal_day == calendar.get_date()[2]:
return
-
+
cal_date = calendar.get_date()
self.date = dt.date(cal_date[0], cal_date[1] + 1, cal_date[2])
@@ -110,23 +110,23 @@ class DateInput(gtk.Entry):
if self.news:
self.emit("date-entered")
self.news = False
-
-
+
+
def show_popup(self):
window = self.get_parent_window()
x, y= window.get_origin()
alloc = self.get_allocation()
-
+
date = self._figure_date(self.get_text())
if date:
- self.prev_cal_day = date.day #avoid
+ self.prev_cal_day = date.day #avoid
self.date_calendar.select_month(date.month-1, date.year)
self.date_calendar.select_day(date.day)
-
+
self.popup.move(x + alloc.x,y + alloc.y + alloc.height)
self.popup.show_all()
-
+
def _on_focus_in_event(self, entry, event):
self.show_popup()
@@ -139,7 +139,7 @@ class DateInput(gtk.Entry):
if self.news:
self.emit("date-entered")
self.news = False
-
+
def _on_key_press_event(self, entry, event):
if self.popup.get_property("visible"):
cal_date = self.date_calendar.get_date()
@@ -165,13 +165,13 @@ class DateInput(gtk.Entry):
else:
self.popup.hide()
return False
-
+
if enter_pressed:
self.prev_cal_day = "borken"
else:
#prev_cal_day is our only way of checking that date is right
- self.prev_cal_day = date.day
-
+ self.prev_cal_day = date.day
+
self.date_calendar.select_month(date.month, date.year)
self.date_calendar.select_day(date.day)
return True
diff --git a/hamster/widgets/dayline.py b/hamster/widgets/dayline.py
index 1d73219..03acf4c 100644
--- a/hamster/widgets/dayline.py
+++ b/hamster/widgets/dayline.py
@@ -32,8 +32,8 @@ class DayLine(graphics.Area):
def get_value_at_pos(self, x):
"""returns mapped value at the coordinates x,y"""
return x / float(self.width / self.view_minutes)
-
-
+
+
#normal stuff
def __init__(self):
graphics.Area.__init__(self)
@@ -55,7 +55,7 @@ class DayLine(graphics.Area):
self.range_start = None
self.range_start_int = None #same date just expressed as integer so we can interpolate it (a temporar hack)
-
+
self.in_motion = False
self.days = []
@@ -70,11 +70,11 @@ class DayLine(graphics.Area):
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)
-
+
start_time_int = int(time.mktime(start_time.timetuple()))
-
+
if self.range_start:
self.range_start = start_time
self.scroll_to_range_start()
@@ -84,16 +84,16 @@ class DayLine(graphics.Area):
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:
@@ -109,13 +109,13 @@ class DayLine(graphics.Area):
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 dt.datetime.fromtimestamp(self.range_start_int) + dt.timedelta(minutes = minutes)
-
+ return dt.datetime.fromtimestamp(self.range_start_int) + dt.timedelta(minutes = minutes)
+
+
-
def draw_cursor(self, area, event):
if event.is_hint:
x, y, state = event.window.get_pointer()
@@ -125,10 +125,10 @@ class DayLine(graphics.Area):
state = event.state
mouse_down = state & gtk.gdk.BUTTON1_MASK
-
+
highlight_start = self.highlight_start + self.graph_x
highlight_end = self.highlight_end + self.graph_x
-
+
if highlight_start != None:
start_drag = 10 > (highlight_start - x) > -1
@@ -157,7 +157,7 @@ class DayLine(graphics.Area):
self.move_type = "scale_drag"
self.drag_start_time = dt.datetime.fromtimestamp(self.range_start_int)
-
+
if mouse_down and self.drag_start:
start, end = 0, 0
if self.move_type and self.move_type != "scale_drag":
@@ -172,9 +172,9 @@ class DayLine(graphics.Area):
elif self.move_type == "move":
width = self.highlight_end - self.highlight_start
start = x - self.drag_start + self.graph_x
-
+
end = start + width
-
+
if end - start > 1:
self.highlight = (self.get_time(start), self.get_time(end))
self.redraw_canvas()
@@ -193,8 +193,8 @@ class DayLine(graphics.Area):
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 - dt.datetime.fromtimestamp(self.range_start_int))
return delta.days * 24 * 60 + delta.seconds / 60
@@ -204,7 +204,7 @@ class DayLine(graphics.Area):
self.animate(self, {"range_start_int": int(time.mktime(self.range_start.timetuple())),
"tweenType": graphics.Easing.Expo.easeOut,
"tweenTime": 0.4})
-
+
def on_expose(self):
# check if maybe we are approaching day boundaries and should ask for
# more data!
@@ -223,13 +223,13 @@ class DayLine(graphics.Area):
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 = now + dt.timedelta(hours = 12 + 2 * 4)
+ range_end = now + dt.timedelta(hours = 12 + 2 * 4)
self.graph_x = -self.width / 3 #so x moves one third out of screen
-
+
pixels_in_minute = self.width / self.view_minutes
minutes = self._minutes_from_start(range_end)
@@ -239,17 +239,17 @@ class DayLine(graphics.Area):
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))
context.save()
context.translate(self.graph_x, self.graph_y)
-
+
#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:
@@ -257,7 +257,7 @@ class DayLine(graphics.Area):
end_minutes = self._minutes_from_start(dt.datetime.now())
else:
end_minutes = start_minutes
-
+
if end_minutes * pixels_in_minute > 0 and \
start_minutes * pixels_in_minute + self.graph_x < self.width:
context.set_source_rgba(0.86, 0.86, 0.86, 0.5)
@@ -276,14 +276,14 @@ class DayLine(graphics.Area):
self.context.line_to(round(end_minutes * pixels_in_minute) + 0.5, 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 = (now + dt.timedelta(minutes=i))
-
+
if label_time.minute == 0:
context.set_source_rgb(0.8, 0.8, 0.8)
self.context.move_to(round(i * pixels_in_minute) + 0.5, graph_y2 - 15)
@@ -294,9 +294,9 @@ class DayLine(graphics.Area):
self.context.move_to(round(i * pixels_in_minute) + 0.5, graph_y2 - 5)
self.context.line_to(round(i * pixels_in_minute) + 0.5, 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)
@@ -309,12 +309,12 @@ class DayLine(graphics.Area):
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(round(i * pixels_in_minute) + 2, graph_y2 - label_h - 8)
+
+ context.move_to(round(i * pixels_in_minute) + 2, graph_y2 - label_h - 8)
context.show_layout(self.layout)
context.stroke()
-
+
#highlight rectangle
if self.highlight:
self.highlight_start = round(self._minutes_from_start(self.highlight[0]) * pixels_in_minute)
@@ -338,17 +338,17 @@ class DayLine(graphics.Area):
self.context.line_to(self.highlight_end + 0.5, graph_y + graph_height)
context.stroke()
- context.restore()
+ context.restore()
#and now put a frame around the whole thing
context.set_source_rgb(0.7, 0.7, 0.7)
context.rectangle(0, graph_y-0.5, self.width - 0.5, graph_height)
context.stroke()
-
+
if self.move_type == "move" and (self.highlight_start + self.graph_x <= 0 or self.highlight_end + self.graph_x >= self.width):
if self.highlight_start + self.graph_x <= 0:
self.range_start = self.range_start - dt.timedelta(minutes=30)
if self.highlight_end + self.graph_x >= self.width:
self.range_start = self.range_start + dt.timedelta(minutes=30)
-
+
self.scroll_to_range_start()
diff --git a/hamster/widgets/facttree.py b/hamster/widgets/facttree.py
index d395717..7521384 100644
--- a/hamster/widgets/facttree.py
+++ b/hamster/widgets/facttree.py
@@ -28,13 +28,13 @@ import pango
def parent_painter(column, cell, model, iter):
cell_text = model.get_value(iter, 1)
-
+
if model.get_value(iter, 6) is None:
if model.get_path(iter) == (0,):
text = '<span weight="heavy">%s</span>' % cell_text
else:
text = '<span weight="heavy" rise="-20000">%s</span>' % cell_text
-
+
cell.set_property('markup', text)
else:
@@ -45,7 +45,7 @@ def parent_painter(column, cell, model, iter):
markup = stuff.format_activity(activity_name,
category,
description,
- pad_description = True)
+ pad_description = True)
cell.set_property('markup', markup)
def duration_painter(column, cell, model, iter):
@@ -76,10 +76,10 @@ class FactTree(gtk.TreeView):
"edit-clicked": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, )),
"double-click": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, ))
}
-
+
def __init__(self):
gtk.TreeView.__init__(self)
-
+
self.set_headers_visible(False)
self.set_show_expanders(False)
@@ -101,7 +101,7 @@ class FactTree(gtk.TreeView):
tagColumn = gtk.TreeViewColumn("", tag_cell, data=6)
tagColumn.set_expand(True)
self.append_column(tagColumn)
-
+
# duration
timeColumn = gtk.TreeViewColumn()
@@ -121,10 +121,10 @@ class FactTree(gtk.TreeView):
self.connect("key-press-event", self._on_key_pressed)
self.show()
-
+
def clear(self):
self.store_model.clear()
-
+
def add_fact(self, fact, parent = None):
duration = stuff.duration_minutes(fact["delta"]) / 60
@@ -144,7 +144,7 @@ class FactTree(gtk.TreeView):
def add_group(self, group_label, facts):
total = sum([stuff.duration_minutes(fact["delta"]) for fact in facts])
-
+
# adds group of facts with the given label
group_row = self.store_model.append(None,
[-1,
@@ -154,7 +154,7 @@ class FactTree(gtk.TreeView):
"",
"",
None])
-
+
for fact in facts:
self.add_fact(fact, group_row)
@@ -166,7 +166,7 @@ class FactTree(gtk.TreeView):
def attach_model(self):
self.set_model(self.store_model)
self.expand_all()
-
+
def get_selected_fact(self):
selection = self.get_selection()
(model, iter) = selection.get_selected()
@@ -177,11 +177,11 @@ class FactTree(gtk.TreeView):
# a hackish solution to make edit icon keyboard accessible
pointer = event.window.get_pointer() # x, y, flags
path = self.get_path_at_pos(pointer[0], pointer[1]) #column, innerx, innery
-
+
if path and path[1] == self.edit_column:
self.emit("edit-clicked", self.get_selected_fact())
return True
-
+
return False
def _on_row_activated(self, tree, path, column):
@@ -197,6 +197,5 @@ class FactTree(gtk.TreeView):
and event.state & gtk.gdk.CONTROL_MASK):
self.emit("edit-clicked", self.get_selected_fact())
return True
-
+
return False
-
\ No newline at end of file
diff --git a/hamster/widgets/reportchooserdialog.py b/hamster/widgets/reportchooserdialog.py
index 8becd86..e61cea7 100644
--- a/hamster/widgets/reportchooserdialog.py
+++ b/hamster/widgets/reportchooserdialog.py
@@ -33,7 +33,7 @@ class ReportChooserDialog(gtk.Dialog):
}
def __init__(self):
gtk.Dialog.__init__(self)
-
+
self.dialog = gtk.FileChooserDialog(title = _("Save report - Time Tracker"),
parent = None,
@@ -81,7 +81,7 @@ class ReportChooserDialog(gtk.Dialog):
filter.set_name("All files")
filter.add_pattern("*")
self.dialog.add_filter(filter)
-
+
def show(self, start_date, end_date):
#set suggested name to something readable, replace backslashes with dots
@@ -89,15 +89,15 @@ class ReportChooserDialog(gtk.Dialog):
filename = "Time track %s - %s." % (start_date.strftime("%x").replace("/", "."),
end_date.strftime("%x").replace("/", "."))
self.dialog.set_current_name(filename)
-
+
response = self.dialog.run()
-
+
if response != gtk.RESPONSE_OK:
self.emit("report-chooser-closed")
self.dialog.destroy()
else:
self.on_save_button_clicked()
-
+
def present(self):
self.dialog.present()
@@ -109,13 +109,13 @@ class ReportChooserDialog(gtk.Dialog):
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 = []
# format, path, start_date, end_date
diff --git a/hamster/widgets/tags.py b/hamster/widgets/tags.py
index 0320249..945b393 100644
--- a/hamster/widgets/tags.py
+++ b/hamster/widgets/tags.py
@@ -35,20 +35,20 @@ class TagsEntry(gtk.Entry):
self.tags = None
self.filter = None # currently applied filter string
self.filter_tags = [] #filtered tags
-
+
self.popup = gtk.Window(type = gtk.WINDOW_POPUP)
self.scroll_box = gtk.ScrolledWindow()
self.scroll_box.set_shadow_type(gtk.SHADOW_IN)
self.scroll_box.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
viewport = gtk.Viewport()
viewport.set_shadow_type(gtk.SHADOW_NONE)
-
+
self.tag_box = TagBox()
self.tag_box.connect("tag-selected", self.on_tag_selected)
self.tag_box.connect("tag-unselected", self.on_tag_unselected)
self.tag_box.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(65536.0,65536.0,65536.0))
-
+
viewport.add(self.tag_box)
self.scroll_box.add(viewport)
self.popup.add(self.scroll_box)
@@ -72,7 +72,7 @@ class TagsEntry(gtk.Entry):
def on_tag_selected(self, tag_box, tag):
tags = self.get_tags()
tags.append(tag)
-
+
self.tag_box.selected_tags = tags
self.set_text(", ".join(tags))
@@ -82,13 +82,13 @@ class TagsEntry(gtk.Entry):
tags = self.get_tags()
while tag in tags: #it could be that dear user is mocking us and entering same tag over and over again
tags.remove(tag)
-
+
self.tag_box.selected_tags = tags
self.set_text(", ".join(tags))
self.set_position(len(self.get_text()))
-
-
+
+
def hide_popup(self):
self.popup.hide()
@@ -96,7 +96,7 @@ class TagsEntry(gtk.Entry):
if not self.filter_tags:
self.popup.hide()
return
-
+
alloc = self.get_allocation()
x, y = self.get_parent_window().get_origin()
@@ -105,14 +105,14 @@ class TagsEntry(gtk.Entry):
w = alloc.width
height = self.tag_box.count_height(w)
-
+
self.scroll_box.set_size_request(w, height)
self.popup.resize(w, height)
self.popup.show_all()
-
+
def complete_inline(self):
- return
+ return
def refresh_activities(self):
# scratch activities and categories so that they get repopulated on demand
@@ -123,17 +123,17 @@ class TagsEntry(gtk.Entry):
self.tags = self.tags or [tag["name"] for tag in runtime.storage.get_tags(autocomplete = True)]
cursor_tag = self.get_cursor_tag()
-
+
self.filter = cursor_tag
entered_tags = self.get_tags()
self.tag_box.selected_tags = entered_tags
-
+
self.filter_tags = [tag for tag in self.tags if (tag or "").lower().startswith((self.filter or "").lower())]
-
+
self.tag_box.draw(self.filter_tags)
-
-
+
+
def _on_focus_out_event(self, widget, event):
self.hide_popup()
@@ -159,11 +159,11 @@ class TagsEntry(gtk.Entry):
else:
self.populate_suggestions()
self.show_popup()
-
+
if event.keyval not in (gtk.keysyms.Delete, gtk.keysyms.BackSpace):
self.complete_inline()
-
+
def get_cursor_tag(self):
#returns the tag on which the cursor is on right now
if self.get_selection_bounds():
@@ -185,7 +185,7 @@ class TagsEntry(gtk.Entry):
cursor = self.get_selection_bounds()[0]
else:
cursor = self.get_position()
-
+
self.set_text(", ".join(tags))
self.set_position(cursor + len(new_tag)-len(old_tag)) # put the cursor back
@@ -216,7 +216,7 @@ class TagBox(graphics.Area):
self.tags = []
self.selected_tags = []
graphics.Area.__init__(self)
-
+
self.font_size = 10 #override default font size
if self.interactive:
@@ -228,9 +228,9 @@ class TagBox(graphics.Area):
self.hover_tag = regions[0]
else:
self.hover_tag = None
-
+
self.redraw_canvas()
-
+
def on_tag_click(self, widget, regions):
tag = regions[0]
if tag in self.selected_tags:
@@ -241,11 +241,11 @@ class TagBox(graphics.Area):
self.emit("tag-selected", tag)
self.redraw_canvas()
-
+
def draw(self, tags):
"""Draw chart with given data"""
self.tags = tags
- self.show()
+ self.show()
self.redraw_canvas()
def tag_size(self, label):
@@ -275,10 +275,10 @@ class TagBox(graphics.Area):
if cur_x + w >= width - 5: #if we do not fit, we wrap
cur_x = 5
cur_y += h + 6
-
+
cur_x += w + 8 #some padding too, please
return cur_y + h + 6
-
+
def on_expose(self):
cur_x, cur_y = 4, 4
for tag in self.tags:
@@ -286,21 +286,21 @@ class TagBox(graphics.Area):
if cur_x + w >= self.width - 5: #if we do not fit, we wrap
cur_x = 5
cur_y += h + 6
-
+
if tag in self.selected_tags:
color = (242, 229, 97)
elif tag == self.hover_tag:
color = (252, 248, 204)
else:
color = (241, 234, 170)
-
+
Tag(self.context,
self.layout,
True,
tag,
color,
- gtk.gdk.Rectangle(cur_x, cur_y, self.width - cur_x, self.height - cur_y))
-
+ gtk.gdk.Rectangle(cur_x, cur_y, self.width - cur_x, self.height - cur_y))
+
if self.interactive:
self.register_mouse_region(cur_x, cur_y, cur_x + w, cur_y + h, tag)
@@ -311,33 +311,33 @@ class TagBox(graphics.Area):
# wish he could write tutorials
class TagCellRenderer(gtk.GenericCellRenderer):
__gproperties__ = {
- "data": (gobject.TYPE_PYOBJECT, "Data", "Data", gobject.PARAM_READWRITE),
+ "data": (gobject.TYPE_PYOBJECT, "Data", "Data", gobject.PARAM_READWRITE),
}
-
+
def __init__(self):
gtk.GenericCellRenderer.__init__(self)
self.height = 0
self.width = None
self.data = None
-
+
self._font = pango.FontDescription(gtk.Style().font_desc.to_string())
self._font_size = 10
self.layout = None
-
+
def font_size(self):
return self._font_size
def set_font_size(self, val):
self._font_size = val
self._font.set_size(pango.SCALE * self._font_size)
-
-
+
+
def do_set_property (self, pspec, value):
setattr (self, pspec.name, value)
-
+
def do_get_property (self, pspec):
return getattr (self, pspec.name)
-
+
def tag_size(self, label):
text_w, text_h = self.set_text(label)
@@ -355,7 +355,7 @@ class TagCellRenderer(gtk.GenericCellRenderer):
if not self.data: return
self.width = cell_area.width
-
+
context = window.cairo_create()
if isinstance(self.data, dict):
@@ -366,25 +366,25 @@ class TagCellRenderer(gtk.GenericCellRenderer):
x, y, width, h = cell_area
context.translate(x, y)
-
+
if not self.layout:
self.layout = context.create_layout()
self.layout.set_font_description(self._font)
-
+
cur_x, cur_y = 4, 2
for tag in tags:
w, h = self.tag_size(tag)
if cur_x + w >= self.width - 5: #if we do not fit, we wrap
cur_x = 5
cur_y += h + 6
-
+
Tag(context,
self.layout,
True,
tag,
None,
- gtk.gdk.Rectangle(cur_x, cur_y, self.width - cur_x, self.height - cur_y))
-
+ gtk.gdk.Rectangle(cur_x, cur_y, self.width - cur_x, self.height - cur_y))
+
cur_x += w + 6 #some padding too, please
@@ -420,20 +420,20 @@ class TagCellRenderer(gtk.GenericCellRenderer):
if cur_x + w >= self.width - 5: #if we do not fit, we wrap
cur_x = 5
cur_y += h + 6
-
+
cur_x += w + 6 #some padding too, please
cur_y += h + 3
-
+
self.height = cur_y # TODO - this should actually trigger whole tree redraw if heights do not match
-
+
return (0, 0, min_width, self.height)
-
+
class Tag(object):
def __init__(self, context, layout, render_now = False, label = None, color = None, rect = None):
self.font_size = 10
-
+
if not context:
render_now = False
else:
@@ -441,10 +441,10 @@ class Tag(object):
self.layout = layout
self.x, self.y, self.width, self.height = rect.x, rect.y, rect.width, rect.height
-
+
if not render_now:
return
-
+
self.label = label
self.color = color or (241, 234, 170)
@@ -484,9 +484,9 @@ class Tag(object):
if x - round(x) != 0.5: x += 0.5
if y - round(y) != 0.5: y += 0.5
- w, h = self.tag_size(label)
+ w, h = self.tag_size(label)
corner = h / 3
-
+
self.context.move_to(x, y + corner)
self.context.line_to(x + corner, y)
self.context.line_to(x + w, y)
@@ -494,7 +494,7 @@ class Tag(object):
self.context.line_to(x + corner, y + h)
self.context.line_to(x, y + h - corner)
self.context.line_to(x, y + corner)
-
+
self.set_color(color)
self.context.fill_preserve()
self.set_color((180, 180, 180))
@@ -510,7 +510,7 @@ class Tag(object):
#self.layout.set_width((self.width) * pango.SCALE)
self.context.move_to(x + 12,y)
-
+
self.set_color((30, 30, 30))
self.context.show_layout(self.layout)
diff --git a/hamster/widgets/timeinput.py b/hamster/widgets/timeinput.py
index 7dbffed..f45168d 100644
--- a/hamster/widgets/timeinput.py
+++ b/hamster/widgets/timeinput.py
@@ -78,21 +78,21 @@ class TimeInput(gtk.Entry):
def set_time(self, time):
self.time = time
self.set_text(self._format_time(time))
-
+
def _on_text_changed(self, widget):
self.news = True
-
+
def figure_time(self, str_time):
if not str_time:
return self.time
-
+
# strip everything non-numeric and consider hours to be first number
# and minutes - second number
numbers = re.split("\D", str_time)
numbers = filter(lambda x: x!="", numbers)
-
+
hours, minutes = None, None
-
+
if len(numbers) == 1 and len(numbers[0]) == 4:
hours, minutes = int(numbers[0][:2]), int(numbers[0][2:])
else:
@@ -100,10 +100,10 @@ class TimeInput(gtk.Entry):
hours = int(numbers[0])
if len(numbers) >= 2:
minutes = int(numbers[1])
-
+
if (hours is None or minutes is None) or hours > 24 or minutes > 60:
return self.time #no can do
-
+
return dt.datetime.now().replace(hour = hours, minute = minutes,
second = 0, microsecond = 0)
@@ -111,15 +111,15 @@ class TimeInput(gtk.Entry):
def _select_time(self, time_text):
#convert forth and back so we have text formated as we want
time = self.figure_time(time_text)
- time_text = self._format_time(time)
-
+ time_text = self._format_time(time)
+
self.set_text(time_text)
self.set_position(len(time_text))
self.popup.hide()
if self.news:
self.emit("time-entered")
self.news = False
-
+
def get_time(self):
self.time = self.figure_time(self.get_text())
self.set_text(self._format_time(self.time))
@@ -128,10 +128,10 @@ class TimeInput(gtk.Entry):
def _format_time(self, time):
if time is None:
return None
-
+
#return time.strftime("%I:%M%p").lstrip("0").lower()
return time.strftime("%H:%M").lower()
-
+
def _on_focus_in_event(self, entry, event):
self.show_popup()
@@ -144,41 +144,41 @@ class TimeInput(gtk.Entry):
if self.news:
self.emit("time-entered")
self.news = False
-
+
def show_popup(self):
focus_time = self.figure_time(self.get_text())
-
+
hours = gtk.ListStore(gobject.TYPE_STRING)
-
+
# populate times
i_time = self.start_time or dt.datetime(1900, 1, 1, 0, 0)
-
+
if focus_time and focus_time < i_time:
focus_time += dt.timedelta(days = 1)
-
+
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(hours = 24)
-
+
i, focus_row = 0, None
-
+
while i_time < end_time:
row_text = self._format_time(i_time)
if self.start_time:
delta = (i_time - self.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:
@@ -186,13 +186,13 @@ class TimeInput(gtk.Entry):
i += 1
- self.time_tree.set_model(hours)
+ self.time_tree.set_model(hours)
#focus on row
if focus_row != None:
self.time_tree.set_cursor(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
@@ -206,19 +206,19 @@ class TimeInput(gtk.Entry):
self.popup.move(x + alloc.x,y + alloc.y + alloc.height)
self.popup.show_all()
-
+
def _on_time_tree_button_press_event(self, tree, event):
model, iter = tree.get_selection().get_selected()
time = model.get_value(iter, 0)
self._select_time(time)
-
-
+
+
def _on_key_press_event(self, entry, event):
cursor = self.time_tree.get_cursor()
if not cursor or not cursor[0]:
return
-
+
i = cursor[0][0]
if event.keyval == gtk.keysyms.Up:
@@ -227,7 +227,7 @@ class TimeInput(gtk.Entry):
i+=1
elif (event.keyval == gtk.keysyms.Return or
event.keyval == gtk.keysyms.KP_Enter):
-
+
if self.popup.get_property("visible"):
self._select_time(self.time_tree.get_model()[i][0])
else:
@@ -238,13 +238,13 @@ class TimeInput(gtk.Entry):
#any kind of other input
self.popup.hide()
return False
-
+
# keep it in the sane borders
i = min(max(i, 0), len(self.time_tree.get_model()) - 1)
-
+
self.time_tree.set_cursor(i)
self.time_tree.scroll_to_cell(i, use_align = True, row_align = 0.4)
return True
-
+
diff --git a/hamster/widgets/timeline.py b/hamster/widgets/timeline.py
index 982358c..cab33af 100644
--- a/hamster/widgets/timeline.py
+++ b/hamster/widgets/timeline.py
@@ -35,24 +35,24 @@ MONTH = dt.timedelta(30)
class TimeLine(graphics.Area):
"""this widget is kind of half finished"""
-
+
def __init__(self):
graphics.Area.__init__(self)
self.start_time, self.end_time = None, None
self.facts = []
self.day_start = GconfStore().get_day_start()
self.first_weekday = stuff.locale_first_weekday()
-
+
self.minor_tick = None
-
+
self.tick_totals = []
-
+
self.bar_color = "#ccc"
-
+
def draw(self, facts, start_date, end_date):
self.facts = facts
-
+
if start_date > end_date:
start_date, end_date = end_date, start_date
@@ -65,7 +65,7 @@ class TimeLine(graphics.Area):
if facts:
fact_start_time = facts[0]["start_time"]
fact_end_time = facts[-1]["start_time"] + facts[-1]["delta"]
-
+
self.start_time = min([start_time, fact_start_time])
self.end_time = max([end_time, fact_end_time])
@@ -77,14 +77,14 @@ class TimeLine(graphics.Area):
if facts:
fact_start_time = dt.datetime.combine(facts[0]["date"], dt.time())
fact_end_time = dt.datetime.combine(facts[-1]["date"], dt.time())
-
+
self.start_time = min([start_time, fact_start_time])
self.end_time = max([end_time, fact_end_time])
days = (self.end_time - self.start_time).days
-
+
# determine fraction and do addittional start time move
if days > 125: # about 4 month -> show per month
@@ -108,7 +108,7 @@ class TimeLine(graphics.Area):
self.minor_tick = dt.timedelta(seconds = 60 * 60)
self.count_hours()
-
+
self.redraw_canvas()
@@ -119,7 +119,7 @@ class TimeLine(graphics.Area):
graph_x = 2
graph_width = self.width - graph_x - 2
-
+
total_minutes = stuff.duration_minutes(self.end_time - self.start_time)
bar_width = float(graph_width) / len(self.tick_totals)
@@ -131,16 +131,16 @@ class TimeLine(graphics.Area):
major_step = dt.timedelta(seconds = 60 * 60)
else:
major_step = dt.timedelta(days=1)
-
+
x = graph_x
major_tick_step = graph_width / (total_minutes / float(stuff.duration_minutes(major_step)))
current_time = self.start_time
-
+
def first_weekday(date):
return (date.weekday() + 1 - self.first_weekday) % 7 == 0
-
+
# calculate position of each bar
# essentially we care more about the exact 1px gap between bars than about the bar width
# so after each iteration, we adjust the bar width
@@ -148,7 +148,7 @@ class TimeLine(graphics.Area):
exes = {}
adapted_bar_width = bar_width
for i, (current_time, total) in enumerate(self.tick_totals):
-
+
# move the x bit further when ticks kick in
if (major_step < DAY and current_time.time() == dt.time(0,0)) \
or (self.minor_tick == DAY and first_weekday(current_time)) \
@@ -159,7 +159,7 @@ class TimeLine(graphics.Area):
exes[current_time] = (x, round(adapted_bar_width)) #saving those as getting pixel precision is not an exact science
x = round(x + adapted_bar_width)
adapted_bar_width = (self.width - x) / float(max(len(self.tick_totals) - i - 1, 1))
-
+
@@ -168,7 +168,7 @@ class TimeLine(graphics.Area):
self.set_color(color)
self.context.line_to(round(x) + 0.5, self.height)
self.context.stroke()
-
+
def somewhere_in_middle(time, color):
# draws line somewhere in middle of the minor tick
left_index = exes.keys()[bisect(exes.keys(), time) - 1]
@@ -176,17 +176,17 @@ class TimeLine(graphics.Area):
adjustment = stuff.duration_minutes(time - left_index) / float(stuff.duration_minutes(self.minor_tick))
x, width = exes[left_index]
line(x + round(width * adjustment) - 1, color)
-
-
+
+
# mark tick lines
current_time = self.start_time
while current_time < self.end_time:
current_time += major_step
x += major_tick_step
-
+
if current_time >= self.end_time: # TODO - fix the loop so we do not have to break
break
-
+
if major_step < DAY: # about the same day
if current_time.time() == dt.time(0,0): # midnight
line(exes[current_time][0] - 2, "#bbb")
@@ -194,15 +194,15 @@ class TimeLine(graphics.Area):
if self.minor_tick == DAY: # week change
if first_weekday(current_time):
line(exes[current_time][0] - 2, "#bbb")
-
+
if self.minor_tick <= WEEK: # month change
if current_time.day == 1:
if current_time in exes:
line(exes[current_time][0] - 2, "#bbb")
else: #if we are somewhere in middle then it gets a bit more complicated
somewhere_in_middle(current_time, "#bbb")
-
- # year change
+
+ # year change
if current_time.timetuple().tm_yday == 1: # year change
if current_time in exes:
line(exes[current_time][0] - 2, "#f00")
@@ -210,7 +210,7 @@ class TimeLine(graphics.Area):
somewhere_in_middle(current_time, "#f00")
- # the bars
+ # the bars
for i, (current_time, total) in enumerate(self.tick_totals):
bar_size = max(round(self.height * total * 0.9), 1)
x, bar_width = exes[current_time]
@@ -232,7 +232,7 @@ class TimeLine(graphics.Area):
step_format = "%b %d"
else:
step_format = "%a"
- else:
+ else:
step_format = "%H<small><sup>%M</sup></small>"
@@ -241,22 +241,22 @@ class TimeLine(graphics.Area):
if (self.end_time - self.start_time) > dt.timedelta(10) \
and self.minor_tick == DAY and first_weekday(current_time) == False:
continue
-
+
x, bar_width = exes[current_time]
self.set_color("#666")
self.layout.set_width(int((self.width - x) * pango.SCALE))
self.layout.set_markup(current_time.strftime(step_format))
w, h = self.layout.get_pixel_size()
-
+
self.context.move_to(x + 2, self.height - h - 2)
self.context.show_layout(self.layout)
-
+
def count_hours(self):
#go through facts and make array of time used by our fraction
fractions = []
-
+
current_time = self.start_time
minor_tick = self.minor_tick
@@ -264,20 +264,20 @@ class TimeLine(graphics.Area):
# if minor tick is month, the starting date will have been
# already adjusted to the first
# now we have to make sure to move month by month
- if self.minor_tick >= dt.timedelta(days=28):
+ if self.minor_tick >= dt.timedelta(days=28):
minor_tick = dt.timedelta(calendar.monthrange(current_time.year, current_time.month)[1]) # days in month
-
+
fractions.append(current_time)
current_time += minor_tick
-
+
hours = [0] * len(fractions)
-
+
tick_minutes = float(stuff.duration_minutes(self.minor_tick))
-
+
for fact in self.facts:
if self.minor_tick < dt.timedelta(1):
end_time = fact["start_time"] + fact["delta"] # the thing about ongoing task - it has no end time
-
+
# find in which fraction the fact starts and
# add duration up to the border of tick to that fraction
# then move cursor to the start of next fraction
@@ -285,16 +285,16 @@ class TimeLine(graphics.Area):
step_time = fractions[first_index]
first_end = min(end_time, step_time + self.minor_tick)
first_tick = stuff.duration_minutes(first_end - fact["start_time"]) / tick_minutes
-
+
hours[first_index] += first_tick
step_time = step_time + self.minor_tick
-
+
# now go through ticks until we reach end of the time
while step_time < end_time:
index = bisect(fractions, step_time) - 1
interval = min([1, stuff.duration_minutes(end_time - step_time) / tick_minutes])
hours[index] += interval
-
+
step_time += self.minor_tick
else:
hour_index = bisect(fractions, dt.datetime.combine(fact["date"], dt.time())) - 1
diff --git a/tests/charting_test.py b/tests/charting_test.py
index bf369b9..b6f64ed 100644
--- a/tests/charting_test.py
+++ b/tests/charting_test.py
@@ -24,12 +24,12 @@ class TestIteratorFunctions(unittest.TestCase):
# target is reached
integrator = graphics.Integrator(0)
integrator.target(10)
-
+
while integrator.update():
pass
self.assertEquals(round(integrator.value, 0), 10)
-
+
class TestSizeListFunctions(unittest.TestCase):
def test_values_stay(self):
# on shrinkage, values are kept
diff --git a/tests/hamsterdbus_test.py b/tests/hamsterdbus_test.py
index ed30d3a..70879c2 100644
--- a/tests/hamsterdbus_test.py
+++ b/tests/hamsterdbus_test.py
@@ -10,7 +10,7 @@ from hamster.hamsterdbus import HAMSTER_PATH, HAMSTER_URI
def rndstr(length=10):
result = ''
- for i in range(1,10):
+ for i in range(1,10):
result += choice(letters)
return result
@@ -39,19 +39,19 @@ class TestTracking(unittest.TestCase):
activity = fact['name'] + '@' + fact['category'] + ',' + \
fact['description']
fact_id = self.addfact(activity, fact['start_time'], fact['end_time'])
- self.failUnless(type(fact_id) == dbus.Int32 and fact_id != 0,
+ self.failUnless(type(fact_id) == dbus.Int32 and fact_id != 0,
'expected non-zero dbus.Int32 as return value')
dbfact = self.getfactbyid(fact_id)
- self.assertEqual(fact['name'], dbfact['name'],
+ self.assertEqual(fact['name'], dbfact['name'],
'expected same activity name')
- self.assertEqual(fact['category'], dbfact['category'],
+ self.assertEqual(fact['category'], dbfact['category'],
'expected same category name')
- self.assertEqual(fact['description'], dbfact['description'],
+ self.assertEqual(fact['description'], dbfact['description'],
'expected same description name')
- self.assertEqual(fact['start_time'], dbfact['start_time'],
+ self.assertEqual(fact['start_time'], dbfact['start_time'],
'expected same start_time')
- self.assertEqual(fact['end_time'], dbfact['end_time'],
+ self.assertEqual(fact['end_time'], dbfact['end_time'],
'expected same end_time')
facts = self.getfacts(dbfact['start_time'], dbfact['end_time'])
@@ -82,19 +82,19 @@ class TestTracking(unittest.TestCase):
activity = fact['name'] + '@' + fact['category'] + ',' + \
fact['description']
fact_id = self.addfact(activity, fact['start_time'], 0)
- self.failUnless(type(fact_id) == dbus.Int32 and fact_id != 0,
+ self.failUnless(type(fact_id) == dbus.Int32 and fact_id != 0,
'expected non-zero dbus.Int32 as return value')
current = self.getcurrentfact()
- self.assertEqual(fact['name'], current['name'],
+ self.assertEqual(fact['name'], current['name'],
'expected same activity name')
- self.assertEqual(fact['category'], current['category'],
+ self.assertEqual(fact['category'], current['category'],
'expected same category name')
- self.assertEqual(fact['description'], current['description'],
+ self.assertEqual(fact['description'], current['description'],
'expected same description name')
- self.assertEqual(fact['start_time'], current['start_time'],
+ self.assertEqual(fact['start_time'], current['start_time'],
'expected same start_time')
- self.assertEqual(0, current['end_time'],
+ self.assertEqual(0, current['end_time'],
'expected same end_time')
self.stoptracking()
@@ -140,9 +140,9 @@ class TestTracking(unittest.TestCase):
def __rndfactgenerator(self, name=None, category=None, description=None,
start_time=None, end_time=None):
- fact = {'name':name or rndstr(), 'category':category or rndstr(),
- 'description':description or rndstr(),
- 'start_time':start_time or timegm(dt.datetime.now().timetuple()),
+ fact = {'name':name or rndstr(), 'category':category or rndstr(),
+ 'description':description or rndstr(),
+ 'start_time':start_time or timegm(dt.datetime.now().timetuple()),
'end_time':end_time or timegm((dt.datetime.now() + \
dt.timedelta(hours=1)).timetuple())}
return fact
diff --git a/tests/stuff_test.py b/tests/stuff_test.py
index da4e8b7..dd690b4 100644
--- a/tests/stuff_test.py
+++ b/tests/stuff_test.py
@@ -20,7 +20,7 @@ class TestActivityInputParsing(unittest.TestCase):
self.assertEquals(activity.activity_name, "with start time")
self.assertEquals(activity.start_time.strftime("%H:%M"), "12:35")
- #rest must be empty
+ #rest must be empty
assert activity.category_name is None \
and activity.end_time is None and activity.category_name is None\
and activity.description is None
@@ -32,7 +32,7 @@ class TestActivityInputParsing(unittest.TestCase):
self.assertEquals(activity.start_time.strftime("%H:%M"), "12:35")
self.assertEquals(activity.end_time.strftime("%H:%M"), "14:25")
- #rest must be empty
+ #rest must be empty
assert activity.category_name is None \
and activity.category_name is None\
and activity.description is None
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]