gtg-contributors team mailing list archive
-
gtg-contributors team
-
Mailing list archive
-
Message #00540
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