added initial fast model implementation

svn: r5872
This commit is contained in:
Richard Taylor 2006-02-03 15:49:59 +00:00
parent 64ba0101d4
commit 754231f151
14 changed files with 1181 additions and 1 deletions

View File

@ -1,3 +1,9 @@
2006-02-03 Richard Taylor <rjt-gramps@thegrindstone.me.uk>
* src/GrampsDb/_GrampsBSDDB.py: fixed typo
* src/Models: added initial fast model implementation
* src/TreeViews: added treeview that uses new Models
* src/try_tree_model.py: harness for running treeview
2006-02-02 Don Allingham <don@gramps-project.org> 2006-02-02 Don Allingham <don@gramps-project.org>
* src/PersonView.py: history * src/PersonView.py: history
* src/EditPerson.py: start the save routine. * src/EditPerson.py: start the save routine.

View File

@ -440,7 +440,7 @@ class GrampsBSDDB(GrampsDbBase):
def rebuild_secondary(self,callback=None): def rebuild_secondary(self,callback=None):
if self.read_only: if self.readonly:
return return
table_flags = db.DB_CREATE|db.DB_AUTO_COMMIT table_flags = db.DB_CREATE|db.DB_AUTO_COMMIT

View File

@ -48,6 +48,7 @@ from _GrampsDbFactories import \
from _ReadGedcom import GedcomParser from _ReadGedcom import GedcomParser
from _WriteGedcom import GedcomWriter from _WriteGedcom import GedcomWriter
from _WriteXML import XmlWriter from _WriteXML import XmlWriter
from _GrampsDbExceptions import GrampsDbException from _GrampsDbExceptions import GrampsDbException

View File

@ -0,0 +1,153 @@
import gtk
import time
import logging
log = logging.getLogger(".")
class FastFilterModel(gtk.GenericTreeModel):
"""A I{gtk.GenericTreeModel} that links to a BSDB cursor to provide
fast access to large tables. This is a pure virtual class, it must be
subclassed and the subclass must implement L{_get_table}, L{_get_cursor} and
L{_get_object_class}.
The primary trick is to use the path specification as the tree iter.
This means that when the TreeView asks for the iter for path=[1,2]
we just echo it straight back. The onlt hard part is making sure that
on_iter_next can do something sensible. It needs to know how many
non duplicate records are in the table and then it can just accept the
iter from the TreeView and increment it until it reaches the total
length.
The record itself is only fetched when its value is requested from
on_get_value() and when the number of childen need to calculated. The
cursor looks after the number of children calculation but it does require
walking the list of duplicate keys, usually this is quite short.
@ivar _db: handle of the Gramps DB
@ivar _table: main table to be displayed
@ivar _cursor: cursor for accessing the table.
@ivar _obj_class: the class of the object that is being pulled from
the database. This should probably be one of the primary RelLib
classes.
@ivar _num_children_cache: dictionary to hold the number of
children for each primary record so that we don't have to look
it up every time.
@ivar _length: the number of primary (non duplicate) records.
"""
column_types = (object,)
def __init__(self,db,data_filter):
gtk.GenericTreeModel.__init__(self)
self._db = db
self._data_filter = data_filter
self._fetch_func = self._get_fetch_func(db)
self._build_data()
def _build_data(self):
if not self._data_filter.is_empty():
self._keys = self._data_filter.apply(self._db)
else:
return
self._length = len(self._keys)
# Methods that must be implemented by subclasses.
def _get_fetch_func(self,db):
raise NotImplementedError("subclass of FastModel must implement _get_fetch_func")
# GenericTreeModel methods
def on_get_flags(self):
return gtk.TREE_MODEL_LIST_ONLY|gtk.TREE_MODEL_ITERS_PERSIST
def on_get_n_columns(self):
return len(self.__class__.column_types)
def on_get_column_type(self, index):
return self.column_types[index]
def on_get_iter(self, path):
return list(path)
def on_get_path(self, rowref):
return list(rowref)
def on_get_value(self, rowref, column):
"""
Fetch the real object from the database.
"""
# We only have one column
if column is 0:
record = self._fetch_func(self._keys[rowref[0]])
# This should never return none, but there is a subtle bug
# somewhere that I can't find and sometimes it does.
if record is None:
log.warn("Failed to fetch a record from the cursor rowref = %s" % (str(rowref)))
return (record,rowref)
def on_iter_next(self, rowref):
"""
Calculate the next iter at the same level in the tree.
"""
# The length of the rowref (i.e. the number of elements in the path)
# tells us the level in the tree.
if len(rowref) == 1:
# If we are at the top of the tree we just increment the
# first element in the iter until we reach the total length.
if rowref[0]+1 >= self._length:
ret = None
else:
ret = [rowref[0]+1,]
else:
# We only support one level.
ret = None
return ret
def on_iter_children(self, rowref):
"""
Return the first child of the given rowref.
"""
if rowref:
# If the rowref is not none then we must be
# asking for the second level so the first
# child is always 0.
ret = [rowref[0],0]
else:
# If rowref is None the we are asking for the
# top level and that is always [0]
ret = [0,]
return ret
def on_iter_has_child(self, rowref):
return False
def on_iter_n_children(self, rowref):
return self._length
def on_iter_nth_child(self, parent, n):
if parent:
ret = [parent[0],n]
else:
ret = [n,]
return ret
def on_iter_parent(self, child):
if len(child) > 1:
return [child[0]]
return None

195
src/Models/_FastModel.py Normal file
View File

@ -0,0 +1,195 @@
import gtk
import time
import logging
log = logging.getLogger(".")
class FastModel(gtk.GenericTreeModel):
"""A I{gtk.GenericTreeModel} that links to a BSDB cursor to provide
fast access to large tables. This is a pure virtual class, it must be
subclassed and the subclass must implement L{_get_table}, L{_get_cursor} and
L{_get_object_class}.
The primary trick is to use the path specification as the tree iter.
This means that when the TreeView asks for the iter for path=[1,2]
we just echo it straight back. The onlt hard part is making sure that
on_iter_next can do something sensible. It needs to know how many
non duplicate records are in the table and then it can just accept the
iter from the TreeView and increment it until it reaches the total
length.
The record itself is only fetched when its value is requested from
on_get_value() and when the number of childen need to calculated. The
cursor looks after the number of children calculation but it does require
walking the list of duplicate keys, usually this is quite short.
@ivar _db: handle of the Gramps DB
@ivar _table: main table to be displayed
@ivar _cursor: cursor for accessing the table.
@ivar _obj_class: the class of the object that is being pulled from
the database. This should probably be one of the primary RelLib
classes.
@ivar _num_children_cache: dictionary to hold the number of
children for each primary record so that we don't have to look
it up every time.
@ivar _length: the number of primary (non duplicate) records.
"""
column_types = (object,)
def __init__(self,db):
gtk.GenericTreeModel.__init__(self)
self._db = db
self._table = self._get_table(db)
self._cursor = self._get_cursor(db)
self._object_class = self._get_object_class(db)
self._length = self._get_length(db)
self._num_children_cache = {}
# Methods that must be implemented by subclasses.
def _get_table(self,db):
raise NotImplementedError("subclass of FastModel must implement _get_table")
def _get_cursor(self,db):
raise NotImplementedError("subclass of FastModel must implement _get_cursor")
def _get_object_class(self,db):
raise NotImplementedError("subclass of FastModel must implement _get_cursor")
def _get_length(self,db):
raise NotImplementedError("subclass of FastModel must implement _get_length")
# GenericTreeModel methods
def on_get_flags(self):
return gtk.TREE_MODEL_ITERS_PERSIST
def on_get_n_columns(self):
return len(self.__class__.column_types)
def on_get_column_type(self, index):
return self.column_types[index]
def on_get_iter(self, path):
return list(path)
def on_get_path(self, rowref):
return list(rowref)
def on_get_value(self, rowref, column):
"""
Fetch the real object from the database.
"""
# We only have one column
if column is 0:
obj = self._object_class()
# Use the rowref as the path, because the iter methods
# simple return the path as the iter it is safe to use
# it here.
record = self._cursor.lookup_path(rowref)
# This should never return none, but there is a subtle bug
# somewhere that I can't find and sometimes it does.
if record is not None:
obj.unserialize(record[1])
else:
log.warn("Failed to fetch a record from the cursor rowref = %s" % (str(rowref)))
return (obj,rowref)
def on_iter_next(self, rowref):
"""
Calculate the next iter at the same level in the tree.
"""
# The length of the rowref (i.e. the number of elements in the path)
# tells us the level in the tree.
if len(rowref) == 1:
# If we are at the top of the tree we just increment the
# first element in the iter until we reach the total length.
if rowref[0]+1 >= self._length:
ret = None
else:
ret = [rowref[0]+1,]
elif len(rowref) == 2:
# If we are at the second level we first check to see if we
# have the number of children of this row already in the cache
if not self._num_children_cache.has_key(rowref[0]):
# If not calculate the number of children and put it in the cache.
self._num_children_cache[rowref[0]] = self._cursor.get_n_children([rowref[0],])
num_children = self._num_children_cache[rowref[0]]
# Now increment the second element of the iter path until we
# reach the number of children.
if rowref[1]+1 < num_children:
ret = [rowref[0],rowref[1]+1]
else:
ret = None
else:
# We only support two levels.
ret = None
return ret
def on_iter_children(self, rowref):
"""
Return the first child of the given rowref.
"""
if rowref:
# If the rowref is not none then we must be
# asking for the second level so the first
# child is always 0.
ret = [rowref[0],0]
else:
# If rowref is None the we are asking for the
# top level and that is always [0]
ret = [0,]
return ret
def on_iter_has_child(self, rowref):
if rowref:
ret = self._cursor.has_children(rowref)
else:
ret = range(0,self._length)
return ret
def on_iter_n_children(self, rowref):
if rowref:
# If we are at the second level we first check to see if we
# have the number of children of this row already in the cache
if not self._num_children_cache.has_key(rowref[0]):
# If not calculate the number of children and put it in the cache.
self._num_children_cache[rowref[0]] = self._cursor.get_n_children([rowref[0],])
ret = self._num_children_cache[rowref[0]]
else:
ret = self._length
return ret
def on_iter_nth_child(self, parent, n):
if parent:
ret = [parent[0],n]
else:
ret = [n,]
return ret
def on_iter_parent(self, child):
if len(child) > 1:
return [child[0]]
return None

176
src/Models/_ListCursor.py Normal file
View File

@ -0,0 +1,176 @@
import cPickle
import logging
log = logging.getLogger(".")
class ListCursor(object):
"""
Provides a wrapper around the cursor class that provides fast
traversal using treeview paths for models that are LISTONLY, i.e.
they have no tree structure.
It keeps track of the current index that the cursor is pointing
at.
@ivar _index: The current index pointed to by the cursor.
To speed up lookups the cursor is kept as close as possible to the
likely next lookup and is moved using next()/prev() were ever
possible.
@ivar _object_cache: A cache of previously fetched records. These are
indexed by the values of the L{_index}.
"""
def __init__(self,cursor):
"""
@param cursor: The cursor used to fetch the records.
@type cursor: An object supporting the cursor methods of a U{BSDB
cursor<http://pybsddb.sourceforge.net/bsddb3.html>}.
It must have a BTREE index type and DB_DUP to support duplicate
records. It should probably also have DB_DUPSORT if you want to
have sorted records.
"""
self._cursor = cursor
self._object_cache = {}
self.top()
def top(self):
self._cursor.first()
self._index = 0
def next(self):
"""
Move to the next record.
"""
data = self._cursor.next()
# If there was a next record that data will
# not be None
if data is not None:
# Up date the index pointers so that
# they point to the current record.
self._index+= 1
return data
def prev(self):
"""
Move to the previous record.
"""
data = self._cursor.prev()
# If there was a next record that data will
# not be None
if data is not None:
# Up date the index pointers so that
# they point to the current record.
self._index -= 1
return data
def has_children(self,path):
"""
This cursor is only for simple lists so no records have
children.
@param path: The path spec to check.
@type path: A TreeView path.
"""
return False
def get_n_children(self,path):
"""
Return the number of children that the record at I{path} has.
@param path: The path spec to check.
@type path: A TreeView path.
"""
return 0
def lookup(self,index,use_cache=True):
"""
Lookup a primary record.
@param index: The index of the primary record. This is its
possition in the sequence of non_duplicate keys.
@type index: int
@para use_case: If B{True} the record will be looked up in the
object cache and will be returned from there. This will not
update the possition of the cursor. If B{False} the record will
fetched from the cursor even if it is in the object cache and
cursor will be left possitioned on the record.
"""
# See if the record is in the cache.
if self._object_cache.has_key(index) and use_cache is True:
ret = self._object_cache[index]
# If the record is not in the cache or we are ignoring the
# cache.
else:
# If the cursor points to the record we want
# the first index will be equal to the
# index required
if index == self._index:
ret = self._cursor.current()
# If the current cursor is behind the
# requested index move it forward.
elif index < self._index:
while index < self._index:
ret = self.prev()
if ret is None:
log.warn("Failed to move back to index = %s" % (str(index)))
break
ret = self._cursor.current()
# If the current cursor is in front of
# requested index move it backward.
else:
while index > self._index:
ret = self.next()
if ret is None:
log.warn("Failed to move forward to index = %s" % (str(index)))
break
ret = self._cursor.current()
# when we have got the record save it in
# the cache
if ret is not None:
ret = self._unpickle(ret)
self._object_cache[index] = ret
return ret
def _unpickle(self,rec):
"""
It appears that reading an object from a cursor does not
automatically unpickle it. So this method provides
a convenient way to unpickle the object.
"""
if rec and type(rec[1]) == type(""):
tmp = [rec[0],None]
tmp[1] = cPickle.loads(rec[1])
rec = tmp
return rec
def lookup_path(self,path):
"""
Lookup a record from a patch specification.
@param path: The path spec to check.
@type path: A TreeView path.
"""
return self.lookup(path[0])

324
src/Models/_PathCursor.py Normal file
View File

@ -0,0 +1,324 @@
import cPickle
import logging
log = logging.getLogger(".")
class PathCursor(object):
"""
Provides a wrapper around the cursor class that provides fast
traversal using treeview paths.
It keeps track of the current index that the cursor is pointing
at by using a two stage index. The first element of the index is
the sequence number of the record in the list of non_duplicate
keys and the second element is the sequence number of the record
within the duplicate keys to which it is a member.
For example, with the following table indexed on Surname::
Record Value Index
============ =====
Blogs, Jo [0,0]
Blogs, Jane [0,1]
Smith, Wilman [1,0]
Smith, John [1,1]
@ivar _index: The current index pointed to by the cursor.
To speed up lookups the cursor is kept as close as possible to the
likely next lookup and is moved using next_dup()/prev_dup() were ever
possible.
@ivar _object_cache: A cache of previously fetched records. These are
indexed by the values of the L{_index}.
"""
def __init__(self,cursor):
"""
@param cursor: The cursor used to fetch the records.
@type cursor: An object supporting the cursor methods of a U{BSDB
cursor<http://pybsddb.sourceforge.net/bsddb3.html>}.
It must have a BTREE index type and DB_DUP to support duplicate
records. It should probably also have DB_DUPSORT if you want to
have sorted records.
"""
self._cursor = cursor
self._object_cache = {}
self.top()
def top(self):
self._cursor.first()
self._index = [0,0]
def next_nodup(self):
"""
Move to the next non-duplcate record.
"""
data = self._cursor.next_nodup()
# If there was a next record that data will
# not be None
if data is not None:
# Up date the index pointers so that
# they point to the current record.
self._index[0] += 1
self._index[1] = 0
return data
def prev_nodup(self):
"""
Move to the previous non-duplicate record.
"""
data = self._cursor.prev_nodup()
# If there was a next record that data will
# not be None
if data is not None:
# Up date the index pointers so that
# they point to the current record.
self._index[0] -= 1
self._index[1] = 0
return data
def next_dup(self):
"""
Move to the next record with a duplicate key to the current record.
"""
data = self._cursor.next_dup()
# If there was a next record that data will
# not be None
if data is not None:
# Update the secondary index.
self._index[1] += 1
return data
def has_children(self,path):
"""
Check is the I{path} has any children.
At the moment this method lies. There is no fast way to check
if a given key has any duplicates and the TreeView insists on
checking for every row. So this methods returns True if the
path is 1 element long and False if it is more. This works
for us because we show the first record in a set of duplicates
as the first child. So all top level rows have at least one child.
@param path: The path spec to check.
@type path: A TreeView path.
"""
if len(path) == 1:
return True
else:
return False
def get_n_children(self,path):
"""
Return the number of children that the record at I{path} has.
@param path: The path spec to check.
@type path: A TreeView path.
"""
# Only top level records can have children.
if len(path) > 1:
return 0
# Fetch the primary record
ret = self.lookup(path[0],use_cache=False)
if ret is not None:
# Now count the duplicates. We start at 1 because
# we want to include the primary in the duplicates.
count = 1
ret = self.next_dup()
while ret:
ret = self.next_dup()
count += 1
self._index[1] += 1
ret = count
else:
# If we failed to find the primary something is
# wrong.
ret = 0
return ret
def lookup(self,index,use_cache=True):
"""
Lookup a primary record.
@param index: The index of the primary record. This is its
possition in the sequence of non_duplicate keys.
@type index: int
@para use_case: If B{True} the record will be looked up in the
object cache and will be returned from there. This will not
update the possition of the cursor. If B{False} the record will
fetched from the cursor even if it is in the object cache and
cursor will be left possitioned on the record.
"""
# See if the record is in the cache.
if self._object_cache.has_key(index) and use_cache is True:
ret = self._object_cache[index]['primary']
# If the record is not in the cache or we are ignoring the
# cache.
else:
# If the cursor points to a duplicate record
# it will have a second index value of 0.
if self._index[1] != 0:
# We need to move the cursor to the
# first of a set of duplicates so that
# we can then shift it to the required
# index.
self.next_nodup()
# If the cursor points to the record we want
# the first index will be equal to the
# index required
if index == self._index[0]:
ret = self._cursor.current()
# If the current cursor is behind the
# requested index move it forward.
elif index < self._index[0]:
while index < self._index[0]:
ret = self.prev_nodup()
if ret is None:
log.warn("Failed to move back to index = %s" % (str(index)))
break
# Because prev_nodup() leaves the cursor on
# the last of a set of duplicates we need
# to go up one further and then back down
# again.
ret = self.prev_nodup()
if ret is None:
# We are at the start
self.top()
ret = self._cursor.current()
else:
ret = self.next_nodup()
# If the current cursor is in front of
# requested index move it backward.
else:
while index > self._index[0]:
ret = self.next_nodup()
if ret is None:
log.warn("Failed to move forward to index = %s" % (str(index)))
break
ret = self._cursor.current()
# when we have got the record save it in
# the cache
if ret is not None:
ret = self._unpickle(ret)
self._object_cache[index] = {'primary':ret}
return ret
def _unpickle(self,rec):
"""
It appears that reading an object from a cursor does not
automatically unpickle it. So this method provides
a convenient way to unpickle the object.
"""
if rec and type(rec[1]) == type(""):
tmp = [rec[0],None]
tmp[1] = cPickle.loads(rec[1])
rec = tmp
return rec
def lookup_path(self,path):
"""
Lookup a record from a patch specification.
@param path: The path spec to check.
@type path: A TreeView path.
"""
# If the path is for a primary record it will only
# be 1 element long.
if len(path) == 1:
ret = self.lookup(path[0])
# If it is for a secondary object we need to
# traverse the duplicates.
else:
# First check to see if the record has already
# been fetched.
if self._object_cache.has_key(path[0]) and \
self._object_cache[path[0]].has_key(path[1]):
# return record from cache.
ret = self._object_cache[path[0]][path[1]]
else:
# If we already in the duplicates for this
# primary index then the first index will
# be the same as the first element of the
# path.
if self._index[0] == path[0]:
# If the second elements match we are
# already looking at the correct record.
if self._index[1] == path[1]:
ret = self._cursor.current()
# If the cursor is in front we can
# move it back. Unfortunately there is no
# prev_dup() method so we have to
# reposition of the cursor at the start
# of the duplicates and step forward
else:
self.prev_nodup()
self.next_nodup()
ret = self.lookup(path[0],use_cache=False)
# If the request if not for the first duplicate
# we step forward the number of duplicates
# that have been requested.
count = 0
while count < path[1]:
ret = self.next_dup()
count += 1
# If the primary elements do not match we
# must move the cursor to the start of the
# duplicates that are requested.
else:
self.prev_nodup()
self.next_nodup()
ret = self.lookup(path[0],use_cache=False)
# If the request if not for the first duplicate
# we step forward the number of duplicates
# that have been requested.
count = 0
while count < path[1]:
ret = self.next_dup()
count += 1
# Put the fetched record in the cache
if ret is not None:
ret = self._unpickle(ret)
self._object_cache[path[0]][path[1]] = ret
return ret

View File

@ -0,0 +1,29 @@
import gtk
import time
import bsddb
import cPickle
import logging
log = logging.getLogger(".")
from _PathCursor import PathCursor
from _ListCursor import ListCursor
from _FastFilterModel import FastFilterModel
import RelLib
class PersonFilterModel(FastFilterModel):
"""Provides a fast model interface to the Person table.
"""
def __init__(self,db,apply_filter):
FastFilterModel.__init__(self,db,apply_filter)
def _get_object_class(self,db):
return RelLib.Person
def _get_fetch_func(self,db):
return db.get_person_from_handle

View File

@ -0,0 +1,36 @@
import gtk
import time
import bsddb
import cPickle
import logging
log = logging.getLogger(".")
from _PathCursor import PathCursor
from _ListCursor import ListCursor
from _FastModel import FastModel
import RelLib
class PersonListModel(FastModel):
"""Provides a fast model interface to the Person table.
"""
def __init__(self,db):
FastModel.__init__(self,db)
def _get_table(self,db):
return db.surnames
def _get_cursor(self,db):
return ListCursor(db.surnames.cursor())
def _get_object_class(self,db):
return RelLib.Person
def _get_length(self,db):
return self._table.stat()['ndata']
def on_get_flags(self):
return gtk.TREE_MODEL_LIST_ONLY|gtk.TREE_MODEL_ITERS_PERSIST

View File

@ -0,0 +1,33 @@
import gtk
import time
import bsddb
import cPickle
import logging
log = logging.getLogger(".")
from _PathCursor import PathCursor
from _ListCursor import ListCursor
from _FastModel import FastModel
import RelLib
class PersonTreeModel(FastModel):
"""Provides a fast model interface to the Person table.
"""
def __init__(self,db):
FastModel.__init__(self,db)
def _get_table(self,db):
return db.surnames
def _get_cursor(self,db):
return PathCursor(db.surnames.cursor())
def _get_object_class(self,db):
return RelLib.Person
def _get_length(self,db):
return self._table.stat()['nkeys']

4
src/Models/__init__.py Normal file
View File

@ -0,0 +1,4 @@
from _PersonListModel import PersonListModel
from _PersonTreeModel import PersonTreeModel
from _PersonFilterModel import PersonFilterModel

View File

@ -0,0 +1,82 @@
from gettext import gettext as _
import gtk
from Models import \
PersonTreeModel, PersonListModel, PersonFilterModel
from NameDisplay import displayer
display_given = displayer.display_given
class PersonTreeView(gtk.TreeView):
def __init__(self,db,apply_filter=None):
gtk.TreeView.__init__(self)
self._db = db
# Add the Name column
self._name_col = gtk.TreeViewColumn(_("Family Name"))
self._name_col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
self._name_col.set_fixed_width(300)
self._id_cell1 = gtk.CellRendererText()
self._name_col.pack_start(self._id_cell1,True)
self._name_col.set_cell_data_func(self._id_cell1,self._family_name)
# Add the Name column
self._given_col = gtk.TreeViewColumn(_("Given Name"))
self._given_col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
self._given_col.set_fixed_width(300)
self._id_cell1 = gtk.CellRendererText()
self._given_col.pack_start(self._id_cell1,True)
self._given_col.set_cell_data_func(self._id_cell1,self._given_name)
# Add the ID column
self._id_col = gtk.TreeViewColumn(_("ID"))
self._id_col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
self._id_col.set_fixed_width(100)
self._id_cell = gtk.CellRendererText()
self._id_col.pack_start(self._id_cell,True)
self._id_col.set_cell_data_func(self._id_cell,self._object_id)
self.append_column(self._name_col)
self.append_column(self._given_col)
self.append_column(self._id_col)
self.set_enable_search(False)
self.set_fixed_height_mode(True)
if apply_filter is not None:
self.set_filter(apply_filter)
else:
self.clear_filter()
def set_filter(self,apply_filter=None):
self.set_model(PersonFilterModel(self._db,apply_filter))
def clear_filter(self):
self.set_model(PersonModel(self._db))
# Accessor methods for the columns
def _object_id(self, column, cell, model, iter, user_data=None):
(o,rowref) = model.get_value(iter, 0)
if len(rowref) > 1:
cell.set_property('text', o.get_gramps_id())
else:
cell.set_property('text', "")
def _family_name(self, column, cell, model, iter, user_data=None):
(o,rowref) = model.get_value(iter, 0)
cell.set_property('text', str(rowref) + " " + o.get_primary_name().get_surname())
def _given_name(self, column, cell, model, iter, user_data=None):
(o,rowref) = model.get_value(iter, 0)
if len(rowref) > 1:
cell.set_property('text', display_given(o))
else:
cell.set_property('text', "")

View File

@ -0,0 +1 @@
from _PersonTreeView import PersonTreeView

140
test/try_tree_model.py Normal file
View File

@ -0,0 +1,140 @@
import time
import gtk
import gobject
import sys, os
sys.path.append("..")
from Models import PersonModel,PersonFilterModel
from TreeViews import PersonTreeView
import GenericFilter
## class ProxyPerson(object):
## """
## This class provides a wrapper around the real object that
## is stored in the model.
## """
## def __init__(self,id,db):
## self._id = id
## self._db = db
## self._obj = None
## def row_ref(self):
## """This should return the value that is used
## as the row reference in the model."""
## return self._id
## def __getattr__(self, name):
## """
## Delegate to the real object.
## """
## # Fetch the object from the database if we
## # don't already have it
## if self._obj is None:
## self._obj = self._get_object()
## # Call the method that we were asked
## # for on the real object.
## return getattr(self._obj, name)
## def _get_object(self):
## """
## Fetch the real object from the database.
## """
## print "getting object = ", self._id
## return self._db.get_person_from_handle(self._id)
class PersonWindow(gtk.Window):
def __init__(self,db):
gtk.Window.__init__(self)
self.set_default_size(700,300)
self._db = db
fil = GenericFilter.GenericFilter()
fil.add_rule(GenericFilter.SearchName(["Taylor"]))
person_tree = PersonTreeView(db,fil)
person_tree.show()
scrollwindow = gtk.ScrolledWindow()
scrollwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrollwindow.set_shadow_type(gtk.SHADOW_ETCHED_IN)
scrollwindow.show()
scrollwindow.add(person_tree)
self.add(scrollwindow)
person_tree.clear_filter()
#person_tree.set_filter(fil)
#person_model = PersonFilterModel(db,fil)
#person_tree.set_model(person_model)
self._person_tree = person_tree
#self._person_model = person_model
#gobject.idle_add(self.load_tree().next)
self._expose_count = 0
## def load_tree(self):
## self._person_tree.freeze_child_notify()
## for i in self._db.get_person_handles():
## self._person_model.add(ProxyPerson(i,self._db))
## yield True
## self._person_tree.thaw_child_notify()
## self._person_tree.set_model(self._person_model)
## yield False
if __name__ == "__main__":
import sys, os
sys.path.append("..")
import GrampsDb
import const
import logging
form = logging.Formatter(fmt="%(relativeCreated)d: %(levelname)s: %(filename)s: line %(lineno)d: %(message)s")
stderrh = logging.StreamHandler(sys.stderr)
stderrh.setFormatter(form)
stderrh.setLevel(logging.DEBUG)
# everything.
l = logging.getLogger()
l.setLevel(logging.DEBUG)
l.addHandler(stderrh)
def cb(d):
pass
def main():
print "start", sys.argv[1]
db = GrampsDb.gramps_db_factory(const.app_gramps)()
db.load(os.path.realpath(sys.argv[1]),
cb, # callback
"w")
print "window"
w = PersonWindow(db)
w.show()
w.connect("destroy", gtk.main_quit)
print "main"
gtk.main()
#import profile
#profile.run('main()','profile.out')
main()