I'm trying to add a toggle field in an filtered model for pygobjects. I used the code from the tutorial and enhanced to add toggle renderer, but as soon as I click in a filtered list the toggles are rendered wrong.
Assume you have a unfiltered list:
###########################
# 0|test|a|toggle=False
# 1|test|b|toggle=False
# 2|test|a|toggle=False
# 3|test|a|toggle=False
##########################
When you now apply a filter, saying you only want to see items of b you get:
# 1|test|b|toggle=False
If you now click on that column, the column from the unfiltered list is "toggled", as the path is 0, but should be 1:
# 0|test|a|toggle=True
But what I want is, of course:
# 1|test|b|toggle=True
So how do I do that?
Here is my code, mostly copied from the Tutorial at http://python-gtk-3-tutorial.readthedocs.org/en/latest/treeview.html#filtering :
from gi.repository import Gtk
#list of tuples for each software, containing the software name, initial release, and main programming languages used
software_list = [("Firefox", 2002, "C++", False),
("Eclipse", 2004, "Java", False),
("Pitivi", 2004, "Python", False),
("Netbeans", 1996, "Java", False),
("Chrome", 2008, "C++", False),
("Filezilla", 2001, "C++", False),
("Bazaar", 2005, "Python", False),
("Git", 2005, "C", False),
("Linux Kernel", 1991, "C", False),
("GCC", 1987, "C", False),
("Frostwire", 2004, "Java", False)]
class TreeViewFilterWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Treeview Filter Demo")
self.set_border_width(10)
#Setting up the self.grid in which the elements are to be positionned
self.grid = Gtk.Grid()
self.grid.set_column_homogeneous(True)
self.grid.set_row_homogeneous(True)
self.add(self.grid)
#Creating the ListStore model
self.software_liststore = Gtk.ListStore(str, int, str, bool)
for software_ref in software_list:
self.software_liststore.append(list(software_ref))
self.current_filter_language = None
#Creating the filter, feeding it with the liststore model
self.language_filter = self.software_liststore.filter_new()
#setting the filter function, note that we're not using the
self.language_filter.set_visible_func(self.language_filter_func)
#creating the treeview, making it use the filter as a model, and adding the columns
self.treeview = Gtk.TreeView.new_with_model(self.language_filter)
for i, column_title in enumerate(["Software", "Release Year", "Programming Language"]):
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
self.treeview.append_column(column)
renderer_toggle = Gtk.CellRendererToggle()
renderer_toggle.connect("toggled", self.on_cell_toggled)
column_toggle = Gtk.TreeViewColumn("toggle", renderer_toggle, active=3)
self.treeview.append_column(column_toggle)
#creating buttons to filter by programming language, and setting up their events
self.buttons = list()
for prog_language in ["Java", "C", "C++", "Python", "None"]:
button = Gtk.Button(prog_language)
self.buttons.append(button)
button.connect("clicked", self.on_selection_button_clicked)
#setting up the layout, putting the treeview in a scrollwindow, and the buttons in a row
self.scrollable_treelist = Gtk.ScrolledWindow()
self.scrollable_treelist.set_vexpand(True)
self.grid.attach(self.scrollable_treelist, 0, 0, 8, 10)
self.grid.attach_next_to(self.buttons[0], self.scrollable_treelist, Gtk.PositionType.BOTTOM, 1, 1)
for i, button in enumerate(self.buttons[1:]):
self.grid.attach_next_to(button, self.buttons[i], Gtk.PositionType.RIGHT, 1, 1)
self.scrollable_treelist.add(self.treeview)
self.show_all()
def on_cell_toggled(self, widget, path):
self.software_liststore[path][3] = not self.software_liststore[path][3]
def language_filter_func(self, model, iter, data):
"""Tests if the language in the row is the one in the filter"""
if self.current_filter_language is None or self.current_filter_language == "None":
return True
else:
return model[iter][2] == self.current_filter_language
def on_selection_button_clicked(self, widget):
"""Called on any of the button clicks"""
#we set the current language filter to the button's label
self.current_filter_language = widget.get_label()
print("%s language selected!" % self.current_filter_language)
#we update the filter, which updates in turn the view
self.language_filter.refilter()
win = TreeViewFilterWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
The path
in on_cell_toggled()
is the path in the filtered model since that's the model applied to your GtkTreeView. You will need to convert it to the path in the unfiltered model before you can change that. To do that, use the convert_path_to_child_path()
method of the filtered model.
If you add sorting later, you will need to do the same with your sorted model, and with both a sorted model and a filtered model, you will need to call both methods. Regardless of how many layers you put on top of your raw model, your renderer's event handler will always give you a path to the layer that your GtkTreeView knows about.
The same rules apply to manipulating tree view selections as well.