Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@member.fsf.org>2010-01-20 02:27:34 (GMT)
committer Aleksey Lim <alsroot@member.fsf.org>2010-01-20 02:27:34 (GMT)
commit2a0bbabf6acfb2bdbd29e3667f9461a1c559b043 (patch)
tree0adbce8fc716a099f78beb32138dbbd19b8bee61
parent15c69e4c19b37a04e7857afdcd2355fcf16334bb (diff)
Initial listview refactoring
-rw-r--r--src/jarabe/journal/controler.py31
-rw-r--r--src/jarabe/journal/entry.py184
-rw-r--r--src/jarabe/journal/expandedentry.py25
-rw-r--r--src/jarabe/journal/homogenetable.py662
-rw-r--r--src/jarabe/journal/homogeneview.py95
-rw-r--r--src/jarabe/journal/journalactivity.py33
-rw-r--r--src/jarabe/journal/journaltoolbox.py33
-rw-r--r--src/jarabe/journal/keepicon.py59
-rw-r--r--src/jarabe/journal/listmodel.py201
-rw-r--r--src/jarabe/journal/listview.py634
-rw-r--r--src/jarabe/journal/objectchooser.py56
-rw-r--r--src/jarabe/journal/palettes.py9
-rw-r--r--src/jarabe/journal/thumbsview.py101
-rw-r--r--src/jarabe/journal/view.py292
-rw-r--r--src/jarabe/journal/widgets.py331
15 files changed, 1797 insertions, 949 deletions
diff --git a/src/jarabe/journal/controler.py b/src/jarabe/journal/controler.py
new file mode 100644
index 0000000..76a3737
--- /dev/null
+++ b/src/jarabe/journal/controler.py
@@ -0,0 +1,31 @@
+# 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 gobject
+
+
+class _Objects(gobject.GObject):
+
+ __gsignals__ = {
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+
+objects = _Objects()
diff --git a/src/jarabe/journal/entry.py b/src/jarabe/journal/entry.py
new file mode 100644
index 0000000..7347fe1
--- /dev/null
+++ b/src/jarabe/journal/entry.py
@@ -0,0 +1,184 @@
+# 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 gtk
+import gobject
+import pango
+
+
+class Entry(gtk.TextView):
+ """One paragraph string entry with additional features
+
+ * multi line mode for wrapping long lines
+ * having wrapping and ellipses simultaneously in inactive mode
+ * accent in inactive mode
+
+ NOTE: Use text property instead of buffer, buffer's value will be
+ changed in inactive mode
+
+ """
+
+ def __init__(self, **kwargs):
+ self._max_line_count = 1
+ self._text = None
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ self._tag = self.props.buffer.create_tag()
+ self._tag.props.weight = pango.WEIGHT_BOLD
+
+ gtk.TextView.set_accepts_tab(self, False)
+ self.set_max_line_count(self._max_line_count)
+
+ self.connect('key-press-event', self.__key_press_event_cb)
+ self.connect('focus-in-event', self.__focus_in_event_cb)
+ self.connect('focus-out-event', self.__focus_out_event_cb)
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ def set_accepts_tab(self, value):
+ # accepts_tab cannot be set by users
+ assert(False)
+
+ def get_accepts_tab(self):
+ return gtk.TextView.get_accepts_tab()
+
+ accepts_tab = gobject.property(
+ getter=get_accepts_tab, setter=set_accepts_tab)
+
+ def set_wrap_mode(self, value):
+ # accepts_tab cannot be set by users
+ assert(False)
+
+ def get_wrap_mode(self):
+ return gtk.TextView.get_wrap_mode()
+
+ wrap_mode = gobject.property(
+ getter=get_wrap_mode, setter=set_wrap_mode)
+
+ def get_max_line_count(self):
+ return self._max_line_count
+
+ def set_max_line_count(self, max_line_count):
+ max_line_count = max(1, max_line_count)
+ self._max_line_count = max_line_count
+
+ if max_line_count == 1:
+ gtk.TextView.set_wrap_mode(self, gtk.WRAP_NONE)
+ else:
+ gtk.TextView.set_wrap_mode(self, gtk.WRAP_WORD)
+
+ context = self.get_pango_context()
+ metrics = context.get_metrics(self.style.font_desc)
+ line_height = pango.PIXELS(metrics.get_ascent() + \
+ metrics.get_descent())
+ self.set_size_request(-1, line_height * max_line_count)
+
+ max_line_count = gobject.property(
+ getter=get_max_line_count, setter=set_max_line_count)
+
+ def get_text(self):
+ return self._text
+
+ def set_text(self, value):
+ self._text = value
+ self.props.buffer.props.text = value
+ if not self.props.has_focus:
+ self._accept()
+
+ text = gobject.property(getter=get_text, setter=set_text)
+
+ def do_size_allocate(self, allocation):
+ gtk.TextView.do_size_allocate(self, allocation)
+ if not self.props.has_focus:
+ self._accept()
+
+ def _accept(self):
+ if self._text is None:
+ return
+
+ gtk.TextView.set_wrap_mode(self, gtk.WRAP_WORD)
+
+ buf = self.props.buffer
+ buf.props.text = self._text
+
+ def accent():
+ start = buf.get_start_iter()
+ end = buf.get_end_iter()
+ buf.apply_tag(self._tag, start, end)
+
+ def last_offset():
+ iter = buf.get_start_iter()
+ for __ in xrange(self._max_line_count):
+ if not self.forward_display_line(iter):
+ return None
+ return iter.get_offset()
+
+ accent()
+ offset = last_offset()
+
+ if offset is not None:
+ offset = len(buf.props.text[:offset].rstrip()) - 1
+ buf.props.text = buf.props.text[:offset] + '...'
+
+ final_offset = last_offset()
+ if final_offset is not None and final_offset < offset + 3:
+ # ellipses added new line
+ buf.props.text = buf.props.text[:offset - 3] + '...'
+
+ accent()
+
+ def __button_release_event_cb(self, widget, event):
+ buf = self.props.buffer
+ if not buf.get_has_selection():
+ buf.select_range(buf.get_end_iter(), buf.get_start_iter())
+ return False
+
+ def __focus_in_event_cb(self, widget, event):
+ self.props.buffer.props.text = self._text
+
+ if self._max_line_count == 1:
+ gtk.TextView.set_wrap_mode(self, gtk.WRAP_NONE)
+ else:
+ gtk.TextView.set_wrap_mode(self, gtk.WRAP_WORD)
+
+ return False
+
+ def __focus_out_event_cb(self, widget, event):
+ self._text = self.props.buffer.props.text
+ self._accept()
+ return False
+
+ def __key_press_event_cb(self, widget, event):
+ ignore_mask = [gtk.keysyms.Return]
+ if self._max_line_count == 1:
+ ignore_mask.extend([gtk.keysyms.Up, gtk.keysyms.Down])
+
+ if event.keyval in ignore_mask:
+ key_event = event
+ if event.keyval in [gtk.keysyms.Up]:
+ # change Shift mask for backwards keys
+ if key_event.state & gtk.gdk.SHIFT_MASK:
+ key_event.state &= ~gtk.gdk.SHIFT_MASK
+ else:
+ key_event.state |= gtk.gdk.SHIFT_MASK
+ key_event.keyval = gtk.keysyms.Tab
+ key_event.hardware_keycode = 0
+ gtk.main_do_event(key_event)
+ return True
+
+ return False
diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py
index e73b717..2a3ead0 100644
--- a/src/jarabe/journal/expandedentry.py
+++ b/src/jarabe/journal/expandedentry.py
@@ -32,7 +32,7 @@ from sugar.graphics.entry import CanvasEntry
from sugar.graphics.canvastextview import CanvasTextView
from sugar.util import format_size
-from jarabe.journal.keepicon import KeepIcon
+from jarabe.journal.widgets import KeepIconCanvas
from jarabe.journal.palettes import ObjectPalette, BuddyPalette
from jarabe.journal import misc
from jarabe.journal import model
@@ -99,7 +99,7 @@ class ExpandedEntry(hippo.CanvasBox):
# Header
- self._keep_icon = self._create_keep_icon()
+ self._keep_icon = KeepIconCanvas(box_width=style.GRID_CELL_SIZE * 3 / 5)
header.append(self._keep_icon)
self._icon = None
@@ -140,7 +140,7 @@ class ExpandedEntry(hippo.CanvasBox):
return
self._metadata = metadata
- self._keep_icon.keep = (int(metadata.get('keep', 0)) == 1)
+ self._keep_icon.check_out(metadata)
self._icon = self._create_icon()
self._icon_box.clear()
@@ -169,11 +169,6 @@ class ExpandedEntry(hippo.CanvasBox):
tags.props.buffer.props.text = metadata.get('tags', '')
tags.props.editable = model.is_editable(metadata)
- def _create_keep_icon(self):
- keep_icon = KeepIcon(False)
- keep_icon.connect('activated', self._keep_icon_activated_cb)
- return keep_icon
-
def _create_icon(self):
icon = CanvasIcon(file_name=misc.get_icon_name(self._metadata))
icon.connect_after('button-release-event',
@@ -405,20 +400,6 @@ class ExpandedEntry(hippo.CanvasBox):
self._update_title_sid = None
- def get_keep(self):
- return int(self._metadata.get('keep', 0)) == 1
-
- def _keep_icon_activated_cb(self, keep_icon):
- if not model.is_editable(self._metadata):
- return
- if self.get_keep():
- self._metadata['keep'] = 0
- else:
- self._metadata['keep'] = 1
- model.write(self._metadata, update_mtime=False)
-
- keep_icon.props.keep = self.get_keep()
-
def _icon_button_release_event_cb(self, button, event):
logging.debug('_icon_button_release_event_cb')
misc.resume(self._metadata)
diff --git a/src/jarabe/journal/homogenetable.py b/src/jarabe/journal/homogenetable.py
new file mode 100644
index 0000000..64a22ec
--- /dev/null
+++ b/src/jarabe/journal/homogenetable.py
@@ -0,0 +1,662 @@
+# 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 gtk
+import gobject
+import math
+import bisect
+import logging
+
+# Having spare rows let us making smooth scrolling w/o empty spaces
+_SPARE_ROWS_COUNT = 2
+
+
+class VHomogeneTable(gtk.Container):
+ """
+ Grid widget with homogeneously placed children of the same class.
+
+ Grid has fixed number of columns that are visible all time and unlimited
+ rows number. There are frame cells - visible at particular moment - frame
+ cells and virtual (widget is model less itself and only ask callback
+ object about right cell's value) ones - just cells. User can scroll up/down
+ grid to see all virtual cells and the same frame cell could represent
+ content of various virtual cells (widget will call cell_fill_in_cb callback
+ to refill frame cell content) in different time moments.
+
+ By default widget doesn't have any cells, to make it useful, assign proper
+ value to either frame_size or cell_size property. Also set cell_count to
+ set number of virual rows.
+
+ """
+ __gsignals__ = {
+ 'set-scroll-adjustments': (gobject.SIGNAL_RUN_FIRST, None,
+ [gtk.Adjustment, gtk.Adjustment]),
+ 'cursor-changed': (gobject.SIGNAL_RUN_FIRST, None, [object]),
+ }
+
+ def __init__(self, cell_class, **kwargs):
+ assert(hasattr(cell_class, 'do_fill_in'))
+
+ self._cell_class = cell_class
+ self._row_cache = []
+ self._cell_cache = []
+ self._cell_cache_pos = 0
+ self._adjustment = None
+ self._adjustment_value_changed_id = None
+ self._bin_window = None
+ self._cell_count = 0
+ self._cell_height = 0
+ self._frame_size = [None, None]
+ self._cell_size = [None, None]
+ self._selected_index = None
+ self._editable = True
+ self._pending_allocate = None
+
+ gtk.Container.__init__(self, **kwargs)
+
+ # when focused cell is out of visible frame,
+ # table itslef will be focused to follow gtk focusing scheme
+ self.props.can_focus = True
+
+ self.connect('key-press-event', self.__key_press_event_cb)
+
+ def set_frame_size(self, value):
+ value = list(value)
+ if self._frame_size == value:
+ return
+
+ if value[0] is not None:
+ self._cell_size[1] = None
+ if value[1] is not None:
+ self._cell_size[0] = None
+
+ self._frame_size = value
+ self._resize_table()
+
+ """Set persistent number of frame rows/columns, value is (rows, columns)
+ Cells will be resized while resizing widget.
+ Mutually exclusive to cell_size."""
+ frame_size = gobject.property(setter=set_frame_size)
+
+ def set_cell_size(self, value):
+ value = list(value)
+ if self._cell_size == value:
+ return
+
+ if value[0] is not None:
+ self._frame_size[1] = None
+ if value[1] is not None:
+ self._frame_size[0] = None
+
+ self._cell_size = value
+ self._resize_table()
+
+ """Set persistent cell sizes, value is (width, height)
+ Number of cells will be changed while resizing widget.
+ Mutually exclusive to frame_size."""
+ cell_size = gobject.property(setter=set_cell_size)
+
+ def get_cell_count(self):
+ return self._cell_count
+
+ def set_cell_count(self, count):
+ if self._cell_count == count:
+ return
+ self._cell_count = count
+ self.refill()
+ self._setup_adjustment(dry_run=False)
+
+ """Number of virtual cells
+ Defines maximal number of virtual rows, the minimal has being described
+ by frame_size/cell_size values."""
+ cell_count = gobject.property(getter=get_cell_count, setter=set_cell_count)
+
+ def get_cell(self, cell_index):
+ """Get cell widget by index
+ Method returns non-None values only for visible cells."""
+ cell = self._get_cell(cell_index)
+ if cell is None:
+ return None
+ else:
+ return cell.widget
+
+ def __getitem__(self, cell_index):
+ return self.get_cell(cell_index)
+
+ def get_cursor(self):
+ return self._selected_index
+
+ def set_cursor(self, cell_index):
+ cell_index = min(max(0, cell_index), self.cell_count - 1)
+ if cell_index == self.cursor:
+ return
+ self.scroll_to_cell(cell_index)
+ self._set_cursor(cell_index)
+
+ """Selected cell"""
+ cursor = gobject.property(getter=get_cursor, setter=set_cursor)
+
+ def get_editable(self):
+ return self._editable
+
+ def set_editable(self, value):
+ self._editable = value
+
+ """Can cells be focused"""
+ editable = gobject.property(getter=get_editable, setter=set_editable)
+
+ def get_editing(self):
+ if not self._editable or self._selected_index is None or \
+ self.props.has_focus:
+ return False
+ cell = self._get_cell(self._selected_index)
+ if cell is None:
+ return False
+ else:
+ return cell.widget.get_focus_child()
+
+ def set_editing(self, value):
+ if value == self.editing:
+ return
+ if value:
+ if not self.props.has_focus:
+ self.grab_focus()
+ cell = self._get_cell(self._selected_index)
+ if cell is not None:
+ cell.widget.child_focus(gtk.DIR_TAB_FORWARD)
+ else:
+ self.grab_focus()
+
+ """Selected cell got focused"""
+ editing = gobject.property(getter=get_editing, setter=set_editing)
+
+ def get_cell_at_pos(self, x, y):
+ """Get cell index at pos which is relative to VHomogeneTable widget"""
+ if self._empty:
+ return None
+
+ x, y = self.get_pointer()
+ x = min(max(0, x), self.allocation.width)
+ y = min(max(0, y), self.allocation.height) + self._pos_y
+
+ return self._get_cell_at_pos(x, y)
+
+ def scroll_to_cell(self, cell_index):
+ """Scroll VHomogeneTable to position where cell is viewable"""
+ if self._empty:
+ return
+
+ self.editing = False
+
+ row = cell_index / self._column_count
+ pos = row * self._cell_height
+
+ if pos < self._pos_y:
+ self._pos_y = pos
+ elif pos + self._cell_height >= self._pos_y + self._page:
+ self._pos_y = pos + self._cell_height - self._page
+ else:
+ return
+
+ self._pos_changed()
+
+ def refill(self):
+ """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)
+
+ # gtk.Widget overrides
+
+ 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=-self._pos_y,
+ width=self.allocation.width,
+ height=self._max_y,
+ 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._row_cache:
+ for cell in row:
+ cell.widget.set_parent_window(self._bin_window)
+
+ if self._pending_allocate is not None:
+ self._allocate_rows(force=self._pending_allocate)
+ self._pending_allocate = None
+ #self.queue_resize()
+
+ def do_size_allocate(self, allocation):
+ resize_tabel = self.allocation != allocation
+ self.allocation = allocation
+
+ if resize_tabel:
+ self._resize_table()
+
+ if self.flags() & gtk.REALIZED:
+ self.window.move_resize(*allocation)
+
+ 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._row_cache:
+ for cell in row:
+ cell.widget.map()
+
+ self._bin_window.show()
+ self.window.show()
+
+ def do_size_request(self, req):
+ req.width = 0
+ req.height = 0
+
+ for row in self._row_cache:
+ for cell in row:
+ cell.widget.size_request()
+
+ def do_set_scroll_adjustments(self, hadjustment, vadjustment):
+ if vadjustment is None or vadjustment == self._adjustment:
+ return
+
+ if self._adjustment is not None:
+ self._adjustment.disconnect(self._adjustment_value_changed_id)
+
+ self._adjustment = vadjustment
+ self._setup_adjustment(dry_run=True)
+
+ self._adjustment_value_changed_id = vadjustment.connect(
+ 'value-changed', self.__adjustment_value_changed_cb)
+
+ # gtk.Container overrides
+
+ def do_forall(self, include_internals, callback, data):
+ for row in self._row_cache:
+ for cell in row:
+ callback(cell.widget, data)
+
+ def do_add(self, widget):
+ # container is not intended to add children manually
+ assert(False)
+
+ def do_remove(self, widget):
+ # container is not intended to remove children manually
+ pass
+
+ def do_set_focus_child(self, widget):
+ if widget is not None:
+ x, y, __, __ = widget.allocation
+ self.cursor = self._get_cell_at_pos(x, y)
+
+ def do_focus(self, type):
+ if self.editing:
+ cell = self._get_cell(self._selected_index)
+ if cell is None:
+ logging.error('cannot find _selected_index cell')
+ elif not cell.widget.child_focus(type):
+ self.grab_focus()
+ return True
+ else:
+ if self.props.has_focus:
+ return False
+ else:
+ if self._selected_index is None:
+ x, y = self.get_pointer()
+ self._set_cursor(self.get_cell_at_pos(x, y))
+ self.grab_focus()
+ 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
+
+ @property
+ def _column_count(self):
+ if self._row_cache:
+ return len(self._row_cache[0])
+ else:
+ return 0
+
+ @property
+ def _row_count(self):
+ if self._column_count == 0:
+ return 0
+ else:
+ rows = math.ceil(float(self.cell_count) / self._column_count)
+ return max(self._frame_row_count, rows)
+
+ @property
+ def _frame_row_count(self):
+ return len(self._row_cache) - _SPARE_ROWS_COUNT
+
+ @property
+ def _page(self):
+ return self._frame_row_count * self._cell_height
+
+ @property
+ def _pos_y(self):
+ if self._adjustment is None or math.isnan(self._adjustment.value):
+ return 0
+ else:
+ return max(0, int(self._adjustment.value))
+
+ @_pos_y.setter
+ def _pos_y(self, value):
+ if self._adjustment is not None:
+ self._adjustment.value = value
+
+ @property
+ def _max_pos_y(self):
+ if self._adjustment is None:
+ return 0
+ else:
+ return max(0, self._max_y - self._page)
+
+ @property
+ def _max_y(self):
+ if self._adjustment is None:
+ return self.allocation.height
+ else:
+ return int(self._adjustment.upper)
+
+ def _get_cell(self, cell_index):
+ if cell_index is None:
+ return None
+ column = cell_index % self._column_count
+ base_index = cell_index - column
+ for row in self._row_cache:
+ if row[0].is_valid() and row[0].index == base_index:
+ return row[column]
+ return None
+
+ def _set_cursor(self, cell_index):
+ old_cursor = self._selected_index
+ self._selected_index = cell_index
+ if old_cursor != self._selected_index:
+ self.emit('cursor-changed', old_cursor)
+
+ def _get_cell_at_pos(self, x, y):
+ cell_row = y / self._cell_height
+ cell_column = x / (self.allocation.width / self._column_count)
+ cell_index = cell_row * self._column_count + cell_column
+ return min(cell_index, self.cell_count - 1)
+
+ def _pos_changed(self):
+ if self._adjustment is not None:
+ self._adjustment.value_changed()
+
+ def _abandon_cells(self):
+ for row in self._row_cache:
+ for cell in row:
+ cell.widget.unparent()
+ self._cell_cache_pos = 0
+ self._row_cache = []
+
+ def _pop_a_cell(self):
+ if self._cell_cache_pos < len(self._cell_cache):
+ cell = self._cell_cache[self._cell_cache_pos]
+ self._cell_cache_pos += 1
+ else:
+ cell = _Cell()
+ cell.widget = self._cell_class()
+ self._cell_cache.append(cell)
+ self._cell_cache_pos = len(self._cell_cache)
+
+ cell.invalidate_pos()
+ return cell
+
+ def _resize_table(self):
+ x, y, width, height = self.allocation
+ if x < 0 or y < 0:
+ return
+
+ frame_row_count, column_count = self._frame_size
+ cell_width, cell_height = self._cell_size
+
+ if frame_row_count is None:
+ if cell_height is None:
+ return
+ frame_row_count = max(1, height / cell_height)
+ if column_count is None:
+ if cell_width is None:
+ return
+ column_count = max(1, width / cell_width)
+
+ if (column_count != self._column_count or \
+ frame_row_count != self._frame_row_count):
+ self._abandon_cells()
+ for i_ in range(frame_row_count + _SPARE_ROWS_COUNT):
+ row = []
+ for j_ in range(column_count):
+ cell = self._pop_a_cell()
+ if self.flags() & gtk.REALIZED:
+ cell.widget.set_parent_window(self._bin_window)
+ cell.widget.set_parent(self)
+ row.append(cell)
+ self._row_cache.append(row)
+ else:
+ for row in self._row_cache:
+ for cell in row:
+ cell.invalidate_pos()
+
+ self._cell_height = height / self._frame_row_count
+ self._setup_adjustment(dry_run=True)
+
+ if self.flags() & gtk.REALIZED:
+ self._bin_window.resize(self.allocation.width, self._max_y)
+
+ self._allocate_rows(force=True)
+
+ def _setup_adjustment(self, dry_run):
+ if self._adjustment is None:
+ return
+
+ self._adjustment.lower = 0
+ self._adjustment.upper = self._row_count * self._cell_height
+ self._adjustment.page_size = self._page
+ self._adjustment.changed()
+
+ if self._pos_y > self._max_pos_y:
+ self._pos_y = self._max_pos_y
+ if not dry_run:
+ self._adjustment.value_changed()
+
+ def _allocate_cells(self, row, cell_y):
+ cell_x = 0
+ cell_row = cell_y / self._cell_height
+ cell_index = cell_row * self._column_count
+
+ for cell_column, cell in enumerate(row):
+ if cell.index != cell_index:
+ if cell_index < self.cell_count:
+ cell.widget.do_fill_in(self, cell_index)
+ cell.widget.show()
+ else:
+ cell.widget.hide()
+ cell.index = cell_index
+
+ cell_alloc = gtk.gdk.Rectangle(cell_x, cell_y)
+ cell_alloc.width = self.allocation.width / self._column_count
+ cell_alloc.height = self._cell_height
+ cell.widget.size_request()
+ cell.widget.size_allocate(cell_alloc)
+
+ cell_x += cell_alloc.width
+ cell_index += 1
+
+ def _allocate_rows(self, force):
+ if self._empty:
+ return
+
+ if not self.flags() & gtk.REALIZED:
+ self._pending_allocate = self._pending_allocate or force
+ return
+
+ pos = self._pos_y
+ if pos < 0 or pos > self._max_pos_y:
+ return
+
+ spare_rows = []
+ visible_rows = []
+ page_end = pos + self._page
+
+ if force:
+ spare_rows = [] + self._row_cache
+ else:
+ for row in self._row_cache:
+ row_y = row[0].widget.allocation.y
+ if row_y < 0 or row_y > page_end or \
+ (row_y + self._cell_height) < pos:
+ spare_rows.append(row)
+ else:
+ bisect.insort_right(visible_rows, _IndexedRow(row))
+
+ if visible_rows or spare_rows:
+
+ def try_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_cells(row, cell_y)
+ cell_y = cell_y + self._cell_height
+
+ # visible_rows could not be continuous
+ # lets try to add spare rows to missed points
+ cell_y = int(pos) - int(pos) % self._cell_height
+ for i in visible_rows:
+ cell = i.row[0].widget.allocation
+ try_insert_spare_row(cell_y, cell.y)
+ cell_y = cell.y + cell.height
+
+ 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)
+
+ def __adjustment_value_changed_cb(self, adjustment):
+ self._allocate_rows(force=False)
+
+ def __key_press_event_cb(self, widget, event):
+ if self._empty or self.cursor is None:
+ return
+
+ page = self._column_count * self._frame_row_count
+
+ if event.keyval == gtk.keysyms.Return and self.editable:
+ self.editing = not self.editing
+ elif event.keyval == gtk.keysyms.Left:
+ self.cursor -= 1
+ elif event.keyval == gtk.keysyms.Right:
+ self.cursor += 1
+ elif event.keyval == gtk.keysyms.Up:
+ if self.cursor >= self._column_count:
+ self.cursor -= self._column_count
+ elif event.keyval == gtk.keysyms.Down:
+ if self.cursor / self._column_count < \
+ (self.cell_count - 1) / self._column_count:
+ self.cursor += self._column_count
+ elif event.keyval in (gtk.keysyms.Page_Up, gtk.keysyms.KP_Page_Up):
+ self.cursor -= page
+ elif event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.KP_Page_Down):
+ self.cursor += page
+ elif event.keyval in (gtk.keysyms.Home, gtk.keysyms.KP_Home):
+ self.cursor = 0
+ elif event.keyval in (gtk.keysyms.End, gtk.keysyms.KP_End):
+ self.cursor = self.cell_count - 1
+ else:
+ return False
+
+ return True
+
+
+class _IndexedRow:
+
+ def __init__(self, row):
+ self.row = row
+
+ def __lt__(self, other):
+ return self.row[0].widget.allocation.y < \
+ other.row[0].widget.allocation.y
+
+
+class _Cell:
+ widget = None
+ index = -1
+
+ def invalidate_pos(self):
+ self.widget.size_allocate(gtk.gdk.Rectangle(-1, -1, 0, 0))
+
+ def is_valid(self):
+ return self.index >= 0 and self.widget is not None and \
+ self.widget.allocation >= 0 and self.widget.allocation >= 0
+
+
+VHomogeneTable.set_set_scroll_adjustments_signal('set-scroll-adjustments')
diff --git a/src/jarabe/journal/homogeneview.py b/src/jarabe/journal/homogeneview.py
new file mode 100644
index 0000000..764ecfe
--- /dev/null
+++ b/src/jarabe/journal/homogeneview.py
@@ -0,0 +1,95 @@
+# 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 gtk
+import gobject
+import logging
+
+from sugar.graphics import style
+from sugar.graphics.roundbox import CanvasRoundBox
+
+from jarabe.journal.homogenetable import VHomogeneTable
+
+
+class Cell(gtk.EventBox):
+
+ def __init__(self):
+ gtk.EventBox.__init__(self)
+ self.select(False)
+
+ def do_fill_in_cell_content(self, table, 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())
+ if table.hover_selection:
+ self.select(table.cursor == cell_index)
+
+ def select(self, selected):
+ if selected:
+ self.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ else:
+ self.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_WHITE.get_gdk_color())
+
+
+class HomogeneView(VHomogeneTable):
+
+ __gsignals__ = {
+ 'entry-activated': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self, cell_class, **kwargs):
+ assert(issubclass(cell_class, Cell))
+
+ VHomogeneTable.__init__(self, cell_class, **kwargs)
+
+ self._result_set = None
+ self.hover_selection = False
+
+ self.connect('cursor-changed', self.__cursor_changed_cb)
+
+ def get_result_set(self):
+ return self._result_set
+
+ def set_result_set(self, result_set):
+ if self._result_set is result_set:
+ return
+
+ self._result_set = result_set
+
+ result_set_length = result_set.get_length()
+ if self.cell_count == result_set_length:
+ self.refill()
+ 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
+ old_cell = table[old_cursor]
+ if old_cell is not None:
+ old_cell.select(False)
+ new_cell = table[table.cursor]
+ if new_cell is not None:
+ new_cell.select(True)
diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py
index 0559560..587b57b 100644
--- a/src/jarabe/journal/journalactivity.py
+++ b/src/jarabe/journal/journalactivity.py
@@ -34,7 +34,7 @@ from sugar import wm
from jarabe.model import bundleregistry
from jarabe.journal.journaltoolbox import MainToolbox, DetailToolbox
-from jarabe.journal.listview import ListView
+from jarabe.journal.view import View
from jarabe.journal.detailview import DetailView
from jarabe.journal.volumestoolbar import VolumesToolbar
from jarabe.journal import misc
@@ -42,6 +42,7 @@ from jarabe.journal.journalentrybundle import JournalEntryBundle
from jarabe.journal.objectchooser import ObjectChooser
from jarabe.journal.modalalert import ModalAlert
from jarabe.journal import model
+from jarabe.journal import controler
J_DBUS_SERVICE = 'org.laptop.Journal'
J_DBUS_INTERFACE = 'org.laptop.Journal'
@@ -105,11 +106,15 @@ 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._view = None
self._detail_view = None
self._main_toolbox = None
self._detail_toolbox = None
@@ -152,11 +157,11 @@ class JournalActivity(Window):
self._main_toolbox = MainToolbox()
self._main_view = gtk.VBox()
- 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._view = View()
+ controler.objects.connect('detail-clicked', self.__detail_clicked_cb)
+ self._view.connect('clear-clicked', self.__clear_clicked_cb)
+ self._main_view.pack_start(self._view)
+ self._view.show()
self._volumes_toolbar = VolumesToolbar()
self._volumes_toolbar.connect('volume-changed',
@@ -165,6 +170,7 @@ 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):
@@ -183,7 +189,7 @@ class JournalActivity(Window):
if keyname == 'Escape':
self.show_main_view()
- def __detail_clicked_cb(self, list_view, object_id):
+ def __detail_clicked_cb(self, controler, object_id):
self._show_secondary_view(object_id)
def __clear_clicked_cb(self, list_view):
@@ -193,9 +199,12 @@ class JournalActivity(Window):
self.show_main_view()
def _query_changed_cb(self, toolbar, query):
- self._list_view.update_with_query(query)
+ self._view.update_with_query(query)
self.show_main_view()
+ def __view_changed_cb(self, sender, view):
+ self._view.view = view
+
def show_main_view(self):
if self.toolbar_box != self._main_toolbox:
self.set_toolbar_box(self._main_toolbox)
@@ -259,7 +268,7 @@ class JournalActivity(Window):
def _focus_in_event_cb(self, window, event):
self.search_grab_focus()
- self._list_view.update_dates()
+ self._view.update_dates()
def _check_for_bundle(self, object_id):
registry = bundleregistry.get_registry()
@@ -306,12 +315,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._list_view.set_is_visible(visible)
+ self._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._list_view.set_is_visible(visible)
+ self._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 fe05657..7da9471 100644
--- a/src/jarabe/journal/journaltoolbox.py
+++ b/src/jarabe/journal/journaltoolbox.py
@@ -26,6 +26,7 @@ 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
@@ -41,6 +42,7 @@ from sugar import mime
from jarabe.model import bundleregistry
from jarabe.journal import misc
from jarabe.journal import model
+from jarabe.journal import view
_AUTOSEARCH_TIMEOUT = 1000
@@ -73,7 +75,10 @@ class SearchToolbar(gtk.Toolbar):
__gsignals__ = {
'query-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
- ([object]))
+ ([object])),
+ 'view-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([object]))
}
def __init__(self):
@@ -114,10 +119,36 @@ class SearchToolbar(gtk.Toolbar):
#self.insert(tool_item, -1)
#tool_item.show()
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ self.insert(separator, -1)
+ separator.show()
+
+ list_button = RadioToolButton(named_icon='view-list')
+ list_button.props.tooltip = _('List view')
+ list_button.props.accelerator = _('<Ctrl>1')
+ list_button.connect('toggled', self.__view_button_toggled_cb,
+ view.VIEW_LIST)
+ 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 = _('<Ctrl>2')
+ thumb_button.connect('toggled', self.__view_button_toggled_cb,
+ view.VIEW_THUMBS)
+ self.insert(thumb_button, -1)
+ thumb_button.show()
+
self._query = self._build_query()
self.refresh_filters()
+ def __view_button_toggled_cb(self, button, view):
+ if button.props.active:
+ self.emit('view-changed', view)
+
def give_entry_focus(self):
self._search_entry.grab_focus()
diff --git a/src/jarabe/journal/keepicon.py b/src/jarabe/journal/keepicon.py
deleted file mode 100644
index 2c692c6..0000000
--- a/src/jarabe/journal/keepicon.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright (C) 2006, Red Hat, Inc.
-#
-# 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
-import hippo
-import gconf
-
-from sugar.graphics.icon import CanvasIcon
-from sugar.graphics import style
-from sugar.graphics.xocolor import XoColor
-
-class KeepIcon(CanvasIcon):
- def __init__(self, keep):
- CanvasIcon.__init__(self, icon_name='emblem-favorite',
- box_width=style.GRID_CELL_SIZE * 3 / 5,
- size=style.SMALL_ICON_SIZE)
- self.connect('motion-notify-event', self.__motion_notify_event_cb)
-
- self._keep = None
- self.set_keep(keep)
-
- def set_keep(self, keep):
- if keep == self._keep:
- return
-
- self._keep = keep
- if keep:
- client = gconf.client_get_default()
- color = XoColor(client.get_string('/desktop/sugar/user/color'))
- self.props.xo_color = color
- else:
- self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
- self.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
-
- def get_keep(self):
- return self._keep
-
- keep = gobject.property(type=int, default=0, getter=get_keep,
- setter=set_keep)
-
- def __motion_notify_event_cb(self, icon, event):
- if not self._keep:
- if event.detail == hippo.MOTION_DETAIL_ENTER:
- icon.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
- elif event.detail == hippo.MOTION_DETAIL_LEAVE:
- icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
diff --git a/src/jarabe/journal/listmodel.py b/src/jarabe/journal/listmodel.py
deleted file mode 100644
index d3b7e24..0000000
--- a/src/jarabe/journal/listmodel.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# Copyright (C) 2009, Tomeu Vizoso
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# 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 simplejson
-import gobject
-import gtk
-
-from sugar.graphics.xocolor import XoColor
-from sugar.graphics import style
-from sugar import util
-
-from jarabe.journal import model
-from jarabe.journal import misc
-
-DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
-DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
-DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
-
-class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource):
- __gtype_name__ = 'JournalListModel'
-
- __gsignals__ = {
- 'ready': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([])),
- 'progress': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([])),
- }
-
- COLUMN_UID = 0
- COLUMN_FAVORITE = 1
- COLUMN_ICON = 2
- COLUMN_ICON_COLOR = 3
- COLUMN_TITLE = 4
- COLUMN_DATE = 5
- COLUMN_PROGRESS = 6
- COLUMN_BUDDY_1 = 7
- COLUMN_BUDDY_2 = 8
- COLUMN_BUDDY_3 = 9
-
- _COLUMN_TYPES = {COLUMN_UID: str,
- COLUMN_FAVORITE: bool,
- COLUMN_ICON: str,
- COLUMN_ICON_COLOR: object,
- COLUMN_TITLE: str,
- COLUMN_DATE: str,
- COLUMN_PROGRESS: int,
- COLUMN_BUDDY_1: object,
- COLUMN_BUDDY_3: object,
- COLUMN_BUDDY_2: object}
-
- _PAGE_SIZE = 10
-
- def __init__(self, query):
- gobject.GObject.__init__(self)
-
- self._last_requested_index = None
- self._cached_row = None
- 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])
-
- def on_get_n_columns(self):
- return len(ListModel._COLUMN_TYPES)
-
- def on_get_column_type(self, index):
- return ListModel._COLUMN_TYPES[index]
-
- def on_iter_n_children(self, iter):
- if iter == None:
- return self._result_set.length
- else:
- return 0
-
- def on_get_value(self, index, column):
- if self.view_is_resizing:
- return None
-
- if index == self._last_requested_index:
- return self._cached_row[column]
-
- if index >= self._result_set.length:
- return None
-
- self._result_set.seek(index)
- metadata = self._result_set.read()
-
- self._last_requested_index = index
- self._cached_row = []
- self._cached_row.append(metadata['uid'])
- self._cached_row.append(metadata.get('keep', '0') == '1')
- self._cached_row.append(misc.get_icon_name(metadata))
-
- if misc.is_activity_bundle(metadata):
- xo_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
- style.COLOR_TRANSPARENT.get_svg()))
- else:
- xo_color = misc.get_icon_color(metadata)
- self._cached_row.append(xo_color)
-
- title = gobject.markup_escape_text(metadata.get('title', None))
- self._cached_row.append('<b>%s</b>' % title)
-
- timestamp = int(metadata.get('timestamp', 0))
- self._cached_row.append(util.timestamp_to_elapsed_string(timestamp))
-
- self._cached_row.append(int(metadata.get('progress', 100)))
-
- if metadata.get('buddies', ''):
- buddies = simplejson.loads(metadata['buddies']).values()
- else:
- buddies = []
-
- for n in xrange(0, 3):
- if buddies:
- nick, color = buddies.pop(0)
- self._cached_row.append((nick, XoColor(color)))
- else:
- self._cached_row.append(None)
-
- return self._cached_row[column]
-
- def on_iter_nth_child(self, iter, n):
- return n
-
- def on_get_path(self, iter):
- return (iter)
-
- def on_get_iter(self, path):
- return path[0]
-
- def on_iter_next(self, iter):
- if iter != None:
- if iter >= self._result_set.length - 1:
- return None
- return iter + 1
- return None
-
- def on_get_flags(self):
- return gtk.TREE_MODEL_ITERS_PERSIST | gtk.TREE_MODEL_LIST_ONLY
-
- def on_iter_children(self, iter):
- return None
-
- def on_iter_has_child(self, iter):
- return False
-
- def on_iter_parent(self, iter):
- return None
-
- def do_drag_data_get(self, path, selection):
- uid = self[path][ListModel.COLUMN_UID]
- if selection.target == 'text/uri-list':
- # Get hold of a reference so the temp file doesn't get deleted
- self._temp_drag_file_path = model.get_file(uid)
- logging.debug('putting %r in selection', self._temp_drag_file_path)
- selection.set(selection.target, 8, self._temp_drag_file_path)
- return True
- elif selection.target == 'journal-object-id':
- selection.set(selection.target, 8, uid)
- return True
-
- return False
-
diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
index 9e19f70..f4406b2 100644
--- a/src/jarabe/journal/listview.py
+++ b/src/jarabe/journal/listview.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009, Tomeu Vizoso
+# 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
@@ -14,628 +14,56 @@
# 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
import logging
-from gettext import gettext as _
-import time
-import gobject
-import gtk
import hippo
-import gconf
-import pango
from sugar.graphics import style
-from sugar.graphics.icon import CanvasIcon, Icon, CellRendererIcon
-from sugar.graphics.xocolor import XoColor
-from sugar import util
-
-from jarabe.journal.listmodel import ListModel
-from jarabe.journal.palettes import ObjectPalette, BuddyPalette
-from jarabe.journal import model
-from jarabe.journal import misc
-
-UPDATE_INTERVAL = 300
-
-MESSAGE_EMPTY_JOURNAL = 0
-MESSAGE_NO_MATCH = 1
-
-class TreeView(gtk.TreeView):
- __gtype_name__ = 'JournalTreeView'
-
- def __init__(self):
- gtk.TreeView.__init__(self)
- self.set_headers_visible(False)
- 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
+from jarabe.journal.homogeneview import HomogeneView
+from jarabe.journal.homogeneview import Cell
+from jarabe.journal.widgets import *
-class BaseListView(gtk.Bin):
- __gtype_name__ = 'JournalBaseListView'
- __gsignals__ = {
- 'clear-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([]))
- }
+class _Cell(Cell):
def __init__(self):
- self._query = {}
- self._model = None
- self._progress_bar = None
- self._last_progress_bar_pulse = None
- self._scroll_position = 0.
-
- gobject.GObject.__init__(self)
-
- self.connect('map', self.__map_cb)
- self.connect('unrealize', self.__unrealize_cb)
- self.connect('destroy', self.__destroy_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()
-
- self.tree_view = TreeView()
- selection = self.tree_view.get_selection()
- selection.set_mode(gtk.SELECTION_NONE)
- self.tree_view.props.fixed_height_mode = True
- self.tree_view.modify_base(gtk.STATE_NORMAL,
- style.COLOR_WHITE.get_gdk_color())
- self._scrolled_window.add(self.tree_view)
- self.tree_view.show()
-
- self.cell_title = None
- self.cell_icon = None
- self._title_column = None
- self.date_column = None
- self._add_columns()
-
- self.tree_view.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
- [('text/uri-list', 0, 0),
- ('journal-object-id', 0, 0)],
- gtk.gdk.ACTION_COPY)
-
- # Auto-update stuff
- self._fully_obscured = True
- 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 __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 _add_columns(self):
- cell_favorite = CellRendererFavorite(self.tree_view)
- cell_favorite.connect('clicked', self.__favorite_clicked_cb)
-
- column = gtk.TreeViewColumn()
- column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
- column.props.fixed_width = cell_favorite.props.width
- column.pack_start(cell_favorite)
- column.set_cell_data_func(cell_favorite, self.__favorite_set_data_cb)
- self.tree_view.append_column(column)
-
- self.cell_icon = CellRendererActivityIcon(self.tree_view)
-
- column = gtk.TreeViewColumn()
- column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
- column.props.fixed_width = self.cell_icon.props.width
- column.pack_start(self.cell_icon)
- column.add_attribute(self.cell_icon, 'file-name', ListModel.COLUMN_ICON)
- column.add_attribute(self.cell_icon, 'xo-color',
- ListModel.COLUMN_ICON_COLOR)
- self.tree_view.append_column(column)
-
- self.cell_title = gtk.CellRendererText()
- self.cell_title.props.ellipsize = pango.ELLIPSIZE_MIDDLE
- self.cell_title.props.ellipsize_set = True
-
- self._title_column = gtk.TreeViewColumn()
- self._title_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
- self._title_column.props.expand = True
- self._title_column.props.clickable = True
- self._title_column.pack_start(self.cell_title)
- self._title_column.add_attribute(self.cell_title, 'markup',
- ListModel.COLUMN_TITLE)
- self.tree_view.append_column(self._title_column)
-
- buddies_column = gtk.TreeViewColumn()
- buddies_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
- self.tree_view.append_column(buddies_column)
-
- for column_index in [ListModel.COLUMN_BUDDY_1, ListModel.COLUMN_BUDDY_2,
- ListModel.COLUMN_BUDDY_3]:
- cell_icon = CellRendererBuddy(self.tree_view,
- column_index=column_index)
- buddies_column.pack_start(cell_icon)
- buddies_column.props.fixed_width += cell_icon.props.width
- buddies_column.add_attribute(cell_icon, 'buddy', column_index)
- buddies_column.set_cell_data_func(cell_icon,
- self.__buddies_set_data_cb)
-
- cell_progress = gtk.CellRendererProgress()
- cell_progress.props.ypad = style.GRID_CELL_SIZE / 4
- buddies_column.pack_start(cell_progress)
- buddies_column.add_attribute(cell_progress, 'value',
- ListModel.COLUMN_PROGRESS)
- buddies_column.set_cell_data_func(cell_progress,
- self.__progress_data_cb)
-
- cell_text = gtk.CellRendererText()
- cell_text.props.xalign = 1
-
- # Measure the required width for a date in the form of "10 hours, 10
- # minutes ago"
- timestamp = time.time() - 10 * 60 - 10 * 60 * 60
- date = util.timestamp_to_elapsed_string(timestamp)
- date_width = self._get_width_for_string(date)
-
- self.date_column = gtk.TreeViewColumn()
- self.date_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
- self.date_column.props.fixed_width = date_width
- self.date_column.set_alignment(1)
- self.date_column.props.resizable = True
- self.date_column.props.clickable = True
- self.date_column.pack_start(cell_text)
- self.date_column.add_attribute(cell_text, 'text', ListModel.COLUMN_DATE)
- self.tree_view.append_column(self.date_column)
-
- def _get_width_for_string(self, text):
- # Add some extra margin
- text = text + 'aaaaa'
-
- widget = gtk.Label('')
- context = widget.get_pango_context()
- layout = pango.Layout(context)
- layout.set_text(text)
- width, height_ = layout.get_size()
- return pango.PIXELS(width)
-
- 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 __buddies_set_data_cb(self, column, cell, tree_model, tree_iter):
- progress = tree_model[tree_iter][ListModel.COLUMN_PROGRESS]
- cell.props.visible = progress >= 100
-
- def __progress_data_cb(self, column, cell, tree_model, tree_iter):
- progress = tree_model[tree_iter][ListModel.COLUMN_PROGRESS]
- cell.props.visible = progress < 100
-
- def __favorite_set_data_cb(self, column, cell, tree_model, tree_iter):
- favorite = tree_model[tree_iter][ListModel.COLUMN_FAVORITE]
- if favorite:
- client = gconf.client_get_default()
- color = XoColor(client.get_string('/desktop/sugar/user/color'))
- cell.props.xo_color = color
- else:
- cell.props.xo_color = None
-
- def __favorite_clicked_cb(self, cell, path):
- row = self._model[path]
- metadata = model.get(row[ListModel.COLUMN_UID])
- if not model.is_editable(metadata):
- return
- if metadata.get('keep', 0) == '1':
- metadata['keep'] = '0'
- else:
- metadata['keep'] = '1'
- model.write(metadata, update_mtime=False)
-
- 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()
-
- if self._model is not None:
- self._model.stop()
- self._dirty = False
-
- 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()
-
- self._scroll_position = self.tree_view.props.vadjustment.props.value
- logging.debug('ListView.__model_ready_cb %r', self._scroll_position)
-
- if self.tree_view.window is not None:
- # prevent glitches while later vadjustment setting, see #1235
- self.tree_view.get_bin_window().hide()
-
- # Cannot set it up earlier because will try to access the model
- # and it needs to be ready.
- self.tree_view.set_model(self._model)
-
- self.tree_view.props.vadjustment.props.value = self._scroll_position
- self.tree_view.props.vadjustment.value_changed()
-
- if self.tree_view.window is not None:
- # prevent glitches while later vadjustment setting, see #1235
- self.tree_view.get_bin_window().show()
-
- 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 __map_cb(self, widget):
- logging.debug('ListView.__map_cb %r', self._scroll_position)
- self.tree_view.props.vadjustment.props.value = self._scroll_position
- self.tree_view.props.vadjustment.value_changed()
-
- def __unrealize_cb(self, widget):
- self._scroll_position = self.tree_view.props.vadjustment.props.value
- logging.debug('ListView.__map_cb %r', self._scroll_position)
-
- is_editable = self._query.get('mountpoints', '') == '/'
- self.cell_title.props.editable = is_editable
+ Cell.__init__(self)
- 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
+ self._row = gtk.HBox()
+ self._row.props.spacing = style.DEFAULT_SPACING
+ self.add(self._row)
- def __model_progress_cb(self, tree_model):
- if self._progress_bar is None:
- self._start_progress_bar()
+ keep = KeepIcon(box_width=style.GRID_CELL_SIZE)
+ self._row.pack_start(keep, expand=False)
- if time.time() - self._last_progress_bar_pulse > 0.05:
- self._progress_bar.pulse()
- self._last_progress_bar_pulse = time.time()
+ icon = ObjectIcon(size=style.STANDARD_ICON_SIZE)
+ self._row.pack_start(icon, expand=False)
- 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()
+ title = Title(xalign=0, yalign=0.5, xscale=1, yscale=0)
+ self._row.pack_start(title)
- 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()
+ details = DetailsIcon()
+ self._row.pack_end(details, expand=False)
- def _stop_progress_bar(self):
- if self._progress_bar is None:
- return
- self.remove(self.child)
- self.add(self._scrolled_window)
- self._progress_bar = None
+ date = Timestamp()
+ self._row.pack_end(date, expand=False)
- def _show_message(self, message):
- canvas = hippo.Canvas()
- self.remove(self.child)
- self.add(canvas)
- canvas.show()
+ buddies = Buddies(buddies_max=3,
+ xalign=0, yalign=0.5, xscale=1, yscale=0.15)
+ self._row.pack_end(buddies, expand=False)
- 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)
+ self.show_all()
- 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)
+ def do_fill_in_cell_content(self, table, metadata):
+ for i in self._row.get_children():
+ i.check_out(metadata)
- 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):
- if self.child == self._scrolled_window:
- return
- self.remove(self.child)
- self.add(self._scrolled_window)
- self._scrolled_window.show()
-
- def update_dates(self):
- if not self.tree_view.flags() & gtk.REALIZED:
- return
- visible_range = self.tree_view.get_visible_range()
- if visible_range is None:
- return
-
- logging.debug('ListView.update_dates')
-
- path, end_path = visible_range
- tree_model = self.tree_view.get_model()
-
- while True:
- x, y, width, height = self.tree_view.get_cell_area(path,
- self.date_column)
- x, y = self.tree_view.convert_tree_to_widget_coords(x, y)
- self.tree_view.queue_draw_area(x, y, width, height)
- if path == end_path:
- break
- else:
- next_iter = tree_model.iter_next(tree_model.get_iter(path))
- path = tree_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):
- if visible != self._fully_obscured:
- return
-
- 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]))
- }
+class ListView(HomogeneView):
def __init__(self):
- BaseListView.__init__(self)
- self._is_dragging = False
-
- self.tree_view.connect('drag-begin', self.__drag_begin_cb)
- self.tree_view.connect('button-release-event',
- self.__button_release_event_cb)
-
- self.cell_title.connect('edited', self.__cell_title_edited_cb)
- self.cell_title.connect('editing-canceled', self.__editing_canceled_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 __drag_begin_cb(self, widget, drag_context):
- self._is_dragging = True
-
- def __button_release_event_cb(self, tree_view, event):
- try:
- if self._is_dragging:
- return
- finally:
- self._is_dragging = False
-
- pos = tree_view.get_path_at_pos(int(event.x), int(event.y))
- if pos is None:
- return
-
- path, column, x_, y_ = pos
- if column != self._title_column:
- return
-
- row = self.tree_view.get_model()[path]
- metadata = model.get(row[ListModel.COLUMN_UID])
- self.cell_title.props.editable = model.is_editable(metadata)
-
- tree_view.set_cursor_on_cell(path, column, start_editing=True)
-
- def __detail_cell_clicked_cb(self, cell, path):
- row = self.tree_view.get_model()[path]
- self.emit('detail-clicked', row[ListModel.COLUMN_UID])
-
- def __detail_clicked_cb(self, cell, uid):
- self.emit('detail-clicked', uid)
-
- def __icon_clicked_cb(self, cell, 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._model[path]
- metadata = model.get(row[ListModel.COLUMN_UID])
- metadata['title'] = new_text
- model.write(metadata, update_mtime=False)
- self.cell_title.props.editable = False
-
- def __editing_canceled_cb(self, cell):
- self.cell_title.props.editable = False
-
-class CellRendererFavorite(CellRendererIcon):
- __gtype_name__ = 'JournalCellRendererFavorite'
-
- def __init__(self, tree_view):
- CellRendererIcon.__init__(self, tree_view)
-
- self.props.width = style.GRID_CELL_SIZE
- self.props.height = style.GRID_CELL_SIZE
- self.props.size = style.SMALL_ICON_SIZE
- self.props.icon_name = 'emblem-favorite'
- self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
- self.props.prelit_stroke_color = style.COLOR_BUTTON_GREY.get_svg()
- self.props.prelit_fill_color = style.COLOR_BUTTON_GREY.get_svg()
-
-class CellRendererDetail(CellRendererIcon):
- __gtype_name__ = 'JournalCellRendererDetail'
-
- def __init__(self, tree_view):
- CellRendererIcon.__init__(self, tree_view)
-
- self.props.width = style.GRID_CELL_SIZE
- self.props.height = style.GRID_CELL_SIZE
- self.props.size = style.SMALL_ICON_SIZE
- self.props.icon_name = 'go-right'
- self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
- self.props.stroke_color = style.COLOR_TRANSPARENT.get_svg()
- self.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
- self.props.prelit_stroke_color = style.COLOR_TRANSPARENT.get_svg()
- self.props.prelit_fill_color = style.COLOR_BLACK.get_svg()
-
-class CellRendererActivityIcon(CellRendererIcon):
- __gtype_name__ = 'JournalCellRendererActivityIcon'
-
- __gsignals__ = {
- 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([str])),
- }
-
- def __init__(self, tree_view):
- self._show_palette = True
-
- CellRendererIcon.__init__(self, tree_view)
-
- self.props.width = style.GRID_CELL_SIZE
- self.props.height = style.GRID_CELL_SIZE
- self.props.size = style.STANDARD_ICON_SIZE
- self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
-
- self.tree_view = tree_view
-
- def create_palette(self):
- if not self._show_palette:
- return None
-
- tree_model = self.tree_view.get_model()
- metadata = tree_model.get_metadata(self.props.palette_invoker.path)
-
- palette = ObjectPalette(metadata, detail=True)
- palette.connect('detail-clicked',
- self.__detail_clicked_cb)
- return palette
-
- def __detail_clicked_cb(self, palette, uid):
- self.emit('detail-clicked', uid)
-
- def set_show_palette(self, show_palette):
- self._show_palette = show_palette
-
- show_palette = gobject.property(type=bool, default=True,
- setter=set_show_palette)
-
-class CellRendererBuddy(CellRendererIcon):
- __gtype_name__ = 'JournalCellRendererBuddy'
-
- def __init__(self, tree_view, column_index):
- CellRendererIcon.__init__(self, tree_view)
-
- self.props.width = style.STANDARD_ICON_SIZE
- self.props.height = style.STANDARD_ICON_SIZE
- self.props.size = style.STANDARD_ICON_SIZE
- self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
-
- self.tree_view = tree_view
- self._model_column_index = column_index
-
- def create_palette(self):
- tree_model = self.tree_view.get_model()
- row = tree_model[self.props.palette_invoker.path]
-
- if row[self._model_column_index] is not None:
- nick, xo_color = row[self._model_column_index]
- return BuddyPalette((nick, xo_color.to_string()))
- else:
- return None
-
- def set_buddy(self, buddy):
- if buddy is None:
- self.props.icon_name = None
- else:
- nick_, xo_color = buddy
- self.props.icon_name = 'computer-xo'
- self.props.xo_color = xo_color
-
- buddy = gobject.property(type=object, setter=set_buddy)
-
+ HomogeneView.__init__(self, _Cell)
+ self.frame_size = (None, 1)
+ self.cell_size = (None, style.GRID_CELL_SIZE)
diff --git a/src/jarabe/journal/objectchooser.py b/src/jarabe/journal/objectchooser.py
index 49af3e6..bae7b74 100644
--- a/src/jarabe/journal/objectchooser.py
+++ b/src/jarabe/journal/objectchooser.py
@@ -24,8 +24,7 @@ import wnck
from sugar.graphics import style
from sugar.graphics.toolbutton import ToolButton
-from jarabe.journal.listview import BaseListView
-from jarabe.journal.listmodel import ListModel
+from jarabe.journal.view import View
from jarabe.journal.journaltoolbox import SearchToolbar
from jarabe.journal.volumestoolbar import VolumesToolbar
@@ -80,14 +79,16 @@ 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._list_view = ChooserListView()
- self._list_view.connect('entry-activated', self.__entry_activated_cb)
- vbox.pack_start(self._list_view)
- self._list_view.show()
+ self._view = View()
+ self._view.props.hover_selection = True
+ self._view.connect('entry-activated', self.__entry_activated_cb)
+ vbox.pack_start(self._view)
+ self._view.show()
self._toolbar.set_mount_point('/')
@@ -125,7 +126,10 @@ class ObjectChooser(gtk.Window):
return self._selected_object_id
def __query_changed_cb(self, toolbar, query):
- self._list_view.update_with_query(query)
+ self._view.update_with_query(query)
+
+ def __view_changed_cb(self, sender, view):
+ self._view.change_view(view)
def __volume_changed_cb(self, volume_toolbar, mount_point):
logging.debug('Selected volume: %r.', mount_point)
@@ -134,7 +138,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._list_view.set_is_visible(visible)
+ self._view.set_is_visible(visible)
class TitleBox(VolumesToolbar):
__gtype_name__ = 'TitleBox'
@@ -161,39 +165,3 @@ 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/palettes.py b/src/jarabe/journal/palettes.py
index e0dfbf4..10cc47a 100644
--- a/src/jarabe/journal/palettes.py
+++ b/src/jarabe/journal/palettes.py
@@ -33,17 +33,12 @@ from jarabe.model import friends
from jarabe.model import filetransfer
from jarabe.journal import misc
from jarabe.journal import model
+from jarabe.journal import controler
class ObjectPalette(Palette):
__gtype_name__ = 'ObjectPalette'
- __gsignals__ = {
- 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([str])),
- }
-
def __init__(self, metadata, detail=False):
self._metadata = metadata
@@ -135,7 +130,7 @@ class ObjectPalette(Palette):
model.delete(self._metadata['uid'])
def __detail_activate_cb(self, menu_item):
- self.emit('detail-clicked', self._metadata['uid'])
+ controler.objects.emit('detail-clicked', self._metadata['uid'])
def __friend_selected_cb(self, menu_item, buddy):
logging.debug('__friend_selected_cb')
diff --git a/src/jarabe/journal/thumbsview.py b/src/jarabe/journal/thumbsview.py
new file mode 100644
index 0000000..f2db248
--- /dev/null
+++ b/src/jarabe/journal/thumbsview.py
@@ -0,0 +1,101 @@
+# 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 gtk
+import gobject
+import logging
+
+from jarabe.journal.homogeneview import HomogeneView
+from jarabe.journal.homogeneview import Cell
+from jarabe.journal.widgets import *
+
+
+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
+
+
+class _Cell(Cell):
+
+ def __init__(self):
+ Cell.__init__(self)
+
+ cell = gtk.HBox()
+ self.add(cell)
+
+ # toolbar
+
+ toolbar = gtk.VBox()
+ cell.pack_start(toolbar, expand=False)
+
+ self._keep = KeepIcon(
+ box_width=style.GRID_CELL_SIZE)
+ toolbar.pack_start(self._keep, expand=False)
+
+ self._details = DetailsIcon()
+ toolbar.pack_start(self._details, expand=False)
+
+ # thumb
+
+ main = gtk.VBox()
+ cell.pack_end(main)
+
+ #thumb = Thumb()
+ #main.pack_end(thumb)
+
+ # text
+
+ text = gtk.VBox()
+ main.pack_end(text, expand=False)
+
+ self._title = Title(
+ max_line_count=2,
+ xalign=0, yalign=0, xscale=1, yscale=0)
+ text.pack_start(self._title)
+
+ self._date = Timestamp(
+ xalign=0.0,
+ ellipsize=pango.ELLIPSIZE_END)
+ text.pack_end(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)
+
+
+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)
+
+ HomogeneView.do_size_allocate(self, allocation)
diff --git a/src/jarabe/journal/view.py b/src/jarabe/journal/view.py
new file mode 100644
index 0000000..61b11dc
--- /dev/null
+++ b/src/jarabe/journal/view.py
@@ -0,0 +1,292 @@
+# Copyright (C) 2009, Tomeu Vizoso
+# 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
+from gettext import gettext as _
+import time
+
+import gobject
+import gtk
+import hippo
+
+from sugar.graphics import style
+from sugar.graphics.icon import CanvasIcon
+from sugar.graphics.icon import Icon
+
+from jarabe.journal import model
+from jarabe.journal.listview import ListView
+from jarabe.journal.thumbsview import ThumbsView
+
+
+UPDATE_INTERVAL = 300
+
+VIEW_LIST = 0
+VIEW_THUMBS = 1
+_MESSAGE_PROGRESS = 2
+_MESSAGE_EMPTY_JOURNAL = 3
+_MESSAGE_NO_MATCH = 4
+
+PAGE_SIZE = 10
+
+
+class View(gtk.EventBox):
+
+ __gsignals__ = {
+ 'clear-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'entry-activated': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self):
+ gtk.EventBox.__init__(self)
+
+ self._query = {}
+ self._result_set = None
+ self._pages = {}
+ self._current_page = None
+ self._view = None
+ self._last_progress_bar_pulse = None
+
+ self._page_ctors = {
+ VIEW_LIST: lambda: self._view_new(ListView),
+ VIEW_THUMBS: lambda: self._view_new(ThumbsView),
+ _MESSAGE_PROGRESS: self._progress_new,
+ _MESSAGE_EMPTY_JOURNAL: self._message_new,
+ _MESSAGE_NO_MATCH: self._message_new,
+ }
+
+ self.connect('destroy', self.__destroy_cb)
+
+ # Auto-update stuff
+ self._fully_obscured = True
+ self._dirty = False
+ 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)
+
+ self.view = VIEW_LIST
+
+ def get_view(self):
+ return self._pages[self._view].child
+
+ def set_view(self, view):
+ if self._page == self._view:
+ # change view only if current page is view as well
+ self._page = view
+ self._view = view
+ self.view.result_set = self._result_set
+
+ view = property(get_view, set_view)
+
+ def set_hover_selection(self, hover_selection):
+ for i in self._view_widgets:
+ i.child.props.hover_selection = hover_selection
+
+ hover_selection = gobject.property(type=bool, default=False,
+ setter=set_hover_selection)
+
+ def _set_page(self, page):
+ if self._current_page == page:
+ return
+ self._current_page = page
+
+ if page in self._pages:
+ child = self._pages[page]
+ else:
+ child = self._page_ctors[page]()
+ child.show_all()
+ self._pages[page] = child
+
+ if self.child is not None:
+ self.remove(self.child)
+ self.add(child)
+
+ def _get_page(self):
+ return self._current_page
+
+ _page = property(_get_page, _set_page)
+
+ 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 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('View._refresh query %r', self._query)
+
+ if self._result_set is not None:
+ self._result_set.stop()
+ self._dirty = False
+
+ 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()
+
+ 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():
+ self._page = _MESSAGE_EMPTY_JOURNAL
+ else:
+ self._page = _MESSAGE_NO_MATCH
+ else:
+ self._page = self._view
+ self.view.result_set = self._result_set
+
+ 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 __result_set_progress_cb(self, **kwargs):
+ if self._page != _MESSAGE_PROGRESS:
+ self._last_progress_bar_pulse = time.time()
+ self._page = _MESSAGE_PROGRESS
+
+ if time.time() - self._last_progress_bar_pulse > 0.05:
+ self.child.pulse()
+ self._last_progress_bar_pulse = time.time()
+
+ def _view_new(self, view_class):
+ view = view_class()
+ view.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
+ view.connect('entry-activated', self.__entry_activated_cb)
+
+ scrolled_view = gtk.ScrolledWindow()
+ scrolled_view.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scrolled_view.add(view)
+
+ return scrolled_view
+
+ 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)
+
+ return alignment
+
+ def _message_new(self):
+ canvas = hippo.Canvas()
+
+ 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 self._page == _MESSAGE_EMPTY_JOURNAL:
+ text = _('Your Journal is empty')
+ elif self._page == _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 self._page == _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)
+
+ return canvas
+
+ def __clear_button_clicked_cb(self, button):
+ self.emit('clear-clicked')
+
+ def update_dates(self):
+ self.view.refill()
+
+ def _set_dirty(self):
+ if self._fully_obscured:
+ self._dirty = True
+ else:
+ self.refresh()
+
+ def set_is_visible(self, visible):
+ if visible != self._fully_obscured:
+ return
+
+ 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
+
+ def __entry_activated_cb(self, sender, uid):
+ self.emit('entry-activated', uid)
diff --git a/src/jarabe/journal/widgets.py b/src/jarabe/journal/widgets.py
new file mode 100644
index 0000000..7593986
--- /dev/null
+++ b/src/jarabe/journal/widgets.py
@@ -0,0 +1,331 @@
+# Copyright (C) 2006, Red Hat, Inc.
+# Copyright (C) 2007, One Laptop Per Child
+# 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
+from gettext import gettext as _
+
+import gtk
+import gobject
+import hippo
+import gconf
+import pango
+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.roundbox import CanvasRoundBox
+
+from jarabe.journal.entry import Entry
+from jarabe.journal.palettes import BuddyPalette
+from jarabe.journal.palettes import ObjectPalette
+from jarabe.journal import misc
+from jarabe.journal import model
+from jarabe.journal import controler
+
+
+class KeepIconCanvas(CanvasIcon):
+ def __init__(self, **kwargs):
+ CanvasIcon.__init__(self, icon_name='emblem-favorite',
+ size=style.SMALL_ICON_SIZE,
+ **kwargs)
+
+ 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
+ keep = metadata.get('keep', "")
+ if keep.isdigit():
+ self._set_keep(int(keep))
+ else:
+ self._set_keep(0)
+
+ def _set_keep(self, keep):
+ if keep:
+ client = gconf.client_get_default()
+ color = client.get_string('/desktop/sugar/user/color')
+ self._keep_color = XoColor(color)
+ else:
+ self._keep_color = None
+
+ self._set_colors()
+
+ def __motion_notify_event_cb(self, icon, event):
+ if event.detail == hippo.MOTION_DETAIL_ENTER:
+ self._prelight = True
+ elif event.detail == hippo.MOTION_DETAIL_LEAVE:
+ self._prelight = False
+ self._set_colors()
+
+ def _set_colors(self):
+ if self._prelight:
+ if self._keep_color is None:
+ self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
+ self.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
+ else:
+ stroke_color = style.Color(self._keep_color.get_stroke_color())
+ fill_color = style.Color(self._keep_color.get_fill_color())
+ self.props.stroke_color = fill_color.get_svg()
+ self.props.fill_color = stroke_color.get_svg()
+ else:
+ if self._keep_color is None:
+ self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
+ self.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
+ else:
+ self.props.xo_color = self._keep_color
+
+ def __activated_cb(self, icon):
+ if not model.is_editable(self._metadata):
+ return
+
+ if self._keep_color is None:
+ keep = 1
+ else:
+ keep = 0
+
+ self._metadata['keep'] = keep
+ model.write(self._metadata, update_mtime=False)
+
+ self._set_keep(keep)
+
+
+def KeepIcon(**kwargs):
+ return _CanvasToWidget(KeepIconCanvas, **kwargs)
+
+
+class _Launcher(object):
+
+ def __init__(self, launching, detail):
+ self.metadata = None
+ self._detail = detail
+ self._launching = launching
+
+ if launching:
+ 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:
+ return ObjectPalette(self.metadata, detail=self._detail)
+
+ def __button_release_event_cb(self, button, event):
+ if self.metadata is not None:
+ misc.resume(self.metadata)
+ return True
+
+
+class ObjectIconCanvas(_Launcher, CanvasIcon):
+
+ def __init__(self, launching=True, detail=True, **kwargs):
+ CanvasIcon.__init__(self, **kwargs)
+ _Launcher.__init__(self, launching, detail)
+
+ def check_out(self, metadata):
+ self.metadata = 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:
+ self.props.xo_color = misc.get_icon_color(metadata)
+
+
+def ObjectIcon(**kwargs):
+ return _CanvasToWidget(ObjectIconCanvas, **kwargs)
+
+
+class Title(gtk.Alignment):
+
+ def __init__(self, max_line_count=1, **kwargs):
+ gtk.Alignment.__init__(self, **kwargs)
+
+ 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
+ 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)
+ 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)
+
+
+class Buddies(gtk.Alignment):
+
+ def __init__(self, buddies_max=None, **kwargs):
+ gtk.Alignment.__init__(self, **kwargs)
+
+ self._buddies_max = buddies_max
+
+ self._progress = gtk.ProgressBar()
+ self._progress.modify_bg(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self._progress.show()
+
+ self._buddies = gtk.HBox()
+ self._buddies.show()
+
+ def check_out(self, metadata):
+ if self.child is not None:
+ self.remove(self.child)
+
+ child = None
+
+ if 'progress' in metadata:
+ child = self._progress
+ fraction = int(metadata['progress']) / 100.
+ self._progress.props.fraction = fraction
+
+ elif 'buddies' in metadata and metadata['buddies']:
+ child = self._buddies
+
+ buddies = simplejson.loads(metadata['buddies']).values()
+ buddies = buddies[:self._buddies_max]
+
+ def show(icon, buddy):
+ icon.root.buddy = buddy
+ nick_, color = buddy
+ icon.root.props.xo_color = XoColor(color)
+ icon.show()
+
+ for icon in self._buddies:
+ if buddies:
+ show(icon, buddies.pop())
+ else:
+ icon.hide()
+
+ for buddy in buddies:
+ icon = _CanvasToWidget(_BuddyIcon)
+ show(icon, buddy)
+ self._buddies.add(icon)
+
+ if self.child is not child:
+ if self.child is not None:
+ self.remove(self.child)
+ if child is not None:
+ self.add(child)
+
+
+class Timestamp(gtk.Label):
+
+ def __init__(self, **kwargs):
+ gobject.GObject.__init__(self, **kwargs)
+
+ def check_out(self, metadata):
+ self.props.label = misc.get_date(metadata)
+
+
+class DetailsIconCanvas(CanvasIcon):
+
+ def __init__(self):
+ CanvasIcon.__init__(self,
+ box_width=style.GRID_CELL_SIZE,
+ icon_name='go-right',
+ size=style.SMALL_ICON_SIZE,
+ stroke_color=style.COLOR_TRANSPARENT.get_svg())
+
+ 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 _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'])
+
+ 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._set_leave_color()
+
+
+def DetailsIcon(**kwargs):
+ return _CanvasToWidget(DetailsIconCanvas, **kwargs)
+
+
+class ThumbCanvas(_Launcher, hippo.CanvasWidget):
+
+ def __init__(self, cell, **kwargs):
+ hippo.CanvasWidget.__init__(self, **kwargs)
+ _Launcher.__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 _BuddyIcon(CanvasIcon):
+
+ def __init__(self):
+ CanvasIcon.__init__(self,
+ icon_name='computer-xo',
+ size=style.STANDARD_ICON_SIZE)
+
+ self.buddy = None
+
+ def create_palette(self):
+ return BuddyPalette(self.buddy)
+
+
+class _CanvasToWidget(hippo.Canvas):
+
+ def __init__(self, canvas_class, **kwargs):
+ hippo.Canvas.__init__(self)
+
+ self.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_WHITE.get_gdk_color())
+
+ self.root = canvas_class(**kwargs)
+ self.set_root(self.root)
+
+ def check_out(self, metadata):
+ self.root.check_out(metadata)