wrkyle wrkyle - 3 months ago 28
Python Question

GTK/Python: How to get key-press-event to edit and navigate a TreeView cell?

I've connected to the "key-press-event" to navigate through a Gtk.TreeView. I successfully got Tab to navigate to the right (across a row). I'm having trouble with using Return to navigate downwards. When a cell is selected I can edit its contents but I have to press Return once to commit the value and again to navigate to the cell below. I would like the behaviour to be like the Tab where I press Return once and the change is committed and the selected cell moves down one. I'm gunning for behaviour like that of a spreadsheet.

I'm guessing there is a conflict here with key bindings, i.e., the first Return press event commits the changes and the second Return press event navigates downwards. Also, I tried connecting the Shift_L key (instead of Return) to navigate downwards and while it navigates with one press it also fails to commit the changes to the cell.

I'll whip up a MWE if need be but I figure someone out there might know the issue here and could point me in the right direction or educate me.

EDIT: Okay, I took the time and stripped everything down to a MWE that can be tested/eyeballed by anyone able to help. The pertinent section of code is the callback function named onTreeNavigateKeyPress. Within that, the troublesome condition is the elif keyname == 'Return'. If you run this on your machine you'll see that you can change the cell value and Tab to the right and the Tab both navigates rightward and commits the changed value to the cell. Doing the same with the Return key will commit the change but you'll need to press Return again to navigate downwards. Call me pedantic but I hate that. As you can see from the code I have tried calling the Gtk.TreeView.set_cursor method directly with the new location as well as calling it from a new thread using Glib.timeout_add.

#!/usr/bin/env python3`

from gi.repository import Gtk, Gdk, GLib


class linFitApp(Gtk.Window):

def __init__(self):

Gtk.Window.__init__(self, title='Testing Keypress Events on Treeview')
self.set_position(Gtk.WindowPosition.CENTER)
self.set_default_size(400, 300)
self.set_border_width(5)

self.mainBox = Gtk.Box()
self.scrollTableWindow = Gtk.ScrolledWindow()
self.scrollTableWindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
self.mainBox.pack_start(self.scrollTableWindow, False, False, 0)
self.add(self.mainBox)

############################################################################


#Now to set up the data table
self.dataTableListStore = Gtk.ListStore(float, float, float, float)
self.dataTableTreeView = Gtk.TreeView(model=self.dataTableListStore)
self.dataTableTreeView.props.activate_on_single_click = True
self.dataTableTreeView.connect("key-press-event", self.onTreeNavigateKeyPress)

#set up the x column
self.xColumnTextRenderer = Gtk.CellRendererText()
self.xColumnTextRenderer.set_property("editable", True)
self.xColumnTextRenderer.connect("edited", self.onXChanged)
self.xColumnTreeView = Gtk.TreeViewColumn("x", self.xColumnTextRenderer, text=0)

#set up the y column
self.yColumnTextRenderer = Gtk.CellRendererText()
self.yColumnTextRenderer.set_property("editable", True)
self.yColumnTextRenderer.connect("edited",self.onYChanged)
self.yColumnTreeView = Gtk.TreeViewColumn("y", self.yColumnTextRenderer, text=1)

#set up the dx column
self.dxColumnTextRenderer = Gtk.CellRendererText()
self.dxColumnTextRenderer.set_property("editable", True)
self.dxColumnTextRenderer.connect("edited",self.onDxChanged)
self.dxColumnTreeView = Gtk.TreeViewColumn("dx", self.dxColumnTextRenderer, text=2)

#set up the dy column
self.dyColumnTextRenderer = Gtk.CellRendererText()
self.dyColumnTextRenderer.set_property("editable", True)
self.dyColumnTextRenderer.connect("edited",self.onDyChanged)
self.dyColumnTreeView = Gtk.TreeViewColumn("dy", self.dyColumnTextRenderer, text=3)

#pack treeview into the scrolled window
self.scrollTableWindow.add(self.dataTableTreeView)

#add treeview columns to treeview
self.dataTableTreeView.append_column(self.xColumnTreeView)
self.dataTableTreeView.append_column(self.yColumnTreeView)
self.dataTableTreeView.append_column(self.dxColumnTreeView)
self.dataTableTreeView.append_column(self.dyColumnTreeView)

#fill in treeview with some sample data
self.dataTableListStore.append([0, 4, 0, 0])
self.dataTableListStore.append([5, 8.2, 0, 0])
self.dataTableListStore.append([10, 11.7, 0, 0])
self.dataTableListStore.append([15, 16.5, 0, 0])
self.dataTableListStore.append([20, 19, 0, 0])
self.dataTableListStore.append([25, 24.5, 0, 0])
self.dataTableListStore.append([30, 26.2, 0, 0])



#define the callbacks for cell editing
def onXChanged(self, widget, path, number):
self.dataTableListStore[path][0]=float(number.replace(',', '.'))

def onYChanged(self, widget, path, number):
self.dataTableListStore[path][1]=float(number.replace(',', '.'))

def onDxChanged(self, widget, path, number):
self.dataTableListStore[path][2]=float(number.replace(',', '.'))

def onDyChanged(self, widget, path, number):
self.dataTableListStore[path][3]=float(number.replace(',', '.'))

#define the callback for keypress events
def onTreeNavigateKeyPress(self, treeview, event):
keyname = Gdk.keyval_name(event.keyval)
path, col = treeview.get_cursor()
columns = [c for c in treeview.get_columns()]
colnum = columns.index(col)

if keyname == 'Tab':

if colnum + 1 < len(columns):
next_column = columns[colnum + 1]
else:
next_column = columns[0]
GLib.timeout_add(50,
treeview.set_cursor,
path, next_column, True)


elif keyname == 'Return':

model = treeview.get_model()
#Check if currently in last row of Treeview
if path.get_indices()[0] + 1 == len(model):
path = treeview.get_path_at_pos(0,0)[0]
#treeview.set_cursor(path, columns[colnum], True)
GLib.timeout_add(50,
treeview.set_cursor,
path, columns[colnum], True)
else:
path.next()
#treeview.set_cursor(path, columns[colnum], True)
GLib.timeout_add(50,
treeview.set_cursor,
path, columns[colnum], True)
else:
pass


#create main application window and start Gtk loop
mainWindow = linFitApp()
mainWindow.connect("delete-event", Gtk.main_quit)
mainWindow.show_all()
Gtk.main()

Answer

The answer (maybe just a functional hack) is as follows:

Pressing Return while a Gtk.Treeview cell is in editing mode does not release the key-press-event signal. Instead of connecting my callback function to the key-press-event signal I connect it to the key-release-event signal which is emitted after Return is pressed while a cell is being edited. So pressing the Return key will activate whichever signal it does to commit a new value (I still don't know which signal that is) and releasing the Return key will activate the navigation to the next cell. Voila, we have the two desired actions resulting from a single press of the Return key.

Short answer:

Change this

self.dataTableTreeView.connect("key-press-event", self.onTreeNavigateKeyPress)

to this

self.dataTableTreeView.connect("key-release-event", self.onTreeNavigateKeyPress)