From d0b12ac2dc48c066c86eb3b3dbde642b5829f615 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Tue, 25 Aug 2009 16:28:35 +0000 Subject: Revert thumbs merge --- (limited to 'src') diff --git a/src/jarabe/journal/Makefile.am b/src/jarabe/journal/Makefile.am index d99fa1c..f4bf273 100644 --- a/src/jarabe/journal/Makefile.am +++ b/src/jarabe/journal/Makefile.am @@ -1,24 +1,17 @@ sugardir = $(pythondir)/jarabe/journal -sugar_PYTHON = \ - __init__.py \ - detailview.py \ - expandedentry.py \ - journalactivity.py \ - journalentrybundle.py \ +sugar_PYTHON = \ + __init__.py \ + detailview.py \ + expandedentry.py \ + journalactivity.py \ + journalentrybundle.py \ journaltoolbox.py \ - keepicon.py \ - lazymodel.py \ - listmodel.py \ - listview.py \ - misc.py \ - modalalert.py \ - model.py \ - objectchooser.py \ - objectmodel.py \ - objectview.py \ - palettes.py \ - smoothtable.py \ - source.py \ - tableview.py \ - thumbsview.py \ + keepicon.py \ + listmodel.py \ + listview.py \ + misc.py \ + modalalert.py \ + model.py \ + objectchooser.py \ + palettes.py \ volumestoolbar.py diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py index 9864891..88c0b41 100644 --- a/src/jarabe/journal/expandedentry.py +++ b/src/jarabe/journal/expandedentry.py @@ -16,8 +16,10 @@ import logging from gettext import gettext as _ +import StringIO import hippo +import cairo import gobject import gtk import cjson @@ -170,10 +172,29 @@ class ExpandedEntry(hippo.CanvasBox): height = style.zoom(240) box = hippo.CanvasBox() - preview = misc.load_preview(self._metadata) + if self._metadata.has_key('preview') and \ + len(self._metadata['preview']) > 4: + + if self._metadata['preview'][1:4] == 'PNG': + preview_data = self._metadata['preview'] + else: + # TODO: We are close to be able to drop this. + import base64 + preview_data = base64.b64decode( + self._metadata['preview']) + + png_file = StringIO.StringIO(preview_data) + try: + surface = cairo.ImageSurface.create_from_png(png_file) + has_preview = True + except Exception: + logging.exception('Error while loading the preview') + has_preview = False + else: + has_preview = False - if preview is not None: - preview_box = hippo.CanvasImage(image=preview, + if has_preview: + preview_box = hippo.CanvasImage(image=surface, border=style.LINE_WIDTH, border_color=style.COLOR_BUTTON_GREY.get_int(), xalign=hippo.ALIGNMENT_CENTER, @@ -190,18 +211,16 @@ class ExpandedEntry(hippo.CanvasBox): color=style.COLOR_BUTTON_GREY.get_int(), box_width=width, box_height=height) - preview_box.connect_after('button-release-event', self._preview_box_button_release_event_cb) box.append(preview_box) - return box def _create_buddy_list(self): vbox = hippo.CanvasBox() vbox.props.spacing = style.DEFAULT_SPACING - + text = hippo.CanvasText(text=_('Participants:'), font_desc=style.FONT_NORMAL.get_pango_desc()) text.props.color = style.COLOR_BUTTON_GREY.get_int() diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py index 3ec09e8..08a5a0f 100644 --- a/src/jarabe/journal/journalactivity.py +++ b/src/jarabe/journal/journalactivity.py @@ -18,7 +18,7 @@ import logging from gettext import gettext as _ import sys -import traceback +import traceback import uuid import gtk @@ -34,7 +34,7 @@ from sugar import wm from jarabe.model import bundleregistry from jarabe.journal.journaltoolbox import MainToolbox, DetailToolbox -from jarabe.journal.objectview import ObjectView +from jarabe.journal.listview import ListView from jarabe.journal.detailview import DetailView from jarabe.journal.volumestoolbar import VolumesToolbar from jarabe.journal import misc @@ -105,14 +105,11 @@ class JournalActivity(Window): logging.debug("STARTUP: Loading the journal") Window.__init__(self) - accel_group = gtk.AccelGroup() - self.set_data('sugar-accel-group', accel_group) - self.add_accel_group(accel_group) - self.set_title(_('Journal')) self._main_view = None self._secondary_view = None + self._list_view = None self._detail_view = None self._main_toolbox = None self._detail_toolbox = None @@ -134,10 +131,10 @@ class JournalActivity(Window): model.updated.connect(self.__model_updated_cb) model.deleted.connect(self.__model_deleted_cb) - self._dbus_service = JournalActivityDBusService(self) + self._dbus_service = JournalActivityDBusService(self) self.iconify() - + self._critical_space_alert = None self._check_available_space() @@ -155,11 +152,11 @@ class JournalActivity(Window): self._main_toolbox = MainToolbox() self._main_view = gtk.VBox() - self._objects_view = ObjectView() - self._objects_view.connect('clear-clicked', self.__clear_clicked_cb) - self._objects_view.connect('detail-clicked', self.__detail_clicked_cb) - self._main_view.pack_start(self._objects_view) - self._objects_view.show() + self._list_view = ListView() + self._list_view.connect('detail-clicked', self.__detail_clicked_cb) + self._list_view.connect('clear-clicked', self.__clear_clicked_cb) + self._main_view.pack_start(self._list_view) + self._list_view.show() self._volumes_toolbar = VolumesToolbar() self._volumes_toolbar.connect('volume-changed', @@ -168,7 +165,6 @@ class JournalActivity(Window): search_toolbar = self._main_toolbox.search_toolbar search_toolbar.connect('query-changed', self._query_changed_cb) - search_toolbar.connect('view-changed', self.__view_changed_cb) search_toolbar.set_mount_point('/') def _setup_secondary_view(self): @@ -199,12 +195,9 @@ class JournalActivity(Window): self.show_main_view() def _query_changed_cb(self, toolbar, query): - self._objects_view.update_with_query(query) + self._list_view.update_with_query(query) self.show_main_view() - def __view_changed_cb(self, sender, view): - self._objects_view.change_view(view) - def show_main_view(self): if self.toolbox != self._main_toolbox: self.set_toolbox(self._main_toolbox) @@ -268,7 +261,7 @@ class JournalActivity(Window): def _focus_in_event_cb(self, window, event): self.search_grab_focus() - self._objects_view.update_dates() + self._list_view.update_dates() def _check_for_bundle(self, object_id): registry = bundleregistry.get_registry() @@ -307,12 +300,12 @@ class JournalActivity(Window): if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED: state = event.new_window_state visible = not state & gtk.gdk.WINDOW_STATE_ICONIFIED - self._objects_view.set_is_visible(visible) + self._list_view.set_is_visible(visible) def __visibility_notify_event_cb(self, window, event): logging.debug('visibility_notify_event_cb %r', self) visible = event.state != gtk.gdk.VISIBILITY_FULLY_OBSCURED - self._objects_view.set_is_visible(visible) + self._list_view.set_is_visible(visible) def _check_available_space(self): ''' Check available space on device diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py index a201550..201bf76 100644 --- a/src/jarabe/journal/journaltoolbox.py +++ b/src/jarabe/journal/journaltoolbox.py @@ -26,7 +26,6 @@ import gobject import gio import gtk -from sugar.graphics.radiotoolbutton import RadioToolButton from sugar.graphics.toolbox import Toolbox from sugar.graphics.toolcombobox import ToolComboBox from sugar.graphics.toolbutton import ToolButton @@ -74,10 +73,7 @@ class SearchToolbar(gtk.Toolbar): __gsignals__ = { 'query-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([object])), - 'view-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([object])) + ([object])) } def __init__(self): @@ -118,34 +114,10 @@ class SearchToolbar(gtk.Toolbar): #self.insert(tool_item, -1) #tool_item.show() - separator = gtk.SeparatorToolItem() - separator.props.draw = False - separator.set_expand(True) - self.insert(separator, -1) - separator.show() - - list_button = RadioToolButton(named_icon='view-list') - list_button.props.tooltip = _('List view') - list_button.props.accelerator = _('1') - list_button.connect('toggled', self.__view_button_toggled_cb, 0) - self.insert(list_button, -1) - list_button.show() - - thumb_button = RadioToolButton(named_icon='view-thumbs') - thumb_button.props.group = list_button - thumb_button.props.tooltip = _('Thumbs view') - thumb_button.props.accelerator = _('2') - thumb_button.connect('toggled', self.__view_button_toggled_cb, 1) - self.insert(thumb_button, -1) - thumb_button.show() - self._query = self._build_query() self.refresh_filters() - def __view_button_toggled_cb(self, button, view_num): - self.emit('view-changed', view_num) - def give_entry_focus(self): self._search_entry.grab_focus() diff --git a/src/jarabe/journal/lazymodel.py b/src/jarabe/journal/lazymodel.py deleted file mode 100644 index a5938c6..0000000 --- a/src/jarabe/journal/lazymodel.py +++ /dev/null @@ -1,416 +0,0 @@ -# Copyright (C) 2009, Aleksey Lim -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -import gtk -import logging -from gobject import GObject, SIGNAL_RUN_FIRST, TYPE_PYOBJECT - - -class Source(GObject): - - __gsignals__ = { - 'objects-updated': (SIGNAL_RUN_FIRST, None, []), - 'row-delayed-fetch': (SIGNAL_RUN_FIRST, None, 2 * [TYPE_PYOBJECT]), - } - - def get_count(self): - """ Returns number of objects """ - pass - - def get_row(self, offset): - """ Get object - - Returns: - objects in dict {field_name: value, ...} - False can't fint object - None wait for reply signal - - """ - pass - - def get_order(self): - """ Get current order, returns (field_name, gtk.SortType) """ - pass - - def set_order(self, field_name, sort_type): - """ Set current order """ - pass - - -class LazyModel(gtk.GenericTreeModel): - - def __init__(self, columns, calc_columns=None): - """ columns/calc_columns = {field_name: (column_num, column_type)} """ - gtk.GenericTreeModel.__init__(self) - - self.columns_by_name = {} - self.columns_by_num = {} - self.columns_types = {} - - for name, i in columns.items(): - self.columns_by_name[name] = i[0] - self.columns_by_num[i[0]] = name - self.columns_types[i[0]] = i[1] - - if calc_columns is not None: - for name, i in calc_columns.items(): - self.columns_types[i[0]] = i[1] - - self._n_columns = max(self.columns_types.keys()) + 1 - - self._source = None - self._closing = False - self._view = None - self._last_count = 0 - self._cache = {} - self._frame = (0, -1) - self._in_process = {} - self._postponed = [] - - self.set_source(None, force=True) - self.set_view(None, force=True) - - def on_calc_value(self, row, column): - # stub - pass - - def get_source(self): - return self._source - - def set_source(self, source, force=False): - if self._source == source and not force: - return - - if self._source is not None: - self._source.disconnect_by_func(self.refresh) - self._source.disconnect_by_func(self.__delayed_fetch_cb) - - self._source = source - if self._source is not None: - self._source.connect('objects-updated', self.refresh) - self._source.connect('row-delayed-fetch', self.__delayed_fetch_cb) - - self.refresh() - - source = property(get_source, set_source) - - def get_view(self): - return self._view - - def set_view(self, view, force=False): - if self._view == view and not force: - return - - cursor = None - - if self._view is not None: - cursor = self._view.get_cursor() - self._unset_view_model() - - self._view = view - self._cache = {} - self._frame = (0, -1) - self._in_process = {} - self._postponed = [] - - if self._source is None: - self._last_count = 0 - else: - self._last_count = self._source.get_count() - - if self._source is not None and view is not None: - self._update_columns() - view.set_model(self) - if cursor is not None: - view.set_cursor(*cursor) - - view = property(get_view, set_view) - - def get_order(self): - if self._source is None: - return None - order = self._source.get_order() - if order is None: - return None - return (self.columns_by_name[order[0]], order[1]) - - def set_order(self, column, order): - if self._source is None: - return - self._source.set_order(self.columns_by_num[column], order) - self._update_columns() - - def refresh(self, sender=None): - if self._source is None or self._view is None: - return - - if self._last_count == 0: - self.set_view(self._view, force=True) - - self._update_columns() - - count = self._source.get_count() - if self._frame[0] >= count: - self._frame = (0, -1) - elif self._frame[1] >= count: - self._frame = (self._frame[0], count-1) - - self._cache = {} - - if self._last_count != count: - self._unset_view_model() - self._view.set_model(self) - else: - for i in range(self._frame[0], self._frame[1]+1): - self.emit('row-changed', (i, ), self.get_iter((i, ))) - - self._last_count = count - - def recalc(self, fields): - for i, row in self._cache.items(): - for field in fields: - if field in row: - del row[field] - self.emit('row-changed', (i, ), self.get_iter((i, ))) - - def get_row(self, pos, frame=None): - if self._source is None: - return False - if not isinstance(pos, tuple): - pos = self.get_path(pos) - return self._get_row(pos[0], frame or (pos, pos)) - - def _unset_view_model(self): - try: - self._closing = True - self._view.set_model(None) - finally: - self._closing = False - - def __delayed_fetch_cb(self, source, offset, metadata): - if not offset in self._in_process: - logging.debug('__delayed_fetch_cb: no offset=%s' % offset) - return - - logging.debug('__delayed_fetch_cb: get %s' % offset) - - path = (offset, ) - iterator = self.get_iter(path) - row = Row(self, path, iterator, metadata) - - if self.in_frame(offset): - self._cache[offset] = row - - del self._in_process[offset] - self.emit('row-changed', path, iterator) - if self._in_process: - return - - while self._postponed: - offset, force = self._postponed.pop() - if not force and not self.in_frame(offset): - continue - row = self.get_row((offset, )) - if row is not None and row != False: - self.emit('row-changed', row.path, row.iterator) - else: - break - - def _get_row(self, offset, frame): - - def fetch(): - row = self._source.get_row(offset) - - if row is None or row == False: - if row is not None: - logging.debug('_get_row: can not find row for %s' % offset) - return False - logging.debug('_get_row: wait for reply for %s' % offset) - self._in_process[offset] = True - return None - - row = Row(self, (offset, ), self.get_iter(offset), row) - self._cache[offset] = row - return row - - out = self._cache.get(offset) - if out is not None: - return out - - if frame[0] >= frame[1]: - # just return requested single row and do not change cache - # if requested frame has <= 1 rows - if self._in_process: - self._postponed.append((offset, True)) - return None - else: - return fetch() - - if frame != self._frame: - # switch to new frame - intersect_min = max(frame[0], self._frame[0]) - intersect_max = min(frame[1], self._frame[1]) - if intersect_min > intersect_max: - self._cache = {} - else: - for i in range(self._frame[0], intersect_min): - if i in self._cache: - del self._cache[i] - for i in range(intersect_max+1, self._frame[1]+1): - if i in self._cache: - del self._cache[i] - self._frame = frame - - if self._in_process: - self._postponed.append((offset, False)) - return None - - return fetch() - - def _update_columns(self): - order = self.get_order() - if order is None or not hasattr(self._view, 'get_columns'): - return - - for column in self._view.get_columns(): - if column.get_sort_column_id() == order[0]: - column.props.sort_indicator = True - column.props.sort_order = order[1] - else: - column.props.sort_indicator = False - - def in_frame(self, offset): - return offset >= self._frame[0] and offset <= self._frame[1] - - def on_get_n_columns(self): - return self._n_columns - - def on_get_column_type(self, index): - return self.columns_types.get(index, bool) - - def on_iter_n_children(self, iterator): - if iterator is None and not self._closing: - return self._source.get_count() - else: - return 0 - - def on_get_value(self, offset, column): - if self._view is None or offset >= self._source.get_count(): - return None - - # return value only if iterator came from visible range - # (on setting model, gtk.TreeView scans all items) - vrange = self._view.get_visible_range() - if vrange and offset >= vrange[0][0] and offset <= vrange[1][0]: - row = self._get_row(offset, (vrange[0][0], vrange[1][0])) - return row is not None and row != False and row[column] - - return None - - def on_iter_nth_child(self, iterator, n): - return n - - def on_get_path(self, iterator): - return iterator - - def on_get_iter(self, path): - if self._source.get_count() and not self._closing: - return path[0] - else: - return False - - def on_iter_next(self, iterator): - if iterator is not None: - if iterator >= self._source.get_count() - 1 or self._closing: - return None - return iterator + 1 - return None - - def on_get_flags(self): - return gtk.TREE_MODEL_ITERS_PERSIST | gtk.TREE_MODEL_LIST_ONLY - - def on_iter_children(self, iterator): - return None - - def on_iter_has_child(self, iterator): - return False - - def on_iter_parent(self, iterator): - return None - - -class Row(object): - - def __init__(self, model, path, iterator, metadata): - self.model = model - self.iterator = iterator - self.path = path - self.metadata = metadata - self.row = [None] * len(model.columns_by_name) - self._calced_row = {} - - for name, value in metadata.items(): - column = model.columns_by_name.get(str(name), -1) - if column != -1: - self.row[column] = value - - def __getitem__(self, key): - if isinstance(key, int): - if key < len(self.row): - return self.row[key] - else: - if key in self._calced_row: - return self._calced_row[key] - else: - value = self.model.on_calc_value(self, key) - if value is not None: - self._calced_row[key] = value - return value - else: - return self.metadata[key] - - def __setitem__(self, key, value): - if isinstance(key, int): - if key < len(self.row): - self.row[key] = value - else: - self._calced_row[key] = value - else: - self.metadata[key] = value - - def __delitem__(self, key): - if isinstance(key, int): - if key < len(self.row): - del self.row[key] - else: - del self._calced_row[key] - else: - del self.metadata[key] - - def __contains__(self, key): - if isinstance(key, int): - return key < len(self.row) or key in self._calced_row - else: - return self.metadata.__contains__(key) - - def has_key(self, key): - return self.__contains__(key) - - def get(self, key, default=None): - if key in self: - return self.__getitem__(key) - else: - return default diff --git a/src/jarabe/journal/listmodel.py b/src/jarabe/journal/listmodel.py index 6f0d4f1..917fbb1 100644 --- a/src/jarabe/journal/listmodel.py +++ b/src/jarabe/journal/listmodel.py @@ -65,18 +65,35 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource): COLUMN_BUDDY_3: object, COLUMN_BUDDY_2: object} - def __init__(self, result_set): + _PAGE_SIZE = 10 + + def __init__(self, query): gobject.GObject.__init__(self) self._last_requested_index = None self._cached_row = None - self._result_set = result_set + self._result_set = model.find(query, ListModel._PAGE_SIZE) self._temp_drag_file_path = None # HACK: The view will tell us that it is resizing so the model can # avoid hitting D-Bus and disk. self.view_is_resizing = False + self._result_set.ready.connect(self.__result_set_ready_cb) + self._result_set.progress.connect(self.__result_set_progress_cb) + + def __result_set_ready_cb(self, **kwargs): + self.emit('ready') + + def __result_set_progress_cb(self, **kwargs): + self.emit('progress') + + def setup(self): + self._result_set.setup() + + def stop(self): + self._result_set.stop() + def get_metadata(self, path): return model.get(self[path][ListModel.COLUMN_UID]) 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) diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py index 0fe4b1e..e6e5abf 100644 --- a/src/jarabe/journal/misc.py +++ b/src/jarabe/journal/misc.py @@ -18,8 +18,6 @@ import logging import time import traceback import os -import StringIO -import cairo from gettext import gettext as _ import gio @@ -234,23 +232,3 @@ def is_bundle(metadata): return is_activity_bundle(metadata) or is_content_bundle(metadata) or \ is_journal_bundle(metadata) -def load_preview(metadata): - if not metadata.has_key('preview') or \ - len(metadata['preview']) < 5: - return None - - if metadata['preview'][1:4] == 'PNG': - preview_data = metadata['preview'] - else: - # TODO: We are close to be able to drop this. - import base64 - preview_data = base64.b64decode(metadata['preview']) - - png_file = StringIO.StringIO(preview_data) - try: - surface = cairo.ImageSurface.create_from_png(png_file) - except Exception, e: - logging.error('Error while loading the preview: %r' % e) - return None - - return surface diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py index ede4fdf..15259bb 100644 --- a/src/jarabe/journal/model.py +++ b/src/jarabe/journal/model.py @@ -65,9 +65,7 @@ class _Cache(object): def remove_all(self, entries): for uid in [entry['uid'] for entry in entries]: - obj = self._dict.get(uid) - if obj is None: - continue + obj = self._dict[uid] self._array.remove(obj) del self._dict[uid] @@ -196,8 +194,8 @@ class BaseResultSet(object): objects_excess = len(self._cache) - cache_limit if objects_excess > 0: self._cache.remove_all(self._cache[-objects_excess:]) - #else: - # logging.debug('cache hit and no need to grow the cache') + else: + logging.debug('cache hit and no need to grow the cache') return self._cache[self._position - self._offset] @@ -396,24 +394,16 @@ def _get_mount_point(path): else: dir_path = dir_path.rsplit(os.sep, 1)[0] -def get(object_id, reply_cb=None): +def get(object_id): """Returns the metadata for an object """ if os.path.exists(object_id): stat = os.stat(object_id) metadata = _get_file_metadata(object_id, stat) metadata['mountpoint'] = _get_mount_point(object_id) - - elif reply_cb is None: + else: metadata = _get_datastore().get_properties(object_id, byte_arrays=True) metadata['mountpoint'] = '/' - - else: - _get_datastore().get_properties(object_id, byte_arrays=True, - reply_handler=reply_cb, - error_handler=lambda e: reply_cb(None)) - return None - return metadata def get_file(object_id): diff --git a/src/jarabe/journal/objectchooser.py b/src/jarabe/journal/objectchooser.py index 57e9ceb..31bdba8 100644 --- a/src/jarabe/journal/objectchooser.py +++ b/src/jarabe/journal/objectchooser.py @@ -24,7 +24,7 @@ import wnck from sugar.graphics import style from sugar.graphics.toolbutton import ToolButton -from jarabe.journal.objectview import ObjectView +from jarabe.journal.listview import BaseListView from jarabe.journal.listmodel import ListModel from jarabe.journal.journaltoolbox import SearchToolbar from jarabe.journal.volumestoolbar import VolumesToolbar @@ -80,21 +80,19 @@ class ObjectChooser(gtk.Window): self._toolbar = SearchToolbar() self._toolbar.connect('query-changed', self.__query_changed_cb) - self._toolbar.connect('view-changed', self.__view_changed_cb) self._toolbar.set_size_request(-1, style.GRID_CELL_SIZE) vbox.pack_start(self._toolbar, expand=False) self._toolbar.show() - self._object_view = ObjectView() - self._object_view.props.hover_selection = True - self._object_view.connect('entry-activated', self.__entry_activated_cb) - vbox.pack_start(self._object_view) - self._object_view.show() + self._list_view = ChooserListView() + self._list_view.connect('entry-activated', self.__entry_activated_cb) + vbox.pack_start(self._list_view) + self._list_view.show() self._toolbar.set_mount_point('/') - + width = gtk.gdk.screen_width() - style.GRID_CELL_SIZE * 2 - height = gtk.gdk.screen_height() - style.GRID_CELL_SIZE * 2 + height = gtk.gdk.screen_height() - style.GRID_CELL_SIZE * 2 self.set_size_request(width, height) if what_filter: @@ -127,10 +125,7 @@ class ObjectChooser(gtk.Window): return self._selected_object_id def __query_changed_cb(self, toolbar, query): - self._object_view.update_with_query(query) - - def __view_changed_cb(self, sender, view): - self._object_view.change_view(view) + self._list_view.update_with_query(query) def __volume_changed_cb(self, volume_toolbar, mount_point): logging.debug('Selected volume: %r.', mount_point) @@ -139,7 +134,7 @@ class ObjectChooser(gtk.Window): def __visibility_notify_event_cb(self, window, event): logging.debug('visibility_notify_event_cb %r', self) visible = event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED - self._object_view.set_is_visible(visible) + self._list_view.set_is_visible(visible) class TitleBox(VolumesToolbar): __gtype_name__ = 'TitleBox' @@ -166,3 +161,39 @@ class TitleBox(VolumesToolbar): self.insert(tool_item, -1) tool_item.show() + +class ChooserListView(BaseListView): + __gtype_name__ = 'ChooserListView' + + __gsignals__ = { + 'entry-activated': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str])), + } + + def __init__(self): + BaseListView.__init__(self) + + self.cell_icon.props.show_palette = False + self.tree_view.props.hover_selection = True + + self.tree_view.connect('button-release-event', + self.__button_release_event_cb) + + def __entry_activated_cb(self, entry): + self.emit('entry-activated', entry) + + def __button_release_event_cb(self, tree_view, event): + if event.window != tree_view.get_bin_window(): + return False + + pos = tree_view.get_path_at_pos(event.x, event.y) + if pos is None: + return False + + path, column_, x_, y_ = pos + uid = tree_view.get_model()[path][ListModel.COLUMN_UID] + self.emit('entry-activated', uid) + + return False + diff --git a/src/jarabe/journal/objectmodel.py b/src/jarabe/journal/objectmodel.py deleted file mode 100644 index 5c728ac..0000000 --- a/src/jarabe/journal/objectmodel.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (C) 2009, Aleksey Lim -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -import gobject - -from sugar import util - -from jarabe.journal import misc -from jarabe.journal.source import Source -from jarabe.journal.lazymodel import LazyModel - - -class ObjectModel(LazyModel): - - FIELD_FETCHED_FLAG = 50 - - def __init__(self): - LazyModel.__init__(self, Source.FIELDS_BASE, Source.FIELDS_CALC) - self._fetch_queue = [] - self._object_delayed_fetch_handle = None - - def on_calc_value(self, row, column): - if column == Source.FIELD_MODIFY_TIME: - return util.timestamp_to_elapsed_string( - int(row[Source.FIELD_TIMESTAMP]) or 0) - - if column == Source.FIELD_THUMB: - if self.fetch_metadata(row): - return row[Source.FIELD_THUMB] - return None - - return None - - def fetch_metadata(self, row): - if row.metadata['mountpoint'] != '/': - # do not process non-ds objects - return False - - if self.FIELD_FETCHED_FLAG in row: - return True - - if row not in self._fetch_queue: - self._fetch_queue.append(row) - if len(self._fetch_queue) == 1: - gobject.idle_add(self.__idle_cb) - - return False - - def __idle_cb(self): - while len(self._fetch_queue): - row = self._fetch_queue[0] - if self.in_frame(row.path[0]): - self.source.get_object(row, self.__get_object_cb) - break - del self._fetch_queue[0] - return False - - def __get_object_cb(self, metadata): - row = self._fetch_queue[0] - del self._fetch_queue[0] - - if metadata is not None: - row.metadata.update(metadata) - - row[Source.FIELD_THUMB] = misc.load_preview(metadata) - row[self.FIELD_FETCHED_FLAG] = True - - self.emit('row-changed', row.path, row.iterator) - - if len(self._fetch_queue): - gobject.idle_add(self.__idle_cb) diff --git a/src/jarabe/journal/objectview.py b/src/jarabe/journal/objectview.py deleted file mode 100644 index ab04a5b..0000000 --- a/src/jarabe/journal/objectview.py +++ /dev/null @@ -1,301 +0,0 @@ -# Copyright (C) 2009, Tomeu Vizoso, Aleksey Lim -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -import logging -from gettext import gettext as _ -import time - -import gobject -import gtk -import hippo - -from sugar.graphics import style -from sugar.graphics.icon import CanvasIcon, Icon - -from jarabe.journal import model -from jarabe.journal.listview import ListView -from jarabe.journal.thumbsview import ThumbsView -from jarabe.journal.listmodel import ListModel -from jarabe.journal.objectmodel import ObjectModel -from jarabe.journal.source import Source, LocalSource - -UPDATE_INTERVAL = 300 - -MESSAGE_EMPTY_JOURNAL = 0 -MESSAGE_NO_MATCH = 1 - -VIEW_LIST = 0 -VIEW_THUMBS = 1 - -VIEW_TYPES = [ListView, ThumbsView] - -PAGE_SIZE = 10 - - -class ObjectView(gtk.Bin): - - __gsignals__ = { - 'clear-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])), - 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([object])), - 'entry-activated': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([str])), - } - - def __init__(self): - gobject.GObject.__init__(self) - - self._query = {} - self._result_set = None - self._progress_bar = None - self._last_progress_bar_pulse = None - self._model = ObjectModel() - self._view_widgets = [] - self._view = VIEW_LIST - - self.connect('destroy', self.__destroy_cb) - - for view_class in VIEW_TYPES: - view = view_class() - view.modify_base(gtk.STATE_NORMAL, - style.COLOR_WHITE.get_gdk_color()) - view.connect('detail-clicked', self.__detail_clicked_cb) - view.connect('entry-activated', self.__entry_activated_cb) - view.show() - - widget = gtk.ScrolledWindow() - widget.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) - widget.show() - widget.add(view) - widget.view = view - self._view_widgets.append(widget) - - # Auto-update stuff - self._fully_obscured = True - self._dirty = False - self._refresh_idle_handler = None - self._update_dates_timer = None - - model.created.connect(self.__model_created_cb) - model.updated.connect(self.__model_updated_cb) - model.deleted.connect(self.__model_deleted_cb) - - def set_hover_selection(self, hover_selection): - for i in self._view_widgets: - i.view.props.hover_selection = hover_selection - - hover_selection = gobject.property(type=bool, default=False, - setter=set_hover_selection) - - 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 update_dates(self): - if self._view == VIEW_LIST: - # TODO in 0.88 VIEW_LIST will use lazymodel - self._view_widgets[VIEW_LIST].view.update_dates() - return - self._model.recalc([Source.FIELD_MODIFY_TIME]) - - def change_view(self, view): - if self._view_widgets[view].parent is not None: - return - self._view = view - if self.child is not None: - self.remove(self.child) - self.add(self._view_widgets[view]) - self._view_widgets[view].show() - if view == VIEW_LIST: - # TODO in 0.88 VIEW_LIST will use lazymodel - return - self._model.view = self._view_widgets[view].view - - 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 _refresh(self): - logging.debug('ListView._refresh query %r' % self._query) - self._stop_progress_bar() - - if self._result_set is not None: - self._result_set.stop() - - self._result_set = model.find(self._query, 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() - - def __result_set_ready_cb(self, **kwargs): - self._stop_progress_bar() - - if self._result_set.length == 0: - if self._is_query_empty(): - self._show_message(MESSAGE_EMPTY_JOURNAL) - else: - self._show_message(MESSAGE_NO_MATCH) - else: - # TODO in 0.88 VIEW_LIST will use lazymodel - self._view_widgets[VIEW_LIST].view.set_model( - ListModel(self._result_set)) - self._model.source = LocalSource(self._result_set) - self.change_view(self._view) - - def __result_set_progress_cb(self, **kwargs): - if self._progress_bar is None: - self._start_progress_bar() - - 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 _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_created_cb(self, sender, **kwargs): - self._set_dirty() - - def __model_updated_cb(self, sender, **kwargs): - self._set_dirty() - - def __model_deleted_cb(self, sender, **kwargs): - self._set_dirty() - - def __destroy_cb(self, widget): - if self._result_set is not None: - self._result_set.stop() - - def _start_progress_bar(self): - alignment = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.5) - if self.child is not None: - 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 - if self.child is not None: - self.remove(self.child) - self.add(self._view_widgets[self._view]) - self._view_widgets[self._view].show() - self._progress_bar = None - - def _show_message(self, message): - canvas = hippo.Canvas() - if self.child is not None: - 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 _set_dirty(self): - if self._fully_obscured: - self._dirty = True - else: - self._refresh() - - def __update_dates_timer_cb(self): - self.update_dates() - return True - - def __detail_clicked_cb(self, list_view, object_id): - self.emit('detail-clicked', object_id) - - def __entry_activated_cb(self, sender, uid): - self.emit('entry-activated', uid) - - 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() diff --git a/src/jarabe/journal/smoothtable.py b/src/jarabe/journal/smoothtable.py deleted file mode 100644 index 9f6af15..0000000 --- a/src/jarabe/journal/smoothtable.py +++ /dev/null @@ -1,346 +0,0 @@ -# Copyright (C) 2009, Aleksey Lim -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -import gtk -import gobject -import math -import bisect -import logging - - -class SmoothTable(gtk.Container): - - __gsignals__ = { - 'set-scroll-adjustments': (gobject.SIGNAL_RUN_FIRST, None, - [gtk.Adjustment, gtk.Adjustment]), - } - - def __init__(self, rows, columns, new_cell, fill_in): - assert(rows and columns) - - self._rows = [] - self._adj = None - self._adj_value_changed_id = None - self._bin_window = None - self._bin_rows = 0 - self._cell_height = 0 - self._reordered = None - self._last_allocation = None - self._fill_in = fill_in - self._visible_rows = {} - - gtk.Container.__init__(self) - - for i in range(rows + 2): - row = [] - for j in range(columns): - cell = new_cell() - cell.show() - cell.set_parent(self) - cell.size_allocate(gtk.gdk.Rectangle(-1, -1)) - row.append(cell) - self._rows.append(row) - - self.connect('key-press-event', self.__key_press_event_cb) - - def get_columns(self): - return len(self._rows[0]) - - columns = property(get_columns) - - def get_rows(self): - return len(self._rows) - 2 - - rows = property(get_rows) - - def get_frame(self): - if self._adj is None or self._cell_height == 0: - return (0, 0) - top = int(self._adj.value / self._cell_height) - bottom = int(math.ceil(self._adj.value + self._adj.page_size) / \ - self._cell_height) - return (top * self.columns, bottom * self.columns + (self.columns - 1)) - - frame = property(get_frame) - - def get_bin_rows(self): - return self._bin_rows - - def set_bin_rows(self, bin_rows): - self._bin_rows = max(self.rows, bin_rows) - - if self._adj is None: - return - - for row in self._rows: - for cell in row: - cell.size_allocate(gtk.gdk.Rectangle(-1, -1, 0, 0)) - - self._setup_adjustment(force=True) - - bin_rows = property(get_bin_rows, set_bin_rows) - - def get_visible_cell(self, y, x): - if x >= self.columns: - return None - row = self._visible_rows.get(y) - if row is None: - return None - return row[x] - - def goto(self, row): - if self._adj is None: - return - self._adj.props.value = row * self._cell_height - self._adj.value_changed() - - def do_realize(self): - self.set_flags(gtk.REALIZED) - - self.window = gtk.gdk.Window( - self.get_parent_window(), - window_type=gtk.gdk.WINDOW_CHILD, - x=self.allocation.x, - y=self.allocation.y, - width=self.allocation.width, - height=self.allocation.height, - wclass=gtk.gdk.INPUT_OUTPUT, - colormap=self.get_colormap(), - event_mask=gtk.gdk.VISIBILITY_NOTIFY_MASK) - self.window.set_user_data(self) - - self._bin_window = gtk.gdk.Window( - self.window, - window_type=gtk.gdk.WINDOW_CHILD, - x=0, - y=int(-self._adj.value), - width=self.allocation.width, - height=int(self._adj.upper), - colormap=self.get_colormap(), - wclass=gtk.gdk.INPUT_OUTPUT, - event_mask=(self.get_events() | gtk.gdk.EXPOSURE_MASK | - gtk.gdk.SCROLL_MASK)) - self._bin_window.set_user_data(self) - - self.set_style(self.style.attach(self.window)) - self.style.set_background(self.window, gtk.STATE_NORMAL) - self.style.set_background(self._bin_window, gtk.STATE_NORMAL) - - for row in self._rows: - for cell in row: - cell.set_parent_window(self._bin_window) - - self.queue_resize() - - def do_size_allocate(self, allocation): - if self._reordered is not None: - if allocation == self._reordered: - self._reordered = None - return - self._reordered = None - - self.allocation = allocation - self._cell_height = allocation.height / self.rows - - self._setup_adjustment(force=True) - - if self.flags() & gtk.REALIZED: - self.window.move_resize(*allocation) - self._bin_window.resize(allocation.width, int(self._adj.upper)) - - def do_unrealize(self): - self._bin_window.set_user_data(None) - self._bin_window.destroy() - self._bin_window = None - gtk.Container.do_unrealize(self) - - def do_style_set(self, style): - gtk.Widget.do_style_set(self, style) - if self.flags() & gtk.REALIZED: - self.style.set_background(self._bin_window, gtk.STATE_NORMAL) - - def do_expose_event(self, event): - if event.window != self._bin_window: - return False - gtk.Container.do_expose_event(self, event) - return False - - def do_map(self): - self.set_flags(gtk.MAPPED) - - for row in self._rows: - for cell in row: - cell.map() - - self._bin_window.show() - self.window.show() - - def do_size_request(self, req): - req.width = 0 - req.height = 0 - - for row in self._rows: - for cell in row: - cell.size_request() - - def do_forall(self, include_internals, callback, data): - for row in self._rows: - for cell in row: - callback(cell, data) - - def do_add(self, widget): - pass - - def do_remove(self, widget): - pass - - def do_set_scroll_adjustments(self, hadjustment, vadjustment): - if vadjustment is None or vadjustment == self._adj: - return - - if self._adj is not None: - self._adj.disconnect(self._adj_value_changed_id) - - self._adj = vadjustment - self._setup_adjustment() - - self._adj_value_changed_id = vadjustment.connect('value-changed', - self.__adjustment_value_changed_cb) - - def _setup_adjustment(self, force=False): - self._adj.lower = 0 - self._adj.upper = self._bin_rows * self._cell_height - self._adj.page_size = self.allocation.height - self._adj.changed() - - max_value = max(0, self._adj.upper - self._adj.page_size) - if self._adj.value > max_value: - self._adj.value = max_value - self._adj.value_changed() - elif force: - self._adj.value_changed() - - def _allocate_row(self, row, cell_y): - cell_x = 0 - cell_no = cell_y / self._cell_height * self.columns - cell_row = cell_y / self._cell_height - - for cell_column, cell in enumerate(row): - self._fill_in(cell, cell_row, cell_column) - - callocation = gtk.gdk.Rectangle(cell_x, cell_y) - callocation.width = self.allocation.width / self.columns - callocation.height = self._cell_height - cell.size_allocate(callocation) - - cell_x += callocation.width - cell_no += 1 - - def _get_head(self): - if self._adj is None: - return 0 - return int(self._adj.value) - int(self._adj.value) % self._cell_height - - def __adjustment_value_changed_cb(self, sender=None): - if not self.flags() & gtk.REALIZED or self._cell_height == 0 or \ - self._adj.value < 0 or self._adj.value > self._adj.upper - \ - (self.rows * self._cell_height): - return - - spare_rows = [] - visible_rows = [] - page_end = int(self._adj.value + self._adj.page_size) - - if self._last_allocation != self.allocation: - self._last_allocation = self.allocation - spare_rows = [] + self._rows - else: - - class IndexedRow: - - def __init__(self, row): - self.row = row - - def __lt__(self, other): - return self.row[0].allocation.y < other.row[0].allocation.y - - for row in self._rows: - if row[0].allocation.y < 0 or \ - row[0].allocation.y > page_end or \ - (row[0].allocation.y + self._cell_height) < \ - self._adj.value: - spare_rows.append(row) - else: - bisect.insort_right(visible_rows, IndexedRow(row)) - - if not visible_rows or len(visible_rows) < self.rows + \ - (self._get_head() != visible_rows[0]): - self._reordered = self.allocation - self._visible_rows = {} - - def insert_spare_row(cell_y, end_y): - while cell_y < end_y: - if not spare_rows: - logging.error('spare_rows should not be empty') - return - row = spare_rows.pop() - self._allocate_row(row, cell_y) - self._visible_rows[cell_y / self._cell_height] = row - cell_y = cell_y + self._cell_height - - cell_y = self._get_head() - for i in visible_rows: - cell = i.row[0].allocation - insert_spare_row(cell_y, cell.y) - self._visible_rows[cell.y / self._cell_height] = i.row - cell_y = cell.y + cell.height - insert_spare_row(cell_y, page_end) - - self._bin_window.move(0, int(-self._adj.value)) - self.window.process_updates(True) - - def __key_press_event_cb(self, widget, event): - if self._adj is None or self._cell_height == 0: - return - - page = self.rows * self._cell_height - uplimit = self._adj.upper - page - - if event.keyval == gtk.keysyms.Up: - self._adj.value -= self._cell_height - - elif event.keyval == gtk.keysyms.Down: - self._adj.value += min(uplimit - self._adj.value, - self._cell_height) - - elif event.keyval in (gtk.keysyms.Page_Up, gtk.keysyms.KP_Page_Up): - self._adj.value -= min(self._adj.value, page) - - elif event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.KP_Page_Down): - self._adj.value += min(uplimit - self._adj.value, page) - - elif event.keyval in (gtk.keysyms.Home, gtk.keysyms.KP_Home): - self._adj.value = 0 - - elif event.keyval in (gtk.keysyms.End, gtk.keysyms.KP_End): - self._adj.value = uplimit - - else: - return False - - return True - -SmoothTable.set_set_scroll_adjustments_signal('set-scroll-adjustments') diff --git a/src/jarabe/journal/source.py b/src/jarabe/journal/source.py deleted file mode 100644 index 13bf814..0000000 --- a/src/jarabe/journal/source.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (C) 2009, Aleksey Lim -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -import cairo -from gobject import GObject, SIGNAL_RUN_FIRST, TYPE_PYOBJECT - -from jarabe.journal import model - - -class Source(GObject): - - FIELD_UID = 0 - FIELD_TITLE = 1 - FIELD_MTIME = 2 - FIELD_TIMESTAMP = 3 - FIELD_KEEP = 4 - FIELD_BUDDIES = 5 - FIELD_ICON_COLOR = 6 - FIELD_MIME_TYPE = 7 - FIELD_PROGRESS = 8 - FIELD_ACTIVITY = 9 - FIELD_MOUNT_POINT = 10 - FIELD_ACTIVITY_ID = 11 - FIELD_BUNDLE_ID = 12 - - FIELD_FAVORITE = 30 - FIELD_MODIFY_TIME = 31 - FIELD_THUMB = 32 - - FIELDS_BASE = {'uid': (FIELD_UID, str), - 'title': (FIELD_TITLE, str), - 'mtime': (FIELD_MTIME, str), - 'timestamp': (FIELD_TIMESTAMP, int), - 'keep': (FIELD_KEEP, int), - 'buddies': (FIELD_BUDDIES, str), - 'icon-color': (FIELD_ICON_COLOR, str), - 'mime_type': (FIELD_MIME_TYPE, str), - 'progress': (FIELD_MIME_TYPE, str), - 'activity': (FIELD_ACTIVITY, str), - 'mountpoint': (FIELD_ACTIVITY, str), - 'activity_id': (FIELD_ACTIVITY_ID, str), - 'bundle_id': (FIELD_BUNDLE_ID, str)} - - FIELDS_CALC = {'favorite': (FIELD_FAVORITE, bool), - 'modify_time': (FIELD_MODIFY_TIME, str), - 'thumb': (FIELD_THUMB, cairo.ImageSurface)} - - -class LocalSource(Source): - - __gsignals__ = { - 'objects-updated': (SIGNAL_RUN_FIRST, None, []), - 'row-delayed-fetch': (SIGNAL_RUN_FIRST, None, 2 * [TYPE_PYOBJECT]), - } - - def __init__(self, resultset): - Source.__init__(self) - self._resultset = resultset - - def get_count(self): - return self._resultset.length - - def get_row(self, offset): - if offset >= self.get_count(): - return False - self._resultset.seek(offset) - return self._resultset.read() - - def get_order(self): - """ Get current order, returns (field_name, gtk.SortType) """ - pass - - def set_order(self, field_name, sort_type): - """ Set current order """ - pass - - def get_object(self, metadata, cb): - model.get(metadata['uid'], cb) - return None diff --git a/src/jarabe/journal/tableview.py b/src/jarabe/journal/tableview.py deleted file mode 100644 index 7bccf38..0000000 --- a/src/jarabe/journal/tableview.py +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright (C) 2009, Aleksey Lim -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -import gtk -import math -import hippo -import gobject - -from sugar.graphics import style -from sugar.graphics.roundbox import CanvasRoundBox -from jarabe.journal.smoothtable import SmoothTable - -COLOR_BACKGROUND = style.COLOR_WHITE -COLOR_SELECTED = style.COLOR_TEXT_FIELD_GREY - - -class TableCell(object): - - def __init__(self): - self.row = None - self.table = None - - def do_fill_in(self): - pass - - -class TableView(SmoothTable): - - def __init__(self, cell_class, rows, columns): - SmoothTable.__init__(self, rows, columns, - lambda: self._create_cell(cell_class), self._do_fill_in) - - self._model = None - self._hover_selection = False - self._selected_cell = None - self._row_changed_id = None - - def get_cursor(self): - return (self.frame[0], ) - - def set_cursor(self, cursor): - self.goto(cursor) - - def get_model(self): - return self._model - - def set_model(self, model): - if self._model == model: - return - - if self._model is not None and self._row_changed_id is not None: - self._model.disconnect(self._row_changed_id) - - self._model = model - - if model is not None: - self._row_changed_id = \ - self._model.connect('row-changed', self.__row_changed_cb) - - if model is not None: - rows = math.ceil(float(model.iter_n_children(None)) / self.columns) - self.bin_rows = int(rows) - - model = gobject.property(type=object, - getter=get_model, setter=set_model) - - def get_hover_selection(self): - return self._hover_selection - - def set_hover_selection(self, value): - self._hover_selection = value - - hover_selection = gobject.property(type=object, - getter=get_hover_selection, setter=set_hover_selection) - - def get_visible_range(self): - return ((self.frame[0], ), (self.frame[1], )) - - def _create_cell(self, cell_class): - canvas = hippo.Canvas() - canvas.show() - canvas.modify_bg(gtk.STATE_NORMAL, COLOR_BACKGROUND.get_gdk_color()) - - sel_box = CanvasRoundBox() - sel_box.props.border_color = COLOR_BACKGROUND.get_int() - canvas.set_root(sel_box) - - cell = cell_class() - cell.table = self - sel_box.append(cell, hippo.PACK_EXPAND) - - canvas.connect('enter-notify-event', - self.__enter_notify_event_cb, cell) - canvas.connect('leave-notify-event', self.__leave_notify_event_cb) - - canvas.table_view_cell_sel_box = sel_box - canvas.table_view_cell = cell - - return canvas - - def _do_fill_in(self, canvas, y, x, prepared_row=None): - - cell = canvas.table_view_cell - sel_box = canvas.table_view_cell_sel_box - - if self._selected_cell == cell and cell.get_visible(): - bg_color = COLOR_SELECTED - else: - bg_color = COLOR_BACKGROUND - sel_box.props.background_color = bg_color.get_int() - - cell.row = prepared_row - - if cell.row is None: - cell_num = y * self.columns + x - - if cell_num < self._model.iter_n_children(None): - row = self._model.get_row((cell_num, ), self.frame) - if row is not None and row != False: - cell.row = row - - if cell.row is None: - cell.set_visible(False) - else: - cell.do_fill_in() - cell.set_visible(True) - - def __enter_notify_event_cb(self, canvas, event, cell): - if not self.hover_selection: - return - - if cell.get_visible(): - sel_box = canvas.table_view_cell_sel_box - sel_box.props.background_color = COLOR_SELECTED.get_int() - - self._selected_cell = cell - - def __leave_notify_event_cb(self, canvas, event): - if not self.hover_selection: - return - - sel_box = canvas.table_view_cell_sel_box - sel_box.props.background_color = COLOR_BACKGROUND.get_int() - - self._selected_cell = None - - def __row_changed_cb(self, model, path, iterator): - y = path[0] / self.columns - x = path[0] % self.columns - - canvas = self.get_visible_cell(y, x) - if canvas is None: - return - - row = self._model.get_row(path) - self._do_fill_in(canvas, y, x, row) diff --git a/src/jarabe/journal/thumbsview.py b/src/jarabe/journal/thumbsview.py deleted file mode 100644 index 4a59ff2..0000000 --- a/src/jarabe/journal/thumbsview.py +++ /dev/null @@ -1,287 +0,0 @@ -# Copyright (C) 2009, Aleksey Lim -# -# 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 -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -import sys -import gobject -import hippo -import pango - -from sugar.graphics import style -from sugar.graphics.icon import CanvasIcon -from sugar.graphics.xocolor import XoColor -from sugar.graphics.palette import CanvasInvoker - -from jarabe.journal.keepicon import KeepIcon -from jarabe.journal.source import Source -from jarabe.journal.objectmodel import ObjectModel -from jarabe.journal.tableview import TableView, TableCell -from jarabe.journal.palettes import ObjectPalette -from jarabe.journal import misc -from jarabe.journal import model - -ROWS = 4 -COLUMNS = 5 -STAR_WIDTH = 30 - - -class ThumbsCell(TableCell, hippo.CanvasBox): - - def __init__(self): - TableCell.__init__(self) - - self._last_uid = None - - hippo.CanvasBox.__init__(self, - orientation=hippo.ORIENTATION_HORIZONTAL, - padding_left=style.DEFAULT_SPACING, - padding_top=style.DEFAULT_SPACING * 2, - spacing=style.DEFAULT_PADDING) - - self.connect('button-release-event', self.__button_release_event_cb) - - # tools column - - tools_box = hippo.CanvasBox( - spacing=style.DEFAULT_PADDING, - orientation=hippo.ORIENTATION_VERTICAL, - box_width=STAR_WIDTH) - self.append(tools_box) - - self.keep = KeepIcon(False) - self.keep.props.size = style.SMALL_ICON_SIZE - self.keep.connect('activated', self.__star_activated_cb) - tools_box.append(self.keep) - - details = DetailsIcon( - size=style.SMALL_ICON_SIZE) - details.connect('activated', self.__detail_activated_cb) - tools_box.append(details) - - # main column - - main_box = hippo.CanvasBox( - orientation=hippo.ORIENTATION_VERTICAL) - self.append(main_box, hippo.PACK_EXPAND) - - self.allocation_box = hippo.CanvasBox() - main_box.append(self.allocation_box, hippo.PACK_EXPAND) - - self.activity_box = hippo.CanvasBox( - border=style.LINE_WIDTH, - border_color=style.COLOR_BUTTON_GREY.get_int()) - self.allocation_box.append(self.activity_box, hippo.PACK_FIXED) - - self.thumb = ThumbCanvas(self, - xalign=hippo.ALIGNMENT_START, - yalign=hippo.ALIGNMENT_START) - self.thumb.connect('detail-clicked', self.__detail_clicked_cb) - self.activity_box.append(self.thumb, hippo.PACK_FIXED) - - self.activity_icon = ActivityIcon(self, - xalign=hippo.ALIGNMENT_START, - yalign=hippo.ALIGNMENT_START) - self.activity_icon.connect('detail-clicked', self.__detail_clicked_cb) - self.activity_box.append(self.activity_icon, hippo.PACK_EXPAND) - - self.title = hippo.CanvasText( - padding_top=style.DEFAULT_PADDING, - xalign=hippo.ALIGNMENT_START, - size_mode=hippo.CANVAS_SIZE_ELLIPSIZE_END) - main_box.append(self.title) - - self.date = hippo.CanvasText( - xalign=hippo.ALIGNMENT_START, - size_mode=hippo.CANVAS_SIZE_ELLIPSIZE_END) - main_box.append(self.date) - - def do_fill_in(self): - title_weight = pango.AttrWeight(pango.WEIGHT_BOLD) - title_weight.start_index = 0 - title_weight.end_index = sys.maxint - title_attributes = pango.AttrList() - title_attributes.insert(title_weight) - - self.title.props.attributes = title_attributes - self.title.props.text = self.row[Source.FIELD_TITLE] or '' - - self.date.props.text = self.row[Source.FIELD_MODIFY_TIME] or '' - self.keep.props.keep = int(self.row[Source.FIELD_KEEP] or 0) == 1 - - w, h = self.table.thumb_size - self.activity_box.props.box_width = w - self.activity_box.props.box_height = h - - thumb = self.row[Source.FIELD_THUMB] - - if self._last_uid == self.row[Source.FIELD_UID] and \ - not ObjectModel.FIELD_FETCHED_FLAG in self.row: - # do not blink by preview while re-reading entries - return - else: - self._last_uid = self.row[Source.FIELD_UID] - - if thumb is None: - self.thumb.set_visible(False) - self.activity_icon.set_visible(True) - self.activity_icon.palette = None - self.activity_icon.update_icon() - else: - self.activity_icon.set_visible(False) - self.thumb.set_visible(True) - self.thumb.props.scale_width = w - style.LINE_WIDTH * 2 - self.thumb.props.scale_height = h - style.LINE_WIDTH * 2 - self.thumb.props.image = thumb - self.thumb.palette = None - self.thumb.allocate(w, h, True) - self.activity_box.set_position(self.thumb, - style.LINE_WIDTH, style.LINE_WIDTH) - - def __star_activated_cb(self, keep_button): - self.row.metadata['keep'] = not keep_button.props.keep and 1 or 0 - model.write(self.row.metadata, update_mtime=False) - - def __detail_activated_cb(self, button): - self.table.emit('detail-clicked', self.row[Source.FIELD_UID]) - - def __detail_clicked_cb(self, sender, uid): - self.table.emit('detail-clicked', uid) - - def __button_release_event_cb(self, sender, event): - if not self.table.props.hover_selection: - return False - uid = self.row[Source.FIELD_UID] - self.table.emit('entry-activated', uid) - return False - - -class ActivityCanvas(object): - - def __init__(self, cell): - self._cell = cell - self.connect_after('button-release-event', - self.__button_release_event_cb) - self.palette = None - - def create_palette(self): - if self._cell.table.props.hover_selection: - return - palette = ObjectPalette(self._cell.row.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) - - def __button_release_event_cb(self, button, event): - misc.resume(self._cell.row.metadata) - return True - - -class ActivityIcon(ActivityCanvas, CanvasIcon): - - __gsignals__ = { - 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([str])), - } - - def __init__(self, cell, **kwargs): - CanvasIcon.__init__(self, **kwargs) - ActivityCanvas.__init__(self, cell) - - def update_icon(self): - metadata = self._cell.row.metadata - self.props.file_name = misc.get_icon_name(metadata) - - if misc.is_activity_bundle(metadata): - self.props.fill_color = style.COLOR_TRANSPARENT.get_svg() - self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg() - else: - if 'icon-color' in metadata and metadata['icon-color']: - self.props.xo_color = XoColor(metadata['icon-color']) - - -class ThumbCanvas(ActivityCanvas, hippo.CanvasImage): - - __gsignals__ = { - 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([str])), - } - - def __init__(self, cell, **kwargs): - hippo.CanvasImage.__init__(self, **kwargs) - ActivityCanvas.__init__(self, cell) - - self._palette_invoker = CanvasInvoker() - self._palette_invoker.attach(self) - self.connect('destroy', self.__destroy_cb) - - def __destroy_cb(self, icon): - if self._palette_invoker is not None: - self._palette_invoker.detach() - - -class DetailsIcon(CanvasIcon): - - def __init__(self, **kwargs): - CanvasIcon.__init__(self, **kwargs) - self.props.icon_name = 'go-right' - self.props.stroke_color = style.COLOR_TRANSPARENT.get_svg() - self.connect('motion-notify-event', self.__motion_notify_event_cb) - self.connect('activated', self.__on_leave_cb) - self.__on_leave_cb(None) - - def __on_leave_cb(self, button): - self.props.fill_color = style.COLOR_BUTTON_GREY.get_svg() - - def __motion_notify_event_cb(self, icon, event): - if event.detail == hippo.MOTION_DETAIL_ENTER: - icon.props.fill_color = style.COLOR_BLACK.get_svg() - elif event.detail == hippo.MOTION_DETAIL_LEAVE: - self.__on_leave_cb(None) - - -class ThumbsView(TableView): - - __gsignals__ = { - 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([object])), - 'entry-activated': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([str])), - } - - def __init__(self): - TableView.__init__(self, ThumbsCell, ROWS, COLUMNS) - self.thumb_size = (0, 0) - - def do_size_allocate(self, allocation): - text_layout = self.create_pango_layout('W') - w = allocation.width / COLUMNS - STAR_WIDTH - style.DEFAULT_SPACING - \ - style.DEFAULT_PADDING - style.LINE_WIDTH * 2 - h = allocation.height / ROWS - text_layout.get_pixel_size()[1] * 2 - \ - style.DEFAULT_SPACING * 2 - style.DEFAULT_PADDING - \ - style.LINE_WIDTH * 2 - - # keep thumb size 4:3 - if w / 4. * 3. > h: - w = int(h / 3. * 4.) - else: - h = int(w / 4. * 3.) - - self.thumb_size = (max(0, w), max(0, h)) - - TableView.do_size_allocate(self, allocation) -- cgit v0.9.1