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:
parent
afe85ad0d5
commit
de2d669763
@ -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.
|
||||
|
@ -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
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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))
|
||||
|
Loading…
Reference in New Issue
Block a user