Thursday, December 17, 2009

Quidgets in Action: Custom Filters

I started the Quidgets project out of my frustrations with PyGtk while I was working on this bughugger tool, which a few of us in Ubuntu have been working on to help us streamline our bug triaging on the Ubuntu Desktop a bit.

We use a DictionaryGrid to display, sort, and filter bug data. In order to support correct sorting of status and importance, Brian Murray came up with a numbering scheme. However, what does status of "2" mean? We needed some text in there to also tell us what the numbers meant. But you can see the problem. Since the status and importance columns have to be typed to strings to support the extra text, the GridFilter's filter for numbers couldn't be used, but the GridFilter's string filter doesn't support < (less than), = (equals), > (greater than), etc..., which would obviously be useful.

Fortunately, GridFilter takes an optional argument in the constructor called "filter_hints" so you can tell the GridFilter what filter to use for a specified key. However, as noted above, the number filter won't work, and the string filter won't work. But it's easy to build a custom filter.

A filter actually derives from a combo box. It associates text to select in the combo box with a function that compares what is in a cell of a DictionaryGrid with text that user inputs, x and y.

Using lambda functions, I can write some quite terse code to provide the functions:
class StartsWithNumberFilterCombo(gtk.ComboBox):
def __init__(self):

combo_store = gtk.ListStore(gobject.TYPE_STRING,gobject.TYPE_PYOBJECT)
combo_store.append(["=",lambda x,y: convert_to_num(x) == float(y) ])
combo_store.append(["<",lambda x,y: convert_to_num(x) < float(y) ])
combo_store.append([">",lambda x,y: convert_to_num(x) > float(y) ])
combo_store.append(["<=",lambda x,y: convert_to_num(x) <= float(y) ])
combo_store.append([">=",lambda x,y: convert_to_num(x) >= float(y) ])
gtk.ComboBox.__init__( self, combo_store)
cell = gtk.CellRendererText()
self.pack_start(cell, True)
self.add_attribute(cell, 'text', 0)

def convert_to_num(string):
return float(string.split(" ")[0])
Note that if I need to do more complex transformations and/or comparisons, I could have used actually functions and passed the names of those functions in instead of using lambda functions.

Anyway, then I simply set up the filter hint when I create the GridFilter:
filter_hints = {"gravity":NumericFilterCombo(),"effected users":NumericFilterCombo(),
"status":StartsWithNumberFilterCombo(),"importance":StartsWithNumberFilterCombo()}
grid_filt = GridFilter(self.grid,filter_hints)
Presto-chango, I have a proper and custom filter for my DictionaryGrid, importance and status can be filtered numerically:

I think I shall simplify the creation of a custom filter somewhat by creating a FilterCombo class that you can derive from or that you can use stock and just stuff pairs of strings and functions into. Still, as is, this dramatically reduced the amount of time it took me to set up filters for the DictionaryGrid compared to if I used a stock Gtk TreeView.


About Quidgets
Quickly + Widgets = Quidgets
There is a Launchpad Project for Quidgets
The most up to date changes are in the Quidgets Trunk Branch
You can install Quidgets from the my PPA

2 comments: