1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2000-2006 Donald N. Allingham 5# Copyright (C) 2009 Benny Malengier 6# Copyright (C) 2010 Nick Hall 7# 8# This program is free software; you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation; either version 2 of the License, or 11# (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program; if not, write to the Free Software 20# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 21# 22 23""" 24This module provides the flat treemodel that is used for all flat treeviews. 25 26For performance, Gramps does not use Gtk.TreeStore, as that would mean keeping 27the entire database table of an object in memory. 28Instead, it suffices to keep in memory the sortkey and the matching handle, 29as well as a map of sortkey,handle to treeview path, and vice versa. 30 31For a flat view, the index of sortkey,handle will be the path, so it suffices 32to keep in memory a map that given a sortkey,handle returns the path. 33As we need to be able to insert/delete/update objects, and for that the handle 34is all we know initially, and as sortkey,handle is uniquely determined by 35handle, instead of keeping a map of sortkey,handle to path, we keep a map of 36handle to path 37 38As a user selects another column to sort, the sortkey must be rebuild, and the 39map remade. 40 41The class FlatNodeMap keeps a sortkeyhandle list with (sortkey, handle) entries, 42and a handle2path dictionary. As the Map is flat, the index in sortkeyhandle 43corresponds to the path. 44 45The class FlatBaseModel, is the base class for all flat treeview models. 46It keeps a FlatNodeMap, and obtains data from database as needed 47""" 48 49#------------------------------------------------------------------------- 50# 51# python modules 52# 53#------------------------------------------------------------------------- 54import logging 55import bisect 56from time import perf_counter 57 58_LOG = logging.getLogger(".gui.basetreemodel") 59 60#------------------------------------------------------------------------- 61# 62# GNOME/GTK modules 63# 64#------------------------------------------------------------------------- 65from gi.repository import GObject 66from gi.repository import Gtk 67 68#------------------------------------------------------------------------- 69# 70# Gramps modules 71# 72#------------------------------------------------------------------------- 73from gramps.gen.filters import SearchFilter, ExactSearchFilter 74from gramps.gen.const import GRAMPS_LOCALE as glocale 75from .basemodel import BaseModel 76from ...user import User 77from gramps.gen.proxy.cache import CacheProxyDb 78 79#------------------------------------------------------------------------- 80# 81# FlatNodeMap 82# 83#------------------------------------------------------------------------- 84 85UEMPTY = "" 86 87class FlatNodeMap: 88 """ 89 A NodeMap for a flat treeview. In such a TreeView, the paths possible are 90 0, 1, 2, ..., n-1, where n is the number of items to show. For the model 91 it is needed to keep the Path to Iter mappings of the TreeView in memory 92 93 The order of what is shown is based on the unique key: (sortkey, handle) 94 Naming: 95 * srtkey : key on which to sort 96 * hndl : handle of the object, makes it possible to retrieve the 97 object from the database. As handle is unique, it is used 98 in the iter for the TreeView 99 * index : the index in the internal lists. When a view is in reverse, 100 this is not kept physically, but instead via an offset 101 * path : integer path in the TreeView. This will be index if view is 102 ascending, but will begin at back of list if view shows 103 the entries in reverse. 104 * index2hndl : list of (srtkey, hndl) tuples. The index gives the 105 (srtkey, hndl) it belongs to. 106 This normally is only a part of all possible data 107 * hndl2index : dictionary of *hndl: index* values 108 109 The implementation provides a list of (srtkey, hndl) of which the index is 110 the path, and a dictionary mapping hndl to index. 111 To obtain index given a path, method real_index() is available 112 113 ..Note: glocale.sort_key is applied to the underlying sort key, 114 so as to have localized sort 115 """ 116 117 def __init__(self): 118 """ 119 Create a new instance. 120 """ 121 self._index2hndl = [] 122 self._fullhndl = self._index2hndl 123 self._identical = True 124 self._hndl2index = {} 125 self._reverse = False 126 self.__corr = (0, 1) 127 #We create a stamp to recognize invalid iterators. From the docs: 128 #Set the stamp to be equal to your model's stamp, to mark the 129 #iterator as valid. When your model's structure changes, you should 130 #increment your model's stamp to mark all older iterators as invalid. 131 #They will be recognised as invalid because they will then have an 132 #incorrect stamp. 133 self.stamp = 0 134 135 def destroy(self): 136 """ 137 Unset all elements that can prevent garbage collection 138 """ 139 self._index2hndl = None 140 self._fullhndl = None 141 self._hndl2index = None 142 143 def set_path_map(self, index2hndllist, fullhndllist, identical=True, 144 reverse=False): 145 """ 146 This is the core method to set up the FlatNodeMap 147 Input is a list of (srtkey, handle), of which the index is the path 148 Calling this method sets the index2hndllist, and creates the hndl2index 149 map. 150 fullhndllist is the entire list of (srtkey, handle) that is possible, 151 normally index2hndllist is only part of this list as determined by 152 filtering. To avoid memory, if both lists are the same, pass only one 153 list twice and set identical to True. 154 Reverse sets up how the path is determined from the index. If True the 155 first index is the last path 156 157 :param index2hndllist: the ascending sorted (sortkey, handle) values 158 as they will appear in the flat treeview. This often is 159 a subset of all possible data. 160 :type index2hndllist: a list of (sortkey, handle) tuples 161 :param fullhndllist: the list of all possilbe ascending sorted 162 (sortkey, handle) values as they will appear in the flat 163 treeview if all data is shown. 164 :type fullhndllist: a list of (sortkey, handl) tuples 165 :param identical: identify if index2hndllist and fullhndllist are the 166 same list, so only one is kept in memory. 167 :type identical: bool 168 """ 169 self.stamp += 1 170 self._index2hndl = index2hndllist 171 self._hndl2index = {} 172 self._identical = identical 173 self._fullhndl = self._index2hndl if identical else fullhndllist 174 self._reverse = reverse 175 self.reverse_order() 176 177 def full_srtkey_hndl_map(self): 178 """ 179 The list of all possible (sortkey, handle) tuples. 180 This is stored in FlatNodeMap so that it would not be needed to 181 reiterate over the database to obtain all posibilities. 182 """ 183 return self._fullhndl 184 185 def reverse_order(self): 186 """ 187 This method keeps the index2hndl map, but sets it up the index in 188 reverse order. If the hndl2index map does not exist yet, it is created 189 in the acending order as given in index2hndl 190 The result is always a hndl2index map wich is correct, so or ascending 191 order, or reverse order. 192 """ 193 if self._hndl2index: 194 #if hndl2index is build already, invert order, otherwise keep 195 # requested order 196 self._reverse = not self._reverse 197 if self._reverse: 198 self.__corr = (len(self._index2hndl) - 1, -1) 199 else: 200 self.__corr = (0, 1) 201 if not self._hndl2index: 202 self._hndl2index = dict((key[1], index) 203 for index, key in enumerate(self._index2hndl)) 204 205 def real_path(self, index): 206 """ 207 Given the index in the maps, return the real path. 208 If reverse = False, then index is path, otherwise however, the 209 path must be calculated so that the last index is the first path 210 """ 211 return self.__corr[0] + self.__corr[1] * index 212 213 def real_index(self, path): 214 """ 215 Given the path in the view, return the real index. 216 If reverse = False, then path is index, otherwise however, the 217 index must be calculated so that the last index is the first path 218 """ 219 return self.__corr[0] + self.__corr[1] * path 220 221 def clear_map(self): 222 """ 223 Clears out the index2hndl and the hndl2index 224 """ 225 self._index2hndl = [] 226 self._hndl2index = {} 227 self._fullhndl = self._index2hndl 228 self._identical = True 229 230 def get_path(self, iter): 231 """ 232 Return the path from the passed iter. 233 234 :param handle: the key of the object for which the path in the treeview 235 is needed 236 :type handle: an object handle 237 :Returns: the path, or None if handle does not link to a path 238 """ 239 index = iter.user_data 240 ##GTK3: user data may only be an integer, we store the index 241 ##PROBLEM: pygobject 3.8 stores 0 as None, we need to correct 242 ## when using user_data for that! 243 ##upstream bug: https://bugzilla.gnome.org/show_bug.cgi?id=698366 244 if index is None: 245 index = 0 246 return Gtk.TreePath((self.real_path(index),)) 247 248 def get_path_from_handle(self, handle): 249 """ 250 Return the path from the passed handle 251 252 :param handle: the key of the object for which the path in the treeview 253 is needed 254 :type handle: an object handle 255 :Returns: the path, or None if handle does not link to a path 256 """ 257 index = self._hndl2index.get(handle) 258 if index is None: 259 return None 260 261 return Gtk.TreePath((self.real_path(index),)) 262 263 def get_sortkey(self, handle): 264 """ 265 Return the sortkey used for the passed handle. 266 267 :param handle: the key of the object for which the sortkey 268 is needed 269 :type handle: an object handle 270 :Returns: the sortkey, or None if handle is not present 271 """ 272 index = self._hndl2index.get(handle) 273 return None if index is None else self._index2hndl[index][0] 274 275 def new_iter(self, handle): 276 """ 277 Return a new iter containing the handle 278 """ 279 iter = Gtk.TreeIter() 280 iter.stamp = self.stamp 281 ##GTK3: user data may only be an integer, we store the index 282 ##PROBLEM: pygobject 3.8 stores 0 as None, we need to correct 283 ## when using user_data for that! 284 ##upstream bug: https://bugzilla.gnome.org/show_bug.cgi?id=698366 285 iter.user_data = self._hndl2index[handle] 286 return iter 287 288 def get_iter(self, path): 289 """ 290 Return an iter from the path. The path is assumed to be an integer. 291 This is accomplished by indexing into the index2hndl 292 iters are always created afresh 293 294 Will raise IndexError if the maps are not filled yet, or if it is empty. 295 Caller should take care of this if it allows calling with invalid path 296 297 :param path: path as it appears in the treeview 298 :type path: integer 299 """ 300 iter = self.new_iter(self._index2hndl[self.real_index(path)][1]) 301 return iter 302 303 def get_handle(self, path): 304 """ 305 Return the handle from the path. The path is assumed to be an integer. 306 This is accomplished by indexing into the index2hndl 307 308 Will raise IndexError if the maps are not filled yet, or if it is empty. 309 Caller should take care of this if it allows calling with invalid path 310 311 :param path: path as it appears in the treeview 312 :type path: integer 313 :return handle: unicode form of the handle 314 """ 315 return self._index2hndl[self.real_index(path)][1] 316 317 def iter_next(self, iter): 318 """ 319 Increments the iter y finding the index associated with the iter, 320 adding or substracting one. 321 False is returned if no next handle 322 323 :param iter: Gtk.TreeModel iterator 324 :param type: Gtk.TreeIter 325 """ 326 index = iter.user_data 327 if index is None: 328 ##GTK3: user data may only be an integer, we store the index 329 ##PROBLEM: pygobject 3.8 stores 0 as None, we need to correct 330 ## when using user_data for that! 331 ##upstream bug: https://bugzilla.gnome.org/show_bug.cgi?id=698366 332 index = 0 333 334 if self._reverse : 335 index -= 1 336 if index < 0: 337 # -1 does not raise IndexError, as -1 is last element. Catch. 338 return False 339 else: 340 index += 1 341 if index >= len(self._index2hndl): 342 return False 343 iter.user_data = index 344 return True 345 346 def get_first_iter(self): 347 """ 348 Return the first handle that must be shown (corresponding to path 0) 349 350 Will raise IndexError if the maps are not filled yet, or if it is empty. 351 Caller should take care of this if it allows calling with invalid path 352 """ 353 return self.get_iter(0) 354 355 def __len__(self): 356 """ 357 Return the number of entries in the map. 358 """ 359 return len(self._index2hndl) 360 361 def max_rows(self): 362 """ 363 Return maximum number of entries that might be present in the 364 map 365 """ 366 return len(self._fullhndl) 367 368 def insert(self, srtkey_hndl, allkeyonly=False): 369 """ 370 Insert a node. Given is a tuple (sortkey, handle), and this is added 371 in the correct place, while the hndl2index map is updated. 372 Returns the path of the inserted row 373 374 :param srtkey_hndl: the (sortkey, handle) tuple that must be inserted 375 :type srtkey_hndl: sortkey key already transformed by self.sort_func, object handle 376 377 :Returns: path of the row inserted in the treeview 378 :Returns type: Gtk.TreePath or None 379 """ 380 if srtkey_hndl[1] in self._hndl2index: 381 print(('WARNING: Attempt to add row twice to the model (%s)' % 382 srtkey_hndl[1])) 383 return 384 if not self._identical: 385 bisect.insort_left(self._fullhndl, srtkey_hndl) 386 if allkeyonly: 387 #key is not part of the view 388 return None 389 insert_pos = bisect.bisect_left(self._index2hndl, srtkey_hndl) 390 self._index2hndl.insert(insert_pos, srtkey_hndl) 391 #make sure the index map is updated 392 for srt_key,hndl in self._index2hndl[insert_pos+1:]: 393 self._hndl2index[hndl] += 1 394 self._hndl2index[srtkey_hndl[1]] = insert_pos 395 #update self.__corr so it remains correct 396 if self._reverse: 397 self.__corr = (len(self._index2hndl) - 1, -1) 398 return Gtk.TreePath((self.real_path(insert_pos),)) 399 400 def delete(self, handle): 401 """ 402 Delete the row with the given (handle). 403 This then rebuilds the hndl2index, subtracting one from each item 404 greater than the deleted index. 405 path of deleted row is returned 406 If handle is not present, None is returned 407 408 :param srtkey_hndl: the (sortkey, handle) tuple that must be inserted 409 410 :Returns: path of the row deleted from the treeview 411 :Returns type: Gtk.TreePath or None 412 """ 413 #remove it from the full list first 414 if not self._identical: 415 for indx in range(len(self._fullhndl)): 416 if self._fullhndl[indx][1] == handle: 417 del self._fullhndl[indx] 418 break 419 #now remove it from the index maps 420 try: 421 index = self._hndl2index[handle] 422 except KeyError: 423 # key not present in the treeview 424 return None 425 del self._index2hndl[index] 426 del self._hndl2index[handle] 427 #update self.__corr so it remains correct 428 delpath = self.real_path(index) 429 if self._reverse: 430 self.__corr = (len(self._index2hndl) - 1, -1) 431 #update the handle2path map so it remains correct 432 for dummy_srt_key, hndl in self._index2hndl[index:]: 433 self._hndl2index[hndl] -= 1 434 return Gtk.TreePath((delpath,)) 435 436 437#------------------------------------------------------------------------- 438# 439# FlatBaseModel 440# 441#------------------------------------------------------------------------- 442class FlatBaseModel(GObject.GObject, Gtk.TreeModel, BaseModel): 443 """ 444 The base class for all flat treeview models. 445 It keeps a FlatNodeMap, and obtains data from database as needed 446 ..Note: glocale.sort_key is applied to the underlying sort key, 447 so as to have localized sort 448 """ 449 450 def __init__(self, db, uistate, scol=0, order=Gtk.SortType.ASCENDING, 451 search=None, skip=set(), 452 sort_map=None): 453 cput = perf_counter() 454 GObject.GObject.__init__(self) 455 BaseModel.__init__(self) 456 self.uistate = uistate 457 self.user = User(parent=uistate.window, uistate=uistate) 458 #inheriting classes must set self.map to obtain the data 459 self.prev_handle = None 460 self.prev_data = None 461 462 #GTK3 We leak ref, yes?? 463 #self.set_property("leak_references", False) 464 465 self.db = db 466 #normally sort on first column, so scol=0 467 if sort_map: 468 #sort_map is the stored order of the columns and if they are 469 #enabled or not. We need to store on scol of that map 470 self.sort_map = [ f for f in sort_map if f[0]] 471 #we need the model col, that corresponds with scol 472 col = self.sort_map[scol][1] 473 else: 474 col = scol 475 # get the function that maps data to sort_keys 476 self.sort_func = lambda x: glocale.sort_key(self.smap[col](x)) 477 self.sort_col = scol 478 self.skip = skip 479 self._in_build = False 480 481 self.node_map = FlatNodeMap() 482 self.set_search(search) 483 484 self._reverse = (order == Gtk.SortType.DESCENDING) 485 486 self.rebuild_data() 487 _LOG.debug(self.__class__.__name__ + ' __init__ ' + 488 str(perf_counter() - cput) + ' sec') 489 490 def destroy(self): 491 """ 492 Unset all elements that prevent garbage collection 493 """ 494 BaseModel.destroy(self) 495 self.db = None 496 self.sort_func = None 497 if self.node_map: 498 self.node_map.destroy() 499 self.node_map = None 500 self.rebuild_data = None 501 self.search = None 502 503 def set_search(self, search): 504 """ 505 Change the search function that filters the data in the model. 506 When this method is called, make sure: 507 # you call self.rebuild_data() to recalculate what should be seen 508 in the model 509 # you reattach the model to the treeview so that the treeview updates 510 with the new entries 511 """ 512 if search: 513 if search[0]: 514 #following is None if no data given in filter sidebar 515 self.search = search[1] 516 self.rebuild_data = self._rebuild_filter 517 else: 518 if search[1]: # Search from topbar in columns 519 # we have search[1] = (index, text_unicode, inversion) 520 col = search[1][0] 521 text = search[1][1] 522 inv = search[1][2] 523 func = lambda x: self._get_value(x, col) or UEMPTY 524 if search[2]: 525 self.search = ExactSearchFilter(func, text, inv) 526 else: 527 self.search = SearchFilter(func, text, inv) 528 else: 529 self.search = None 530 self.rebuild_data = self._rebuild_search 531 else: 532 self.search = None 533 self.rebuild_data = self._rebuild_search 534 535 def total(self): 536 """ 537 Total number of items that maximally can be shown 538 """ 539 return self.node_map.max_rows() 540 541 def displayed(self): 542 """ 543 Number of items that are currently displayed 544 """ 545 return len(self.node_map) 546 547 def reverse_order(self): 548 """ 549 reverse the sort order of the sort column 550 """ 551 self._reverse = not self._reverse 552 self.node_map.reverse_order() 553 554 def color_column(self): 555 """ 556 Return the color column. 557 """ 558 return None 559 560 def sort_keys(self): 561 """ 562 Return the (sort_key, handle) list of all data that can maximally 563 be shown. 564 This list is sorted ascending, via localized string sort. 565 """ 566 # use cursor as a context manager 567 with self.gen_cursor() as cursor: 568 #loop over database and store the sort field, and the handle 569 srt_keys=[(self.sort_func(data), key) 570 for key, data in cursor] 571 srt_keys.sort() 572 return srt_keys 573 574 def _rebuild_search(self, ignore=None): 575 """ function called when view must be build, given a search text 576 in the top search bar 577 """ 578 self.clear_cache() 579 self._in_build = True 580 if (self.db is not None) and self.db.is_open(): 581 allkeys = self.node_map.full_srtkey_hndl_map() 582 if not allkeys: 583 allkeys = self.sort_keys() 584 if self.search and self.search.text: 585 dlist = [h for h in allkeys 586 if self.search.match(h[1], self.db) and 587 h[1] not in self.skip and h[1] != ignore] 588 ident = False 589 elif ignore is None and not self.skip: 590 #nothing to remove from the keys present 591 ident = True 592 dlist = allkeys 593 else: 594 ident = False 595 dlist = [h for h in allkeys 596 if h[1] not in self.skip and h[1] != ignore] 597 self.node_map.set_path_map(dlist, allkeys, identical=ident, 598 reverse=self._reverse) 599 else: 600 self.node_map.clear_map() 601 self._in_build = False 602 603 def _rebuild_filter(self, ignore=None): 604 """ function called when view must be build, given filter options 605 in the filter sidebar 606 """ 607 self.clear_cache() 608 self._in_build = True 609 if (self.db is not None) and self.db.is_open(): 610 cdb = CacheProxyDb(self.db) 611 allkeys = self.node_map.full_srtkey_hndl_map() 612 if not allkeys: 613 allkeys = self.sort_keys() 614 if self.search: 615 ident = False 616 if ignore is None: 617 dlist = self.search.apply(cdb, allkeys, tupleind=1, 618 user=self.user) 619 else: 620 dlist = self.search.apply( 621 cdb, [k for k in allkeys if k[1] != ignore], 622 tupleind=1) 623 elif ignore is None : 624 ident = True 625 dlist = allkeys 626 else: 627 ident = False 628 dlist = [ k for k in allkeys if k[1] != ignore ] 629 self.node_map.set_path_map(dlist, allkeys, identical=ident, 630 reverse=self._reverse) 631 else: 632 self.node_map.clear_map() 633 self._in_build = False 634 635 def add_row_by_handle(self, handle): 636 """ 637 Add a row. This is called after object with handle is created. 638 Row is only added if search/filter data is such that it must be shown 639 """ 640 assert isinstance(handle, str) 641 if self.node_map.get_path_from_handle(handle) is not None: 642 return # row is already displayed 643 data = self.map(handle) 644 insert_val = (self.sort_func(data), handle) 645 if not self.search or \ 646 (self.search and self.search.match(handle, self.db)): 647 #row needs to be added to the model 648 insert_path = self.node_map.insert(insert_val) 649 650 if insert_path is not None: 651 node = self.do_get_iter(insert_path)[1] 652 self.row_inserted(insert_path, node) 653 else: 654 self.node_map.insert(insert_val, allkeyonly=True) 655 656 def delete_row_by_handle(self, handle): 657 """ 658 Delete a row, called after the object with handle is deleted 659 """ 660 delete_path = self.node_map.delete(handle) 661 #delete_path is an integer from 0 to n-1 662 if delete_path is not None: 663 self.clear_cache(handle) 664 self.row_deleted(delete_path) 665 666 def update_row_by_handle(self, handle): 667 """ 668 Update a row, called after the object with handle is changed 669 """ 670 if self.node_map.get_path_from_handle(handle) is None: 671 return # row is not currently displayed 672 self.clear_cache(handle) 673 oldsortkey = self.node_map.get_sortkey(handle) 674 newsortkey = self.sort_func(self.map(handle)) 675 if oldsortkey is None or oldsortkey != newsortkey: 676 #or the changed object is not present in the view due to filtering 677 #or the order of the object must change. 678 self.delete_row_by_handle(handle) 679 self.add_row_by_handle(handle) 680 else: 681 #the row is visible in the view, is changed, but the order is fixed 682 path = self.node_map.get_path_from_handle(handle) 683 node = self.do_get_iter(path)[1] 684 self.row_changed(path, node) 685 686 def get_iter_from_handle(self, handle): 687 """ 688 Get the iter for a gramps handle. 689 """ 690 if self.node_map.get_path_from_handle(handle) is None: 691 return None 692 return self.node_map.new_iter(handle) 693 694 def get_handle_from_iter(self, iter): 695 """ 696 Get the gramps handle for an iter. 697 """ 698 index = iter.user_data 699 if index is None: 700 ##GTK3: user data may only be an integer, we store the index 701 ##PROBLEM: pygobject 3.8 stores 0 as None, we need to correct 702 ## when using user_data for that! 703 ##upstream bug: https://bugzilla.gnome.org/show_bug.cgi?id=698366 704 index = 0 705 path = self.node_map.real_path(index) 706 return self.node_map.get_handle(path) 707 708 # The following implement the public interface of Gtk.TreeModel 709 710 def do_get_flags(self): 711 """ 712 Returns the GtkTreeModelFlags for this particular type of model 713 See Gtk.TreeModel 714 """ 715 #print 'do_get_flags' 716 return Gtk.TreeModelFlags.LIST_ONLY #| Gtk.TreeModelFlags.ITERS_PERSIST 717 718 def do_get_n_columns(self): 719 """Internal method. Don't inherit""" 720 return self.on_get_n_columns() 721 722 def on_get_n_columns(self): 723 """ 724 Return the number of columns. Must be implemented in the child objects 725 See Gtk.TreeModel. Inherit as needed 726 """ 727 #print 'do_get_n_col' 728 raise NotImplementedError 729 730 def do_get_path(self, iter): 731 """ 732 Return the tree path (a tuple of indices at the various 733 levels) for a particular iter. We use handles for unique key iters 734 See Gtk.TreeModel 735 """ 736 #print 'do_get_path', iter 737 return self.node_map.get_path(iter) 738 739 def do_get_column_type(self, index): 740 """ 741 See Gtk.TreeModel 742 """ 743 #print 'do_get_col_type' 744 return str 745 746 def do_get_iter_first(self): 747 #print 'get iter first' 748 raise NotImplementedError 749 750 def do_get_iter(self, path): 751 """ 752 See Gtk.TreeModel 753 """ 754 #print 'do_get_iter', path 755 for p in path: 756 break 757 try: 758 return True, self.node_map.get_iter(p) 759 except IndexError: 760 return False, Gtk.TreeIter() 761 762 def _get_value(self, handle, col): 763 """ 764 Given handle and column, return unicode value in the column 765 We need this to search in the column in the GUI 766 """ 767 if handle != self.prev_handle: 768 cached, data = self.get_cached_value(handle, col) 769 if not cached: 770 data = self.map(handle) 771 self.set_cached_value(handle, col, data) 772 if data is None: 773 #object is no longer present 774 return '' 775 self.prev_data = data 776 self.prev_handle = handle 777 return self.fmap[col](self.prev_data) 778 779 def do_get_value(self, iter, col): 780 """ 781 See Gtk.TreeModel. 782 col is the model column that is needed, not the visible column! 783 """ 784 #print ('do_get_val', iter, iter.user_data, col) 785 index = iter.user_data 786 if index is None: 787 ##GTK3: user data may only be an integer, we store the index 788 ##PROBLEM: pygobject 3.8 stores 0 as None, we need to correct 789 ## when using user_data for that! 790 ##upstream bug: https://bugzilla.gnome.org/show_bug.cgi?id=698366 791 index = 0 792 handle = self.node_map._index2hndl[index][1] 793 val = self._get_value(handle, col) 794 #print 'val is', val, type(val) 795 796 return val 797 798 def do_iter_previous(self, iter): 799 #print 'do_iter_previous' 800 raise NotImplementedError 801 802 def do_iter_next(self, iter): 803 """ 804 Sets iter to the next node at this level of the tree 805 See Gtk.TreeModel 806 """ 807 return self.node_map.iter_next(iter) 808 809 def do_iter_children(self, iterparent): 810 """ 811 Return the first child of the node 812 See Gtk.TreeModel 813 """ 814 #print 'do_iter_children' 815 print('ERROR: iter children, should not be called in flat base!!') 816 raise NotImplementedError 817 if handle is None and len(self.node_map): 818 return self.node_map.get_first_handle() 819 return None 820 821 def do_iter_has_child(self, iter): 822 """ 823 Returns true if this node has children 824 See Gtk.TreeModel 825 """ 826 #print 'do_iter_has_child' 827 print('ERROR: iter has_child', iter, 'should not be called in flat base') 828 return False 829 if handle is None: 830 return len(self.node_map) > 0 831 return False 832 833 def do_iter_n_children(self, iter): 834 """ 835 See Gtk.TreeModel 836 """ 837 #print 'do_iter_n_children' 838 print('ERROR: iter_n_children', iter, 'should not be called in flat base') 839 return 0 840 if handle is None: 841 return len(self.node_map) 842 return 0 843 844 def do_iter_nth_child(self, iter, nth): 845 """ 846 See Gtk.TreeModel 847 """ 848 #print 'do_iter_nth_child', iter, nth 849 if iter is None: 850 return True, self.node_map.get_iter(nth) 851 return False, None 852 853 def do_iter_parent(self, iter): 854 """ 855 Returns the parent of this node 856 See Gtk.TreeModel 857 """ 858 #print 'do_iter_parent' 859 return False, None 860