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