Question

I've implemented a custom TreeModel in PyGI (GTK3) the following way (as suggested here and here):

test.py

from gi.repository import Gtk

class Store(GObject.Object, Gtk.TreeModel):
    def __init__(self):
        self.data = [[i*j for i in range(10)] for j in range(10)] #multiplication table
        super(Store, self).__init__()

    #boilerplate TreeModel interface implementation
    def do_get_flags(self):
        print "do_get_flags called"
        return Gtk.TreeModelFlags.LIST_ONLY

    def do_get_n_columns(self):
        print "do_get_n_columns called"
        return len(self.data[0])

    def do_get_column_type(self, index):
        print "do_get_column_type called; index = %s"%(index)
        if index < 10:
            return str
        else:
            raise IndexError

    def do_get_iter(self, path):
        print "do_get_iter called; path = %s"%(path)
        indices = path.get_indices()
        if indices[0] < len(self.alignment.sequences):
            iterator = Gtk.TreeIter()
            iterator.user_data = indices[0]
            print "iterator.user_data = %s, iterator = %s" % (iterator.user_data, iterator)
            return (True, iterator)
        else:
            return (False, None)

    def do_get_path(self, iterator):
        print "do_get_path called; iter  = %s" % (iter)
        if iterator.user_data is not None:
            path = Gtk.TreePath(iterator.user_data)
            return path
        else:
            return None

    def do_get_value(self, iterator, column_index):
        print "do_get_value called; iterator = %s, column_index = %s"%(iterator, column_index)
        item = self.data[iterator.user_data][column_index]

    def do_iter_next(self, iterator):
        print "do_iter_next_called; iterator = %s"%(iterator)
        #returns next iterator
        if not hasattr(iterator, "user_data"):
            print self.do_get_path(iterator)
            return
        else:
            print "user data  = %s" % (iterator.user_data)
        try:
            return self.do_get_iter(Gtk.TreePath((iterator.user_data+1,)))
        except IndexError:
            return None

    def do_iter_has_child(self, iterator):
        print "do_iter_has_child called; rowref = %s" % (iterator)
        return False

    def do_iter_nth_child(self, iterator, index):
        print "do_iter_nth_child called; iterator = %s, index = %s" % (iterator, index)
        output_iterator = Gtk.TreeIter()
        output_iterator.user_data = index
        return (True, output_iterator)

    def do_iter_parent(self, child):
        print "do_iter_parent called; child = %s" % (child)
        return None


def main():
    model = Store()
    view = Gtk.TreeView(model)

    for index, column in enumerate(alignment):
        renderer = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn(str(index))
        column.pack_start(renderer, False)
        column.add_attribute(renderer, "markup", index)
        #column.props.sizing = Gtk.TreeViewColumnSizing.FIXED
        view.append_column(column)

    scrolled = Gtk.ScrolledWindow()
    scrolled.add(view)

    window = Gtk.Window(title="title")
    window.set_size_request(480, 640)
    window.add(scrolled)
    window.show_all()
    return window


if __name__ == "__main__":
    window = main()
    window.connect('destroy', Gtk.main_quit)
    Gtk.main()

Unfortunately this implementation doesn't work due to crazy behaviour of do_iter_next(). Look at the contents of logfile, created by python test.py &> test.log:

test.log

do_get_n_columns called
do_get_column_type called; index = 0
do_get_flags called
do_get_iter called; path = 0
iterator.user_data = 0, iterator = <GtkTreeIter at 0x8797cb0>
do_iter_next_called; iterator = <GtkTreeIter at 0xbfb243c0>
user data  = None
Traceback (most recent call last):
  File "minimal_example.py", line 65, in do_iter_next
    return self.do_get_iter(Gtk.TreePath((iterator.user_data+1,)))
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
do_iter_next_called; iterator = <GtkTreeIter at 0xbfb243c0>
user data  = None
Traceback (most recent call last):
  File "minimal_example.py", line 65, in do_iter_next
    return self.do_get_iter(Gtk.TreePath((iterator.user_data+1,)))
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
do_iter_next_called; iterator = <GtkTreeIter at 0xbfb243c0>
user data  = None

Do you have any ideas about why first do_get_iter() returns one iterator and then do_iter_next() takes on input another one, with empty user_data. Also, do you understand, whether user_data is a specific field, allotted by GTK3 authors, or not? Reference manual doesn't mention it, but why then it is present for my misterious iter at 0xbfb243c0?

Was it helpful?

Solution

Usage of Gtk.TreeIters.user_data is ok. There was some historical baggage associated with the field in versions of pygobject prior to 3.8 in regards to memory leakage (but it still worked). pygobject 3.8 changed the field to only allow integer values and fixed the leak. However, versions 3.8.0 and 3.8.1 continued to interpret an integer value of 0 as None when accessed which is the problem you are seeing. Versions 3.8.2 and above fixed this so a value of 0 is returned as 0 when accessed.

To support pygobject versions prior to 3.8.2, you can write an accessor function to retrieve the user data:

def get_user_data(tree_iter):
    if tree_iter.user_data is None:
        return 0
    return tree_iter.user_data

See: https://bugzilla.gnome.org/show_bug.cgi?id=698366

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top