3275: PageView reworking, changes by B. Malengier and N.Hall

Specifically: improve new treeview by using a linked list implementation so 
 iters can be quickly iterated over
Also: progressdialog on long personview loads.


svn: r14002
This commit is contained in:
Benny Malengier
2010-01-09 11:10:32 +00:00
parent afe85ad0d5
commit de2d669763
9 changed files with 412 additions and 178 deletions

View File

@@ -111,6 +111,13 @@ class CLIDbLoader(object):
""" """
pass pass
def _end_progress(self):
"""
Convenience method to allow to hide the progress bar if wanted at
end of load actions. Inherit if needed
"""
pass
def read_file(self, filename): def read_file(self, filename):
""" """
This method takes care of changing database, and loading the data. This method takes care of changing database, and loading the data.

View File

@@ -89,12 +89,17 @@ class DbLoader(CLIDbLoader):
DBErrorDialog(str(msg.value)) DBErrorDialog(str(msg.value))
def _begin_progress(self): def _begin_progress(self):
self.uistate.window.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) self.uistate.set_busy_cursor(1)
self.uistate.progress.show() self.uistate.progress.show()
self.uistate.pulse_progressbar(0)
def _pulse_progress(self, value): def _pulse_progress(self, value):
self.uistate.pulse_progressbar(value) self.uistate.pulse_progressbar(value)
def _end_progress(self):
self.uistate.set_busy_cursor(0)
self.uistate.progress.hide()
def import_file(self): def import_file(self):
# First thing first: import is a batch transaction # First thing first: import is a batch transaction
# so we will lose the undo history. Warn the user. # so we will lose the undo history. Warn the user.
@@ -222,14 +227,13 @@ class DbLoader(CLIDbLoader):
def do_import(self, dialog, importer, filename): def do_import(self, dialog, importer, filename):
self.import_info = None self.import_info = None
dialog.destroy() dialog.destroy()
self.uistate.window.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) self._begin_progress()
self.uistate.progress.show()
try: try:
#an importer can return an object with info, object.info_text() #an importer can return an object with info, object.info_text()
#returns that info. Otherwise None is set to import_info #returns that info. Otherwise None is set to import_info
self.import_info = importer(self.dbstate.db, filename, self.import_info = importer(self.dbstate.db, filename,
self.uistate.pulse_progressbar) self._pulse_progress)
dirname = os.path.dirname(filename) + os.path.sep dirname = os.path.dirname(filename) + os.path.sep
config.set('paths.recent-import-dir', dirname) config.set('paths.recent-import-dir', dirname)
except UnicodeError, msg: except UnicodeError, msg:
@@ -240,6 +244,7 @@ class DbLoader(CLIDbLoader):
"encoding, and import again") + "\n\n %s" % msg) "encoding, and import again") + "\n\n %s" % msg)
except Exception: except Exception:
_LOG.error("Failed to import database.", exc_info=True) _LOG.error("Failed to import database.", exc_info=True)
self._end_progress()
def import_info_text(self): def import_info_text(self):
""" """
@@ -308,6 +313,7 @@ class DbLoader(CLIDbLoader):
self._dberrordialog(msg) self._dberrordialog(msg)
except Exception: except Exception:
self.dbstate.no_database() self.dbstate.no_database()
self._end_progress()
return True return True
#------------------------------------------------------------------------- #-------------------------------------------------------------------------

View File

@@ -1230,7 +1230,6 @@ class ViewManager(CLIManager):
self.uistate.clear_history(self.dbstate.active.handle) self.uistate.clear_history(self.dbstate.active.handle)
else : else :
self.uistate.clear_history(None) self.uistate.clear_history(None)
self.uistate.progress.hide()
self.dbstate.db.undo_callback = self.__change_undo_label self.dbstate.db.undo_callback = self.__change_undo_label
self.dbstate.db.redo_callback = self.__change_redo_label self.dbstate.db.redo_callback = self.__change_redo_label

View File

@@ -3,6 +3,7 @@
# #
# Copyright (C) 2001-2007 Donald N. Allingham # Copyright (C) 2001-2007 Donald N. Allingham
# Copyright (C) 2009 Nick Hall # Copyright (C) 2009 Nick Hall
# Copyright (C) 2009 Benny Malengier
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@@ -532,6 +533,8 @@ class ListView(NavigationView):
obj A TreeViewColumn object of the column clicked obj A TreeViewColumn object of the column clicked
data The column index data The column index
""" """
self.uistate.set_busy_cursor(1)
self.uistate.push_message(self.dbstate, _("Column clicked, sorting..."))
cput = time.clock() cput = time.clock()
same_col = False same_col = False
if self.sort_col != data: if self.sort_col != data:
@@ -558,6 +561,7 @@ class ListView(NavigationView):
filter_info = (False, value, False) filter_info = (False, value, False)
if same_col: if same_col:
self.list.set_model(None)
self.model.reverse_order() self.model.reverse_order()
else: else:
self.model = self.make_model(self.dbstate.db, self.sort_col, self.model = self.make_model(self.dbstate.db, self.sort_col,
@@ -575,6 +579,8 @@ class ListView(NavigationView):
search_col = self.column_order()[data][1] search_col = self.column_order()[data][1]
self.list.set_search_column(search_col) self.list.set_search_column(search_col)
self.uistate.set_busy_cursor(0)
_LOG.debug(' ' + self.__class__.__name__ + ' column_clicked ' + _LOG.debug(' ' + self.__class__.__name__ + ' column_clicked ' +
str(time.clock() - cput) + ' sec') str(time.clock() - cput) + ' sec')

View File

@@ -105,15 +105,16 @@ class PeopleModel(TreeBaseModel):
""" """
Initialize the model building the initial data Initialize the model building the initial data
""" """
self.lru_name = LRU(TreeBaseModel._CACHE_SIZE) TreeBaseModel.__init__(self, db, search=search, skip=skip,
self.lru_bdate = LRU(TreeBaseModel._CACHE_SIZE) tooltip_column=11, marker_column=10,
self.lru_ddate = LRU(TreeBaseModel._CACHE_SIZE) scol=scol, order=order, sort_map=sort_map)
self.gen_cursor = db.get_person_cursor def _set_base_data(self):
self.map = db.get_raw_person_data """See TreeBaseModel, we also set some extra lru caches
self.scol = scol """
self.gen_cursor = self.db.get_person_cursor
#self.group_list = [] self.number_items = self.db.get_number_of_people
self.map = self.db.get_raw_person_data
self.fmap = [ self.fmap = [
self.column_name, self.column_name,
@@ -146,9 +147,11 @@ class PeopleModel(TreeBaseModel):
self.column_int_id, self.column_int_id,
] ]
self.hmap = [self.column_header] + [None]*len(self.smap) self.hmap = [self.column_header] + [None]*len(self.smap)
TreeBaseModel.__init__(self, db, search=search, skip=skip,
tooltip_column=11, marker_column=10, self.lru_name = LRU(TreeBaseModel._CACHE_SIZE)
scol=scol, order=order, sort_map=sort_map) self.lru_bdate = LRU(TreeBaseModel._CACHE_SIZE)
self.lru_ddate = LRU(TreeBaseModel._CACHE_SIZE)
def clear_cache(self): def clear_cache(self):
""" Clear the LRU cache """ """ Clear the LRU cache """
TreeBaseModel.clear_cache(self) TreeBaseModel.clear_cache(self)
@@ -444,7 +447,7 @@ class PeopleModel(TreeBaseModel):
return data[0] return data[0]
def column_header(self, node): def column_header(self, node):
return node return node.name
def column_header_view(self, node): def column_header_view(self, node):
return True return True

View File

@@ -2,6 +2,7 @@
# Gramps - a GTK+/GNOME based genealogy program # Gramps - a GTK+/GNOME based genealogy program
# #
# Copyright (C) 2009 Nick Hall # Copyright (C) 2009 Nick Hall
# Copyright (C) 2009 Benny Malengier
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@@ -64,8 +65,6 @@ class PlaceTreeModel(PlaceBaseModel, TreeBaseModel):
def __init__(self, db, scol=0, order=gtk.SORT_ASCENDING, search=None, def __init__(self, db, scol=0, order=gtk.SORT_ASCENDING, search=None,
skip=set(), sort_map=None): skip=set(), sort_map=None):
self.hmap = [self.column_header] + [None]*12
PlaceBaseModel.__init__(self, db) PlaceBaseModel.__init__(self, db)
TreeBaseModel.__init__(self, db, scol=scol, order=order, TreeBaseModel.__init__(self, db, scol=scol, order=order,
tooltip_column=13, tooltip_column=13,
@@ -73,6 +72,13 @@ class PlaceTreeModel(PlaceBaseModel, TreeBaseModel):
nrgroups = 3, nrgroups = 3,
group_can_have_handle = True) group_can_have_handle = True)
def _set_base_data(self):
"""See TreeBaseModel, for place, most have been set in init of
PlaceBaseModel
"""
self.number_items = self.db.get_number_of_places
self.hmap = [self.column_header] + [None]*12
def get_tree_levels(self): def get_tree_levels(self):
""" """
Return the headings of the levels in the hierarchy. Return the headings of the levels in the hierarchy.
@@ -134,4 +140,4 @@ class PlaceTreeModel(PlaceBaseModel, TreeBaseModel):
Return a column heading. This is called for nodes with no associated Return a column heading. This is called for nodes with no associated
Gramps handle. Gramps handle.
""" """
return node[0] return node.name

View File

@@ -33,6 +33,7 @@ This module provides the model that is used for all hierarchical treeviews.
from __future__ import with_statement from __future__ import with_statement
import time import time
import locale import locale
from gettext import gettext as _
import logging import logging
_LOG = logging.getLogger(".gui.treebasemodel") _LOG = logging.getLogger(".gui.treebasemodel")
@@ -51,11 +52,162 @@ import gtk
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
import config import config
from Utils import conv_unicode_tosrtkey_ongtk from Utils import conv_unicode_tosrtkey_ongtk
from gui.widgets.progressdialog import LongOpStatus import gui.widgets.progressdialog as progressdlg
from Lru import LRU from Lru import LRU
from bisect import bisect_right from bisect import bisect_right
from Filters import SearchFilter, ExactSearchFilter from Filters import SearchFilter, ExactSearchFilter
#-------------------------------------------------------------------------
#
# Node
#
#-------------------------------------------------------------------------
class Node(object):
"""
This class defines an individual node of a tree in the model. The node
stores the following data:
name Textual description of the node.
sortkey A key which defines the sort order of the node.
ref Reference to this node in the tree dictionary.
handle A Gramps handle. Can be None if no Gramps object is
associated with the node.
parent id of the parent node.
prev Link to the previous sibling via id.
next Link to the next sibling via id.
children A list of (sortkey, nodeid) tuples for the children of the node.
This list is always kept sorted.
"""
__slots__ = ('name', 'sortkey', 'ref', 'handle', 'parent', 'prev',
'next', 'children')#, '__weakref__')
def __init__(self, ref, parent, sortkey, handle):
self.name = sortkey
if sortkey:
self.sortkey = conv_unicode_tosrtkey_ongtk(sortkey)
else:
self.sortkey = None
self.ref = ref
self.handle = handle
self.parent = parent
self.prev = None
self.next = None
self.children = []
def set_handle(self, handle):
"""
Assign the handle of a Gramps object to this node.
"""
if not self.handle:
self.handle = handle
else:
raise ValueError, 'attempt to add twice a node to the model'
def add_child(self, node, nodemap):
"""
Add a node to the list of children for this node using the id's in
nodemap.
"""
nodeid = id(node)
if len(self.children):
index = bisect_right(self.children, (node.sortkey, nodeid))
if index == 0:
node.prev = None
next_nodeid = self.children[0][1]
next_node = nodemap.node(next_nodeid)
next_node.prev = nodeid
node.next = next_nodeid
elif index == len(self.children):
prev_nodeid = self.children[-1][1]
prev_node = nodemap.node(prev_nodeid)
prev_node.next = nodeid
node.prev = prev_nodeid
node.next = None
else:
prev_nodeid = self.children[index - 1][1]
next_nodeid = self.children[index][1]
prev_node = nodemap.node(prev_nodeid)
next_node = nodemap.node(next_nodeid)
prev_node.next = nodeid
next_node.prev = nodeid
node.prev = prev_nodeid
node.next = next_nodeid
self.children.insert(index, (node.sortkey, nodeid))
else:
self.children.append((node.sortkey, nodeid))
def remove_child(self, node, nodemap):
"""
Remove a node from the list of children for this node, using nodemap.
"""
nodeid = id(node)
index = bisect_right(self.children, (node.sortkey, nodeid)) - 1
if not (self.children[index] == (node.sortkey, nodeid)):
raise ValueError, str(node.name) + \
' not present in self.children: ' + str(self.children)\
+ ' at index ' + str(index)
if index == 0:
nodemap.node(self.children[index][1]).prev = None
elif index == len(self.children)-1:
nodemap.node(self.children[index - 1][1]).next = None
else:
nodemap.node(self.children[index - 1][1]).next = \
self.children[index + 1][1]
nodemap.node(self.children[index + 1][1]).prev = \
self.children[index - 1][1]
self.children.pop(index)
#-------------------------------------------------------------------------
#
# NodeMap
#
#-------------------------------------------------------------------------
class NodeMap(object):
"""
Map of id of Node classes to real object
"""
def __init__(self):
self.id2node = {}
def add_node(self, node):
"""
Add a Node object to the map and return id of this node
"""
nodeid = id(node)
self.id2node[nodeid] = node
return nodeid
def del_node(self, node):
"""
Remove a Node object from the map and return nodeid
"""
nodeid = id(node)
del self.id2node[nodeid]
return nodeid
def del_nodeid(self, nodeid):
"""
Remove Node with id nodeid from the map
"""
del self.id2node[nodeid]
def node(self, nodeid):
"""
Obtain the node object from it's id
"""
return self.id2node[nodeid]
def clear(self):
"""
clear the map
"""
self.id2node.clear()
self.id2node = {}
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# TreeBaseModel # TreeBaseModel
@@ -72,14 +224,11 @@ class TreeBaseModel(gtk.GenericTreeModel):
The following data is stored: The following data is stored:
tree A dictionary of unique nodes. Each entry is a list tree A dictionary of unique identifiers which correspond to nodes in
containing the parent node and gramps handle. The handle the hierarchy. Each entry is a node object.
is set to None if no gramps object is associated with the handle2node A dictionary of gramps handles. Each entry is a node object.
node. nodemap A NodeMap, mapping id's of the nodes to the node objects. Node
children A dictionary of parent nodes. Each entry is a list of refer to other notes via id's in a linked list form.
(sortkey, child) tuples. The list is sorted during the
build. The top node of the hierarchy is None.
handle2node A dictionary of gramps handles. Each entry is a node.
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.
@@ -117,17 +266,20 @@ class TreeBaseModel(gtk.GenericTreeModel):
cput = time.clock() cput = time.clock()
gtk.GenericTreeModel.__init__(self) gtk.GenericTreeModel.__init__(self)
# Initialise data structures
self.tree = {}
self.children = {}
self.children[None] = []
self.handle2node = {}
self.__reverse = (order == gtk.SORT_DESCENDING) self.__reverse = (order == gtk.SORT_DESCENDING)
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.db = db
self._set_base_data()
# Initialise data structures
self.tree = {}
self.nodemap = NodeMap()
self.handle2node = {}
self.set_property("leak_references", False) self.set_property("leak_references", False)
self.db = db
#normally sort on first column, so scol=0 #normally sort on first column, so scol=0
if sort_map: if sort_map:
#sort_map is the stored order of the columns and if they are #sort_map is the stored order of the columns and if they are
@@ -168,6 +320,26 @@ class TreeBaseModel(gtk.GenericTreeModel):
_LOG.debug(self.__class__.__name__ + ' __init__ ' + _LOG.debug(self.__class__.__name__ + ' __init__ ' +
str(time.clock() - cput) + ' sec') str(time.clock() - cput) + ' sec')
def _set_base_data(self):
"""
This method must be overwritten in the inheriting class, setting
all needed information
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
shown
map : function to obtain the raw bsddb object datamap
smap : the map with functions to obtain sort value based on sort col
fmap : the map with functions to obtain value of a row with handle
hmap : the map with functions to obtain value of a row without handle
"""
self.gen_cursor = None
self.number_items = None # function
self.map = None
self.smap = None
self.fmap = None
self.hmap = None
def __update_todo(self, *args): def __update_todo(self, *args):
""" """
@@ -221,11 +393,18 @@ class TreeBaseModel(gtk.GenericTreeModel):
""" """
Clear the data map. Clear the data map.
""" """
#invalidate the iters within gtk
self.invalidate_iters()
self.tree.clear()
self.tree = {} self.tree = {}
self.children = {} self.handle2node.clear()
self.children[None] = []
self.handle2node = {} self.handle2node = {}
self.__reverse = False self.nodemap.clear()
self.nodemap = NodeMap()
#start with creating the new iters
topnode = Node(None, None, None, None)
self.nodemap.add_node(topnode)
self.tree[None] = topnode
def set_search(self, search): def set_search(self, search):
""" """
@@ -275,7 +454,6 @@ class TreeBaseModel(gtk.GenericTreeModel):
self.clear() self.clear()
self._build_data(self.current_filter, skip) self._build_data(self.current_filter, skip)
self.sort_data()
self._in_build = False self._in_build = False
@@ -290,37 +468,71 @@ class TreeBaseModel(gtk.GenericTreeModel):
""" """
self.__total = 0 self.__total = 0
self.__displayed = 0 self.__displayed = 0
items = self.number_items()
pmon = progressdlg.ProgressMonitor(progressdlg.GtkProgressDialog,
popup_time=2)
status = progressdlg.LongOpStatus(msg=_("Building People View"),
total_steps=items, interval=items//20,
can_cancel=True)
pmon.add_op(status)
with self.gen_cursor() as cursor: with self.gen_cursor() as cursor:
for handle, data in cursor: for handle, data in cursor:
status.heartbeat()
if status.should_cancel():
break
self.__total += 1 self.__total += 1
if not (handle in skip or (dfilter and not if not (handle in skip or (dfilter and not
dfilter.match(handle, self.db))): dfilter.match(handle, self.db))):
self.__displayed += 1 self.__displayed += 1
self.add_row(handle, data) self.add_row(handle, data)
if not status.was_cancelled():
status.end()
def _rebuild_filter(self, dfilter, skip): def _rebuild_filter(self, dfilter, skip):
""" """
Rebuild the data map where a filter is applied. Rebuild the data map where a filter is applied.
""" """
pmon = progressdlg.ProgressMonitor(progressdlg.GtkProgressDialog,
popup_time=2)
status = progressdlg.LongOpStatus(msg=_("Building People View"),
total_steps=3, interval=1)
pmon.add_op(status)
self.__total = self.number_items()
status_ppl = progressdlg.LongOpStatus(msg=_("Obtaining all people"),
total_steps=self.__total, interval=self.__total//10)
pmon.add_op(status_ppl)
def beat(key):
status_ppl.heartbeat()
return key
with self.gen_cursor() as cursor: with self.gen_cursor() as cursor:
handle_list = [key for key, data in cursor] handle_list = [beat(key) for key, data in cursor]
self.__total = len(handle_list) status_ppl.end()
self.__displayed = 0
status.heartbeat()
if dfilter: if dfilter:
handle_list = dfilter.apply(self.db, handle_list) status_filter = progressdlg.LongOpStatus(msg=_("Applying filter"),
self.__displayed = len(handle_list) total_steps=self.__total, interval=self.__total//10)
else: pmon.add_op(status_filter)
self.__displayed = self.db.get_number_of_people() handle_list = dfilter.apply(self.db, handle_list,
progress=status_filter)
status = LongOpStatus(msg="Loading People", status_filter.end()
total_steps=self.__displayed,
interval=self.__displayed//10)
self.db.emit('long-op-start', (status,))
for handle in handle_list:
status.heartbeat() status.heartbeat()
todisplay = len(handle_list)
status_col = progressdlg.LongOpStatus(msg=_("Constructing column data"),
total_steps=todisplay, interval=todisplay//10)
pmon.add_op(status_col)
for handle in handle_list:
status_col.heartbeat()
data = self.map(handle) data = self.map(handle)
if not handle in skip: if not handle in skip:
self.add_row(handle, data) self.add_row(handle, data)
self.__displayed += 1
status_col.end()
status.end() status.end()
def add_node(self, parent, child, sortkey, handle, add_parent=True): def add_node(self, parent, child, sortkey, handle, add_parent=True):
@@ -339,35 +551,31 @@ class TreeBaseModel(gtk.GenericTreeModel):
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
""" """
sortkey = conv_unicode_tosrtkey_ongtk(sortkey)
if add_parent and not (parent in self.tree): if add_parent and not (parent in self.tree):
#add parent to self.tree as a node with no handle, as the first #add parent to self.tree as a node with no handle, as the first
#group level #group level
self.add_node(None, parent, parent, None, add_parent=False) self.add_node(None, parent, parent, None, add_parent=False)
if child in self.tree: if child in self.tree:
#a node is added that is already present, #a node is added that is already present,
self._add_dup_node(parent, child, sortkey, handle) child_node = self.tree[child]
self._add_dup_node(child_node, parent, child, sortkey, handle)
else: else:
self.tree[child] = [parent, handle] parent_node = self.tree[parent]
if parent in self.children: child_node = Node(child, id(parent_node), sortkey, handle)
if self._in_build: parent_node.add_child(child_node, self.nodemap)
self.children[parent].append((sortkey, child)) self.tree[child] = child_node
else: self.nodemap.add_node(child_node)
index = bisect_right(self.children[parent], (sortkey, child))
self.children[parent].insert(index, (sortkey, child))
else:
self.children[parent] = [(sortkey, child)]
if not self._in_build: if not self._in_build:
# emit row_inserted signal # emit row_inserted signal
path = self.on_get_path(child) path = self.on_get_path(child_node)
node = self.get_iter(path) node = self.get_iter(path)
self.row_inserted(path, node) self.row_inserted(path, node)
if handle: if handle:
self.handle2node[handle] = child self.handle2node[handle] = child_node
def _add_dup_node(self, parent, child, sortkey, handle): def _add_dup_node(self, node, parent, child, sortkey, handle):
""" """
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
@@ -377,67 +585,51 @@ class TreeBaseModel(gtk.GenericTreeModel):
if not self.group_can_have_handle: if not self.group_can_have_handle:
raise ValueError, 'attempt to add twice a node to the model %s' % \ raise ValueError, 'attempt to add twice a node to the model %s' % \
str(parent) + ' ' + str(child) + ' ' + sortkey str(parent) + ' ' + str(child) + ' ' + sortkey
present_val = self.tree[child] if handle:
if handle and present_val[1] is None: node.set_handle(handle)
self.tree[child][1] = handle
elif handle is None:
pass
else:
#handle given, and present handle is not None
raise ValueError, 'attempt to add twice a node to the model'
def sort_data(self):
"""
Sort the data in the map according to the value of the sort key.
"""
for node in self.children:
self.children[node].sort()
def remove_node(self, node): def remove_node(self, node):
""" """
Remove a node from the map. Remove a node from the map.
""" """
if node in self.children: if node.children:
self.tree[node][1] = None node.set_handle(None)
else: else:
path = self.on_get_path(node) path = self.on_get_path(node)
parent = self.tree[node][0] self.nodemap.node(node.parent).remove_child(node, self.nodemap)
del self.tree[node] del self.tree[node.ref]
new_list = [child for child in self.children[parent] self.nodemap.del_node(node)
if child[1] != node] del node
if not new_list:
del self.children[parent]
else:
self.children[parent] = new_list
# 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. Reverse the order of the map. This does not signal rows_reordered,
so to propagate the change to the view, you need to reattach the
model to the view.
""" """
cput = time.clock()
self.__reverse = not self.__reverse self.__reverse = not self.__reverse
self._reverse_level(None)
_LOG.debug(self.__class__.__name__ + ' reverse_order ' +
str(time.clock() - cput) + ' sec')
def _reverse_level(self, node): def _reverse_level(self, node):
""" """
Reverse the order of a single level in the map. Reverse the order of a single level in the map and signal
rows_reordered so the view is updated.
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
does not update for every change signal.
""" """
if node in self.children: if node.children:
rows = range(len(self.children[node])) rows = range(len(node.children)-1,-1,-1)
rows.reverse() if node.parent is None:
if node is None:
path = iter = None path = iter = None
else: else:
path = self.on_get_path(node) path = self.on_get_path(node)
iter = self.get_iter(path) iter = self.get_iter(path)
self.rows_reordered(path, iter, rows) self.rows_reordered(path, iter, rows)
for child in self.children[node]: for child in node.children:
self._reverse_level(child[1]) self._reverse_level(self.nodemap.node(child[1]))
def get_tree_levels(self): def get_tree_levels(self):
""" """
@@ -447,7 +639,8 @@ class TreeBaseModel(gtk.GenericTreeModel):
def add_row(self, handle, data): def add_row(self, handle, data):
""" """
Add a row to the model. In general this will add more then one node. Add a row to the model. In general this will add more then one node by
using the add_node method.
""" """
raise NotImplementedError raise NotImplementedError
@@ -470,13 +663,14 @@ class TreeBaseModel(gtk.GenericTreeModel):
self.clear_cache() self.clear_cache()
node = self.get_node(handle) node = self.get_node(handle)
parent = self.on_iter_parent(node) 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 = self.on_iter_parent(parent) next_parent = self.nodemap.node(parent.parent) \
if parent not in self.children: if parent.parent is not None else None
if self.tree[parent][1]: if not parent.children:
if parent.handle:
# emit row_has_child_toggled signal # emit row_has_child_toggled signal
path = self.on_get_path(parent) path = self.on_get_path(parent)
node = self.get_iter(path) node = self.get_iter(path)
@@ -503,10 +697,7 @@ class TreeBaseModel(gtk.GenericTreeModel):
Get the gramps handle for a node. Return None if the node does Get the gramps handle for a node. Return None if the node does
not correspond to a gramps object. not correspond to a gramps object.
""" """
ret = self.tree.get(node) return node.handle
if ret:
return ret[1]
return ret
def get_node(self, handle): def get_node(self, handle):
""" """
@@ -537,12 +728,14 @@ class TreeBaseModel(gtk.GenericTreeModel):
return object return object
return str return str
def on_get_value(self, node, col): def on_get_value(self, nodeid, col):
""" """
See gtk.GenericTreeModel See gtk.GenericTreeModel
""" """
handle = self.get_handle(node) #print 'get_value', nodeid, col
if handle is None: nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
if node.handle is None:
# Header rows dont get the foreground color set # Header rows dont get the foreground color set
if col == self._marker_column: if col == self._marker_column:
return None return None
@@ -557,7 +750,7 @@ class TreeBaseModel(gtk.GenericTreeModel):
else: else:
# return values for 'data' row, calling a function # return values for 'data' row, calling a function
# according to column_defs table # according to column_defs table
return self._get_value(handle, col) return self._get_value(node.handle, col)
def _get_value(self, handle, col): def _get_value(self, handle, col):
""" """
@@ -578,111 +771,118 @@ class TreeBaseModel(gtk.GenericTreeModel):
""" """
Returns a node from a given path. Returns a node from a given path.
""" """
if not self.tree: if not self.tree or not self.tree[None].children:
return None return None
node = None node = self.tree[None]
pathlist = list(path) pathlist = list(path)
for index in pathlist: for index in pathlist:
if self.__reverse: if self.__reverse:
size = len(self.children[node]) size = len(node.children)
node = self.children[node][size - index - 1][1] node = self.nodemap.node(node.children[size - index - 1][1])
else: else:
node = self.children[node][index][1] node = self.nodemap.node(node.children[index][1])
return node return node
def on_get_path(self, node): def on_get_path(self, nodeid):
""" """
Returns a path from a given node. Returns a path from a given node.
""" """
nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
pathlist = [] pathlist = []
while node.parent is not None:
parent = self.nodemap.node(node.parent)
index = -1
while node is not None: while node is not None:
parent = self.tree[node][0] # Step backwards
for index, value in enumerate(self.children[parent]): nodeid = node.next if self.__reverse else node.prev
if value[1] == node: node = self.nodemap.node(nodeid) if nodeid is not None else \
break None
if self.__reverse: index += 1
size = len(self.children[parent])
pathlist.append(size - index - 1)
else:
pathlist.append(index) pathlist.append(index)
node = parent node = parent
if pathlist is not None: if pathlist is not None:
pathlist.reverse() pathlist.reverse()
return tuple(pathlist) return tuple(pathlist)
else: else:
return None return None
def on_iter_next(self, node): def on_iter_next(self, nodeid):
""" """
Get the next node with the same parent as the given node. Get the next node with the same parent as the given node.
""" """
parent = self.tree[node][0] nodeid = id(nodeid)
for index, child in enumerate(self.children[parent]): node = self.nodemap.node(nodeid)
if child[1] == node: val = node.prev if self.__reverse else node.next
break return self.nodemap.node(val) if val is not None else val
if self.__reverse: def on_iter_children(self, nodeid):
index -= 1
else:
index += 1
if index >= 0 and index < len(self.children[parent]):
return self.children[parent][index][1]
else:
return None
def on_iter_children(self, node):
""" """
Get the first child of the given node. Get the first child of the given node.
""" """
if node in self.children: if nodeid is None:
if self.__reverse: node = self.tree[None]
size = len(self.children[node])
return self.children[node][size - 1][1]
else: else:
return self.children[node][0][1] nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
if node.children:
if self.__reverse:
size = len(node.children)
return self.nodemap.node(node.children[size - 1][1])
else:
return self.nodemap.node(node.children[0][1])
else: else:
return None return None
def on_iter_has_child(self, node): def on_iter_has_child(self, nodeid):
""" """
Find if the given node has any children. Find if the given node has any children.
""" """
if node in self.children: if nodeid is None:
return True node = self.tree[None]
else: else:
return False nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
return True if node.children else False
def on_iter_n_children(self, node): def on_iter_n_children(self, nodeid):
""" """
Get the number of children of the given node. Get the number of children of the given node.
""" """
if node in self.children: if nodeid is None:
return len(self.children[node]) node = self.tree[None]
else: else:
return 0 nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
return len(node.children)
def on_iter_nth_child(self, node, index): def on_iter_nth_child(self, nodeid, index):
""" """
Get the nth child of the given node. Get the nth child of the given node.
""" """
if node in self.children: if nodeid is None:
if len(self.children[node]) > index: node = self.tree[None]
if self.__reverse:
size = len(self.children[node])
return self.children[node][size - index - 1][1]
else: else:
return self.children[node][index][1] nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
if node.children:
if len(node.children) > index:
if self.__reverse:
size = len(node.children)
return self.nodemap.node(node.children[size - index - 1][1])
else:
return self.nodemap.node(node.children[index][1])
else: else:
return None return None
else: else:
return None return None
def on_iter_parent(self, node): def on_iter_parent(self, nodeid):
""" """
Get the parent of the given node. Get the parent of the given node.
""" """
if node in self.tree: nodeid = id(nodeid)
return self.tree[node][0] node = self.nodemap.node(nodeid)
else: return self.nodemap.node(node.parent) if node.parent is not None else \
return None None

View File

@@ -166,6 +166,11 @@ class LongOpStatus(Callback):
self._start = time.time() self._start = time.time()
self.emit('op-heartbeat') self.emit('op-heartbeat')
def step(self):
"""Convenience function so LongOpStatus can be used as a ProgressBar
if set up correctly"""
self.heartbeat()
def estimated_secs_to_complete(self): def estimated_secs_to_complete(self):
"""Return the number of seconds estimated left before operation """Return the number of seconds estimated left before operation
completes. This will change as 'hearbeat' is called. completes. This will change as 'hearbeat' is called.
@@ -391,6 +396,8 @@ class ProgressMonitor(object):
facade.status_obj.disconnect(facade.heartbeat_cb_id) facade.status_obj.disconnect(facade.heartbeat_cb_id)
facade.status_obj.disconnect(facade.end_cb_id) facade.status_obj.disconnect(facade.end_cb_id)
del self._status_stack[idx] del self._status_stack[idx]
if len(self._status_stack) == 0 and self._dlg:
self._dlg.close()
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
@@ -600,4 +607,3 @@ if __name__ == '__main__':
w.show() w.show()
gtk.main() gtk.main()
print 'done' print 'done'

View File

@@ -269,10 +269,10 @@ class PersonView(ListView):
if len(pathlist) == 1: if len(pathlist) == 1:
path = pathlist[0] path = pathlist[0]
if len(path) == 1: if len(path) == 1:
name = model.on_get_iter(path) name = model.on_get_iter(path).name
else: else:
node = model.on_get_iter(path) node = model.on_get_iter(path)
name = model.on_iter_parent(node) name = model.on_iter_parent(node).name
try: try:
person.get_primary_name().set_surname(name) person.get_primary_name().set_surname(name)
@@ -332,7 +332,8 @@ class PersonView(ListView):
def remove_from_person_list(self, person): def remove_from_person_list(self, person):
"""Remove the selected person from the list. A person object is """Remove the selected person from the list. A person object is
expected, not an ID""" expected, not an ID"""
path = self.model.on_get_path(person.get_handle()) node = self.model.get_node(person.get_handle())
path = self.model.on_get_path(node)
(col, row) = path (col, row) = path
if row > 0: if row > 0:
self.selection.select_path((col, row-1)) self.selection.select_path((col, row-1))