Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAleksey Lim <alsroot@member.fsf.org>2009-08-20 00:05:09 (GMT)
committer Aleksey Lim <alsroot@member.fsf.org>2009-08-25 15:47:56 (GMT)
commitf74e5d1fdcd96dba7f4e37dfc5e9b8ef8e6e02d3 (patch)
tree37c8258e67a520742fcf2fc9dbf0a1066c15923e /src
parent9837838cabbc1873ab4733aabca4150bebe842e6 (diff)
Extract objectsview from listview; initial thumbs view commit
Diffstat (limited to 'src')
-rw-r--r--src/jarabe/journal/browse/__init__.py15
-rw-r--r--src/jarabe/journal/browse/lazymodel.py401
-rw-r--r--src/jarabe/journal/browse/localsource.py41
-rw-r--r--src/jarabe/journal/browse/source.py85
-rw-r--r--src/jarabe/journal/browse/tableview.py264
-rw-r--r--src/jarabe/journal/browse/treeview.py175
-rw-r--r--src/jarabe/journal/journalactivity.py35
-rw-r--r--src/jarabe/journal/journaltoolbox.py30
-rw-r--r--src/jarabe/journal/listmodel.py21
-rw-r--r--src/jarabe/journal/listview.py310
-rw-r--r--src/jarabe/journal/objectchooser.py45
-rw-r--r--src/jarabe/journal/objectsview.py315
-rw-r--r--src/jarabe/journal/thumbsview.py41
13 files changed, 1453 insertions, 325 deletions
diff --git a/src/jarabe/journal/browse/__init__.py b/src/jarabe/journal/browse/__init__.py
new file mode 100644
index 0000000..307e9c3
--- /dev/null
+++ b/src/jarabe/journal/browse/__init__.py
@@ -0,0 +1,15 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
diff --git a/src/jarabe/journal/browse/lazymodel.py b/src/jarabe/journal/browse/lazymodel.py
new file mode 100644
index 0000000..b11f02f
--- /dev/null
+++ b/src/jarabe/journal/browse/lazymodel.py
@@ -0,0 +1,401 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import logging
+from gobject import GObject, SIGNAL_RUN_FIRST, TYPE_PYOBJECT
+
+class Source(GObject):
+ __gsignals__ = {
+ 'objects-updated': (SIGNAL_RUN_FIRST, None, []),
+ 'row-delayed-fetch': (SIGNAL_RUN_FIRST, None, 2*[TYPE_PYOBJECT]) }
+
+ def get_count(self):
+ """ Returns number of objects """
+ pass
+
+ def get_row(self, offset):
+ """ Get object
+
+ Returns:
+ objects in dict {field_name: value, ...}
+ False can't fint object
+ None wait for reply signal
+
+ """
+ pass
+
+ def get_order(self):
+ """ Get current order, returns (field_name, gtk.SortType) """
+ pass
+
+ def set_order(self, field_name, sort_type):
+ """ Set current order """
+ pass
+
+class LazyModel(gtk.GenericTreeModel):
+ def __init__(self, columns, calc_columns={}):
+ """ columns/calc_columns = {field_name: (column_num, column_type)} """
+ gtk.GenericTreeModel.__init__(self)
+
+ self._columns_by_name = {}
+ self._columns_by_num = {}
+ self._columns_types = {}
+
+ for name, i in columns.items():
+ self._columns_by_name[name] = i[0]
+ self._columns_by_num[i[0]] = name
+ self._columns_types[i[0]] = i[1]
+
+ for name, i in calc_columns.items():
+ self._columns_types[i[0]] = i[1]
+
+ self._n_columns = max(self._columns_types.keys()) + 1
+
+ self._source = None
+ self._closing = False
+ self._view = None
+
+ self.set_source(None, force=True)
+ self.set_view(None, force=True)
+
+ def on_calc_value(self, row, column):
+ # stub
+ pass
+
+ def get_source(self):
+ return self._source
+
+ def set_source(self, source, force=False):
+ if self._source == source and not force:
+ return
+
+ if self._source is not None:
+ self._source.disconnect_by_func(self.refresh)
+ self._source.disconnect_by_func(self._delayed_fetch_cb)
+
+ self._source = source
+ if self._source is not None:
+ self._source.connect('objects-updated', self.refresh)
+ self._source.connect('row-delayed-fetch', self._delayed_fetch_cb)
+
+ self.refresh()
+
+ source = property(get_source, set_source)
+
+ def get_view(self):
+ return self._view
+
+ def set_view(self, view, force=False):
+ if self._view == view and not force:
+ return
+
+ cursor = None
+
+ if self._view is not None:
+ cursor = self._view.get_cursor()
+ try:
+ self._closing = True
+ self._view.set_model(None)
+ finally:
+ self._closing = False
+
+ self._view = view
+ self._cache = {}
+ self._frame = (0, -1)
+ self._in_process = {}
+ self._postponed = []
+ self._last_count = self._source and self._source.get_count() or 0
+
+ if self._source is not None and view is not None:
+ self._update_columns()
+ view.set_model(self)
+ if cursor is not None:
+ view.set_cursor(*cursor)
+
+ view = property(get_view, set_view)
+
+ def get_order(self):
+ if not self._source:
+ return None
+ order = self._source.get_order()
+ if order is None:
+ return None
+ return (self._columns_by_name[order[0]], order[1])
+
+ def set_order(self, column, order):
+ if not self._source:
+ return
+ self._source.set_order(self._columns_by_num[column], order)
+ self._update_columns()
+
+ def refresh(self, sender=None):
+ if self._source is None or self._view is None:
+ return
+
+ if self._last_count == 0:
+ self.set_view(self._view, force=True)
+
+ self._update_columns()
+
+ count = self._source.get_count()
+
+ for i in range(self._last_count, count):
+ self.emit('row-inserted', (i,), self.get_iter((i,)))
+
+ for i in reversed(range(count, self._last_count)):
+ self.emit('row-deleted', (i,))
+
+ if self._frame[0] >= count:
+ self._frame = (0, -1)
+ elif self._frame[1] >= count:
+ for i in range(count, self._frame[1]):
+ if self._cache.has_key(i):
+ del self._cache[i]
+ self._frame = (self._frame[0], count-1)
+ self._cache = {}
+ for i in range(self._frame[0], self._frame[1]+1):
+ self.emit('row-changed', (i,), self.get_iter((i,)))
+
+ self._last_count = count
+
+ def recalc(self, fields):
+ for i, row in self._cache.items():
+ for field in fields:
+ if row.has_key(field):
+ del row[i]
+ self.emit('row-changed', (i,), self.get_iter((i,)))
+
+ def get_row(self, pos, frame=None):
+ if not self._source:
+ return False
+ if not isinstance(pos, tuple):
+ pos = self.get_path(pos)
+ return self._get_row(pos[0], frame or (pos, pos))
+
+ def _delayed_fetch_cb(self, source, offset, object):
+ if not self._in_process.has_key(offset):
+ logging.debug('_delayed_fetch_cb: no offset=%s' % offset)
+ return
+
+ logging.debug('_delayed_fetch_cb: get %s' % offset)
+
+ path = (offset,)
+ iter = self.get_iter(path)
+ row = Row(self, path, iter, object)
+
+ if self.in_frame(offset):
+ self._cache[offset] = row
+
+ del self._in_process[offset]
+ self.emit('row-changed', path, iter)
+ if self._in_process:
+ return
+
+ while self._postponed:
+ offset, force = self._postponed.pop()
+ if not force and not self.in_frame(offset):
+ continue
+ row = self.get_row((offset,))
+ if row:
+ self.emit('row-changed', row.path, row.iter)
+ else:
+ break
+
+ def _get_row(self, offset, frame):
+ def fetch():
+ row = self._source.get_row(offset)
+
+ if not row:
+ if row is not None:
+ logging.debug('_get_row: can not find row for %s' % offset)
+ return False
+ logging.debug('_get_row: wait for reply for %s' % offset)
+ self._in_process[offset] = True
+ return None
+
+ row = Row(self, (offset,), self.get_iter(offset), row)
+ self._cache[offset] = row
+ return row
+
+ out = self._cache.get(offset)
+ if out:
+ return out
+
+ if frame[0] >= frame[1]:
+ # just return requested single row and do not change cache
+ # if requested frame has <= 1 rows
+ if self._in_process:
+ self._postponed.append((offset, True))
+ return None
+ else:
+ return fetch()
+
+ if frame != self._frame:
+ # switch to new frame
+ intersect_min = max(frame[0], self._frame[0])
+ intersect_max = min(frame[1], self._frame[1])
+ if intersect_min > intersect_max:
+ self._cache = {}
+ else:
+ for i in range(self._frame[0], intersect_min):
+ if self._cache.has_key(i):
+ del self._cache[i]
+ for i in range(intersect_max+1, self._frame[1]+1):
+ if self._cache.has_key(i):
+ del self._cache[i]
+ self._frame = frame
+
+ if self._in_process:
+ self._postponed.append((offset, False))
+ return None
+
+ return fetch()
+
+ def _update_columns(self):
+ order = self.get_order()
+ if not order or not hasattr(self._view, 'get_columns'):
+ return
+
+ for column in self._view.get_columns():
+ if column.get_sort_column_id() == order[0]:
+ column.props.sort_indicator = True
+ column.props.sort_order = order[1]
+ else:
+ column.props.sort_indicator = False
+
+ def in_frame(self, offset):
+ return offset >= self._frame[0] and offset <= self._frame[1]
+
+ # interface implementation -------------------------------------------------
+
+ def on_get_n_columns(self):
+ return self._n_columns
+
+ def on_get_column_type(self, index):
+ return self._columns_types.get(index, bool)
+
+ def on_iter_n_children(self, iter):
+ if iter is None and not self._closing:
+ return self._source.get_count()
+ else:
+ return 0
+
+ def on_get_value(self, offset, column):
+ if not self._view or offset >= self._source.get_count():
+ return None
+
+ # return value only if iter came from visible range
+ # (on setting model, gtk.TreeView scans all items)
+ range = self._view.get_visible_range()
+ if range and offset >= range[0][0] and offset <= range[1][0]:
+ row = self._get_row(offset, (range[0][0], range[1][0]))
+ return row and row[column]
+
+ return None
+
+ def on_iter_nth_child(self, iter, n):
+ return n
+
+ def on_get_path(self, iter):
+ return (iter)
+
+ def on_get_iter(self, path):
+ if self._source.get_count() and not self._closing:
+ return path[0]
+ else:
+ return False
+
+ def on_iter_next(self, iter):
+ if iter != None:
+ if iter >= self._source.get_count() - 1 or self._closing:
+ 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
+
+class Row:
+ def __init__(self, model, path, iter, object):
+ self.model = model
+ self.iter = iter
+ self.path = path
+ self.object = object
+ self.row = [None] * len(model._columns_by_name)
+ self._calced_row = {}
+
+ for name, value in object.items():
+ column = model._columns_by_name.get(str(name), -1)
+ if column != -1:
+ self.row[column] = value
+
+ def __getitem__(self, key):
+ if isinstance(key, int):
+ if key < len(self.row):
+ return self.row[key]
+ else:
+ if self._calced_row.has_key(key):
+ return self._calced_row[key]
+ else:
+ value = self.model.on_calc_value(self, key)
+ if value is not None:
+ self._calced_row[key] = value
+ return value
+ else:
+ return self.object[key]
+
+ def __setitem__(self, key, value):
+ if isinstance(key, int):
+ if key < len(self.row):
+ self.row[key] = value
+ else:
+ self._calced_row[key] = value
+ else:
+ self.object[key] = value
+
+ def __delitem__(self, key):
+ if isinstance(key, int):
+ if key < len(self.row):
+ del self.row[key]
+ else:
+ del self._calced_row[key]
+ else:
+ del self.object[key]
+
+ def __contains__(self, key):
+ if isinstance(key, int):
+ return key < len(self.row)
+ else:
+ return self.object.__contains__(key)
+
+ def has_key(self, key):
+ return self.__contains__(key)
+
+ def get(self, key, default=None):
+ if self.has_key(key):
+ return self.__getitem__(key)
+ else:
+ return default
diff --git a/src/jarabe/journal/browse/localsource.py b/src/jarabe/journal/browse/localsource.py
new file mode 100644
index 0000000..e19941e
--- /dev/null
+++ b/src/jarabe/journal/browse/localsource.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+
+from jarabe.journal.browse.source import Source
+
+class LocalSource(Source):
+ def __init__(self, resultset):
+ Source.__init__(self)
+ self._resultset = resultset
+
+ def get_count(self):
+ return self._resultset.length
+
+ def get_row(self, offset):
+ if offset >= self.get_count():
+ return False
+ self._resultset.seek(offset)
+ return self._resultset.read()
+
+ def get_order(self):
+ """ Get current order, returns (field_name, gtk.SortType) """
+ pass
+
+ def set_order(self, field_name, sort_type):
+ """ Set current order """
+ pass
diff --git a/src/jarabe/journal/browse/source.py b/src/jarabe/journal/browse/source.py
new file mode 100644
index 0000000..64fa2ab
--- /dev/null
+++ b/src/jarabe/journal/browse/source.py
@@ -0,0 +1,85 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+from gobject import property, GObject, SIGNAL_RUN_FIRST, TYPE_PYOBJECT
+
+FIELD_UID = 0
+FIELD_TITLE = 1
+FIELD_MTIME = 2
+FIELD_TIMESTAMP = 3
+FIELD_KEEP = 4
+FIELD_BUDDIES = 5
+FIELD_ICON_COLOR = 6
+FIELD_MIME_TYPE = 7
+FIELD_PROGRESS = 8
+FIELD_ACTIVITY = 9
+FIELD_MOUNT_POINT = 10
+FIELD_ACTIVITY_ID = 11
+FIELD_BUNDLE_ID = 12
+
+FIELD_FAVORITE = 30
+FIELD_ICON = 31
+FIELD_MODIFY_TIME = 32
+FIELD_THUMB = 33
+
+FIELDS_LIST = {'uid': (FIELD_UID, str),
+ 'title': (FIELD_TITLE, str),
+ 'mtime': (FIELD_MTIME, str),
+ 'timestamp': (FIELD_TIMESTAMP, int),
+ 'keep': (FIELD_KEEP, int),
+ 'buddies': (FIELD_BUDDIES, str),
+ 'icon-color': (FIELD_ICON_COLOR, str),
+ 'mime_type': (FIELD_MIME_TYPE, str),
+ 'progress': (FIELD_MIME_TYPE, str),
+ 'activity': (FIELD_ACTIVITY, str),
+ 'mountpoint': (FIELD_ACTIVITY, str),
+ 'activity_id': (FIELD_ACTIVITY_ID, str),
+ 'bundle_id': (FIELD_BUNDLE_ID, str),
+
+ 'favorite': (FIELD_FAVORITE, bool),
+ 'icon': (FIELD_ICON, str),
+ 'modify_time': (FIELD_MODIFY_TIME, str),
+ 'thumb': (FIELD_THUMB, gtk.gdk.Pixbuf)}
+
+class Source(GObject):
+ __gsignals__ = {
+ 'objects-updated': (SIGNAL_RUN_FIRST, None, []),
+ 'row-delayed-fetch': (SIGNAL_RUN_FIRST, None, 2*[TYPE_PYOBJECT])
+ }
+
+ def get_count(self):
+ """ Returns number of objects """
+ pass
+
+ def get_row(self, offset):
+ """ Get object
+
+ Returns:
+ objects in dict {field_name: value, ...}
+ False can't fint object
+ None wait for reply signal
+
+ """
+ pass
+
+ def get_order(self):
+ """ Get current order, returns (field_name, gtk.SortType) """
+ pass
+
+ def set_order(self, field_name, sort_type):
+ """ Set current order """
+ pass
diff --git a/src/jarabe/journal/browse/tableview.py b/src/jarabe/journal/browse/tableview.py
new file mode 100644
index 0000000..34fe42b
--- /dev/null
+++ b/src/jarabe/journal/browse/tableview.py
@@ -0,0 +1,264 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import hippo
+import math
+import gobject
+import logging
+
+from sugar.graphics import style
+from sugar.graphics.roundbox import CanvasRoundBox
+
+COLOR_BACKGROUND = style.COLOR_WHITE
+COLOR_SELECTED = style.COLOR_TEXT_FIELD_GREY
+
+class TableCell:
+ def __init__(self):
+ self.row = None
+
+ def fillin(self):
+ pass
+
+ def on_release(self, widget, event):
+ pass
+
+class TableView(gtk.Viewport):
+ def __init__(self, cell_class, rows, cols):
+ gobject.GObject.__init__(self)
+
+ self._cell_class = cell_class
+ self._rows = rows
+ self._cols = cols
+ self._cells = []
+ self._model = None
+ self._hover_selection = True
+ self._full_adjustment = None
+ self._full_height = 0
+ self._selected_cell = None
+
+ self._table = gtk.Table()
+ self._table.show()
+
+ for y in range(self._rows + 1):
+ self._cells.append(self._cols * [None])
+ for x in range(self._cols):
+ canvas = hippo.Canvas()
+ canvas.show()
+ canvas.modify_bg(gtk.STATE_NORMAL,
+ COLOR_BACKGROUND.get_gdk_color())
+
+ sel_box = CanvasRoundBox()
+ sel_box.props.border_color = COLOR_BACKGROUND.get_int()
+ canvas.set_root(sel_box)
+ canvas.root = sel_box
+
+ cell = self._cell_class()
+ sel_box.append(cell, hippo.PACK_EXPAND)
+
+ if self._hover_selection:
+ canvas.connect('enter-notify-event',
+ self.__enter_notify_event_cb, cell)
+ canvas.connect('leave-notify-event',
+ self.__leave_notify_event_cb)
+ canvas.connect('button-release-event',
+ self.__button_release_event_cb, cell)
+
+ self._table.attach(canvas, x, x + 1, y, y + 1,
+ gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, 0, 0)
+ self._cells[y][x] = (canvas, cell)
+
+ smooth_box = gtk.ScrolledWindow()
+ smooth_box.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
+ smooth_box.show()
+ smooth_box.add_with_viewport(self._table)
+ self.add(smooth_box)
+
+ self.connect('key-press-event', self.__key_press_event_cb)
+
+ def do_set_scroll_adjustments(self, hadj, vadj):
+ if vadj is None:
+ return
+ if self._full_adjustment is not None:
+ self._full_adjustment.disconnect_by_func(
+ self.__adjustment_value_changed)
+ self._full_adjustment = vadj
+ self._full_adjustment.connect('value-changed',
+ self.__adjustment_value_changed)
+ self._setup_adjustment()
+
+ def get_size(self):
+ return (self._cols, self._rows)
+
+ def get_cursor(self):
+ frame = self._get_frame()
+ return (frame[0],)
+
+ def set_cursor(self, cursor):
+ if self._full_adjustment is None:
+ return
+ #self._full_adjustment.props.value = cursor
+
+ def get_model(self):
+ return self._model
+
+ def set_model(self, model):
+ if self._model == model:
+ return
+ if self._model:
+ self._model.disconnect_by_func(self.__row_changed_cb)
+ self._model.disconnect_by_func(self.__table_resized_cb)
+ self._model = model
+ if model:
+ self._model.connect('row-changed', self.__row_changed_cb)
+ self._model.connect('row-inserted', self.__table_resized_cb)
+ self._model.connect('row-deleted', self.__table_resized_cb)
+ self._setup_adjustment()
+
+ model = gobject.property(type=object,
+ getter=get_model, setter=set_model)
+
+ def get_hover_selection(self):
+ return self._hover_selection
+
+ def set_hover_selection(self, value):
+ self._hover_selection = value
+
+ hover_selection = gobject.property(type=object,
+ getter=get_hover_selection, setter=set_hover_selection)
+
+ def get_visible_range(self):
+ frame = self._get_frame()
+ return ((frame[0],), (frame[1],))
+
+ def do_size_allocate(self, alloc):
+ gtk.Viewport.do_size_allocate(self, alloc)
+ self._full_height = alloc.height + 100
+ self._table.set_size_request(-1, self._full_height)
+ self._setup_adjustment()
+
+ def _fillin_cell(self, canvas, cell):
+ if cell.row is None:
+ cell.set_visible(False)
+ else:
+ cell.fillin()
+ cell.set_visible(True)
+
+ bg_color = COLOR_BACKGROUND
+ if self._selected_cell == cell:
+ if cell.get_visible():
+ bg_color = COLOR_SELECTED
+ canvas.root.props.background_color = bg_color.get_int()
+
+ def _setup_adjustment(self):
+ if self._full_adjustment is None or self._full_height == 0:
+ return
+
+ adj = self._full_adjustment.props
+
+ if self._model is None:
+ adj.upper = 0
+ adj.value = 0
+ return
+
+ if self._cols == 0:
+ adj.upper = 0
+ else:
+ count = self._model.iter_n_children(None)
+ adj.upper = int(math.ceil(float(count) / self._cols))
+
+ adj.value = min(adj.value, adj.upper - self._rows)
+ adj.page_size = self._rows
+ adj.page_increment = self._rows
+ self._full_adjustment.changed()
+
+ self.__adjustment_value_changed(self._full_adjustment)
+
+ def _get_frame(self):
+ return (int(self._full_adjustment.props.value) * self._cols,
+ (int(self._full_adjustment.props.value) + self._rows) * self._cols - 1)
+
+ def __row_changed_cb(self, model, path, iter):
+ range = self._get_frame()
+ if path[0] < range[0] or path[0] > range[1]:
+ return
+
+ y = (path[0] - range[0]) / self._cols
+ x = (path[0] - range[0]) % self._cols
+
+ canvas, cell = self._cells[y][x]
+ cell.row = self._model.get_row(path)
+ self._fillin_cell(canvas, cell)
+
+ def __table_resized_cb(self, model=None, path=None, iter=None):
+ self._setup_adjustment()
+
+ def __key_press_event_cb(self, widget, event):
+ if self._full_adjustment is None or self._full_height == 0:
+ return
+
+ adj = self._full_adjustment.props
+ uplimit = adj.upper - self._rows
+
+ if event.keyval == gtk.keysyms.Up:
+ adj.value -= 1
+ elif event.keyval == gtk.keysyms.Down:
+ if adj.value + 1 <= uplimit:
+ adj.value += 1
+ elif event.keyval in (gtk.keysyms.Page_Up, gtk.keysyms.KP_Page_Up):
+ adj.value -= self._rows
+ elif event.keyval in (gtk.keysyms.Page_Down, gtk.keysyms.KP_Page_Down):
+ if adj.value + self._rows <= uplimit:
+ adj.value += self._rows
+ elif event.keyval in (gtk.keysyms.Home, gtk.keysyms.KP_Home):
+ adj.value = 0
+ elif event.keyval in (gtk.keysyms.End, gtk.keysyms.KP_End):
+ adj.value = uplimit
+ else:
+ return False
+
+ return True
+
+ def __button_release_event_cb(self, widget, event, cell):
+ cell.on_release(widget, event)
+
+ def __enter_notify_event_cb(self, canvas, event, cell):
+ if cell.get_visible():
+ canvas.root.props.background_color = COLOR_SELECTED.get_int()
+ self._selected_cell = cell
+
+ def __leave_notify_event_cb(self, canvas, event):
+ canvas.root.props.background_color = COLOR_BACKGROUND.get_int()
+ self._selected_cell = None
+
+ def __adjustment_value_changed(self, adjustment):
+ if self._model:
+ count = self._model.iter_n_children(None)
+ else:
+ count = 0
+ cell_num = int(adjustment.props.value) * self._cols
+
+ for y in range(self._rows):
+ for x in range(self._cols):
+ canvas, cell = self._cells[y][x]
+
+ cell.row = None
+ if cell_num < count:
+ cell.row = self._model.get_row((cell_num,),
+ self._get_frame())
+
+ self._fillin_cell(canvas, cell)
+ cell_num += 1
diff --git a/src/jarabe/journal/browse/treeview.py b/src/jarabe/journal/browse/treeview.py
new file mode 100644
index 0000000..0980405
--- /dev/null
+++ b/src/jarabe/journal/browse/treeview.py
@@ -0,0 +1,175 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+import logging
+
+from sugar.graphics.palette import Invoker
+
+_SHOW_PALETTE_TIMEOUT = 200
+
+class TreeView(gtk.TreeView):
+ def __init__(self):
+ gtk.TreeView.__init__(self)
+ self._invoker = _TreeInvoker(self)
+
+ def set_cursor(self, path, column, edit=False):
+ if path is not None:
+ gtk.TreeView.set_cursor(self, path, column, edit)
+
+ def append_column(self, column):
+ if isinstance(column, TreeViewColumn):
+ column.view = self
+ return gtk.TreeView.append_column(self, column)
+
+ def create_palette(self):
+ return self._invoker.cell_palette
+
+class TreeViewColumn(gtk.TreeViewColumn):
+ def __init__(self, title=None, cell=None, **kwargs):
+ gtk.TreeViewColumn.__init__(self, title, cell, **kwargs)
+ self.view = None
+ self._order_by = None
+ self.palette_cb = None
+ self.connect('clicked', self._clicked_cb)
+
+ def set_sort_column_id(self, field):
+ self.props.clickable = True
+ self._order_by = field
+
+ def get_sort_column_id(self):
+ return self._order_by
+
+ def _clicked_cb(self, column):
+ if not self.view:
+ return
+
+ if self.props.sort_indicator:
+ if self.props.sort_order == gtk.SORT_DESCENDING:
+ new_order = gtk.SORT_ASCENDING
+ else:
+ new_order = gtk.SORT_DESCENDING
+ else:
+ new_order = gtk.SORT_ASCENDING
+
+ self.view.get_model().set_order(self._order_by, new_order)
+
+class _TreeInvoker(Invoker):
+ def __init__(self, tree=None):
+ Invoker.__init__(self)
+ self._position_hint = self.AT_CURSOR
+
+ self._tree = None
+ self.cell_palette = None
+ self._palette_pos = None
+ self._enter_timeout = None
+
+ self._enter_hid = None
+ self._motion_hid = None
+ self._leave_hid = None
+ self._button_hid = None
+
+ if tree:
+ self.attach(tree)
+
+ def get_toplevel(self):
+ return self._tree.get_toplevel()
+
+ def attach(self, tree):
+ self._tree = tree
+ self._enter_hid = tree.connect('enter-notify-event', self._enter_cb)
+ self._motion_hid = tree.connect('motion-notify-event', self._enter_cb)
+ self._leave_hid = tree.connect('leave-notify-event', self._leave_cb)
+ self._button_hid = tree.connect('button-release-event', self._button_cb)
+ Invoker.attach(self, tree)
+
+ def detach(self):
+ Invoker.detach(self)
+ self._tree.disconnect(self._enter_hid)
+ self._tree.disconnect(self._motion_hid)
+ self._tree.disconnect(self._leave_hid)
+ self._tree.disconnect(self._button_cb)
+
+ def _close_palette(self):
+ if self._enter_timeout:
+ gobject.source_remove(self._enter_timeout)
+ self._enter_timeout = None
+ self.cell_palette = None
+ self._palette_pos = None
+
+ def _open_palette(self, notify, force):
+ if self._enter_timeout:
+ gobject.source_remove(self._enter_timeout)
+ self._enter_timeout = None
+
+ coords = self._tree.convert_widget_to_bin_window_coords(
+ *self._tree.get_pointer())
+
+ pos = self._tree.get_path_at_pos(*coords)
+ if not pos:
+ self._close_palette()
+ return False
+
+ path, column, x, y = pos
+ if not hasattr(column, 'palette_cb') or not column.palette_cb:
+ self._close_palette()
+ return False
+
+ row = self._tree.props.model.get_row(path)
+ if not row:
+ logging.debug('_open_palette: wait for row %s' % path)
+ self._enter_timeout = gobject.timeout_add(500, self._open_palette,
+ self.notify_mouse_enter, False)
+ return False
+
+ palette = column.palette_cb(self._tree.props.model, row, x, y)
+ if not palette:
+ self._close_palette()
+ return False
+
+ if self._palette_pos != (path, column) or self.cell_palette != palette:
+ if self.palette:
+ self.palette.popdown(True)
+ self.palette = None
+
+ self._palette_pos = (path, column)
+ self.cell_palette = palette
+ notify()
+
+ return False
+
+ def notify_popup(self):
+ Invoker.notify_popup(self)
+
+ def notify_popdown(self):
+ Invoker.notify_popdown(self)
+
+ def _enter_cb(self, widget, event):
+ if self._enter_timeout:
+ gobject.source_remove(self._enter_timeout)
+ self._enter_timeout = gobject.timeout_add(_SHOW_PALETTE_TIMEOUT,
+ self._open_palette, self.notify_mouse_enter, False)
+
+ def _leave_cb(self, widget, event):
+ self.notify_mouse_leave()
+ self._close_palette()
+
+ def _button_cb(self, widget, event):
+ if event.button == 3:
+ return self._open_palette(self.notify_right_click, True)
+ else:
+ return False
diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py
index 08a5a0f..10a89bf 100644
--- a/src/jarabe/journal/journalactivity.py
+++ b/src/jarabe/journal/journalactivity.py
@@ -18,7 +18,7 @@
import logging
from gettext import gettext as _
import sys
-import traceback
+import traceback
import uuid
import gtk
@@ -34,7 +34,7 @@ from sugar import wm
from jarabe.model import bundleregistry
from jarabe.journal.journaltoolbox import MainToolbox, DetailToolbox
-from jarabe.journal.listview import ListView
+from jarabe.journal.objectsview import ObjectsView
from jarabe.journal.detailview import DetailView
from jarabe.journal.volumestoolbar import VolumesToolbar
from jarabe.journal import misc
@@ -105,11 +105,14 @@ class JournalActivity(Window):
logging.debug("STARTUP: Loading the journal")
Window.__init__(self)
+ accel_group = gtk.AccelGroup()
+ self.set_data('sugar-accel-group', accel_group)
+ self.add_accel_group(accel_group)
+
self.set_title(_('Journal'))
self._main_view = None
self._secondary_view = None
- self._list_view = None
self._detail_view = None
self._main_toolbox = None
self._detail_toolbox = None
@@ -131,10 +134,10 @@ class JournalActivity(Window):
model.updated.connect(self.__model_updated_cb)
model.deleted.connect(self.__model_deleted_cb)
- self._dbus_service = JournalActivityDBusService(self)
+ self._dbus_service = JournalActivityDBusService(self)
self.iconify()
-
+
self._critical_space_alert = None
self._check_available_space()
@@ -152,11 +155,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._objects_view = ObjectsView()
+ self._objects_view.connect('clear-clicked', self.__clear_clicked_cb)
+ self._objects_view.connect('detail-clicked', self.__detail_clicked_cb)
+ self._main_view.pack_start(self._objects_view)
+ self._objects_view.show()
self._volumes_toolbar = VolumesToolbar()
self._volumes_toolbar.connect('volume-changed',
@@ -165,6 +168,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):
@@ -195,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._objects_view.update_with_query(query)
self.show_main_view()
+ def __view_changed_cb(self, sender, view):
+ self._objects_view.change_view(view)
+
def show_main_view(self):
if self.toolbox != self._main_toolbox:
self.set_toolbox(self._main_toolbox)
@@ -261,7 +268,7 @@ class JournalActivity(Window):
def _focus_in_event_cb(self, window, event):
self.search_grab_focus()
- self._list_view.update_dates()
+ self._objects_view.update_dates()
def _check_for_bundle(self, object_id):
registry = bundleregistry.get_registry()
@@ -300,12 +307,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._objects_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._objects_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 201bf76..a201550 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
@@ -73,7 +74,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 +118,34 @@ class SearchToolbar(gtk.Toolbar):
#self.insert(tool_item, -1)
#tool_item.show()
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ list_button = RadioToolButton(named_icon='view-list')
+ list_button.props.tooltip = _('List view')
+ list_button.props.accelerator = _('<Ctrl>1')
+ list_button.connect('toggled', self.__view_button_toggled_cb, 0)
+ self.insert(list_button, -1)
+ list_button.show()
+
+ thumb_button = RadioToolButton(named_icon='view-thumbs')
+ thumb_button.props.group = list_button
+ thumb_button.props.tooltip = _('Thumbs view')
+ thumb_button.props.accelerator = _('<Ctrl>2')
+ thumb_button.connect('toggled', self.__view_button_toggled_cb, 1)
+ self.insert(thumb_button, -1)
+ thumb_button.show()
+
self._query = self._build_query()
self.refresh_filters()
+ def __view_button_toggled_cb(self, button, view_num):
+ self.emit('view-changed', view_num)
+
def give_entry_focus(self):
self._search_entry.grab_focus()
diff --git a/src/jarabe/journal/listmodel.py b/src/jarabe/journal/listmodel.py
index 917fbb1..6f0d4f1 100644
--- a/src/jarabe/journal/listmodel.py
+++ b/src/jarabe/journal/listmodel.py
@@ -65,35 +65,18 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource):
COLUMN_BUDDY_3: object,
COLUMN_BUDDY_2: object}
- _PAGE_SIZE = 10
-
- def __init__(self, query):
+ def __init__(self, result_set):
gobject.GObject.__init__(self)
self._last_requested_index = None
self._cached_row = None
- self._result_set = model.find(query, ListModel._PAGE_SIZE)
+ self._result_set = result_set
self._temp_drag_file_path = None
# HACK: The view will tell us that it is resizing so the model can
# avoid hitting D-Bus and disk.
self.view_is_resizing = False
- self._result_set.ready.connect(self.__result_set_ready_cb)
- self._result_set.progress.connect(self.__result_set_progress_cb)
-
- def __result_set_ready_cb(self, **kwargs):
- self.emit('ready')
-
- def __result_set_progress_cb(self, **kwargs):
- self.emit('progress')
-
- def setup(self):
- self._result_set.setup()
-
- def stop(self):
- self._result_set.stop()
-
def get_metadata(self, path):
return model.get(self[path][ListModel.COLUMN_UID])
diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
index 251388d..36cd06a 100644
--- a/src/jarabe/journal/listview.py
+++ b/src/jarabe/journal/listview.py
@@ -20,12 +20,11 @@ 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.icon import CellRendererIcon
from sugar.graphics.xocolor import XoColor
from sugar import util
@@ -61,32 +60,14 @@ class BaseListView(gtk.Bin):
__gtype_name__ = 'JournalBaseListView'
__gsignals__ = {
- 'clear-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([]))
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([object]))
}
def __init__(self):
- self._query = {}
- self._model = None
- self._progress_bar = None
- self._last_progress_bar_pulse = None
-
- gobject.GObject.__init__(self)
-
- 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()
- 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()
+ gtk.TreeView.__init__(self)
+ self.props.fixed_height_mode = True
self.cell_title = None
self.cell_icon = None
@@ -94,32 +75,45 @@ class BaseListView(gtk.Bin):
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)
+ self.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
+ [('text/uri-list', 0, 0), ('journal-object-id', 0, 0)],
+ gtk.gdk.ACTION_COPY)
+
+ self.cell_title.props.editable = True
+ self.cell_title.connect('edited', self.__cell_title_edited_cb)
- # Auto-update stuff
- self._fully_obscured = True
- self._dirty = False
- self._refresh_idle_handler = None
- self._update_dates_timer = None
+ self.cell_icon.connect('clicked', self.__icon_clicked_cb)
+ self.cell_icon.connect('detail-clicked', self.__detail_clicked_cb)
- model.created.connect(self.__model_created_cb)
- model.updated.connect(self.__model_updated_cb)
- model.deleted.connect(self.__model_deleted_cb)
+ cell_detail = CellRendererDetail(self)
+ cell_detail.connect('clicked', self.__detail_cell_clicked_cb)
- def __model_created_cb(self, sender, **kwargs):
- self._set_dirty()
+ column = gtk.TreeViewColumn('')
+ column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ column.props.fixed_width = cell_detail.props.width
+ column.pack_start(cell_detail)
+ self.append_column(column)
- def __model_updated_cb(self, sender, **kwargs):
- self._set_dirty()
+ self.connect('notify::hover-selection',
+ self.__notify_hover_selection_cb)
- def __model_deleted_cb(self, sender, **kwargs):
- self._set_dirty()
+ def __notify_hover_selection_cb(self, widget, pspec):
+ self.cell_icon.props.show_palette = not self.props.hover_selection
+
+ def do_size_request(self, requisition):
+ # HACK: We tell the model that the view is just resizing so it can avoid
+ # hitting both D-Bus and disk.
+ model = self.get_model()
+ if model is not None:
+ model.view_is_resizing = True
+ try:
+ gtk.TreeView.do_size_request(self, requisition)
+ finally:
+ if model is not None:
+ model.view_is_resizing = False
def _add_columns(self):
- cell_favorite = CellRendererFavorite(self.tree_view)
+ cell_favorite = CellRendererFavorite(self)
cell_favorite.connect('clicked', self.__favorite_clicked_cb)
column = gtk.TreeViewColumn('')
@@ -127,9 +121,9 @@ class BaseListView(gtk.Bin):
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.append_column(column)
- self.cell_icon = CellRendererActivityIcon(self.tree_view)
+ self.cell_icon = CellRendererActivityIcon(self)
column = gtk.TreeViewColumn('')
column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
@@ -137,8 +131,8 @@ class BaseListView(gtk.Bin):
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)
+ ListModel.COLUMN_ICON_COLOR)
+ self.append_column(column)
self.cell_title = gtk.CellRendererText()
self.cell_title.props.ellipsize = pango.ELLIPSIZE_MIDDLE
@@ -152,15 +146,15 @@ class BaseListView(gtk.Bin):
self._title_column.add_attribute(self.cell_title, 'markup',
ListModel.COLUMN_TITLE)
self._title_column.connect('clicked', self.__header_clicked_cb)
- self.tree_view.append_column(self._title_column)
+ self.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)
+ self.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,
+ cell_icon = CellRendererBuddy(self,
column_index=column_index)
buddies_column.pack_start(cell_icon)
buddies_column.props.fixed_width += cell_icon.props.width
@@ -186,7 +180,7 @@ class BaseListView(gtk.Bin):
self.date_column.pack_start(cell_text)
self.date_column.add_attribute(cell_text, 'text', ListModel.COLUMN_DATE)
self.date_column.connect('clicked', self.__header_clicked_cb)
- self.tree_view.append_column(self.date_column)
+ self.append_column(self.date_column)
def __header_clicked_cb(self, column_clicked):
if column_clicked == self._title_column:
@@ -206,7 +200,7 @@ class BaseListView(gtk.Bin):
else:
self._query['order_by'] = ['+timestamp']
- self.refresh()
+ self._refresh()
# Need to update the column indicators after the model has been reset
if self._query['order_by'] == ['-timestamp']:
@@ -237,19 +231,8 @@ class BaseListView(gtk.Bin):
width, height_ = layout.get_size()
return pango.PIXELS(width)
- def do_size_allocate(self, allocation):
- self.allocation = allocation
- self.child.size_allocate(allocation)
-
- def do_size_request(self, requisition):
- requisition.width, requisition.height = self.child.size_request()
-
- def __destroy_cb(self, widget):
- if self._model is not None:
- self._model.stop()
-
def __favorite_set_data_cb(self, column, cell, tree_model, tree_iter):
- favorite = self._model[tree_iter][ListModel.COLUMN_FAVORITE]
+ favorite = self.get_model()[tree_iter][ListModel.COLUMN_FAVORITE]
if favorite:
client = gconf.client_get_default()
color = XoColor(client.get_string('/desktop/sugar/user/color'))
@@ -259,7 +242,7 @@ class BaseListView(gtk.Bin):
cell.props.fill_color = style.COLOR_WHITE.get_svg()
def __favorite_clicked_cb(self, cell, path):
- row = self._model[path]
+ row = self.get_model()[path]
metadata = model.get(row[ListModel.COLUMN_UID])
if metadata['keep'] == '1':
metadata['keep'] = '0'
@@ -267,212 +250,37 @@ class BaseListView(gtk.Bin):
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()
- self._start_progress_bar()
-
- if self._model is not None:
- self._model.stop()
-
- self._model = ListModel(self._query)
- self._model.connect('ready', self.__model_ready_cb)
- self._model.connect('progress', self.__model_progress_cb)
- self._model.setup()
-
- def __model_ready_cb(self, tree_model):
- self._stop_progress_bar()
-
- # Cannot set it up earlier because will try to access the model and it
- # needs to be ready.
- self.tree_view.set_model(self._model)
-
- if len(tree_model) == 0:
- if self._is_query_empty():
- self._show_message(MESSAGE_EMPTY_JOURNAL)
- else:
- self._show_message(MESSAGE_NO_MATCH)
- else:
- self._clear_message()
-
- def _is_query_empty(self):
- # FIXME: This is a hack, we shouldn't have to update this every time
- # a new search term is added.
- if self._query.get('query', '') or self._query.get('mime_type', '') or \
- self._query.get('keep', '') or self._query.get('mtime', '') or \
- self._query.get('activity', ''):
- return False
- else:
- return True
-
- def __model_progress_cb(self, tree_model):
- if time.time() - self._last_progress_bar_pulse > 0.05:
- if self._progress_bar is not None:
- self._progress_bar.pulse()
- self._last_progress_bar_pulse = time.time()
-
- def _start_progress_bar(self):
- alignment = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.5)
- self.remove(self.child)
- self.add(alignment)
- alignment.show()
-
- self._progress_bar = gtk.ProgressBar()
- self._progress_bar.props.pulse_step = 0.01
- self._last_progress_bar_pulse = time.time()
- alignment.add(self._progress_bar)
- self._progress_bar.show()
-
- def _stop_progress_bar(self):
- if self.child != self._progress_bar:
- return
- self.remove(self.child)
- self.add(self._scrolled_window)
-
- def _show_message(self, message):
- canvas = hippo.Canvas()
- self.remove(self.child)
- self.add(canvas)
- canvas.show()
-
- box = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
- background_color=style.COLOR_WHITE.get_int(),
- yalign=hippo.ALIGNMENT_CENTER,
- spacing=style.DEFAULT_SPACING,
- padding_bottom=style.GRID_CELL_SIZE)
- canvas.set_root(box)
-
- icon = CanvasIcon(size=style.LARGE_ICON_SIZE,
- icon_name='activity-journal',
- stroke_color = style.COLOR_BUTTON_GREY.get_svg(),
- fill_color = style.COLOR_TRANSPARENT.get_svg())
- box.append(icon)
-
- if message == MESSAGE_EMPTY_JOURNAL:
- text = _('Your Journal is empty')
- elif message == MESSAGE_NO_MATCH:
- text = _('No matching entries')
- else:
- raise ValueError('Invalid message')
-
- text = hippo.CanvasText(text=text,
- xalign=hippo.ALIGNMENT_CENTER,
- font_desc=style.FONT_BOLD.get_pango_desc(),
- color = style.COLOR_BUTTON_GREY.get_int())
- box.append(text)
-
- if message == MESSAGE_NO_MATCH:
- button = gtk.Button(label=_('Clear search'))
- button.connect('clicked', self.__clear_button_clicked_cb)
- button.props.image = Icon(icon_name='dialog-cancel',
- icon_size=gtk.ICON_SIZE_BUTTON)
- canvas_button = hippo.CanvasWidget(widget=button,
- xalign=hippo.ALIGNMENT_CENTER)
- box.append(canvas_button)
-
- def __clear_button_clicked_cb(self, button):
- self.emit('clear-clicked')
-
- def _clear_message(self):
- self.remove(self.child)
- self.add(self._scrolled_window)
- self._scrolled_window.show()
-
def update_dates(self):
logging.debug('ListView.update_dates')
- visible_range = self.tree_view.get_visible_range()
+ visible_range = self.get_visible_range()
if visible_range is None:
return
path, end_path = visible_range
while True:
- x, y, width, height = self.tree_view.get_cell_area(path,
- self.date_column)
- x, y = self.tree_view.convert_tree_to_widget_coords(x, y)
- self.tree_view.queue_draw_area(x, y, width, height)
+ x, y, width, height = self.get_cell_area(path, self.date_column)
+ x, y = self.convert_tree_to_widget_coords(x, y)
+ self.queue_draw_area(x, y, width, height)
if path == end_path:
break
else:
- next_iter = self._model.iter_next(self._model.get_iter(path))
- path = self._model.get_path(next_iter)
-
- def _set_dirty(self):
- if self._fully_obscured:
- self._dirty = True
- else:
- self.refresh()
-
- def set_is_visible(self, visible):
- logging.debug('canvas_visibility_notify_event_cb %r', visible)
- if visible:
- self._fully_obscured = False
- if self._dirty:
- self.refresh()
- if self._update_dates_timer is None:
- logging.debug('Adding date updating timer')
- self._update_dates_timer = \
- gobject.timeout_add_seconds(UPDATE_INTERVAL,
- self.__update_dates_timer_cb)
- else:
- self._fully_obscured = True
- if self._update_dates_timer is not None:
- logging.debug('Remove date updating timer')
- gobject.source_remove(self._update_dates_timer)
- self._update_dates_timer = None
-
- def __update_dates_timer_cb(self):
- self.update_dates()
- return True
-
-class ListView(BaseListView):
- __gtype_name__ = 'JournalListView'
-
- __gsignals__ = {
- 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([object]))
- }
-
- def __init__(self):
- BaseListView.__init__(self)
-
- self.cell_title.props.editable = True
- self.cell_title.connect('edited', self.__cell_title_edited_cb)
-
- self.cell_icon.connect('clicked', self.__icon_clicked_cb)
- self.cell_icon.connect('detail-clicked', self.__detail_clicked_cb)
-
- cell_detail = CellRendererDetail(self.tree_view)
- cell_detail.connect('clicked', self.__detail_cell_clicked_cb)
-
- column = gtk.TreeViewColumn('')
- column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
- column.props.fixed_width = cell_detail.props.width
- column.pack_start(cell_detail)
- self.tree_view.append_column(column)
+ next_iter = self.get_model().iter_next(
+ self.get_model().get_iter(path))
+ path = self.get_model().get_path(next_iter)
def __detail_cell_clicked_cb(self, cell, path):
- row = self.tree_view.get_model()[path]
+ row = self.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]
+ row = self.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]
+ row = self.get_model()[path]
metadata = model.get(row[ListModel.COLUMN_UID])
metadata['title'] = new_text
model.write(metadata, update_mtime=False)
diff --git a/src/jarabe/journal/objectchooser.py b/src/jarabe/journal/objectchooser.py
index 31bdba8..ea9c432 100644
--- a/src/jarabe/journal/objectchooser.py
+++ b/src/jarabe/journal/objectchooser.py
@@ -24,7 +24,7 @@ import wnck
from sugar.graphics import style
from sugar.graphics.toolbutton import ToolButton
-from jarabe.journal.listview import BaseListView
+from jarabe.journal.objectsview import ObjectsView
from jarabe.journal.listmodel import ListModel
from jarabe.journal.journaltoolbox import SearchToolbar
from jarabe.journal.volumestoolbar import VolumesToolbar
@@ -84,15 +84,16 @@ class ObjectChooser(gtk.Window):
vbox.pack_start(self._toolbar, expand=False)
self._toolbar.show()
- self._list_view = ChooserListView()
+ self._list_view = ObjectsView()
+ self._list_view.props.hover_selection = True
self._list_view.connect('entry-activated', self.__entry_activated_cb)
vbox.pack_start(self._list_view)
self._list_view.show()
self._toolbar.set_mount_point('/')
-
+
width = gtk.gdk.screen_width() - style.GRID_CELL_SIZE * 2
- height = gtk.gdk.screen_height() - style.GRID_CELL_SIZE * 2
+ height = gtk.gdk.screen_height() - style.GRID_CELL_SIZE * 2
self.set_size_request(width, height)
if what_filter:
@@ -161,39 +162,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/objectsview.py b/src/jarabe/journal/objectsview.py
new file mode 100644
index 0000000..fca232b
--- /dev/null
+++ b/src/jarabe/journal/objectsview.py
@@ -0,0 +1,315 @@
+# Copyright (C) 2009, Tomeu Vizoso, Aleksey Lim
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+import time
+
+import gobject
+import gtk
+import hippo
+
+from sugar.graphics import style
+from sugar.graphics.icon import CanvasIcon, Icon
+
+from jarabe.journal import model
+from jarabe.journal.listview import ListView
+from jarabe.journal.thumbsview import ThumbsView
+from jarabe.journal.listmodel import ListModel
+from jarabe.journal.browse.lazymodel import LazyModel
+from jarabe.journal.browse.localsource import LocalSource
+from jarabe.journal.browse.source import *
+
+UPDATE_INTERVAL = 300
+
+MESSAGE_EMPTY_JOURNAL = 0
+MESSAGE_NO_MATCH = 1
+
+VIEW_LIST = 0
+VIEW_THUMBS = 1
+
+VIEW_TYPES = [ListView, ThumbsView]
+
+PAGE_SIZE = 10
+
+class ObjectsView(gtk.Bin):
+ __gsignals__ = {
+ 'clear-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([object])),
+ 'entry-activated': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._query = {}
+ self._result_set = None
+ self._progress_bar = None
+ self._last_progress_bar_pulse = None
+ self._model = LazyModel(FIELDS_LIST)
+ self._view_widgets = []
+ self._view = VIEW_LIST
+
+ self.connect('destroy', self.__destroy_cb)
+
+ for view_class in VIEW_TYPES:
+ widget = gtk.ScrolledWindow()
+ widget.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ widget.show()
+
+ view = view_class()
+ view.modify_base(gtk.STATE_NORMAL,
+ style.COLOR_WHITE.get_gdk_color())
+ view.connect('detail-clicked', self.__detail_clicked_cb)
+ view.connect('button-release-event', self.__button_release_event_cb)
+ view.show()
+
+ is_view_scrollable = view.set_scroll_adjustments(None, None)
+ if is_view_scrollable:
+ widget.add(view)
+ else:
+ widget.add_with_viewport(view)
+
+ widget.view = view
+ self._view_widgets.append(widget)
+
+ # Auto-update stuff
+ self._fully_obscured = True
+ self._dirty = False
+ self._refresh_idle_handler = None
+ self._update_dates_timer = None
+
+ model.created.connect(self.__model_created_cb)
+ model.updated.connect(self.__model_updated_cb)
+ model.deleted.connect(self.__model_deleted_cb)
+
+ def set_hover_selection(self, hover_selection):
+ for i in self._view_widgets:
+ i.view.props.hover_selection = hover_selection
+
+ hover_selection = gobject.property(type=bool, default=False,
+ setter=set_hover_selection)
+
+ def update_with_query(self, query_dict):
+ logging.debug('ListView.update_with_query')
+ self._query = query_dict
+
+ if 'order_by' not in self._query:
+ self._query['order_by'] = ['+timestamp']
+
+ self._refresh()
+
+ def update_dates(self):
+ if self._view == VIEW_LIST:
+ # TODO in 0.88 VIEW_LIST will use lazymodel
+ self._view_widgets[VIEW_LIST].view.update_dates()
+ return
+ self._model.recalc([FIELD_MODIFY_TIME])
+
+ def change_view(self, view):
+ self._view = view
+ if self.child is not None:
+ self.remove(self.child)
+ self.add(self._view_widgets[view])
+ self._view_widgets[view].show()
+ if view == VIEW_LIST:
+ # TODO in 0.88 VIEW_LIST will use lazymodel
+ return
+ self._model.view = self._view_widgets[view].view
+
+ def set_is_visible(self, visible):
+ logging.debug('canvas_visibility_notify_event_cb %r' % visible)
+ if visible:
+ self._fully_obscured = False
+ if self._dirty:
+ self._refresh()
+ if self._update_dates_timer is None:
+ logging.debug('Adding date updating timer')
+ self._update_dates_timer = \
+ gobject.timeout_add_seconds(UPDATE_INTERVAL,
+ self.__update_dates_timer_cb)
+ else:
+ self._fully_obscured = True
+ if self._update_dates_timer is not None:
+ logging.debug('Remove date updating timer')
+ gobject.source_remove(self._update_dates_timer)
+ self._update_dates_timer = None
+
+ def _refresh(self):
+ logging.debug('ListView._refresh query %r' % self._query)
+ self._stop_progress_bar()
+ self._start_progress_bar()
+
+ if self._result_set is not None:
+ self._result_set.stop()
+
+ self._result_set = model.find(self._query, PAGE_SIZE)
+ self._result_set.ready.connect(self.__result_set_ready_cb)
+ self._result_set.progress.connect(self.__result_set_progress_cb)
+ self._result_set.setup()
+
+ def __result_set_ready_cb(self, **kwargs):
+ self._stop_progress_bar()
+
+ if self._result_set.length == 0:
+ if self._is_query_empty():
+ self._show_message(MESSAGE_EMPTY_JOURNAL)
+ else:
+ self._show_message(MESSAGE_NO_MATCH)
+ else:
+ # TODO in 0.88 VIEW_LIST will use lazymodel
+ self._view_widgets[VIEW_LIST].view.set_model(
+ ListModel(self._result_set))
+ self._model.source = LocalSource(self._result_set)
+ self.change_view(self._view)
+
+ def __result_set_progress_cb(self, **kwargs):
+ if time.time() - self._last_progress_bar_pulse > 0.05:
+ if self._progress_bar is not None:
+ self._progress_bar.pulse()
+ self._last_progress_bar_pulse = time.time()
+
+ def _is_query_empty(self):
+ # FIXME: This is a hack, we shouldn't have to update this every time
+ # a new search term is added.
+ if self._query.get('query', '') or self._query.get('mime_type', '') or \
+ self._query.get('keep', '') or self._query.get('mtime', '') or \
+ self._query.get('activity', ''):
+ return False
+ else:
+ return True
+
+ def __model_created_cb(self, sender, **kwargs):
+ self._set_dirty()
+
+ def __model_updated_cb(self, sender, **kwargs):
+ self._set_dirty()
+
+ def __model_deleted_cb(self, sender, **kwargs):
+ self._set_dirty()
+
+ def __destroy_cb(self, widget):
+ if self._result_set is not None:
+ self._result_set.stop()
+
+ def _start_progress_bar(self):
+ alignment = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.5)
+ if self.child is not None:
+ self.remove(self.child)
+ self.add(alignment)
+ alignment.show()
+
+ self._progress_bar = gtk.ProgressBar()
+ self._progress_bar.props.pulse_step = 0.01
+ self._last_progress_bar_pulse = time.time()
+ alignment.add(self._progress_bar)
+ self._progress_bar.show()
+
+ def _stop_progress_bar(self):
+ if self.child != self._progress_bar:
+ return
+ if self.child is not None:
+ self.remove(self.child)
+ self.add(self._view_widgets[self._view])
+ self._view_widgets[self._view].show()
+
+ def _show_message(self, message):
+ canvas = hippo.Canvas()
+ if self.child is not None:
+ self.remove(self.child)
+ self.add(canvas)
+ canvas.show()
+
+ box = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
+ background_color=style.COLOR_WHITE.get_int(),
+ yalign=hippo.ALIGNMENT_CENTER,
+ spacing=style.DEFAULT_SPACING,
+ padding_bottom=style.GRID_CELL_SIZE)
+ canvas.set_root(box)
+
+ icon = CanvasIcon(size=style.LARGE_ICON_SIZE,
+ icon_name='activity-journal',
+ stroke_color = style.COLOR_BUTTON_GREY.get_svg(),
+ fill_color = style.COLOR_TRANSPARENT.get_svg())
+ box.append(icon)
+
+ if message == MESSAGE_EMPTY_JOURNAL:
+ text = _('Your Journal is empty')
+ elif message == MESSAGE_NO_MATCH:
+ text = _('No matching entries')
+ else:
+ raise ValueError('Invalid message')
+
+ text = hippo.CanvasText(text=text,
+ xalign=hippo.ALIGNMENT_CENTER,
+ font_desc=style.FONT_BOLD.get_pango_desc(),
+ color = style.COLOR_BUTTON_GREY.get_int())
+ box.append(text)
+
+ if message == MESSAGE_NO_MATCH:
+ button = gtk.Button(label=_('Clear search'))
+ button.connect('clicked', self.__clear_button_clicked_cb)
+ button.props.image = Icon(icon_name='dialog-cancel',
+ icon_size=gtk.ICON_SIZE_BUTTON)
+ canvas_button = hippo.CanvasWidget(widget=button,
+ xalign=hippo.ALIGNMENT_CENTER)
+ box.append(canvas_button)
+
+ def __clear_button_clicked_cb(self, button):
+ self.emit('clear-clicked')
+
+ def _set_dirty(self):
+ if self._fully_obscured:
+ self._dirty = True
+ else:
+ self._refresh()
+
+ def __update_dates_timer_cb(self):
+ self.update_dates()
+ return True
+
+ def __detail_clicked_cb(self, list_view, object_id):
+ self.emit('detail-clicked', object_id)
+
+ def __button_release_event_cb(self, tree_view, event):
+ if not tree_view.props.hover_selection:
+ return False
+
+ 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][FIELD_UID]
+ self.emit('entry-activated', uid)
+
+ return False
+
+ def do_size_allocate(self, allocation):
+ self.allocation = allocation
+ self.child.size_allocate(allocation)
+
+ def do_size_request(self, requisition):
+ requisition.width, requisition.height = self.child.size_request()
diff --git a/src/jarabe/journal/thumbsview.py b/src/jarabe/journal/thumbsview.py
new file mode 100644
index 0000000..4dca8df
--- /dev/null
+++ b/src/jarabe/journal/thumbsview.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+import logging
+import hippo
+
+from jarabe.journal.browse.lazymodel import Source
+from jarabe.journal.browse.tableview import TableView, TableCell
+
+class ThumbsCell(TableCell, hippo.CanvasBox):
+ def __init__(self):
+ TableCell.__init__(self)
+ hippo.CanvasBox.__init__(self, orientation=hippo.ORIENTATION_VERTICAL)
+
+ label = hippo.CanvasWidget(widget=gtk.Button('!!!'))
+ self.append(label)
+
+class ThumbsView(TableView):
+ __gsignals__ = {
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([object])),
+ }
+
+ def __init__(self):
+ TableView.__init__(self, ThumbsCell, 3, 3)