GtkTreeModelFilter does not handle parent node visibility correctly
Steps to reproduce
- Run attached example program
- Enter the following regexp in the
GtkEntry
: RPM-GPG.* - Press the "Filter" button
Current behavior
No entries are show in the GtkTreeView
Expected outcome
Entries matching the regexp should be shown in the GtkTreeView
Version information
- OS: Fedora 30
- GTK:
- gtk3-devel-3.24.11-1.fc30.x86_64
- gtk3-3.24.11-1.fc30.x86_64
- GLib:
- glib2-2.60.7-2.fc30.x86_64
Additional information
The issue is that GtkTreeModelFilter
does not properly handle parent node visibility and leaves them "hidden", even though child nodes are marked as visible. According to the documentation of GtkTreeModelFilter
, the widget should be handling this by itself:
Determining the visibility state of a given node based on the state of its child nodes is a frequently occurring use case. Therefore, GtkTreeModelFilter explicitly supports this. For example, when a node does not have any children, you might not want the node to be visible. As soon as the first row is added to the node’s child level (or the last row removed), the node’s visibility should be updated.
However that does not seem to happen as evident by the debug output of the example program:
- With no filtering applied, the output for some of the entries in question is:
etc -> 1
pki -> 1
rpm-gpg -> 1
etc -> 1
pki -> 1
RPM-GPG-KEY-fedora-12-i386 -> 1
etc -> 1
pki -> 1
RPM-GPG-KEY-fedora-21-s390x -> 1
etc -> 1
pki -> 1
- However, once the filter is applied the output for the same entries is:
rpm-gpg -> 0
RPM-GPG-KEY-fedora-12-i386 -> 1
etc -> 0
pki -> 0
RPM-GPG-KEY-fedora-21-s390x -> 1
etc -> 0
pki -> 0
As you can see, visibility of the parent nodes remains as FALSE
.
Example program
#!/usr/bin/python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import os
import re
class MainWindow(Gtk.Window):
def __init__(self):
self.regexp = None
self.filter = None
Gtk.Window.__init__(self, title="TreeView Filter Test")
self.grid = Gtk.Grid()
self.add(self.grid)
self.tree_model = Gtk.TreeStore(str)
self.populate_tree("/etc")
self.filter_model = self.tree_model.filter_new()
self.filter_model.set_visible_func(self.apply_filter)
self.tree_view = Gtk.TreeView.new_with_model(self.filter_model)
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Filename", renderer, text=0)
self.tree_view.append_column(column)
self.tree_view.expand_all()
self.scrolled_window = Gtk.ScrolledWindow()
self.scrolled_window.set_vexpand(True)
self.scrolled_window.set_hexpand(True)
self.scrolled_window.add(self.tree_view)
self.grid.attach(self.scrolled_window, 0, 0, 2, 1)
self.filter_entry = Gtk.Entry()
self.filter_entry.set_hexpand(True)
self.grid.attach(self.filter_entry, 0, 1, 1, 1)
self.filter_button = Gtk.Button("Filter")
self.filter_button.connect("clicked", self.set_filter)
self.grid.attach(self.filter_button, 1, 1, 1, 1)
self.show_all()
def populate_tree(self, pathname, parent=None):
tree_iter = self.tree_model.append(parent, [os.path.basename(pathname)])
try: entries = os.listdir(pathname)
except OSError: return
for entry in entries:
if os.path.isdir(os.path.join(pathname, entry)):
self.populate_tree(os.path.join(pathname, entry), tree_iter)
elif os.path.isfile(os.path.join(pathname, entry)):
self.tree_model.append(tree_iter, [entry])
def set_filter(self, button):
self.filter = self.filter_entry.get_text()
if not self.filter:
self.regexp = None
else:
self.regexp = re.compile(self.filter)
self.filter_model.refilter()
def apply_filter(self, model, iter, data):
visible = True
#if self.regexp:
# if not self.regexp.search(model[iter][0]):
# visible = False
if self.filter and self.filter not in model[iter][0]:
visible = False
print("%s -> %u" % (model[iter][0], visible))
return visible
win = MainWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
Additional discussion on the GNOME Discourse: https://discourse.gnome.org/t/using-gtktreemodelfilter-properly/2170/3