~gnome-zeitgeist/gnome-activity-journal/minimal

« back to all changes in this revision

Viewing changes to src/daywidgets.py

  • Committer: tehk
  • Date: 2010-05-04 05:27:03 UTC
  • mfrom: (843.1.42 journal-rewrite)
  • Revision ID: tehk@tehk-desktop-20100504052703-pqp9o4yuewaoc9lv
new-core branch was merge

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 General Public License for more details.
18
 
#
19
 
# You should have received a copy of the GNU General Public License
20
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 
 
22
 
import gtk
23
 
import time
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 common import shade_gdk_color, combine_gdk_color, get_gtk_rgba
38
 
from widgets import *
39
 
from thumb import ThumbBox
40
 
from timeline import TimelineView, TimelineHeader
41
 
from eventgatherer import get_dayevents, get_file_events
42
 
 
43
 
CLIENT = ZeitgeistClient()
44
 
 
45
 
 
46
 
class GenericViewWidget(gtk.VBox):
47
 
    __gsignals__ = {
48
 
        "unfocus-day" : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()),
49
 
        # Sends a list zeitgeist events
50
 
        }
51
 
 
52
 
    def __init__(self):
53
 
        gtk.VBox.__init__(self)
54
 
        self.daylabel = None
55
 
        self.connect("style-set", self.change_style)
56
 
 
57
 
    def _set_date_strings(self):
58
 
        self.date_string = date.fromtimestamp(self.day_start).strftime("%d %B")
59
 
        self.year_string = date.fromtimestamp(self.day_start).strftime("%Y")
60
 
        if time.time() < self.day_end and time.time() > self.day_start:
61
 
            self.week_day_string = _("Today")
62
 
        elif time.time() - 86400 < self.day_end and time.time() - 86400> self.day_start:
63
 
            self.week_day_string = _("Yesterday")
64
 
        else:
65
 
            self.week_day_string = date.fromtimestamp(self.day_start).strftime("%A")
66
 
        self.emit("style-set", None)
67
 
 
68
 
    def click(self, widget, event):
69
 
        if event.button in (1, 3):
70
 
            self.emit("unfocus-day")
71
 
 
72
 
    def change_style(self, widget, style):
73
 
        rc_style = self.style
74
 
        color = rc_style.bg[gtk.STATE_NORMAL]
75
 
        color = shade_gdk_color(color, 102/100.0)
76
 
        self.view.modify_bg(gtk.STATE_NORMAL, color)
77
 
        self.view.modify_base(gtk.STATE_NORMAL, color)
78
 
 
79
 
 
80
 
class ThumbnailDayWidget(GenericViewWidget):
81
 
 
82
 
    def __init__(self):
83
 
        GenericViewWidget.__init__(self)
84
 
        self.monitors = []
85
 
        self.scrolledwindow = gtk.ScrolledWindow()
86
 
        self.scrolledwindow.set_shadow_type(gtk.SHADOW_NONE)
87
 
        self.scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
88
 
        self.view = ThumbBox()
89
 
        self.scrolledwindow.add_with_viewport(self.view)
90
 
        self.scrolledwindow.get_children()[0].set_shadow_type(gtk.SHADOW_NONE)
91
 
        self.pack_end(self.scrolledwindow)
92
 
 
93
 
    def set_day(self, start, end):
94
 
 
95
 
        self.day_start = start
96
 
        self.day_end = end
97
 
        for widget in self:
98
 
            if self.scrolledwindow != widget:
99
 
                self.remove(widget)
100
 
        self._set_date_strings()
101
 
        today = int(time.time() ) - 7*86400
102
 
        if self.daylabel:
103
 
            #Disconnect here
104
 
            pass
105
 
        if self.day_start < today:
106
 
            self.daylabel = DayLabel(self.date_string, self.week_day_string+", "+ self.year_string)
107
 
        else:
108
 
            self.daylabel = DayLabel(self.week_day_string, self.date_string+", "+ self.year_string)
109
 
        self.daylabel.set_size_request(100, 60)
110
 
        self.daylabel.connect("button-press-event", self.click)
111
 
        self.daylabel.set_tooltip_text(_("Click to return to multiday view"))
112
 
        self.pack_start(self.daylabel, False, False)
113
 
        self.show_all()
114
 
        self.view.hide_all()
115
 
        self.daylabel.show_all()
116
 
        self.view.show()
117
 
 
118
 
        hour = 60*60
119
 
        get_file_events(start*1000, (start + 12*hour -1) * 1000, self.set_morning_events)
120
 
        get_file_events((start + 12*hour)*1000, (start + 18*hour - 1)*1000, self.set_afternoon_events)
121
 
        get_file_events((start + 18*hour)*1000, end*1000, self.set_evening_events)
122
 
 
123
 
    def set_morning_events(self, events):
124
 
        if len(events) > 0:
125
 
            timestamp = int(events[0].timestamp)
126
 
            if self.day_start*1000 <= timestamp and timestamp < (self.day_start + 12*60*60)*1000:
127
 
                self.view.set_morning_events(events)
128
 
            self.view.views[0].show_all()
129
 
            self.view.labels[0].show_all()
130
 
        else:
131
 
            self.view.set_morning_events(events)
132
 
            self.view.views[0].hide_all()
133
 
            self.view.labels[0].hide_all()
134
 
 
135
 
    def set_afternoon_events(self, events):
136
 
        if len(events) > 0:
137
 
            timestamp = int(events[0].timestamp)
138
 
            if (self.day_start + 12*60*60)*1000 <= timestamp and timestamp < (self.day_start + 18*60*60)*1000:
139
 
                self.view.set_afternoon_events(events)
140
 
            self.view.views[1].show_all()
141
 
            self.view.labels[1].show_all()
142
 
        else:
143
 
            self.view.set_afternoon_events(events)
144
 
            self.view.views[1].hide_all()
145
 
            self.view.labels[1].hide_all()
146
 
 
147
 
    def set_evening_events(self, events):
148
 
        if len(events) > 0:
149
 
            timestamp = int(events[0].timestamp)
150
 
            if (self.day_start + 18*60*60)*1000 <= timestamp and timestamp < self.day_end*1000:
151
 
                self.view.set_evening_events(events)
152
 
            self.view.views[2].show_all()
153
 
            self.view.labels[2].show_all()
154
 
        else:
155
 
            self.view.set_evening_events(events)
156
 
            self.view.views[2].hide_all()
157
 
            self.view.labels[2].hide_all()
158
 
 
159
 
 
160
 
class SingleDayWidget(GenericViewWidget):
161
 
 
162
 
    def __init__(self):
163
 
        GenericViewWidget.__init__(self)
164
 
        self.ruler = TimelineHeader()
165
 
        self.scrolledwindow = gtk.ScrolledWindow()
166
 
        self.scrolledwindow.set_shadow_type(gtk.SHADOW_NONE)
167
 
        self.scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
168
 
        self.view = TimelineView()
169
 
        self.scrolledwindow.add(self.view)
170
 
        self.pack_end(self.scrolledwindow)
171
 
        self.pack_end(self.ruler, False, False)
172
 
        self.view.set_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK)
173
 
 
174
 
    def set_day(self, start, end):
175
 
        self.day_start = start
176
 
        self.day_end = end
177
 
        for widget in self:
178
 
            if widget not in (self.ruler, self.scrolledwindow):
179
 
                self.remove(widget)
180
 
        self._set_date_strings()
181
 
        today = int(time.time() ) - 7*86400
182
 
        if self.daylabel:
183
 
            #Disconnect here
184
 
            pass
185
 
        if self.day_start < today:
186
 
            self.daylabel = DayLabel(self.date_string, self.week_day_string+", "+ self.year_string)
187
 
        else:
188
 
            self.daylabel = DayLabel(self.week_day_string, self.date_string+", "+ self.year_string)
189
 
        self.daylabel.set_size_request(100, 60)
190
 
        self.daylabel.connect("button-press-event", self.click)
191
 
        self.daylabel.set_tooltip_text(_("Click to return to multiday view"))
192
 
 
193
 
        self.pack_start(self.daylabel, False, False)
194
 
        get_dayevents(start*1000, end*1000, 1, self.view.set_model_from_list)
195
 
        self.show_all()
196
 
 
197
 
    def change_style(self, widget, style):
198
 
        GenericViewWidget.change_style(self, widget, style)
199
 
        rc_style = self.style
200
 
        color = rc_style.bg[gtk.STATE_NORMAL]
201
 
        color = shade_gdk_color(color, 102/100.0)
202
 
        self.ruler.modify_bg(gtk.STATE_NORMAL, color)
203
 
 
204
 
 
205
 
class DayWidget(gtk.VBox):
206
 
 
207
 
    __gsignals__ = {
208
 
        "focus-day" : (gobject.SIGNAL_RUN_FIRST,
209
 
                    gobject.TYPE_NONE,
210
 
                    (gobject.TYPE_INT,))
211
 
        }
212
 
 
213
 
    def __init__(self, start, end):
214
 
        super(DayWidget, self).__init__()
215
 
        hour = 60*60
216
 
        self.day_start = start
217
 
        self.day_end = end
218
 
 
219
 
        self._set_date_strings()
220
 
        self._periods = [
221
 
            (_("Morning"), start, start + 12*hour - 1),
222
 
            (_("Afternoon"), start + 12*hour, start + 18*hour - 1),
223
 
            (_("Evening"), start + 18*hour, end),
224
 
        ]
225
 
 
226
 
        self._init_widgets()
227
 
        self._init_pinbox()
228
 
        gobject.timeout_add_seconds(
229
 
            86400 - (int(time.time() - time.timezone) % 86400), self._refresh)
230
 
 
231
 
 
232
 
        self.show_all()
233
 
        self._init_events()
234
 
 
235
 
    def refresh(self):
236
 
        pass
237
 
 
238
 
    def _set_date_strings(self):
239
 
        self.date_string = date.fromtimestamp(self.day_start).strftime("%d %B")
240
 
        self.year_string = date.fromtimestamp(self.day_start).strftime("%Y")
241
 
        if time.time() < self.day_end and time.time() > self.day_start:
242
 
            self.week_day_string = _("Today")
243
 
        elif time.time() - 86400 < self.day_end and time.time() - 86400> self.day_start:
244
 
            self.week_day_string = _("Yesterday")
245
 
        else:
246
 
                self.week_day_string = date.fromtimestamp(self.day_start).strftime("%A")
247
 
        self.emit("style-set", None)
248
 
 
249
 
    def _refresh(self):
250
 
        self._init_date_label()
251
 
        self._init_pinbox()
252
 
        pinbox.show_all()
253
 
 
254
 
    def _init_pinbox(self):
255
 
        if self.day_start <= time.time() < self.day_end:
256
 
            self.view.pack_start(pinbox, False, False)
257
 
 
258
 
    def _init_widgets(self):
259
 
        self.vbox = gtk.VBox()
260
 
        self.pack_start(self.vbox)
261
 
 
262
 
        self.daylabel = None
263
 
 
264
 
        self._init_date_label()
265
 
 
266
 
        #label.modify_bg(gtk.STATE_SELECTED, style.bg[gtk.STATE_SELECTED])
267
 
 
268
 
        self.view = gtk.VBox()
269
 
        scroll = gtk.ScrolledWindow()
270
 
        scroll.set_shadow_type(gtk.SHADOW_NONE)
271
 
 
272
 
        evbox2 = gtk.EventBox()
273
 
        evbox2.add(self.view)
274
 
        self.view.set_border_width(6)
275
 
 
276
 
        scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
277
 
        scroll.add_with_viewport(evbox2)
278
 
        for w in scroll.get_children():
279
 
            w.set_shadow_type(gtk.SHADOW_NONE)
280
 
        self.vbox.pack_start(scroll)
281
 
        self.show_all()
282
 
 
283
 
        def change_style(widget, style):
284
 
            rc_style = self.style
285
 
            color = rc_style.bg[gtk.STATE_NORMAL]
286
 
            color = shade_gdk_color(color, 102/100.0)
287
 
            evbox2.modify_bg(gtk.STATE_NORMAL, color)
288
 
 
289
 
        self.connect("style-set", change_style)
290
 
 
291
 
    def _init_date_label(self):
292
 
        self._set_date_strings()
293
 
 
294
 
        today = int(time.time() ) - 7*86400
295
 
        if self.daylabel:
296
 
            # Disconnect HERE
297
 
            pass
298
 
        if self.day_start < today:
299
 
            self.daylabel = DayLabel(self.date_string, self.week_day_string+", "+ self.year_string)
300
 
        else:
301
 
            self.daylabel = DayLabel(self.week_day_string, self.date_string+", "+ self.year_string)
302
 
        self.daylabel.connect("button-press-event", self.click)
303
 
        self.daylabel.set_tooltip_text(
304
 
            _("Left click for a detailed timeline view")
305
 
            + u"\n" +
306
 
            _("Right click for a thumbnail view"))
307
 
        self.daylabel.set_size_request(100, 60)
308
 
        evbox = gtk.EventBox()
309
 
        evbox.add(self.daylabel)
310
 
        evbox.set_size_request(100, 60)
311
 
        self.vbox.pack_start(evbox, False, False)
312
 
        def cursor_func(x, y):
313
 
            if evbox.window:
314
 
                evbox.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
315
 
        self.connect("motion-notify-event", cursor_func)
316
 
 
317
 
        def change_style(widget, style):
318
 
            rc_style = self.style
319
 
            color = rc_style.bg[gtk.STATE_NORMAL]
320
 
            evbox.modify_bg(gtk.STATE_NORMAL, color)
321
 
            self.daylabel.modify_bg(gtk.STATE_NORMAL, color)
322
 
 
323
 
        self.connect("style-set", change_style)
324
 
        #self.connect("leave-notify-event", lambda x, y: evbox.window.set_cursor(None))
325
 
 
326
 
        self.vbox.reorder_child(self.daylabel, 0)
327
 
 
328
 
    def click(self, widget, event):
329
 
        if event.button == 1:
330
 
            self.emit("focus-day", 1)
331
 
        elif event.button == 3:
332
 
            self.emit("focus-day", 2)
333
 
 
334
 
    def _init_events(self):
335
 
        for w in self.view:
336
 
            if not w == pinbox:
337
 
                self.view.remove(w)
338
 
        for period in self._periods:
339
 
            part = DayPartWidget(period[0], period[1], period[2])
340
 
            self.view.pack_start(part, False, False)
341
 
 
342
 
 
343
 
class CategoryBox(gtk.HBox):
344
 
 
345
 
    def __init__(self, category, events, pinnable = False):
346
 
        super(CategoryBox, self).__init__()
347
 
        self.view = gtk.VBox(True)
348
 
        self.vbox = gtk.VBox()
349
 
        for event in events:
350
 
            item = Item(event, pinnable)
351
 
            hbox = gtk.HBox ()
352
 
            #label = gtk.Label("")
353
 
            #hbox.pack_start(label, False, False, 7)
354
 
            hbox.pack_start(item, True, True, 0)
355
 
            self.view.pack_start(hbox, False, False, 0)
356
 
            hbox.show()
357
 
            #label.show()
358
 
 
359
 
        # If this isn't a set of ungrouped events, give it a label
360
 
        if category:
361
 
            # Place the items into a box and simulate left padding
362
 
            self.box = gtk.HBox()
363
 
            #label = gtk.Label("")
364
 
            self.box.pack_start(self.view)
365
 
 
366
 
            hbox = gtk.HBox()
367
 
            # Add the title button
368
 
            if category in SUPPORTED_SOURCES:
369
 
                text = SUPPORTED_SOURCES[category].group_label(len(events))
370
 
            else:
371
 
                text = "Unknown"
372
 
 
373
 
            label = gtk.Label()
374
 
            label.set_markup("<span>%s</span>" % text)
375
 
            #label.set_ellipsize(pango.ELLIPSIZE_END)
376
 
 
377
 
            hbox.pack_start(label, True, True, 0)
378
 
 
379
 
            label = gtk.Label()
380
 
            label.set_markup("<span>(%d)</span>" % len(events))
381
 
            label.set_alignment(1.0,0.5)
382
 
            label.set_alignment(1.0,0.5)
383
 
            hbox.pack_end(label, False, False, 2)
384
 
 
385
 
            hbox.set_border_width(3)
386
 
 
387
 
            self.expander = gtk.Expander()
388
 
            self.expander.set_label_widget(hbox)
389
 
 
390
 
            self.vbox.pack_start(self.expander, False, False)
391
 
            self.expander.add(self.box)#
392
 
 
393
 
            self.pack_start(self.vbox, True, True, 24)
394
 
 
395
 
            self.expander.show_all()
396
 
            self.show()
397
 
            hbox.show_all()
398
 
            label.show_all()
399
 
            self.view.show()
400
 
 
401
 
        else:
402
 
            self.box = self.view
403
 
            self.vbox.pack_end(self.box)
404
 
            self.box.show()
405
 
            self.show()
406
 
 
407
 
            self.pack_start(self.vbox, True, True, 16)
408
 
 
409
 
        self.show_all()
410
 
 
411
 
    def on_toggle(self, view, bool):
412
 
        if bool:
413
 
            self.box.show()
414
 
        else:
415
 
            self.box.hide()
416
 
        pinbox.show_all()
417
 
 
418
 
 
419
 
class DayLabel(gtk.DrawingArea):
420
 
 
421
 
    _events = (
422
 
        gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK |
423
 
        gtk.gdk.KEY_PRESS_MASK | gtk.gdk.BUTTON_MOTION_MASK |
424
 
        gtk.gdk.POINTER_MOTION_HINT_MASK | gtk.gdk.BUTTON_RELEASE_MASK |
425
 
        gtk.gdk.BUTTON_PRESS_MASK
426
 
    )
427
 
 
428
 
    def __init__(self, day, date):
429
 
        if day == _("Today"):
430
 
            self.leading = True
431
 
        else:
432
 
            self.leading = False
433
 
        super(DayLabel, self).__init__()
434
 
        self.date = date
435
 
        self.day = day
436
 
        self.set_events(self._events)
437
 
        self.connect("expose_event", self.expose)
438
 
        self.connect("enter-notify-event", self._on_enter)
439
 
        self.connect("leave-notify-event", self._on_leave)
440
 
 
441
 
    def _on_enter(self, widget, event):
442
 
        widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
443
 
 
444
 
    def _on_leave(self, widget, event):
445
 
        widget.window.set_cursor(None)
446
 
 
447
 
    def expose(self, widget, event):
448
 
        context = widget.window.cairo_create()
449
 
        self.context = context
450
 
 
451
 
        bg = self.style.bg[0]
452
 
        red, green, blue = bg.red/65535.0, bg.green/65535.0, bg.blue/65535.0
453
 
        self.font_name = self.style.font_desc.get_family()
454
 
 
455
 
        widget.style.set_background(widget.window, gtk.STATE_NORMAL)
456
 
 
457
 
        # set a clip region for the expose event
458
 
        context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
459
 
        context.clip()
460
 
        self.draw(widget, event, context)
461
 
        self.day_text(widget, event, context)
462
 
        return False
463
 
 
464
 
    def day_text(self, widget, event, context):
465
 
        actual_y = self.get_size_request()[1]
466
 
        if actual_y > event.area.height:
467
 
            y = actual_y
468
 
        else:
469
 
            y = event.area.height
470
 
        x = event.area.width
471
 
        gc = self.style.fg_gc[gtk.STATE_SELECTED if self.leading else gtk.STATE_NORMAL]
472
 
        layout = widget.create_pango_layout(self.day)
473
 
        layout.set_font_description(pango.FontDescription(self.font_name + " Bold 15"))
474
 
        w, h = layout.get_pixel_size()
475
 
        widget.window.draw_layout(gc, (x-w)/2, (y)/2 - h + 5, layout)
476
 
        self.date_text(widget, event, context, (y)/2 + 5)
477
 
 
478
 
    def date_text(self, widget, event, context, lastfontheight):
479
 
        gc = self.style.fg_gc[gtk.STATE_SELECTED if self.leading else gtk.STATE_INSENSITIVE]
480
 
        layout = widget.create_pango_layout(self.date)
481
 
        layout.set_font_description(pango.FontDescription(self.font_name + " 10"))
482
 
        w, h = layout.get_pixel_size()
483
 
        widget.window.draw_layout(gc, (event.area.width-w)/2, lastfontheight, layout)
484
 
 
485
 
    def draw(self, widget, event, context):
486
 
        if self.leading:
487
 
            bg = self.style.bg[gtk.STATE_SELECTED]
488
 
            red, green, blue = bg.red/65535.0, bg.green/65535.0, bg.blue/65535.0
489
 
        else:
490
 
            bg = self.style.bg[gtk.STATE_NORMAL]
491
 
            red = (bg.red * 125 / 100)/65535.0
492
 
            green = (bg.green * 125 / 100)/65535.0
493
 
            blue = (bg.blue * 125 / 100)/65535.0
494
 
        x = 0; y = 0
495
 
        r = 5
496
 
        w, h = event.area.width, event.area.height
497
 
        context.set_source_rgba(red, green, blue, 1)
498
 
        context.new_sub_path()
499
 
        context.arc(r+x, r+y, r, math.pi, 3 * math.pi /2)
500
 
        context.arc(w-r, r+y, r, 3 * math.pi / 2, 0)
501
 
        context.close_path()
502
 
        context.rectangle(0, r, w, h)
503
 
        context.fill_preserve()
504
 
 
505
 
 
506
 
class DayButton(gtk.DrawingArea):
507
 
    leading = False
508
 
    pressed = False
509
 
    sensitive = True
510
 
    hover = False
511
 
    header_size = 60
512
 
    bg_color = (0, 0, 0, 0)
513
 
    header_color = (1, 1, 1, 1)
514
 
    leading_header_color = (1, 1, 1, 1)
515
 
    internal_color = (0, 1, 0, 1)
516
 
    arrow_color = (1,1,1,1)
517
 
    arrow_color_selected = (1, 1, 1, 1)
518
 
 
519
 
    __gsignals__ = {
520
 
        "clicked":  (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,()),
521
 
        }
522
 
    _events = (
523
 
        gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK |
524
 
        gtk.gdk.KEY_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.BUTTON_PRESS_MASK |
525
 
        gtk.gdk.MOTION_NOTIFY |   gtk.gdk.POINTER_MOTION_MASK
526
 
    )
527
 
    def __init__(self, side = 0, leading = False):
528
 
        super(DayButton, self).__init__()
529
 
        self.set_events(self._events)
530
 
        self.set_flags(gtk.CAN_FOCUS)
531
 
        self.leading = leading
532
 
        self.side = side
533
 
        self.connect("button_press_event", self.on_press)
534
 
        self.connect("button_release_event", self.clicked_sender)
535
 
        self.connect("key_press_event", self.keyboard_clicked_sender)
536
 
        self.connect("motion_notify_event", self.on_hover)
537
 
        self.connect("leave_notify_event", self._enter_leave_notify, False)
538
 
        self.connect("expose_event", self.expose)
539
 
        self.connect("style-set", self.change_style)
540
 
        self.set_size_request(20, -1)
541
 
 
542
 
    def set_sensitive(self, case):
543
 
        self.sensitive = case
544
 
        self.queue_draw()
545
 
 
546
 
    def _enter_leave_notify(self, widget, event, bol):
547
 
        self.hover = bol
548
 
        self.queue_draw()
549
 
 
550
 
    def on_hover(self, widget, event):
551
 
        if event.y > self.header_size:
552
 
            if not self.hover:
553
 
                self.hover = True
554
 
                self.queue_draw()
555
 
        else:
556
 
            if self.hover:
557
 
                self.hover = False
558
 
                self.queue_draw()
559
 
        return False
560
 
 
561
 
    def on_press(self, widget, event):
562
 
        if event.y > self.header_size:
563
 
            self.pressed = True
564
 
            self.queue_draw()
565
 
 
566
 
    def keyboard_clicked_sender(self, widget, event):
567
 
        if event.keyval in (gtk.keysyms.Return, gtk.keysyms.space):
568
 
            if self.sensitive:
569
 
                self.emit("clicked")
570
 
            self.pressed = False
571
 
            self.queue_draw()
572
 
            return True
573
 
        return False
574
 
 
575
 
    def clicked_sender(self, widget, event):
576
 
        if event.y > self.header_size:
577
 
            if self.sensitive:
578
 
                self.emit("clicked")
579
 
        self.pressed = False
580
 
        self.queue_draw()
581
 
        return True
582
 
 
583
 
    def change_style(self, *args, **kwargs):
584
 
        self.bg_color = get_gtk_rgba(self.style, "bg", 0)
585
 
        self.header_color = get_gtk_rgba(self.style, "bg", 0, 1.25)
586
 
        self.leading_header_color = get_gtk_rgba(self.style, "bg", 3)
587
 
        self.internal_color = get_gtk_rgba(self.style, "bg", 0, 1.02)
588
 
        self.arrow_color = get_gtk_rgba(self.style, "text", 0, 0.6)
589
 
        self.arrow_color_selected = get_gtk_rgba(self.style, "bg", 3)
590
 
        self.arrow_color_insensitive = get_gtk_rgba(self.style, "text", 4)
591
 
 
592
 
    def expose(self, widget, event):
593
 
        context = widget.window.cairo_create()
594
 
 
595
 
        context.set_source_rgba(*self.bg_color)
596
 
        context.set_operator(cairo.OPERATOR_SOURCE)
597
 
        context.paint()
598
 
        context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
599
 
        context.clip()
600
 
 
601
 
        x = 0; y = 0
602
 
        r = 5
603
 
        w, h = event.area.width, event.area.height
604
 
        size = 20
605
 
        if self.sensitive:
606
 
            context.set_source_rgba(*(self.leading_header_color if self.leading else self.header_color))
607
 
            context.new_sub_path()
608
 
            context.move_to(x+r,y)
609
 
            context.line_to(x+w-r,y)
610
 
            context.curve_to(x+w,y,x+w,y,x+w,y+r)
611
 
            context.line_to(x+w,y+h-r)
612
 
            context.curve_to(x+w,y+h,x+w,y+h,x+w-r,y+h)
613
 
            context.line_to(x+r,y+h)
614
 
            context.curve_to(x,y+h,x,y+h,x,y+h-r)
615
 
            context.line_to(x,y+r)
616
 
            context.curve_to(x,y,x,y,x+r,y)
617
 
            context.set_source_rgba(*(self.leading_header_color if self.leading else self.header_color))
618
 
            context.close_path()
619
 
            context.rectangle(0, r, w,  self.header_size)
620
 
            context.fill()
621
 
            context.set_source_rgba(*self.internal_color)
622
 
            context.rectangle(0, self.header_size, w,  h)
623
 
            context.fill()
624
 
            if self.hover:
625
 
                widget.style.paint_box(widget.window, gtk.STATE_PRELIGHT, gtk.SHADOW_OUT,
626
 
                                         event.area, widget, "button",
627
 
                                         event.area.x, self.header_size,
628
 
                                         w, h-self.header_size)
629
 
        size = 10
630
 
        if not self.sensitive:
631
 
            state = gtk.STATE_INSENSITIVE
632
 
        elif self.is_focus() or self.pressed:
633
 
            widget.style.paint_focus(widget.window, gtk.STATE_ACTIVE, event.area,
634
 
                                     widget, None, event.area.x, self.header_size,
635
 
                                     w, h-self.header_size)
636
 
            state = gtk.STATE_SELECTED
637
 
        else:
638
 
            state = gtk.STATE_NORMAL
639
 
        arrow = gtk.ARROW_RIGHT if self.side else gtk.ARROW_LEFT
640
 
        self.style.paint_arrow(widget.window, state, gtk.SHADOW_NONE, None,
641
 
                               self, "arrow", arrow, True,
642
 
                               w/2-size/2, h/2 + size/2, size, size)
643
 
 
644
 
 
645
 
class EventGroup(gtk.VBox):
646
 
 
647
 
    def __init__(self, title):
648
 
        super(EventGroup, self).__init__()
649
 
 
650
 
        # Create the title label
651
 
        self.label = gtk.Label(title)
652
 
        self.label.set_alignment(0.03, 0.5)
653
 
        self.pack_start(self.label, False, False, 6)
654
 
        self.events = []
655
 
        # Create the main container
656
 
        self.view = gtk.VBox()
657
 
        self.pack_start(self.view)
658
 
 
659
 
        # Connect to relevant signals
660
 
        self.connect("style-set", self.on_style_change)
661
 
 
662
 
        # Populate the widget with content
663
 
        self.get_events()
664
 
 
665
 
    def on_style_change(self, widget, style):
666
 
        """ Update used colors according to the system theme. """
667
 
        color = self.style.bg[gtk.STATE_NORMAL]
668
 
        fcolor = self.style.fg[gtk.STATE_NORMAL]
669
 
        color = combine_gdk_color(color, fcolor)
670
 
        self.label.modify_fg(gtk.STATE_NORMAL, color)
671
 
 
672
 
    @staticmethod
673
 
    def event_exists(uri):
674
 
        # TODO: Move this into Zeitgeist's datamodel.py
675
 
        return not uri.startswith("file://") or os.path.exists(
676
 
            urllib.unquote(str(uri[7:])))
677
 
 
678
 
    def set_events(self, events):
679
 
        self.events = []
680
 
        for widget in self.view:
681
 
            self.view.remove(widget)
682
 
 
683
 
        if self == pinbox:
684
 
            box = CategoryBox(None, events, True)
685
 
            self.view.pack_start(box)
686
 
        else:
687
 
            categories = {}
688
 
            for event in events:
689
 
                subject = event.subjects[0]
690
 
                if self.event_exists(subject.uri):
691
 
                    if not categories.has_key(subject.interpretation):
692
 
                        categories[subject.interpretation] = []
693
 
                    categories[subject.interpretation].append(event)
694
 
                    self.events.append(event)
695
 
 
696
 
            if not categories:
697
 
                self.hide_all()
698
 
            else:
699
 
                # Make the group title, etc. visible
700
 
                self.show_all()
701
 
 
702
 
                ungrouped_events = []
703
 
                for key in sorted(categories.iterkeys()):
704
 
                    events = categories[key]
705
 
                    if len(events) > 3:
706
 
                        box = CategoryBox(key, list(reversed(events)))
707
 
                        self.view.pack_start(box)
708
 
                    else:
709
 
                        ungrouped_events += events
710
 
 
711
 
                ungrouped_events.sort(key=lambda x: x.timestamp)
712
 
                box = CategoryBox(None, ungrouped_events)
713
 
                self.view.pack_start(box)
714
 
 
715
 
                # Make the group's contents visible
716
 
                self.view.show()
717
 
                pinbox.show_all()
718
 
 
719
 
        if len(self.events) == 0:
720
 
            self.hide()
721
 
        else:
722
 
            self.show()
723
 
 
724
 
    def get_events(self, *discard):
725
 
        if self.event_templates and len(self.event_templates) > 0:
726
 
            CLIENT.find_events_for_templates(self.event_templates,
727
 
                self.set_events, self.event_timerange, num_events=50000,
728
 
                result_type=ResultType.MostRecentSubjects)
729
 
        else:
730
 
            self.view.hide()
731
 
 
732
 
 
733
 
class DayPartWidget(EventGroup):
734
 
 
735
 
    def __init__(self, title, start, end):
736
 
        # Setup event criteria for querying
737
 
        self.event_timerange = [start * 1000, end * 1000]
738
 
        self.event_templates = (
739
 
            Event.new_for_values(interpretation=Interpretation.VISIT_EVENT.uri),
740
 
            Event.new_for_values(interpretation=Interpretation.MODIFY_EVENT.uri),
741
 
            Event.new_for_values(interpretation=Interpretation.CREATE_EVENT.uri),
742
 
            Event.new_for_values(interpretation=Interpretation.OPEN_EVENT.uri),
743
 
        )
744
 
 
745
 
        # Initialize the widget
746
 
        super(DayPartWidget, self).__init__(title)
747
 
 
748
 
        # FIXME: Move this into EventGroup
749
 
        CLIENT.install_monitor(self.event_timerange, self.event_templates,
750
 
            self.notify_insert_handler, self.notify_delete_handler)
751
 
 
752
 
    def notify_insert_handler(self, time_range, events):
753
 
        # FIXME: Don't regenerate everything, we already get the
754
 
        # information we need
755
 
        self.get_events()
756
 
 
757
 
    def notify_delete_handler(self, time_range, event_ids):
758
 
        # FIXME: Same as above
759
 
        self.get_events()
760
 
 
761
 
class PinBox(EventGroup):
762
 
 
763
 
    def __init__(self):
764
 
        # Setup event criteria for querying
765
 
        self.event_timerange = TimeRange.until_now()
766
 
 
767
 
        # Initialize the widget
768
 
        super(PinBox, self).__init__(_("Pinned items"))
769
 
 
770
 
        # Connect to relevant signals
771
 
        bookmarker.connect("reload", self.get_events)
772
 
 
773
 
    @property
774
 
    def event_templates(self):
775
 
        if not bookmarker.bookmarks:
776
 
            # Abort, or we will query with no templates and get lots of
777
 
            # irrelevant events.
778
 
            return None
779
 
 
780
 
        templates = []
781
 
        for bookmark in bookmarker.bookmarks:
782
 
            templates.append(Event.new_for_values(subject_uri=bookmark))
783
 
        return templates
784
 
 
785
 
    def set_events(self, *args, **kwargs):
786
 
        super(PinBox, self).set_events(*args, **kwargs)
787
 
        # Make the pin icons visible
788
 
        self.view.show_all()
789
 
        self.show_all()
790
 
 
791
 
pinbox = PinBox()