1
# -.- coding: utf-8 -.-
3
# GNOME Activity Journal
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>
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.
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.
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/>.
31
from datetime import date
33
from zeitgeist.client import ZeitgeistClient
34
from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation, \
39
CLIENT = ZeitgeistClient()
41
class DayWidget(gtk.VBox):
43
def __init__(self, start, end):
44
super(DayWidget, self).__init__()
46
self.day_start = start
49
self._set_date_strings()
51
(_("Morning"), start, start + 12*hour - 1),
52
(_("Afternoon"), start + 12*hour, start + 18*hour - 1),
53
(_("Evening"), start + 18*hour, end),
59
gobject.timeout_add_seconds(
60
86400 - (int(time.time() - time.timezone) % 86400), self._refresh)
66
w.on_style_change(None, None)
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")
76
self.week_day_string = date.fromtimestamp(self.day_start).strftime("%A")
77
self.emit("style-set", None)
80
self._init_date_label()
83
def _init_pinbox(self):
84
if self.day_start <= time.time() < self.day_end:
85
self.view.pack_start(pinbox, False, False)
88
def _init_widgets(self):
89
self.vbox = gtk.VBox()
90
evbox = gtk.EventBox()
93
self.pack_start(evbox)
95
self._init_date_label()
97
#label.modify_bg(gtk.STATE_SELECTED, style.bg[gtk.STATE_SELECTED])
99
self.view = gtk.VBox()
100
scroll = gtk.ScrolledWindow()
101
scroll.set_shadow_type(gtk.SHADOW_NONE)
103
evbox2 = gtk.EventBox()
104
evbox2.add(self.view)
105
self.view.set_border_width(6)
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)
114
def change_style(widget, style):
115
rc_style = self.style
116
color = rc_style.bg[gtk.STATE_NORMAL]
118
if color.red * 102/100 > 65535.0:
121
color.red = color.red * 102 / 100
123
if color.green * 102/100 > 65535.0:
124
color.green = 65535.0
126
color.green = color.green * 102 / 100
128
if color.blue * 102/100 > 65535.0:
131
color.blue = color.blue * 102 / 100
132
evbox2.modify_bg(gtk.STATE_NORMAL, color)
134
self.connect("style-set", change_style)
136
def _init_date_label(self):
137
self._set_date_strings()
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)
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)
148
def _init_events(self):
152
for period in self._periods:
153
part = DayPartWidget(period[0], period[1], period[2])
154
self.view.pack_start(part, False, False)
157
class CategoryBox(gtk.VBox):
159
def __init__(self, category, events):
160
super(CategoryBox, self).__init__()
162
self.view = gtk.VBox(True)
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)
174
# If this isn't a set of ungrouped events, give it a label
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)
183
# Add the title button
184
self.btn = CategoryButton(category, len(events))
185
self.btn.connect("toggle", self.on_toggle)
188
hbox.pack_start(lbl, False, False, 8)
189
hbox.pack_start(self.btn, True, True, 0)
190
self.pack_start(hbox, False, False)
201
self.pack_end(self.box)
205
def on_toggle(self, view, bool):
211
class DayLabel(gtk.DrawingArea):
213
def __init__(self, day, date):
214
if day == _("Today"):
218
super(DayLabel, self).__init__()
221
self.connect("expose_event", self.expose)
223
def expose(self, widget, event):
224
context = widget.window.cairo_create()
225
self.context = context
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()
231
context.set_source_rgba(red, green, blue, 1)
233
context.set_operator(cairo.OPERATOR_SOURCE)
235
# set a clip region for the expose event
236
context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
238
self.draw(widget, event, context)
239
self.day_text(widget, event, context)
242
def day_text(self, widget, event, context):
243
actual_y = self.get_size_request()[1]
244
if actual_y > event.area.height:
247
y = event.area.height
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)
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)
264
def draw(self, widget, event, context):
266
bg = self.style.bg[3]
267
red, green, blue = bg.red/65535.0, bg.green/65535.0, bg.blue/65535.0
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
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)
284
#context.set_source(grad)
285
context.set_source_rgba(red, green, blue, 1)
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)
291
context.rectangle(0, r, w, h)
292
context.fill_preserve()
294
class EventGroup(gtk.VBox):
296
def __init__(self, title):
297
super(EventGroup, self).__init__()
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)
304
# Create the main container
305
self.view = gtk.VBox()
306
self.pack_start(self.view)
308
# Connect to relevant signals
309
self.connect("style-set", self.on_style_change)
311
# Populate the widget with content
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)
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:])))
329
def set_events(self, events):
331
for widget in self.view:
332
self.view.remove(widget)
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)
345
ungrouped_events = []
346
for key in sorted(categories.iterkeys()):
347
events = categories[key]
349
box = CategoryBox(key, list(reversed(events)))
350
self.view.pack_start(box)
352
ungrouped_events += events
354
ungrouped_events.sort(key=lambda x: x.timestamp)
355
box = CategoryBox(None, ungrouped_events)
356
self.view.pack_start(box)
362
if len(bookmarker.bookmarks) > 0:
367
def _handle_find_events(self, ids):
368
CLIENT.get_events(ids, self.set_events)
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)
378
class DayPartWidget(EventGroup):
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)
388
# Initialize the widget
389
super(DayPartWidget, self).__init__(title)
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)
396
def notify_insert_handler(self, time_range, events):
397
# FIXME: Don't regenerate everything, we already get the
398
# information we need
401
def notify_delete_handler(self, time_range, event_ids):
402
# FIXME: Same as above
405
class PinBox(EventGroup):
408
# Setup event criteria for querying
409
self.event_timerange = TimeRange.until_now()
411
# Initialize the widget
412
super(PinBox, self).__init__(_("Pinned items"))
414
# Connect to relevant signals
415
bookmarker.connect("reload", lambda widget, uris: self.get_events())
416
bookmarker.connect("reload", self.check_is_visible)
418
def check_is_visible(self, widget, bookmarks):
419
if len(bookmarks) > 0:
425
def event_templates(self):
426
if not bookmarker.bookmarks:
427
# Abort, or we will query with no templates and get lots of
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: