← Back to team overview

gtg-contributors team mailing list archive

opinion on filtering

 

Hello people,
== tecnical part, skippable ==
I am looking at ways to have a nice tree structure like the one
liblarch is aiming at without having to write a GenericTreeModel
(because gtk is under-documented and buggy, as Lionel knows very
well).
Now, basically we started to look at the GenericTreeModel because the
gtk TreeModelFilter does not offer the kind of filtering gtg is
currently adopting: in gtk, if a node of the tree is marked as
invisible, all its child are not displayed, while in gtg we really use
a collapsed view of the tree in which only visible nodes are shown.
== non tecnical part, I need your opinion on this ==
We can avoid all these problem if we decide to play along with gtk
rules. Instead of using the filters to showing *only* the tasks which
match the filter, why don't we show those tasks and their parents,
using the ui to highlight the matching tasks?
This behaviour is coded in the attached example. Play with it to
better understand what I'm proposing.

The main benefit of this solution, except that is super-easy to code,
is that it shows in a non-obtrusive way also the parents of the tasks
which match the filter (a feature that has been asked for several
times).

What do you think?
 Luca



ps: I think I found out why deleted tasks are still maintained in
memory. It's, by design, a property of gtk tree models.

>From http://www.pygtk.org/pygtk2tutorial/sec-GenericTreeModel.html:
"""One of the problems with the GenericTreeModel is that TreeIters
hold a reference to a Python object returned from your custom tree
model. Since the TreeIter may be created and initialized in C code and
live on the stack, it's not possible to know when the TreeIter has
been destroyed and the Python object reference is no longer being
used. Therefore, the Python object referenced in a TreeIter has by
default its reference count incremented but it is not decremented when
the TreeIter is destroyed. This ensures that the Python object will
not be destroyed while being used by a TreeIter and possibly cause a
segfault. Unfortunately the extra reference counts lead to the
situation that, at best, the Python object will have an excessive
reference count and, at worst, it will never be freed even when it is
not being used. The latter case leads to memory leaks and the former
to reference leaks.

To provide for the situation where the custom TreeModel holds a
reference to the Python object until it is no longer available (i.e.
the TreeIter is invalid because the model has changed) and there is no
need to leak references, the GenericTreeModel has the
"leak-references" property. By default "leak-references" is TRUE to
indicate that the GenericTreeModel will leak references. If
"leak-references" is set to FALSE, the reference count of the Python
object will not be incremented when referenced in a TreeIter. This
means that your custom TreeModel must keep a reference to all Python
objects used in TreeIters until the model is destroyed. Unfortunately,
even this cannot protect against buggy code that attempts to use a
saved TreeIter on a different GenericTreeModel. To protect against
that case your application would have to keep references to all Python
objects referenced from a TreeIter for any GenericTreeModel instance.
Of course, this ultimately has the same result as leaking references.
"""
#!/usr/bin/env python

# example treeviewcolumn.py

import pygtk
pygtk.require('2.0')
import gtk

class TreeViewColumnExample(object):

        

    # close the window and quit
    def delete_event(self, widget, event, data=None):
        gtk.main_quit()
        return False

    def make_pb(self, tvcolumn, cell, model, iter):
        stock = model.get_value(iter, 1)
        pb = self.treeview.render_icon(stock, gtk.ICON_SIZE_MENU, None)
        cell.set_property('pixbuf', pb)
        return

    def mysuperduperfun(self, tvcolumn, cell, model, iter):
        visible = model.get_value(iter, 4)
        if visible:
            cell.set_property('foreground-gdk',
                          self.treeview.style.text[gtk.STATE_NORMAL])
            cell.set_property('scale', 1)
        else:
            cell.set_property('foreground-gdk',
                          self.treeview.style.text[gtk.STATE_INSENSITIVE])
            if self.apply_filter:
                cell.set_property('scale', 0.7)
            else:
                cell.set_property('scale', 1)

    def myvisibilityfunction(self, model, iter):
        if not self.apply_filter:
            return True
        #DFS on the tree to search for a visible node
        def dfs(a_iter):
            if model.get_value(a_iter, 4):
                return True
            child_iter = model.iter_children(a_iter)
            while child_iter != None:
                if dfs(child_iter):
                    return True
                child_iter = model.iter_next(child_iter)
            return False
        return dfs(iter)



    def __init__(self):
        self.apply_filter = False
        # Create a new window
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)

        self.window.set_title("TreeViewColumn Example")

        #self.window.set_size_request(200, 200)

        self.window.connect("delete_event", self.delete_event)

        # create a liststore with one string column to use as the model
        self.liststore = gtk.TreeStore(str, str, str, 'gboolean', 'gboolean')
        self.treemodelfilter = self.liststore.filter_new(root=None)
        self.treemodelfilter.set_visible_func(self.myvisibilityfunction)

        # create the TreeView using liststore
        self.treeview = gtk.TreeView(self.treemodelfilter)

        # create the TreeViewColumns to display the data
        self.tvcolumn = gtk.TreeViewColumn('Pixbuf and Text')
        self.tvcolumn1 = gtk.TreeViewColumn('Text Only')

        # add a row with text and a stock item - color strings for
        # the background
        a=self.liststore.append(None, ['FilterTrue', gtk.STOCK_OPEN, 'Open a File',
                                       True, True])
        a=self.liststore.append(None, ['FilterFalse', gtk.STOCK_OPEN, 'Open a File',
                                       True, False])
        b=self.liststore.append(a, ['FilterFalse', gtk.STOCK_NEW, 'New File', True,
                                    False])
        b=self.liststore.append(a, ['FilterFalse', gtk.STOCK_NEW, 'New File', True,
                                    False])
        b=self.liststore.append(a, ['FilterFalse', gtk.STOCK_NEW, 'New File', True,
                                    False])
        b=self.liststore.append(a, ['FilterFalse', gtk.STOCK_NEW, 'New File', True,
                                    False])
        c= self.liststore.append(b, ['FilterTrue', gtk.STOCK_PRINT, 'Print File',
                                     False, True])
        d= self.liststore.append(b, ['FilterFalse', gtk.STOCK_PRINT, 'Print File',
                                     False, False])
        e= self.liststore.append(c, ['FilterFalse', gtk.STOCK_PRINT, 'Print File',
                                     False, False])
        e= self.liststore.append(c, ['FilterFalse', gtk.STOCK_PRINT, 'Print File',
                                     False, False])
        e= self.liststore.append(c, ['FilterFalse', gtk.STOCK_PRINT, 'Print File',
                                     False, False])
        e= self.liststore.append(c, ['FilterFalse', gtk.STOCK_PRINT, 'Print File',
                                     False, False])
        self.treeview.expand_all()

        # add columns to treeview
        self.treeview.append_column(self.tvcolumn)

        # create a CellRenderers to render the data
        self.cellpb = gtk.CellRendererPixbuf()
        self.cell = gtk.CellRendererText()



        # add the cells to the columns - 2 in the first
        self.tvcolumn.pack_start(self.cellpb, False)
        self.tvcolumn.pack_start(self.cell, True)

        # set the cell attributes to the appropriate liststore column
        # GTK+ 2.0 doesn't support the "stock_id" property
        if gtk.gtk_version[1] < 2:
            self.tvcolumn.set_cell_data_func(self.cellpb, self.make_pb)
        else:
            self.tvcolumn.set_attributes(self.cellpb, stock_id=1)
        self.tvcolumn.set_attributes(self.cell, text=0)
        self.tvcolumn.set_cell_data_func(self.cell, self.mysuperduperfun)

        # make treeview searchable
        self.treeview.set_search_column(0)

        # Allow sorting on the column
        self.tvcolumn.set_sort_column_id(0)

        # Allow drag and drop reordering of rows
        self.treeview.set_reorderable(True)

        self.button = gtk.Button("filter!")
        def do_filter(data):
            self.apply_filter = not self.apply_filter
            self.treemodelfilter.refilter()
        self.button.connect("clicked", do_filter)
        box1 = gtk.VBox(False, 0)
        box1.pack_start(self.treeview)
        box1.pack_start(self.button)
        self.window.add(box1)

        self.window.show_all()

def main():
    gtk.main()

if __name__ == "__main__":
    tvcexample = TreeViewColumnExample()
    main()

Follow ups