~ubuntu-branches/ubuntu/natty/gnome-activity-journal/natty

« back to all changes in this revision

Viewing changes to src/daywidgets.py

  • Committer: Bazaar Package Importer
  • Author(s): Siegfried-Angel Gevatter Pujals
  • Date: 2010-01-19 19:02:56 UTC
  • Revision ID: james.westby@ubuntu.com-20100119190256-x0iws5zymbi2oitq
Tags: upstream-0.3.2
Import upstream version 0.3.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -.- coding: utf-8 -.-
 
2
#
 
3
# GNOME Activity Journal
 
4
#
 
5
# Copyright © 2009-2010 Seif Lotfy <seif@lotfy.com>
 
6
# Copyright © 2010 Randal Barlow <email.tehk@gmail.com>
 
7
# Copyright © 2010 Siegfried Gevatter <siegfried@gevatter.com>
 
8
#
 
9
# This program is free software: you can redistribute it and/or modify
 
10
# it under the terms of the GNU General Public License as published by
 
11
# the Free Software Foundation, either version 3 of the License, or
 
12
# (at your option) any later version.
 
13
#
 
14
# This program is distributed in the hope that it will be useful,
 
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
17
# GNU Lesser General Public License for more details.
 
18
#
 
19
# You should have received a copy of the GNU Lesser General Public License
 
20
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
21
 
 
22
import gtk
 
23
import time, datetime
 
24
import gobject
 
25
import gettext
 
26
import cairo
 
27
import pango
 
28
import math
 
29
import os
 
30
import urllib
 
31
from datetime import date
 
32
 
 
33
from zeitgeist.client import ZeitgeistClient
 
34
from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation, \
 
35
    ResultType, TimeRange
 
36
 
 
37
from widgets import *
 
38
 
 
39
CLIENT = ZeitgeistClient()
 
40
 
 
41
class DayWidget(gtk.VBox):
 
42
 
 
43
    def __init__(self, start, end):
 
44
        super(DayWidget, self).__init__()
 
45
        hour = 60*60
 
46
        self.day_start = start
 
47
        self.day_end = end
 
48
        
 
49
        self._set_date_strings()
 
50
        self._periods = [
 
51
            (_("Morning"), start, start + 12*hour - 1),
 
52
            (_("Afternoon"), start + 12*hour, start + 18*hour - 1),
 
53
            (_("Evening"), start + 18*hour, end),
 
54
        ]
 
55
 
 
56
        self._init_widgets()
 
57
        self._init_pinbox()
 
58
        self._init_events()
 
59
        gobject.timeout_add_seconds(
 
60
            86400 - (int(time.time() - time.timezone) % 86400), self._refresh)
 
61
        
 
62
        self.show_all()
 
63
 
 
64
    def refresh(self):
 
65
        for w in self.view:
 
66
            w.on_style_change(None, None)
 
67
 
 
68
    def _set_date_strings(self):
 
69
        self.date_string = date.fromtimestamp(self.day_start).strftime("%d %B")
 
70
        self.year_string = date.fromtimestamp(self.day_start).strftime("%Y")
 
71
        if time.time() < self.day_end and time.time() > self.day_start:
 
72
            self.week_day_string = _("Today")
 
73
        elif time.time() - 86400 < self.day_end and time.time() - 86400> self.day_start:
 
74
            self.week_day_string = _("Yesterday")
 
75
        else:
 
76
                self.week_day_string = date.fromtimestamp(self.day_start).strftime("%A")
 
77
        self.emit("style-set", None)
 
78
 
 
79
    def _refresh(self):
 
80
        self._init_date_label()
 
81
        self._init_pinbox()
 
82
 
 
83
    def _init_pinbox(self):
 
84
        if self.day_start <= time.time() < self.day_end:
 
85
            self.view.pack_start(pinbox, False, False)
 
86
        
 
87
            
 
88
    def _init_widgets(self):
 
89
        self.vbox = gtk.VBox()
 
90
        evbox = gtk.EventBox()
 
91
        evbox.add(self.vbox)
 
92
 
 
93
        self.pack_start(evbox)
 
94
 
 
95
        self._init_date_label()
 
96
 
 
97
        #label.modify_bg(gtk.STATE_SELECTED, style.bg[gtk.STATE_SELECTED])
 
98
 
 
99
        self.view = gtk.VBox()
 
100
        scroll = gtk.ScrolledWindow()
 
101
        scroll.set_shadow_type(gtk.SHADOW_NONE)
 
102
 
 
103
        evbox2 = gtk.EventBox()
 
104
        evbox2.add(self.view)
 
105
        self.view.set_border_width(6)
 
106
 
 
107
        scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
108
        scroll.add_with_viewport(evbox2)
 
109
        for w in scroll.get_children():
 
110
            w.set_shadow_type(gtk.SHADOW_NONE)
 
111
        self.vbox.pack_start(scroll)
 
112
        self.show_all()
 
113
        
 
114
        def change_style(widget, style):
 
115
            rc_style = self.style
 
116
            color = rc_style.bg[gtk.STATE_NORMAL]
 
117
            
 
118
            if color.red * 102/100 > 65535.0:
 
119
                color.red = 65535.0
 
120
            else:
 
121
                color.red = color.red * 102 / 100
 
122
                
 
123
            if color.green * 102/100 > 65535.0:
 
124
                color.green = 65535.0
 
125
            else:
 
126
                color.green = color.green * 102 / 100
 
127
                
 
128
            if color.blue * 102/100 > 65535.0:
 
129
                color.blue = 65535.0
 
130
            else:
 
131
                color.blue = color.blue * 102 / 100                
 
132
            evbox2.modify_bg(gtk.STATE_NORMAL, color)
 
133
 
 
134
        self.connect("style-set", change_style)
 
135
 
 
136
    def _init_date_label(self):
 
137
        self._set_date_strings()
 
138
        
 
139
        today = int(time.time() )- 7*86400
 
140
        if self.day_start < today:
 
141
            self.daylabel = DayLabel(self.date_string, self.week_day_string+", "+ self.year_string)
 
142
        else:
 
143
            self.daylabel = DayLabel(self.week_day_string, self.date_string+", "+ self.year_string)
 
144
        self.daylabel.set_size_request(100, 60)
 
145
        self.vbox.pack_start(self.daylabel, False, False)
 
146
        self.vbox.reorder_child(self.daylabel, 0)
 
147
 
 
148
    def _init_events(self):
 
149
        for w in self.view:
 
150
            if not w == pinbox:
 
151
                self.view.remove(w)
 
152
        for period in self._periods:
 
153
            part = DayPartWidget(period[0], period[1], period[2])
 
154
            self.view.pack_start(part, False, False)
 
155
            part.get_events()
 
156
 
 
157
class CategoryBox(gtk.VBox):
 
158
 
 
159
    def __init__(self, category, events):
 
160
        super(CategoryBox, self).__init__()
 
161
 
 
162
        self.view = gtk.VBox(True)
 
163
        for event in events:
 
164
            item = Item(event)
 
165
            hbox = gtk.HBox ()
 
166
            label = gtk.Label("")
 
167
            hbox.pack_start(label, False, False, 7)
 
168
            hbox.pack_start(item, True, True, 0)
 
169
            self.view.pack_start(hbox, False, False, 0)
 
170
            hbox.show()
 
171
            label.show()
 
172
            
 
173
 
 
174
        # If this isn't a set of ungrouped events, give it a label
 
175
        if category:
 
176
            # Place the items into a box and simulate left padding
 
177
            self.box = gtk.HBox()
 
178
            label = gtk.Label("")
 
179
            self.box.pack_start(label, False, False, 7)
 
180
            self.box.pack_start(self.view)
 
181
            self.pack_end(self.box)
 
182
            
 
183
            # Add the title button
 
184
            self.btn = CategoryButton(category, len(events))
 
185
            self.btn.connect("toggle", self.on_toggle)
 
186
            hbox = gtk.HBox ()
 
187
            lbl = gtk.Label("")
 
188
            hbox.pack_start(lbl, False, False, 8)
 
189
            hbox.pack_start(self.btn, True, True, 0)
 
190
            self.pack_start(hbox, False, False)
 
191
            
 
192
            self.show()
 
193
            hbox.show_all()
 
194
            label.show_all()
 
195
            self.btn.show()
 
196
            self.box.hide()
 
197
            self.view.show()
 
198
 
 
199
        else:
 
200
            self.box = self.view
 
201
            self.pack_end(self.box)
 
202
            self.box.show()
 
203
            self.show()
 
204
 
 
205
    def on_toggle(self, view, bool):
 
206
        if bool:
 
207
            self.box.show()
 
208
        else:
 
209
            self.box.hide()
 
210
 
 
211
class DayLabel(gtk.DrawingArea):
 
212
 
 
213
    def __init__(self, day, date):
 
214
        if day == _("Today"):
 
215
            self.leading = True
 
216
        else:
 
217
            self.leading = False
 
218
        super(DayLabel, self).__init__()
 
219
        self.date = date
 
220
        self.day = day
 
221
        self.connect("expose_event", self.expose)
 
222
    
 
223
    def expose(self, widget, event):
 
224
        context = widget.window.cairo_create()
 
225
        self.context = context
 
226
 
 
227
        bg = self.style.bg[0]
 
228
        red, green, blue = bg.red/65535.0, bg.green/65535.0, bg.blue/65535.0
 
229
        self.font_name = self.style.font_desc.get_family()
 
230
        
 
231
        context.set_source_rgba(red, green, blue, 1)
 
232
 
 
233
        context.set_operator(cairo.OPERATOR_SOURCE)
 
234
        context.paint()
 
235
        # set a clip region for the expose event
 
236
        context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
 
237
        context.clip()
 
238
        self.draw(widget, event, context)
 
239
        self.day_text(widget, event, context)
 
240
        return False
 
241
 
 
242
    def day_text(self, widget, event, context):
 
243
        actual_y = self.get_size_request()[1]
 
244
        if actual_y > event.area.height:
 
245
            y = actual_y
 
246
        else:
 
247
            y = event.area.height
 
248
        x = event.area.width
 
249
        gc = self.style.fg_gc[gtk.STATE_SELECTED if self.leading else gtk.STATE_NORMAL]        
 
250
        layout = widget.create_pango_layout(self.day)
 
251
        layout.set_font_description(pango.FontDescription(self.font_name + " Bold 15"))
 
252
        w, h = layout.get_pixel_size()
 
253
        widget.window.draw_layout(gc, (x-w)/2, (y)/2 - h + 5, layout)
 
254
        self.date_text(widget, event, context, (y)/2 + 5)
 
255
 
 
256
    def date_text(self, widget, event, context, lastfontheight):
 
257
        gc = self.style.text_gc[gtk.STATE_SELECTED if self.leading else gtk.STATE_INSENSITIVE]
 
258
        layout = widget.create_pango_layout(self.date)
 
259
        layout.set_font_description(pango.FontDescription(self.font_name + " 10"))
 
260
        w, h = layout.get_pixel_size()
 
261
        widget.window.draw_layout(gc, (event.area.width-w)/2, lastfontheight, layout)
 
262
 
 
263
        
 
264
    def draw(self, widget, event, context):
 
265
        if self.leading:
 
266
            bg = self.style.bg[3]
 
267
            red, green, blue = bg.red/65535.0, bg.green/65535.0, bg.blue/65535.0
 
268
        else:
 
269
            bg = self.style.bg[gtk.STATE_NORMAL]
 
270
            red = (bg.red * 125 / 100)/65535.0
 
271
            green = (bg.green * 125 / 100)/65535.0
 
272
            blue = (bg.blue * 125 / 100)/65535.0
 
273
        
 
274
        # Draw
 
275
        x = 0; y = 0
 
276
        r = 5
 
277
        w, h = event.area.width, event.area.height
 
278
        # Temporary color, I will fix this later when I have a chance to sleep. 
 
279
        #grad = cairo.LinearGradient(0, 3*event.area.height, 0, 0)
 
280
        #grad.add_color_stop_rgb(0,  0, 0, 0)
 
281
        #grad.add_color_stop_rgb(1,  red, green, blue)
 
282
        
 
283
        #if self.leading:
 
284
            #context.set_source(grad)
 
285
        context.set_source_rgba(red, green, blue, 1)
 
286
 
 
287
        context.new_sub_path()
 
288
        context.arc(r+x, r+y, r, math.pi, 3 * math.pi /2)
 
289
        context.arc(w-r, r+y, r, 3 * math.pi / 2, 0)
 
290
        context.close_path()
 
291
        context.rectangle(0, r, w, h)
 
292
        context.fill_preserve()
 
293
 
 
294
class EventGroup(gtk.VBox):
 
295
 
 
296
    def __init__(self, title):
 
297
        super(EventGroup, self).__init__()
 
298
        
 
299
        # Create the title label
 
300
        self.label = gtk.Label(title)
 
301
        self.label.set_alignment(0.03, 0.5)
 
302
        self.pack_start(self.label, False, False, 6)
 
303
        
 
304
        # Create the main container
 
305
        self.view = gtk.VBox()
 
306
        self.pack_start(self.view)
 
307
 
 
308
        # Connect to relevant signals
 
309
        self.connect("style-set", self.on_style_change)
 
310
        
 
311
        # Populate the widget with content
 
312
        self.get_events()
 
313
 
 
314
    def on_style_change(self, widget, style):
 
315
        """ Update used colors according to the system theme. """
 
316
        color = self.style.bg[gtk.STATE_NORMAL]
 
317
        fcolor = self.style.fg[gtk.STATE_NORMAL] 
 
318
        color.red = (2 * color.red + fcolor.red) / 3
 
319
        color.green = (2 * color.green + fcolor.green) / 3
 
320
        color.blue = (2 * color.blue + fcolor.blue) / 3
 
321
        self.label.modify_fg(gtk.STATE_NORMAL, color)
 
322
 
 
323
    @staticmethod
 
324
    def event_exists(uri):
 
325
        # TODO: Move this into Zeitgeist's datamodel.py
 
326
        return not uri.startswith("file://") or os.path.exists(
 
327
            urllib.unquote(str(uri[7:])))
 
328
 
 
329
    def set_events(self, events):
 
330
        
 
331
        for widget in self.view:
 
332
            self.view.remove(widget)
 
333
 
 
334
        categories = {}
 
335
        for event in events:
 
336
            subject = event.subjects[0]
 
337
            if self.event_exists(subject.uri):
 
338
                if not categories.has_key(subject.interpretation):
 
339
                    categories[subject.interpretation] = []
 
340
                categories[subject.interpretation].append(event)
 
341
 
 
342
        if not categories:
 
343
            self.hide_all()
 
344
        else:
 
345
            ungrouped_events = []
 
346
            for key in sorted(categories.iterkeys()):
 
347
                events = categories[key]
 
348
                if len(events) > 3:
 
349
                    box = CategoryBox(key, list(reversed(events)))
 
350
                    self.view.pack_start(box)
 
351
                else:
 
352
                    ungrouped_events += events
 
353
 
 
354
            ungrouped_events.sort(key=lambda x: x.timestamp)
 
355
            box = CategoryBox(None, ungrouped_events)
 
356
            self.view.pack_start(box)
 
357
            self.view.show()
 
358
            box.show()
 
359
            self.show()
 
360
            self.label.show()
 
361
        
 
362
        if len(bookmarker.bookmarks) > 0:
 
363
            pinbox.show_all()
 
364
        else:
 
365
            pinbox.hide_all()
 
366
 
 
367
    def _handle_find_events(self, ids):
 
368
        CLIENT.get_events(ids, self.set_events)
 
369
 
 
370
    def get_events(self):
 
371
        if self.event_templates is not None:
 
372
            CLIENT.find_event_ids_for_templates(self.event_templates,
 
373
                self._handle_find_events, self.event_timerange, num_events=50000,
 
374
                result_type=ResultType.MostRecentSubjects)
 
375
        else:
 
376
            self.view.hide()
 
377
 
 
378
class DayPartWidget(EventGroup):
 
379
 
 
380
    def __init__(self, title, start, end):
 
381
        # Setup event criteria for querying
 
382
        self.event_timerange = [start * 1000, end * 1000]
 
383
        self.event_templates = (
 
384
            Event.new_for_values(interpretation=Interpretation.VISIT_EVENT.uri),
 
385
            Event.new_for_values(interpretation=Interpretation.MODIFY_EVENT.uri)
 
386
        )
 
387
        
 
388
        # Initialize the widget
 
389
        super(DayPartWidget, self).__init__(title)
 
390
        
 
391
        # FIXME: Move this into EventGroup
 
392
        CLIENT.install_monitor(self.event_timerange, self.event_templates,
 
393
            self.notify_insert_handler, self.notify_delete_handler)
 
394
        self.show_all()
 
395
 
 
396
    def notify_insert_handler(self, time_range, events):
 
397
        # FIXME: Don't regenerate everything, we already get the
 
398
        # information we need
 
399
        self.get_events()
 
400
 
 
401
    def notify_delete_handler(self, time_range, event_ids):
 
402
        # FIXME: Same as above
 
403
        self.get_events()
 
404
 
 
405
class PinBox(EventGroup):
 
406
 
 
407
    def __init__(self):
 
408
        # Setup event criteria for querying
 
409
        self.event_timerange = TimeRange.until_now()
 
410
 
 
411
        # Initialize the widget
 
412
        super(PinBox, self).__init__(_("Pinned items"))
 
413
 
 
414
        # Connect to relevant signals
 
415
        bookmarker.connect("reload", lambda widget, uris: self.get_events())
 
416
        bookmarker.connect("reload", self.check_is_visible)
 
417
 
 
418
    def check_is_visible(self, widget, bookmarks):
 
419
        if len(bookmarks) > 0:
 
420
            self.show_all()
 
421
        else:
 
422
            self.hide_all()
 
423
 
 
424
    @property
 
425
    def event_templates(self):
 
426
        if not bookmarker.bookmarks:
 
427
            # Abort, or we will query with no templates and get lots of
 
428
            # irrelevant events.
 
429
            return None
 
430
        
 
431
        templates = []
 
432
        for bookmark in bookmarker.bookmarks:
 
433
            templates.append(Event.new_for_values(
 
434
                subjects=[Subject.new_for_values(uri=bookmark)]))
 
435
        if len(templates) > 0:
 
436
            self.show_all()
 
437
        else:
 
438
            self.hide_all()
 
439
        return templates
 
440
 
 
441
pinbox = PinBox()