From 4d9e3297eff3d60ee0f3ae71d9641cf8325d2575 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Thu, 21 Jan 2010 16:48:24 +0000 Subject: Implement thumbs --- diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py index 2a3ead0..d5f095b 100644 --- a/src/jarabe/journal/expandedentry.py +++ b/src/jarabe/journal/expandedentry.py @@ -140,7 +140,7 @@ class ExpandedEntry(hippo.CanvasBox): return self._metadata = metadata - self._keep_icon.check_out(metadata) + self._keep_icon.fill_in(metadata) self._icon = self._create_icon() self._icon_box.clear() diff --git a/src/jarabe/journal/homogenetable.py b/src/jarabe/journal/homogenetable.py index 64a22ec..43ff4fe 100644 --- a/src/jarabe/journal/homogenetable.py +++ b/src/jarabe/journal/homogenetable.py @@ -45,6 +45,7 @@ class VHomogeneTable(gtk.Container): 'set-scroll-adjustments': (gobject.SIGNAL_RUN_FIRST, None, [gtk.Adjustment, gtk.Adjustment]), 'cursor-changed': (gobject.SIGNAL_RUN_FIRST, None, [object]), + 'frame-scrolled': (gobject.SIGNAL_RUN_FIRST, None, []), } def __init__(self, cell_class, **kwargs): @@ -64,6 +65,7 @@ class VHomogeneTable(gtk.Container): self._selected_index = None self._editable = True self._pending_allocate = None + self._frame_range = None gtk.Container.__init__(self, **kwargs) @@ -115,9 +117,10 @@ class VHomogeneTable(gtk.Container): def set_cell_count(self, count): if self._cell_count == count: return + self._cell_count = count - self.refill() self._setup_adjustment(dry_run=False) + self._resize_table() """Number of virtual cells Defines maximal number of virtual rows, the minimal has being described @@ -149,6 +152,21 @@ class VHomogeneTable(gtk.Container): """Selected cell""" cursor = gobject.property(getter=get_cursor, setter=set_cursor) + def get_frame_range(self): + if self._frame_range is None: + return xrange(0) + else: + begin, end = self._frame_range + return xrange(begin, end + 1) + + """Range of visible cells""" + frame_range = gobject.property(getter=get_frame_range) + + @property + def frame_cells(self): + for cell in self._cell_cache: + yield cell.widget + def get_editable(self): return self._editable @@ -213,12 +231,12 @@ class VHomogeneTable(gtk.Container): self._pos_changed() - def refill(self): + def refill(self, cells=None): """Force VHomogeneTable widget to run filling method for all cells""" for cell in self._cell_cache: - cell.invalidate_pos() - cell.index = -1 - self._allocate_rows(force=True) + if cells is None or cell.index in cells: + cell.index = -1 + self._allocate_rows(force=False) # gtk.Widget overrides @@ -360,16 +378,6 @@ class VHomogeneTable(gtk.Container): return True @property - def _frame_range(self): - if self._empty: - return xrange(0) - else: - first = self._pos_y / self._cell_height * self._column_count - last = int(math.ceil(float(self._pos_y + self._page) / \ - self._cell_height) * self._column_count) - return xrange(first, min(last, self.cell_count)) - - @property def _empty(self): return not self._row_cache @@ -501,6 +509,7 @@ class VHomogeneTable(gtk.Container): for row in self._row_cache: for cell in row: cell.invalidate_pos() + cell.index = -1 self._cell_height = height / self._frame_row_count self._setup_adjustment(dry_run=True) @@ -561,6 +570,7 @@ class VHomogeneTable(gtk.Container): spare_rows = [] visible_rows = [] + frame_rows = [] page_end = pos + self._page if force: @@ -584,6 +594,7 @@ class VHomogeneTable(gtk.Container): row = spare_rows.pop() self._allocate_cells(row, cell_y) cell_y = cell_y + self._cell_height + frame_rows.append(row) # visible_rows could not be continuous # lets try to add spare rows to missed points @@ -591,16 +602,26 @@ class VHomogeneTable(gtk.Container): for i in visible_rows: cell = i.row[0].widget.allocation try_insert_spare_row(cell_y, cell.y) + self._allocate_cells(i.row, cell.y) cell_y = cell.y + cell.height + frame_rows.append(i.row) try_insert_spare_row(cell_y, page_end) - if self.editing and self._selected_index not in self._frame_range: - self.editing = False - self._bin_window.move(0, int(-pos)) self._bin_window.process_updates(True) + if frame_rows: + frame_range = (frame_rows[0][0].index, frame_rows[-1][-1].index) + else: + frame_range = None + if frame_range != self._frame_range: + self._frame_range = frame_range + self.emit('frame-scrolled') + + if self.editing and self._selected_index not in self.frame_range: + self.editing = False + def __adjustment_value_changed_cb(self, adjustment): self._allocate_rows(force=False) diff --git a/src/jarabe/journal/homogeneview.py b/src/jarabe/journal/homogeneview.py index 764ecfe..3176e76 100644 --- a/src/jarabe/journal/homogeneview.py +++ b/src/jarabe/journal/homogeneview.py @@ -30,13 +30,14 @@ class Cell(gtk.EventBox): gtk.EventBox.__init__(self) self.select(False) - def do_fill_in_cell_content(self, table, metadata): + def do_fill_in_cell_content(self, table, offset, metadata): # needs to be overriden pass def do_fill_in(self, table, cell_index): - table.result_set.seek(cell_index) - self.do_fill_in_cell_content(table, table.result_set.read()) + result_set = table.get_result_set() + result_set.seek(cell_index) + self.do_fill_in_cell_content(table, cell_index, result_set.read()) if table.hover_selection: self.select(table.cursor == cell_index) @@ -82,8 +83,6 @@ class HomogeneView(VHomogeneTable): else: self.cell_count = result_set_length - result_set = property(get_result_set, set_result_set) - def __cursor_changed_cb(self, table, old_cursor): if not self.hover_selection: return diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py index f4406b2..a820ca4 100644 --- a/src/jarabe/journal/listview.py +++ b/src/jarabe/journal/listview.py @@ -56,9 +56,9 @@ class _Cell(Cell): self.show_all() - def do_fill_in_cell_content(self, table, metadata): + def do_fill_in_cell_content(self, table, offset, metadata): for i in self._row.get_children(): - i.check_out(metadata) + i.fill_in(metadata) class ListView(HomogeneView): diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py index 85b4f46..b512fb6 100644 --- a/src/jarabe/journal/model.py +++ b/src/jarabe/journal/model.py @@ -288,7 +288,8 @@ class InplaceResultSet(BaseResultSet): metadata['mountpoint'] = self._mount_point entries.append(metadata) - logging.debug('InplaceResultSet.find took %f s.', time.time() - t) + logging.debug('InplaceResultSet.find took %f s. for %s entries', + time.time() - t, total_count) return entries, total_count @@ -408,16 +409,33 @@ 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: + def apply_cb(metadata): + metadata['mountpoint'] = '/' + reply_cb(metadata) + + def error_cb(e): + logging.error('Cannot get object %s', object_id) + reply_cb(None) + + _get_datastore().get_properties(object_id, byte_arrays=True, + reply_handler=reply_cb, + error_handler=lambda e: reply_cb(None)) + return None + + if reply_cb is not None: + reply_cb(metadata) + return metadata def get_file(object_id): diff --git a/src/jarabe/journal/preview.py b/src/jarabe/journal/preview.py new file mode 100644 index 0000000..1670884 --- /dev/null +++ b/src/jarabe/journal/preview.py @@ -0,0 +1,216 @@ +# Copyright (C) 2010, 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 +import os +import math + +import gtk +import gio +import gobject + +from sugar import dispatch +from sugar.util import LRU +from sugar.graphics import style + +from jarabe.journal import model + + +fetched = dispatch.Signal() + +THUMB_WIDTH = style.zoom(240) +THUMB_HEIGHT = style.zoom(180) + +_CHUNK_SIZE = 1024 * 10 # 10K +_MAX_FILESIZE = 1024 * 1024 * 10 # 10M + +_fetch_queue = [] + + +def fetch(offset, metadata): + entry = _CacheEntry(offset, metadata) + + if entry not in _fetch_queue: + _fetch_queue.append(entry) + if len(_fetch_queue) == 1: + gobject.idle_add(_process_queue) + +def discard_queue(visible_range): + new_queue = [] + + for i in _fetch_queue: + if i.offset in visible_range: + new_queue.append(i) + + global _fetch_queue + _fetch_queue = new_queue + +def _process_queue(): + while len(_fetch_queue): + entry = _fetch_queue[0] + + logging.debug('Loading preview for %s', entry.uid) + + if entry.uid.startswith('/'): + if not os.path.isfile(entry.uid): + logging.warning('Preview %s is not a file', entry.uid) + _commit(entry, None) + elif os.path.getsize(entry.uid) > _MAX_FILESIZE: + logging.debug('Preview %s is too big to load', entry.uid) + _commit(entry, None) + else: + _AsyncLoader(entry) + else: + _load_props(entry) + + break + + return False + +def _commit(entry, pixbuf): + if not _fetch_queue or _fetch_queue[0] != entry: + logging.debug('Discard %r preview', entry.uid) + else: + del _fetch_queue[0] + + if pixbuf is None: + logging.debug('Empty preview for %s', entry.uid) + else: + logging.debug('Ready preview for %s', entry.uid) + fetched.send(None, offset=entry.offset, pixbuf=pixbuf) + + if len(_fetch_queue): + gobject.idle_add(_process_queue) + +def _load_preview(entry, preview): + if not preview: + logging.debug('Empty preview for %s', entry.uid) + _commit(entry, None) + return + + if preview[1:4] != 'PNG': + # TODO: We are close to be able to drop this. + import base64 + preview = base64.b64decode(preview) + + loader = gtk.gdk.PixbufLoader() + loader.connect('size-prepared', _size_prepared_cb) + + try: + loader.write(preview) + except Exception: + logging.exception('Can not load preview from metadata for %s', + entry.uid) + finally: + loader.close() + + pixbuf = loader.get_pixbuf() + if pixbuf is None: + _commit(entry, None) + else: + _commit(entry, pixbuf) + +def _load_props(entry): + + def reply_cb(props): + if props is None: + _commit(entry, None) + else: + _load_preview(entry, props.get('preview')) + + model.get(entry.uid, reply_cb) + +def _size_prepared_cb(loader, width, height): + dest_width = THUMB_WIDTH + dest_height = THUMB_HEIGHT + + if width == dest_width and height == dest_height: + return + + ratio_width = float(dest_width) / width + ratio_height = float(dest_height) / height + ratio = min(ratio_width, ratio_height) + + # preserve original ration + if ratio_width != ratio: + dest_width = int(math.ceil(width * ratio)) + elif ratio_height != ratio: + dest_height = int(math.ceil(height * ratio)) + + loader.set_size(dest_width, dest_height) + + +class _AsyncLoader(object): + + def __init__(self, entry): + self._entry = entry + + self._loader = gtk.gdk.PixbufLoader() + self._loader.connect('size-prepared', _size_prepared_cb) + + self._stream = None + self._file = gio.File(entry.uid) + self._file.read_async(self.__file_read_async_cb) + + def __file_read_async_cb(self, input_file, result): + try: + self._stream = self._file.read_finish(result) + except Exception: + logging.exception('Can not read preview for %s', self._entry.uid) + _commit(self._entry, None) + return + + self._stream.read_async(_CHUNK_SIZE, self.__stream_read_async_cb, + gobject.PRIORITY_LOW) + + def __stream_read_async_cb(self, input_stream, result): + data = self._stream.read_finish(result) + + if data and self._process_loader(self._loader.write, data): + self._stream.read_async(_CHUNK_SIZE, self.__stream_read_async_cb, + gobject.PRIORITY_LOW) + return + + if data is None: + logging.warning('Bad preview data from %s', self._entry.uid) + + self._stream.close() + + if self._process_loader(self._loader.close): + _commit(self._entry, self._loader.get_pixbuf()) + else: + _commit(self._entry, None) + + def _process_loader(self, method, *args): + try: + method(*args) + except Exception, e: + logging.debug('Can not process preview for %s: %r', + self._entry.uid, e) + return False + else: + return True + +class _CacheEntry(object): + uid = None + offset = None + + def __init__(self, offset, metadata): + self.uid = metadata['uid'] + self.offset = offset + + def __cmp__(self, other): + return cmp((self.uid, self.offset), (other.uid, other.offset)) diff --git a/src/jarabe/journal/thumbsview.py b/src/jarabe/journal/thumbsview.py index f2db248..b96698a 100644 --- a/src/jarabe/journal/thumbsview.py +++ b/src/jarabe/journal/thumbsview.py @@ -21,19 +21,10 @@ import logging from jarabe.journal.homogeneview import HomogeneView from jarabe.journal.homogeneview import Cell from jarabe.journal.widgets import * +from jarabe.journal import preview -TOOLBAR_WIDTH = 20 - -THUMB_WIDTH = 240 -THUMB_HEIGHT = 180 - -TEXT_HEIGHT = gtk.EventBox().create_pango_layout('W').get_pixel_size()[1] - -CELL_WIDTH = THUMB_WIDTH + TOOLBAR_WIDTH + style.DEFAULT_PADDING + \ - style.DEFAULT_SPACING -CELL_HEIGHT = THUMB_HEIGHT + TEXT_HEIGHT * 3 + style.DEFAULT_PADDING * 3 + \ - style.DEFAULT_SPACING +_TEXT_HEIGHT = gtk.EventBox().create_pango_layout('W').get_pixel_size()[1] class _Cell(Cell): @@ -41,6 +32,8 @@ class _Cell(Cell): def __init__(self): Cell.__init__(self) + self._offset = None + cell = gtk.HBox() self.add(cell) @@ -49,43 +42,70 @@ class _Cell(Cell): toolbar = gtk.VBox() cell.pack_start(toolbar, expand=False) - self._keep = KeepIcon( - box_width=style.GRID_CELL_SIZE) + self._keep = KeepIcon() toolbar.pack_start(self._keep, expand=False) self._details = DetailsIcon() toolbar.pack_start(self._details, expand=False) - # thumb + # main main = gtk.VBox() cell.pack_end(main) - #thumb = Thumb() - #main.pack_end(thumb) + self._icon = ObjectIcon( + border=style.LINE_WIDTH, + border_color=style.COLOR_PANEL_GREY.get_int(), + box_width=preview.THUMB_WIDTH, + box_height=preview.THUMB_HEIGHT) + self._icon.show() - # text + self._thumb = Thumb() + self._thumb.show() - text = gtk.VBox() - main.pack_end(text, expand=False) + self._thumb_box = gtk.HBox() + main.pack_start(self._thumb_box, expand=False) self._title = Title( max_line_count=2, xalign=0, yalign=0, xscale=1, yscale=0) - text.pack_start(self._title) + main.pack_start(self._title, expand=False) self._date = Timestamp( xalign=0.0, ellipsize=pango.ELLIPSIZE_END) - text.pack_end(self._date, expand=False) + main.pack_start(self._date, expand=False) self.show_all() - def do_fill_in_cell_content(self, table, metadata): - self._keep.check_out(metadata) - self._details.check_out(metadata) - self._title.check_out(metadata) - self._date.check_out(metadata) + def discard_thumb(self): + self._set_thumb_widget(self._icon) + self._offset = None + + def do_fill_in_cell_content(self, table, offset, metadata): + self._keep.fill_in(metadata) + self._details.fill_in(metadata) + self._title.fill_in(metadata) + self._date.fill_in(metadata) + self._icon.fill_in(metadata) + self._thumb.fill_in(metadata) + + if self._offset != offset: + self.discard_thumb() + preview.fetch(offset, metadata) + else: + self._set_thumb_widget(self._thumb) + + def fill_pixbuf_in(self, offset, pixbuf): + self._offset = offset + self._thumb.image.set_from_pixbuf(pixbuf) + self._set_thumb_widget(self._thumb) + + def _set_thumb_widget(self, widget): + if widget not in self._thumb_box.get_children(): + for child in self._thumb_box.get_children(): + self._thumb_box.remove(child) + self._thumb_box.pack_start(widget, expand=False) class ThumbsView(HomogeneView): @@ -93,9 +113,29 @@ class ThumbsView(HomogeneView): def __init__(self): HomogeneView.__init__(self, _Cell) - def do_size_allocate(self, allocation): - column_count = gtk.gdk.screen_width() / CELL_WIDTH - row_count = gtk.gdk.screen_height() / CELL_HEIGHT - self.frame_size = (row_count, column_count) + cell_width = preview.THUMB_WIDTH + style.SMALL_ICON_SIZE + \ + style.DEFAULT_PADDING + style.DEFAULT_SPACING * 2 + cell_height = preview.THUMB_HEIGHT + _TEXT_HEIGHT * 3 + \ + style.DEFAULT_PADDING * 3 + style.DEFAULT_SPACING + self.cell_size = (cell_width, cell_height) + + self.connect('frame-scrolled', self.__frame_scrolled_cb) + preview.fetched.connect(self.__preview_fetched_cb) + + def set_result_set(self, result_set): + if result_set is self.get_result_set(): + return + + for cell in self.frame_cells: + cell.discard_thumb() + + HomogeneView.set_result_set(self, result_set) + + def __frame_scrolled_cb(self, table): + preview.discard_queue(table.frame_range) - HomogeneView.do_size_allocate(self, allocation) + def __preview_fetched_cb(self, sender, signal, offset, pixbuf): + cell = self.get_cell(offset) + if cell is not None: + cell.fill_pixbuf_in(offset, pixbuf) + self.refill([offset]) diff --git a/src/jarabe/journal/view.py b/src/jarabe/journal/view.py index 61b11dc..f2aa11d 100644 --- a/src/jarabe/journal/view.py +++ b/src/jarabe/journal/view.py @@ -63,6 +63,7 @@ class View(gtk.EventBox): self._current_page = None self._view = None self._last_progress_bar_pulse = None + self._progress_bar = None self._page_ctors = { VIEW_LIST: lambda: self._view_new(ListView), @@ -93,7 +94,7 @@ class View(gtk.EventBox): # change view only if current page is view as well self._page = view self._view = view - self.view.result_set = self._result_set + self.view.set_result_set(self._result_set) view = property(get_view, set_view) @@ -148,7 +149,7 @@ class View(gtk.EventBox): self.refresh() def refresh(self): - logging.debug('View._refresh query %r', self._query) + logging.debug('View.refresh query %r', self._query) if self._result_set is not None: self._result_set.stop() @@ -159,9 +160,6 @@ class View(gtk.EventBox): self._result_set.progress.connect(self.__result_set_progress_cb) self._result_set.setup() - self._page = self._view - self.view.result_set = self._result_set - def __result_set_ready_cb(self, **kwargs): if self._result_set.length == 0: if self._is_query_empty(): @@ -170,7 +168,7 @@ class View(gtk.EventBox): self._page = _MESSAGE_NO_MATCH else: self._page = self._view - self.view.result_set = self._result_set + self.view.set_result_set(self._result_set) def _is_query_empty(self): # FIXME: This is a hack, we shouldn't have to update this every time @@ -188,7 +186,7 @@ class View(gtk.EventBox): self._page = _MESSAGE_PROGRESS if time.time() - self._last_progress_bar_pulse > 0.05: - self.child.pulse() + self._progress_bar.pulse() self._last_progress_bar_pulse = time.time() def _view_new(self, view_class): @@ -205,9 +203,9 @@ class View(gtk.EventBox): def _progress_new(self): alignment = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.5) - progress_bar = gtk.ProgressBar() - progress_bar.props.pulse_step = 0.01 - alignment.add(progress_bar) + self._progress_bar = gtk.ProgressBar() + self._progress_bar.props.pulse_step = 0.01 + alignment.add(self._progress_bar) return alignment diff --git a/src/jarabe/journal/widgets.py b/src/jarabe/journal/widgets.py index 7593986..6febd86 100644 --- a/src/jarabe/journal/widgets.py +++ b/src/jarabe/journal/widgets.py @@ -29,7 +29,8 @@ import simplejson from sugar.graphics import style from sugar.graphics.icon import CanvasIcon from sugar.graphics.xocolor import XoColor -from sugar.graphics.palette import CanvasInvoker +from sugar.graphics.palette import Invoker +from sugar.graphics.palette import WidgetInvoker from sugar.graphics.roundbox import CanvasRoundBox from jarabe.journal.entry import Entry @@ -38,6 +39,7 @@ from jarabe.journal.palettes import ObjectPalette from jarabe.journal import misc from jarabe.journal import model from jarabe.journal import controler +from jarabe.journal import preview class KeepIconCanvas(CanvasIcon): @@ -46,15 +48,15 @@ class KeepIconCanvas(CanvasIcon): size=style.SMALL_ICON_SIZE, **kwargs) - self._metadata = None + self.metadata = None self._prelight = False self._keep_color = None self.connect_after('activated', self.__activated_cb) self.connect('motion-notify-event', self.__motion_notify_event_cb) - def check_out(self, metadata): - self._metadata = metadata + def fill_in(self, metadata): + self.metadata = metadata keep = metadata.get('keep', "") if keep.isdigit(): self._set_keep(int(keep)) @@ -96,7 +98,7 @@ class KeepIconCanvas(CanvasIcon): self.props.xo_color = self._keep_color def __activated_cb(self, icon): - if not model.is_editable(self._metadata): + if not model.is_editable(self.metadata): return if self._keep_color is None: @@ -104,8 +106,8 @@ class KeepIconCanvas(CanvasIcon): else: keep = 0 - self._metadata['keep'] = keep - model.write(self._metadata, update_mtime=False) + self.metadata['keep'] = keep + model.write(self.metadata, update_mtime=False) self._set_keep(keep) @@ -116,19 +118,15 @@ def KeepIcon(**kwargs): class _Launcher(object): - def __init__(self, launching, detail): + def __init__(self, detail): self.metadata = None self._detail = detail - self._launching = launching - if launching: - self.connect_after('button-release-event', - self.__button_release_event_cb) + self.connect_after('button-release-event', + self.__button_release_event_cb) def create_palette(self): - if not self._launching or self.metadata is None: - return - else: + if self.metadata is not None: return ObjectPalette(self.metadata, detail=self._detail) def __button_release_event_cb(self, button, event): @@ -139,12 +137,13 @@ class _Launcher(object): class ObjectIconCanvas(_Launcher, CanvasIcon): - def __init__(self, launching=True, detail=True, **kwargs): + def __init__(self, detail=True, **kwargs): CanvasIcon.__init__(self, **kwargs) - _Launcher.__init__(self, launching, detail) + _Launcher.__init__(self, detail) - def check_out(self, metadata): + def fill_in(self, metadata): self.metadata = metadata + self.palette = None self.props.file_name = misc.get_icon_name(metadata) @@ -164,26 +163,26 @@ class Title(gtk.Alignment): def __init__(self, max_line_count=1, **kwargs): gtk.Alignment.__init__(self, **kwargs) - self._metadata = None + self.metadata = None self._entry = Entry(max_line_count=max_line_count) self.add(self._entry) self._entry.connect_after('focus-out-event', self.__focus_out_event_cb) - def check_out(self, metadata): - self._metadata = metadata + def fill_in(self, metadata): + self.metadata = metadata self._entry.props.text = metadata.get('title', _('Untitled')) self._entry.props.editable = model.is_editable(metadata) def __focus_out_event_cb(self, widget, event): - old_title = self._metadata.get('title', None) + old_title = self.metadata.get('title', None) new_title = self._entry.props.text if old_title != new_title: - self._metadata['title'] = new_title - self._metadata['title_set_by_user'] = '1' - model.write(self._metadata, update_mtime=False) + self.metadata['title'] = new_title + self.metadata['title_set_by_user'] = '1' + model.write(self.metadata, update_mtime=False) class Buddies(gtk.Alignment): @@ -201,7 +200,7 @@ class Buddies(gtk.Alignment): self._buddies = gtk.HBox() self._buddies.show() - def check_out(self, metadata): + def fill_in(self, metadata): if self.child is not None: self.remove(self.child) @@ -247,7 +246,7 @@ class Timestamp(gtk.Label): def __init__(self, **kwargs): gobject.GObject.__init__(self, **kwargs) - def check_out(self, metadata): + def fill_in(self, metadata): self.props.label = misc.get_date(metadata) @@ -260,22 +259,22 @@ class DetailsIconCanvas(CanvasIcon): size=style.SMALL_ICON_SIZE, stroke_color=style.COLOR_TRANSPARENT.get_svg()) - self._metadata = None + self.metadata = None self.connect('motion-notify-event', self.__motion_notify_event_cb) self.connect_after('activated', self.__activated_cb) self._set_leave_color() - def check_out(self, metadata): - self._metadata = metadata + def fill_in(self, metadata): + self.metadata = metadata def _set_leave_color(self): self.props.fill_color = style.COLOR_BUTTON_GREY.get_svg() def __activated_cb(self, button): self._set_leave_color() - controler.objects.emit('detail-clicked', self._metadata['uid']) + controler.objects.emit('detail-clicked', self.metadata['uid']) def __motion_notify_event_cb(self, icon, event): if event.detail == hippo.MOTION_DETAIL_ENTER: @@ -288,19 +287,45 @@ def DetailsIcon(**kwargs): return _CanvasToWidget(DetailsIconCanvas, **kwargs) -class ThumbCanvas(_Launcher, hippo.CanvasWidget): +class Thumb(_Launcher, gtk.EventBox): - def __init__(self, cell, **kwargs): - hippo.CanvasWidget.__init__(self, **kwargs) - _Launcher.__init__(self, cell) + def __init__(self, detail=True): + gtk.EventBox.__init__(self) + _Launcher.__init__(self, detail) - self._palette_invoker = CanvasInvoker() - self._palette_invoker.attach(self) + self.modify_fg(gtk.STATE_NORMAL, + style.COLOR_PANEL_GREY.get_gdk_color()) + self.modify_bg(gtk.STATE_NORMAL, + style.COLOR_WHITE.get_gdk_color()) + + self.add_events(gtk.gdk.BUTTON_PRESS_MASK | \ + gtk.gdk.BUTTON_RELEASE_MASK | \ + gtk.gdk.LEAVE_NOTIFY_MASK | \ + gtk.gdk.ENTER_NOTIFY_MASK) + self.set_size_request(preview.THUMB_WIDTH, preview.THUMB_HEIGHT) + + self.connect_after('expose-event', self.__expose_event_cb) + + self.image = gtk.Image() + self.image.show() + self.add(self.image) + + self._invoker = WidgetInvoker(self) + self._invoker._position_hint = Invoker.AT_CURSOR self.connect('destroy', self.__destroy_cb) + def __expose_event_cb(self, widget, event): + __, __, width, height = self.allocation + fg = self.style.fg_gc[gtk.STATE_NORMAL] + self.window.draw_rectangle(fg, False, 0, 0, width - 1, height - 1) + + def fill_in(self, metadata): + self.metadata = metadata + self._invoker.palette = None + def __destroy_cb(self, icon): - if self._palette_invoker is not None: - self._palette_invoker.detach() + if self._invoker is not None: + self._invoker.detach() class _BuddyIcon(CanvasIcon): @@ -327,5 +352,5 @@ class _CanvasToWidget(hippo.Canvas): self.root = canvas_class(**kwargs) self.set_root(self.root) - def check_out(self, metadata): - self.root.check_out(metadata) + def fill_in(self, metadata): + self.root.fill_in(metadata) -- cgit v0.9.1