# # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham # # 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 # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # $Id$ #------------------------------------------------------------------------- # # python modules # #------------------------------------------------------------------------- import locale #------------------------------------------------------------------------- # # GNOME/GTK modules # #------------------------------------------------------------------------- import gtk #------------------------------------------------------------------------- # # GRAMPS modules # #------------------------------------------------------------------------- from Filters import SearchFilter #------------------------------------------------------------------------- # # NodeMap # #------------------------------------------------------------------------- class NodeMap: """ Provides the Path to Iter mappings for a TreeView model. The implementation provides a list of nodes and a dictionary of handles. The datalist provides the path (index) to iter (handle) mapping, while the the indexmap provides the handle to index mappings """ def __init__(self): """ Create a new instance, clearing the datalist and indexmap """ self.data_list = [] self.index_map = {} def set_path_map(self, dlist): """ Takes a list of handles and builds the index map from it. """ self.data_list = dlist i = 0 self.index_map = {} for key in self.data_list: self.index_map[key] = i i +=1 def clear_map(self): """ Clears out the data_list and the index_map """ self.data_list = [] self.index_map = {} def get_path(self, handle): """ Returns the path from the passed handle. This is accomplished by indexing into the index_map to get the index (path) """ return self.index_map.get(handle) def get_handle(self, path): """ Returns the handle from the path. The path is assumed to be an integer. This is accomplished by indexing into the data_list """ return self.data_list[path] def delete_by_index(self, index): """ Deletes the item at the specified path, then rebuilds the index_map, subtracting one from each item greater than the deleted index. """ handle = self.data_list[index] del self.data_list[index] del self.index_map[handle] for key in self.index_map: if self.index_map[key] > index: self.index_map[key] -= 1 def find_next_handle(self, handle): """ Finds the next handle based off the passed handle. This is accomplished by finding the index of associated with the handle, adding one to find the next index, then finding the handle associated with the next index. """ try: return self.data_list[self.index_map.get(handle)+1] except IndexError: return None def __len__(self): """ Returns the number of entries in the map. """ return len(self.data_list) def get_first_handle(self): """ Returns the first handle in the map. """ return self.data_list[0] #------------------------------------------------------------------------- # # BaseModel # #------------------------------------------------------------------------- class BaseModel(gtk.GenericTreeModel): def __init__(self, db, scol=0, order=gtk.SORT_ASCENDING, tooltip_column=None, search=None, skip=set(), sort_map=None): gtk.GenericTreeModel.__init__(self) self.prev_handle = None self.prev_data = None self.set_property("leak_references",False) self.db = db if sort_map: self.sort_map = [ f for f in sort_map if f[0]] col = self.sort_map[scol][1] self.sort_func = self.smap[col] else: self.sort_func = self.smap[scol] self.sort_col = scol self.skip = skip self.node_map = NodeMap() if search: if search[0]: self.search = search[1] self.rebuild_data = self._rebuild_filter else: if search[1]: col = search[1][0] text = search[1][1] inv = search[1][2] func = lambda x: self.on_get_value(x, col) or u"" self.search = SearchFilter(func, text, inv) else: self.search = None self.rebuild_data = self._rebuild_search else: self.search = None self.rebuild_data = self._rebuild_search self.reverse = (order == gtk.SORT_DESCENDING) self.tooltip_column = tooltip_column self.rebuild_data() def set_sort_column(self,col): self.sort_func = self.smap[col] def sort_keys(self): cursor = self.gen_cursor() self.sort_data = [] data = cursor.next() while data: key = locale.strxfrm(self.sort_func(data[1])) self.sort_data.append((key,data[0])) data = cursor.next() cursor.close() self.sort_data.sort(reverse=self.reverse) return [ x[1] for x in self.sort_data ] def _rebuild_search(self,ignore=None): if self.db.is_open(): if self.search and self.search.text: dlist = [h for h in self.sort_keys()\ if self.search.match(h) and \ h not in self.skip and h != ignore] else: dlist = [h for h in self.sort_keys() \ if h not in self.skip and h != ignore] self.node_map.set_path_map(dlist) else: self.node_map.clear_map() def _rebuild_filter(self, ignore=None): if self.db.is_open(): if self.search: dlist = self.search.apply(self.db, [ k for k in self.sort_keys()\ if k != ignore]) else: dlist = [ k for k in self.sort_keys() \ if k != ignore ] self.node_map.set_path_map(dlist) else: self.node_map.clear_map() def add_row_by_handle(self,handle): if self.search and self.search.match(handle): data = self.map(handle) key = locale.strxfrm(self.sort_func(data)) self.sort_data.append((key,handle)) self.sort_data.sort(reverse=self.reverse) self.node_map.set_path_map([ x[1] for x in self.sort_data ]) index = self.node_map.get_path(handle) if index != None: node = self.get_iter(index) self.row_inserted(index, node) def delete_row_by_handle(self,handle): index = self.node_map.get_path(handle) # remove from sort array i = 0 for (key, node) in self.sort_data: if handle == node: del self.sort_data[i] break i += 1 self.node_map.delete_by_index(index) self.row_deleted(index) def update_row_by_handle(self,handle): index = self.node_map.get_path(handle) node = self.get_iter(index) self.row_changed(index,node) def on_get_flags(self): '''returns the GtkTreeModelFlags for this particular type of model''' return gtk.TREE_MODEL_LIST_ONLY | gtk.TREE_MODEL_ITERS_PERSIST def on_get_n_columns(self): return 1 def on_get_path(self, node): '''returns the tree path (a tuple of indices at the various levels) for a particular node.''' return self.node_map.get_path(node) def on_get_column_type(self,index): if index == self.tooltip_column: return object return str def on_get_iter(self, path): try: return self.node_map.get_handle(path[0]) except: return None def on_get_value(self,node,col): try: if node != self.prev_handle: self.prev_data = self.map(str(node)) self.prev_handle = node return self.fmap[col](self.prev_data) except: return u'' def on_iter_next(self, node): '''returns the next node at this level of the tree''' return self.node_map.find_next_handle(node) def on_iter_children(self,node): """Return the first child of the node""" if node == None and len(self.node_map): return self.node_map.get_first_handle() return None def on_iter_has_child(self, node): '''returns true if this node has children''' if node == None: return len(self.node_map) > 0 return False def on_iter_n_children(self,node): if node == None: return len(self.node_map) return 0 def on_iter_nth_child(self,node,n): if node == None: return self.node_map.get_handle(n) return None def on_iter_parent(self, node): '''returns the parent of this node''' return None