Search code examples
pythongtkpygtkgtktreeviewgtkentry

PyGTK Entry widget in TreeViewColumn header


How can I make a gtk.Entry widget focusable or editable within a gtk.TreeViewColumn header/title? I've tried this:

# Create tree-view.
treeview = gtk.TreeView()

#...

# Create column.
renderer = gtk.CellRendererText()
column = gtk.TreeViewColumn(None, renderer, text=0)

# Set column header.
header = gtk.VBox()

title = gtk.Label("Column")
header.pack_start(title)

filter = gtk.Entry()
#...
header.pack_start(filter)

header.show_all()
column.set_widget(header)

# Add column
treeview.append_column(column)

But the Entry widget in the column header is not editable and will not focus. I've tried setting 'clickable' to both True and False. I'm using pygtk 2.21.0-0ubuntu1 and libgtk 2.22.0-0ubuntu1 on Ubuntu 10.04. Any help would be greatly appreciated.

EDIT:

The issue stems from how a GtkTreeViewColumn header is displayed. The header widget is placed inside a GtkAlignment whose parent is a GtkHBox whose parent is a GtkButton whose parent is finally the GtkTreeView. The GtkButton is intercepting and preventing my GtkEntry from being focused and receiving mouse input.


Solution

  • In order to make a GtkEntry focusable within a GtkTreeView header I had to:

    1) Find the header GtkButton.

    def find_closest_ancestor(widget, ancestor_class):
        if not isinstance(widget, gtk.Widget):
            raise TypeError("%r is not a gtk.Widget" % widget)
        ancestor = widget.get_parent()
        while ancestor is not None:
            if isinstance(ancestor, ancestor_class):
                break;
            ancestor = ancestor.get_parent() if hasattr(ancestor, 'get_parent') and callable(ancestor.get_parent) else None
        return ancestor
    

    2) Propagate the button-press-event signal from the header GtkButton to the GtkEntry.

    def propagate_button_press_event(parent, event, *data):
        parent_alloc = parent.get_allocation()
        x = parent_alloc.x + int(event.x)
        y = parent_alloc.y + int(event.y)
        children = parent.get_children()
        print "Propagating event:%r" % event
        print "- from parent:%r" % parent
        while children:
            for child in children:
                child_alloc = child.get_allocation()
                if child_alloc.x <= x <= child_alloc.x + child_alloc.width and child_alloc.y <= y <= child_alloc.y + child_alloc.height:
                    print "- to child:%r" % child
                    if child.get_property('can-focus'):
                        event.send_event = True
                        child.grab_focus()
                        child.emit('button-press-event', event, *data)
                        return True
                    else:
                        children = child.get_children() if hasattr(child, 'get_children') and callable(child.get_children) else None
                        break;
            else:
                children = None
        return False
    

    3) Propagate the focus (i.e., focus-in-event signal) from the header GtkButton to the GtkEntry.

    def propagate_focus_in_event(parent, event, *data):
        print 'focus-in', parent, event
        child = parent.get_child()
        if child.get_property('can-focus'):
            child.grab_focus()
        else:
            if not child.child_focus(gtk.DIR_TAB_FORWARD):
                parent.get_toplevel().child_focus(gtk.DIR_TAB_FORWARD)
        return True
    

    Example:

    # Fix style glitches
    _gtk_styles = """
        # Use the default GtkEntry style for GtkEntry widgets in treeview headers.
        widget "*.treeview-header-entry" style "entry" 
    """
    gtk.rc_parse_string(_gtk_styles)
    
    # Columns
    _columns = [
        (0, "Title"),
        (1, "Description")
        # etc.
    ]
    
    # Create tree-view.
    items_view = gtk.TreeView(self.items_store)
    items_view.show()
    
    # Setup treeview columns.
    renderer = gtk.CellRendererText()
    for column in _columns:
        column_index, column_title, column_filter = column
        column_view = gtk.TreeViewColumn(None, renderer, text=column_index)
        column_view.set_clickable(True)
    
        column_widget = gtk.VBox()
        column_widget.show()
    
        column_align = gtk.Alignment(0, 0, 0, 0)
        column_align.show()
        column_widget.pack_start(column_align)
        column_label = gtk.Label(column_title)
        column_label.show()
        column_align.add(column_label)
    
        column_entry = gtk.Entry()
        column_entry.set_name('treeview-header-entry')
        column_entry.show()
        column_widget.pack_start(column_entry)
    
        column_view.set_widget(column_widget)
        items_view.append_column(column_view)
    
    # Setup column headers.
    columns = items_view.get_columns()
    for column in columns:
        column_widget = column.get_widget()
        column_header = find_closest_ancestor(column_widget, gtk.Button)
        if column_header:
            column_header.connect('focus-in-event', propagate_focus_in_event)
            column_header.connect('button-press-event', propagate_button_press_event)
            column_header.set_focus_on_click(False)