Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/jarabe/journal/listview.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/jarabe/journal/listview.py')
-rw-r--r--src/jarabe/journal/listview.py359
1 files changed, 274 insertions, 85 deletions
diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
index 3366def..251388d 100644
--- a/src/jarabe/journal/listview.py
+++ b/src/jarabe/journal/listview.py
@@ -20,99 +20,106 @@ import time
import gobject
import gtk
+import hippo
import gconf
import pango
from sugar.graphics import style
-from sugar.graphics.icon import CellRendererIcon
+from sugar.graphics.icon import CanvasIcon, Icon, CellRendererIcon
from sugar.graphics.xocolor import XoColor
from sugar import util
-from jarabe.journal.source import Source
from jarabe.journal.listmodel import ListModel
from jarabe.journal.palettes import ObjectPalette, BuddyPalette
from jarabe.journal import model
from jarabe.journal import misc
-class ListView(gtk.TreeView):
- __gtype_name__ = 'JournalListView'
+UPDATE_INTERVAL = 300
- __gsignals__ = {
- 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([object])),
- 'entry-activated': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([str])),
- }
+MESSAGE_EMPTY_JOURNAL = 0
+MESSAGE_NO_MATCH = 1
+
+class TreeView(gtk.TreeView):
+ __gtype_name__ = 'JournalTreeView'
def __init__(self):
gtk.TreeView.__init__(self)
- self.props.fixed_height_mode = True
- self.cell_title = None
- self.cell_icon = None
- self._title_column = None
- self.date_column = None
- self._add_columns()
+ def do_size_request(self, requisition):
+ # HACK: We tell the model that the view is just resizing so it can avoid
+ # hitting both D-Bus and disk.
+ tree_model = self.get_model()
+ if tree_model is not None:
+ tree_model.view_is_resizing = True
+ try:
+ gtk.TreeView.do_size_request(self, requisition)
+ finally:
+ if tree_model is not None:
+ tree_model.view_is_resizing = False
- self.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
- [('text/uri-list', 0, 0), ('journal-object-id', 0, 0)],
- gtk.gdk.ACTION_COPY)
+class BaseListView(gtk.Bin):
+ __gtype_name__ = 'JournalBaseListView'
- self.cell_title.props.editable = True
- self.cell_title.connect('edited', self.__cell_title_edited_cb)
+ __gsignals__ = {
+ 'clear-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([]))
+ }
- self.cell_icon.connect('clicked', self.__icon_clicked_cb)
- self.cell_icon.connect('detail-clicked', self.__detail_clicked_cb)
+ def __init__(self):
+ self._query = {}
+ self._model = None
+ self._progress_bar = None
+ self._last_progress_bar_pulse = None
- cell_detail = CellRendererDetail(self)
- cell_detail.connect('clicked', self.__detail_cell_clicked_cb)
+ gobject.GObject.__init__(self)
- column = gtk.TreeViewColumn('')
- column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
- column.props.fixed_width = cell_detail.props.width
- column.pack_start(cell_detail)
- self.append_column(column)
+ self.connect('destroy', self.__destroy_cb)
- self.connect('notify::hover-selection',
- self.__notify_hover_selection_cb)
- self.connect('button-release-event', self.__button_release_event_cb)
+ self._scrolled_window = gtk.ScrolledWindow()
+ self._scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.add(self._scrolled_window)
+ self._scrolled_window.show()
- def __button_release_event_cb(self, tree_view, event):
- if not tree_view.props.hover_selection:
- return False
+ self.tree_view = TreeView()
+ self.tree_view.props.fixed_height_mode = True
+ self.tree_view.modify_base(gtk.STATE_NORMAL,
+ style.COLOR_WHITE.get_gdk_color())
+ self._scrolled_window.add(self.tree_view)
+ self.tree_view.show()
- if event.window != tree_view.get_bin_window():
- return False
+ self.cell_title = None
+ self.cell_icon = None
+ self._title_column = None
+ self.date_column = None
+ self._add_columns()
- pos = tree_view.get_path_at_pos(event.x, event.y)
- if pos is None:
- return False
+ self.tree_view.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
+ [('text/uri-list', 0, 0),
+ ('journal-object-id', 0, 0)],
+ gtk.gdk.ACTION_COPY)
- path, column_, x_, y_ = pos
- uid = tree_view.get_model()[path][Source.FIELD_UID]
- self.emit('entry-activated', uid)
+ # Auto-update stuff
+ self._fully_obscured = True
+ self._dirty = False
+ self._refresh_idle_handler = None
+ self._update_dates_timer = None
- return False
+ model.created.connect(self.__model_created_cb)
+ model.updated.connect(self.__model_updated_cb)
+ model.deleted.connect(self.__model_deleted_cb)
- def __notify_hover_selection_cb(self, widget, pspec):
- self.cell_icon.props.show_palette = not self.props.hover_selection
+ def __model_created_cb(self, sender, **kwargs):
+ self._set_dirty()
- def do_size_request(self, requisition):
- # HACK: We tell the model that the view is just resizing so it can avoid
- # hitting both D-Bus and disk.
- tree_model = self.get_model()
- if tree_model is not None:
- tree_model.view_is_resizing = True
- try:
- gtk.TreeView.do_size_request(self, requisition)
- finally:
- if tree_model is not None:
- tree_model.view_is_resizing = False
+ def __model_updated_cb(self, sender, **kwargs):
+ self._set_dirty()
+
+ def __model_deleted_cb(self, sender, **kwargs):
+ self._set_dirty()
def _add_columns(self):
- cell_favorite = CellRendererFavorite(self)
+ cell_favorite = CellRendererFavorite(self.tree_view)
cell_favorite.connect('clicked', self.__favorite_clicked_cb)
column = gtk.TreeViewColumn('')
@@ -120,9 +127,9 @@ class ListView(gtk.TreeView):
column.props.fixed_width = cell_favorite.props.width
column.pack_start(cell_favorite)
column.set_cell_data_func(cell_favorite, self.__favorite_set_data_cb)
- self.append_column(column)
+ self.tree_view.append_column(column)
- self.cell_icon = CellRendererActivityIcon(self)
+ self.cell_icon = CellRendererActivityIcon(self.tree_view)
column = gtk.TreeViewColumn('')
column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
@@ -130,8 +137,8 @@ class ListView(gtk.TreeView):
column.pack_start(self.cell_icon)
column.add_attribute(self.cell_icon, 'file-name', ListModel.COLUMN_ICON)
column.add_attribute(self.cell_icon, 'xo-color',
- ListModel.COLUMN_ICON_COLOR)
- self.append_column(column)
+ ListModel.COLUMN_ICON_COLOR)
+ self.tree_view.append_column(column)
self.cell_title = gtk.CellRendererText()
self.cell_title.props.ellipsize = pango.ELLIPSIZE_MIDDLE
@@ -145,15 +152,15 @@ class ListView(gtk.TreeView):
self._title_column.add_attribute(self.cell_title, 'markup',
ListModel.COLUMN_TITLE)
self._title_column.connect('clicked', self.__header_clicked_cb)
- self.append_column(self._title_column)
+ self.tree_view.append_column(self._title_column)
buddies_column = gtk.TreeViewColumn('')
buddies_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
- self.append_column(buddies_column)
+ self.tree_view.append_column(buddies_column)
for column_index in [ListModel.COLUMN_BUDDY_1, ListModel.COLUMN_BUDDY_2,
ListModel.COLUMN_BUDDY_3]:
- cell_icon = CellRendererBuddy(self,
+ cell_icon = CellRendererBuddy(self.tree_view,
column_index=column_index)
buddies_column.pack_start(cell_icon)
buddies_column.props.fixed_width += cell_icon.props.width
@@ -179,7 +186,7 @@ class ListView(gtk.TreeView):
self.date_column.pack_start(cell_text)
self.date_column.add_attribute(cell_text, 'text', ListModel.COLUMN_DATE)
self.date_column.connect('clicked', self.__header_clicked_cb)
- self.append_column(self.date_column)
+ self.tree_view.append_column(self.date_column)
def __header_clicked_cb(self, column_clicked):
if column_clicked == self._title_column:
@@ -199,7 +206,7 @@ class ListView(gtk.TreeView):
else:
self._query['order_by'] = ['+timestamp']
- self._refresh()
+ self.refresh()
# Need to update the column indicators after the model has been reset
if self._query['order_by'] == ['-timestamp']:
@@ -230,8 +237,19 @@ class ListView(gtk.TreeView):
width, height_ = layout.get_size()
return pango.PIXELS(width)
+ def do_size_allocate(self, allocation):
+ self.allocation = allocation
+ self.child.size_allocate(allocation)
+
+ def do_size_request(self, requisition):
+ requisition.width, requisition.height = self.child.size_request()
+
+ def __destroy_cb(self, widget):
+ if self._model is not None:
+ self._model.stop()
+
def __favorite_set_data_cb(self, column, cell, tree_model, tree_iter):
- favorite = self.get_model()[tree_iter][ListModel.COLUMN_FAVORITE]
+ favorite = self._model[tree_iter][ListModel.COLUMN_FAVORITE]
if favorite:
client = gconf.client_get_default()
color = XoColor(client.get_string('/desktop/sugar/user/color'))
@@ -241,7 +259,7 @@ class ListView(gtk.TreeView):
cell.props.fill_color = style.COLOR_WHITE.get_svg()
def __favorite_clicked_cb(self, cell, path):
- row = self.get_model()[path]
+ row = self._model[path]
metadata = model.get(row[ListModel.COLUMN_UID])
if metadata['keep'] == '1':
metadata['keep'] = '0'
@@ -249,41 +267,212 @@ class ListView(gtk.TreeView):
metadata['keep'] = '1'
model.write(metadata, update_mtime=False)
- def update_dates(self):
- if not self.flags() & gtk.REALIZED:
+ def update_with_query(self, query_dict):
+ logging.debug('ListView.update_with_query')
+ self._query = query_dict
+
+ if 'order_by' not in self._query:
+ self._query['order_by'] = ['+timestamp']
+
+ self.refresh()
+
+ def refresh(self):
+ logging.debug('ListView.refresh query %r', self._query)
+ self._stop_progress_bar()
+ self._start_progress_bar()
+
+ if self._model is not None:
+ self._model.stop()
+
+ self._model = ListModel(self._query)
+ self._model.connect('ready', self.__model_ready_cb)
+ self._model.connect('progress', self.__model_progress_cb)
+ self._model.setup()
+
+ def __model_ready_cb(self, tree_model):
+ self._stop_progress_bar()
+
+ # Cannot set it up earlier because will try to access the model and it
+ # needs to be ready.
+ self.tree_view.set_model(self._model)
+
+ if len(tree_model) == 0:
+ if self._is_query_empty():
+ self._show_message(MESSAGE_EMPTY_JOURNAL)
+ else:
+ self._show_message(MESSAGE_NO_MATCH)
+ else:
+ self._clear_message()
+
+ def _is_query_empty(self):
+ # FIXME: This is a hack, we shouldn't have to update this every time
+ # a new search term is added.
+ if self._query.get('query', '') or self._query.get('mime_type', '') or \
+ self._query.get('keep', '') or self._query.get('mtime', '') or \
+ self._query.get('activity', ''):
+ return False
+ else:
+ return True
+
+ def __model_progress_cb(self, tree_model):
+ if time.time() - self._last_progress_bar_pulse > 0.05:
+ if self._progress_bar is not None:
+ self._progress_bar.pulse()
+ self._last_progress_bar_pulse = time.time()
+
+ def _start_progress_bar(self):
+ alignment = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.5)
+ self.remove(self.child)
+ self.add(alignment)
+ alignment.show()
+
+ self._progress_bar = gtk.ProgressBar()
+ self._progress_bar.props.pulse_step = 0.01
+ self._last_progress_bar_pulse = time.time()
+ alignment.add(self._progress_bar)
+ self._progress_bar.show()
+
+ def _stop_progress_bar(self):
+ if self.child != self._progress_bar:
return
+ self.remove(self.child)
+ self.add(self._scrolled_window)
+
+ def _show_message(self, message):
+ canvas = hippo.Canvas()
+ self.remove(self.child)
+ self.add(canvas)
+ canvas.show()
+
+ box = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
+ background_color=style.COLOR_WHITE.get_int(),
+ yalign=hippo.ALIGNMENT_CENTER,
+ spacing=style.DEFAULT_SPACING,
+ padding_bottom=style.GRID_CELL_SIZE)
+ canvas.set_root(box)
+
+ icon = CanvasIcon(size=style.LARGE_ICON_SIZE,
+ icon_name='activity-journal',
+ stroke_color = style.COLOR_BUTTON_GREY.get_svg(),
+ fill_color = style.COLOR_TRANSPARENT.get_svg())
+ box.append(icon)
+
+ if message == MESSAGE_EMPTY_JOURNAL:
+ text = _('Your Journal is empty')
+ elif message == MESSAGE_NO_MATCH:
+ text = _('No matching entries')
+ else:
+ raise ValueError('Invalid message')
+
+ text = hippo.CanvasText(text=text,
+ xalign=hippo.ALIGNMENT_CENTER,
+ font_desc=style.FONT_BOLD.get_pango_desc(),
+ color = style.COLOR_BUTTON_GREY.get_int())
+ box.append(text)
+
+ if message == MESSAGE_NO_MATCH:
+ button = gtk.Button(label=_('Clear search'))
+ button.connect('clicked', self.__clear_button_clicked_cb)
+ button.props.image = Icon(icon_name='dialog-cancel',
+ icon_size=gtk.ICON_SIZE_BUTTON)
+ canvas_button = hippo.CanvasWidget(widget=button,
+ xalign=hippo.ALIGNMENT_CENTER)
+ box.append(canvas_button)
+
+ def __clear_button_clicked_cb(self, button):
+ self.emit('clear-clicked')
+
+ def _clear_message(self):
+ self.remove(self.child)
+ self.add(self._scrolled_window)
+ self._scrolled_window.show()
+ def update_dates(self):
logging.debug('ListView.update_dates')
- visible_range = self.get_visible_range()
+ visible_range = self.tree_view.get_visible_range()
if visible_range is None:
return
-
path, end_path = visible_range
while True:
- x, y, width, height = self.get_cell_area(path, self.date_column)
- x, y = self.convert_tree_to_widget_coords(x, y)
- self.queue_draw_area(x, y, width, height)
+ x, y, width, height = self.tree_view.get_cell_area(path,
+ self.date_column)
+ x, y = self.tree_view.convert_tree_to_widget_coords(x, y)
+ self.tree_view.queue_draw_area(x, y, width, height)
if path == end_path:
break
else:
- next_iter = self.get_model().iter_next(
- self.get_model().get_iter(path))
- path = self.get_model().get_path(next_iter)
+ next_iter = self._model.iter_next(self._model.get_iter(path))
+ path = self._model.get_path(next_iter)
+
+ def _set_dirty(self):
+ if self._fully_obscured:
+ self._dirty = True
+ else:
+ self.refresh()
+
+ def set_is_visible(self, visible):
+ logging.debug('canvas_visibility_notify_event_cb %r', visible)
+ if visible:
+ self._fully_obscured = False
+ if self._dirty:
+ self.refresh()
+ if self._update_dates_timer is None:
+ logging.debug('Adding date updating timer')
+ self._update_dates_timer = \
+ gobject.timeout_add_seconds(UPDATE_INTERVAL,
+ self.__update_dates_timer_cb)
+ else:
+ self._fully_obscured = True
+ if self._update_dates_timer is not None:
+ logging.debug('Remove date updating timer')
+ gobject.source_remove(self._update_dates_timer)
+ self._update_dates_timer = None
+
+ def __update_dates_timer_cb(self):
+ self.update_dates()
+ return True
+
+class ListView(BaseListView):
+ __gtype_name__ = 'JournalListView'
+
+ __gsignals__ = {
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([object]))
+ }
+
+ def __init__(self):
+ BaseListView.__init__(self)
+
+ self.cell_title.props.editable = True
+ self.cell_title.connect('edited', self.__cell_title_edited_cb)
+
+ self.cell_icon.connect('clicked', self.__icon_clicked_cb)
+ self.cell_icon.connect('detail-clicked', self.__detail_clicked_cb)
+
+ cell_detail = CellRendererDetail(self.tree_view)
+ cell_detail.connect('clicked', self.__detail_cell_clicked_cb)
+
+ column = gtk.TreeViewColumn('')
+ column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ column.props.fixed_width = cell_detail.props.width
+ column.pack_start(cell_detail)
+ self.tree_view.append_column(column)
def __detail_cell_clicked_cb(self, cell, path):
- row = self.get_model()[path]
+ row = self.tree_view.get_model()[path]
self.emit('detail-clicked', row[ListModel.COLUMN_UID])
def __detail_clicked_cb(self, cell, uid):
self.emit('detail-clicked', uid)
def __icon_clicked_cb(self, cell, path):
- row = self.get_model()[path]
+ row = self.tree_view.get_model()[path]
metadata = model.get(row[ListModel.COLUMN_UID])
misc.resume(metadata)
def __cell_title_edited_cb(self, cell, path, new_text):
- row = self.get_model()[path]
+ row = self._model[path]
metadata = model.get(row[ListModel.COLUMN_UID])
metadata['title'] = new_text
model.write(metadata, update_mtime=False)