3275: PageView reworking main work by Nick Hall
Moving personview to the new pageview classes, and using a generic treebasemodel svn: r13515
This commit is contained in:
parent
e44a7cc8e2
commit
1b556586a4
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,8 @@
|
|||||||
#
|
#
|
||||||
# Copyright (C) 2000-2007 Donald N. Allingham
|
# Copyright (C) 2000-2007 Donald N. Allingham
|
||||||
# Copyright (C) 2009 Gary Burton
|
# Copyright (C) 2009 Gary Burton
|
||||||
|
# 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
|
||||||
@ -34,15 +36,6 @@ from __future__ import with_statement
|
|||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
import time
|
import time
|
||||||
import cgi
|
import cgi
|
||||||
import locale
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# set up logging
|
|
||||||
#
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
import logging
|
|
||||||
log = logging.getLogger(".")
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -51,6 +44,14 @@ log = logging.getLogger(".")
|
|||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
import gtk
|
import gtk
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# set up logging
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
import logging
|
||||||
|
_LOG = logging.getLogger(".")
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# GRAMPS modules
|
# GRAMPS modules
|
||||||
@ -62,412 +63,144 @@ from BasicUtils import name_displayer
|
|||||||
import DateHandler
|
import DateHandler
|
||||||
import ToolTips
|
import ToolTips
|
||||||
import GrampsLocale
|
import GrampsLocale
|
||||||
import config
|
|
||||||
from gen.utils.longop import LongOpStatus
|
|
||||||
from Filters import SearchFilter, ExactSearchFilter
|
|
||||||
from Lru import LRU
|
from Lru import LRU
|
||||||
|
from gui.views.treemodels.treebasemodel import TreeBaseModel
|
||||||
|
|
||||||
_CACHE_SIZE = 250
|
#-------------------------------------------------------------------------
|
||||||
invalid_date_format = config.get('preferences.invalid-date-format')
|
#
|
||||||
|
# COLUMN constants
|
||||||
class NodeTreeMap(object):
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
def __init__(self):
|
COLUMN_ID = 1
|
||||||
|
COLUMN_GENDER = 2
|
||||||
self.sortnames = {}
|
COLUMN_NAME = 3
|
||||||
self.temp_top_path2iter = []
|
COLUMN_DEATH = 5
|
||||||
|
COLUMN_BIRTH = 6
|
||||||
self.iter2path = {}
|
COLUMN_EVENT = 7
|
||||||
self.path2iter = {}
|
COLUMN_FAMILY = 8
|
||||||
self.sname_sub = {}
|
COLUMN_CHANGE = 17
|
||||||
|
COLUMN_MARKER = 18
|
||||||
self.temp_iter2path = {}
|
|
||||||
self.temp_path2iter = {}
|
|
||||||
self.temp_sname_sub = {}
|
|
||||||
|
|
||||||
def clear_sort_names(self):
|
|
||||||
self.sortnames = {}
|
|
||||||
|
|
||||||
def clear_temp_data(self):
|
|
||||||
self.temp_iter2path = {}
|
|
||||||
self.temp_path2iter = {}
|
|
||||||
self.temp_sname_sub = {}
|
|
||||||
|
|
||||||
def build_toplevel(self):
|
|
||||||
self.temp_top_path2iter = sorted(self.temp_sname_sub, key=locale.strxfrm)
|
|
||||||
for name in self.temp_top_path2iter:
|
|
||||||
self.build_sub_entry(name)
|
|
||||||
|
|
||||||
def get_group_names(self):
|
|
||||||
return self.temp_top_path2iter
|
|
||||||
|
|
||||||
def assign_sort_name(self, handle, sorted_name, group_name):
|
|
||||||
self.sortnames[handle] = sorted_name
|
|
||||||
if group_name in self.temp_sname_sub:
|
|
||||||
self.temp_sname_sub[group_name] += [handle]
|
|
||||||
else:
|
|
||||||
self.temp_sname_sub[group_name] = [handle]
|
|
||||||
|
|
||||||
def assign_data(self):
|
|
||||||
self.top_path2iter = self.temp_top_path2iter
|
|
||||||
self.iter2path = self.temp_iter2path
|
|
||||||
self.path2iter = self.temp_path2iter
|
|
||||||
self.sname_sub = self.temp_sname_sub
|
|
||||||
self.top_iter2path = {}
|
|
||||||
for i, item in enumerate(self.top_path2iter):
|
|
||||||
self.top_iter2path[item] = i
|
|
||||||
|
|
||||||
def get_path(self, node):
|
|
||||||
if node in self.top_iter2path:
|
|
||||||
return (self.top_iter2path[node], )
|
|
||||||
else:
|
|
||||||
(surname, index) = self.iter2path[node]
|
|
||||||
return (self.top_iter2path[surname], index)
|
|
||||||
|
|
||||||
def has_entry(self, handle):
|
|
||||||
return handle in self.iter2path
|
|
||||||
|
|
||||||
def get_iter(self, path):
|
|
||||||
try:
|
|
||||||
if len(path)==1: # Top Level
|
|
||||||
return self.top_path2iter[path[0]]
|
|
||||||
else: # Sublevel
|
|
||||||
surname = self.top_path2iter[path[0]]
|
|
||||||
return self.path2iter[(surname, path[1])]
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def has_top_node(self, node):
|
|
||||||
return node in self.sname_sub
|
|
||||||
|
|
||||||
def find_next_node(self, node):
|
|
||||||
if node in self.top_iter2path:
|
|
||||||
path = self.top_iter2path[node]
|
|
||||||
if path+1 < len(self.top_path2iter):
|
|
||||||
return self.top_path2iter[path+1]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
(surname, val) = self.iter2path[node]
|
|
||||||
return self.path2iter.get((surname, val+1))
|
|
||||||
|
|
||||||
def first_child(self, node):
|
|
||||||
if node is None:
|
|
||||||
return self.top_path2iter[0]
|
|
||||||
else:
|
|
||||||
return self.path2iter.get((node, 0))
|
|
||||||
|
|
||||||
def has_child(self, node):
|
|
||||||
if node is None:
|
|
||||||
return len(self.sname_sub)
|
|
||||||
if node in self.sname_sub and self.sname_sub[node]:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def number_of_children(self, node):
|
|
||||||
if node is None:
|
|
||||||
return len(self.sname_sub)
|
|
||||||
if node in self.sname_sub:
|
|
||||||
return len(self.sname_sub[node])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_nth_child(self, node, n):
|
|
||||||
if node is None:
|
|
||||||
if n < len(self.top_path2iter):
|
|
||||||
return self.top_path2iter[n]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return self.path2iter.get((node, n))
|
|
||||||
|
|
||||||
def get_parent_of(self, node):
|
|
||||||
path = self.iter2path.get(node)
|
|
||||||
if path:
|
|
||||||
return path[0]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def build_sub_entry(self, name):
|
|
||||||
slist = sorted(( (self.sortnames[x], x) \
|
|
||||||
for x in self.temp_sname_sub[name] ),
|
|
||||||
key=lambda x: locale.strxfrm(x[0]))
|
|
||||||
|
|
||||||
for val, (junk, person_handle) in enumerate(slist):
|
|
||||||
tpl = (name, val)
|
|
||||||
self.temp_iter2path[person_handle] = tpl
|
|
||||||
self.temp_path2iter[tpl] = person_handle
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# PeopleModel
|
# PeopleModel
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
class PeopleModel(gtk.GenericTreeModel):
|
class PeopleModel(TreeBaseModel):
|
||||||
"""
|
"""
|
||||||
Basic GenericTreeModel interface to handle the Tree interface for
|
Basic GenericTreeModel interface to handle the Tree interface for
|
||||||
the PersonView
|
the PersonView
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Model types
|
|
||||||
GENERIC = 0
|
|
||||||
SEARCH = 1
|
|
||||||
FAST = 2
|
|
||||||
|
|
||||||
# Column numbers
|
|
||||||
_ID_COL = 1
|
|
||||||
_GENDER_COL = 2
|
|
||||||
_NAME_COL = 3
|
|
||||||
_DEATH_COL = 5
|
|
||||||
_BIRTH_COL = 6
|
|
||||||
_EVENT_COL = 7
|
|
||||||
_FAMILY_COL = 8
|
|
||||||
_CHANGE_COL = 17
|
|
||||||
_MARKER_COL = 18
|
|
||||||
|
|
||||||
_GENDER = [ _(u'female'), _(u'male'), _(u'unknown') ]
|
_GENDER = [ _(u'female'), _(u'male'), _(u'unknown') ]
|
||||||
|
|
||||||
# dynamic calculation of column indices, for use by various Views
|
# The following is accessed from PersonView - CHECK
|
||||||
COLUMN_INT_ID = 12
|
COLUMN_INT_ID = 12 # dynamic calculation of column indices
|
||||||
|
|
||||||
# indices into main column definition table
|
def __init__(self, db, scol=0, order=gtk.SORT_ASCENDING, search=None,
|
||||||
COLUMN_DEF_LIST = 0
|
skip=set(), sort_map=None):
|
||||||
COLUMN_DEF_HEADER = 1
|
|
||||||
COLUMN_DEF_TYPE = 2
|
|
||||||
|
|
||||||
def __init__(self, db, filter_info=None, skip=[]):
|
|
||||||
"""
|
"""
|
||||||
Initialize the model building the initial data
|
Initialize the model building the initial data
|
||||||
"""
|
"""
|
||||||
gtk.GenericTreeModel.__init__(self)
|
self.lru_name = LRU(TreeBaseModel._CACHE_SIZE)
|
||||||
|
self.lru_bdate = LRU(TreeBaseModel._CACHE_SIZE)
|
||||||
|
self.lru_ddate = LRU(TreeBaseModel._CACHE_SIZE)
|
||||||
|
|
||||||
self.db = db
|
self.gen_cursor = db.get_person_cursor
|
||||||
self.in_build = False
|
self.map = db.get_raw_person_data
|
||||||
self.lru_data = LRU(_CACHE_SIZE)
|
self.scol = scol
|
||||||
self.lru_name = LRU(_CACHE_SIZE)
|
|
||||||
self.lru_bdate = LRU(_CACHE_SIZE)
|
|
||||||
self.lru_ddate = LRU(_CACHE_SIZE)
|
|
||||||
|
|
||||||
config.connect("preferences.todo-color",
|
#self.group_list = []
|
||||||
self.update_todo)
|
|
||||||
config.connect("preferences.custom-marker-color",
|
|
||||||
self.update_custom)
|
|
||||||
config.connect("preferences.complete-color",
|
|
||||||
self.update_complete)
|
|
||||||
|
|
||||||
self.complete_color = config.get('preferences.complete-color')
|
|
||||||
self.todo_color = config.get('preferences.todo-color')
|
|
||||||
self.custom_color = config.get('preferences.custom-marker-color')
|
|
||||||
|
|
||||||
self.marker_color_column = 10
|
|
||||||
self.tooltip_column = 11
|
|
||||||
|
|
||||||
self.mapper = NodeTreeMap()
|
|
||||||
|
|
||||||
self.total = 0
|
|
||||||
self.displayed = 0
|
|
||||||
|
|
||||||
if filter_info and filter_info != (1, (0, u'', False)):
|
|
||||||
if filter_info[0] == PeopleModel.GENERIC:
|
|
||||||
data_filter = filter_info[1]
|
|
||||||
self._build_data = self._build_filter_sub
|
|
||||||
elif filter_info[0] == PeopleModel.SEARCH:
|
|
||||||
col, text, inv = filter_info[1][:3]
|
|
||||||
func = lambda x: self.on_get_value(x, col) or u""
|
|
||||||
|
|
||||||
if col == self._GENDER_COL:
|
|
||||||
data_filter = ExactSearchFilter(func, text, inv)
|
|
||||||
else:
|
|
||||||
data_filter = SearchFilter(func, text, inv)
|
|
||||||
|
|
||||||
self._build_data = self._build_search_sub
|
|
||||||
else:
|
|
||||||
data_filter = filter_info[1]
|
|
||||||
self._build_data = self._build_search_sub
|
|
||||||
else:
|
|
||||||
self._build_data = self._build_search_sub
|
|
||||||
data_filter = None
|
|
||||||
self.current_filter = data_filter
|
|
||||||
self.rebuild_data(data_filter, skip)
|
|
||||||
|
|
||||||
def update_todo(self,client,cnxn_id,entry,data):
|
|
||||||
self.todo_color = config.get('preferences.todo-color')
|
|
||||||
|
|
||||||
def update_custom(self,client,cnxn_id,entry,data):
|
|
||||||
self.custom_color = config.get('preferences.custom-marker-color')
|
|
||||||
|
|
||||||
def update_complete(self,client,cnxn_id,entry,data):
|
|
||||||
self.complete_color = config.get('preferences.complete-color')
|
|
||||||
|
|
||||||
def rebuild_data(self, data_filter=None, skip=[]):
|
|
||||||
"""
|
|
||||||
Convience function that calculates the new data and assigns it.
|
|
||||||
"""
|
|
||||||
self.calculate_data(data_filter, skip)
|
|
||||||
self.assign_data()
|
|
||||||
self.current_filter = data_filter
|
|
||||||
|
|
||||||
def _build_search_sub(self,dfilter, skip):
|
|
||||||
ngn = name_displayer.name_grouping_data
|
|
||||||
nsn = name_displayer.raw_sorted_name
|
|
||||||
|
|
||||||
self.mapper.clear_sort_names()
|
|
||||||
|
|
||||||
self.total = 0
|
|
||||||
self.displayed = 0
|
|
||||||
with self.db.get_person_cursor() as cursor:
|
|
||||||
for handle, d in cursor:
|
|
||||||
self.total += 1
|
|
||||||
if not (handle in skip or (dfilter and not dfilter.match(handle,self.db))):
|
|
||||||
name_data = d[PeopleModel._NAME_COL]
|
|
||||||
group_name = ngn(self.db, name_data)
|
|
||||||
sorted_name = nsn(name_data)
|
|
||||||
self.displayed += 1
|
|
||||||
self.mapper.assign_sort_name(handle, sorted_name, group_name)
|
|
||||||
|
|
||||||
def _build_filter_sub(self,dfilter, skip):
|
|
||||||
ngn = name_displayer.name_grouping_data
|
|
||||||
nsn = name_displayer.raw_sorted_name
|
|
||||||
handle_list = self.db.iter_person_handles()
|
|
||||||
self.total = self.db.get_number_of_people()
|
|
||||||
|
|
||||||
if dfilter:
|
|
||||||
handle_list = dfilter.apply(self.db, handle_list)
|
|
||||||
self.displayed = len(handle_list)
|
|
||||||
else:
|
|
||||||
self.displayed = self.db.get_number_of_people()
|
|
||||||
|
|
||||||
self.mapper.clear_sort_names()
|
|
||||||
status = LongOpStatus(msg="Loading People",
|
|
||||||
total_steps=self.displayed,
|
|
||||||
interval=self.displayed//10)
|
|
||||||
self.db.emit('long-op-start', (status,))
|
|
||||||
for handle in handle_list:
|
|
||||||
status.heartbeat()
|
|
||||||
d = self.db.get_raw_person_data(handle)
|
|
||||||
if not handle in skip:
|
|
||||||
name_data = d[PeopleModel._NAME_COL]
|
|
||||||
group_name = ngn(self.db, name_data)
|
|
||||||
sorted_name = nsn(name_data)
|
|
||||||
|
|
||||||
self.mapper.assign_sort_name(handle, sorted_name, group_name)
|
|
||||||
status.end()
|
|
||||||
|
|
||||||
def calculate_data(self, dfilter=None, skip=[]):
|
|
||||||
"""
|
|
||||||
Calculate the new path to node values for the model.
|
|
||||||
"""
|
|
||||||
self.clear_cache()
|
|
||||||
self.in_build = True
|
|
||||||
|
|
||||||
self.total = 0
|
|
||||||
self.displayed = 0
|
|
||||||
|
|
||||||
if dfilter:
|
|
||||||
self.dfilter = dfilter
|
|
||||||
|
|
||||||
self.mapper.clear_temp_data()
|
|
||||||
|
|
||||||
if not self.db.is_open():
|
|
||||||
return
|
|
||||||
|
|
||||||
self._build_data(dfilter, skip)
|
|
||||||
self.mapper.build_toplevel()
|
|
||||||
|
|
||||||
self.in_build = False
|
|
||||||
|
|
||||||
|
self.fmap = [
|
||||||
|
self.column_name,
|
||||||
|
self.column_id,
|
||||||
|
self.column_gender,
|
||||||
|
self.column_birth_day,
|
||||||
|
self.column_birth_place,
|
||||||
|
self.column_death_day,
|
||||||
|
self.column_death_place,
|
||||||
|
self.column_spouse,
|
||||||
|
self.column_change,
|
||||||
|
self.column_marker_text,
|
||||||
|
self.column_marker_color,
|
||||||
|
self.column_tooltip,
|
||||||
|
self.column_int_id,
|
||||||
|
]
|
||||||
|
self.smap = [
|
||||||
|
self.sort_name,
|
||||||
|
self.column_id,
|
||||||
|
self.column_gender,
|
||||||
|
self.sort_birth_day,
|
||||||
|
self.column_birth_place,
|
||||||
|
self.sort_death_day,
|
||||||
|
self.column_death_place,
|
||||||
|
self.column_spouse,
|
||||||
|
self.column_change,
|
||||||
|
self.column_marker_text,
|
||||||
|
self.column_marker_color,
|
||||||
|
self.column_tooltip,
|
||||||
|
self.column_int_id,
|
||||||
|
]
|
||||||
|
self.hmap = [
|
||||||
|
self.column_header,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
]
|
||||||
|
TreeBaseModel.__init__(self, db, search=search, skip=skip,
|
||||||
|
tooltip_column=11, marker_column=10,
|
||||||
|
scol=scol, order=order, sort_map=sort_map)
|
||||||
def clear_cache(self):
|
def clear_cache(self):
|
||||||
|
""" Clear the LRU cache """
|
||||||
|
TreeBaseModel.clear_cache(self)
|
||||||
self.lru_name.clear()
|
self.lru_name.clear()
|
||||||
self.lru_data.clear()
|
|
||||||
self.lru_bdate.clear()
|
self.lru_bdate.clear()
|
||||||
self.lru_ddate.clear()
|
self.lru_ddate.clear()
|
||||||
|
|
||||||
def build_sub_entry(self, name):
|
|
||||||
self.mapper.build_sub_entry(name)
|
|
||||||
|
|
||||||
def assign_data(self):
|
|
||||||
self.mapper.assign_data()
|
|
||||||
|
|
||||||
def on_get_flags(self):
|
|
||||||
"""returns the GtkTreeModelFlags for this particular type of model"""
|
|
||||||
return gtk.TREE_MODEL_ITERS_PERSIST
|
|
||||||
|
|
||||||
def on_get_n_columns(self):
|
def on_get_n_columns(self):
|
||||||
return len(PeopleModel.COLUMN_DEFS)
|
""" Return the number of columns in the model """
|
||||||
|
return len(self.fmap)+1
|
||||||
|
|
||||||
def on_get_path(self, node):
|
def add_row(self, handle, data):
|
||||||
"""returns the tree path (a tuple of indices at the various
|
"""
|
||||||
levels) for a particular node."""
|
Add nodes to the node map for a single person.
|
||||||
return self.mapper.get_path(node)
|
|
||||||
|
|
||||||
def is_visable(self, handle):
|
handle The handle of the gramps object.
|
||||||
return self.mapper.has_entry(handle)
|
data The object data.
|
||||||
|
"""
|
||||||
|
ngn = name_displayer.name_grouping_data
|
||||||
|
nsn = name_displayer.raw_sorted_name
|
||||||
|
|
||||||
def on_get_column_type(self, index):
|
name_data = data[COLUMN_NAME]
|
||||||
return PeopleModel.COLUMN_DEFS[index][PeopleModel.COLUMN_DEF_TYPE]
|
group_name = ngn(self.db, name_data)
|
||||||
|
sort_key = self.sort_func(data, handle)
|
||||||
|
|
||||||
def on_get_iter(self, path):
|
#if group_name not in self.group_list:
|
||||||
return self.mapper.get_iter(path)
|
#self.group_list.append(group_name)
|
||||||
|
#self.add_node(None, group_name, group_name, None)
|
||||||
|
|
||||||
def on_get_value(self, node, col):
|
# add as node: parent, child, sortkey, handle; parent and child are
|
||||||
# test for header or data row-type
|
# nodes in the treebasemodel, and will be used as iters
|
||||||
if self.mapper.has_top_node(node):
|
self.add_node(group_name, handle, sort_key, handle)
|
||||||
# Header rows dont get the foreground color set
|
|
||||||
if col == self.marker_color_column:
|
|
||||||
return None
|
|
||||||
# test for 'header' column being empty (most are)
|
|
||||||
if not PeopleModel.COLUMN_DEFS[col][PeopleModel.COLUMN_DEF_HEADER]:
|
|
||||||
return u''
|
|
||||||
# return values for 'header' row, calling a function
|
|
||||||
# according to column_defs table
|
|
||||||
return (PeopleModel.COLUMN_DEFS[col]
|
|
||||||
[PeopleModel.COLUMN_DEF_HEADER](self, node)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# return values for 'data' row, calling a function
|
|
||||||
# according to column_defs table
|
|
||||||
try:
|
|
||||||
if node in self.lru_data:
|
|
||||||
data = self.lru_data[node]
|
|
||||||
else:
|
|
||||||
data = self.db.get_raw_person_data(str(node))
|
|
||||||
if not self.in_build:
|
|
||||||
self.lru_data[node] = data
|
|
||||||
return (PeopleModel.COLUMN_DEFS[col]
|
|
||||||
[PeopleModel.COLUMN_DEF_LIST](self, data, node)
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def on_iter_next(self, node):
|
def sort_name(self, data, node):
|
||||||
"""returns the next node at this level of the tree"""
|
|
||||||
return self.mapper.find_next_node(node)
|
|
||||||
|
|
||||||
def on_iter_children(self, node):
|
|
||||||
"""Return the first child of the node"""
|
|
||||||
return self.mapper.first_child(node)
|
|
||||||
|
|
||||||
def on_iter_has_child(self, node):
|
|
||||||
"""returns true if this node has children"""
|
|
||||||
return self.mapper.has_child(node)
|
|
||||||
|
|
||||||
def on_iter_n_children(self, node):
|
|
||||||
return self.mapper.number_of_children(node)
|
|
||||||
|
|
||||||
def on_iter_nth_child(self, node, n):
|
|
||||||
return self.mapper.get_nth_child(node, n)
|
|
||||||
|
|
||||||
def on_iter_parent(self, node):
|
|
||||||
"""returns the parent of this node"""
|
|
||||||
return self.mapper.get_parent_of(node)
|
|
||||||
|
|
||||||
def column_sort_name(self, data, node):
|
|
||||||
n = Name()
|
n = Name()
|
||||||
n.unserialize(data[PeopleModel._NAME_COL])
|
n.unserialize(data[COLUMN_NAME])
|
||||||
return name_displayer.sort_string(n)
|
return name_displayer.sort_string(n)
|
||||||
|
|
||||||
def column_spouse(self, data, node):
|
def column_spouse(self, data, node):
|
||||||
spouses_names = u""
|
spouses_names = u""
|
||||||
handle = data[0]
|
handle = data[0]
|
||||||
for family_handle in data[PeopleModel._FAMILY_COL]:
|
for family_handle in data[COLUMN_FAMILY]:
|
||||||
family = self.db.get_family_from_handle(family_handle)
|
family = self.db.get_family_from_handle(family_handle)
|
||||||
for spouse_id in [family.get_father_handle(),
|
for spouse_id in [family.get_father_handle(),
|
||||||
family.get_mother_handle()]:
|
family.get_mother_handle()]:
|
||||||
@ -485,40 +218,46 @@ class PeopleModel(gtk.GenericTreeModel):
|
|||||||
if node in self.lru_name:
|
if node in self.lru_name:
|
||||||
name = self.lru_name[node]
|
name = self.lru_name[node]
|
||||||
else:
|
else:
|
||||||
name = name_displayer.raw_sorted_name(data[PeopleModel._NAME_COL])
|
name = name_displayer.raw_sorted_name(data[COLUMN_NAME])
|
||||||
if not self.in_build:
|
if not self._in_build:
|
||||||
self.lru_name[node] = name
|
self.lru_name[node] = name
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def column_id(self, data, node):
|
def column_id(self, data, node):
|
||||||
return data[PeopleModel._ID_COL]
|
return data[COLUMN_ID]
|
||||||
|
|
||||||
def column_change(self, data, node):
|
def column_change(self, data, node):
|
||||||
return unicode(
|
return unicode(
|
||||||
time.strftime('%x %X',
|
time.strftime('%x %X',
|
||||||
time.localtime(data[PeopleModel._CHANGE_COL])),
|
time.localtime(data[COLUMN_CHANGE])),
|
||||||
GrampsLocale.codeset)
|
GrampsLocale.codeset)
|
||||||
|
|
||||||
def column_gender(self, data, node):
|
def column_gender(self, data, node):
|
||||||
return PeopleModel._GENDER[data[PeopleModel._GENDER_COL]]
|
return PeopleModel._GENDER[data[COLUMN_GENDER]]
|
||||||
|
|
||||||
def column_birth_day(self, data, node):
|
def column_birth_day(self, data, node):
|
||||||
if node in self.lru_bdate:
|
if node in self.lru_bdate:
|
||||||
value = self.lru_bdate[node]
|
value = self.lru_bdate[node]
|
||||||
else:
|
else:
|
||||||
value = self._get_birth_data(data, node)
|
value = self._get_birth_data(data, node, False)
|
||||||
if not self.in_build:
|
if not self._in_build:
|
||||||
self.lru_bdate[node] = value
|
self.lru_bdate[node] = value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _get_birth_data(self, data, node):
|
def sort_birth_day(self, data, node):
|
||||||
index = data[PeopleModel._BIRTH_COL]
|
return self._get_birth_data(data, node, True)
|
||||||
|
|
||||||
|
def _get_birth_data(self, data, node, sort_mode):
|
||||||
|
index = data[COLUMN_BIRTH]
|
||||||
if index != -1:
|
if index != -1:
|
||||||
try:
|
try:
|
||||||
local = data[PeopleModel._EVENT_COL][index]
|
local = data[COLUMN_EVENT][index]
|
||||||
b = EventRef()
|
b = EventRef()
|
||||||
b.unserialize(local)
|
b.unserialize(local)
|
||||||
birth = self.db.get_event_from_handle(b.ref)
|
birth = self.db.get_event_from_handle(b.ref)
|
||||||
|
if sort_mode:
|
||||||
|
retval = "%09d" % birth.get_date_object().get_sort_value()
|
||||||
|
else:
|
||||||
date_str = DateHandler.get_date(birth)
|
date_str = DateHandler.get_date(birth)
|
||||||
if date_str != "":
|
if date_str != "":
|
||||||
retval = cgi.escape(date_str)
|
retval = cgi.escape(date_str)
|
||||||
@ -529,7 +268,7 @@ class PeopleModel(gtk.GenericTreeModel):
|
|||||||
except:
|
except:
|
||||||
return u''
|
return u''
|
||||||
|
|
||||||
for event_ref in data[PeopleModel._EVENT_COL]:
|
for event_ref in data[COLUMN_EVENT]:
|
||||||
er = EventRef()
|
er = EventRef()
|
||||||
er.unserialize(event_ref)
|
er.unserialize(event_ref)
|
||||||
event = self.db.get_event_from_handle(er.ref)
|
event = self.db.get_event_from_handle(er.ref)
|
||||||
@ -538,6 +277,9 @@ class PeopleModel(gtk.GenericTreeModel):
|
|||||||
if (etype in [EventType.BAPTISM, EventType.CHRISTEN]
|
if (etype in [EventType.BAPTISM, EventType.CHRISTEN]
|
||||||
and er.get_role() == EventRoleType.PRIMARY
|
and er.get_role() == EventRoleType.PRIMARY
|
||||||
and date_str != ""):
|
and date_str != ""):
|
||||||
|
if sort_mode:
|
||||||
|
retval = "%09d" % event.get_date_object().get_sort_value()
|
||||||
|
else:
|
||||||
retval = u"<i>%s</i>" % cgi.escape(date_str)
|
retval = u"<i>%s</i>" % cgi.escape(date_str)
|
||||||
if not DateHandler.get_date_valid(event):
|
if not DateHandler.get_date_valid(event):
|
||||||
return invalid_date_format % retval
|
return invalid_date_format % retval
|
||||||
@ -550,19 +292,25 @@ class PeopleModel(gtk.GenericTreeModel):
|
|||||||
if node in self.lru_ddate:
|
if node in self.lru_ddate:
|
||||||
value = self.lru_ddate[node]
|
value = self.lru_ddate[node]
|
||||||
else:
|
else:
|
||||||
value = self._get_death_data(data, node)
|
value = self._get_death_data(data, node, False)
|
||||||
if not self.in_build:
|
if not self._in_build:
|
||||||
self.lru_ddate[node] = value
|
self.lru_ddate[node] = value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _get_death_data(self, data, node):
|
def sort_death_day(self, data, node):
|
||||||
index = data[PeopleModel._DEATH_COL]
|
return self._get_death_data(data, node, True)
|
||||||
|
|
||||||
|
def _get_death_data(self, data, node, sort_mode):
|
||||||
|
index = data[COLUMN_DEATH]
|
||||||
if index != -1:
|
if index != -1:
|
||||||
try:
|
try:
|
||||||
local = data[PeopleModel._EVENT_COL][index]
|
local = data[COLUMN_EVENT][index]
|
||||||
ref = EventRef()
|
ref = EventRef()
|
||||||
ref.unserialize(local)
|
ref.unserialize(local)
|
||||||
event = self.db.get_event_from_handle(ref.ref)
|
event = self.db.get_event_from_handle(ref.ref)
|
||||||
|
if sort_mode:
|
||||||
|
retval = "%09d" % event.get_date_object().get_sort_value()
|
||||||
|
else:
|
||||||
date_str = DateHandler.get_date(event)
|
date_str = DateHandler.get_date(event)
|
||||||
if date_str != "":
|
if date_str != "":
|
||||||
retval = cgi.escape(date_str)
|
retval = cgi.escape(date_str)
|
||||||
@ -573,15 +321,20 @@ class PeopleModel(gtk.GenericTreeModel):
|
|||||||
except:
|
except:
|
||||||
return u''
|
return u''
|
||||||
|
|
||||||
for event_ref in data[PeopleModel._EVENT_COL]:
|
for event_ref in data[COLUMN_EVENT]:
|
||||||
er = EventRef()
|
er = EventRef()
|
||||||
er.unserialize(event_ref)
|
er.unserialize(event_ref)
|
||||||
event = self.db.get_event_from_handle(er.ref)
|
event = self.db.get_event_from_handle(er.ref)
|
||||||
etype = event.get_type()
|
etype = event.get_type()
|
||||||
date_str = DateHandler.get_date(event)
|
date_str = DateHandler.get_date(event)
|
||||||
if (etype in [EventType.BURIAL, EventType.CREMATION, EventType.CAUSE_DEATH]
|
if (etype in [EventType.BURIAL,
|
||||||
|
EventType.CREMATION,
|
||||||
|
EventType.CAUSE_DEATH]
|
||||||
and er.get_role() == EventRoleType.PRIMARY
|
and er.get_role() == EventRoleType.PRIMARY
|
||||||
and date_str):
|
and date_str):
|
||||||
|
if sort_mode:
|
||||||
|
retval = "%09d" % event.get_date_object().get_sort_value()
|
||||||
|
else:
|
||||||
retval = "<i>%s</i>" % cgi.escape(date_str)
|
retval = "<i>%s</i>" % cgi.escape(date_str)
|
||||||
if not DateHandler.get_date_valid(event):
|
if not DateHandler.get_date_valid(event):
|
||||||
return invalid_date_format % retval
|
return invalid_date_format % retval
|
||||||
@ -590,10 +343,10 @@ class PeopleModel(gtk.GenericTreeModel):
|
|||||||
return u""
|
return u""
|
||||||
|
|
||||||
def column_birth_place(self, data, node):
|
def column_birth_place(self, data, node):
|
||||||
index = data[PeopleModel._BIRTH_COL]
|
index = data[COLUMN_BIRTH]
|
||||||
if index != -1:
|
if index != -1:
|
||||||
try:
|
try:
|
||||||
local = data[PeopleModel._EVENT_COL][index]
|
local = data[COLUMN_EVENT][index]
|
||||||
br = EventRef()
|
br = EventRef()
|
||||||
br.unserialize(local)
|
br.unserialize(local)
|
||||||
event = self.db.get_event_from_handle(br.ref)
|
event = self.db.get_event_from_handle(br.ref)
|
||||||
@ -607,7 +360,7 @@ class PeopleModel(gtk.GenericTreeModel):
|
|||||||
except:
|
except:
|
||||||
return u''
|
return u''
|
||||||
|
|
||||||
for event_ref in data[PeopleModel._EVENT_COL]:
|
for event_ref in data[COLUMN_EVENT]:
|
||||||
er = EventRef()
|
er = EventRef()
|
||||||
er.unserialize(event_ref)
|
er.unserialize(event_ref)
|
||||||
event = self.db.get_event_from_handle(er.ref)
|
event = self.db.get_event_from_handle(er.ref)
|
||||||
@ -625,10 +378,10 @@ class PeopleModel(gtk.GenericTreeModel):
|
|||||||
return u""
|
return u""
|
||||||
|
|
||||||
def column_death_place(self, data, node):
|
def column_death_place(self, data, node):
|
||||||
index = data[PeopleModel._DEATH_COL]
|
index = data[COLUMN_DEATH]
|
||||||
if index != -1:
|
if index != -1:
|
||||||
try:
|
try:
|
||||||
local = data[PeopleModel._EVENT_COL][index]
|
local = data[COLUMN_EVENT][index]
|
||||||
dr = EventRef()
|
dr = EventRef()
|
||||||
dr.unserialize(local)
|
dr.unserialize(local)
|
||||||
event = self.db.get_event_from_handle(dr.ref)
|
event = self.db.get_event_from_handle(dr.ref)
|
||||||
@ -642,12 +395,13 @@ class PeopleModel(gtk.GenericTreeModel):
|
|||||||
except:
|
except:
|
||||||
return u''
|
return u''
|
||||||
|
|
||||||
for event_ref in data[PeopleModel._EVENT_COL]:
|
for event_ref in data[COLUMN_EVENT]:
|
||||||
er = EventRef()
|
er = EventRef()
|
||||||
er.unserialize(event_ref)
|
er.unserialize(event_ref)
|
||||||
event = self.db.get_event_from_handle(er.ref)
|
event = self.db.get_event_from_handle(er.ref)
|
||||||
etype = event.get_type()
|
etype = event.get_type()
|
||||||
if (etype in [EventType.BURIAL, EventType.CREMATION, EventType.CAUSE_DEATH]
|
if (etype in [EventType.BURIAL, EventType.CREMATION,
|
||||||
|
EventType.CAUSE_DEATH]
|
||||||
and er.get_role() == EventRoleType.PRIMARY):
|
and er.get_role() == EventRoleType.PRIMARY):
|
||||||
|
|
||||||
place_handle = event.get_place_handle()
|
place_handle = event.get_place_handle()
|
||||||
@ -659,18 +413,18 @@ class PeopleModel(gtk.GenericTreeModel):
|
|||||||
return u""
|
return u""
|
||||||
|
|
||||||
def column_marker_text(self, data, node):
|
def column_marker_text(self, data, node):
|
||||||
if PeopleModel._MARKER_COL < len(data):
|
if COLUMN_MARKER < len(data):
|
||||||
return str(data[PeopleModel._MARKER_COL])
|
return str(data[COLUMN_MARKER])
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def column_marker_color(self, data, node):
|
def column_marker_color(self, data, node):
|
||||||
try:
|
try:
|
||||||
if data[PeopleModel._MARKER_COL]:
|
if data[COLUMN_MARKER]:
|
||||||
if data[PeopleModel._MARKER_COL][0] == MarkerType.COMPLETE:
|
if data[COLUMN_MARKER][0] == MarkerType.COMPLETE:
|
||||||
return self.complete_color
|
return self.complete_color
|
||||||
if data[PeopleModel._MARKER_COL][0] == MarkerType.TODO_TYPE:
|
if data[COLUMN_MARKER][0] == MarkerType.TODO_TYPE:
|
||||||
return self.todo_color
|
return self.todo_color
|
||||||
if data[PeopleModel._MARKER_COL][0] == MarkerType.CUSTOM:
|
if data[COLUMN_MARKER][0] == MarkerType.CUSTOM:
|
||||||
return self.custom_color
|
return self.custom_color
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
@ -694,24 +448,3 @@ class PeopleModel(gtk.GenericTreeModel):
|
|||||||
def column_header_view(self, node):
|
def column_header_view(self, node):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# table of column definitions
|
|
||||||
# (unless this is declared after the PeopleModel class, an error is thrown)
|
|
||||||
|
|
||||||
COLUMN_DEFS = [
|
|
||||||
(column_name, column_header, str),
|
|
||||||
(column_id, None, str),
|
|
||||||
(column_gender, None, str),
|
|
||||||
(column_birth_day, None, str),
|
|
||||||
(column_birth_place, None, str),
|
|
||||||
(column_death_day, None, str),
|
|
||||||
(column_death_place, None, str),
|
|
||||||
(column_spouse, None, str),
|
|
||||||
(column_change, None, str),
|
|
||||||
(column_marker_text, None, str),
|
|
||||||
(column_marker_color, None, str),
|
|
||||||
# the order of the above columns must match PeopleView.column_names
|
|
||||||
|
|
||||||
# these columns are hidden, and must always be last in the list
|
|
||||||
(column_tooltip, None, object),
|
|
||||||
(column_int_id, None, str),
|
|
||||||
]
|
|
||||||
|
@ -37,7 +37,6 @@ import pango
|
|||||||
import const
|
import const
|
||||||
import ManagedWindow
|
import ManagedWindow
|
||||||
from Filters import SearchBar
|
from Filters import SearchBar
|
||||||
from DisplayModels import PeopleModel
|
|
||||||
from glade import Glade
|
from glade import Glade
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
@ -62,7 +61,7 @@ class BaseSelector(ManagedWindow.ManagedWindow):
|
|||||||
set of handles to skip in the view, and search_bar to show the
|
set of handles to skip in the view, and search_bar to show the
|
||||||
SearchBar at the top or not.
|
SearchBar at the top or not.
|
||||||
"""
|
"""
|
||||||
self.filter = filter
|
self.filter = (2, filter, False)
|
||||||
|
|
||||||
# Set window title, some selectors may set self.title in their __init__
|
# Set window title, some selectors may set self.title in their __init__
|
||||||
if not hasattr(self, 'title'):
|
if not hasattr(self, 'title'):
|
||||||
@ -231,6 +230,12 @@ class BaseSelector(ManagedWindow.ManagedWindow):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def exact_search(self):
|
||||||
|
"""
|
||||||
|
Returns a tuple indicating columns requiring an exact search
|
||||||
|
"""
|
||||||
|
return ()
|
||||||
|
|
||||||
def setup_filter(self):
|
def setup_filter(self):
|
||||||
"""
|
"""
|
||||||
Builds the default filters and add them to the filter bar.
|
Builds the default filters and add them to the filter bar.
|
||||||
@ -244,18 +249,20 @@ class BaseSelector(ManagedWindow.ManagedWindow):
|
|||||||
"""
|
"""
|
||||||
Builds the selection people see in the Selector
|
Builds the selection people see in the Selector
|
||||||
"""
|
"""
|
||||||
|
if self.filter:
|
||||||
|
filter_info = self.filter
|
||||||
|
else:
|
||||||
#search info for the
|
#search info for the
|
||||||
filter_info = (False, self.search_bar.get_value(), False)
|
if self.search_bar.get_value()[0] in self.exact_search():
|
||||||
|
filter_info = (0, self.search_bar.get_value(), True)
|
||||||
|
else:
|
||||||
|
filter_info = (0, self.search_bar.get_value(), False)
|
||||||
|
|
||||||
#set up cols the first time
|
#set up cols the first time
|
||||||
if self.setupcols :
|
if self.setupcols :
|
||||||
self.add_columns(self.tree)
|
self.add_columns(self.tree)
|
||||||
|
|
||||||
#reset the model with correct sorting
|
#reset the model with correct sorting
|
||||||
if self.get_model_class() is PeopleModel:
|
|
||||||
self.model = PeopleModel(self.db,
|
|
||||||
(PeopleModel.FAST, self.filter),
|
|
||||||
skip=self.skip_list)
|
|
||||||
else:
|
|
||||||
self.model = self.get_model_class()(self.db, self.sort_col,
|
self.model = self.get_model_class()(self.db, self.sort_col,
|
||||||
self.sortorder,
|
self.sortorder,
|
||||||
sort_map=self.column_order(),
|
sort_map=self.column_order(),
|
||||||
@ -272,9 +279,6 @@ class BaseSelector(ManagedWindow.ManagedWindow):
|
|||||||
self.columns[self.sort_col].set_sort_order(self.sortorder)
|
self.columns[self.sort_col].set_sort_order(self.sortorder)
|
||||||
|
|
||||||
# set the search column to be the sorted column
|
# set the search column to be the sorted column
|
||||||
if self.get_model_class() is PeopleModel:
|
|
||||||
search_col = 0
|
|
||||||
else:
|
|
||||||
search_col = self.column_order()[self.sort_col][1]
|
search_col = self.column_order()[self.sort_col][1]
|
||||||
self.tree.set_search_column(search_col)
|
self.tree.set_search_column(search_col)
|
||||||
|
|
||||||
@ -283,18 +287,17 @@ class BaseSelector(ManagedWindow.ManagedWindow):
|
|||||||
def column_clicked(self, obj, data):
|
def column_clicked(self, obj, data):
|
||||||
if self.sort_col != data:
|
if self.sort_col != data:
|
||||||
self.sortorder = gtk.SORT_ASCENDING
|
self.sortorder = gtk.SORT_ASCENDING
|
||||||
|
self.sort_col = data
|
||||||
|
self.build_tree()
|
||||||
else:
|
else:
|
||||||
if (self.columns[data].get_sort_order() == gtk.SORT_DESCENDING
|
if (self.columns[data].get_sort_order() == gtk.SORT_DESCENDING
|
||||||
or not self.columns[data].get_sort_indicator()):
|
or not self.columns[data].get_sort_indicator()):
|
||||||
self.sortorder = gtk.SORT_ASCENDING
|
self.sortorder = gtk.SORT_ASCENDING
|
||||||
else:
|
else:
|
||||||
self.sortorder = gtk.SORT_DESCENDING
|
self.sortorder = gtk.SORT_DESCENDING
|
||||||
|
self.model.reverse_order()
|
||||||
|
|
||||||
self.sort_col = data
|
|
||||||
handle = self.first_selected()
|
handle = self.first_selected()
|
||||||
|
|
||||||
self.build_tree()
|
|
||||||
|
|
||||||
if handle:
|
if handle:
|
||||||
path = self.model.on_get_path(handle)
|
path = self.model.on_get_path(handle)
|
||||||
self.selection.select_path(path)
|
self.selection.select_path(path)
|
||||||
@ -303,9 +306,12 @@ class BaseSelector(ManagedWindow.ManagedWindow):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def show_toggle(self, obj):
|
def show_toggle(self, obj):
|
||||||
filter = None if obj.get_active() else self.filter
|
filter_info = None if obj.get_active() else self.filter
|
||||||
|
|
||||||
self.model = PeopleModel(self.db, (PeopleModel.FAST, filter),
|
self.model = self.get_model_class()(self.db, self.sort_col,
|
||||||
skip=self.skip_list)
|
self.sortorder,
|
||||||
|
sort_map=self.column_order(),
|
||||||
|
skip=self.skip_list,
|
||||||
|
search=filter_info)
|
||||||
self.tree.set_model(self.model)
|
self.tree.set_model(self.model)
|
||||||
self.tree.grab_focus()
|
self.tree.grab_focus()
|
||||||
|
@ -107,6 +107,12 @@ class SelectPerson(BaseSelector):
|
|||||||
]
|
]
|
||||||
return column_names
|
return column_names
|
||||||
|
|
||||||
|
def exact_search(self):
|
||||||
|
"""
|
||||||
|
Returns a tuple indicating columns requiring an exact search
|
||||||
|
"""
|
||||||
|
return (2,) # Gender ('female' contains the string 'male')
|
||||||
|
|
||||||
def _on_row_activated(self, treeview, path, view_col):
|
def _on_row_activated(self, treeview, path, view_col):
|
||||||
store, paths = self.selection.get_selected_rows()
|
store, paths = self.selection.get_selected_rows()
|
||||||
if paths and len(paths[0]) == 2 :
|
if paths and len(paths[0]) == 2 :
|
||||||
|
@ -1404,9 +1404,9 @@ class GrampsDbRead(GrampsDbBase, Callback):
|
|||||||
Return the Person display common information stored in the database's
|
Return the Person display common information stored in the database's
|
||||||
metadata.
|
metadata.
|
||||||
"""
|
"""
|
||||||
default = [(1, 1, 100), (1, 2, 100), (1, 3, 150), (0, 4, 150),
|
default = [(1, 0, 250), (1, 1, 50), (1, 2, 75), (1, 3, 100),
|
||||||
(1, 5, 150), (0, 6, 150), (0, 7, 100), (0, 8, 100),
|
(1, 4, 175), (1, 5, 100), (1, 6, 175), (1, 7, 100),
|
||||||
]
|
(0, 8, 100)]
|
||||||
return self.__get_column_order(PERSON_COL_KEY, default)
|
return self.__get_column_order(PERSON_COL_KEY, default)
|
||||||
|
|
||||||
def __get_columns(self, key, default):
|
def __get_columns(self, key, default):
|
||||||
|
@ -34,7 +34,7 @@ import cPickle as pickle
|
|||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
_LOG = logging.getLogger('.listview')
|
_LOG = logging.getLogger('.gui.listview')
|
||||||
|
|
||||||
#----------------------------------------------------------------
|
#----------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -215,14 +215,15 @@ class ListView(NavigationView):
|
|||||||
|
|
||||||
def build_tree(self):
|
def build_tree(self):
|
||||||
if self.active:
|
if self.active:
|
||||||
cput = time.clock()
|
cput0 = time.clock()
|
||||||
if config.get('interface.filter'):
|
if config.get('interface.filter'):
|
||||||
filter_info = (True, self.generic_filter)
|
filter_info = (True, self.generic_filter, False)
|
||||||
else:
|
else:
|
||||||
if self.search_bar.get_value()[0] in self.exact_search():
|
value = self.search_bar.get_value()
|
||||||
filter_info = (False, self.search_bar.get_value(), True)
|
if value[0] in self.exact_search():
|
||||||
|
filter_info = (False, value, True)
|
||||||
else:
|
else:
|
||||||
filter_info = (False, self.search_bar.get_value(), False)
|
filter_info = (False, value, False)
|
||||||
|
|
||||||
if self.dirty or not self.model:
|
if self.dirty or not self.model:
|
||||||
self.model = self.make_model(self.dbstate.db, self.sort_col,
|
self.model = self.make_model(self.dbstate.db, self.sort_col,
|
||||||
@ -235,8 +236,11 @@ class ListView(NavigationView):
|
|||||||
self.model.set_search(filter_info)
|
self.model.set_search(filter_info)
|
||||||
self.model.rebuild_data()
|
self.model.rebuild_data()
|
||||||
|
|
||||||
|
cput1 = time.clock()
|
||||||
self.build_columns()
|
self.build_columns()
|
||||||
|
cput2 = time.clock()
|
||||||
self.list.set_model(self.model)
|
self.list.set_model(self.model)
|
||||||
|
cput3 = time.clock()
|
||||||
self.__display_column_sort()
|
self.__display_column_sort()
|
||||||
self.goto_active(None)
|
self.goto_active(None)
|
||||||
|
|
||||||
@ -244,11 +248,17 @@ class ListView(NavigationView):
|
|||||||
self.tooltips = TreeTips.TreeTips(
|
self.tooltips = TreeTips.TreeTips(
|
||||||
self.list, self.model.tooltip_column, True)
|
self.list, self.model.tooltip_column, True)
|
||||||
self.dirty = False
|
self.dirty = False
|
||||||
|
cput4 = time.clock()
|
||||||
self.uistate.show_filter_results(self.dbstate,
|
self.uistate.show_filter_results(self.dbstate,
|
||||||
self.model.displayed(),
|
self.model.displayed(),
|
||||||
self.model.total())
|
self.model.total())
|
||||||
_LOG.debug(self.__class__.__name__ + ' build_tree ' +
|
_LOG.debug(self.__class__.__name__ + ' build_tree ' +
|
||||||
str(time.clock() - cput) + ' sec')
|
str(time.clock() - cput0) + ' sec')
|
||||||
|
_LOG.debug('parts ' + str(cput1-cput0) + ' , '
|
||||||
|
+ str(cput2-cput1) + ' , '
|
||||||
|
+ str(cput3-cput2) + ' , '
|
||||||
|
+ str(cput4-cput3) + ' , '
|
||||||
|
+ str(time.clock() - cput4))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.dirty = True
|
self.dirty = True
|
||||||
@ -534,12 +544,13 @@ class ListView(NavigationView):
|
|||||||
handle = self.first_selected()
|
handle = self.first_selected()
|
||||||
|
|
||||||
if config.get('interface.filter'):
|
if config.get('interface.filter'):
|
||||||
filter_info = (True, self.generic_filter)
|
filter_info = (True, self.generic_filter, False)
|
||||||
else:
|
else:
|
||||||
if self.search_bar.get_value()[0] in self.exact_search():
|
value = self.search_bar.get_value()
|
||||||
filter_info = (False, self.search_bar.get_value(), True)
|
if value[0] in self.exact_search():
|
||||||
|
filter_info = (False, value, True)
|
||||||
else:
|
else:
|
||||||
filter_info = (False, self.search_bar.get_value(), False)
|
filter_info = (False, value, False)
|
||||||
|
|
||||||
if same_col:
|
if same_col:
|
||||||
self.model.reverse_order()
|
self.model.reverse_order()
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
# Copyright (C) 2000-2007 Donald N. Allingham
|
# Copyright (C) 2000-2007 Donald N. Allingham
|
||||||
# Copyright (C) 2009 Gary Burton
|
# Copyright (C) 2009 Gary Burton
|
||||||
# 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
|
||||||
@ -49,20 +50,21 @@ import gtk
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
import config
|
import config
|
||||||
|
from Utils import conv_unicode_tosrtkey_ongtk
|
||||||
from gen.utils.longop import LongOpStatus
|
from gen.utils.longop import LongOpStatus
|
||||||
from Filters import SearchFilter
|
|
||||||
# from Filters import ExactSearchFilter
|
|
||||||
from Lru import LRU
|
from Lru import LRU
|
||||||
|
from bisect import bisect_right
|
||||||
|
from Filters import SearchFilter, ExactSearchFilter
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# TreeNodeMap
|
# TreeBaseModel
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
class TreeNodeMap(object):
|
class TreeBaseModel(gtk.GenericTreeModel):
|
||||||
"""
|
"""
|
||||||
A NodeMap for a hierarchical treeview. The map defines the mapping
|
The base class for all hierarchical treeview models. The model defines the
|
||||||
between a unique node and a path. Paths are defined by a tuple.
|
mapping between a unique node and a path. Paths are defined by a tuple.
|
||||||
The first element is an integer specifying the position in the top
|
The first element is an integer specifying the position in the top
|
||||||
level of the hierarchy. The next element relates to the next level
|
level of the hierarchy. The next element relates to the next level
|
||||||
in the hierarchy. The number of elements depends on the depth of the
|
in the hierarchy. The number of elements depends on the depth of the
|
||||||
@ -75,275 +77,56 @@ class TreeNodeMap(object):
|
|||||||
is set to None if no gramps object is associated with the
|
is set to None if no gramps object is associated with the
|
||||||
node.
|
node.
|
||||||
children A dictionary of parent nodes. Each entry is a list of
|
children A dictionary of parent nodes. Each entry is a list of
|
||||||
(child, sortkey) tuples. The list is sorted during the
|
(sortkey, child) tuples. The list is sorted during the
|
||||||
build. The top node of the hierarchy is None.
|
build. The top node of the hierarchy is None.
|
||||||
path2node A dictionary of paths. Each entry is a node.
|
|
||||||
node2path A dictionary of nodes. Each entry is a path.
|
|
||||||
handle2node A dictionary of gramps handles. Each entry is a node.
|
handle2node A dictionary of gramps handles. Each entry is a node.
|
||||||
|
|
||||||
Nodes are added using the add_node method.
|
The model obtains data from database as needed and holds a cache of most
|
||||||
The path2node and node2path mapping is built using build_toplevel.
|
recently used data.
|
||||||
A simple recursive algorithm is used.
|
As iter for generictreemodel, node is used. This will be the handle for
|
||||||
Branches of the tree can be re-built using build_sub_entry.
|
database objects.
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
"""
|
|
||||||
Initialise data structures
|
|
||||||
"""
|
|
||||||
self.tree = {}
|
|
||||||
self.children = {}
|
|
||||||
self.path2node = {}
|
|
||||||
self.node2path = {}
|
|
||||||
|
|
||||||
self.handle2node = {}
|
Creation:
|
||||||
|
db : the database
|
||||||
self.__reverse = False
|
tooltip_column : column number of tooltip
|
||||||
|
marker_column : column number of marker
|
||||||
def clear(self):
|
search : the search that must be shown
|
||||||
"""
|
skip : values not to show
|
||||||
Clear the entire map.
|
scol : column on which to sort
|
||||||
"""
|
order : order of the sort
|
||||||
self.tree = {}
|
sort_map : mapping from columns seen on the GUI and the columns
|
||||||
self.children = {}
|
as defined here
|
||||||
self.path2node = {}
|
nrgroups : maximum number of grouping level, 0 = no group,
|
||||||
self.node2path = {}
|
1= one group, .... Some optimizations can be for only
|
||||||
|
one group. nrgroups=0 should never be used, as then a
|
||||||
self.handle2node = {}
|
flatbasemodel should be used
|
||||||
|
group_can_have_handle :
|
||||||
def clear_sub_entry(self, node):
|
can groups have a handle. If False, this means groups
|
||||||
"""
|
are only used to group subnodes, not for holding data and
|
||||||
Clear a single branch of the map.
|
showing subnodes
|
||||||
"""
|
|
||||||
if node is None:
|
|
||||||
self.path2node = {}
|
|
||||||
self.node2path = {}
|
|
||||||
else:
|
|
||||||
if node in self.children:
|
|
||||||
for child in self.children[node]:
|
|
||||||
self.clear_node(child[0])
|
|
||||||
|
|
||||||
def clear_node(self, node):
|
|
||||||
if node in self.node2path:
|
|
||||||
path = self.node2path[node]
|
|
||||||
del self.path2node[path]
|
|
||||||
del self.node2path[node]
|
|
||||||
if node in self.children:
|
|
||||||
for child in self.children[node]:
|
|
||||||
self.clear_node(child[0])
|
|
||||||
|
|
||||||
def add_node(self, parent, child, sortkey, handle):
|
|
||||||
"""
|
|
||||||
Add a node to the map.
|
|
||||||
|
|
||||||
parent The parent node for the child. None for top level.
|
|
||||||
child A unique ID for the node.
|
|
||||||
sortkey A key by which to sort child nodes of each parent.
|
|
||||||
handle The gramps handle of the object corresponding to the
|
|
||||||
node. None if the node does not have a handle.
|
|
||||||
"""
|
|
||||||
if child in self.tree:
|
|
||||||
if handle:
|
|
||||||
self.tree[child][1] = handle
|
|
||||||
node_added = False
|
|
||||||
else:
|
|
||||||
|
|
||||||
self.tree[child] = [parent, handle]
|
|
||||||
if parent in self.children:
|
|
||||||
self.children[parent] += [(child, sortkey)]
|
|
||||||
else:
|
|
||||||
self.children[parent] = [(child, sortkey)]
|
|
||||||
node_added = True
|
|
||||||
|
|
||||||
if handle:
|
|
||||||
self.handle2node[handle] = child
|
|
||||||
|
|
||||||
return node_added
|
|
||||||
|
|
||||||
def remove_node(self, node):
|
|
||||||
if node in self.children:
|
|
||||||
self.tree[node][1] = None
|
|
||||||
node_removed = False
|
|
||||||
else:
|
|
||||||
parent = self.tree[node][0]
|
|
||||||
del self.tree[node]
|
|
||||||
new_list = []
|
|
||||||
for child in self.children[parent]:
|
|
||||||
if child[0] != node:
|
|
||||||
new_list.append(child)
|
|
||||||
if len(new_list) == 0:
|
|
||||||
del self.children[parent]
|
|
||||||
else:
|
|
||||||
self.children[parent] = new_list
|
|
||||||
node_removed = True
|
|
||||||
|
|
||||||
return node_removed
|
|
||||||
|
|
||||||
def build_sub_entry(self, node, path, sort):
|
|
||||||
"""
|
|
||||||
Build the path2node and node2path maps for the children of a
|
|
||||||
given node and recursively builds the next level down.
|
|
||||||
|
|
||||||
node The parent node.
|
|
||||||
path The path of the parent node.
|
|
||||||
"""
|
|
||||||
if sort:
|
|
||||||
self.children[node].sort(key=lambda x: locale.strxfrm(x[1]))
|
|
||||||
for i, child in enumerate(self.children[node]):
|
|
||||||
if self.__reverse:
|
|
||||||
new_path = path + [len(self.children[node]) - i - 1]
|
|
||||||
else:
|
|
||||||
new_path = path + [i]
|
|
||||||
self.path2node[tuple(new_path)] = child[0]
|
|
||||||
self.node2path[child[0]] = tuple(new_path)
|
|
||||||
if child[0] in self.children:
|
|
||||||
self.build_sub_entry(child[0], new_path, sort)
|
|
||||||
|
|
||||||
def build_toplevel(self, sort=True):
|
|
||||||
"""
|
|
||||||
Build the complete map from the top level.
|
|
||||||
"""
|
|
||||||
if len(self.tree) == 0:
|
|
||||||
return
|
|
||||||
self.build_sub_entry(None, [], sort)
|
|
||||||
|
|
||||||
def reverse_order(self):
|
|
||||||
self.__reverse = not self.__reverse
|
|
||||||
self.path2node = {}
|
|
||||||
self.node2path = {}
|
|
||||||
self.build_toplevel(sort=False)
|
|
||||||
|
|
||||||
def get_handle(self, node):
|
|
||||||
"""
|
|
||||||
Get the gramps handle for a node. Return None if the node does
|
|
||||||
not correspond to a gramps object.
|
|
||||||
"""
|
|
||||||
if node in self.tree:
|
|
||||||
return self.tree[node][1]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_node(self, handle):
|
|
||||||
"""
|
|
||||||
Get the node for a handle.
|
|
||||||
"""
|
|
||||||
if handle in self.handle2node:
|
|
||||||
return self.handle2node[handle]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# The following methods support the public interface of the
|
|
||||||
# GenericTreeModel.
|
|
||||||
|
|
||||||
def get_path(self, node):
|
|
||||||
"""
|
|
||||||
Get the path for a node.
|
|
||||||
"""
|
|
||||||
# For trees without the active person a key error is thrown
|
|
||||||
return self.node2path[node]
|
|
||||||
|
|
||||||
def get_iter(self, path):
|
|
||||||
"""
|
|
||||||
Build the complete map from the top level.
|
|
||||||
"""
|
|
||||||
if path in self.path2node:
|
|
||||||
return self.path2node[path]
|
|
||||||
else:
|
|
||||||
# Empty tree
|
|
||||||
return None
|
|
||||||
|
|
||||||
def find_next_node(self, node):
|
|
||||||
"""
|
|
||||||
Get the next node with the same parent as the given node.
|
|
||||||
"""
|
|
||||||
path_list = list(self.node2path[node])
|
|
||||||
path_list[len(path_list)-1] += 1
|
|
||||||
path = tuple(path_list)
|
|
||||||
if path in self.path2node:
|
|
||||||
return self.path2node[path]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def first_child(self, node):
|
|
||||||
"""
|
|
||||||
Get the first child of the given node.
|
|
||||||
"""
|
|
||||||
if node in self.children:
|
|
||||||
if self.__reverse:
|
|
||||||
size = len(self.children[node])
|
|
||||||
return self.children[node][size - 1][0]
|
|
||||||
else:
|
|
||||||
return self.children[node][0][0]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def has_child(self, node):
|
|
||||||
"""
|
|
||||||
Find if the given node has any children.
|
|
||||||
"""
|
|
||||||
if node in self.children:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def number_of_children(self, node):
|
|
||||||
"""
|
|
||||||
Get the number of children of the given node.
|
|
||||||
"""
|
|
||||||
if node in self.children:
|
|
||||||
return len(self.children[node])
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_nth_child(self, node, index):
|
|
||||||
"""
|
|
||||||
Get the nth child of the given node.
|
|
||||||
"""
|
|
||||||
if node in self.children:
|
|
||||||
if len(self.children[node]) > index:
|
|
||||||
if self.__reverse:
|
|
||||||
size = len(self.children[node])
|
|
||||||
return self.children[node][size - index - 1][0]
|
|
||||||
else:
|
|
||||||
return self.children[node][index][0]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_parent_of(self, node):
|
|
||||||
"""
|
|
||||||
Get the parent of the given node.
|
|
||||||
"""
|
|
||||||
if node in self.tree:
|
|
||||||
return self.tree[node][0]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# TreeBaseModel
|
|
||||||
#
|
|
||||||
#-------------------------------------------------------------------------
|
|
||||||
class TreeBaseModel(gtk.GenericTreeModel):
|
|
||||||
"""
|
|
||||||
The base class for all hierarchical treeview models.
|
|
||||||
It keeps a TreeNodeMap, and obtains data from database as needed.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# LRU cache size
|
# LRU cache size
|
||||||
_CACHE_SIZE = 250
|
_CACHE_SIZE = 250
|
||||||
|
|
||||||
# Search/Filter modes
|
|
||||||
GENERIC = 0
|
|
||||||
SEARCH = 1
|
|
||||||
FAST = 2
|
|
||||||
|
|
||||||
def __init__(self, db, tooltip_column, marker_column=None,
|
def __init__(self, db, tooltip_column, marker_column=None,
|
||||||
search=None, skip=set(),
|
search=None, skip=set(),
|
||||||
scol=0, order=gtk.SORT_ASCENDING, sort_map=None):
|
scol=0, order=gtk.SORT_ASCENDING, sort_map=None,
|
||||||
|
nrgroups = 1,
|
||||||
|
group_can_have_handle = False):
|
||||||
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.nrgroups = nrgroups
|
||||||
|
self.group_can_have_handle = group_can_have_handle
|
||||||
|
|
||||||
|
self.set_property("leak_references", False)
|
||||||
self.db = db
|
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:
|
||||||
@ -353,11 +136,12 @@ class TreeBaseModel(gtk.GenericTreeModel):
|
|||||||
#we need the model col, that corresponds with scol
|
#we need the model col, that corresponds with scol
|
||||||
col = self.sort_map[scol][1]
|
col = self.sort_map[scol][1]
|
||||||
self.sort_func = self.smap[col]
|
self.sort_func = self.smap[col]
|
||||||
|
self.sort_col = col
|
||||||
else:
|
else:
|
||||||
self.sort_func = self.smap[scol]
|
self.sort_func = self.smap[scol]
|
||||||
self.sort_col = scol
|
self.sort_col = scol
|
||||||
|
|
||||||
self.in_build = False
|
self._in_build = False
|
||||||
|
|
||||||
self.lru_data = LRU(TreeBaseModel._CACHE_SIZE)
|
self.lru_data = LRU(TreeBaseModel._CACHE_SIZE)
|
||||||
|
|
||||||
@ -372,15 +156,13 @@ class TreeBaseModel(gtk.GenericTreeModel):
|
|||||||
self.todo_color = config.get('preferences.todo-color')
|
self.todo_color = config.get('preferences.todo-color')
|
||||||
self.custom_color = config.get('preferences.custom-marker-color')
|
self.custom_color = config.get('preferences.custom-marker-color')
|
||||||
|
|
||||||
self.mapper = TreeNodeMap()
|
self._tooltip_column = tooltip_column
|
||||||
self.set_search(search)
|
self._marker_column = marker_column
|
||||||
|
|
||||||
self.tooltip_column = tooltip_column
|
|
||||||
self.marker_color_column = marker_column
|
|
||||||
|
|
||||||
self.__total = 0
|
self.__total = 0
|
||||||
self.__displayed = 0
|
self.__displayed = 0
|
||||||
|
|
||||||
|
self.set_search(search)
|
||||||
self.rebuild_data(self.current_filter, skip)
|
self.rebuild_data(self.current_filter, skip)
|
||||||
|
|
||||||
_LOG.debug(self.__class__.__name__ + ' __init__ ' +
|
_LOG.debug(self.__class__.__name__ + ' __init__ ' +
|
||||||
@ -388,20 +170,63 @@ class TreeBaseModel(gtk.GenericTreeModel):
|
|||||||
|
|
||||||
|
|
||||||
def __update_todo(self, *args):
|
def __update_todo(self, *args):
|
||||||
|
"""
|
||||||
|
Update the todo color when the preferences change.
|
||||||
|
"""
|
||||||
self.todo_color = config.get('preferences.todo-color')
|
self.todo_color = config.get('preferences.todo-color')
|
||||||
|
|
||||||
def __update_custom(self, *args):
|
def __update_custom(self, *args):
|
||||||
|
"""
|
||||||
|
Update the custom color when the preferences change.
|
||||||
|
"""
|
||||||
self.custom_color = config.get('preferences.custom-marker-color')
|
self.custom_color = config.get('preferences.custom-marker-color')
|
||||||
|
|
||||||
def __update_complete(self, *args):
|
def __update_complete(self, *args):
|
||||||
|
"""
|
||||||
|
Update the complete color when the preferences change.
|
||||||
|
"""
|
||||||
self.complete_color = config.get('preferences.complete-color')
|
self.complete_color = config.get('preferences.complete-color')
|
||||||
|
|
||||||
def displayed(self):
|
def displayed(self):
|
||||||
|
"""
|
||||||
|
Return the number of rows displayed.
|
||||||
|
"""
|
||||||
return self.__displayed
|
return self.__displayed
|
||||||
|
|
||||||
def total(self):
|
def total(self):
|
||||||
|
"""
|
||||||
|
Return the total number of rows without a filter or search condition.
|
||||||
|
"""
|
||||||
return self.__total
|
return self.__total
|
||||||
|
|
||||||
|
def tooltip_column(self):
|
||||||
|
"""
|
||||||
|
Return the tooltip column.
|
||||||
|
"""
|
||||||
|
return self._tooltip_column
|
||||||
|
|
||||||
|
def marker_column(self):
|
||||||
|
"""
|
||||||
|
Return the marker color column.
|
||||||
|
"""
|
||||||
|
return self._marker_column
|
||||||
|
|
||||||
|
def clear_cache(self):
|
||||||
|
"""
|
||||||
|
Clear the LRU cache.
|
||||||
|
"""
|
||||||
|
self.lru_data.clear()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""
|
||||||
|
Clear the data map.
|
||||||
|
"""
|
||||||
|
self.tree = {}
|
||||||
|
self.children = {}
|
||||||
|
self.children[None] = []
|
||||||
|
self.handle2node = {}
|
||||||
|
self.__reverse = False
|
||||||
|
|
||||||
def set_search(self, search):
|
def set_search(self, search):
|
||||||
"""
|
"""
|
||||||
Change the search function that filters the data in the model.
|
Change the search function that filters the data in the model.
|
||||||
@ -412,24 +237,28 @@ class TreeBaseModel(gtk.GenericTreeModel):
|
|||||||
with the new entries
|
with the new entries
|
||||||
"""
|
"""
|
||||||
if search:
|
if search:
|
||||||
if search[0]:
|
if search[0] == 1: # Filter
|
||||||
#following is None if no data given in filter sidebar
|
#following is None if no data given in filter sidebar
|
||||||
self.search = search[1]
|
self.search = search[1]
|
||||||
self._build_data = self._build_filter_sub
|
self._build_data = self._rebuild_filter
|
||||||
else:
|
elif search[0] == 0: # Search
|
||||||
if search[1]:
|
if search[1]:
|
||||||
# we have search[1] = (index, text_unicode, inversion)
|
# we have search[1] = (index, text_unicode, inversion)
|
||||||
col = search[1][0]
|
col, text, inv = search[1]
|
||||||
text = search[1][1]
|
|
||||||
inv = search[1][2]
|
|
||||||
func = lambda x: self._get_value(x, col) or u""
|
func = lambda x: self._get_value(x, col) or u""
|
||||||
|
if search[2]:
|
||||||
|
self.search = ExactSearchFilter(func, text, inv)
|
||||||
|
else:
|
||||||
self.search = SearchFilter(func, text, inv)
|
self.search = SearchFilter(func, text, inv)
|
||||||
else:
|
else:
|
||||||
self.search = None
|
self.search = None
|
||||||
self._build_data = self._build_search_sub
|
self._build_data = self._rebuild_search
|
||||||
|
else: # Fast filter
|
||||||
|
self.search = search[1]
|
||||||
|
self._build_data = self._rebuild_search
|
||||||
else:
|
else:
|
||||||
self.search = None
|
self.search = None
|
||||||
self._build_data = self._build_search_sub
|
self._build_data = self._rebuild_search
|
||||||
|
|
||||||
self.current_filter = self.search
|
self.current_filter = self.search
|
||||||
|
|
||||||
@ -437,23 +266,28 @@ class TreeBaseModel(gtk.GenericTreeModel):
|
|||||||
"""
|
"""
|
||||||
Rebuild the data map.
|
Rebuild the data map.
|
||||||
"""
|
"""
|
||||||
|
cput = time.clock()
|
||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
self.in_build = True
|
self._in_build = True
|
||||||
|
|
||||||
self.mapper.clear()
|
|
||||||
|
|
||||||
if not self.db.is_open():
|
if not self.db.is_open():
|
||||||
return
|
return
|
||||||
|
|
||||||
#self._build_data(data_filter, skip)
|
self.clear()
|
||||||
self._build_data(self.current_filter, skip)
|
self._build_data(self.current_filter, skip)
|
||||||
self.mapper.build_toplevel()
|
self.sort_data()
|
||||||
|
|
||||||
self.in_build = False
|
self._in_build = False
|
||||||
|
|
||||||
self.current_filter = data_filter
|
self.current_filter = data_filter
|
||||||
|
|
||||||
def _build_search_sub(self, dfilter, skip):
|
_LOG.debug(self.__class__.__name__ + ' rebuild_data ' +
|
||||||
|
str(time.clock() - cput) + ' sec')
|
||||||
|
|
||||||
|
def _rebuild_search(self, dfilter, skip):
|
||||||
|
"""
|
||||||
|
Rebuild the data map where a search condition is applied.
|
||||||
|
"""
|
||||||
self.__total = 0
|
self.__total = 0
|
||||||
self.__displayed = 0
|
self.__displayed = 0
|
||||||
with self.gen_cursor() as cursor:
|
with self.gen_cursor() as cursor:
|
||||||
@ -464,8 +298,10 @@ class TreeBaseModel(gtk.GenericTreeModel):
|
|||||||
self.__displayed += 1
|
self.__displayed += 1
|
||||||
self.add_row(handle, data)
|
self.add_row(handle, data)
|
||||||
|
|
||||||
def _build_filter_sub(self, dfilter, skip):
|
def _rebuild_filter(self, dfilter, skip):
|
||||||
|
"""
|
||||||
|
Rebuild the data map where a filter is applied.
|
||||||
|
"""
|
||||||
with self.gen_cursor() as cursor:
|
with self.gen_cursor() as cursor:
|
||||||
handle_list = [key for key, data in cursor]
|
handle_list = [key for key, data in cursor]
|
||||||
self.__total = len(handle_list)
|
self.__total = len(handle_list)
|
||||||
@ -487,54 +323,160 @@ class TreeBaseModel(gtk.GenericTreeModel):
|
|||||||
self.add_row(handle, data)
|
self.add_row(handle, data)
|
||||||
status.end()
|
status.end()
|
||||||
|
|
||||||
def reverse_order(self):
|
def add_node(self, parent, child, sortkey, handle, add_parent=True):
|
||||||
self.mapper.reverse_order()
|
"""
|
||||||
|
Add a node to the map.
|
||||||
|
|
||||||
def clear_cache(self):
|
parent The parent node for the child. None for top level. If
|
||||||
self.lru_data.clear()
|
this node does not exist, it will be added under the top
|
||||||
|
level if add_parent=True. For performance, if you have
|
||||||
|
added the parent, passing add_parent=False, will skip adding
|
||||||
|
missing parent
|
||||||
|
child A unique ID for the node.
|
||||||
|
sortkey A key by which to sort child nodes of each parent.
|
||||||
|
handle The gramps handle of the object corresponding to the
|
||||||
|
node. None if the node does not have a handle.
|
||||||
|
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)
|
||||||
|
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)]
|
||||||
|
|
||||||
def build_sub_entry(self, node):
|
if not self._in_build:
|
||||||
self.mapper.clear_sub_entry(node)
|
# emit row_inserted signal
|
||||||
if node is None:
|
path = self.on_get_path(child)
|
||||||
self.mapper.build_toplevel(sort=True)
|
node = self.get_iter(path)
|
||||||
|
self.row_inserted(path, node)
|
||||||
|
|
||||||
|
if handle:
|
||||||
|
self.handle2node[handle] = child
|
||||||
|
|
||||||
|
def _add_dup_node(self, 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
|
||||||
|
again, and this time setting the handle
|
||||||
|
Otherwise, a node should never be added twice!
|
||||||
|
"""
|
||||||
|
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()
|
||||||
|
|
||||||
|
def remove_node(self, node):
|
||||||
|
"""
|
||||||
|
Remove a node from the map.
|
||||||
|
"""
|
||||||
|
if node in self.children:
|
||||||
|
self.tree[node][1] = None
|
||||||
else:
|
else:
|
||||||
path = self.on_get_path(node)
|
path = self.on_get_path(node)
|
||||||
self.mapper.build_sub_entry(node, list(path), sort=True)
|
parent = self.tree[node][0]
|
||||||
|
del self.tree[node]
|
||||||
|
new_list = []
|
||||||
|
for child in self.children[parent]:
|
||||||
|
if child[1] != node:
|
||||||
|
new_list.append(child)
|
||||||
|
if len(new_list) == 0:
|
||||||
|
del self.children[parent]
|
||||||
|
else:
|
||||||
|
self.children[parent] = new_list
|
||||||
|
|
||||||
|
# emit row_deleted signal
|
||||||
|
self.row_deleted(path)
|
||||||
|
|
||||||
|
def reverse_order(self):
|
||||||
|
"""
|
||||||
|
Reverse the order of the map.
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
if node in self.children:
|
||||||
|
rows = range(len(self.children[node]))
|
||||||
|
rows.reverse()
|
||||||
|
if node 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])
|
||||||
|
|
||||||
def add_row(self, handle, data):
|
def add_row(self, handle, data):
|
||||||
pass
|
"""
|
||||||
|
Add a row to the model. In general this will add more then one node.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def add_row_by_handle(self, handle):
|
def add_row_by_handle(self, handle):
|
||||||
"""
|
"""
|
||||||
Add a row to the model.
|
Add a row to the model.
|
||||||
"""
|
"""
|
||||||
|
cput = time.clock()
|
||||||
data = self.map(handle)
|
data = self.map(handle)
|
||||||
top_node = self.add_row(handle, data)
|
self.add_row(handle, data)
|
||||||
parent_node = self.on_iter_parent(top_node)
|
|
||||||
|
|
||||||
self.build_sub_entry(parent_node)
|
_LOG.debug(self.__class__.__name__ + ' add_row_by_handle ' +
|
||||||
|
str(time.clock() - cput) + ' sec')
|
||||||
path = self.on_get_path(top_node)
|
|
||||||
node = self.get_iter(path)
|
|
||||||
# only one row_inserted and row_has_child_toggled is needed?
|
|
||||||
self.row_inserted(path, node)
|
|
||||||
self.row_has_child_toggled(path, node)
|
|
||||||
|
|
||||||
def delete_row_by_handle(self, handle):
|
def delete_row_by_handle(self, handle):
|
||||||
"""
|
"""
|
||||||
Delete a row from the model.
|
Delete a row from the model.
|
||||||
"""
|
"""
|
||||||
|
cput = time.clock()
|
||||||
self.clear_cache()
|
self.clear_cache()
|
||||||
|
|
||||||
node = self.get_node(handle)
|
node = self.get_node(handle)
|
||||||
parent = self.on_iter_parent(node)
|
parent = self.on_iter_parent(node)
|
||||||
while node and self.mapper.remove_node(node):
|
self.remove_node(node)
|
||||||
path = self.on_get_path(node)
|
|
||||||
node = parent
|
|
||||||
parent = self.on_iter_parent(parent)
|
|
||||||
|
|
||||||
self.build_sub_entry(node)
|
while parent is not None:
|
||||||
|
next_parent = self.on_iter_parent(parent)
|
||||||
|
if parent not in self.children:
|
||||||
|
self.remove_node(parent)
|
||||||
|
parent = next_parent
|
||||||
|
|
||||||
self.row_deleted(path)
|
_LOG.debug(self.__class__.__name__ + ' delete_row_by_handle ' +
|
||||||
|
str(time.clock() - cput) + ' sec')
|
||||||
|
|
||||||
def update_row_by_handle(self, handle):
|
def update_row_by_handle(self, handle):
|
||||||
"""
|
"""
|
||||||
@ -543,31 +485,26 @@ class TreeBaseModel(gtk.GenericTreeModel):
|
|||||||
self.delete_row_by_handle(handle)
|
self.delete_row_by_handle(handle)
|
||||||
self.add_row_by_handle(handle)
|
self.add_row_by_handle(handle)
|
||||||
|
|
||||||
# If the node hasn't moved all we need is to call row_changed.
|
# If the node hasn't moved, all we need is to call row_changed.
|
||||||
#self.row_changed(path, node)
|
#self.row_changed(path, node)
|
||||||
|
|
||||||
def get_node(self, handle):
|
|
||||||
return self.mapper.get_node(handle)
|
|
||||||
|
|
||||||
def get_handle(self, node):
|
def get_handle(self, node):
|
||||||
return self.mapper.get_handle(node)
|
|
||||||
|
|
||||||
def _get_value(self, handle, col):
|
|
||||||
"""
|
"""
|
||||||
Returns the contents of a given column of a gramps object
|
Get the gramps handle for a node. Return None if the node does
|
||||||
|
not correspond to a gramps object.
|
||||||
"""
|
"""
|
||||||
try:
|
ret = self.tree.get(node)
|
||||||
if handle in self.lru_data:
|
if ret:
|
||||||
data = self.lru_data[handle]
|
return ret[1]
|
||||||
else:
|
return ret
|
||||||
data = self.map(handle)
|
|
||||||
if not self.in_build:
|
|
||||||
self.lru_data[handle] = data
|
|
||||||
return (self.fmap[col](data, handle))
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# The following define the public interface of gtk.GenericTreeModel
|
def get_node(self, handle):
|
||||||
|
"""
|
||||||
|
Get the node for a handle.
|
||||||
|
"""
|
||||||
|
return self.handle2node.get(handle)
|
||||||
|
|
||||||
|
# The following implement the public interface of gtk.GenericTreeModel
|
||||||
|
|
||||||
def on_get_flags(self):
|
def on_get_flags(self):
|
||||||
"""
|
"""
|
||||||
@ -582,34 +519,22 @@ class TreeBaseModel(gtk.GenericTreeModel):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def on_get_path(self, node):
|
|
||||||
"""
|
|
||||||
See gtk.GenericTreeModel
|
|
||||||
"""
|
|
||||||
return self.mapper.get_path(node)
|
|
||||||
|
|
||||||
def on_get_column_type(self, index):
|
def on_get_column_type(self, index):
|
||||||
"""
|
"""
|
||||||
See gtk.GenericTreeModel
|
See gtk.GenericTreeModel
|
||||||
"""
|
"""
|
||||||
if index == self.tooltip_column:
|
if index == self._tooltip_column:
|
||||||
return object
|
return object
|
||||||
return str
|
return str
|
||||||
|
|
||||||
def on_get_iter(self, path):
|
|
||||||
"""
|
|
||||||
See gtk.GenericTreeModel
|
|
||||||
"""
|
|
||||||
return self.mapper.get_iter(path)
|
|
||||||
|
|
||||||
def on_get_value(self, node, col):
|
def on_get_value(self, node, col):
|
||||||
"""
|
"""
|
||||||
See gtk.GenericTreeModel
|
See gtk.GenericTreeModel
|
||||||
"""
|
"""
|
||||||
handle = self.mapper.get_handle(node)
|
handle = self.get_handle(node)
|
||||||
if handle is None:
|
if handle is None:
|
||||||
# Header rows dont get the foreground color set
|
# Header rows dont get the foreground color set
|
||||||
if col == self.marker_color_column:
|
if col == self._marker_column:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Look for header fuction for column and call it
|
# Look for header fuction for column and call it
|
||||||
@ -624,38 +549,130 @@ class TreeBaseModel(gtk.GenericTreeModel):
|
|||||||
# according to column_defs table
|
# according to column_defs table
|
||||||
return self._get_value(handle, col)
|
return self._get_value(handle, col)
|
||||||
|
|
||||||
|
def _get_value(self, handle, col):
|
||||||
|
"""
|
||||||
|
Returns the contents of a given column of a gramps object
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if handle in self.lru_data:
|
||||||
|
data = self.lru_data[handle]
|
||||||
|
else:
|
||||||
|
data = self.map(handle)
|
||||||
|
if not self._in_build:
|
||||||
|
self.lru_data[handle] = data
|
||||||
|
return (self.fmap[col](data, handle))
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def on_get_iter(self, path):
|
||||||
|
"""
|
||||||
|
Returns a node from a given path.
|
||||||
|
"""
|
||||||
|
if not self.tree:
|
||||||
|
return None
|
||||||
|
node = None
|
||||||
|
pathlist = list(path)
|
||||||
|
for index in pathlist:
|
||||||
|
if self.__reverse:
|
||||||
|
size = len(self.children[node])
|
||||||
|
node = self.children[node][size - index - 1][1]
|
||||||
|
else:
|
||||||
|
node = self.children[node][index][1]
|
||||||
|
return node
|
||||||
|
|
||||||
|
def on_get_path(self, node):
|
||||||
|
"""
|
||||||
|
Returns a path from a given node.
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
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, node):
|
||||||
"""
|
"""
|
||||||
See gtk.GenericTreeModel
|
Get the next node with the same parent as the given node.
|
||||||
"""
|
"""
|
||||||
return self.mapper.find_next_node(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
|
||||||
|
|
||||||
|
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, node):
|
||||||
"""
|
"""
|
||||||
See gtk.GenericTreeModel
|
Get the first child of the given node.
|
||||||
"""
|
"""
|
||||||
return self.mapper.first_child(node)
|
if node in self.children:
|
||||||
|
if self.__reverse:
|
||||||
|
size = len(self.children[node])
|
||||||
|
return self.children[node][size - 1][1]
|
||||||
|
else:
|
||||||
|
return self.children[node][0][1]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def on_iter_has_child(self, node):
|
def on_iter_has_child(self, node):
|
||||||
"""
|
"""
|
||||||
See gtk.GenericTreeModel
|
Find if the given node has any children.
|
||||||
"""
|
"""
|
||||||
return self.mapper.has_child(node)
|
if node in self.children:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def on_iter_n_children(self, node):
|
def on_iter_n_children(self, node):
|
||||||
"""
|
"""
|
||||||
See gtk.GenericTreeModel
|
Get the number of children of the given node.
|
||||||
"""
|
"""
|
||||||
return self.mapper.number_of_children(node)
|
if node in self.children:
|
||||||
|
return len(self.children[node])
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
def on_iter_nth_child(self, node, index):
|
def on_iter_nth_child(self, node, index):
|
||||||
"""
|
"""
|
||||||
See gtk.GenericTreeModel
|
Get the nth child of the given node.
|
||||||
"""
|
"""
|
||||||
return self.mapper.get_nth_child(node, index)
|
if node in self.children:
|
||||||
|
if len(self.children[node]) > index:
|
||||||
|
if self.__reverse:
|
||||||
|
size = len(self.children[node])
|
||||||
|
return self.children[node][size - index - 1][1]
|
||||||
|
else:
|
||||||
|
return self.children[node][index][1]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def on_iter_parent(self, node):
|
def on_iter_parent(self, node):
|
||||||
"""
|
"""
|
||||||
See gtk.GenericTreeModel
|
Get the parent of the given node.
|
||||||
"""
|
"""
|
||||||
return self.mapper.get_parent_of(node)
|
if node in self.tree:
|
||||||
|
return self.tree[node][0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user