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/

Reply via email to