Ok, this could be a weird one (or maybe not, I haven't been reading this list long enough to know what's weird around here ;)
I am trying to implement a custom TreeCellRenderer which displays text, and when edited brings up a combo box subclass that I've written in Python. This combo box does auto-completion from a set of values pulled from a database & stored in a gtk.ListStore. Now, I have the cell renderer working happily (editing supplied by a gtk.Entry), and the DBCombo working happily too. The problem seems to be convincing the TreeView widget that it would like to use the DBCombo as the editing widget. Buried deep in the Gtk code (gtktreeviewcolumn.c, line 2606) is the following bit of code g_return_val_if_fail (GTK_IS_CELL_EDITABLE (*editable_widget), FALSE); which is on the code path that is followed when you ask to edit a TreeView's Cell. That code basically means that I can't just implement the interface in python, and have the calls be made with no one the wiser. I tried to declare my DBCombo like this: class DBCombo(gtk.Combo, gtk.CellEditable) but python complains: Traceback (most recent call last): File "widgets.py", line 11, in ? class DBCombo(gtk.Combo, gtk.CellEditable): TypeError: multiple bases have instance lay-out conflict So, I went and read the C code for GtkEntry to see how "officially" adding the interface CellEditable to a GType is done. Turns out that the call is: g_type_add_interface_static(...) There's unfortunately no corresponding function in PyGtk. I'm at a loss. How should I go about adding the CellEditable interface to my little widget? I suppose I could write some C code that does the correct magic, then wrap that C code myself, but... It's not like I don't already have C code to compile in my project (BTW, I have a very primitive GtkHTML 3 wrapper if anyone should want it). Anyway, I'm rambling now. I'll stop. I've attached the code in question to this message. Astute readers of the list archives may recognise the origination of the MyCellRenderer code that I copied (and mangled) from http://www.mail-archive.com/[EMAIL PROTECTED]/msg05242.html cheers, Austin Henry -- The only exercise some people get is jumping to conclusions, running down their friends, side-stepping responsibility, and pushing their luck.
"""A little (maybe) module for the widgets I've written myself""" import pygtk pygtk.require("2.0") import gtk from gtk import TRUE, FALSE import gnome.canvas import gobject class DBCombo(gtk.Combo, gtk.CellEditable): """ For now, this is a simple combo box widget that pulls its data from a gtk.ListStore. The ListStore should have one column of string type. Eventually, I'd like to do autocompletion while you type, and have signals for when an item which wasn't previously in the list is selected when focus leaves the widget. All in good time, however. """ def __init__(self, listStore = None, column = -1): self.__gobject_init__() self.initialize(listStore, column) def initialize(self, listStore = None, column = -1): self.handler_ids = {} # copy of the model data for use with autocompletion self.completion_list = [] # stuff for handling inserts on a slow link self.insert_pending = 0 self.inserts = [] if listStore: self.set_model(listStore, column) # callbacks for typing self.handler_ids['insert-text'] = \ self.entry.connect('insert-text', self.on_text_insert) def set_model(self, listStore, column): assert listStore assert column is not -1 self.model = listStore self.model_column = column # connect to the model changed signals here & do stuff based on them. self.handler_ids['rows_reloaded'] = \ self.model.connect("rows_reloaded", self.on_rows_reloaded) self.populate_strings() def populate_strings(self): # populate popdown_strings list from the model list = [] self.model.foreach( lambda model, path, iter: list.append(model.get_value(iter, self.model_column))) # for now, do the extremely naieve completion list thing self.completion_list = list # ensure that the text in the entry doesn't change when we repopulate # the list of values for the dropdown. text = self.entry.get_text() self.set_popdown_strings(self.completion_list) # set the list self.entry.set_text(text) def on_rows_reloaded(self, *args): self.populate_strings() def on_text_insert(self, entry, text, length, *args): entry.stop_emission('insert-text') self.inserts.append((text, entry.get_position())) # batch inserts to minimise the effects of a very slow remote link if not self.insert_pending: self.insert_pending = 1 gtk.idle_add(lambda: self._insert_text(entry)) def _insert_text(self, entry): self.insert_pending = 0 entry.handler_block(self.handler_ids['insert-text']) for ins in self.inserts: text, pos = ins # replacing the previous line with the following two (usually) does # the right thing for text entry when on a really slow remote # link. God only knows how often this program'll be used that way #text = ins[0] #pos = entry.get_position() entry.insert_text(text, pos) entry.set_position(pos + len(text)) self.inserts = [] # do autocompletion stuff outside the loop # XXX really terrible algorithm. probably the worst possible text = entry.get_text().upper() complete = '' for i in self.completion_list: pos = i.upper().find(text) if pos == 0: complete = i[len(text):] break if complete: entry.insert_text(complete, len(text)) entry.select_region(len(text), len(text) + len(complete)) entry.handler_unblock(self.handler_ids['insert-text']) return gtk.FALSE class MyCellRenderer(gtk.GenericCellRenderer): property_names = ('text',) __gproperties__ = { 'text': (gobject.TYPE_STRING, 'text', 'text displayed by the cell', '', gobject.PARAM_READWRITE), } __gsignals__ = { 'value-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_STRING)) } def __init__(self): self.__gobject_init__() self.set_property('mode', gtk.CELL_RENDERER_MODE_EDITABLE) self.xpad = 2; self.ypad = 2 self.xalign = 0.0; self.yalign = 0.5 def __getattr__(self, name): try: return self.get_property(name) except TypeError: raise AttributeError def __setattr__(self, name, value): """The implication of this function is that you can not shadow a gtk property this class might posess with a normal python instance variable""" try: self.set_property(name, value) except TypeError: self.__dict__[name] = value def do_get_property(self, property): if property.name not in self.property_names: raise TypeError('No property named %s' % (property.name,)) return self.__dict__[property.name] def do_set_property(self, property, value): if property.name not in self.property_names: raise TypeError('No property named %s' % (property.name,)) self.__dict__[property.name] = value def on_render(self, window, widget, background_area, cell_area, expose_area, flags): width, height, x_offset, y_offset = self.on_get_size(widget, cell_area) if width <= 0 or height <= 0: return layout = widget.create_pango_layout(self.text) if (flags & gtk.CELL_RENDERER_SELECTED) == gtk.CELL_RENDERER_SELECTED: if widget.get_property('has-focus'): state = gtk.STATE_SELECTED else: state = gtk.STATE_ACTIVE else: state = gtk.STATE_NORMAL widget.style.paint_layout(window, state, TRUE, cell_area, widget, "cellradio", cell_area.x + x_offset + self.xpad, cell_area.y + y_offset + self.ypad, layout) def on_get_size(self, widget, cell_area): layout = widget.create_pango_layout(self.text) rect = layout.get_pixel_extents() calc_width = (self.xpad * 2) + rect[1][2] calc_height = (self.ypad * 2) + rect[1][3] if cell_area: x_offset = self.xalign * (cell_area.width - calc_width) x_offset = max(x_offset, 0) y_offset = self.yalign * (cell_area.height - calc_height) y_offset = max(y_offset, 0) else: x_offset = 0 y_offset = 0 return calc_width, calc_height, x_offset, y_offset def on_start_editing(self, event, widget, path, background_area, cell_area, flags): print "start editing" self.edit_widget = DBCombo() # haven't bothered to implement the signal in DBCombo yet... # self.edit_widget.connect('editing-done', self.on_editing_done, path) self.edit_widget.show() return self.edit_widget def on_editing_done(self, widget, path): print "editing-done", path self.emit('value-changed', widget.get_text(), path) return gtk.TRUE def on_remove_widget(self, widget): print 'remove-widget' del self.edit_widget return gtk.TRUE class MenuCalendar(gnome.canvas.Canvas): def __init__(self): gnome.canvas.Canvas.__init__(self) self.initialize() def initialize(self): self.root_group = self.root() self.root_group.add('GnomeCanvasRect', x1 = 20, y1 = 20, x2 = 40, y2 = 40, fill_color = 'red', outline_color = 'black', width_units = 1) # register all of the types listed below. Maybe search through the __dict__ # and register all of the subclasses of GtkWidget... Later. widgets = (DBCombo, MenuCalendar, MyCellRenderer) map(gobject.type_register, widgets) # creates a GType (I think) called __widgets__+DBCombo, which can be used in # the .glade file to construct it automatically. Unfortunately, the # constructor for the python class doesn't get called... if __name__ == '__main__': class Tree(gtk.TreeView): def __init__(self): self.store = gtk.ListStore(str, str) gtk.TreeView.__init__(self) self.set_size_request(300, 200) self.set_model(self.store) self.set_headers_visible(TRUE) rend = gtk.CellRendererText() column = gtk.TreeViewColumn('First', rend, text=0) self.append_column(column) rend = MyCellRenderer() rend.connect('value-changed', self.on_value_changed) column = gtk.TreeViewColumn('Second', rend, text=1) self.append_column(column) def insert(self, name): iter = self.store.append() self.store.set(iter, 0, name, 1, 'hi') # def callback(self, *args): # print args def on_value_changed(self, toggle, value, row): iter = self.store.get_iter(str(row)) self.store.set(iter, 1, value) return gtk.TRUE w = gtk.Window() w.set_position(gtk.WIN_POS_CENTER) w.connect('delete-event', gtk.mainquit) t = Tree() t.insert('foo') t.insert('bar') t.insert('baz') w.add(t) w.show_all() gtk.main()
_______________________________________________ pygtk mailing list [EMAIL PROTECTED] http://www.daa.com.au/mailman/listinfo/pygtk Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/