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

« back to all changes in this revision

Viewing changes to src/supporting_widgets.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 Siegfried Gevatter <siegfried@gevatter.com>
 
7
# Copyright © 2010 Markus Korn <thekorn@gmx.de>
 
8
# Copyright © 2010 Randal Barlow <email.tehk@gmail.com>
 
9
#
 
10
# This program is free software: you can redistribute it and/or modify
 
11
# it under the terms of the GNU General Public License as published by
 
12
# the Free Software Foundation, either version 3 of the License, or
 
13
# (at your option) any later version.
 
14
#
 
15
# This program is distributed in the hope that it will be useful,
 
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
18
# GNU General Public License for more details.
 
19
#
 
20
# You should have received a copy of the GNU General Public License
 
21
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
22
 
 
23
from __future__ import with_statement
 
24
import cairo
 
25
import os
 
26
import gtk
 
27
import gettext
 
28
import datetime
 
29
import math
 
30
import time
 
31
import gobject
 
32
import pango
 
33
import gio
 
34
from dbus.exceptions import DBusException
 
35
try:
 
36
    import gst
 
37
except ImportError:
 
38
    gst = None
 
39
 
 
40
from zeitgeist.client import ZeitgeistClient
 
41
from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation, \
 
42
    ResultType
 
43
 
 
44
from common import shade_gdk_color, combine_gdk_color, is_command_available, \
 
45
    launch_command
 
46
from config import BASE_PATH, VERSION, settings, get_icon_path, get_data_path
 
47
from sources import Source, SUPPORTED_SOURCES
 
48
from gio_file import GioFile, SIZE_NORMAL, SIZE_LARGE
 
49
from bookmarker import bookmarker
 
50
try:
 
51
    from tracker_wrapper import tracker
 
52
except DBusException:
 
53
    print "Tracker disabled."
 
54
 
 
55
import content_objects
 
56
import common
 
57
 
 
58
CLIENT = ZeitgeistClient()
 
59
ITEMS = []
 
60
 
 
61
 
 
62
class DayLabel(gtk.DrawingArea):
 
63
 
 
64
 
 
65
    _events = (
 
66
        gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK |
 
67
        gtk.gdk.KEY_PRESS_MASK | gtk.gdk.BUTTON_MOTION_MASK |
 
68
        gtk.gdk.POINTER_MOTION_HINT_MASK | gtk.gdk.BUTTON_RELEASE_MASK |
 
69
        gtk.gdk.BUTTON_PRESS_MASK
 
70
    )
 
71
 
 
72
    def __init__(self,date=None):
 
73
        super(DayLabel, self).__init__()
 
74
        self.set_events(self._events)
 
75
        self.connect("expose_event", self.expose)
 
76
        if date:
 
77
            self.date = date
 
78
        else:
 
79
            self.date = datetime.date.today()
 
80
        self.set_size_request(100, 60)
 
81
 
 
82
    @property
 
83
    def date_string(self):
 
84
        return self.date.strftime("%x")
 
85
 
 
86
    @property
 
87
    def weekday_string(self):
 
88
        return self.date.strftime("%A")
 
89
 
 
90
    @property
 
91
    def leading(self):
 
92
        if self.date == datetime.date.today():
 
93
            return True
 
94
 
 
95
    def set_date(self, date):
 
96
        self.date = date
 
97
        self.queue_draw()
 
98
 
 
99
    def expose(self, widget, event):
 
100
        context = widget.window.cairo_create()
 
101
        self.context = context
 
102
 
 
103
        bg = self.style.bg[0]
 
104
        red, green, blue = bg.red/65535.0, bg.green/65535.0, bg.blue/65535.0
 
105
        self.font_name = self.style.font_desc.get_family()
 
106
 
 
107
        widget.style.set_background(widget.window, gtk.STATE_NORMAL)
 
108
 
 
109
        # set a clip region for the expose event
 
110
        context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
 
111
        context.clip()
 
112
        self.draw(widget, event, context)
 
113
        self.day_text(widget, event, context)
 
114
        return False
 
115
 
 
116
    def day_text(self, widget, event, context):
 
117
        actual_y = self.get_size_request()[1]
 
118
        if actual_y > event.area.height:
 
119
            y = actual_y
 
120
        else:
 
121
            y = event.area.height
 
122
        x = event.area.width
 
123
        gc = self.style.fg_gc[gtk.STATE_SELECTED if self.leading else gtk.STATE_NORMAL]
 
124
        layout = widget.create_pango_layout(self.weekday_string)
 
125
        layout.set_font_description(pango.FontDescription(self.font_name + " Bold 15"))
 
126
        w, h = layout.get_pixel_size()
 
127
        widget.window.draw_layout(gc, (x-w)/2, (y)/2 - h + 5, layout)
 
128
        self.date_text(widget, event, context, (y)/2 + 5)
 
129
 
 
130
    def date_text(self, widget, event, context, lastfontheight):
 
131
        gc = self.style.fg_gc[gtk.STATE_SELECTED if self.leading else gtk.STATE_INSENSITIVE]
 
132
        layout = widget.create_pango_layout(self.date_string)
 
133
        layout.set_font_description(pango.FontDescription(self.font_name + " 10"))
 
134
        w, h = layout.get_pixel_size()
 
135
        widget.window.draw_layout(gc, (event.area.width-w)/2, lastfontheight, layout)
 
136
 
 
137
    def draw(self, widget, event, context):
 
138
        if self.leading:
 
139
            bg = self.style.bg[gtk.STATE_SELECTED]
 
140
            red, green, blue = bg.red/65535.0, bg.green/65535.0, bg.blue/65535.0
 
141
        else:
 
142
            bg = self.style.bg[gtk.STATE_NORMAL]
 
143
            red = (bg.red * 125 / 100)/65535.0
 
144
            green = (bg.green * 125 / 100)/65535.0
 
145
            blue = (bg.blue * 125 / 100)/65535.0
 
146
        x = 0; y = 0
 
147
        r = 5
 
148
        w, h = event.area.width, event.area.height
 
149
        context.set_source_rgba(red, green, blue, 1)
 
150
        context.new_sub_path()
 
151
        context.arc(r+x, r+y, r, math.pi, 3 * math.pi /2)
 
152
        context.arc(w-r, r+y, r, 3 * math.pi / 2, 0)
 
153
        context.close_path()
 
154
        context.rectangle(0, r, w, h)
 
155
        context.fill_preserve()
 
156
 
 
157
 
 
158
class DayButton(gtk.DrawingArea):
 
159
    leading = False
 
160
    pressed = False
 
161
    sensitive = True
 
162
    hover = False
 
163
    header_size = 60
 
164
    bg_color = (0, 0, 0, 0)
 
165
    header_color = (1, 1, 1, 1)
 
166
    leading_header_color = (1, 1, 1, 1)
 
167
    internal_color = (0, 1, 0, 1)
 
168
    arrow_color = (1,1,1,1)
 
169
    arrow_color_selected = (1, 1, 1, 1)
 
170
 
 
171
    __gsignals__ = {
 
172
        "clicked":  (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,()),
 
173
        }
 
174
    _events = (
 
175
        gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK |
 
176
        gtk.gdk.KEY_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.BUTTON_PRESS_MASK |
 
177
        gtk.gdk.MOTION_NOTIFY |   gtk.gdk.POINTER_MOTION_MASK
 
178
    )
 
179
    def __init__(self, side = 0, leading = False):
 
180
        super(DayButton, self).__init__()
 
181
        self.set_events(self._events)
 
182
        self.set_flags(gtk.CAN_FOCUS)
 
183
        self.leading = leading
 
184
        self.side = side
 
185
        self.connect("button_press_event", self.on_press)
 
186
        self.connect("button_release_event", self.clicked_sender)
 
187
        self.connect("key_press_event", self.keyboard_clicked_sender)
 
188
        self.connect("motion_notify_event", self.on_hover)
 
189
        self.connect("leave_notify_event", self._enter_leave_notify, False)
 
190
        self.connect("expose_event", self.expose)
 
191
        self.connect("style-set", self.change_style)
 
192
        self.set_size_request(20, -1)
 
193
 
 
194
    def set_sensitive(self, case):
 
195
        self.sensitive = case
 
196
        self.queue_draw()
 
197
 
 
198
    def _enter_leave_notify(self, widget, event, bol):
 
199
        self.hover = bol
 
200
        self.queue_draw()
 
201
 
 
202
    def on_hover(self, widget, event):
 
203
        if event.y > self.header_size:
 
204
            if not self.hover:
 
205
                self.hover = True
 
206
                self.queue_draw()
 
207
        else:
 
208
            if self.hover:
 
209
                self.hover = False
 
210
                self.queue_draw()
 
211
        return False
 
212
 
 
213
    def on_press(self, widget, event):
 
214
        if event.y > self.header_size:
 
215
            self.pressed = True
 
216
            self.queue_draw()
 
217
 
 
218
    def keyboard_clicked_sender(self, widget, event):
 
219
        if event.keyval in (gtk.keysyms.Return, gtk.keysyms.space):
 
220
            if self.sensitive:
 
221
                self.emit("clicked")
 
222
            self.pressed = False
 
223
            self.queue_draw()
 
224
            return True
 
225
        return False
 
226
 
 
227
    def clicked_sender(self, widget, event):
 
228
        if event.y > self.header_size:
 
229
            if self.sensitive:
 
230
                self.emit("clicked")
 
231
        self.pressed = False
 
232
        self.queue_draw()
 
233
        return True
 
234
 
 
235
    def change_style(self, *args, **kwargs):
 
236
        self.bg_color = common.get_gtk_rgba(self.style, "bg", 0)
 
237
        self.header_color = common.get_gtk_rgba(self.style, "bg", 0, 1.25)
 
238
        self.leading_header_color = common.get_gtk_rgba(self.style, "bg", 3)
 
239
        self.internal_color = common.get_gtk_rgba(self.style, "bg", 0, 1.02)
 
240
        self.arrow_color = common.get_gtk_rgba(self.style, "text", 0, 0.6)
 
241
        self.arrow_color_selected = common.get_gtk_rgba(self.style, "bg", 3)
 
242
        self.arrow_color_insensitive = common.get_gtk_rgba(self.style, "text", 4)
 
243
 
 
244
    def expose(self, widget, event):
 
245
        context = widget.window.cairo_create()
 
246
 
 
247
        context.set_source_rgba(*self.bg_color)
 
248
        context.set_operator(cairo.OPERATOR_SOURCE)
 
249
        context.paint()
 
250
        context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
 
251
        context.clip()
 
252
 
 
253
        x = 0; y = 0
 
254
        r = 5
 
255
        w, h = event.area.width, event.area.height
 
256
        size = 20
 
257
        if self.sensitive:
 
258
            context.set_source_rgba(*(self.leading_header_color if self.leading else self.header_color))
 
259
            context.new_sub_path()
 
260
            context.move_to(x+r,y)
 
261
            context.line_to(x+w-r,y)
 
262
            context.curve_to(x+w,y,x+w,y,x+w,y+r)
 
263
            context.line_to(x+w,y+h-r)
 
264
            context.curve_to(x+w,y+h,x+w,y+h,x+w-r,y+h)
 
265
            context.line_to(x+r,y+h)
 
266
            context.curve_to(x,y+h,x,y+h,x,y+h-r)
 
267
            context.line_to(x,y+r)
 
268
            context.curve_to(x,y,x,y,x+r,y)
 
269
            context.set_source_rgba(*(self.leading_header_color if self.leading else self.header_color))
 
270
            context.close_path()
 
271
            context.rectangle(0, r, w,  self.header_size)
 
272
            context.fill()
 
273
            context.set_source_rgba(*self.internal_color)
 
274
            context.rectangle(0, self.header_size, w,  h)
 
275
            context.fill()
 
276
            if self.hover:
 
277
                widget.style.paint_box(widget.window, gtk.STATE_PRELIGHT, gtk.SHADOW_OUT,
 
278
                                         event.area, widget, "button",
 
279
                                         event.area.x, self.header_size,
 
280
                                         w, h-self.header_size)
 
281
        size = 10
 
282
        if not self.sensitive:
 
283
            state = gtk.STATE_INSENSITIVE
 
284
        elif self.is_focus() or self.pressed:
 
285
            widget.style.paint_focus(widget.window, gtk.STATE_ACTIVE, event.area,
 
286
                                     widget, None, event.area.x, self.header_size,
 
287
                                     w, h-self.header_size)
 
288
            state = gtk.STATE_SELECTED
 
289
        else:
 
290
            state = gtk.STATE_NORMAL
 
291
        arrow = gtk.ARROW_RIGHT if self.side else gtk.ARROW_LEFT
 
292
        self.style.paint_arrow(widget.window, state, gtk.SHADOW_NONE, None,
 
293
                               self, "arrow", arrow, True,
 
294
                               w/2-size/2, h/2 + size/2, size, size)
 
295
 
 
296
 
 
297
class SearchBox(gtk.EventBox):
 
298
 
 
299
    __gsignals__ = {
 
300
        "clear" : (gobject.SIGNAL_RUN_FIRST,
 
301
                   gobject.TYPE_NONE,
 
302
                   ()),
 
303
        "search" : (gobject.SIGNAL_RUN_FIRST,
 
304
                    gobject.TYPE_NONE,
 
305
                    (gobject.TYPE_PYOBJECT,))
 
306
    }
 
307
 
 
308
    def __init__(self):
 
309
        gtk.EventBox.__init__(self)
 
310
 
 
311
        self.text = ""
 
312
 
 
313
        self.set_border_width(3)
 
314
        self.hbox = gtk.HBox()
 
315
        self.add(self.hbox)
 
316
 
 
317
        self.results = []
 
318
 
 
319
        self.search = SearchEntry()
 
320
 
 
321
        self.hbox.pack_start(self.search)
 
322
        self.hbox.set_border_width(6)
 
323
 
 
324
        self.category = {}
 
325
 
 
326
        for source in SUPPORTED_SOURCES.keys():
 
327
            s = SUPPORTED_SOURCES[source]._desc_pl
 
328
            self.category[s] = source
 
329
 
 
330
        self._init_combobox()
 
331
        self.show_all()
 
332
 
 
333
        def change_style(widget, style):
 
334
 
 
335
            rc_style = self.style
 
336
            color = rc_style.bg[gtk.STATE_NORMAL]
 
337
            color = shade_gdk_color(color, 102/100.0)
 
338
            self.modify_bg(gtk.STATE_NORMAL, color)
 
339
 
 
340
            color = rc_style.bg[gtk.STATE_NORMAL]
 
341
            fcolor = rc_style.fg[gtk.STATE_NORMAL]
 
342
            color = combine_gdk_color(color, fcolor)
 
343
 
 
344
            self.search.modify_text(gtk.STATE_NORMAL, color)
 
345
 
 
346
        self.hbox.connect("style-set", change_style)
 
347
        self.search.connect("search", self.set_search)
 
348
        self.search.connect("clear", self.clear)
 
349
 
 
350
    def clear(self, widget):
 
351
        if self.text.strip() != "" and self.text.strip() != self.search.default_text:
 
352
            self.text = ""
 
353
            self.results = []
 
354
            self.emit("clear")
 
355
 
 
356
    def _init_combobox(self):
 
357
 
 
358
        self.clearbtn = gtk.Button()
 
359
        #label = gtk.Label()
 
360
        #label.set_markup("<span><b>X</b></span>")
 
361
 
 
362
        img = gtk.image_new_from_stock("gtk-close", gtk.ICON_SIZE_MENU)
 
363
        self.clearbtn.add(img)
 
364
        self.clearbtn.set_focus_on_click(False)
 
365
        self.clearbtn.set_relief(gtk.RELIEF_NONE)
 
366
        self.hbox.pack_end(self.clearbtn, False, False)
 
367
        self.clearbtn.connect("clicked", lambda button: self.hide())
 
368
        self.clearbtn.connect("clicked", lambda button: self.search.set_text(""))
 
369
 
 
370
        self.combobox = gtk.combo_box_new_text()
 
371
        self.combobox.set_focus_on_click(False)
 
372
        self.hbox.pack_end(self.combobox, False, False, 6)
 
373
        self.combobox.append_text("All activities")
 
374
        self.combobox.set_active(0)
 
375
        for cat in self.category.keys():
 
376
            self.combobox.append_text(cat)
 
377
 
 
378
    def set_search(self, widget, text=None):
 
379
        if not self.text.strip() == text.strip():
 
380
            self.text = text
 
381
            def callback(results):
 
382
                self.results = [s[1] for s in results]
 
383
                self.emit("search", results)
 
384
 
 
385
            if not text:
 
386
                text = self.search.get_text()
 
387
            if text == self.search.default_text or text.strip() == "":
 
388
                pass
 
389
            else:
 
390
                cat = self.combobox.get_active()
 
391
                if cat == 0:
 
392
                    interpretation = None
 
393
                else:
 
394
                    cat = self.category[self.combobox.get_active_text()]
 
395
                    interpretation = self.category[self.combobox.get_active_text()]
 
396
            if "tracker" in globals().keys():
 
397
                tracker.search(text, interpretation, callback)
 
398
 
 
399
 
 
400
class SearchEntry(gtk.Entry):
 
401
 
 
402
    __gsignals__ = {
 
403
        "clear" : (gobject.SIGNAL_RUN_FIRST,
 
404
                   gobject.TYPE_NONE,
 
405
                   ()),
 
406
        "search" : (gobject.SIGNAL_RUN_FIRST,
 
407
                    gobject.TYPE_NONE,
 
408
                    (gobject.TYPE_STRING,))
 
409
    }
 
410
 
 
411
    default_text = _("Type here to search...")
 
412
 
 
413
    # The font style of the text in the entry.
 
414
    #font_style = None
 
415
 
 
416
    # TODO: What is this?
 
417
    search_timeout = 0
 
418
 
 
419
    def __init__(self, accel_group = None):
 
420
        gtk.Entry.__init__(self)
 
421
 
 
422
        self.set_width_chars(30)
 
423
        self.set_text(self.default_text)
 
424
        self.set_size_request(-1, 32)
 
425
        self.connect("changed", lambda w: self._queue_search())
 
426
        self.connect("focus-in-event", self._entry_focus_in)
 
427
        self.connect("focus-out-event", self._entry_focus_out)
 
428
        #self.connect("icon-press", self._icon_press)
 
429
        self.show_all()
 
430
 
 
431
    def _icon_press(self, widget, pos, event):
 
432
        # Note: GTK_ENTRY_ICON_SECONDARY does not seem to be bound in PyGTK.
 
433
        if int(pos) == 1 and not self.get_text() == self.default_text:
 
434
            self._entry_clear_no_change_handler()
 
435
 
 
436
    def _entry_focus_in(self, widget, x):
 
437
        if self.get_text() == self.default_text:
 
438
            self.set_text("")
 
439
            #self.modify_font(self.font_style)
 
440
 
 
441
    def _entry_focus_out(self, widget, x):
 
442
        if self.get_text() == "":
 
443
            self.set_text(self.default_text)
 
444
            #self.modify_font(self.font_style)
 
445
 
 
446
    def _entry_clear_no_change_handler(self):
 
447
        if not self.get_text() == self.default_text:
 
448
            self.set_text("")
 
449
 
 
450
    def _queue_search(self):
 
451
        if self.search_timeout != 0:
 
452
            gobject.source_remove(self.search_timeout)
 
453
            self.search_timeout = 0
 
454
 
 
455
        if self.get_text() == self.default_text or len(self.get_text()) == 0:
 
456
            self.emit("clear")
 
457
        else:
 
458
            self.search_timeout = gobject.timeout_add(200, self._typing_timeout)
 
459
 
 
460
    def _typing_timeout(self):
 
461
        if len(self.get_text()) > 0:
 
462
            self.emit("search", self.get_text())
 
463
 
 
464
        self.search_timeout = 0
 
465
        return False
 
466
 
 
467
 
 
468
class PreviewTooltip(gtk.Window):
 
469
 
 
470
    # per default we are using thumbs at a size of 128 * 128 px
 
471
    # in tooltips. For preview of text files we are using 256 * 256 px
 
472
    # which is dynamically defined in StaticPreviewTooltip.preview()
 
473
    TOOLTIP_SIZE = SIZE_NORMAL
 
474
 
 
475
    def __init__(self):
 
476
        gtk.Window.__init__(self, type=gtk.WINDOW_POPUP)
 
477
 
 
478
    def preview(self, gio_file):
 
479
        return False
 
480
 
 
481
 
 
482
class StaticPreviewTooltip(PreviewTooltip):
 
483
 
 
484
    def __init__(self):
 
485
        super(StaticPreviewTooltip, self).__init__()
 
486
        self.__current = None
 
487
        self.__monitor = None
 
488
 
 
489
    def replace_content(self, content):
 
490
        children = self.get_children()
 
491
        if children:
 
492
            self.remove(children[0])
 
493
            # hack to force the tooltip to have the exact same size
 
494
            # as the child image
 
495
            self.resize(1,1)
 
496
        self.add(content)
 
497
 
 
498
    def preview(self, gio_file):
 
499
        if gio_file == self.__current:
 
500
            return bool(self.__current)
 
501
        if self.__monitor is not None:
 
502
            self.__monitor.cancel()
 
503
        self.__current = gio_file
 
504
        self.__monitor = gio_file.get_monitor()
 
505
        self.__monitor.connect("changed", self._do_update_preview)
 
506
        # for text previews we are always using SIZE_LARGE
 
507
        if "text-x-generic" in gio_file.icon_names or "text-x-script" in gio_file.icon_names:
 
508
            size = SIZE_LARGE
 
509
        else:
 
510
            size = self.TOOLTIP_SIZE
 
511
        if not isinstance(gio_file, GioFile): return False
 
512
        pixbuf = gio_file.get_thumbnail(size=size, border=1)
 
513
        if pixbuf is None:
 
514
            self.__current = None
 
515
            return False
 
516
        img = gtk.image_new_from_pixbuf(pixbuf)
 
517
        img.set_alignment(0.5, 0.5)
 
518
        img.show_all()
 
519
        self.replace_content(img)
 
520
        del pixbuf, size
 
521
        return True
 
522
 
 
523
    def _do_update_preview(self, monitor, file, other_file, event_type):
 
524
        if event_type == gio.FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
 
525
            if self.__current is not None:
 
526
                self.__current.refresh()
 
527
            self.__current = None
 
528
            gtk.tooltip_trigger_tooltip_query(gtk.gdk.display_get_default())
 
529
 
 
530
 
 
531
class VideoPreviewTooltip(PreviewTooltip):
 
532
 
 
533
    def __init__(self):
 
534
        PreviewTooltip.__init__(self)
 
535
        hbox = gtk.HBox()
 
536
        self.movie_window = gtk.DrawingArea()
 
537
        hbox.pack_start(self.movie_window)
 
538
        self.add(hbox)
 
539
        self.player = gst.element_factory_make("playbin", "player")
 
540
        bus = self.player.get_bus()
 
541
        bus.add_signal_watch()
 
542
        bus.enable_sync_message_emission()
 
543
        bus.connect("message", self.on_message)
 
544
        bus.connect("sync-message::element", self.on_sync_message)
 
545
        self.connect("hide", self._handle_hide)
 
546
        self.connect("show", self._handle_show)
 
547
        self.set_default_size(*self.TOOLTIP_SIZE)
 
548
 
 
549
    def _handle_hide(self, widget):
 
550
        self.player.set_state(gst.STATE_NULL)
 
551
 
 
552
    def _handle_show(self, widget):
 
553
        self.player.set_state(gst.STATE_PLAYING)
 
554
 
 
555
    def preview(self, gio_file):
 
556
        if gio_file.uri == self.player.get_property("uri"):
 
557
            return True
 
558
        self.player.set_property("uri", gio_file.uri)
 
559
        return True
 
560
 
 
561
    def on_message(self, bus, message):
 
562
        t = message.type
 
563
        if t == gst.MESSAGE_EOS:
 
564
            self.player.set_state(gst.STATE_NULL)
 
565
            self.hide_all()
 
566
        elif t == gst.MESSAGE_ERROR:
 
567
            self.player.set_state(gst.STATE_NULL)
 
568
            err, debug = message.parse_error()
 
569
            print "Error: %s" % err, debug
 
570
 
 
571
    def on_sync_message(self, bus, message):
 
572
        if message.structure is None:
 
573
            return
 
574
        message_name = message.structure.get_name()
 
575
        if message_name == "prepare-xwindow-id":
 
576
            imagesink = message.src
 
577
            imagesink.set_property("force-aspect-ratio", True)
 
578
            gtk.gdk.threads_enter()
 
579
            try:
 
580
                self.show_all()
 
581
                imagesink.set_xwindow_id(self.movie_window.window.xid)
 
582
            finally:
 
583
                gtk.gdk.threads_leave()
 
584
 
 
585
 
 
586
class AnimatedImage(gtk.Image):
 
587
    animating = None
 
588
    mod = 7
 
589
    i = 0
 
590
    speed = 100
 
591
    def __init__(self, uri, speed = 0):
 
592
        super(AnimatedImage, self).__init__()
 
593
        if speed: self.speed = speed
 
594
        self.frames = []
 
595
        for i in (6, 5, 4, 3, 2, 1, 0):
 
596
            self.frames.append(gtk.gdk.pixbuf_new_from_file_at_size(get_icon_path(uri % i), 16, 16))
 
597
        self.set_from_pixbuf(self.frames[0])
 
598
 
 
599
    def next(self):
 
600
        """
 
601
        Move to next frame
 
602
        """
 
603
        self.set_from_pixbuf(self.frames[self.i % self.mod])
 
604
        self.i += 1
 
605
        return True
 
606
 
 
607
    def start(self):
 
608
        """
 
609
        start the image's animation
 
610
        """
 
611
        if self.animating: gobject.source_remove(self.animating)
 
612
        self.animating = gobject.timeout_add(self.speed, self.next)
 
613
 
 
614
    def stop(self):
 
615
        """
 
616
        stop the image's animation
 
617
        """
 
618
        if self.animating: gobject.source_remove(self.animating)
 
619
        self.animating = None
 
620
        return False
 
621
 
 
622
    def animate_for_seconds(self, seconds):
 
623
        """
 
624
        :param seconds: int seconds for the amount of time when you want
 
625
        animate the throbber
 
626
        """
 
627
        self.start()
 
628
        gobject.timeout_add_seconds(seconds, self.stop)
 
629
 
 
630
 
 
631
class Throbber(gtk.ToolButton):
 
632
    def __init__(self):
 
633
        super(Throbber, self).__init__()
 
634
        self.image = AnimatedImage(get_data_path("zlogo/zg%d.png"), 150)
 
635
        self.image.set_tooltip_text(_("Powered by Zeitgeist"))
 
636
        #self.image.set_alignment(0.9, 0.98)
 
637
        self.set_icon_widget(self.image)
 
638
 
 
639
 
 
640
class AboutDialog(gtk.AboutDialog):
 
641
    name = "Activity Journal"
 
642
    authors = (
 
643
        "Seif Lotfy <seif@lotfy.com>",
 
644
        "Randal Barlow <email.tehk@gmail.com>",
 
645
        "Siegfried-Angel Gevatter <siegfried@gevatter.com>",
 
646
        "Peter Lund <peterfirefly@gmail.com>",
 
647
        "Hylke Bons <hylkebons@gmail.com>",
 
648
        "Markus Korn <thekorn@gmx.de>",
 
649
        "Mikkel Kamstrup <mikkel.kamstrup@gmail.com>"
 
650
        )
 
651
    artists = (
 
652
               "Hylke Bons <hylkebons@gmail.com>",
 
653
               "Thorsten Prante <thorsten@prante.eu>"
 
654
                )
 
655
    copyright_ = "Copyright © 2009-2010 Activity Journal authors"
 
656
    comment = "A viewport into the past powered by Zeitgeist"
 
657
    version = VERSION
 
658
    def __init__(self):
 
659
        super(AboutDialog, self).__init__()
 
660
        self.set_name(self.name)
 
661
        self.set_version(self.version)
 
662
        self.set_comments(self.comment)
 
663
        self.set_copyright(self.copyright_)
 
664
        self.set_authors(self.authors)
 
665
        self.set_artists(self.artists)
 
666
 
 
667
        license = None
 
668
        for name in ("/usr/share/common-licenses/GPL",
 
669
            os.path.join(BASE_PATH, "COPYING")):
 
670
            if os.path.isfile(name):
 
671
                with open(name) as licensefile:
 
672
                    license = licensefile.read()
 
673
                    break
 
674
        if not license:
 
675
            license = "GNU General Public License, version 3 or later."
 
676
 
 
677
        self.set_license(license)
 
678
        #self.set_logo_icon_name("gnome-activity-journal")
 
679
        self.set_logo(gtk.gdk.pixbuf_new_from_file_at_size(get_icon_path(
 
680
            "hicolor/scalable/apps/gnome-activity-journal.svg"), 48, 48))
 
681
 
 
682
 
 
683
class ContextMenu(gtk.Menu):
 
684
    subjects = []# A list of Zeitgeist event uris
 
685
    informationwindow = None
 
686
    def __init__(self):
 
687
        super(ContextMenu, self).__init__()
 
688
        self.menuitems = {
 
689
            "open" : gtk.ImageMenuItem(gtk.STOCK_OPEN),
 
690
            "unpin" : gtk.MenuItem(_("Remove Pin")),
 
691
            "pin" : gtk.MenuItem(_("Pin to Today")),
 
692
            "delete" : gtk.MenuItem(_("Delete item from Journal")),
 
693
            "delete_uri" : gtk.MenuItem(_("Delete all events with this URI")),
 
694
            "info" : gtk.MenuItem(_("More Information")),
 
695
            }
 
696
        callbacks = {
 
697
            "open" : self.do_open,
 
698
            "unpin" : self.do_unbookmark,
 
699
            "pin" : self.do_bookmark,
 
700
            "delete" : self.do_delete,
 
701
            "delete_uri" : self.do_delete_events_with_shared_uri,
 
702
            "info" : self.do_show_info,
 
703
            }
 
704
        names = ["open", "unpin", "pin", "delete", "delete_uri", "info"]
 
705
        if is_command_available("nautilus-sendto"):
 
706
            self.menuitems["sendto"] = gtk.MenuItem(_("Send To..."))
 
707
            callbacks["sendto"] = self.do_send_to
 
708
            names.append("sendto")
 
709
        for name in names:
 
710
            item = self.menuitems[name]
 
711
            self.append(item)
 
712
            item.connect("activate", callbacks[name])
 
713
        self.show_all()
 
714
 
 
715
    def do_popup(self, time, subjects):
 
716
        """
 
717
        Call this method to popup the context menu
 
718
 
 
719
        :param time: the event time from the button press event
 
720
        :param subjects: a list of uris
 
721
        """
 
722
        self.subjects = subjects
 
723
        if len(subjects) == 1:
 
724
            uri = subjects[0]
 
725
            if bookmarker.is_bookmarked(uri):
 
726
                self.menuitems["pin"].hide()
 
727
                self.menuitems["unpin"].show()
 
728
            else:
 
729
                self.menuitems["pin"].show()
 
730
                self.menuitems["unpin"].hide()
 
731
 
 
732
        self.popup(None, None, None, 3, time)
 
733
 
 
734
    def do_open(self, menuitem):
 
735
        for obj in self.subjects:
 
736
            obj.launch()
 
737
 
 
738
    def do_show_info(self, menuitem):
 
739
        if self.subjects and self.informationwindow:
 
740
            self.informationwindow.set_content_object(self.subjects[0])
 
741
 
 
742
    def do_bookmark(self, menuitem):
 
743
        for obj in self.subjects:
 
744
            uri = obj.uri
 
745
            uri = unicode(uri)
 
746
            isbookmarked = bookmarker.is_bookmarked(uri)
 
747
            if not isbookmarked:
 
748
                bookmarker.bookmark(uri)
 
749
 
 
750
    def do_unbookmark(self, menuitem):
 
751
        for obj in self.subjects:
 
752
            uri = obj.uri
 
753
            uri = unicode(uri)
 
754
            isbookmarked = bookmarker.is_bookmarked(uri)
 
755
            if isbookmarked:
 
756
                bookmarker.unbookmark(uri)
 
757
 
 
758
    def do_delete(self, menuitem):
 
759
        for obj in self.subjects:
 
760
            CLIENT.delete_events([obj.event.id])
 
761
 
 
762
    def do_delete_events_with_shared_uri(self, menuitem):
 
763
        for uri in map(lambda obj: obj.uri, self.subjects):
 
764
            CLIENT.find_event_ids_for_template(
 
765
                Event.new_for_values(subject_uri=uri),
 
766
                lambda ids: CLIENT.delete_events(map(int, ids)))
 
767
 
 
768
 
 
769
    def do_send_to(self, menuitem):
 
770
        launch_command("nautilus-sendto", map(lambda obj: obj.uri, self.subjects))
 
771
 
 
772
 
 
773
class Toolbar(gtk.Toolbar):
 
774
    @staticmethod
 
775
    def get_toolbutton(path, label_string):
 
776
        button = gtk.ToolButton()
 
777
        pixbuf = gtk.gdk.pixbuf_new_from_file(path)
 
778
        image = gtk.Image()
 
779
        image.set_from_pixbuf(pixbuf)
 
780
        button.set_icon_widget(image)
 
781
        button.set_label(label_string)
 
782
        return button
 
783
 
 
784
    def __init__(self):
 
785
        """"""
 
786
        super(Toolbar, self).__init__()
 
787
        #self.set_style(gtk.TOOLBAR_BOTH)
 
788
        self.multiview_button = mv = self.get_toolbutton(
 
789
            get_data_path("multiview_icon.png"),
 
790
            _("MultiView"))
 
791
        self.thumbview_button = tbv = self.get_toolbutton(
 
792
            get_data_path("thumbview_icon.png"),
 
793
            _("ThumbView"))
 
794
        self.timelineview_button = tlv = self.get_toolbutton(
 
795
            get_data_path("timelineview_icon.png"),
 
796
            _("TimelineView"))
 
797
        #
 
798
        #self.append_space()
 
799
        self.pin_button = pin = self.get_toolbutton(
 
800
            get_icon_path("hicolor/24x24/status/pin.png"),
 
801
            _("Show Pinned Pane"))
 
802
        separator = gtk.SeparatorToolItem()
 
803
        for item in (pin, separator, tlv, tbv, mv):
 
804
            self.insert(item, 0)
 
805
        #
 
806
        separator = gtk.SeparatorToolItem()
 
807
        separator.set_expand(True)
 
808
        separator.set_draw(False)
 
809
        self.goto_today_button = today = gtk.ToolButton(gtk.STOCK_GOTO_LAST)
 
810
        today.set_label( _("Goto Today"))
 
811
        self.throbber = Throbber()
 
812
        for item in (separator, today, self.throbber):
 
813
            self.insert(item, -1)
 
814
 
 
815
    def do_throb(self):
 
816
        self.throbber.image.animate_for_seconds(1)
 
817
 
 
818
 
 
819
class StockIconButton(gtk.Button):
 
820
    def __init__(self, stock_id, size=gtk.ICON_SIZE_BUTTON):
 
821
        super(StockIconButton, self).__init__()
 
822
        self.set_alignment(0, 0)
 
823
        self.set_relief(gtk.RELIEF_NONE)
 
824
        image = gtk.image_new_from_stock(stock_id, size)
 
825
        self.add(image)
 
826
 
 
827
 
 
828
class Pane(gtk.Frame):
 
829
    """
 
830
    A pane container
 
831
    """
 
832
    def __init__(self):
 
833
        super(Pane, self).__init__()
 
834
        #self.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
 
835
        close_button = StockIconButton(gtk.STOCK_CLOSE)
 
836
        self.set_label_widget(close_button)#, False, False)
 
837
        self.connect("delete-event", self.hide_on_delete)
 
838
        close_button.connect("clicked", self.hide_on_delete)
 
839
        self.set_label_align(0,0)
 
840
 
 
841
 
 
842
    def hide_on_delete(self, widget, *args):
 
843
        self.hide()
 
844
        return True
 
845
 
 
846
 
 
847
searchbox = SearchBox()
 
848
if gst is not None:
 
849
    VideoPreviewTooltip = VideoPreviewTooltip()
 
850
else:
 
851
    VideoPreviewTooltip = None
 
852
StaticPreviewTooltip = StaticPreviewTooltip()
 
853
ContextMenu = ContextMenu()