Hi everybody, I have seen the recent discussion in the list about implementing custom CellRenderers in Python and some guys (Austin Henry) found it difficult because you cannot (yet) implement a C interface with Python.
I have succesfully used a gtk.Combo inside a Cell in a gtk.TreeView so I'm going to share my code with the list members just in case anyone find it useful. Before even reading the code let me warn you about some important points: - This code has some nasty hacks and they should be replaced with proper pygtk code when c interfaces become accesible from Python. - It's far from being bug free, I just want to share the technique I'm using for possible feedback. Having said that, I will describe the two changes I have added to a regular gtk.TreeView for the curious: - Custom CellEditors: when the cursor is on a cell I draw a GridEditor on top of it. A GridEditor is nothing but a gtk.Window with a custom widget for editing purposes like a gtk.Entry, a gtk.Combo, a gtk.Toggle or anything that you want. It does not have to implement the CellEditable interface which is why you couldn't implement a custom CellEditor. - Auto editing mode: you don't have to press enter anymore to get into the edition state like in a regular gtk.TreeView. You just move through the grid with the tab key, up arrow, down arrow, page up and page down. Well this is not totally true: when the Grid is realized you are not in editing mode yet, so just for the first time, you have to press enter. As you have already read, i call my widget Grid and I hope you find it usefull. Comments are greatly apreciated Lorenzo Gil Sanchez
import pygtk pygtk.require('2.0') import gtk import gobject import pango class GridCellEditor(gtk.Window): def __init__(self, grid, columnIndex): gtk.Window.__init__(self) self.grid = grid self.columnIndex = columnIndex self.set_decorated(gtk.FALSE) self.editor = self.createEditor() self.add(self.editor) self.editor.grab_focus() self.path = None self.connect('delete-event', self.deleteEvent) def applyChanges(self): iter = self.grid.model.get_iter(self.path) self.grid.model.set_value(iter, self.columnIndex, self.getValue()) def createEditor(self): raise 'this is an abstract method' def deleteEvent(self, widget, event, data=None): self.grid.get_toplevel().destroy() def focusOutEvent(self, widget, data=None): self.hide() def getValue(self): raise 'this is an abstract method' def keyPressEvent(self, widget, event, data=None): if event.keyval in [gtk.keysyms.Tab, gtk.keysyms.Down, gtk.keysyms.Up, gtk.keysyms.Page_Down, gtk.keysyms.Page_Up]: self.applyChanges() self.grid.keyPressCallback(widget, event) return gtk.TRUE return gtk.FALSE def setValue(self, value): raise 'this is an abstract method' def show(self, x, y, w, h, path): self.set_transient_for(self.grid.get_toplevel()) self.set_destroy_with_parent(gtk.TRUE) self.path = path iter = self.grid.model.get_iter(self.path) value = self.grid.model.get_value(iter, self.columnIndex) self.setValue(value) self.set_size_request(w, h) self.resize(w, h) self.show_all() self.present() self.move(x, y) class GridCellEditorText(GridCellEditor): def __init__(self, grid, columnIndex): GridCellEditor.__init__(self, grid, columnIndex) def createEditor(self): editor = gtk.Entry() editor.set_has_frame(gtk.FALSE) editor.connect('key-press-event', self.keyPressEvent) editor.connect('focus-out-event', self.focusOutEvent) return editor def getValue(self): return self.editor.get_text() def setValue(self, value): if value == None: value = '' self.editor.set_text(value) class GridCellEditorToggle(GridCellEditor): def __init__(self, grid, columnIndex): GridCellEditor.__init__(self, grid, columnIndex) def createEditor(self): editor = gtk.Alignment(xalign=0.5, yalign=0.5) button = gtk.CheckButton() button.connect('key-press-event', self.keyPressEvent) button.connect('focus-out-event', self.focusOutEvent) button.show() editor.add(button) return editor def getValue(self): return self.editor.get_child().get_active() def setValue(self, value): self.editor.get_child().set_active(value) class GridCellEditorCombo(GridCellEditor): def __init__(self, grid, columnIndex, values): self.values = values GridCellEditor.__init__(self, grid, columnIndex) def buttonPressEvent(self, widget, data=None): self.listOpened = gtk.TRUE def createEditor(self): combo = gtk.Combo() combo.set_popdown_strings(self.values) combo.set_use_arrows(gtk.FALSE) combo.entry.connect('key-press-event', self.keyPressEvent) combo.entry.connect('focus-out-event', self.focusOutEvent) button = combo.get_children()[-1] button.connect('pressed', self.buttonPressEvent) self.listOpened = gtk.FALSE combo.show() return combo def focusOutEvent(self, widget, data=None): if not self.listOpened: self.hide() def keyPressEvent(self, widget, event, data=None): self.listOpened = gtk.FALSE return GridCellEditor.keyPressEvent(self, widget, event, data) def getValue(self): return self.editor.entry.get_text() def setValue(self, value): self.editor.entry.set_text(value) class Grid(gtk.TreeView): def __init__(self, model): gtk.TreeView.__init__(self, model) self.model = model self.connect('key-press-event', self.keyPressCallback) self.connect('button-release-event', self.buttonReleaseCallback) self.set_rules_hint(gtk.TRUE) def cursorChangedCallback(self, widget, data=None): path, column = self.get_cursor() if path != None and column != None: self.updateEditor(path, column) def buttonReleaseCallback(self, widget, event, data=None): if event.button == 1: path, column, x, y = self.get_path_at_pos(event.x, event.y) self.setCursor(path, column) def keyPressCallback(self, widget, event, data=None): if event.keyval == gtk.keysyms.Tab: self.onKeyTab(event) elif event.keyval == gtk.keysyms.Down: self.onKeyDown(event) elif event.keyval == gtk.keysyms.Up: self.onKeyUp(event) elif event.keyval == gtk.keysyms.Page_Down: self.onKeyPageDown(event) elif event.keyval == gtk.keysyms.Page_Up: self.onKeyPageUp(event) elif event.keyval == gtk.keysyms.Return: self.cursorChangedCallback(None, None) return gtk.TRUE def onKeyDown(self, event): path, column = self.get_cursor() self.moveCursorToNextRow(path, column) def onKeyPageDown(self, event): path, column = self.get_cursor() self.moveCursorToNextPage(path, column) def onKeyPageUp(self, event): path, column = self.get_cursor() self.moveCursorToPrevPage(path, column) def onKeyUp(self, event): path, column = self.get_cursor() self.moveCursorToPrevRow(path, column) def onKeyTab(self, event): path, column = self.get_cursor() if event.state == gtk.gdk.SHIFT_MASK: self.moveCursorToPrevCell(path, column) else: self.moveCursorToNextCell(path, column) def setCursor(self, path, column): self.set_cursor(path, column) self.updateEditor(path, column) def updateEditor(self, path, column): cell_area = self.get_cell_area(path, column) bin = self.get_bin_window() x_bin, y_bin = bin.get_origin() columns = self.get_columns() index = columns.index(column) if self.editors[index] != None: self.editors[index].show(x_bin + cell_area.x, y_bin + cell_area.y, cell_area.width, cell_area.height, path) def moveCursorToNextCell(self, path, column): columns = self.get_columns() index = columns.index(column) + 1 if index == len(columns): index = 0 path = (path[0]+1,) try: self.model.get_iter(path) except: return column = columns[index] self.setCursor(path, column) def moveCursorToNextPage(self, path, column): rowHeight = column.cell_get_size()[4] gridHeight = self.allocation[3] - rowHeight visibleRows = gridHeight / rowHeight valid = 0 path = (path[0]+visibleRows,) while not valid: try: self.model.get_iter(path) valid = 1 except: path = (path[0]-1,) self.setCursor(path, column) def moveCursorToNextRow(self, path, column): path = (path[0]+1,) try: self.model.get_iter(path) except: path = (path[0]-1,) self.setCursor(path, column) def moveCursorToPrevCell(self, path, column): columns = self.get_columns() index = columns.index(column) - 1 if index == -1: index = len(columns) - 1 if path[0] == 0: self.get_toplevel().child_focus(gtk.DIR_TAB_BACKWARD) return else: path = (path[0]-1, ) column = columns[index] self.setCursor(path, column) def moveCursorToPrevPage(self, path, column): rowHeight = column.cell_get_size()[4] gridHeight = self.allocation[3] - rowHeight visibleRows = gridHeight / rowHeight path = (max(0,path[0]-visibleRows),) self.setCursor(path, column) def moveCursorToPrevRow(self, path, column): if path[0] > 0: path = (path[0]-1,) self.setCursor(path, column) def createColumns(self): self.editors = [] for i in range(self.model.get_n_columns()): title = self.model.getColumnTitle(i) type = self.model.get_column_type(i) if type == gobject.TYPE_BOOLEAN: renderer = gtk.CellRendererToggle() column = gtk.TreeViewColumn(title, renderer, active=i) self.editors.append(GridCellEditorToggle(self, i)) elif type == gobject.TYPE_PYOBJECT: renderer = gtk.CellRendererText() xalign = 1.0 renderer.set_property('xalign', xalign) column = gtk.TreeViewColumn(title, renderer, text=i) self.editors.append(GridCellEditorCombo(self, i, ['value 1','2','3'])) else: renderer = gtk.CellRendererText() if type in [gobject.TYPE_INT, gobject.TYPE_FLOAT]: xalign = 1.0 else: xalign = 0.0 renderer.set_property('xalign', xalign) column = gtk.TreeViewColumn(title, renderer, text=i) self.editors.append(GridCellEditorText(self, i)) column.set_alignment(0.5) column.set_resizable(gtk.TRUE) self.append_column(column) def setEditors(self, editors): self.editors = editors def test(): win = gtk.Window(gtk.WINDOW_TOPLEVEL) win.connect('destroy', lambda w: gtk.main_quit()) model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN, gobject.TYPE_STRING) for i in range(10): iter = model.append(('foo', gtk.FALSE, 'option%d'%(i%3))) grid = Grid(model) editors = [GridCellEditorText(grid, 0), GridCellEditorToggle(grid, 1), GridCellEditorCombo(grid, 2, ['option 0','option 1','option 2']) ] grid.setEditors(editors) renderer = gtk.CellRendererText() column = gtk.TreeViewColumn('Column 1', renderer, text=0) column.set_resizable(gtk.TRUE) grid.append_column(column) renderer = gtk.CellRendererToggle() column = gtk.TreeViewColumn('Column 2', renderer, active=1) column.set_resizable(gtk.TRUE) grid.append_column(column) renderer = gtk.CellRendererText() column = gtk.TreeViewColumn('Column 3', renderer, text=2) column.set_resizable(gtk.TRUE) grid.append_column(column) grid.show() win.add(grid) win.show() gtk.main() if __name__ == '__main__': test()
_______________________________________________ pygtk mailing list [EMAIL PROTECTED] http://www.daa.com.au/mailman/listinfo/pygtk Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/