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
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):
"""
This method takes care of changing database, and loading the data.

View File

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

View File

@ -1230,7 +1230,6 @@ class ViewManager(CLIManager):
self.uistate.clear_history(self.dbstate.active.handle)
else :
self.uistate.clear_history(None)
self.uistate.progress.hide()
self.dbstate.db.undo_callback = self.__change_undo_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) 2009 Nick Hall
# Copyright (C) 2009 Benny Malengier
#
# 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
@ -532,6 +533,8 @@ class ListView(NavigationView):
obj A TreeViewColumn object of the column clicked
data The column index
"""
self.uistate.set_busy_cursor(1)
self.uistate.push_message(self.dbstate, _("Column clicked, sorting..."))
cput = time.clock()
same_col = False
if self.sort_col != data:
@ -558,6 +561,7 @@ class ListView(NavigationView):
filter_info = (False, value, False)
if same_col:
self.list.set_model(None)
self.model.reverse_order()
else:
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]
self.list.set_search_column(search_col)
self.uistate.set_busy_cursor(0)
_LOG.debug(' ' + self.__class__.__name__ + ' column_clicked ' +
str(time.clock() - cput) + ' sec')

View File

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

View File

@ -2,6 +2,7 @@
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009 Nick Hall
# Copyright (C) 2009 Benny Malengier
#
# 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
@ -64,8 +65,6 @@ class PlaceTreeModel(PlaceBaseModel, TreeBaseModel):
def __init__(self, db, scol=0, order=gtk.SORT_ASCENDING, search=None,
skip=set(), sort_map=None):
self.hmap = [self.column_header] + [None]*12
PlaceBaseModel.__init__(self, db)
TreeBaseModel.__init__(self, db, scol=scol, order=order,
tooltip_column=13,
@ -73,6 +72,13 @@ class PlaceTreeModel(PlaceBaseModel, TreeBaseModel):
nrgroups = 3,
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):
"""
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
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
import time
import locale
from gettext import gettext as _
import logging
_LOG = logging.getLogger(".gui.treebasemodel")
@ -51,11 +52,162 @@ import gtk
#-------------------------------------------------------------------------
import config
from Utils import conv_unicode_tosrtkey_ongtk
from gui.widgets.progressdialog import LongOpStatus
import gui.widgets.progressdialog as progressdlg
from Lru import LRU
from bisect import bisect_right
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
@ -72,14 +224,11 @@ class TreeBaseModel(gtk.GenericTreeModel):
The following data is stored:
tree A dictionary of unique nodes. Each entry is a list
containing the parent node and gramps handle. The handle
is set to None if no gramps object is associated with the
node.
children A dictionary of parent nodes. Each entry is a list of
(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.
tree A dictionary of unique identifiers which correspond to nodes in
the hierarchy. 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
refer to other notes via id's in a linked list form.
The model obtains data from database as needed and holds a cache of most
recently used data.
@ -116,18 +265,21 @@ class TreeBaseModel(gtk.GenericTreeModel):
group_can_have_handle = False):
cput = time.clock()
gtk.GenericTreeModel.__init__(self)
self.__reverse = (order == gtk.SORT_DESCENDING)
self.scol = scol
self.nrgroups = nrgroups
self.group_can_have_handle = group_can_have_handle
self.db = db
self._set_base_data()
# Initialise data structures
self.tree = {}
self.children = {}
self.children[None] = []
self.nodemap = NodeMap()
self.handle2node = {}
self.__reverse = (order == gtk.SORT_DESCENDING)
self.nrgroups = nrgroups
self.group_can_have_handle = group_can_have_handle
self.set_property("leak_references", False)
self.db = db
#normally sort on first column, so scol=0
if sort_map:
#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__ ' +
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):
"""
@ -221,11 +393,18 @@ class TreeBaseModel(gtk.GenericTreeModel):
"""
Clear the data map.
"""
#invalidate the iters within gtk
self.invalidate_iters()
self.tree.clear()
self.tree = {}
self.children = {}
self.children[None] = []
self.handle2node.clear()
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):
"""
@ -275,7 +454,6 @@ class TreeBaseModel(gtk.GenericTreeModel):
self.clear()
self._build_data(self.current_filter, skip)
self.sort_data()
self._in_build = False
@ -290,37 +468,71 @@ class TreeBaseModel(gtk.GenericTreeModel):
"""
self.__total = 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:
for handle, data in cursor:
status.heartbeat()
if status.should_cancel():
break
self.__total += 1
if not (handle in skip or (dfilter and not
dfilter.match(handle, self.db))):
self.__displayed += 1
self.add_row(handle, data)
if not status.was_cancelled():
status.end()
def _rebuild_filter(self, dfilter, skip):
"""
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:
handle_list = [key for key, data in cursor]
self.__total = len(handle_list)
handle_list = [beat(key) for key, data in cursor]
status_ppl.end()
self.__displayed = 0
status.heartbeat()
if dfilter:
handle_list = dfilter.apply(self.db, handle_list)
self.__displayed = len(handle_list)
else:
self.__displayed = self.db.get_number_of_people()
status_filter = progressdlg.LongOpStatus(msg=_("Applying filter"),
total_steps=self.__total, interval=self.__total//10)
pmon.add_op(status_filter)
handle_list = dfilter.apply(self.db, handle_list,
progress=status_filter)
status_filter.end()
status.heartbeat()
status = LongOpStatus(msg="Loading People",
total_steps=self.__displayed,
interval=self.__displayed//10)
self.db.emit('long-op-start', (status,))
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.heartbeat()
status_col.heartbeat()
data = self.map(handle)
if not handle in skip:
self.add_row(handle, data)
self.__displayed += 1
status_col.end()
status.end()
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
parent as a top group with no handle
"""
sortkey = conv_unicode_tosrtkey_ongtk(sortkey)
if add_parent and not (parent in self.tree):
#add parent to self.tree as a node with no handle, as the first
#group level
self.add_node(None, parent, parent, None, add_parent=False)
if child in self.tree:
#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:
self.tree[child] = [parent, handle]
if parent in self.children:
if self._in_build:
self.children[parent].append((sortkey, child))
else:
index = bisect_right(self.children[parent], (sortkey, child))
self.children[parent].insert(index, (sortkey, child))
else:
self.children[parent] = [(sortkey, child)]
parent_node = self.tree[parent]
child_node = Node(child, id(parent_node), sortkey, handle)
parent_node.add_child(child_node, self.nodemap)
self.tree[child] = child_node
self.nodemap.add_node(child_node)
if not self._in_build:
# emit row_inserted signal
path = self.on_get_path(child)
path = self.on_get_path(child_node)
node = self.get_iter(path)
self.row_inserted(path, node)
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
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:
raise ValueError, 'attempt to add twice a node to the model %s' % \
str(parent) + ' ' + str(child) + ' ' + sortkey
present_val = self.tree[child]
if handle and present_val[1] is None:
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()
if handle:
node.set_handle(handle)
def remove_node(self, node):
"""
Remove a node from the map.
"""
if node in self.children:
self.tree[node][1] = None
if node.children:
node.set_handle(None)
else:
path = self.on_get_path(node)
parent = self.tree[node][0]
del self.tree[node]
new_list = [child for child in self.children[parent]
if child[1] != node]
if not new_list:
del self.children[parent]
else:
self.children[parent] = new_list
self.nodemap.node(node.parent).remove_child(node, self.nodemap)
del self.tree[node.ref]
self.nodemap.del_node(node)
del node
# emit row_deleted signal
self.row_deleted(path)
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_level(None)
_LOG.debug(self.__class__.__name__ + ' reverse_order ' +
str(time.clock() - cput) + ' sec')
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:
rows = range(len(self.children[node]))
rows.reverse()
if node is None:
if node.children:
rows = range(len(node.children)-1,-1,-1)
if node.parent is None:
path = iter = None
else:
path = self.on_get_path(node)
iter = self.get_iter(path)
self.rows_reordered(path, iter, rows)
for child in self.children[node]:
self._reverse_level(child[1])
for child in node.children:
self._reverse_level(self.nodemap.node(child[1]))
def get_tree_levels(self):
"""
@ -447,7 +639,8 @@ class TreeBaseModel(gtk.GenericTreeModel):
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
@ -470,13 +663,14 @@ class TreeBaseModel(gtk.GenericTreeModel):
self.clear_cache()
node = self.get_node(handle)
parent = self.on_iter_parent(node)
parent = self.nodemap.node(node.parent)
self.remove_node(node)
while parent is not None:
next_parent = self.on_iter_parent(parent)
if parent not in self.children:
if self.tree[parent][1]:
next_parent = self.nodemap.node(parent.parent) \
if parent.parent is not None else None
if not parent.children:
if parent.handle:
# emit row_has_child_toggled signal
path = self.on_get_path(parent)
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
not correspond to a gramps object.
"""
ret = self.tree.get(node)
if ret:
return ret[1]
return ret
return node.handle
def get_node(self, handle):
"""
@ -537,12 +728,14 @@ class TreeBaseModel(gtk.GenericTreeModel):
return object
return str
def on_get_value(self, node, col):
def on_get_value(self, nodeid, col):
"""
See gtk.GenericTreeModel
"""
handle = self.get_handle(node)
if handle is None:
#print 'get_value', nodeid, col
nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
if node.handle is None:
# Header rows dont get the foreground color set
if col == self._marker_column:
return None
@ -557,7 +750,7 @@ class TreeBaseModel(gtk.GenericTreeModel):
else:
# return values for 'data' row, calling a function
# according to column_defs table
return self._get_value(handle, col)
return self._get_value(node.handle, col)
def _get_value(self, handle, col):
"""
@ -578,111 +771,118 @@ class TreeBaseModel(gtk.GenericTreeModel):
"""
Returns a node from a given path.
"""
if not self.tree:
if not self.tree or not self.tree[None].children:
return None
node = None
node = self.tree[None]
pathlist = list(path)
for index in pathlist:
if self.__reverse:
size = len(self.children[node])
node = self.children[node][size - index - 1][1]
size = len(node.children)
node = self.nodemap.node(node.children[size - index - 1][1])
else:
node = self.children[node][index][1]
node = self.nodemap.node(node.children[index][1])
return node
def on_get_path(self, node):
def on_get_path(self, nodeid):
"""
Returns a path from a given node.
"""
nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
pathlist = []
while node is not None:
parent = self.tree[node][0]
for index, value in enumerate(self.children[parent]):
if value[1] == node:
break
if self.__reverse:
size = len(self.children[parent])
pathlist.append(size - index - 1)
else:
pathlist.append(index)
while node.parent is not None:
parent = self.nodemap.node(node.parent)
index = -1
while node is not None:
# Step backwards
nodeid = node.next if self.__reverse else node.prev
node = self.nodemap.node(nodeid) if nodeid is not None else \
None
index += 1
pathlist.append(index)
node = parent
if pathlist is not None:
pathlist.reverse()
return tuple(pathlist)
else:
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.
"""
parent = self.tree[node][0]
for index, child in enumerate(self.children[parent]):
if child[1] == node:
break
if self.__reverse:
index -= 1
else:
index += 1
nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
val = node.prev if self.__reverse else node.next
return self.nodemap.node(val) if val is not None else val
if index >= 0 and index < len(self.children[parent]):
return self.children[parent][index][1]
else:
return None
def on_iter_children(self, node):
def on_iter_children(self, nodeid):
"""
Get the first child of the given node.
"""
if node in self.children:
if nodeid is None:
node = self.tree[None]
else:
nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
if node.children:
if self.__reverse:
size = len(self.children[node])
return self.children[node][size - 1][1]
size = len(node.children)
return self.nodemap.node(node.children[size - 1][1])
else:
return self.children[node][0][1]
return self.nodemap.node(node.children[0][1])
else:
return None
def on_iter_has_child(self, node):
def on_iter_has_child(self, nodeid):
"""
Find if the given node has any children.
"""
if node in self.children:
return True
if nodeid is None:
node = self.tree[None]
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.
"""
if node in self.children:
return len(self.children[node])
if nodeid is None:
node = self.tree[None]
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.
"""
if node in self.children:
if len(self.children[node]) > index:
if nodeid is None:
node = self.tree[None]
else:
nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
if node.children:
if len(node.children) > index:
if self.__reverse:
size = len(self.children[node])
return self.children[node][size - index - 1][1]
size = len(node.children)
return self.nodemap.node(node.children[size - index - 1][1])
else:
return self.children[node][index][1]
return self.nodemap.node(node.children[index][1])
else:
return None
else:
return None
def on_iter_parent(self, node):
def on_iter_parent(self, nodeid):
"""
Get the parent of the given node.
"""
if node in self.tree:
return self.tree[node][0]
else:
return None
nodeid = id(nodeid)
node = self.nodemap.node(nodeid)
return self.nodemap.node(node.parent) if node.parent is not None else \
None

View File

@ -166,6 +166,11 @@ class LongOpStatus(Callback):
self._start = time.time()
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):
"""Return the number of seconds estimated left before operation
completes. This will change as 'hearbeat' is called.
@ -284,14 +289,14 @@ class ProgressMonitor(object):
__default_popup_time = 5 # seconds
def __init__(self, dialog_class, dialog_class_params=(),
title=_("Progress Information"),
title=_("Progress Information"),
popup_time = None):
"""
@param dialog_class: A class used to display the progress dialog.
@type dialog_class: GtkProgressDialog or the same interface.
@param dialog_class_params: A tuple that will be used as the initial
arguments to the dialog_class, this might be used for passing in
arguments to the dialog_class, this might be used for passing in
a parent window handle.
@type dialog_class_params: tuple
@ -391,6 +396,8 @@ class ProgressMonitor(object):
facade.status_obj.disconnect(facade.heartbeat_cb_id)
facade.status_obj.disconnect(facade.end_cb_id)
del self._status_stack[idx]
if len(self._status_stack) == 0 and self._dlg:
self._dlg.close()
#-------------------------------------------------------------------------
#
@ -551,7 +558,7 @@ class GtkProgressDialog(gtk.Dialog):
self.destroy()
if __name__ == '__main__':
def test(a, b):
d = ProgressMonitor(GtkProgressDialog)
@ -600,4 +607,3 @@ if __name__ == '__main__':
w.show()
gtk.main()
print 'done'

View File

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