~kklimonda/ubuntu/natty/hamster-applet/lp.697667

« back to all changes in this revision

Viewing changes to hamster/stuff.py

  • Committer: Bazaar Package Importer
  • Author(s): Chris Coulson
  • Date: 2010-02-10 02:52:31 UTC
  • mfrom: (1.1.14 upstream)
  • Revision ID: james.westby@ubuntu.com-20100210025231-x0q5h4q7nlvihl09
Tags: 2.29.90-0ubuntu1
* New upstream version
  - workspace tracking - switch activity, when switching desktops
  - chart improvements - theme friendly and less noisier
  - for those without GNOME panel there is now a standalone version, 
    accessible via Applications -> Accessories -> Time Tracker
  - overview window remembers position
  - maintaining cursor on the selected row after edits / refreshes
  - descriptions once again in the main input field, delimited by comma
  - activity suggestion box now sorts items by recency (Patryk Zawadzki)
  - searching
  - simplified save report dialog, thanks to the what you see is what you 
    report revamp
  - overview/stats replaced with activities / totals and stats accessible 
    from totals
  - interactive graphs to drill down in totals
  - miscellaneous performance improvements
  - pixel-perfect graphs
* Updated 01_startup-fix.patch to apply to new source layout
* debian/control:
  - Add build-depend on gnome-doc-utils

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# - coding: utf-8 -
2
 
 
3
 
# Copyright (C) 2008 Toms Bauģis <toms.baugis at gmail.com>
4
 
 
5
 
# This file is part of Project Hamster.
6
 
 
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.
11
 
 
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.
16
 
 
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/>.
19
 
 
20
 
 
21
 
# some widgets that repeat all over the place
22
 
# cells, columns, trees and other
23
 
 
24
 
import logging
25
 
import gtk
26
 
import pango
27
 
from pango import ELLIPSIZE_END
28
 
 
29
 
from itertools import groupby
30
 
import datetime as dt
31
 
import time
32
 
import re
33
 
import locale
34
 
import os
35
 
 
36
 
def format_duration(minutes, human = True):
37
 
    """formats duration in a human readable format.
38
 
    accepts either minutes or timedelta"""
39
 
    
40
 
    if isinstance(minutes, dt.timedelta):
41
 
        minutes = duration_minutes(minutes)
42
 
        
43
 
    if not minutes:
44
 
        if human:
45
 
            return ""
46
 
        else:
47
 
            return "00:00"
48
 
    
49
 
    hours = minutes / 60
50
 
    minutes = minutes % 60
51
 
    formatted_duration = ""
52
 
    
53
 
    if human:
54
 
        if minutes % 60 == 0:
55
 
            # duration in round hours
56
 
            formatted_duration += _("%dh") % (hours)
57
 
        elif hours == 0:
58
 
            # duration less than hour
59
 
            formatted_duration += _("%dmin") % (minutes % 60.0)
60
 
        else:
61
 
            # x hours, y minutes
62
 
            formatted_duration += _("%dh %dmin") % (hours, minutes % 60)
63
 
    else:
64
 
        formatted_duration += "%02d:%02d" % (hours, minutes)
65
 
    
66
 
    
67
 
    return formatted_duration
68
 
 
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
72
 
 
73
 
 
74
 
def load_ui_file(name):
75
 
    from configuration import runtime
76
 
    ui = gtk.Builder()
77
 
    ui.add_from_file(os.path.join(runtime.data_dir, name))
78
 
    return ui 
79
 
 
80
 
def zero_hour(date):
81
 
    return dt.datetime.combine(date.date(), dt.time(0,0))
82
 
    
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
85
 
# see bug 562298
86
 
def locale_from_utf8(utf8_str):
87
 
    try:
88
 
        retval = unicode (utf8_str, "utf-8").encode(locale.getpreferredencoding())
89
 
    except:
90
 
        retval = utf8_str
91
 
    return retval
92
 
 
93
 
def locale_to_utf8(locale_str):
94
 
    try:
95
 
        retval = unicode (locale_str, locale.getpreferredencoding()).encode("utf-8")
96
 
    except:
97
 
        retval = locale_str
98
 
    return retval
99
 
 
100
 
def locale_first_weekday():
101
 
    """figure if week starts on monday or sunday"""
102
 
    first_weekday = 6 #by default settle on monday
103
 
 
104
 
    try:
105
 
        process = os.popen("locale first_weekday week-1stday")
106
 
        week_offset, week_start = process.read().split('\n')[:2]
107
 
        process.close()
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"))
112
 
    except:
113
 
        logging.warn("WARNING - Failed to get first weekday from locale")
114
 
        
115
 
    return first_weekday
116
 
    
117
 
class CategoryCell(gtk.CellRendererText):
118
 
    def __init__(self):
119
 
        gtk.CellRendererText.__init__(self)        
120
 
        self.set_property('alignment', pango.ALIGN_RIGHT)
121
 
        
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)
126
 
 
127
 
 
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"
131
 
    text = name    
132
 
    if category and category != _("Unsorted"):
133
 
        text += """ - <span color="%s" size="x-small">%s</span>""" % (insensitive_color, category)
134
 
 
135
 
    if description:
136
 
        text+= "\n"
137
 
        if pad_description:
138
 
            text += " " * 23
139
 
 
140
 
        text += """<span style="italic" size="small">%s</span>""" % description
141
 
        
142
 
    return text
143
 
    
144
 
 
145
 
def totals(iter, keyfunc, sumfunc):
146
 
    """groups items by field described in keyfunc and counts totals using value
147
 
       from sumfunc
148
 
    """
149
 
    data = sorted(iter, key=keyfunc)
150
 
    res = {}
151
 
 
152
 
    for k, group in groupby(data, keyfunc):
153
 
        res[k] = sum([sumfunc(entry) for entry in group])
154
 
 
155
 
    return res
156
 
 
157
 
 
158
 
 
159
 
 
160
 
def dateDict(date, prefix = ""):
161
 
    """converts date into dictionary, having prefix for all the keys"""
162
 
    res = {}
163
 
    
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")
185
 
    
186
 
    for i, value in res.items():
187
 
        res[i] = locale_to_utf8(value)
188
 
 
189
 
    return res
190
 
 
191
 
def escape_pango(text):
192
 
    if not text:
193
 
        return text
194
 
    
195
 
    text = text.replace ("&", "&amp;")
196
 
    text = text.replace("<", "&lt;")
197
 
    text = text.replace(">", "&gt;")
198
 
    return text
199
 
 
200
 
def figure_time(str_time):
201
 
    if not str_time or not str_time.strip():
202
 
        return None
203
 
    
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)
208
 
    
209
 
    hours, minutes = None, None
210
 
    
211
 
    if len(numbers) == 1 and len(numbers[0]) == 4:
212
 
        hours, minutes = int(numbers[0][:2]), int(numbers[0][2:])
213
 
    else:
214
 
        if len(numbers) >= 1:
215
 
            hours = int(numbers[0])
216
 
        if len(numbers) >= 2:
217
 
            minutes = int(numbers[1])
218
 
        
219
 
    if (hours is None or minutes is None) or hours > 24 or minutes > 60:
220
 
        return None #no can do
221
 
 
222
 
    return dt.datetime.now().replace(hour = hours, minute = minutes,
223
 
                                     second = 0, microsecond = 0)
224
 
 
225
 
def parse_activity_input(text):
226
 
    """Currently pretty braindead function that tries to parse arbitrary input
227
 
    into a activity"""
228
 
    class InputParseResult(object):
229
 
        def __init__(self):
230
 
            self.activity_name = None
231
 
            self.category_name = None
232
 
            self.start_time = None
233
 
            self.end_time = None
234
 
            self.description = None
235
 
            self.tags = []
236
 
            
237
 
    
238
 
    res = InputParseResult()
239
 
    
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 =
247
 
                                                                int(potential_time))
248
 
    
249
 
        else:
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)
253
 
                
254
 
            res.start_time = figure_time(potential_time)
255
 
        
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
259
 
        elif res.start_time:
260
 
            text = text[text.find(" ")+1:]
261
 
    
262
 
    if text.find("@") > 0:
263
 
        text, res.category_name = text.split("@", 1)
264
 
        res.category_name = res.category_name.strip()
265
 
 
266
 
    #only thing left now is the activity name itself
267
 
    res.activity_name = text
268
 
 
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]"
273
 
 
274
 
    return res