~manishsinha/gnome-activity-journal/fixes-924988

« back to all changes in this revision

Viewing changes to src/supporting_widgets.py

  • Committer: Bazaar Package Importer
  • Author(s): Siegfried-Angel Gevatter Pujals, Siegfried-Angel Gevatter Pujals, Manish Sinha
  • Date: 2011-07-16 19:12:03 UTC
  • mfrom: (4.1.1 sid)
  • Revision ID: james.westby@ubuntu.com-20110716191203-jpvrhnlpsi12hav7
Tags: 0.8.0-1
[ Siegfried-Angel Gevatter Pujals ]
* New upstream releases (Closes: #621011) (LP: #643795, #722227, #734412,
  #743054, #743125, #650917).
* debian/postinst:
   - Restart zeitgeist-daemon after installation, to ensure that
     the extension gets loaded (LP: #638217).
* debian/control:
   - Bump Zeitgeist dependency to 0.8.0.
   - Remove Tracker from Suggests, support for it has been disabled
     for now.
   - Bump Standards-Version to 3.9.2.
* debian/rules:
   - Remove build/ directory on clean.
* debian/copyright:
   - Update copyright years and add Stefano Candori and Collabora.

[ Manish Sinha ]
* debian/control:
   - Add Recommends on gstreamer0.10-plugins-base (LP: #705545).

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
# Copyright © 2010 Siegfried Gevatter <siegfried@gevatter.com>
7
7
# Copyright © 2010 Markus Korn <thekorn@gmx.de>
8
8
# Copyright © 2010 Randal Barlow <email.tehk@gmail.com>
 
9
# Copyright © 2010 Stefano Candori <stefano.candori@gmail.com>
9
10
#
10
11
# This program is free software: you can redistribute it and/or modify
11
12
# it under the terms of the GNU General Public License as published by
38
39
except ImportError:
39
40
    gst = None
40
41
 
41
 
from zeitgeist.client import ZeitgeistClient
42
 
from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation, \
43
 
    ResultType
 
42
from zeitgeist.datamodel import Event, Subject, StorageState
44
43
 
 
44
from common import *
45
45
import content_objects
46
 
from common import shade_gdk_color, combine_gdk_color, is_command_available, \
47
 
    launch_command, get_gtk_rgba, SIZE_NORMAL, SIZE_LARGE, GioFile
48
46
from config import BASE_PATH, VERSION, settings, PluginManager, get_icon_path, get_data_path, bookmarker, SUPPORTED_SOURCES
 
47
import external
49
48
from store import STORE, get_related_events_for_uri, CLIENT
50
 
from external import TRACKER
51
49
 
52
50
 
53
51
class DayLabel(gtk.DrawingArea):
76
74
    @property
77
75
    def weekday_string(self):
78
76
        if self.date == datetime.date.today():
79
 
            return "Today"
 
77
            return _("Today")
80
78
        timedelta = datetime.date.today() -self.date
81
79
        if timedelta.days == 1:
82
 
            return "Yesterday"
 
80
            return _("Yesterday")
83
81
        return self.date.strftime("%A")
84
82
 
85
83
    @property
107
105
 
108
106
    def day_text(self, widget, event, context):
109
107
        actual_y = self.get_size_request()[1]
110
 
        if actual_y > event.area.height:
111
 
            y = actual_y
112
 
        else:
113
 
            y = event.area.height
114
 
        x = event.area.width
 
108
        gx, gy, gw, gh, gq = widget.window.get_geometry()
 
109
        y = actual_y if actual_y > gh else gh; x = gw
 
110
 
115
111
        gc = self.style.fg_gc[gtk.STATE_SELECTED if self.leading else gtk.STATE_NORMAL]
116
112
        layout = widget.create_pango_layout(self.weekday_string)
117
113
        layout.set_font_description(pango.FontDescription(self.font_name + " Bold 15"))
120
116
        self.date_text(widget, event, context, (y)/2 + 5)
121
117
 
122
118
    def date_text(self, widget, event, context, lastfontheight):
 
119
        gx, gy, gw, gh, gq = widget.window.get_geometry()
123
120
        gc = self.style.fg_gc[gtk.STATE_SELECTED if self.leading else gtk.STATE_INSENSITIVE]
124
121
        layout = widget.create_pango_layout(self.date_string)
125
122
        layout.set_font_description(pango.FontDescription(self.font_name + " 10"))
126
123
        w, h = layout.get_pixel_size()
127
 
        widget.window.draw_layout(gc, (event.area.width-w)/2, lastfontheight, layout)
 
124
        widget.window.draw_layout(gc, (gw-w)/2, lastfontheight, layout)
128
125
 
129
126
    def draw(self, widget, event, context):
 
127
        gx, gy, w, h, gq = widget.window.get_geometry()
130
128
        if self.leading:
131
129
            bg = self.style.bg[gtk.STATE_SELECTED]
132
130
            red, green, blue = bg.red/65535.0, bg.green/65535.0, bg.blue/65535.0
137
135
            blue = (bg.blue * 125 / 100)/65535.0
138
136
        x = 0; y = 0
139
137
        r = 5
140
 
        w, h = event.area.width, event.area.height
141
138
        context.set_source_rgba(red, green, blue, 1)
142
139
        context.new_sub_path()
143
140
        context.arc(r+x, r+y, r, math.pi, 3 * math.pi /2)
150
147
class DayButton(gtk.DrawingArea):
151
148
    leading = False
152
149
    pressed = False
 
150
    today_pressed = False
153
151
    sensitive = True
 
152
    today_hover = False
154
153
    hover = False
155
154
    header_size = 60
156
155
    bg_color = (0, 0, 0, 0)
157
156
    header_color = (1, 1, 1, 1)
158
157
    leading_header_color = (1, 1, 1, 1)
159
158
    internal_color = (0, 1, 0, 1)
160
 
    arrow_color = (1,1,1,1)
 
159
    arrow_color = (1, 1, 1, 1)
161
160
    arrow_color_selected = (1, 1, 1, 1)
162
161
 
163
162
    __gsignals__ = {
164
163
        "clicked":  (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,()),
 
164
        "jump-to-today": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,()),
165
165
        }
166
166
    _events = (
167
167
        gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK |
168
168
        gtk.gdk.KEY_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.BUTTON_PRESS_MASK |
169
169
        gtk.gdk.MOTION_NOTIFY |   gtk.gdk.POINTER_MOTION_MASK
170
170
    )
 
171
 
 
172
    @classmethod
 
173
    def new(cls, side = 0, sensitive=True):
 
174
        button = DayButton(side, sensitive)
 
175
        def query_tooltip(widget, x, y, keyboard_mode, tooltip, ebutton):
 
176
            if not ebutton.sensitive:
 
177
                return False
 
178
            elif y < ebutton.header_size and ebutton.side == 1:
 
179
                text = _("Go to Today")
 
180
            elif y >= ebutton.header_size:
 
181
                text = _("Go to the previous day ") if ebutton.side == 0 else _("Go to the next day")
 
182
            else:
 
183
                return False
 
184
            tooltip.set_text(text)
 
185
            return True
 
186
        evbox = gtk.EventBox()
 
187
        evbox.connect("query-tooltip", query_tooltip, button)
 
188
        evbox.set_property("has-tooltip", True)
 
189
        evbox.add(button)
 
190
 
 
191
        return button, evbox
 
192
 
171
193
    def __init__(self, side = 0, sensitive=True):
172
194
        super(DayButton, self).__init__()
173
195
        self.set_events(self._events)
192
214
 
193
215
    def _enter_leave_notify(self, widget, event, bol):
194
216
        self.hover = bol
 
217
        self.today_hover = bol
195
218
        self.queue_draw()
196
219
 
197
220
    def on_hover(self, widget, event):
198
221
        if event.y > self.header_size:
199
222
            if not self.hover:
200
223
                self.hover = True
201
 
                self.queue_draw()
 
224
            else:
 
225
                self.today_hover = False
202
226
        else:
203
227
            if self.hover:
204
228
                self.hover = False
205
 
                self.queue_draw()
 
229
            else:
 
230
                self.today_hover = True
 
231
        self.queue_draw()
206
232
        return False
207
233
 
208
234
    def on_press(self, widget, event):
209
235
        if event.y > self.header_size:
210
236
            self.pressed = True
211
 
            self.queue_draw()
 
237
        else:
 
238
            self.today_pressed = True
 
239
        self.queue_draw()
212
240
 
213
241
    def keyboard_clicked_sender(self, widget, event):
214
242
        if event.keyval in (gtk.keysyms.Return, gtk.keysyms.space):
223
251
        if event.y > self.header_size:
224
252
            if self.sensitive:
225
253
                self.emit("clicked")
 
254
        elif event.y < self.header_size:
 
255
            self.emit("jump-to-today")
226
256
        self.pressed = False
 
257
        self.today_pressed = False;
227
258
        self.queue_draw()
228
259
        return True
229
260
 
247
278
 
248
279
        x = 0; y = 0
249
280
        r = 5
250
 
        w, h = event.area.width, event.area.height
 
281
        gx, gy, w, h, gq = widget.window.get_geometry()
251
282
        size = 20
252
283
        if self.sensitive:
253
284
            context.set_source_rgba(*(self.leading_header_color if self.leading else self.header_color))
261
292
            context.curve_to(x,y+h,x,y+h,x,y+h-r)
262
293
            context.line_to(x,y+r)
263
294
            context.curve_to(x,y,x,y,x+r,y)
264
 
            context.set_source_rgba(*(self.leading_header_color if self.leading else self.header_color))
 
295
            # What's this for, exactly? Appears to have been already set.
 
296
            #context.set_source_rgba(*(self.leading_header_color if self.leading else self.header_color))
265
297
            context.close_path()
266
298
            context.rectangle(0, r, w,  self.header_size)
267
299
            context.fill()
273
305
                                         event.area, widget, "button",
274
306
                                         event.area.x, self.header_size,
275
307
                                         w, h-self.header_size)
 
308
            if self.side > 0 and self.today_hover:
 
309
                widget.style.paint_box(widget.window, gtk.STATE_PRELIGHT, gtk.SHADOW_OUT,
 
310
                                         event.area, widget, "button",
 
311
                                         event.area.x, 0,
 
312
                                         w, self.header_size)
276
313
        size = 10
277
314
        if not self.sensitive:
278
315
            state = gtk.STATE_INSENSITIVE
287
324
        self.style.paint_arrow(widget.window, state, gtk.SHADOW_NONE, None,
288
325
                               self, "arrow", arrow, True,
289
326
                               w/2-size/2, h/2 + size/2, size, size)
 
327
        size = 7
 
328
        
 
329
        # Paint today button arrows.
 
330
        if self.sensitive and self.side > 0:
 
331
            if self.today_hover:
 
332
                if self.today_pressed:
 
333
                    self.style.paint_arrow(widget.window, gtk.STATE_SELECTED, gtk.SHADOW_NONE, None,
 
334
                                         self, "arrow", arrow, True,
 
335
                                         w/2, self.header_size/2 - size/2, size, size)
 
336
                    self.style.paint_arrow(widget.window, gtk.STATE_SELECTED, gtk.SHADOW_OUT, None,
 
337
                                         self, "arrow", arrow, True,
 
338
                                         w/2-size/2, self.header_size/2 - size/2, size, size)
 
339
                
 
340
                else:
 
341
                    self.style.paint_arrow(widget.window, state, gtk.SHADOW_NONE, None,
 
342
                                         self, "arrow", arrow, True,
 
343
                                         w/2, self.header_size/2 - size/2, size, size)
 
344
                    self.style.paint_arrow(widget.window, state, gtk.SHADOW_OUT, None,
 
345
                                         self, "arrow", arrow, True,
 
346
                                         w/2-size/2, self.header_size/2 - size/2, size, size)
 
347
            else:
 
348
                self.style.paint_arrow(widget.window, gtk.STATE_SELECTED, gtk.SHADOW_NONE, None,
 
349
                                        self, "arrow", arrow, True,
 
350
                                        w/2, self.header_size/2 - size/2, size, size)
 
351
                self.style.paint_arrow(widget.window, gtk.STATE_SELECTED, gtk.SHADOW_OUT, None,
 
352
                                        self, "arrow", arrow, True,
 
353
                                        w/2-size/2, self.header_size/2 - size/2, size, size)
 
354
        #return
290
355
 
291
356
 
292
357
class SearchBox(gtk.ToolItem):
300
365
                    (gobject.TYPE_PYOBJECT,))
301
366
    }
302
367
 
 
368
    @property
 
369
    def use_fts(self):
 
370
        #if STORE.fts_search_enabled:
 
371
        #    return self.fts_checkbutton.get_active()
 
372
        return False
 
373
 
303
374
    def __init__(self):
304
375
        gtk.ToolItem.__init__(self)
305
376
 
312
383
        self.search = SearchEntry()
313
384
        self.hbox.pack_start(self.search)
314
385
        self.category = {}
 
386
        #if STORE.fts_search_enabled:
 
387
        #    self.fts_checkbutton = gtk.CheckButton(_("Use Zeitgeist FTS"))
315
388
 
316
389
        for source in SUPPORTED_SOURCES.keys():
317
390
            s = SUPPORTED_SOURCES[source]._desc_pl
318
391
            self.category[s] = source
 
392
        self.combobox = gtk.combo_box_new_text()
 
393
        self.combobox.set_focus_on_click(False)
 
394
        self.hbox.pack_start(self.combobox, False, False, 6)
 
395
        #if STORE.fts_search_enabled:
 
396
        #    self.hbox.pack_end(self.fts_checkbutton)
 
397
        self.combobox.append_text("All activities")
 
398
        self.combobox.set_active(0)
 
399
        for cat in self.category.keys():
 
400
            self.combobox.append_text(cat)
319
401
 
320
 
        self._init_combobox()
321
402
        self.show_all()
322
403
 
323
404
        def change_style(widget, style):
339
420
        self.connect("search", self.__search)
340
421
 
341
422
    def clear(self, widget):
342
 
        if self.text.strip() != "" and self.text.strip() != self.search.default_text:
 
423
        if self.text.strip() != "" and self.text.strip() != self.search.default_text: 
343
424
            self.text = ""
344
 
            self.results = []
345
 
            self.emit("clear")
 
425
            self.search.set_text("")
 
426
            self.results = [] 
 
427
            self.emit("clear")  
346
428
 
347
 
    def _init_combobox(self):
348
 
        self.combobox = gtk.combo_box_new_text()
349
 
        self.combobox.set_focus_on_click(False)
350
 
        self.hbox.pack_end(self.combobox, False, False, 6)
351
 
        self.combobox.append_text("All activities")
352
 
        self.combobox.set_active(0)
353
 
        for cat in self.category.keys():
354
 
            self.combobox.append_text(cat)
355
429
 
356
430
    def set_search(self, widget, text=None):
357
431
        if not self.text.strip() == text.strip():
376
450
 
377
451
    def do_search(self, text, callback=None, interpretation=None):
378
452
        if not callback: return
379
 
        if TRACKER and 1==2: #DISABLED FOR NOW. Causes a crash in zeitgeist
380
 
            self.do_search_tracker(text, callback, interpretation)
381
 
        else:
382
 
            self.do_search_objs(text, callback, interpretation)
383
 
 
384
 
    @staticmethod
385
 
    def do_search_tracker(text, callback, interpretation=None):
386
 
        TRACKER.search(text, interpretation, callback)
387
 
 
388
 
    @staticmethod
389
 
    def do_search_objs(text, callback, interpretation=None):
 
453
        self.do_search_objs(text, callback, interpretation)
 
454
 
 
455
    def do_search_objs(self, text, callback, interpretation=None):
390
456
        def _search(text, callback):
391
 
            matching = []
392
 
            for obj in content_objects.ContentObject.instances:
393
 
                subject = obj.event.subjects[0]
394
 
                if text.lower() in subject.text.lower() or text in subject.uri:
395
 
                    if interpretation:
396
 
                        try:
397
 
                            if subject.interpretation != interpretation:
398
 
                                continue
399
 
                        except: continue
400
 
                    matching.append(obj)
 
457
            if STORE.fts_search_enabled and self.use_fts:
 
458
                matching = STORE.search_using_zeitgeist_fts(text, [Event.new_for_values(subject_interpretation=interpretation)] if interpretation else [])
 
459
            else:
 
460
                def matching_test_function(obj):
 
461
                    subject = obj.event.subjects[0]
 
462
                    if text.lower() in subject.text.lower() or text in subject.uri:
 
463
                        if interpretation:
 
464
                            try:
 
465
                                if subject.interpretation != interpretation:
 
466
                                    return False
 
467
                            except Exception:
 
468
                                return False
 
469
                        return True
 
470
                    return False
 
471
                matching = STORE.search_store_using_matching_function(matching_test_function)
401
472
            gtk.gdk.threads_enter()
402
473
            callback(matching)
403
474
            gtk.gdk.threads_leave()
411
482
            Event.new_for_values(subject_text="*"+text+"*", subject_interpretation=interpretation),
412
483
            Event.new_for_values(subject_uri="*"+text+"*", subject_interpretation=interpretation)
413
484
        ]
414
 
        CLIENT.find_event_ids_for_templates(templates, self._search_callback, storage_state=2, num_events=20, result_type=0)
 
485
        CLIENT.find_event_ids_for_templates(templates, self._search_callback,
 
486
            storage_state=StorageState.Available, num_events=20, result_type=0)
415
487
 
416
488
    def _search_callback(self, ids):
417
489
        objs = []
425
497
 
426
498
    def toggle_visibility(self):
427
499
        if self.get_property("visible"):
 
500
            self.clear(None)
428
501
            self.hide()
429
502
            return False
430
503
        self.show()
433
506
    def __search(self, this, results):
434
507
        content_objects.ContentObject.clear_search_matches()
435
508
        for obj in results:
436
 
            setattr(obj, "matches_search", True)
 
509
            if obj.content_object:
 
510
                setattr(obj.content_object, "matches_search", True)
437
511
 
438
512
 
439
513
class SearchEntry(gtk.Entry):
576
650
        self.movie_window = gtk.DrawingArea()
577
651
        hbox.pack_start(self.movie_window)
578
652
        self.add(hbox)
579
 
        self.player = gst.element_factory_make("playbin", "player")
 
653
        self.player = gst.element_factory_make("playbin2", "player")
580
654
        bus = self.player.get_bus()
581
655
        bus.add_signal_watch()
582
656
        bus.enable_sync_message_emission()
584
658
        bus.connect("sync-message::element", self.on_sync_message)
585
659
        self.connect("hide", self._handle_hide)
586
660
        self.connect("show", self._handle_show)
587
 
        self.set_default_size(*self.TOOLTIP_SIZE)
 
661
        self.set_default_size(*SIZE_LARGE)
588
662
 
589
663
    def _handle_hide(self, widget):
 
664
        self.hide_all()
590
665
        self.player.set_state(gst.STATE_NULL)
591
666
 
592
667
    def _handle_show(self, widget):
 
668
        self.show_all()
593
669
        self.player.set_state(gst.STATE_PLAYING)
594
670
 
595
671
    def preview(self, gio_file):
617
693
            imagesink.set_property("force-aspect-ratio", True)
618
694
            gtk.gdk.threads_enter()
619
695
            try:
620
 
                self.show_all()
621
696
                imagesink.set_xwindow_id(self.movie_window.window.xid)
622
697
            finally:
623
698
                gtk.gdk.threads_leave()
624
699
 
 
700
class AudioPreviewTooltip(PreviewTooltip):
 
701
 
 
702
    def __init__(self):
 
703
        PreviewTooltip.__init__(self)     
 
704
        #Playing label stuffs
 
705
        screen = self.get_screen()
 
706
        map_ = screen.get_rgba_colormap()
 
707
        if map_ is None:
 
708
            map_ = screen.get_rgb_colormap()
 
709
        self.set_colormap(map_)
 
710
        self.set_app_paintable(True)
 
711
        img = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY,gtk.ICON_SIZE_LARGE_TOOLBAR)
 
712
        self.image = AnimatedImage(get_data_path("zlogo/zg%d.png"), 150, size=20)
 
713
        self.image.start()
 
714
        label = gtk.Label()
 
715
        label.set_markup(_("<b>Playing...</b>"))
 
716
        hbox = gtk.HBox()
 
717
        hal = gtk.Alignment()
 
718
        hal.set_padding(0,0,5,0)
 
719
        hal.add(label)
 
720
        hbox.pack_start(self.image)
 
721
        hbox.pack_end(hal)
 
722
        self.resize(1,1)
 
723
        self.add(hbox)
 
724
        #GStreamer stuffs
 
725
        self.player = gst.element_factory_make("playbin2", "player")
 
726
        fakesink = gst.element_factory_make("fakesink", "fakesink")
 
727
        self.player.set_property("video-sink", fakesink)
 
728
        bus = self.player.get_bus()
 
729
        bus.add_signal_watch()
 
730
        bus.connect("message", self.on_message)
 
731
        self.connect("hide", self._handle_hide)
 
732
        self.connect("show", self._handle_show)
 
733
        self.connect("expose-event", self.transparent_expose)
 
734
        
 
735
    def transparent_expose(self, widget, event):
 
736
        cr = widget.window.cairo_create()
 
737
        cr.set_operator(cairo.OPERATOR_CLEAR)
 
738
        region = gtk.gdk.region_rectangle(event.area)
 
739
        cr.region(region)
 
740
        cr.fill()
 
741
        return False
 
742
 
 
743
    def _handle_hide(self, widget):
 
744
        self.image.stop()
 
745
        self.player.set_state(gst.STATE_NULL)
 
746
 
 
747
    def _handle_show(self, widget):
 
748
        self.image.start()
 
749
        self.show_all()
 
750
        self.player.set_state(gst.STATE_PLAYING)
 
751
 
 
752
    def preview(self, gio_file):
 
753
        if gio_file.uri == self.player.get_property("uri"):
 
754
            return True
 
755
        self.player.set_property("uri", gio_file.uri)
 
756
        return True
 
757
 
 
758
    def on_message(self, bus, message):
 
759
        t = message.type
 
760
        if t == gst.MESSAGE_EOS:
 
761
            self.player.set_state(gst.STATE_NULL)
 
762
        elif t == gst.MESSAGE_ERROR:
 
763
            self.player.set_state(gst.STATE_NULL)
 
764
            err, debug = message.parse_error()
 
765
            print "Error: %s" % err, debug
 
766
            
 
767
    def replace_content(self, content):
 
768
        children = self.get_children()
 
769
        if children:
 
770
            self.remove(children[0])
 
771
            # hack to force the tooltip to have the exact same size
 
772
            # as the child image
 
773
            self.resize(1,1)
 
774
        self.add(content)
625
775
 
626
776
class AnimatedImage(gtk.Image):
627
777
    animating = None
628
778
    mod = 7
629
779
    i = 0
630
780
    speed = 100
631
 
    def __init__(self, uri, speed = 0):
 
781
    def __init__(self, uri, speed = 0, size = 16):
632
782
        super(AnimatedImage, self).__init__()
633
783
        if speed: self.speed = speed
634
784
        self.frames = []
635
785
        for i in (6, 5, 4, 3, 2, 1, 0):
636
 
            self.frames.append(gtk.gdk.pixbuf_new_from_file_at_size(get_icon_path(uri % i), 16, 16))
 
786
            self.frames.append(gtk.gdk.pixbuf_new_from_file_at_size(get_icon_path(uri % i), size, size))
637
787
        self.set_from_pixbuf(self.frames[0])
638
788
 
639
789
    def next(self):
668
818
        gobject.timeout_add_seconds(seconds, self.stop)
669
819
 
670
820
 
671
 
class Throbber(gtk.ToolButton):
 
821
class ThrobberPopupButton(gtk.ToolItem):
 
822
 
 
823
    __gsignals__ = {
 
824
        "toggle-erase-mode" : (gobject.SIGNAL_RUN_FIRST,
 
825
                   gobject.TYPE_NONE,
 
826
                   ()),
 
827
    }
 
828
 
672
829
    def __init__(self):
673
 
        super(Throbber, self).__init__()
674
 
        self.image = AnimatedImage(get_data_path("zlogo/zg%d.png"), 150)
675
 
        self.image.set_tooltip_text(_("Powered by Zeitgeist"))
676
 
        #self.image.set_alignment(0.9, 0.98)
677
 
        self.set_icon_widget(self.image)
 
830
        super(ThrobberPopupButton, self).__init__()
 
831
        box = gtk.HBox()
 
832
        self.button = button = gtk.ToggleButton()
 
833
        button.set_relief(gtk.RELIEF_NONE)
 
834
        self.image = image = AnimatedImage(get_data_path("zlogo/zg%d.png"), 150)
 
835
        image.set_tooltip_text(_("Preferences"))
 
836
        arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE)
 
837
        arrow.set_size_request(10, 10)
 
838
        box.pack_start(image, True, True, 1)
 
839
        box.pack_start(arrow, True, True, 1)
 
840
        button.add(box)
 
841
        self.add(button)
 
842
 
 
843
        self.menu = gtk.Menu()
 
844
        self.about = get_menu_item_with_stock_id_and_text(gtk.STOCK_ABOUT, _("About"))
 
845
        self.preferences = get_menu_item_with_stock_id_and_text(gtk.STOCK_PREFERENCES, _("Preferences"))
 
846
        self.erase_mode = get_menu_item_with_stock_id_and_text(gtk.STOCK_CANCEL, _("Toggle Erase Mode"))
 
847
        for item in (self.about, self.erase_mode, self.preferences): self.menu.insert(item, 0)
 
848
        self.about.connect("activate", self.show_about_window)
 
849
        self.erase_mode.connect("activate", lambda x: self.emit("toggle-erase-mode"))
 
850
        self.menu.show_all()
 
851
        button.connect("toggled", self.on_toggle)
 
852
        self.preferences.connect("activate", lambda *args: self.preferences.toggle())
 
853
        self.menu.connect("hide", self.on_hide)
 
854
 
 
855
    def on_hide(self, *args):
 
856
        self.button.set_active(False)
 
857
        self.menu.popdown()
 
858
        return False
 
859
 
 
860
    def on_toggle(self, widget):
 
861
        alloc = self.get_allocation()
 
862
        x, y = self.window.get_position()
 
863
        func = lambda a:(x+alloc.x, y+alloc.y+alloc.height, True)
 
864
        #print func(1,2)
 
865
        self.menu.popup(None, None, func, 0, 0)
 
866
 
 
867
    def show_about_window(self, *etc):
 
868
        aboutwindow = AboutDialog()
 
869
        window = self.get_toplevel()
 
870
        aboutwindow.set_transient_for(window)
 
871
        aboutwindow.run()
 
872
        aboutwindow.destroy()
 
873
        self.preferences.toggle()
678
874
 
679
875
 
680
876
class AboutDialog(gtk.AboutDialog):
686
882
        "Peter Lund <peterfirefly@gmail.com>",
687
883
        "Hylke Bons <hylkebons@gmail.com>",
688
884
        "Markus Korn <thekorn@gmx.de>",
689
 
        "Mikkel Kamstrup <mikkel.kamstrup@gmail.com>"
690
 
        "Thorsten Prante <thorsten@prante.eu>"
 
885
        "Mikkel Kamstrup <mikkel.kamstrup@gmail.com>",
 
886
        "Thorsten Prante <thorsten@prante.eu>",
 
887
        "Stefano Candori <stefano.candori@gmail.com>"
691
888
        )
692
889
    artists = (
693
890
               "Hylke Bons <hylkebons@gmail.com>",
694
891
               "Thorsten Prante <thorsten@prante.eu>"
695
892
                )
696
 
    copyright_ = "Copyright © 2009-2010 Activity Journal authors"
 
893
    copyright_ = "Copyright © 2009-2011 Activity Journal authors"
697
894
    comment = "A viewport into the past powered by Zeitgeist"
698
895
    version = VERSION
699
896
    def __init__(self):
723
920
 
724
921
class ContextMenu(gtk.Menu):
725
922
    subjects = []# A list of Zeitgeist event uris
726
 
    informationwindow = None
 
923
    infowindow = None
 
924
    parent_window = None
 
925
    
727
926
    def __init__(self):
728
927
        super(ContextMenu, self).__init__()
729
 
        self.infowindow = None
730
928
        self.menuitems = {
731
929
            "open" : gtk.ImageMenuItem(gtk.STOCK_OPEN),
732
930
            "unpin" : gtk.MenuItem(_("Remove Pin")),
733
931
            "pin" : gtk.MenuItem(_("Add Pin")),
734
 
            "delete" : gtk.MenuItem(_("Delete item from Journal")),
 
932
            "delete" : get_menu_item_with_stock_id_and_text(gtk.STOCK_DELETE, _("Delete item from Journal")),
735
933
            "delete_uri" : gtk.MenuItem(_("Delete all events with this URI")),
736
 
            "info" : gtk.MenuItem(_("More Information")),
 
934
            "info" : get_menu_item_with_stock_id_and_text(gtk.STOCK_INFO, _("More Information")),
737
935
            }
738
936
        callbacks = {
739
937
            "open" : self.do_open,
745
943
            }
746
944
        names = ["open", "unpin", "pin", "delete", "delete_uri", "info"]
747
945
        if is_command_available("nautilus-sendto"):
748
 
            self.menuitems["sendto"] = gtk.MenuItem(_("Send To..."))
 
946
            self.menuitems["sendto"] = get_menu_item_with_stock_id_and_text(gtk.STOCK_CONNECT, _("Send To..."))
749
947
            callbacks["sendto"] = self.do_send_to
750
948
            names.append("sendto")
751
949
        for name in names:
781
979
        if self.subjects:
782
980
            if self.infowindow:
783
981
                self.infowindow.destroy()
784
 
            self.infowindow = InformationContainer()
785
 
            self.infowindow.set_position(gtk.WIN_POS_CENTER)
 
982
            self.infowindow = InformationContainer(parent=self.parent_window)
786
983
            self.infowindow.set_content_object(self.subjects[0])
787
 
            self.infowindow.set_size_request(400,400)
788
984
            self.infowindow.show_all()
789
985
 
790
986
    def do_bookmark(self, menuitem):
800
996
            uri = obj.uri
801
997
            uri = unicode(uri)
802
998
            isbookmarked = bookmarker.is_bookmarked(uri)
803
 
            if isbookmarked:
 
999
            if isbookmarked:               
804
1000
                bookmarker.unbookmark(uri)
805
1001
 
806
1002
    def do_delete(self, menuitem):
807
1003
        for obj in self.subjects:
808
 
            CLIENT.delete_events([obj.event.id])
 
1004
            CLIENT.find_event_ids_for_template(
 
1005
                Event.new_for_values(subject_uri=obj.uri),
 
1006
                lambda ids: CLIENT.delete_events(map(int, ids)),
 
1007
                timerange=DayParts.get_day_part_range_for_item(obj))
 
1008
                
 
1009
    def do_delete_object(self, obj):
 
1010
        if obj is None: return
 
1011
        CLIENT.find_event_ids_for_template(
 
1012
            Event.new_for_values(subject_uri=obj.uri),
 
1013
            lambda ids: CLIENT.delete_events(map(int, ids)))
 
1014
 
809
1015
 
810
1016
    def do_delete_events_with_shared_uri(self, menuitem):
811
1017
        for uri in map(lambda obj: obj.uri, self.subjects):
816
1022
    def do_send_to(self, menuitem):
817
1023
        launch_command("nautilus-sendto", map(lambda obj: obj.uri, self.subjects))
818
1024
 
819
 
 
820
 
class ToolButton(gtk.ToolButton):
 
1025
    def set_parent_window(self, parent):
 
1026
        self.parent_window = parent
 
1027
        
 
1028
class ContextMenuMolteplicity(gtk.Menu):
 
1029
    subjects = []# A list of Zeitgeist event uris
 
1030
    infowindow = None
 
1031
    parent_window = None
 
1032
    
 
1033
    def __init__(self):
 
1034
        super(ContextMenuMolteplicity, self).__init__()
 
1035
        self.menuitems = {
 
1036
            "delete" : get_menu_item_with_stock_id_and_text(gtk.STOCK_DELETE, _("Delete items from Journal")),
 
1037
            "info" : get_menu_item_with_stock_id_and_text(gtk.STOCK_INFO, _("Show all grouped items"))
 
1038
            }
 
1039
        callbacks = {
 
1040
            "delete" : self.do_delete,
 
1041
            "info" : self.do_show_info
 
1042
            }
 
1043
        names = ["delete", "info"]  
 
1044
        for name in names:
 
1045
            item = self.menuitems[name]
 
1046
            self.append(item)
 
1047
            item.connect("activate", callbacks[name])
 
1048
        self.show_all()
 
1049
 
 
1050
    def do_popup(self, time, subjects):
 
1051
        """
 
1052
        Call this method to popup the context menu
 
1053
 
 
1054
        :param time: the event time from the button press event
 
1055
        :param subjects: a list of uris
 
1056
        """
 
1057
        self.subjects = subjects
 
1058
        if len(subjects) == 1:
 
1059
            uri = subjects[0].uri
 
1060
            
 
1061
        self.popup(None, None, None, 3, time)
 
1062
        
 
1063
    def do_show_info(self, menuitem):
 
1064
        if self.subjects:
 
1065
            self.do_show_molteplicity_list(self.subjects)
 
1066
            
 
1067
    def do_show_molteplicity_list(self, item_list):
 
1068
        if item_list:
 
1069
            if self.infowindow:
 
1070
                self.infowindow.destroy()
 
1071
            self.infowindow = MolteplicityInformationContainer(parent=self.parent_window)
 
1072
            self.infowindow.set_item_list(item_list)
 
1073
            self.infowindow.show_all()    
 
1074
            
 
1075
    def do_delete(self, menuitem):
 
1076
        for obj_ in self.subjects:
 
1077
            obj = obj_.content_object
 
1078
            CLIENT.find_event_ids_for_template(
 
1079
                Event.new_for_values(subject_uri=obj.uri),
 
1080
                lambda ids: CLIENT.delete_events(map(int, ids)),
 
1081
                timerange=DayParts.get_day_part_range_for_item(obj))
 
1082
                
 
1083
    def do_delete_list(self, list_):
 
1084
        for obj_ in list_:
 
1085
            obj = obj_.content_object
 
1086
            CLIENT.find_event_ids_for_template(
 
1087
                Event.new_for_values(subject_uri=obj.uri),
 
1088
                lambda ids: CLIENT.delete_events(map(int, ids)),
 
1089
                timerange=DayParts.get_day_part_range_for_item(obj))
 
1090
 
 
1091
 
 
1092
    def set_parent_window(self, parent):
 
1093
        self.parent_window = parent
 
1094
 
 
1095
 
 
1096
class ToolButton(gtk.RadioToolButton):
821
1097
    def __init__(self, *args, **kwargs):
822
1098
        super(ToolButton, self).__init__(*args, **kwargs)
823
1099
 
828
1104
    def set_tooltip_text(self, text):
829
1105
        gtk.Widget.set_tooltip_text(self, text)
830
1106
 
 
1107
class InformationToolButton(gtk.ToolButton):
 
1108
    def __init__(self, *args, **kwargs):
 
1109
        super(InformationToolButton, self).__init__(*args, **kwargs)
 
1110
 
 
1111
    def set_label(self, text):
 
1112
        super(InformationToolButton, self).set_label(text)
 
1113
        self.set_tooltip_text(text)
 
1114
 
 
1115
    def set_tooltip_text(self, text):
 
1116
        gtk.Widget.set_tooltip_text(self, text)
 
1117
 
831
1118
 
832
1119
class Toolbar(gtk.Toolbar):
 
1120
 
 
1121
    __gsignals__ = {
 
1122
        "previous" : (gobject.SIGNAL_RUN_FIRST,
 
1123
                   gobject.TYPE_NONE,
 
1124
                   ()),
 
1125
        "jump-to-today" : (gobject.SIGNAL_RUN_FIRST,
 
1126
                   gobject.TYPE_NONE,
 
1127
                   ()),
 
1128
        "next" : (gobject.SIGNAL_RUN_FIRST,
 
1129
                   gobject.TYPE_NONE,
 
1130
                   ()),
 
1131
    }
 
1132
    
833
1133
    @staticmethod
834
 
    def get_toolbutton(path, label_string):
835
 
        button = ToolButton()
 
1134
    def get_toolbutton(path, label_string, radio=True):
 
1135
        if radio:
 
1136
            button = ToolButton()
 
1137
        else:
 
1138
            button = InformationToolButton()
836
1139
        pixbuf = gtk.gdk.pixbuf_new_from_file(path)
837
1140
        image = gtk.Image()
838
1141
        image.set_from_pixbuf(pixbuf)
842
1145
 
843
1146
    def __init__(self):
844
1147
        super(Toolbar, self).__init__()
845
 
        #self.set_style(gtk.TOOLBAR_BOTH)
846
 
        #
847
 
        #self.append_space()
 
1148
        #Search button
848
1149
        self.search_button = sb = gtk.ToolButton(gtk.STOCK_FIND)
849
 
        self.search_button.set_tooltip_text(_("Search"))
850
1150
        self.search_dialog = sdialog = SearchBox
851
1151
        self.search_dialog.search.connect("close", self.toggle_searchbox_visibility)
852
1152
        self.search_button.connect("clicked", self.toggle_searchbox_visibility)
 
1153
        #Previuos-day button
 
1154
        self.previousd_button = pdb = gtk.ToolButton(gtk.STOCK_GO_BACK)
 
1155
        self.previousd_button.connect("clicked", lambda x: self.emit("previous"))
 
1156
        #Jump-to-today button
 
1157
        self.home_button = hb = gtk.ToolButton(gtk.STOCK_HOME)
 
1158
        self.home_button.connect("clicked", lambda x: self.emit("jump-to-today"))
 
1159
        self.home_button.set_sensitive(False)
 
1160
        #Next-day button button
 
1161
        self.nextd_button = ndb = gtk.ToolButton(gtk.STOCK_GO_FORWARD)
 
1162
        self.nextd_button.connect("clicked", lambda x: self.emit("next"))
 
1163
        self.nextd_button.set_sensitive(False)
853
1164
 
854
1165
        sep1 = gtk.SeparatorToolItem()
855
 
        sep2 = gtk.SeparatorToolItem()
856
 
        for item in (sdialog, sb, sep1):
 
1166
        sep2 = gtk.SeparatorToolItem()       
 
1167
        for item in (sdialog, sb, sep1, ndb, hb, pdb, sep2):
857
1168
            self.insert(item, 0)
858
 
        #
859
 
        self.preference_button = gtk.ToolButton(gtk.STOCK_PREFERENCES)
860
 
        self.preference_button.set_tooltip_text(_("Preferences"))
 
1169
            
861
1170
        separator = gtk.SeparatorToolItem()
862
1171
        separator.set_expand(True)
863
 
        separator.set_draw(False)
864
 
        self.goto_today_button = today = gtk.ToolButton(gtk.STOCK_GOTO_LAST)
865
 
        today.set_label( _("Go to Today"))
866
 
        today.set_tooltip_text( _("Go to Today"))
867
 
        self.throbber = Throbber()
868
 
        for item in (separator, today, self.preference_button, self.throbber):
869
 
            self.insert(item, -1)
870
 
 
871
 
    def do_throb(self):
872
 
        self.throbber.image.animate_for_seconds(1)
 
1172
        separator.set_draw(False)            
 
1173
        self.insert(separator, -1)
873
1174
 
874
1175
    def toggle_searchbox_visibility(self, w):
875
1176
        result = self.search_dialog.toggle_visibility()
882
1183
        self.insert(button, i)
883
1184
        button.show()
884
1185
 
885
 
 
886
 
class TagCloud(gtk.VBox):
887
 
    __gsignals__ = {
888
 
        "add-tag":  (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,(gobject.TYPE_STRING,)),
889
 
        "remove-tag":  (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,(gobject.TYPE_STRING,)),
890
 
    }
891
 
 
892
 
    max_font_size = 11000.0
893
 
    min_font_size = 6000.0
894
 
    mid_font_size = 8500.0
895
 
    _size_diff = max_font_size - min_font_size
896
 
 
897
 
    class SelectTagMenu(gtk.Menu):
898
 
        def clear(self):
899
 
            for item in self.get_children():
900
 
                self.remove(item)
901
 
                item.destroy()
902
 
 
903
 
        def set_tags(self, tags, callback):
904
 
            self.clear()
905
 
            for tag in tags:
906
 
                item = gtk.MenuItem(label=tag)
907
 
                self.add(item)
908
 
                item.connect("activate", callback, tag)
909
 
            self.show_all()
910
 
 
911
 
    def __init__(self):
912
 
        super(TagCloud, self).__init__()
913
 
        self.tag_dict = {}
914
 
        self.label = gtk.Label()
915
 
        self.pack_start(self.label, True, True)
916
 
        self.label.set_line_wrap(True)
917
 
        self.label.set_line_wrap_mode(pango.WRAP_WORD)
918
 
        self.label.set_justify(gtk.JUSTIFY_CENTER)
919
 
        self.label.connect( "size-allocate", self.size_allocate )
920
 
        self.button_box = box = gtk.HBox()
921
 
        self.entry_box = entry_box = gtk.HBox()
922
 
        self.pack_end(self.button_box, False, False)
923
 
        self.add_button = add = StockIconButton(gtk.STOCK_CANCEL)
924
 
        self.finish_button = finish = StockIconButton(gtk.STOCK_OK)
925
 
        self.remove_button = remove = StockIconButton(gtk.STOCK_REMOVE)
926
 
        self.entry = entry = gtk.Entry()
927
 
        box.pack_start(add, False, False)
928
 
        box.pack_start(entry_box)
929
 
        box.pack_end(remove, False, False)
930
 
        entry_box.pack_start(entry)
931
 
        entry_box.pack_end(finish, False, False)
932
 
        finish.connect("clicked", self._add_tag)
933
 
        entry.connect("activate", self._add_tag)
934
 
        self.add_button.connect("clicked", self.toggle_tag_entry_box)
935
 
        self.remove_button.connect("button-press-event", self.show_remove_menu)
936
 
        entry_box.show_all()
937
 
        entry_box.set_no_show_all(True)
938
 
        # Remove tags
939
 
        self.tag_menu = tag_menu = self.SelectTagMenu()
940
 
        tag_menu.attach_to_widget(remove, lambda *args:None)
941
 
        ##
942
 
        self.toggle_tag_entry_box()
943
 
        self.label.connect("activate-link", self.on_tag_activated)
944
 
 
945
 
    def show_remove_menu(self, w, event):
946
 
        if event.button == 1 and self.tag_dict:
947
 
            self.tag_menu.set_tags(self.tag_dict.keys(), self.remove_tag)
948
 
            self.tag_menu.popup(None, None, None, event.button, event.time)
949
 
 
950
 
    def toggle_tag_entry_box(self, *args):
951
 
        if self.entry_box.get_property("visible"):
952
 
            self.entry_box.hide()
953
 
            self.entry.set_text("")
954
 
            self.add_button.set_stock(gtk.STOCK_ADD)
955
 
        else:
956
 
            self.entry_box.show()
957
 
            self.add_button.set_stock(gtk.STOCK_CANCEL)
958
 
 
959
 
    def remove_tag(self, w, tag):
960
 
        if tag:
961
 
            self.emit("remove-tag", tag)
962
 
 
963
 
    def _add_tag(self, *args):
964
 
        tag = self.entry.get_text()
965
 
        if tag:
966
 
            self.emit("add-tag", tag)
967
 
        self.toggle_tag_entry_box()
968
 
 
969
 
    def get_text(self):
970
 
        return self.label.get_text()
971
 
 
972
 
    def set_text(self, text):
973
 
        return self.label.set_text(text)
974
 
 
975
 
    def set_markup(self, markup):
976
 
        return self.label.set_markup(markup)
977
 
 
978
 
    def size_allocate(self, label, alloc):
979
 
        label.set_size_request(alloc.width - 2, -1 )
980
 
 
981
 
    def clear(self):
982
 
        self.set_text("")
983
 
 
984
 
    def make_tag(self, tag, value, min_value, max_value):
985
 
        if (min_value+max_value+value)/3 == value:
986
 
            size = self.mid_font_size
987
 
        else:
988
 
            value = (value-min_value)/(max_value-min_value)
989
 
            size = (value * self._size_diff) + self.min_font_size
990
 
        return "<a href='" + tag + "'><span size='" + str(int(size)) + "'>" + tag + "</span></a>"
991
 
 
992
 
    def on_tag_activated(self, widget, tag):
993
 
        if TRACKER:
994
 
            def _thread():
995
 
                files = TRACKER.get_uris_for_tag(tag)
996
 
                if files:
997
 
                    results = []
998
 
                    for obj in content_objects.ContentObject.instances:
999
 
                        if obj.uri in files:
1000
 
                            results.append(obj)
1001
 
                    SearchBox.emit("search", results)
1002
 
            thread = threading.Thread(target=_thread)
1003
 
            thread.start()
1004
 
        return True
1005
 
 
1006
 
    def set_tags(self, tag_dict):
1007
 
        self.tag_dict = tag_dict
1008
 
        if not tag_dict: return self.set_text("")
1009
 
        text_lst = []
1010
 
        min_value = min(float(min(1, *tag_dict.values())), 1)
1011
 
        max_value = max(float(max(1, *tag_dict.values())), 1)
1012
 
        for tag in tag_dict.keys():
1013
 
            text_lst.append(self.make_tag(tag, tag_dict[tag], min_value, max_value))
1014
 
        self.set_markup("  ".join(text_lst))
1015
 
 
1016
 
 
1017
1186
class StockIconButton(gtk.Button):
1018
1187
    def __init__(self, stock_id, label=None, size=gtk.ICON_SIZE_BUTTON):
1019
1188
        super(StockIconButton, self).__init__()
1041
1210
class InformationBox(gtk.VBox):
1042
1211
    """
1043
1212
    Holds widgets which display information about a uri
 
1213
    
 
1214
    obj: the content object to be shown
 
1215
    is_molteplicity: bool used to discriminate grouped items from single ones
1044
1216
    """
1045
1217
    obj = None
 
1218
    is_molteplicity = False
1046
1219
 
1047
1220
    class _ImageDisplay(gtk.Image):
1048
1221
        """
1049
1222
        A display based on GtkImage to display a uri's thumb or icon using GioFile
1050
1223
        """
1051
1224
        def set_content_object(self, obj):
1052
 
            if obj:
 
1225
            if obj:             
1053
1226
                if isinstance(obj, GioFile) and obj.has_preview():
1054
1227
                    pixbuf = obj.get_thumbnail(size=SIZE_NORMAL, border=3)
 
1228
                    if pixbuf is None: pixbuf = obj.get_icon(size=64)
1055
1229
                else:
1056
1230
                    pixbuf = obj.get_icon(size=64)
 
1231
                    
 
1232
                if pixbuf is None: pixbuf = obj.get_actor_pixbuf(size=64)
1057
1233
                self.set_from_pixbuf(pixbuf)
1058
1234
 
1059
1235
    def __init__(self):
1063
1239
        self.label = gtk.Label()
1064
1240
        self.pathlabel = gtk.Label()
1065
1241
        self.pathlabel.modify_font(pango.FontDescription("Monospace 7"))
 
1242
        self.box_label = gtk.EventBox()
 
1243
        self.box_label.add(self.pathlabel)
 
1244
        self.box_label.connect("button-press-event", self.on_path_label_clicked)
 
1245
        self.box_label.connect("enter-notify-event", self.on_path_label_enter)
 
1246
        self.box_label.connect("leave-notify-event", self.on_path_label_leave)
 
1247
        self.box_label.connect("realize", self.on_realize_event)
1066
1248
        labelvbox = gtk.VBox()
1067
1249
        labelvbox.pack_start(self.label)
1068
 
        labelvbox.pack_end(self.pathlabel)
 
1250
        labelvbox.pack_end(self.box_label)
1069
1251
        self.pack_start(labelvbox, True, True, 5)
1070
1252
        self.box.set_shadow_type(gtk.SHADOW_NONE)
1071
1253
        vbox.pack_start(self.box, True, True)
1074
1256
        self.add(vbox)
1075
1257
        self.display_widget = self._ImageDisplay()
1076
1258
        self.box.add(self.display_widget)
1077
 
        self.show_all()
 
1259
        self.show_all()  
1078
1260
 
1079
1261
    def set_displaytype(self, obj):
1080
1262
        """
1086
1268
    def set_content_object(self, obj):
1087
1269
        self.obj = obj
1088
1270
        self.set_displaytype(obj)
1089
 
        self.label.set_markup("<span size='10336'>" + obj.text.replace("&", "&amp;") + "</span>")
1090
 
        self.pathlabel.set_markup("<span color='#979797'>" + obj.uri.replace("&", "&amp;") + "</span>")
1091
 
 
 
1271
        text = get_text_or_uri(obj,molteplicity=self.is_molteplicity)
 
1272
        self.label.set_markup("<span size='10336'>" + text + "</span>")
 
1273
        path = obj.uri.replace("&", "&amp;").replace("%20", " ")
 
1274
        self.is_file = path.startswith("file://")
 
1275
        if self.is_file:
 
1276
            self.textpath = os.path.dirname(path)[7:]
 
1277
        else:
 
1278
            if self.is_molteplicity:
 
1279
                path = text.split(" ")[-1]  #take only the uri
 
1280
            self.textpath = path
 
1281
            
 
1282
        self.pathlabel.set_markup("<span color='#979797'>" + self.textpath + "</span>")
 
1283
 
 
1284
    def on_realize_event(self, parms):
 
1285
        if self.is_file:
 
1286
            hand = gtk.gdk.Cursor(gtk.gdk.HAND2)
 
1287
            self.box_label.window.set_cursor(hand)    
 
1288
   
 
1289
    def on_path_label_clicked(self, wid, e):
 
1290
        if self.is_file:
 
1291
            os.system('xdg-open "%s"' % self.textpath)
 
1292
 
 
1293
    def on_path_label_enter(self, wid, e):
 
1294
        if self.is_file:
 
1295
            self.pathlabel.set_markup("<span color='#970000'><b>" + self.textpath+ "</b></span>")
 
1296
 
 
1297
    def on_path_label_leave(self, wid, e):
 
1298
        if self.is_file:
 
1299
            self.pathlabel.set_markup("<span color='#999797'>" + self.textpath + "</span>")
1092
1300
 
1093
1301
class _RelatedPane(gtk.TreeView):
1094
1302
    """
1100
1308
 
1101
1309
    Displays related events using a widget based on gtk.TreeView
1102
1310
    """
1103
 
    def __init__(self):
 
1311
    def __init__(self, column_name):
1104
1312
        super(_RelatedPane, self).__init__()
1105
1313
        self.popupmenu = ContextMenu
1106
1314
        self.connect("button-press-event", self.on_button_press)
1107
1315
        self.connect("row-activated", self.row_activated)
1108
 
        pcolumn = gtk.TreeViewColumn(_("Used With"))
 
1316
        pcolumn = gtk.TreeViewColumn(_(column_name))
1109
1317
        pixbuf_render = gtk.CellRendererPixbuf()
1110
1318
        pcolumn.pack_start(pixbuf_render, False)
1111
1319
        pcolumn.set_cell_data_func(pixbuf_render, self.celldatamethod, "pixbuf")
1114
1322
        pcolumn.pack_end(text_render, True)
1115
1323
        pcolumn.set_cell_data_func(text_render, self.celldatamethod, "text")
1116
1324
        self.append_column(pcolumn)
1117
 
        #self.set_headers_visible(False)
 
1325
        self.set_headers_visible(True)
1118
1326
 
1119
1327
    def celldatamethod(self, column, cell, model, iter_, user_data):
1120
1328
        if model:
1121
1329
            obj = model.get_value(iter_, 0)
1122
1330
            if user_data == "text":
1123
 
                cell.set_property("text", obj.text.replace("&", "&amp;"))
 
1331
                text = get_text_or_uri(obj)
 
1332
                cell.set_property("text", text)
1124
1333
            elif user_data == "pixbuf":
1125
1334
                cell.set_property("pixbuf", obj.icon)
1126
1335
 
1162
1371
                obj = model[path[0]][0]
1163
1372
                obj.launch()
1164
1373
 
1165
 
 
1166
 
class InformationContainer(gtk.Window):
 
1374
class InformationContainer(gtk.Dialog):
1167
1375
    """
1168
1376
    . . . . .
1169
1377
    .  URI  .
1187
1395
        def __init__(self):
1188
1396
            gtk.Toolbar.__init__(self)
1189
1397
            self.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR)
1190
 
            self.open_button = ob = ToolButton(gtk.STOCK_OPEN)
 
1398
            self.open_button = ob = InformationToolButton(gtk.STOCK_OPEN)
1191
1399
            ob.set_label(_("Launch this subject"))
1192
 
            self.delete_button = del_ = ToolButton(gtk.STOCK_DELETE)
 
1400
            self.delete_button = del_ = InformationToolButton(gtk.STOCK_DELETE)
1193
1401
            del_.set_label(_("Delete this subject"))
1194
1402
            self.pin_button = pin = Toolbar.get_toolbutton(
1195
1403
                get_icon_path("hicolor/24x24/status/pin.png"),
1196
 
                _("Add Pin"))
 
1404
                _("Add Pin"),radio=False)
 
1405
            self.note_button = note = Toolbar.get_toolbutton(
 
1406
                get_icon_path("hicolor/24x24/status/note.png"),
 
1407
                _("Edit Note"),radio=False)
1197
1408
            sep = gtk.SeparatorToolItem()
1198
 
            for item in (del_, pin, sep, ob):
 
1409
            for item in (del_, note, pin, sep, ob):
1199
1410
                if item:
1200
1411
                    self.insert(item, 0)
1201
 
 
1202
 
    def __init__(self):
 
1412
                    
 
1413
    class _EditNoteWindow(gtk.Window):
 
1414
    
 
1415
        def __init__(self, parent, obj):  
 
1416
            gtk.Window.__init__(self)
 
1417
            self.obj = obj
 
1418
            self.set_title(_("Edit Note"))
 
1419
            self.set_transient_for(parent)
 
1420
            self.set_destroy_with_parent(True)
 
1421
            self.set_size_request(400, 400)
 
1422
            self.connect("destroy", self.on_save_note)
 
1423
            sw = gtk.ScrolledWindow()
 
1424
            sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
1425
            textview = gtk.TextView()
 
1426
            self.textbuffer = textview.get_buffer()
 
1427
            self.textbuffer.set_text("" if self.obj.annotation is None else self.obj.annotation) 
 
1428
            sw.add(textview)
 
1429
            self.add(sw)
 
1430
            self.show_all()
 
1431
            
 
1432
        def on_save_note(self, *arg):
 
1433
            self.obj.annotation = self.textbuffer.get_text(self.textbuffer.get_start_iter (),
 
1434
                                                           self.textbuffer.get_end_iter())
 
1435
    
 
1436
    def __init__(self, parent=None):
1203
1437
        super(gtk.Window, self).__init__()
 
1438
        if parent: self.set_transient_for(parent)
 
1439
        self.set_destroy_with_parent(True)
 
1440
        self.set_size_request(400, 400)
1204
1441
        self.connect("destroy", self.hide_on_delete)
1205
1442
        box1 = gtk.VBox()
1206
1443
        box2 = gtk.VBox()
1207
1444
        vbox = gtk.VBox()
1208
1445
        self.toolbar = self._InformationToolbar()
1209
1446
        self.infopane = InformationBox()
1210
 
        if TRACKER:
1211
 
            self.tag_cloud_frame = frame = gtk.Frame()
1212
 
            frame.set_label( _("Tags"))
1213
 
            self.tag_cloud = TagCloud()
1214
 
            frame.add(self.tag_cloud)
1215
 
        self.relatedpane = _RelatedPane()
 
1447
        self.relatedpane = _RelatedPane("Used With")
1216
1448
        scrolledwindow = gtk.ScrolledWindow()
1217
1449
        box2.set_border_width(5)
1218
1450
        box1.pack_start(self.toolbar, False, False)
1219
1451
        box2.pack_start(self.infopane, False, False, 4)
1220
 
        if TRACKER:
1221
 
            box2.pack_start(frame, False, False, 4)
1222
1452
        scrolledwindow.set_shadow_type(gtk.SHADOW_IN)
1223
1453
        scrolledwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
1224
1454
        scrolledwindow.add(self.relatedpane)
1225
1455
        vbox.pack_end(scrolledwindow, True, True)
 
1456
        scrolledwindow.set_size_request(50, 100)
1226
1457
        box2.pack_end(vbox, True, True, 10)
1227
 
        box1.add(box2)
1228
 
        self.add(box1)
 
1458
        box1.pack_start(box2, True, True)
 
1459
        area = self.get_content_area()
 
1460
        area.add(box1)
1229
1461
        def _launch(w):
1230
1462
            self.obj.launch()
1231
1463
        self.toolbar.open_button.connect("clicked", _launch)
1232
1464
        self.toolbar.delete_button.connect("clicked", self.do_delete_events_with_shared_uri)
1233
1465
        self.toolbar.pin_button.connect("clicked", self.do_toggle_bookmark)
1234
 
        if TRACKER:
1235
 
            self.tag_cloud.connect("add-tag", self.on_add_tag)
1236
 
            self.tag_cloud.connect("remove-tag", self.on_remove_tag)
 
1466
        self.toolbar.note_button.connect("clicked", self.do_edit_note)
1237
1467
        self.connect("size-allocate", self.size_allocate)
1238
1468
        # Remove the close button
1239
1469
        separator = gtk.SeparatorToolItem()
1248
1478
            self.infopane.display_widget.show()
1249
1479
 
1250
1480
    def do_toggle_bookmark(self, *args):
1251
 
        if bookmarker.is_bookmarked(self.obj.uri):
1252
 
            bookmarker.unbookmark(self.obj.uri)
 
1481
        uri = unicode(self.obj.uri)
 
1482
        if bookmarker.is_bookmarked(uri):
 
1483
            bookmarker.unbookmark(uri)
1253
1484
        else:
1254
 
            bookmarker.bookmark(self.obj.uri)
1255
 
 
1256
 
    def on_remove_tag(self, w, text):
1257
 
        if TRACKER:
1258
 
            TRACKER.remove_tag_from_uri(text, self.obj.uri)
1259
 
        self.set_tags(self.obj)
1260
 
 
1261
 
    def on_add_tag(self, w, text):
1262
 
        if TRACKER:
1263
 
            TRACKER.add_tag_to_uri(text, self.obj.uri)
1264
 
        self.set_tags(self.obj)
 
1485
            bookmarker.bookmark(uri)
 
1486
    
 
1487
    def do_edit_note(self, *args):
 
1488
        window = self._EditNoteWindow(self, self.obj)
1265
1489
 
1266
1490
    def do_delete_events_with_shared_uri(self, *args):
1267
1491
        CLIENT.find_event_ids_for_template(
1271
1495
 
1272
1496
    def set_content_object(self, obj):
1273
1497
        self.obj = obj
 
1498
        if not isinstance(self.obj, GioFile):
 
1499
            self.toolbar.note_button.set_sensitive(False)
1274
1500
        def _callback(events):
1275
1501
            self.relatedpane.set_model_from_list(events)
1276
1502
        get_related_events_for_uri(obj.uri, _callback)
1277
1503
        self.infopane.set_content_object(obj)
1278
 
        if TRACKER:
1279
 
            self.set_tags(obj)
 
1504
        self.set_title(_("More Information"))
1280
1505
        self.show()
1281
1506
        self.emit("content-object-set")
1282
1507
 
1283
 
    def set_tags(self, obj):
1284
 
        tag_dict = {}
1285
 
        tags = TRACKER.get_tag_dict_for_uri(obj.uri)
1286
 
        self.tag_cloud.set_tags(tags)
1287
 
 
1288
1508
    def hide_on_delete(self, widget, *args):
1289
1509
        super(InformationContainer, self).hide_on_delete(widget)
1290
1510
        return True
 
1511
        
 
1512
class MolteplicityInformationContainer(gtk.Dialog):
 
1513
    """
 
1514
    . . . . .
 
1515
    .Origin .
 
1516
    . Info  .
 
1517
    . . . . .
 
1518
    . . . . .
 
1519
    .       .
 
1520
    .       . <--- Similar items (same basename)
 
1521
    .       .
 
1522
    . . . . .
 
1523
 
 
1524
    A pane which holds the information pane and grouped items pane
 
1525
    """
 
1526
    def __init__(self, parent=None):
 
1527
        super(gtk.Window, self).__init__()
 
1528
        if parent: self.set_transient_for(parent)
 
1529
        self.set_destroy_with_parent(True)
 
1530
        self.set_size_request(400, 400)
 
1531
        self.connect("destroy", self.hide_on_delete)
 
1532
        box2 = gtk.VBox()
 
1533
        vbox = gtk.VBox()
 
1534
        self.infopane = InformationBox()
 
1535
        self.infopane.is_molteplicity = True
 
1536
        self.relatedpane = _RelatedPane("Grouped items")
 
1537
        scrolledwindow = gtk.ScrolledWindow()
 
1538
        box2.set_border_width(5)
 
1539
        box2.pack_start(self.infopane, False, False, 4)
 
1540
        scrolledwindow.set_shadow_type(gtk.SHADOW_IN)
 
1541
        scrolledwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
1542
        scrolledwindow.add(self.relatedpane)
 
1543
        vbox.pack_end(scrolledwindow, True, True)
 
1544
        scrolledwindow.set_size_request(50, 100)
 
1545
        box2.pack_end(vbox, True, True, 10)
 
1546
        area = self.get_content_area()
 
1547
        area.add(box2)
 
1548
        self.connect("size-allocate", self.size_allocate)
 
1549
 
 
1550
    def size_allocate(self, widget, allocation):
 
1551
        if allocation.height < 400:
 
1552
            self.infopane.display_widget.hide()
 
1553
        else:
 
1554
            self.infopane.display_widget.show()
 
1555
 
 
1556
    def set_item_list(self, list_):
 
1557
        self.infopane.set_content_object(list_[0].content_object)
 
1558
        self.relatedpane.set_model_from_list(list_)
 
1559
        self.set_title(_("More Information"))
 
1560
        self.show()
 
1561
 
 
1562
    def hide_on_delete(self, widget, *args):
 
1563
        super(MolteplicityInformationContainer, self).hide_on_delete(widget)
 
1564
        return True
1291
1565
 
1292
1566
 
1293
1567
class PreferencesDialog(gtk.Dialog):
1342
1616
            self.manager = manager
1343
1617
            self.set_model(store)
1344
1618
 
1345
 
    def __init__(self):
 
1619
    def __init__(self, parent=None):
1346
1620
        super(PreferencesDialog, self).__init__()
 
1621
        if parent: self.set_transient_for(parent)
 
1622
        self.set_destroy_with_parent(True)
1347
1623
        self.set_has_separator(False)
1348
1624
        self.set_title(_("Preferences"))
1349
1625
        self.set_size_request(400, 500)
1351
1627
        self.notebook = notebook = gtk.Notebook()
1352
1628
        area.pack_start(notebook)
1353
1629
        notebook.set_border_width(10)
 
1630
        #Plugin page
1354
1631
        plugbox = gtk.VBox()
1355
1632
        plugbox.set_border_width(10)
1356
1633
        self.plug_tree = self._PluginTreeView()
1363
1640
        scroll_win.add(self.plug_tree)
1364
1641
        plugbox.add(scroll_win)
1365
1642
        notebook.append_page(plugbox, gtk.Label( _("Plugins")))
 
1643
        #Configuration page
 
1644
        vbox = gtk.VBox()
 
1645
        vbox.set_border_width(5)
 
1646
        hbox_tray = gtk.HBox()
 
1647
        label = gtk.Label(_("Show icon in system tray"))
 
1648
        self.check_button = gtk.CheckButton()
 
1649
        self.check_button.set_active(settings.get("tray_icon", False))
 
1650
        self.check_button.connect("toggled", self.on_check_toggled)
 
1651
        hbox_tray.pack_start(self.check_button,False,False)
 
1652
        hbox_tray.pack_start(label, False,False)
 
1653
        vbox.pack_start(hbox_tray,False,False)
 
1654
        notebook.append_page(vbox, gtk.Label( _("Configuration")))
 
1655
 
1366
1656
        self.connect("delete-event", lambda *args: (True, self.hide())[0])
1367
1657
        close_button = gtk.Button(stock=gtk.STOCK_CLOSE)
1368
1658
        self.add_action_widget(close_button, gtk.RESPONSE_DELETE_EVENT)
1369
1659
        close_button.connect("clicked", lambda *args: (True, self.hide())[0])
1370
1660
 
 
1661
    def on_check_toggled(self, button, *args):
 
1662
        settings.set("tray_icon", button.get_active())
 
1663
 
 
1664
 
 
1665
#Code adapted from Emesene2 source. Get it at: https://github.com/emesene/emesene
 
1666
#Thanks Emesene's team
 
1667
class NiceBar(gtk.EventBox):
 
1668
    '''A class used to display messages in a non-intrusive bar'''
 
1669
    
 
1670
    NORMALBACKGROUND = gtk.gdk.Color(65025,65025,46155)
 
1671
    NORMALFOREGROUND = "black"
 
1672
    ALERTBACKGROUND = gtk.gdk.Color(57600,23040,19712)
 
1673
    ALERTFOREGROUND = NORMALFOREGROUND
 
1674
 
 
1675
    def __init__(self, default_background=ALERTBACKGROUND, default_foreground=None):
 
1676
 
 
1677
        gtk.EventBox.__init__(self)
 
1678
 
 
1679
        self.message_label = gtk.Label()
 
1680
        self.message_label.set_line_wrap(True)
 
1681
        self.message_label.set_ellipsize(pango.ELLIPSIZE_END)
 
1682
        self.message_image = gtk.Image()
 
1683
        self.message_hbox = gtk.HBox()
 
1684
        self.message_hbox.set_border_width(2)
 
1685
 
 
1686
        if default_background is None:
 
1687
            default_background = self.NORMALBACKGROUND
 
1688
        if default_foreground is None:
 
1689
            default_foreground = self.NORMALFOREGROUND
 
1690
 
 
1691
        self.default_back = default_background
 
1692
        self.default_fore = default_foreground
 
1693
        self.empty_queue()
 
1694
        self.markup = '<span foreground="%s" font-weight="550" >%s</span>'
 
1695
        self.modify_bg(gtk.STATE_NORMAL, default_background)
 
1696
 
 
1697
        self.message_hbox.pack_end(self.message_label)
 
1698
        self.add(self.message_hbox)
 
1699
 
 
1700
    def new_message(self, message, stock=None, background=None, \
 
1701
                                                  foreground=None):
 
1702
        ''' Adds the actual message to the queue and show a new one '''
 
1703
 
 
1704
        if self.actual_message != '':
 
1705
            self.messages_queue.append([self.actual_message, \
 
1706
                    self.actual_image, self.actual_background,
 
1707
                    self.actual_foreground])
 
1708
 
 
1709
        self.display_message(message, stock, background, foreground)
 
1710
 
 
1711
    def remove_message(self):
 
1712
        ''' Removes the actual message and display the next if any '''
 
1713
        try:
 
1714
            message, stock, back, fore = self.messages_queue.pop()
 
1715
            self.display_message(message, stock, back, fore)
 
1716
        except IndexError:
 
1717
            self.hide()
 
1718
 
 
1719
    def display_message(self, message, stock=None, background=None, \
 
1720
                        foreground=None):
 
1721
        '''
 
1722
            Displays a message without modifying the queue
 
1723
            A background, text color and a stock image are optional
 
1724
        '''
 
1725
 
 
1726
        self.actual_message = message
 
1727
        self.actual_image = stock
 
1728
        self.actual_background = background or self.default_back
 
1729
        self.actual_foreground = foreground or self.default_fore
 
1730
 
 
1731
        if self.message_image.get_parent() is not None:
 
1732
            self.message_hbox.remove(self.message_image)
 
1733
 
 
1734
        if stock is not None:
 
1735
            self.message_image = gtk.image_new_from_stock(stock, \
 
1736
                                             gtk.ICON_SIZE_LARGE_TOOLBAR)
 
1737
            self.message_hbox.pack_start(self.message_image, False, False)
 
1738
 
 
1739
        self.modify_bg(gtk.STATE_NORMAL, self.actual_background)
 
1740
        self.message_label.set_markup(self.markup % (self.actual_foreground,
 
1741
                                                       self.actual_message))
 
1742
        self.show_all()
 
1743
 
 
1744
    def empty_queue(self):
 
1745
        ''' Delets all messages and hide the bar '''
 
1746
 
 
1747
        self.messages_queue = list()
 
1748
        self.actual_message = ''
 
1749
        self.actual_image = None
 
1750
        self.actual_background = self.default_back
 
1751
        self.actual_foreground = self.default_fore
 
1752
        self.hide()
1371
1753
 
1372
1754
###
1373
1755
if gst is not None:
1374
1756
    VideoPreviewTooltip = VideoPreviewTooltip()
 
1757
    AudioPreviewTooltip = AudioPreviewTooltip()
1375
1758
else:
1376
1759
    VideoPreviewTooltip = None
 
1760
    AudioPreviewTooltip = None
1377
1761
StaticPreviewTooltip = StaticPreviewTooltip()
1378
1762
ContextMenu = ContextMenu()
 
1763
ContextMenuMolteplicity = ContextMenuMolteplicity()
1379
1764
SearchBox = SearchBox()