46
58
retval = locale_str
61
def locale_first_weekday():
62
"""figure if week starts on monday or sunday"""
63
first_weekday = 6 #by default settle on monday
66
process = os.popen("locale first_weekday week-1stday")
67
week_offset, week_start = process.read().split('\n')[:2]
69
week_start = dt.date(*time.strptime(week_start, "%Y%m%d")[:3])
70
week_offset = dt.timedelta(int(week_offset) - 1)
71
beginning = week_start + week_offset
72
first_weekday = int(beginning.strftime("%w"))
74
print "WARNING - Failed to get first weekday from locale"
49
79
class CategoryCell(gtk.CellRendererText):
50
80
def __init__(self):
51
81
gtk.CellRendererText.__init__(self)
56
86
self.set_property('scale', pango.SCALE_SMALL)
57
87
self.set_property('yalign', 0.0)
59
class ExpanderColumn(gtk.TreeViewColumn):
60
def __init__(self, label, text):
61
gtk.TreeViewColumn.__init__(self, label)
90
insensitive_color = gtk.Label().style.fg[gtk.STATE_INSENSITIVE].to_string()
91
def format_activity(name, category, description, pad_description = False):
92
"returns pango markup for activity with category and description"
94
if category and category != _("Unsorted"):
95
text += """ - <span color="%s" size="x-small">%s</span>""" % (insensitive_color, category)
102
text += """<span style="italic" size="small">%s</span>""" % description
107
class ActivityColumn(gtk.TreeViewColumn):
108
def activity_painter(self, column, cell, model, iter):
109
activity_name = model.get_value(iter, self.name)
110
description = model.get_value(iter, self.description)
111
category = model.get_value(iter, self.category)
113
markup = format_activity(activity_name, category, description)
114
cell.set_property('markup', markup)
117
def __init__(self, name, description, category = None):
118
gtk.TreeViewColumn.__init__(self)
120
self.name, self.description, self.category = name, description, category
63
121
self.set_expand(True)
64
122
cell = gtk.CellRendererText()
65
cell.set_property('ellipsize', ELLIPSIZE_END)
66
123
self.pack_start(cell, True)
67
self.set_attributes(cell, text=text)
70
def format_duration(minutes):
72
return "%02d:%02d" % (minutes / 60, minutes % 60)
124
cell.set_property("ellipsize", pango.ELLIPSIZE_END)
125
self.set_cell_data_func(cell, self.activity_painter)
127
def duration_minutes(duration):
128
"""returns minutes from duration, otherwise we keep bashing in same math"""
129
return duration.seconds / 60 + duration.days * 24 * 60
131
def format_duration(minutes, human = True):
132
"""formats duration in a human readable format.
133
accepts either minutes or timedelta"""
135
if isinstance(minutes, dt.timedelta):
136
minutes = duration_minutes(minutes)
145
minutes = minutes % 60
146
formatted_duration = ""
149
if minutes % 60 == 0:
150
# duration in round hours
151
formatted_duration += _("%dh") % (hours)
153
# duration less than hour
154
formatted_duration += _("%dmin") % (minutes % 60.0)
157
formatted_duration += _("%dh %dmin") % (hours, minutes % 60)
76
def dateDict(date, prefix):
159
formatted_duration += "%02d:%02d" % (hours, minutes)
162
return formatted_duration
164
def totals(iter, keyfunc, sumfunc):
165
"""groups items by field described in keyfunc and counts totals using value
168
data = sorted(iter, key=keyfunc)
171
for k, group in groupby(data, keyfunc):
172
res[k] = sum([sumfunc(entry) for entry in group])
179
def dateDict(date, prefix = ""):
77
180
"""converts date into dictionary, having prefix for all the keys"""
113
216
text = text.replace(">", ">")
117
class DayStore(object):
118
"""A day view contains a treeview for facts of the day and another
119
one for totals. It creates those widgets on init, use
120
fill_view(store) to fill the tree and calculate totals """
122
def __init__(self, date = None):
123
date = date or dt.date.today()
125
# ID, Time, Name, Duration, Date, Description
126
self.fact_store = gtk.ListStore(int, str, str, str, str, str)
128
self.facts = storage.get_facts(date)
131
for fact in self.facts:
134
if fact["end_time"]: # not set if just started
135
delta = fact["end_time"] - fact["start_time"]
136
duration = 24 * delta.days + delta.seconds / 60
137
elif fact["start_time"].date() == dt.date.today(): # give duration to today's last activity
138
delta = dt.datetime.now() - fact["start_time"]
139
duration = 24 * delta.days + delta.seconds / 60
141
fact_category = fact['category']
143
if fact_category not in self.totals:
144
self.totals[fact_category] = 0
147
self.totals[fact_category] += duration
149
current_duration = format_duration(duration)
151
self.fact_store.append([fact['id'], escape_pango(fact['name']),
152
fact["start_time"].strftime("%H:%M"),
154
fact["start_time"].strftime("%Y%m%d"),
155
escape_pango(fact["description"])])
219
def figure_time(str_time):
223
# strip everything non-numeric and consider hours to be first number
224
# and minutes - second number
225
numbers = re.split("\D", str_time)
226
numbers = filter(lambda x: x!="", numbers)
228
hours, minutes = None, None
230
if len(numbers) == 1 and len(numbers[0]) == 4:
231
hours, minutes = int(numbers[0][:2]), int(numbers[0][2:])
233
if len(numbers) >= 1:
234
hours = int(numbers[0])
235
if len(numbers) >= 2:
236
minutes = int(numbers[1])
238
if (hours is None or minutes is None) or hours > 24 or minutes > 60:
239
return None #no can do
241
return dt.datetime.now().replace(hour = hours, minute = minutes,
242
second = 0, microsecond = 0)
244
def parse_activity_input(text):
245
"""Currently pretty braindead function that tries to parse arbitrary input
247
class InputParseResult(object):
249
self.activity_name = None
250
self.category_name = None
251
self.start_time = None
253
self.description = None
257
res = InputParseResult()
259
input_parts = text.split(" ")
260
if len(input_parts) > 1 and re.match('^-?\d', input_parts[0]): #look for time only if there is more
261
potential_time = text.split(" ")[0]
262
potential_end_time = None
263
if len(potential_time) > 1 and potential_time.startswith("-"):
264
#if starts with minus, treat as minus delta minutes
265
res.start_time = dt.datetime.now() + dt.timedelta(minutes =
269
if potential_time.find("-") > 0:
270
potential_time, potential_end_time = potential_time.split("-", 2)
271
res.end_time = figure_time(potential_end_time)
273
res.start_time = figure_time(potential_time)
275
#remove parts that worked
276
if res.start_time and potential_end_time and not res.end_time:
277
res.start_time = None #scramble
279
text = text[text.find(" ")+1:]
281
#see if we have description of activity somewhere here (delimited by comma)
282
if text.find(",") > 0:
283
text, res.description = text.split(",", 1)
284
res.description = res.description.strip()
286
if text.find("@") > 0:
287
text, res.category_name = text.split("@", 1)
288
res.category_name = res.category_name.strip()
290
#only thing left now is the activity name itself
291
res.activity_name = text
293
#this is most essential
294
if (text.find("bbq") > -1 or text.find("barbeque") > -1
295
or text.find("barbecue") > -1) and text.find("omg") > -1:
296
res.description = "[ponies = 1], [rainbows = 0]"