diff options
author | Tomeu Vizoso <tomeu@sugarlabs.org> | 2009-06-27 16:14:01 (GMT) |
---|---|---|
committer | Tomeu Vizoso <tomeu@sugarlabs.org> | 2009-06-27 16:14:01 (GMT) |
commit | 7fa188ada12b38439d0b609dc989c5576788bb69 (patch) | |
tree | 01fb7d72d7dc393892db85dcaaa21a23e8f4dcee /src/jarabe/journal/listview.py | |
parent | 014a004eb2a708bd368f494e98a6c3705495cf76 (diff) |
Refactor journal to use a gtk.TreeView
Diffstat (limited to 'src/jarabe/journal/listview.py')
-rw-r--r-- | src/jarabe/journal/listview.py | 697 |
1 files changed, 340 insertions, 357 deletions
diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py index 4578853..0d4547e 100644 --- a/src/jarabe/journal/listview.py +++ b/src/jarabe/journal/listview.py @@ -1,4 +1,4 @@ -# Copyright (C) 2007, One Laptop Per Child +# Copyright (C) 2009, Tomeu Vizoso # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -20,28 +20,48 @@ import sys from gettext import gettext as _ import time -import hippo import gobject import gtk -import dbus +import hippo +import gconf +import pango from sugar.graphics import style -from sugar.graphics.icon import CanvasIcon, Icon +from sugar.graphics.icon import CanvasIcon, Icon, CellRendererIcon +from sugar.graphics.xocolor import XoColor +from sugar import util from jarabe.journal.collapsedentry import CollapsedEntry +from jarabe.journal.listmodel import ListModel +from jarabe.journal.palettes import ObjectPalette, BuddyPalette from jarabe.journal import model - -DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore' -DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore' -DS_DBUS_PATH = '/org/laptop/sugar/DataStore' +from jarabe.journal import misc UPDATE_INTERVAL = 300 -EMPTY_JOURNAL = _("Your Journal is empty") -NO_MATCH = _("No matching entries ") +MESSAGE_EMPTY_JOURNAL = 0 +MESSAGE_NO_MATCH = 1 -class BaseListView(gtk.HBox): - __gtype_name__ = 'BaseListView' +class TreeView(gtk.TreeView): + __gtype_name__ = 'JournalTreeView' + + def __init__(self): + gtk.TreeView.__init__(self) + + 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. + model = self.get_model() + if model is not None: + model.view_is_resizing = True + try: + gtk.TreeView.do_size_request(self, requisition) + finally: + if model is not None: + model.view_is_resizing = False + +class BaseListView(gtk.Bin): + __gtype_name__ = 'JournalBaseListView' __gsignals__ = { 'clear-clicked': (gobject.SIGNAL_RUN_FIRST, @@ -51,57 +71,34 @@ class BaseListView(gtk.HBox): def __init__(self): self._query = {} - self._result_set = None - self._entries = [] - self._page_size = 0 - self._reflow_sid = 0 - self._do_scroll_hid = None + self._model = None self._progress_bar = None self._last_progress_bar_pulse = None - gtk.HBox.__init__(self) - self.set_flags(gtk.HAS_FOCUS|gtk.CAN_FOCUS) - self.connect('key-press-event', self._key_press_event_cb) - - self._box = hippo.CanvasBox( - orientation=hippo.ORIENTATION_VERTICAL, - background_color=style.COLOR_WHITE.get_int()) + gobject.GObject.__init__(self) - self._canvas = hippo.Canvas() - self._canvas.set_root(self._box) + self.connect('destroy', self.__destroy_cb) - self.pack_start(self._canvas) - self._canvas.show() + 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() - self._vadjustment = gtk.Adjustment(value=0, lower=0, upper=0, - step_incr=1, page_incr=0, - page_size=0) - self._vadjustment.connect('value-changed', - self._vadjustment_value_changed_cb) - self._vadjustment.connect('changed', self._vadjustment_changed_cb) + 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() - self._vscrollbar = gtk.VScrollbar(self._vadjustment) - self.pack_end(self._vscrollbar, expand=False, fill=False) - self._vscrollbar.show() - - self.connect('scroll-event', self._scroll_event_cb) - self.connect('destroy', self.__destroy_cb) + self.cell_title = None + self.cell_icon = None + self._add_columns() - # DND stuff - self._temp_file_path = None - self._pressed_button = None - self._press_start_x = None - self._press_start_y = None - self._last_clicked_entry = None - self._canvas.drag_source_set(0, [], 0) - self._canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK | - gtk.gdk.POINTER_MOTION_HINT_MASK) - self._canvas.connect_after("motion_notify_event", - self._canvas_motion_notify_event_cb) - self._canvas.connect("button_press_event", - self._canvas_button_press_event_cb) - self._canvas.connect("drag_end", self._drag_end_cb) - self._canvas.connect("drag_data_get", self._drag_data_get_cb) + 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) # Auto-update stuff self._fully_obscured = True @@ -109,123 +106,160 @@ class BaseListView(gtk.HBox): self._refresh_idle_handler = None self._update_dates_timer = None - bus = dbus.SessionBus() - datastore = dbus.Interface( - bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH), DS_DBUS_INTERFACE) - self._datastore_created_handler = \ - datastore.connect_to_signal('Created', - self.__datastore_created_cb) - self._datastore_updated_handler = \ - datastore.connect_to_signal('Updated', - self.__datastore_updated_cb) - - self._datastore_deleted_handler = \ - datastore.connect_to_signal('Deleted', - self.__datastore_deleted_cb) + model.created.connect(self.__model_created_cb) + model.updated.connect(self.__model_updated_cb) + model.deleted.connect(self.__model_deleted_cb) - def __destroy_cb(self, widget): - self._datastore_created_handler.remove() - self._datastore_updated_handler.remove() - self._datastore_deleted_handler.remove() - - def _vadjustment_changed_cb(self, vadjustment): - if vadjustment.props.upper > self._page_size: - self._vscrollbar.show() - else: - self._vscrollbar.hide() - - def _vadjustment_value_changed_cb(self, vadjustment): - if self._do_scroll_hid is None: - self._do_scroll_hid = gobject.idle_add(self._do_scroll) + def __model_created_cb(self, sender, **kwargs): + self._set_dirty() - def _do_scroll(self): - current_position = int(self._vadjustment.props.value) + def __model_updated_cb(self, sender, **kwargs): + self._set_dirty() - self._result_set.seek(current_position) - metadata_list = self._result_set.read(self._page_size) + def __model_deleted_cb(self, sender, **kwargs): + self._set_dirty() - if self._result_set.length != self._vadjustment.props.upper: - self._vadjustment.props.upper = self._result_set.length - self._vadjustment.changed() + def _add_columns(self): + cell_favorite = CellRendererFavorite(self.tree_view) + cell_favorite.connect('activate', self.__favorite_activate_cb) + + column = gtk.TreeViewColumn('') + column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED + 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.tree_view.append_column(column) + + self.cell_icon = CellRendererActivityIcon(self.tree_view) + self.cell_icon.connect('activate', self.__icon_activate_cb) + + column = gtk.TreeViewColumn('') + column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED + column.props.fixed_width = self.cell_icon.props.width + 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.tree_view.append_column(column) + + self.cell_title = gtk.CellRendererText() + self.cell_title.props.ellipsize = pango.ELLIPSIZE_MIDDLE + self.cell_title.props.ellipsize_set = True + + column = gtk.TreeViewColumn(_('Title')) + column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED + column.props.expand = True + column.pack_start(self.cell_title) + column.add_attribute(self.cell_title, 'markup', ListModel.COLUMN_TITLE) + self.tree_view.append_column(column) + + buddies_column = gtk.TreeViewColumn('') + buddies_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED + 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.tree_view, + column_index=column_index) + buddies_column.pack_start(cell_icon) + buddies_column.props.fixed_width += cell_icon.props.width + buddies_column.add_attribute(cell_icon, 'buddy', column_index) + + cell_text = gtk.CellRendererText() + cell_text.props.xalign = 1 + + # Measure the required width for a date in the form of "10 hours, 10 + # minutes ago" + timestamp = time.time() - 10 * 60 - 10 * 60 * 60 + date = util.timestamp_to_elapsed_string(timestamp) + date_width = self._get_width_for_string(date) + + self.date_column = gtk.TreeViewColumn(_('Date')) + self.date_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED + self.date_column.props.fixed_width = date_width + self.date_column.set_alignment(1) + self.date_column.props.resizable = True + self.date_column.pack_start(cell_text) + self.date_column.add_attribute(cell_text, 'text', ListModel.COLUMN_DATE) + self.tree_view.append_column(self.date_column) + + def _get_width_for_string(self, text): + # Add some extra margin + text = text + 'aaaaa' + + widget = gtk.Label('') + context = widget.get_pango_context() + layout = pango.Layout(context) + layout.set_text(text) + width, height = layout.get_size() + return pango.PIXELS(width) - self._refresh_view(metadata_list) - self._dirty = False + def do_size_allocate(self, allocation): + self.allocation = allocation + self.child.size_allocate(allocation) - self._do_scroll_hid = None - return False + def do_size_request(self, requisition): + requisition.width, requisition.height = self.child.size_request() - def _refresh_view(self, metadata_list): - logging.debug('ListView %r' % self) - # Indicate when the Journal is empty - if len(metadata_list) == 0: - if self._is_query_empty(): - self._show_message(EMPTY_JOURNAL) - else: - self._show_message(NO_MATCH) - return + def __destroy_cb(self, widget): + if self._model is not None: + self._model.destroy() + + def __favorite_set_data_cb(self, column, cell, tree_model, tree_iter): + favorite = self._model[tree_iter][ListModel.COLUMN_FAVORITE] + if favorite: + client = gconf.client_get_default() + color = XoColor(client.get_string('/desktop/sugar/user/color')) + cell.props.xo_color = color + else: + cell.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg() + cell.props.fill_color = style.COLOR_WHITE.get_svg() + + def __favorite_activate_cb(self, cell, path): + row = self._model[path] + metadata = model.get(row[ListModel.COLUMN_UID]) + if metadata['keep'] == 1: + metadata['keep'] = 0 + else: + metadata['keep'] = 1 + model.write(metadata, update_mtime=False) - # Refresh view and create the entries if they don't exist yet. - for i in range(0, self._page_size): - try: - if i < len(metadata_list): - if i >= len(self._entries): - entry = self.create_entry() - self._box.append(entry) - self._entries.append(entry) - entry.metadata = metadata_list[i] - else: - entry = self._entries[i] - entry.metadata = metadata_list[i] - entry.set_visible(True) - elif i < len(self._entries): - entry = self._entries[i] - entry.set_visible(False) - except Exception: - logging.error('Exception while displaying entry:\n' + \ - ''.join(traceback.format_exception(*sys.exc_info()))) - - def create_entry(self): - """ Create a descendant of BaseCollapsedEntry - """ - raise NotImplementedError + def __icon_activate_cb(self, cell, path): + row = self._model[path] + metadata = model.get(row[ListModel.COLUMN_UID]) + misc.resume(metadata) def update_with_query(self, query_dict): logging.debug('ListView.update_with_query') self._query = query_dict - if self._page_size > 0: - self.refresh() + self.refresh() def refresh(self): logging.debug('ListView.refresh query %r' % self._query) self._stop_progress_bar() self._start_progress_bar() - if self._result_set is not None: - self._result_set.stop() - self._result_set = model.find(self._query, self._page_size) - self._result_set.ready.connect(self.__result_set_ready_cb) - self._result_set.progress.connect(self.__result_set_progress_cb) - self._result_set.setup() + if self._model is not None: + self._model.stop() - def __result_set_ready_cb(self, **kwargs): - if kwargs['sender'] != self._result_set: - return + 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, model): self._stop_progress_bar() - self._vadjustment.props.upper = self._result_set.length - self._vadjustment.changed() + # 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) - self._vadjustment.props.value = min(self._vadjustment.props.value, - self._result_set.length - self._page_size) - if self._result_set.length == 0: + if len(model) == 0: if self._is_query_empty(): - self._show_message(EMPTY_JOURNAL) + self._show_message(MESSAGE_EMPTY_JOURNAL) else: - self._show_message(NO_MATCH) + self._show_message(MESSAGE_NO_MATCH) else: self._clear_message() - self._do_scroll() def _is_query_empty(self): # FIXME: This is a hack, we shouldn't have to update this every time @@ -237,18 +271,16 @@ class BaseListView(gtk.HBox): else: return True - def __result_set_progress_cb(self, **kwargs): + 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): - self.remove(self._canvas) - self.remove(self._vscrollbar) - alignment = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.5) - self.pack_start(alignment) + self.remove(self.child) + self.add(alignment) alignment.show() self._progress_bar = gtk.ProgressBar() @@ -258,102 +290,23 @@ class BaseListView(gtk.HBox): self._progress_bar.show() def _stop_progress_bar(self): - for widget in self.get_children(): - self.remove(widget) - self._progress_bar = None - - self.pack_start(self._canvas) - self.pack_end(self._vscrollbar, expand=False, fill=False) - - def _scroll_event_cb(self, hbox, event): - if event.direction == gtk.gdk.SCROLL_UP: - if self._vadjustment.props.value > self._vadjustment.props.lower: - self._vadjustment.props.value -= 1 - elif event.direction == gtk.gdk.SCROLL_DOWN: - max_value = self._result_set.length - self._page_size - if self._vadjustment.props.value < max_value: - self._vadjustment.props.value += 1 - - def do_focus(self, direction): - if not self.is_focus(): - self.grab_focus() - return True - return False - - def _key_press_event_cb(self, widget, event): - keyname = gtk.gdk.keyval_name(event.keyval) - - if keyname == 'Up': - if self._vadjustment.props.value > self._vadjustment.props.lower: - self._vadjustment.props.value -= 1 - elif keyname == 'Down': - max_value = self._result_set.length - self._page_size - if self._vadjustment.props.value < max_value: - self._vadjustment.props.value += 1 - elif keyname == 'Page_Up' or keyname == 'KP_Page_Up': - new_position = max(0, - self._vadjustment.props.value - self._page_size) - if new_position != self._vadjustment.props.value: - self._vadjustment.props.value = new_position - elif keyname == 'Page_Down' or keyname == 'KP_Page_Down': - new_position = min(self._result_set.length - self._page_size, - self._vadjustment.props.value + self._page_size) - if new_position != self._vadjustment.props.value: - self._vadjustment.props.value = new_position - elif keyname == 'Home' or keyname == 'KP_Home': - new_position = 0 - if new_position != self._vadjustment.props.value: - self._vadjustment.props.value = new_position - elif keyname == 'End' or keyname == 'KP_End': - new_position = max(0, self._result_set.length - self._page_size) - if new_position != self._vadjustment.props.value: - self._vadjustment.props.value = new_position - else: - return False - - return True - - def do_size_allocate(self, allocation): - gtk.HBox.do_size_allocate(self, allocation) - new_page_size = int(allocation.height / style.GRID_CELL_SIZE) - - logging.debug("do_size_allocate: %r" % new_page_size) - - if new_page_size != self._page_size: - self._page_size = new_page_size - self._queue_reflow() - - def _queue_reflow(self): - if not self._reflow_sid: - self._reflow_sid = gobject.idle_add(self._reflow_idle_cb) - - def _reflow_idle_cb(self): - self._box.clear() - self._entries = [] - - self._vadjustment.props.page_size = self._page_size - self._vadjustment.props.page_increment = self._page_size - self._vadjustment.changed() - - if self._result_set is not None: - self._result_set.stop() - self._result_set = model.find(self._query, self._page_size) - - max_value = max(0, self._result_set.length - self._page_size) - if self._vadjustment.props.value > max_value: - self._vadjustment.props.value = max_value - else: - self._do_scroll() - - self._reflow_sid = 0 + 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) - self._canvas.set_root(box) + canvas.set_root(box) icon = CanvasIcon(size=style.LARGE_ICON_SIZE, icon_name='activity-journal', @@ -361,141 +314,65 @@ class BaseListView(gtk.HBox): fill_color = style.COLOR_TRANSPARENT.get_svg()) box.append(icon) - text = hippo.CanvasText(text=message, + 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) - 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) + 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._canvas.set_root(self._box) - - # TODO: Dnd methods. This should be merged somehow inside hippo-canvas. - def _canvas_motion_notify_event_cb(self, widget, event): - if not self._pressed_button: - return True - - # if the mouse button is not pressed, no drag should occurr - if not event.state & gtk.gdk.BUTTON1_MASK: - self._pressed_button = None - return True - - logging.debug("motion_notify_event_cb") - - if event.is_hint: - x, y, state_ = event.window.get_pointer() - else: - x = event.x - y = event.y - - if widget.drag_check_threshold(int(self._press_start_x), - int(self._press_start_y), - int(x), - int(y)): - context_ = widget.drag_begin([('text/uri-list', 0, 0), - ('journal-object-id', 0, 0)], - gtk.gdk.ACTION_COPY, - 1, - event) - return True - - def _drag_end_cb(self, widget, drag_context): - logging.debug("drag_end_cb") - self._pressed_button = None - self._press_start_x = None - self._press_start_y = None - self._last_clicked_entry = None - - # Release and delete the temp file - self._temp_file_path = None - - def _drag_data_get_cb(self, widget, context, selection, target_type, - event_time): - logging.debug("drag_data_get_cb: requested target " + selection.target) - - metadata = self._last_clicked_entry.metadata - if selection.target == 'text/uri-list': - # Get hold of a reference so the temp file doesn't get deleted - self._temp_file_path = model.get_file(metadata['uid']) - selection.set(selection.target, 8, self._temp_file_path) - elif selection.target == 'journal-object-id': - selection.set(selection.target, 8, metadata['uid']) - - def _canvas_button_press_event_cb(self, widget, event): - logging.debug("button_press_event_cb") - - if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS: - self._last_clicked_entry = \ - self._get_entry_at_coords(event.x, event.y) - if self._last_clicked_entry: - self._pressed_button = event.button - self._press_start_x = event.x - self._press_start_y = event.y - - return False - - def _get_entry_at_coords(self, x, y): - for entry in self._box.get_children(): - entry_x, entry_y = entry.get_context().translate_to_widget(entry) - entry_width, entry_height = entry.get_allocation() - - if (x >= entry_x ) and (x <= entry_x + entry_width) and \ - (y >= entry_y ) and (y <= entry_y + entry_height): - return entry - return None + self.remove(self.child) + self.add(self._scrolled_window) + self._scrolled_window.show() def update_dates(self): logging.debug('ListView.update_dates') - for entry in self._entries: - if entry.get_visible(): - entry.update_date() - - def __datastore_created_cb(self, uid): - self._set_dirty() - - def __datastore_updated_cb(self, uid): - self._set_dirty() - - def __datastore_deleted_cb(self, uid): - self._set_dirty() + 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.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._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._schedule_refresh() - - def _schedule_refresh(self): - if self._refresh_idle_handler is None: - logging.debug('Add refresh idle callback') - self._refresh_idle_handler = \ - gobject.idle_add(self.__refresh_idle_cb) - - def __refresh_idle_cb(self): - self.refresh() - if self._refresh_idle_handler is not None: - logging.debug('Remove refresh idle callback') - gobject.source_remove(self._refresh_idle_handler) - self._refresh_idle_handler = None - return False + 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._schedule_refresh() + self.refresh() if self._update_dates_timer is None: logging.debug('Adding date updating timer') self._update_dates_timer = \ @@ -513,7 +390,7 @@ class BaseListView(gtk.HBox): return True class ListView(BaseListView): - __gtype_name__ = 'ListView' + __gtype_name__ = 'JournalListView' __gsignals__ = { 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, @@ -524,11 +401,117 @@ class ListView(BaseListView): def __init__(self): BaseListView.__init__(self) - def create_entry(self): - entry = CollapsedEntry() - entry.connect('detail-clicked', self.__entry_activated_cb) - return entry + self.cell_title.props.editable = True + self.cell_icon.connect('detail-clicked', self.__detail_clicked_cb) + + cell_detail = CellRendererDetail(self.tree_view) + cell_detail.connect('activate', self.__detail_activate_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_activate_cb(self, cell, 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) + +class CellRendererFavorite(CellRendererIcon): + __gtype_name__ = 'JournalCellRendererFavorite' + + def __init__(self, tree_view): + CellRendererIcon.__init__(self, tree_view) + + self.props.width = style.GRID_CELL_SIZE + self.props.height = style.GRID_CELL_SIZE + self.props.size = style.SMALL_ICON_SIZE + self.props.icon_name = 'emblem-favorite' + self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE + self.props.prelit_stroke_color = style.COLOR_BUTTON_GREY.get_svg() + self.props.prelit_fill_color = style.COLOR_BUTTON_GREY.get_svg() + +class CellRendererDetail(CellRendererIcon): + __gtype_name__ = 'JournalCellRendererDetail' + + def __init__(self, tree_view): + CellRendererIcon.__init__(self, tree_view) + + self.props.width = style.GRID_CELL_SIZE + self.props.height = style.GRID_CELL_SIZE + self.props.size = style.SMALL_ICON_SIZE + self.props.icon_name = 'go-right' + self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE + self.props.stroke_color = style.COLOR_TRANSPARENT.get_svg() + self.props.fill_color = style.COLOR_BUTTON_GREY.get_svg() + self.props.prelit_stroke_color = style.COLOR_TRANSPARENT.get_svg() + self.props.prelit_fill_color = style.COLOR_BLACK.get_svg() + +class CellRendererActivityIcon(CellRendererIcon): + __gtype_name__ = 'JournalCellRendererActivityIcon' + + __gsignals__ = { + 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str])), + } + + def __init__(self, tree_view): + CellRendererIcon.__init__(self, tree_view) + + self.props.width = style.GRID_CELL_SIZE + self.props.height = style.GRID_CELL_SIZE + self.props.size = style.STANDARD_ICON_SIZE + self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE + + self.tree_view = tree_view + + def create_palette(self): + tree_model = self.tree_view.get_model() + metadata = tree_model.get_metadata(self.props.palette_invoker.path) + + palette = ObjectPalette(metadata, detail=True) + palette.connect('detail-clicked', + self.__detail_clicked_cb) + return palette + + def __detail_clicked_cb(self, palette, uid): + self.emit('detail-clicked', uid) + +class CellRendererBuddy(CellRendererIcon): + __gtype_name__ = 'JournalCellRendererBuddy' + + def __init__(self, tree_view, column_index): + CellRendererIcon.__init__(self, tree_view) + + self.props.width = style.STANDARD_ICON_SIZE + self.props.height = style.STANDARD_ICON_SIZE + self.props.size = style.STANDARD_ICON_SIZE + self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE + + self.tree_view = tree_view + self._model_column_index = column_index + + def create_palette(self): + model = self.tree_view.get_model() + row = model[self.props.palette_invoker.path] + + if row[self._model_column_index] is not None: + nick, xo_color = row[self._model_column_index] + return BuddyPalette((nick, xo_color.to_string())) + else: + return None + + def set_buddy(self, buddy): + if buddy is None: + self.props.icon_name = None + else: + nick_, xo_color = buddy + self.props.icon_name = 'computer-xo' + self.props.xo_color = xo_color - def __entry_activated_cb(self, entry): - self.emit('detail-clicked', entry) + buddy = gobject.property(type=object, setter=set_buddy) |