Created
March 15, 2015 20:30
-
-
Save Cilyan/6bf798e493035b51592d to your computer and use it in GitHub Desktop.
This snippet illustrates the creation of a custom GtkTreeModel in PyGTK / Python 2 where column number is different between children and parents, and model generates fake headers in each cell
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pygtk | |
pygtk.require('2.0') | |
import gtk | |
data = [ | |
[('val_a1', 'val_b1', 'val_c1', 'val_d1', 'val_e1'), ('val_x1', 'val_y1', 'val_z1'), ('val_x2', 'val_y2', 'val_z2')], | |
[('val_a2', 'val_b2', 'val_c2', 'val_d2', 'val_e2'), ('val_x3', 'val_y3', 'val_z3')], | |
[('val_a3', 'val_b3', 'val_c3', 'val_d3', 'val_e3')], | |
[('val_a4', 'val_b4', 'val_c4', 'val_d4', 'val_e4'), ('val_x4', 'val_y4', 'val_z4'), ('val_x5', 'val_y5', 'val_z5')], | |
] | |
class MyTreeModel(gtk.GenericTreeModel): | |
# The columns exposed by the model to the view | |
column_types = (str, str, str, str, str) | |
# Column headers | |
parent_headers = ("P.Col 1", "P.Col 2", "P.Col 3", "P.Col 4", "P.Col 5") | |
child_headers = ("C.Col 1", "C.Col 2", "C.Col 3") | |
def __init__(self, data): | |
gtk.GenericTreeModel.__init__(self) | |
self.data = data | |
def on_get_flags(self): | |
""" | |
Get Model capabilities | |
""" | |
return gtk.TREE_MODEL_ITERS_PERSIST | |
def on_get_n_columns(self): | |
""" | |
Get number of columns in the model | |
""" | |
return len(self.column_types) | |
def on_get_column_type(self, n): | |
""" | |
Get data type of a specified column in the model | |
""" | |
return self.column_types[n] | |
def on_get_iter(self, path): | |
""" | |
Obtain a reference to the row at path. For us, this is a tuple that | |
contain the position of the row in the double list of data. | |
""" | |
if len(path) > 2: | |
return None # Invalid path | |
parent_idx = path[0] | |
if parent_idx >= len(self.data): | |
return None # Invalid path | |
first_level_list = self.data[parent_idx] | |
if len(path) == 1: | |
# Access the parent at index 0 in the first level list | |
return (parent_idx, 0) | |
else: | |
# Access a child, at index path[1] + 1 (0 is the parent) | |
child_idx = path[1] + 1 | |
if child_idx >= len(first_level_list): | |
return None # Invalid path | |
else: | |
return (parent_idx, child_idx) | |
def on_get_path(self, iter_): | |
""" | |
Get a path from a rowref (this is the inverse of on_get_iter) | |
""" | |
parent_idx, child_idx = iter_ | |
if child_idx == 0: | |
return (parent_idx, ) | |
else: | |
(parent_idx, child_idx-1) | |
def on_get_value(self, iter_, column): | |
""" | |
This is where the view asks for values. This is thus where we | |
start mapping our data model to a fake table to present to the view | |
""" | |
parent_idx, child_idx = iter_ | |
item = self.data[parent_idx][child_idx] | |
# For parents, map columns 1:1 to data | |
if child_idx == 0: | |
return self.markup(item[column], column, False) | |
# For children, we have to fake some columns | |
else: | |
if column == 0 or column == 4: | |
return "" # Fake empty text | |
else: | |
# map 1, 2, 3 to 0, 1, 2. | |
return self.markup(item[column-1], column-1, True) | |
def markup(self, text, column, is_child): | |
""" | |
Produce a markup for a cell with a title and a text | |
""" | |
headers = self.child_headers if is_child else self.parent_headers | |
title = headers[column] | |
return "<b>%s</b>\n%s"%(title, text) | |
def on_iter_next(self, iter_): | |
""" | |
Get the next sibling of the item pointed by iter_ | |
""" | |
parent_idx, child_idx = iter_ | |
# For parents, point to the next parent | |
if child_idx == 0: | |
next_parent_idx = parent_idx + 1 | |
if next_parent_idx < len(self.data): | |
return (next_parent_idx, 0) | |
else: | |
return None | |
# For children, get next tuple in the list | |
else: | |
next_child_idx = child_idx + 1 | |
if next_child_idx < len(self.data[parent_idx]): | |
return (parent_idx, next_child_idx) | |
else: | |
return None | |
def on_iter_has_child(self, iter_): | |
""" | |
Tells if the row referenced by iter_ has children | |
""" | |
parent_idx, child_idx = iter_ | |
if child_idx == 0 and len(self.data[parent_idx]) > 1: | |
return True | |
else: | |
return False | |
def on_iter_children(self, iter_): | |
""" | |
Return a row reference to the first child row of the row specified | |
by iter_. If iter_ is None, a reference to the first top level row | |
is returned. If there is no child row None is returned. | |
""" | |
if iter_ is None: | |
return (0, 0) | |
parent_idx, child_idx = iter_ | |
if self.on_iter_has_child(iter_): | |
return (parent_idx, 1) | |
else: | |
return None | |
def on_iter_n_children(self, iter_): | |
""" | |
Return the number of child rows that the row specified by iter_ | |
has. If iter_ is None, the number of top level rows is returned. | |
""" | |
if iter_ is None: | |
return len(self.data) | |
else: | |
parent_idx, child_idx = iter_ | |
if child_idx == 0: | |
return len(self.data[parent_idx]) - 1 | |
else: | |
return 0 | |
def on_iter_nth_child(self, iter_, n): | |
""" | |
Return a row reference to the nth child row of the row specified by | |
iter_. If iter_ is None, a reference to the nth top level row is | |
returned. | |
""" | |
if iter_ is None: | |
if n < len(self.data): | |
return (n, 0) | |
else: | |
return None | |
else: | |
parent_idx, child_idx = iter_ | |
if child_idx == 0: | |
if n+1 < len(self.data[parent_idx]): | |
return (parent_idx, n+1) | |
else: | |
return None | |
else: | |
return None | |
def on_iter_parent(self, iter_): | |
""" | |
Get a reference to the parent | |
""" | |
parent_idx, child_idx = iter_ | |
if child_idx == 0: | |
return None | |
else: | |
return (parent_idx, 0) | |
class BasicTreeViewExample: | |
def delete_event(self, widget, event, data=None): | |
gtk.main_quit() | |
return False | |
def __init__(self): | |
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) | |
self.window.set_title("Basic TreeView Example") | |
self.window.set_size_request(200, 200) | |
self.window.connect("delete_event", self.delete_event) | |
# Create the model with data in it | |
self.model = MyTreeModel(data) | |
self.treeview = gtk.TreeView(self.model) | |
self.treeview.set_headers_visible(False) | |
for i in range(5): | |
tvcolumn = gtk.TreeViewColumn('Column %s' % (i)) | |
self.treeview.append_column(tvcolumn) | |
cell = gtk.CellRendererText() | |
tvcolumn.pack_start(cell, True) | |
tvcolumn.add_attribute(cell, 'markup', i) | |
self.window.add(self.treeview) | |
self.window.show_all() | |
def main(): | |
gtk.main() | |
if __name__ == "__main__": | |
tvexample = BasicTreeViewExample() | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment