Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/jarabe/journal/lazymodel.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/jarabe/journal/lazymodel.py')
-rw-r--r--src/jarabe/journal/lazymodel.py420
1 files changed, 420 insertions, 0 deletions
diff --git a/src/jarabe/journal/lazymodel.py b/src/jarabe/journal/lazymodel.py
new file mode 100644
index 0000000..d590fbc
--- /dev/null
+++ b/src/jarabe/journal/lazymodel.py
@@ -0,0 +1,420 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import logging
+from gobject import GObject, SIGNAL_RUN_FIRST, TYPE_PYOBJECT
+
+
+class Source(GObject):
+
+ __gsignals__ = {
+ 'objects-updated': (SIGNAL_RUN_FIRST, None, []),
+ 'row-delayed-fetch': (SIGNAL_RUN_FIRST, None, 2 * [TYPE_PYOBJECT]),
+ }
+
+ def get_count(self):
+ """ Returns number of objects """
+ pass
+
+ def get_row(self, offset):
+ """ Get object
+
+ Returns:
+ objects in dict {field_name: value, ...}
+ False can't fint object
+ None wait for reply signal
+
+ """
+ pass
+
+ def get_order(self):
+ """ Get current order, returns (field_name, gtk.SortType) """
+ pass
+
+ def set_order(self, field_name, sort_type):
+ """ Set current order """
+ pass
+
+
+class LazyModel(gtk.GenericTreeModel):
+
+ def __init__(self, columns, calc_columns=None):
+ """ columns/calc_columns = {field_name: (column_num, column_type)} """
+ gtk.GenericTreeModel.__init__(self)
+
+ self.columns_by_name = {}
+ self.columns_by_num = {}
+ self.columns_types = {}
+
+ for name, i in columns.items():
+ self.columns_by_name[name] = i[0]
+ self.columns_by_num[i[0]] = name
+ self.columns_types[i[0]] = i[1]
+
+ if calc_columns is not None:
+ for name, i in calc_columns.items():
+ self.columns_types[i[0]] = i[1]
+
+ self._n_columns = max(self.columns_types.keys()) + 1
+
+ self._source = None
+ self._closing = False
+ self._view = None
+ self._last_count = 0
+ self._cache = {}
+ self._frame = (0, -1)
+ self._in_process = {}
+ self._postponed = []
+
+ self.set_source(None, force=True)
+ self.set_view(None, force=True)
+
+ def on_calc_value(self, row, column):
+ # stub
+ pass
+
+ def get_source(self):
+ return self._source
+
+ def set_source(self, source, force=False):
+ if self._source == source and not force:
+ return
+
+ if self._source is not None:
+ self._source.disconnect_by_func(self.refresh)
+ self._source.disconnect_by_func(self.__delayed_fetch_cb)
+
+ self._source = source
+ if self._source is not None:
+ self._source.connect('objects-updated', self.refresh)
+ self._source.connect('row-delayed-fetch', self.__delayed_fetch_cb)
+
+ self.refresh()
+
+ source = property(get_source, set_source)
+
+ def get_view(self):
+ return self._view
+
+ def set_view(self, view, force=False):
+ if self._view == view and not force:
+ return
+
+ cursor = None
+
+ if self._view is not None:
+ cursor = self._view.get_cursor()
+ 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 self._source is None:
+ return None
+ order = self._source.get_order()
+ if order is None:
+ return None
+ return (self.columns_by_name[order[0]], order[1])
+
+ def set_order(self, column, order):
+ if self._source is None:
+ return
+ self._source.set_order(self.columns_by_num[column], order)
+ self._update_columns()
+
+ def refresh(self, sender=None):
+ if self._source is None or self._view is None:
+ return
+
+ if self._last_count == 0:
+ self.set_view(self._view, force=True)
+
+ self._update_columns()
+
+ count = self._source.get_count()
+
+ # TODO how to handle large update,
+ # commented for now since in TableView
+ # it works fine w/o these updates
+ #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._last_count != count:
+ self.emit('rows-reordered', (0, ), self.get_iter((0, )), None)
+
+ if self._frame[0] >= count:
+ self._frame = (0, -1)
+ elif self._frame[1] >= count:
+ for i in range(count, self._frame[1]):
+ if i in self._cache:
+ del self._cache[i]
+ self._frame = (self._frame[0], count-1)
+
+ self._cache = {}
+
+ if self._last_count == count:
+ for i in range(self._frame[0], self._frame[1]+1):
+ self.emit('row-changed', (i, ), self.get_iter((i, )))
+
+ self._last_count = count
+
+ def recalc(self, fields):
+ for i, row in self._cache.items():
+ for field in fields:
+ if field in row:
+ del row[field]
+ self.emit('row-changed', (i, ), self.get_iter((i, )))
+
+ def get_row(self, pos, frame=None):
+ if self._source is None:
+ return False
+ if not isinstance(pos, tuple):
+ pos = self.get_path(pos)
+ return self._get_row(pos[0], frame or (pos, pos))
+
+ def __delayed_fetch_cb(self, source, offset, metadata):
+ if not offset in self._in_process:
+ logging.debug('__delayed_fetch_cb: no offset=%s' % offset)
+ return
+
+ logging.debug('__delayed_fetch_cb: get %s' % offset)
+
+ path = (offset, )
+ iterator = self.get_iter(path)
+ row = Row(self, path, iterator, metadata)
+
+ if self.in_frame(offset):
+ self._cache[offset] = row
+
+ del self._in_process[offset]
+ self.emit('row-changed', path, iterator)
+ if self._in_process:
+ return
+
+ while self._postponed:
+ offset, force = self._postponed.pop()
+ if not force and not self.in_frame(offset):
+ continue
+ row = self.get_row((offset, ))
+ if row is not None and row != False:
+ self.emit('row-changed', row.path, row.iterator)
+ else:
+ break
+
+ def _get_row(self, offset, frame):
+
+ def fetch():
+ row = self._source.get_row(offset)
+
+ if row is None or row == False:
+ if row is not None:
+ logging.debug('_get_row: can not find row for %s' % offset)
+ return False
+ logging.debug('_get_row: wait for reply for %s' % offset)
+ self._in_process[offset] = True
+ return None
+
+ row = Row(self, (offset, ), self.get_iter(offset), row)
+ self._cache[offset] = row
+ return row
+
+ out = self._cache.get(offset)
+ if out is not None:
+ return out
+
+ if frame[0] >= frame[1]:
+ # just return requested single row and do not change cache
+ # if requested frame has <= 1 rows
+ if self._in_process:
+ self._postponed.append((offset, True))
+ return None
+ else:
+ return fetch()
+
+ if frame != self._frame:
+ # switch to new frame
+ intersect_min = max(frame[0], self._frame[0])
+ intersect_max = min(frame[1], self._frame[1])
+ if intersect_min > intersect_max:
+ self._cache = {}
+ else:
+ for i in range(self._frame[0], intersect_min):
+ if i in self._cache:
+ del self._cache[i]
+ for i in range(intersect_max+1, self._frame[1]+1):
+ if i in self._cache:
+ del self._cache[i]
+ self._frame = frame
+
+ if self._in_process:
+ self._postponed.append((offset, False))
+ return None
+
+ return fetch()
+
+ def _update_columns(self):
+ order = self.get_order()
+ if order is None or not hasattr(self._view, 'get_columns'):
+ return
+
+ for column in self._view.get_columns():
+ if column.get_sort_column_id() == order[0]:
+ column.props.sort_indicator = True
+ column.props.sort_order = order[1]
+ else:
+ column.props.sort_indicator = False
+
+ def in_frame(self, offset):
+ return offset >= self._frame[0] and offset <= self._frame[1]
+
+ def on_get_n_columns(self):
+ return self._n_columns
+
+ def on_get_column_type(self, index):
+ return self.columns_types.get(index, bool)
+
+ def on_iter_n_children(self, iterator):
+ if iterator is None and not self._closing:
+ return self._source.get_count()
+ else:
+ return 0
+
+ def on_get_value(self, offset, column):
+ if self._view is None or offset >= self._source.get_count():
+ return None
+
+ # return value only if iterator came from visible range
+ # (on setting model, gtk.TreeView scans all items)
+ vrange = self._view.get_visible_range()
+ if vrange and offset >= vrange[0][0] and offset <= vrange[1][0]:
+ row = self._get_row(offset, (vrange[0][0], vrange[1][0]))
+ return row is not None and row != False and row[column]
+
+ return None
+
+ def on_iter_nth_child(self, iterator, n):
+ return n
+
+ def on_get_path(self, iterator):
+ return iterator
+
+ def on_get_iter(self, path):
+ if self._source.get_count() and not self._closing:
+ return path[0]
+ else:
+ return False
+
+ def on_iter_next(self, iterator):
+ if iterator is not None:
+ if iterator >= self._source.get_count() - 1 or self._closing:
+ return None
+ return iterator + 1
+ return None
+
+ def on_get_flags(self):
+ return gtk.TREE_MODEL_ITERS_PERSIST | gtk.TREE_MODEL_LIST_ONLY
+
+ def on_iter_children(self, iterator):
+ return None
+
+ def on_iter_has_child(self, iterator):
+ return False
+
+ def on_iter_parent(self, iterator):
+ return None
+
+
+class Row:
+
+ def __init__(self, model, path, iterator, metadata):
+ self.model = model
+ self.iterator = iterator
+ self.path = path
+ self.metadata = metadata
+ self.row = [None] * len(model.columns_by_name)
+ self._calced_row = {}
+
+ for name, value in metadata.items():
+ column = model.columns_by_name.get(str(name), -1)
+ if column != -1:
+ self.row[column] = value
+
+ def __getitem__(self, key):
+ if isinstance(key, int):
+ if key < len(self.row):
+ return self.row[key]
+ else:
+ if key in self._calced_row:
+ return self._calced_row[key]
+ else:
+ value = self.model.on_calc_value(self, key)
+ if value is not None:
+ self._calced_row[key] = value
+ return value
+ else:
+ return self.metadata[key]
+
+ def __setitem__(self, key, value):
+ if isinstance(key, int):
+ if key < len(self.row):
+ self.row[key] = value
+ else:
+ self._calced_row[key] = value
+ else:
+ self.metadata[key] = value
+
+ def __delitem__(self, key):
+ if isinstance(key, int):
+ if key < len(self.row):
+ del self.row[key]
+ else:
+ del self._calced_row[key]
+ else:
+ del self.metadata[key]
+
+ def __contains__(self, key):
+ if isinstance(key, int):
+ return key < len(self.row) or key in self._calced_row
+ else:
+ return self.metadata.__contains__(key)
+
+ def has_key(self, key):
+ return self.__contains__(key)
+
+ def get(self, key, default=None):
+ if key in self:
+ return self.__getitem__(key)
+ else:
+ return default