Fix for exponential cost on treeviews; whitespace cleanup

This commit is contained in:
Doug Blank 2015-08-21 09:45:59 -04:00
parent e5d6be61f8
commit a93872a0f3

View File

@ -85,7 +85,7 @@ class Node(object):
def __init__(self, ref, parent, sortkey, handle, secondary): def __init__(self, ref, parent, sortkey, handle, secondary):
if sortkey: if sortkey:
self.name = sortkey self.name = sortkey
#sortkey must be localized sort, so #sortkey must be localized sort, so
self.sortkey = glocale.sort_key(sortkey) self.sortkey = glocale.sort_key(sortkey)
if not self.sortkey: if not self.sortkey:
self.sortkey = glocale.sort_key('') self.sortkey = glocale.sort_key('')
@ -145,7 +145,7 @@ class Node(object):
else: else:
self.children.append((node.sortkey, nodeid)) self.children.append((node.sortkey, nodeid))
def remove_child(self, node, nodemap): def remove_child(self, node, nodemap):
""" """
Remove a node from the list of children for this node, using nodemap. Remove a node from the list of children for this node, using nodemap.
@ -191,7 +191,7 @@ class NodeMap(object):
## item.parent = None ## item.parent = None
## item.children = [] ## item.children = []
self.id2node.clear() self.id2node.clear()
def add_node(self, node): def add_node(self, node):
""" """
Add a Node object to the map and return id of this node Add a Node object to the map and return id of this node
@ -199,7 +199,7 @@ class NodeMap(object):
nodeid = id(node) nodeid = id(node)
self.id2node[nodeid] = node self.id2node[nodeid] = node
return nodeid return nodeid
def del_node(self, node): def del_node(self, node):
""" """
Remove a Node object from the map and return nodeid Remove a Node object from the map and return nodeid
@ -207,7 +207,7 @@ class NodeMap(object):
nodeid = id(node) nodeid = id(node)
del self.id2node[nodeid] del self.id2node[nodeid]
return nodeid return nodeid
def del_nodeid(self, nodeid): def del_nodeid(self, nodeid):
""" """
Remove Node with id nodeid from the map Remove Node with id nodeid from the map
@ -247,26 +247,26 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
handle2node A dictionary of gramps handles. Each entry is a node object. handle2node A dictionary of gramps handles. Each entry is a node object.
nodemap A NodeMap, mapping id's of the nodes to the node objects. Node nodemap A NodeMap, mapping id's of the nodes to the node objects. Node
refer to other nodes via id's in a linked list form. refer to other nodes via id's in a linked list form.
The model obtains data from database as needed and holds a cache of most The model obtains data from database as needed and holds a cache of most
recently used data. recently used data.
As iter for generictreemodel, node is used. This will be the handle for As iter for generictreemodel, node is used. This will be the handle for
database objects. database objects.
Creation: Creation:
db : the database db : the database
search : the search that must be shown search : the search that must be shown
skip : values not to show skip : values not to show
scol : column on which to sort scol : column on which to sort
order : order of the sort order : order of the sort
sort_map : mapping from columns seen on the GUI and the columns sort_map : mapping from columns seen on the GUI and the columns
as defined here as defined here
nrgroups : maximum number of grouping level, 0 = no group, nrgroups : maximum number of grouping level, 0 = no group,
1= one group, .... Some optimizations can be for only 1= one group, .... Some optimizations can be for only
one group. nrgroups=0 should never be used, as then a one group. nrgroups=0 should never be used, as then a
flatbasemodel should be used flatbasemodel should be used
group_can_have_handle : group_can_have_handle :
can groups have a handle. If False, this means groups can groups have a handle. If False, this means groups
are only used to group subnodes, not for holding data and are only used to group subnodes, not for holding data and
showing subnodes showing subnodes
has_secondary : If True, the model contains two Gramps object types. has_secondary : If True, the model contains two Gramps object types.
@ -284,23 +284,23 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
GObject.GObject.__init__(self) GObject.GObject.__init__(self)
BaseModel.__init__(self) BaseModel.__init__(self)
#We create a stamp to recognize invalid iterators. From the docs: #We create a stamp to recognize invalid iterators. From the docs:
#Set the stamp to be equal to your model's stamp, to mark the #Set the stamp to be equal to your model's stamp, to mark the
#iterator as valid. When your model's structure changes, you should #iterator as valid. When your model's structure changes, you should
#increment your model's stamp to mark all older iterators as invalid. #increment your model's stamp to mark all older iterators as invalid.
#They will be recognised as invalid because they will then have an #They will be recognised as invalid because they will then have an
#incorrect stamp. #incorrect stamp.
self.stamp = 0 self.stamp = 0
#two unused attributes pesent to correspond to flatbasemodel #two unused attributes pesent to correspond to flatbasemodel
self.prev_handle = None self.prev_handle = None
self.prev_data = None self.prev_data = None
self.__reverse = (order == Gtk.SortType.DESCENDING) self.__reverse = (order == Gtk.SortType.DESCENDING)
self.scol = scol self.scol = scol
self.nrgroups = nrgroups self.nrgroups = nrgroups
self.group_can_have_handle = group_can_have_handle self.group_can_have_handle = group_can_have_handle
self.has_secondary = has_secondary self.has_secondary = has_secondary
self.db = db self.db = db
self._set_base_data() self._set_base_data()
# Initialise data structures # Initialise data structures
@ -327,9 +327,9 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
if self.has_secondary: if self.has_secondary:
self.sort_func2 = self.smap2[scol] self.sort_func2 = self.smap2[scol]
self.sort_col = scol self.sort_col = scol
self._in_build = False self._in_build = False
self.__total = 0 self.__total = 0
self.__displayed = 0 self.__displayed = 0
@ -353,7 +353,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
self.sort_func2 = None self.sort_func2 = None
if self.nodemap: if self.nodemap:
self.nodemap.destroy() self.nodemap.destroy()
self.nodemap = None self.nodemap = None
self.rebuild_data = None self.rebuild_data = None
self._build_data = None self._build_data = None
@ -364,9 +364,9 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
def _set_base_data(self): def _set_base_data(self):
""" """
This method must be overwritten in the inheriting class, setting This method must be overwritten in the inheriting class, setting
all needed information all needed information
gen_cursor : func to create cursor to loop over objects in model gen_cursor : func to create cursor to loop over objects in model
number_items : func to obtain number of items that are shown if all number_items : func to obtain number of items that are shown if all
shown shown
@ -375,14 +375,14 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
fmap : the map with functions to obtain value of a row with handle fmap : the map with functions to obtain value of a row with handle
""" """
self.gen_cursor = None self.gen_cursor = None
self.number_items = None # function self.number_items = None # function
self.map = None self.map = None
self.smap = None self.smap = None
self.fmap = None self.fmap = None
if self.has_secondary: if self.has_secondary:
self.gen_cursor2 = None self.gen_cursor2 = None
self.number_items2 = None # function self.number_items2 = None # function
self.map2 = None self.map2 = None
self.smap2 = None self.smap2 = None
self.fmap2 = None self.fmap2 = None
@ -392,7 +392,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
Return the number of rows displayed. Return the number of rows displayed.
""" """
return self.__displayed return self.__displayed
def total(self): def total(self):
""" """
Return the total number of rows without a filter or search condition. Return the total number of rows without a filter or search condition.
@ -421,9 +421,9 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
def set_search(self, search): def set_search(self, search):
""" """
Change the search function that filters the data in the model. Change the search function that filters the data in the model.
When this method is called, make sure: When this method is called, make sure:
# you call self.rebuild_data() to recalculate what should be seen # you call self.rebuild_data() to recalculate what should be seen
in the model in the model
# you reattach the model to the treeview so that the treeview updates # you reattach the model to the treeview so that the treeview updates
with the new entries with the new entries
@ -469,7 +469,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
self.search2 = search[2] self.search2 = search[2]
_LOG.debug("search2 no search parameter") _LOG.debug("search2 no search parameter")
self._build_data = self._rebuild_search self._build_data = self._rebuild_search
self.current_filter = self.search self.current_filter = self.search
if self.has_secondary: if self.has_secondary:
self.current_filter2 = self.search2 self.current_filter2 = self.search2
@ -477,7 +477,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
def rebuild_data(self, data_filter=None, data_filter2=None, skip=[]): def rebuild_data(self, data_filter=None, data_filter2=None, skip=[]):
""" """
Rebuild the data map. Rebuild the data map.
When called externally (from listview), data_filter and data_filter2 When called externally (from listview), data_filter and data_filter2
should be None; set_search will already have been called to establish should be None; set_search will already have been called to establish
the filter functions. When called internally (from __init__) both the filter functions. When called internally (from __init__) both
@ -514,24 +514,24 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
items = self.number_items() items = self.number_items()
_LOG.debug("rebuild search primary") _LOG.debug("rebuild search primary")
self.__rebuild_search(dfilter, skip, items, self.__rebuild_search(dfilter, skip, items,
self.gen_cursor, self.add_row) self.gen_cursor, self.add_row)
if self.has_secondary: if self.has_secondary:
_LOG.debug("rebuild search secondary") _LOG.debug("rebuild search secondary")
items = self.number_items2() items = self.number_items2()
self.__rebuild_search(dfilter2, skip, items, self.__rebuild_search(dfilter2, skip, items,
self.gen_cursor2, self.add_row2) self.gen_cursor2, self.add_row2)
def __rebuild_search(self, dfilter, skip, items, gen_cursor, add_func): def __rebuild_search(self, dfilter, skip, items, gen_cursor, add_func):
""" """
Rebuild the data map for a single Gramps object type, where a search Rebuild the data map for a single Gramps object type, where a search
condition is applied. condition is applied.
""" """
pmon = progressdlg.ProgressMonitor(progressdlg.GtkProgressDialog, pmon = progressdlg.ProgressMonitor(progressdlg.GtkProgressDialog,
popup_time=2) popup_time=2)
status = progressdlg.LongOpStatus(msg=_("Building View"), status = progressdlg.LongOpStatus(msg=_("Building View"),
total_steps=items, interval=items//20, total_steps=items, interval=items//20,
can_cancel=True) can_cancel=True)
pmon.add_op(status) pmon.add_op(status)
with gen_cursor() as cursor: with gen_cursor() as cursor:
@ -562,7 +562,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
# The tree only has primary data # The tree only has primary data
items = self.number_items() items = self.number_items()
_LOG.debug("rebuild filter primary") _LOG.debug("rebuild filter primary")
self.__rebuild_filter(dfilter, skip, items, self.__rebuild_filter(dfilter, skip, items,
self.gen_cursor, self.map, self.add_row) self.gen_cursor, self.map, self.add_row)
else: else:
# The tree has both primary and secondary data. The navigation type # The tree has both primary and secondary data. The navigation type
@ -570,16 +570,16 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
# secondary data. # secondary data.
items = self.number_items2() items = self.number_items2()
_LOG.debug("rebuild filter secondary") _LOG.debug("rebuild filter secondary")
self.__rebuild_filter(dfilter2, skip, items, self.__rebuild_filter(dfilter2, skip, items,
self.gen_cursor2, self.map2, self.add_row2) self.gen_cursor2, self.map2, self.add_row2)
def __rebuild_filter(self, dfilter, skip, items, gen_cursor, data_map, def __rebuild_filter(self, dfilter, skip, items, gen_cursor, data_map,
add_func): add_func):
""" """
Rebuild the data map for a single Gramps object type, where a filter Rebuild the data map for a single Gramps object type, where a filter
is applied. is applied.
""" """
pmon = progressdlg.ProgressMonitor(progressdlg.GtkProgressDialog, pmon = progressdlg.ProgressMonitor(progressdlg.GtkProgressDialog,
popup_time=2) popup_time=2)
status = progressdlg.LongOpStatus(msg=_("Building View"), status = progressdlg.LongOpStatus(msg=_("Building View"),
total_steps=3, interval=1) total_steps=3, interval=1)
@ -587,7 +587,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
status_ppl = progressdlg.LongOpStatus(msg=_("Loading items..."), status_ppl = progressdlg.LongOpStatus(msg=_("Loading items..."),
total_steps=items, interval=items//10) total_steps=items, interval=items//10)
pmon.add_op(status_ppl) pmon.add_op(status_ppl)
self.__total += items self.__total += items
with gen_cursor() as cursor: with gen_cursor() as cursor:
@ -596,7 +596,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
handle = handle.decode('utf-8') handle = handle.decode('utf-8')
status_ppl.heartbeat() status_ppl.heartbeat()
if not handle in skip: if not handle in skip:
if not dfilter or dfilter.apply(self.db, [handle]): if not dfilter or dfilter.match(handle, self.db):
add_func(handle, data) add_func(handle, data)
self.__displayed += 1 self.__displayed += 1
status_ppl.end() status_ppl.end()
@ -606,7 +606,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
secondary=False): secondary=False):
""" """
Add a node to the map. Add a node to the map.
parent The parent node for the child. None for top level. If parent The parent node for the child. None for top level. If
this node does not exist, it will be added under the top this node does not exist, it will be added under the top
level if add_parent=True. For performance, if you have level if add_parent=True. For performance, if you have
@ -616,7 +616,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
sortkey A key by which to sort child nodes of each parent. sortkey A key by which to sort child nodes of each parent.
handle The gramps handle of the object corresponding to the handle The gramps handle of the object corresponding to the
node. None if the node does not have a handle. node. None if the node does not have a handle.
add_parent Bool, if True, check if parent is present, if not add the add_parent Bool, if True, check if parent is present, if not add the
parent as a top group with no handle parent as a top group with no handle
""" """
self.clear_path_cache() self.clear_path_cache()
@ -652,12 +652,12 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
def _add_dup_node(self, node, parent, child, sortkey, handle, secondary): def _add_dup_node(self, node, parent, child, sortkey, handle, secondary):
""" """
How to handle adding a node a second time How to handle adding a node a second time
Default: if group nodes can have handles, it is allowed to add it Default: if group nodes can have handles, it is allowed to add it
again, and this time setting the handle again, and this time setting the handle
Otherwise, a node should never be added twice! Otherwise, a node should never be added twice!
""" """
if not self.group_can_have_handle: if not self.group_can_have_handle:
print ('WARNING: Attempt to add node twice to the model (%s: %s)' print ('WARNING: Attempt to add node twice to the model (%s: %s)'
% (str(parent), str(child))) % (str(parent), str(child)))
return return
if handle: if handle:
@ -687,15 +687,15 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
self.__total -= 1 self.__total -= 1
self.nodemap.del_node(node) self.nodemap.del_node(node)
del node del node
# emit row_deleted signal # emit row_deleted signal
self.row_deleted(path) self.row_deleted(path)
def reverse_order(self): def reverse_order(self):
""" """
Reverse the order of the map. Only for Gtk 3.9+ does this signal Reverse the order of the map. Only for Gtk 3.9+ does this signal
rows_reordered, so to propagate the change to the view, you need to rows_reordered, so to propagate the change to the view, you need to
reattach the model to the view. reattach the model to the view.
""" """
self.clear_path_cache() self.clear_path_cache()
self.__reverse = not self.__reverse self.__reverse = not self.__reverse
@ -704,7 +704,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
def _reverse_level(self, node): def _reverse_level(self, node):
""" """
Reverse the order of a single level in the map and signal Reverse the order of a single level in the map and signal
rows_reordered so the view is updated. rows_reordered so the view is updated.
If many changes are done, it is better to detach the model, do the If many changes are done, it is better to detach the model, do the
changes to reverse the level, and reattach the model, so the view changes to reverse the level, and reattach the model, so the view
@ -730,7 +730,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
Return the headings of the levels in the hierarchy. Return the headings of the levels in the hierarchy.
""" """
raise NotImplementedError raise NotImplementedError
def add_row(self, handle, data): def add_row(self, handle, data):
""" """
Add a row to the model. In general this will add more than one node by Add a row to the model. In general this will add more than one node by
@ -759,7 +759,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
_LOG.debug(self.__class__.__name__ + ' add_row_by_handle ' + _LOG.debug(self.__class__.__name__ + ' add_row_by_handle ' +
str(time.clock() - cput) + ' sec') str(time.clock() - cput) + ' sec')
_LOG.debug("displayed %d / total: %d" % _LOG.debug("displayed %d / total: %d" %
(self.__displayed, self.__total)) (self.__displayed, self.__total))
def delete_row_by_handle(self, handle): def delete_row_by_handle(self, handle):
@ -775,7 +775,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
parent = self.nodemap.node(node.parent) parent = self.nodemap.node(node.parent)
self.remove_node(node) self.remove_node(node)
while parent is not None: while parent is not None:
next_parent = parent.parent and self.nodemap.node(parent.parent) next_parent = parent.parent and self.nodemap.node(parent.parent)
if not parent.children: if not parent.children:
@ -787,10 +787,10 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
else: else:
self.remove_node(parent) self.remove_node(parent)
parent = next_parent parent = next_parent
_LOG.debug(self.__class__.__name__ + ' delete_row_by_handle ' + _LOG.debug(self.__class__.__name__ + ' delete_row_by_handle ' +
str(time.clock() - cput) + ' sec') str(time.clock() - cput) + ' sec')
_LOG.debug("displayed %d / total: %d" % _LOG.debug("displayed %d / total: %d" %
(self.__displayed, self.__total)) (self.__displayed, self.__total))
def update_row_by_handle(self, handle): def update_row_by_handle(self, handle):
@ -804,7 +804,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
self.delete_row_by_handle(handle) self.delete_row_by_handle(handle)
self.add_row_by_handle(handle) self.add_row_by_handle(handle)
# If the node hasn't moved, all we need is to call row_changed. # If the node hasn't moved, all we need is to call row_changed.
#self.row_changed(path, node) #self.row_changed(path, node)
@ -815,7 +815,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
iter = Gtk.TreeIter() iter = Gtk.TreeIter()
iter.stamp = self.stamp iter.stamp = self.stamp
#user data should be an object, so we store the long as str #user data should be an object, so we store the long as str
iter.user_data = nodeid iter.user_data = nodeid
return iter return iter
@ -823,10 +823,10 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
""" """
Return an iter from the node. Return an iter from the node.
iters are always created afresh iters are always created afresh
Will raise IndexError if the maps are not filled yet, or if it is empty. Will raise IndexError if the maps are not filled yet, or if it is empty.
Caller should take care of this if it allows calling with invalid path Caller should take care of this if it allows calling with invalid path
:param path: node as it appears in the treeview :param path: node as it appears in the treeview
:type path: Node :type path: Node
""" """
@ -843,14 +843,14 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
def get_iter_from_handle(self, handle): def get_iter_from_handle(self, handle):
""" """
Get the iter for a gramps handle. Should return None if iter not Get the iter for a gramps handle. Should return None if iter not
visible visible
""" """
node = self._get_node(handle) node = self._get_node(handle)
if node is None: if node is None:
return None return None
return self._get_iter(node) return self._get_iter(node)
def get_handle_from_iter(self, iter): def get_handle_from_iter(self, iter):
""" """
Get the gramps handle for an iter. Return None if the iter does Get the gramps handle for an iter. Return None if the iter does
@ -926,7 +926,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
raise NotImplementedError raise NotImplementedError
cached, data = self.get_cached_value(handle, col) cached, data = self.get_cached_value(handle, col)
if not cached: if not cached:
if not secondary: if not secondary:
data = self.map(handle) data = self.map(handle)
@ -934,7 +934,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
data = self.map2(handle) data = self.map2(handle)
if store_cache: if store_cache:
self.set_cached_value(handle, col, data) self.set_cached_value(handle, col, data)
if data is None: if data is None:
return '' return ''
if not secondary: if not secondary:
@ -946,7 +946,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
if self.fmap2[col] is None: if self.fmap2[col] is None:
return '' return ''
value = self.fmap2[col](data) value = self.fmap2[col](data)
return value return value
def do_get_iter(self, path): def do_get_iter(self, path):
@ -1002,8 +1002,8 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
if sib_pathtup: if sib_pathtup:
# Compute path to here from sibling: # Compute path to here from sibling:
# parent_path + sib_path + offset # parent_path + sib_path + offset
newtup = (sib_pathtup[:-1] + newtup = (sib_pathtup[:-1] +
(sib_pathtup[-1] + index + 2, ) + (sib_pathtup[-1] + index + 2, ) +
tuple(reversed(pathlist))) tuple(reversed(pathlist)))
#print("computed path:", iter.user_data, newtup) #print("computed path:", iter.user_data, newtup)
retval = Gtk.TreePath(newtup) retval = Gtk.TreePath(newtup)
@ -1050,7 +1050,7 @@ class TreeBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel):
else: else:
return False, None return False, None
return True, self._new_iter(nodeid) return True, self._new_iter(nodeid)
def do_iter_has_child(self, iter): def do_iter_has_child(self, iter):
""" """
Find if the given node has any children. Find if the given node has any children.