3
# Copyright (C) 2008 Toms Bauģis <toms.baugis at gmail.com>
5
# This file is part of Project Hamster.
7
# Project Hamster is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
12
# Project Hamster is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with Project Hamster. If not, see <http://www.gnu.org/licenses/>.
21
# some widgets that repeat all over the place
22
# cells, columns, trees and other
27
from pango import ELLIPSIZE_END
29
from itertools import groupby
36
def format_duration(minutes, human = True):
37
"""formats duration in a human readable format.
38
accepts either minutes or timedelta"""
40
if isinstance(minutes, dt.timedelta):
41
minutes = duration_minutes(minutes)
50
minutes = minutes % 60
51
formatted_duration = ""
55
# duration in round hours
56
formatted_duration += _("%dh") % (hours)
58
# duration less than hour
59
formatted_duration += _("%dmin") % (minutes % 60.0)
62
formatted_duration += _("%dh %dmin") % (hours, minutes % 60)
64
formatted_duration += "%02d:%02d" % (hours, minutes)
67
return formatted_duration
69
def duration_minutes(duration):
70
"""returns minutes from duration, otherwise we keep bashing in same math"""
71
return duration.seconds / 60 + duration.days * 24 * 60
74
def load_ui_file(name):
75
from configuration import runtime
77
ui.add_from_file(os.path.join(runtime.data_dir, name))
81
return dt.datetime.combine(date.date(), dt.time(0,0))
83
# it seems that python or something has bug of sorts, that breaks stuff for
84
# japanese locale, so we have this locale from and to ut8 magic in some places
86
def locale_from_utf8(utf8_str):
88
retval = unicode (utf8_str, "utf-8").encode(locale.getpreferredencoding())
93
def locale_to_utf8(locale_str):
95
retval = unicode (locale_str, locale.getpreferredencoding()).encode("utf-8")
100
def locale_first_weekday():
101
"""figure if week starts on monday or sunday"""
102
first_weekday = 6 #by default settle on monday
105
process = os.popen("locale first_weekday week-1stday")
106
week_offset, week_start = process.read().split('\n')[:2]
108
week_start = dt.date(*time.strptime(week_start, "%Y%m%d")[:3])
109
week_offset = dt.timedelta(int(week_offset) - 1)
110
beginning = week_start + week_offset
111
first_weekday = int(beginning.strftime("%w"))
113
logging.warn("WARNING - Failed to get first weekday from locale")
117
class CategoryCell(gtk.CellRendererText):
119
gtk.CellRendererText.__init__(self)
120
self.set_property('alignment', pango.ALIGN_RIGHT)
122
insensitive_color = gtk.Label().style.fg[gtk.STATE_INSENSITIVE]
123
self.set_property('foreground-gdk', insensitive_color)
124
self.set_property('scale', pango.SCALE_SMALL)
125
self.set_property('yalign', 0.0)
128
insensitive_color = gtk.Label().style.fg[gtk.STATE_INSENSITIVE].to_string()
129
def format_activity(name, category, description, pad_description = False):
130
"returns pango markup for activity with category and description"
132
if category and category != _("Unsorted"):
133
text += """ - <span color="%s" size="x-small">%s</span>""" % (insensitive_color, category)
140
text += """<span style="italic" size="small">%s</span>""" % description
145
def totals(iter, keyfunc, sumfunc):
146
"""groups items by field described in keyfunc and counts totals using value
149
data = sorted(iter, key=keyfunc)
152
for k, group in groupby(data, keyfunc):
153
res[k] = sum([sumfunc(entry) for entry in group])
160
def dateDict(date, prefix = ""):
161
"""converts date into dictionary, having prefix for all the keys"""
164
res[prefix+"a"] = date.strftime("%a")
165
res[prefix+"A"] = date.strftime("%A")
166
res[prefix+"b"] = date.strftime("%b")
167
res[prefix+"B"] = date.strftime("%B")
168
res[prefix+"c"] = date.strftime("%c")
169
res[prefix+"d"] = date.strftime("%d")
170
res[prefix+"H"] = date.strftime("%H")
171
res[prefix+"I"] = date.strftime("%I")
172
res[prefix+"j"] = date.strftime("%j")
173
res[prefix+"m"] = date.strftime("%m")
174
res[prefix+"M"] = date.strftime("%M")
175
res[prefix+"p"] = date.strftime("%p")
176
res[prefix+"S"] = date.strftime("%S")
177
res[prefix+"U"] = date.strftime("%U")
178
res[prefix+"w"] = date.strftime("%w")
179
res[prefix+"W"] = date.strftime("%W")
180
res[prefix+"x"] = date.strftime("%x")
181
res[prefix+"X"] = date.strftime("%X")
182
res[prefix+"y"] = date.strftime("%y")
183
res[prefix+"Y"] = date.strftime("%Y")
184
res[prefix+"Z"] = date.strftime("%Z")
186
for i, value in res.items():
187
res[i] = locale_to_utf8(value)
191
def escape_pango(text):
195
text = text.replace ("&", "&")
196
text = text.replace("<", "<")
197
text = text.replace(">", ">")
200
def figure_time(str_time):
201
if not str_time or not str_time.strip():
204
# strip everything non-numeric and consider hours to be first number
205
# and minutes - second number
206
numbers = re.split("\D", str_time)
207
numbers = filter(lambda x: x!="", numbers)
209
hours, minutes = None, None
211
if len(numbers) == 1 and len(numbers[0]) == 4:
212
hours, minutes = int(numbers[0][:2]), int(numbers[0][2:])
214
if len(numbers) >= 1:
215
hours = int(numbers[0])
216
if len(numbers) >= 2:
217
minutes = int(numbers[1])
219
if (hours is None or minutes is None) or hours > 24 or minutes > 60:
220
return None #no can do
222
return dt.datetime.now().replace(hour = hours, minute = minutes,
223
second = 0, microsecond = 0)
225
def parse_activity_input(text):
226
"""Currently pretty braindead function that tries to parse arbitrary input
228
class InputParseResult(object):
230
self.activity_name = None
231
self.category_name = None
232
self.start_time = None
234
self.description = None
238
res = InputParseResult()
240
input_parts = text.split(" ")
241
if len(input_parts) > 1 and re.match('^-?\d', input_parts[0]): #look for time only if there is more
242
potential_time = text.split(" ")[0]
243
potential_end_time = None
244
if len(potential_time) > 1 and potential_time.startswith("-"):
245
#if starts with minus, treat as minus delta minutes
246
res.start_time = dt.datetime.now() + dt.timedelta(minutes =
250
if potential_time.find("-") > 0:
251
potential_time, potential_end_time = potential_time.split("-", 2)
252
res.end_time = figure_time(potential_end_time)
254
res.start_time = figure_time(potential_time)
256
#remove parts that worked
257
if res.start_time and potential_end_time and not res.end_time:
258
res.start_time = None #scramble
260
text = text[text.find(" ")+1:]
262
if text.find("@") > 0:
263
text, res.category_name = text.split("@", 1)
264
res.category_name = res.category_name.strip()
266
#only thing left now is the activity name itself
267
res.activity_name = text
269
#this is most essential
270
if (text.find("bbq") > -1 or text.find("barbeque") > -1
271
or text.find("barbecue") > -1) and text.find("omg") > -1:
272
res.description = "[ponies = 1], [rainbows = 0]"