From f376c3c8b9376be00b38fed588e0d7f157fa8651 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Sat, 22 Aug 2009 02:01:28 +0000 Subject: Implement ThumbsView --- (limited to 'src') diff --git a/src/jarabe/journal/browse/lazymodel.py b/src/jarabe/journal/browse/lazymodel.py index b11f02f..5f2428e 100644 --- a/src/jarabe/journal/browse/lazymodel.py +++ b/src/jarabe/journal/browse/lazymodel.py @@ -219,7 +219,7 @@ class LazyModel(gtk.GenericTreeModel): def fetch(): row = self._source.get_row(offset) - if not row: + 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 @@ -343,7 +343,7 @@ class Row: self.model = model self.iter = iter self.path = path - self.object = object + self.metadata = object self.row = [None] * len(model._columns_by_name) self._calced_row = {} @@ -365,7 +365,7 @@ class Row: self._calced_row[key] = value return value else: - return self.object[key] + return self.metadata[key] def __setitem__(self, key, value): if isinstance(key, int): @@ -374,7 +374,7 @@ class Row: else: self._calced_row[key] = value else: - self.object[key] = value + self.metadata[key] = value def __delitem__(self, key): if isinstance(key, int): @@ -383,13 +383,13 @@ class Row: else: del self._calced_row[key] else: - del self.object[key] + del self.metadata[key] def __contains__(self, key): if isinstance(key, int): return key < len(self.row) else: - return self.object.__contains__(key) + return self.metadata.__contains__(key) def has_key(self, key): return self.__contains__(key) diff --git a/src/jarabe/journal/browse/tableview.py b/src/jarabe/journal/browse/tableview.py index 87acdee..362c6ba 100644 --- a/src/jarabe/journal/browse/tableview.py +++ b/src/jarabe/journal/browse/tableview.py @@ -16,7 +16,6 @@ import gtk import hippo -import math import gobject import logging @@ -30,11 +29,9 @@ COLOR_SELECTED = style.COLOR_TEXT_FIELD_GREY class TableCell: def __init__(self): self.row = None + self.tree = None - def fillin(self): - pass - - def on_release(self, widget, event): + def do_fill_in(self): pass class TableView(SmoothTable): @@ -93,12 +90,11 @@ class TableView(SmoothTable): canvas.set_root(sel_box) cell = cell_class() + cell.tree = 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.connect('button-release-event', self.__button_release_event_cb, - cell) canvas.table_view_cell_sel_box = sel_box canvas.table_view_cell = cell @@ -129,12 +125,9 @@ class TableView(SmoothTable): if cell.row is None: cell.set_visible(False) else: - cell.fillin() + cell.do_fill_in() cell.set_visible(True) - def __button_release_event_cb(self, widget, event, cell): - cell.on_release(widget, event) - def __enter_notify_event_cb(self, canvas, event, cell): if not self.hover_selection: return diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py index 88c0b41..9864891 100644 --- a/src/jarabe/journal/expandedentry.py +++ b/src/jarabe/journal/expandedentry.py @@ -16,10 +16,8 @@ import logging from gettext import gettext as _ -import StringIO import hippo -import cairo import gobject import gtk import cjson @@ -172,29 +170,10 @@ class ExpandedEntry(hippo.CanvasBox): height = style.zoom(240) box = hippo.CanvasBox() - 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 + preview = misc.load_preview(self._metadata) - if has_preview: - preview_box = hippo.CanvasImage(image=surface, + if preview is not None: + preview_box = hippo.CanvasImage(image=preview, border=style.LINE_WIDTH, border_color=style.COLOR_BUTTON_GREY.get_int(), xalign=hippo.ALIGNMENT_CENTER, @@ -211,16 +190,18 @@ 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/listview.py b/src/jarabe/journal/listview.py index 36cd06a..12a3f23 100644 --- a/src/jarabe/journal/listview.py +++ b/src/jarabe/journal/listview.py @@ -251,10 +251,14 @@ class BaseListView(gtk.Bin): model.write(metadata, update_mtime=False) def update_dates(self): + if not self.flags() & gtk.REALIZED: + return + logging.debug('ListView.update_dates') visible_range = self.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) diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py index e6e5abf..0fe4b1e 100644 --- a/src/jarabe/journal/misc.py +++ b/src/jarabe/journal/misc.py @@ -18,6 +18,8 @@ import logging import time import traceback import os +import StringIO +import cairo from gettext import gettext as _ import gio @@ -232,3 +234,23 @@ 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 15259bb..d8ce491 100644 --- a/src/jarabe/journal/model.py +++ b/src/jarabe/journal/model.py @@ -65,7 +65,9 @@ class _Cache(object): def remove_all(self, entries): for uid in [entry['uid'] for entry in entries]: - obj = self._dict[uid] + obj = self._dict.get(uid) + if obj is None: + continue self._array.remove(obj) del self._dict[uid] @@ -394,16 +396,24 @@ def _get_mount_point(path): else: dir_path = dir_path.rsplit(os.sep, 1)[0] -def get(object_id): +def get(object_id, reply_cb=None): """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) - else: + + elif reply_cb is None: 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/objectmodel.py b/src/jarabe/journal/objectmodel.py new file mode 100644 index 0000000..621f261 --- /dev/null +++ b/src/jarabe/journal/objectmodel.py @@ -0,0 +1,76 @@ +# 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 cairo +import gobject +import logging + +from sugar import util + +from jarabe.journal import misc +from jarabe.journal.source import Source +from jarabe.journal.browse.lazymodel import LazyModel + +class ObjectModel(LazyModel): + 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: + row = self.fetch_metadata(row) + if row is not None: + return + return None + + return None + + def fetch_metadata(self, row, force=False): + if row.has_key(Source.FIELD_FETCHED): + return row + + if row not in self._fetch_queue: + self._fetch_queue.append(row) + if len(self._fetch_queue) == 1: + gobject.idle_add(self.__idle_cb, force) + + def __idle_cb(self, force): + while len(self._fetch_queue): + row = self._fetch_queue[0] + if force or 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[Source.FIELD_THUMB] = misc.load_preview(metadata) + row.metadata.update(metadata) + row[Source.FIELD_FETCHED] = True + self.emit('row-changed', row.path, row.iter) + + if len(self._fetch_queue): + self.__idle_cb(False) diff --git a/src/jarabe/journal/objectview.py b/src/jarabe/journal/objectview.py index fca232b..9aaa108 100644 --- a/src/jarabe/journal/objectview.py +++ b/src/jarabe/journal/objectview.py @@ -29,9 +29,8 @@ 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.browse.lazymodel import LazyModel -from jarabe.journal.browse.localsource import LocalSource -from jarabe.journal.browse.source import * +from jarabe.journal.objectmodel import ObjectModel +from jarabe.journal.source import Source, LocalSource UPDATE_INTERVAL = 300 @@ -65,7 +64,7 @@ class ObjectsView(gtk.Bin): self._result_set = None self._progress_bar = None self._last_progress_bar_pulse = None - self._model = LazyModel(FIELDS_LIST) + self._model = ObjectModel() self._view_widgets = [] self._view = VIEW_LIST @@ -123,7 +122,7 @@ class ObjectsView(gtk.Bin): # TODO in 0.88 VIEW_LIST will use lazymodel self._view_widgets[VIEW_LIST].view.update_dates() return - self._model.recalc([FIELD_MODIFY_TIME]) + self._model.recalc([Source.FIELD_MODIFY_TIME]) def change_view(self, view): self._view = view @@ -302,7 +301,7 @@ class ObjectsView(gtk.Bin): return False path, column_, x_, y_ = pos - uid = tree_view.get_model()[path][FIELD_UID] + uid = tree_view.get_model()[path][Source.FIELD_UID] self.emit('entry-activated', uid) return False diff --git a/src/jarabe/journal/source.py b/src/jarabe/journal/source.py new file mode 100644 index 0000000..72a963b --- /dev/null +++ b/src/jarabe/journal/source.py @@ -0,0 +1,94 @@ +# 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 cairo +from gobject import property, GObject, SIGNAL_RUN_FIRST, TYPE_PYOBJECT + +from sugar import util + +from jarabe.journal.browse.lazymodel import LazyModel +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 + + FIELD_FETCHED = 50 + + 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/thumbsview.py b/src/jarabe/journal/thumbsview.py index 920bf2c..3debb94 100644 --- a/src/jarabe/journal/thumbsview.py +++ b/src/jarabe/journal/thumbsview.py @@ -19,21 +19,195 @@ import gobject import logging import hippo -from jarabe.journal.browse.lazymodel import Source +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.objectmodel import Source from jarabe.journal.browse.tableview import TableView, TableCell -from jarabe.journal.browse.source import * +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) - hippo.CanvasBox.__init__(self, orientation=hippo.ORIENTATION_VERTICAL) - self.label = hippo.CanvasText( + hippo.CanvasBox.__init__(self, + orientation=hippo.ORIENTATION_VERTICAL, + padding_left=style.DEFAULT_SPACING, + padding_top=style.DEFAULT_SPACING, + spacing=style.DEFAULT_PADDING) + + self.preview_box = hippo.CanvasBox( + spacing=style.DEFAULT_PADDING, + orientation=hippo.ORIENTATION_HORIZONTAL) + self.append(self.preview_box, hippo.PACK_EXPAND) + + star_box = hippo.CanvasBox( + orientation=hippo.ORIENTATION_VERTICAL, + box_width=STAR_WIDTH) + self.preview_box.append(star_box) + + self.keep = KeepIcon(False) + self.keep.props.size = style.SMALL_ICON_SIZE + self.keep.connect('activated', self.__star_activated_cb) + star_box.append(self.keep) + + self.activity_box = hippo.CanvasBox() + self.preview_box.append(self.activity_box, hippo.PACK_EXPAND) + + self.thumb = ThumbCanvas( + border=style.LINE_WIDTH, + border_color=style.COLOR_BUTTON_GREY.get_int(), + xalign=hippo.ALIGNMENT_CENTER, + yalign=hippo.ALIGNMENT_CENTER) + self.activity_box.append(self.thumb, hippo.PACK_EXPAND) + + self.activity_icon = ActivityIcon( + border=style.LINE_WIDTH, + border_color=style.COLOR_BUTTON_GREY.get_int(), + xalign=hippo.ALIGNMENT_CENTER, + yalign=hippo.ALIGNMENT_CENTER) + self.activity_box.append(self.activity_icon, hippo.PACK_EXPAND) + + title_box = hippo.CanvasBox( + orientation=hippo.ORIENTATION_HORIZONTAL) + self.append(title_box) + + tool_box = hippo.CanvasBox( + orientation=hippo.ORIENTATION_VERTICAL, + box_width=STAR_WIDTH) + title_box.append(tool_box) + + details = DetailsIcon( + size=style.SMALL_ICON_SIZE) + details.connect('activated', self.__detail_activated_cb) + tool_box.append(details) + + text_box = hippo.CanvasBox( + orientation=hippo.ORIENTATION_VERTICAL) + title_box.append(text_box) + + self.title = hippo.CanvasText( + xalign=hippo.ALIGNMENT_START, size_mode=hippo.CANVAS_SIZE_ELLIPSIZE_END) - self.append(self.label) + text_box.append(self.title) + + self.date = hippo.CanvasText( + xalign=hippo.ALIGNMENT_START, + size_mode=hippo.CANVAS_SIZE_ELLIPSIZE_END) + text_box.append(self.date) + + def do_fill_in(self): + 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 + + thumb = self.row[Source.FIELD_THUMB] + + w, h = self.activity_box.get_allocation() + if w / 4. * 3. > h: + w = int(h / 3. * 4.) + else: + h = int(w / 4. * 3.) + + if thumb is None: + self.thumb.set_visible(False) + self.activity_icon.set_visible(True) + self.activity_icon.set_metadata(self.row.metadata) + self.activity_icon.allocate(w, h, True) + else: + self.activity_icon.set_visible(False) + self.thumb.set_visible(True) + self.thumb.props.scale_width = w + self.thumb.props.scale_height = h + self.thumb.props.image = thumb + self.thumb.set_metadata(self.row.metadata) + self.thumb.allocate(w, h, True) + + 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.tree.emit('detail-clicked', self.row[Source.FIELD_UID]) + +class ActivityCanvas: + def __init__(self): + self._metadata = None + self.connect_after('button-release-event', + self.__button_release_event_cb) + + def set_metadata(self, metadata): + self.palette = None + self._metadata = metadata + + def create_palette(self): + if self._metadata is None: + return None + + palette = ObjectPalette(self._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, foo): + misc.resume(self._metadata) + return True + +class ActivityIcon(ActivityCanvas, CanvasIcon): + def __init__(self, **kwargs): + CanvasIcon.__init__(self, **kwargs) + ActivityCanvas.__init__(self) + + def set_metadata(self, metadata): + ActivityCanvas.set_metadata(self, 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 metadata.has_key('icon-color') and metadata['icon-color']: + self.props.xo_color = XoColor(self._metadata['icon-color']) + +class ThumbCanvas(ActivityCanvas, hippo.CanvasImage): + def __init__(self, **kwargs): + hippo.CanvasImage.__init__(self, **kwargs) + ActivityCanvas.__init__(self) + + 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.fill_color = style.COLOR_BUTTON_GREY.get_svg() + self.props.stroke_color = style.COLOR_TRANSPARENT.get_svg() + self.connect('motion-notify-event', self.__motion_notify_event_cb) - def fillin(self): - self.label.props.text = self.row[FIELD_TITLE] or '' + 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: + icon.props.fill_color = style.COLOR_BUTTON_GREY.get_svg() class ThumbsView(TableView): __gsignals__ = { @@ -43,4 +217,4 @@ class ThumbsView(TableView): } def __init__(self): - TableView.__init__(self, ThumbsCell, 3, 3) + TableView.__init__(self, ThumbsCell, ROWS, COLUMNS) -- cgit v0.9.1