Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Don't set a Gtk.TreeView's selection when focusing?

The following code displays a window with a button and tree view. A handle for the 'clicked' signal is attached to the button and focuses the tree view. When the window is initially displayed, the tree selection has no selected items, but when the tree view receives focus, the first item is automatically selected. Is there a way to keep a selection from being made when the tree view receives focus?

window before clicking   window after clicking
Before click, button has focus and tree selection has no selected items. After click, tree view has focus, but an item has been selected.

The issue that arises from this is that I have an interface that keeps some things in sync by attaching to the 'changed' signal on the tree selection of the tree view. When the window is displayed, depending on where the tree views are in the interface, they may receive focus by default. That causes a 'changed' signal, and unexpected synchronization happens. It's possible to call set_can_focus(False) for all the tree views, but that:

  1. only prevents keyboard cycling focus, not programmatic focus, and the selection still turns on with programmatic focus; and
  2. seems to disable the ability to deselect a selection (e.g., by control-clicking on a row).

Similarly I can use grab_default to ensure that something else gets focus first when the window is displayed, but it doesn't keep a stray focus event from making an unexpected selection.

Based on a posted answer that says that says that selection mode SINGLE "requires at least one item to be selected", and that that explains why an element is selected on focus, I looked more into the selection mode constants. Of these, SINGLE and BROWSE seem most relevant. The pygtk documentation, GTK Selection Mode Constants, only says that:

gtk.SELECTION_SINGLE A single selection allowed by clicking.
gtk.SELECTION_BROWSE A single selection allowed by browsing with the pointer.

The GTK+3 documentation, enum GtkSelectionMode, goes into a bit more detail:

GTK_SELECTION_SINGLE Zero or one element may be selected.
GTK_SELECTION_BROWSE Exactly one element is selected. In some circumstances, such as initially or during a search operation, it’s possible for no element to be selected with GTK_SELECTION_BROWSE. What is really enforced is that the user can’t deselect a currently selected element except by selecting another element.

I don't see anything here to suggest that at least one element must be selected when the selection mode is SINGLE.

Here's code to reproduce the window and serve as an example.

from gi.repository import Gtk

# A ListStore with some words
list_store = Gtk.ListStore(str)
for selection in "Can a machine think?".split():
    list_store.append([selection])

# A TreeView with a single column
tree_view = Gtk.TreeView(model=list_store)
cell_renderer = Gtk.CellRendererText()
tree_view_column = Gtk.TreeViewColumn(cell_renderer=cell_renderer,text=0,title='Words')
tree_view.append_column(tree_view_column)

# A button to focus the list
focus = Gtk.Button(label='Focus List')
focus.connect('clicked',lambda *_: tree_view.grab_focus())

# A Box to hold everything, and a Window for the Box.
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
box.add(focus)       # button on top gets initial focus
box.add(tree_view)   # tree_view below doesn't, and has no selected items 
window = Gtk.Window()
window.add(box)
window.show_all()

Gtk.main()
like image 584
Joshua Taylor Avatar asked Nov 09 '22 19:11

Joshua Taylor


1 Answers

Looking at the source in root/gtk/gtktreeview.c for tree_view.grab_focus(), we can see that gtk_tree_view_focus_to_cursor always gets called, and selects the first element. You can work around this, in some cases, though.

This is a nasty hack.

It overrides the grab_focus method, stores the selection before calling grab_focus, and clears the selection afterwards if there was no selection before.

def tree_view_grab_focus():
    selection = tree_view.get_selection()
    _, selected = selection.get_selected()
    Gtk.TreeView.grab_focus(tree_view)
    if selected is None:
        selection.unselect_all()

tree_view.grab_focus = tree_view_grab_focus

Unfortunately it only applies when calling grab_focus from Python, other callers (such as GTK's keyboard navigation) don't.

like image 157
Gary van der Merwe Avatar answered Nov 14 '22 22:11

Gary van der Merwe