1

I have Python3/GTK3 application with a (preferences) Dialog containing a Notebook with multiple Pages (tabs). On some of the Pages are TextEntrys along with other Widgets.

When I click into a TextEntry on one Page then switch to another Page which also contains a TextEntry, any text in the (new Page) TextEntry is highlighted. If I accidentally hit the space bar, I'll wipe out that text. To undo, I have to cancel (close) the dialog. This is unacceptable if the user has made changes in other parts of the dialog as those changes are now lost.

A representative sample script is below. I have commented out various calls to grab the focus which I have tried, but none work. I am using the Box for layout, yet in my application I use a Grid and the problem persists, so I suspect the layout container is not a culprit.

#!/usr/bin/env python3


import gi
gi.require_version( "Gtk", "3.0" )
from gi.repository import Gtk


def addPageToNotebook( notebook, text ):
    box = Gtk.Box()

    box.pack_start( Gtk.Label.new( "Label " + text ), False, False, 0 )

    textEntry = Gtk.Entry()
    textEntry.set_text( text )
    # textEntry.set_receives_default( False )
    box.pack_start( textEntry, True, True, 0 )

    button = Gtk.Button.new_with_label( "Button" + text )
    # button.set_receives_default( True )
    box.pack_start( button, False, False, 0 )

    notebook.append_page( box, Gtk.Label.new( "Tab " + text ) )
    return textEntry, button


def onSwitchPage( notebook, page, pageNumber, textEntry1, button1, textEntry2, button2 ):
    # button1.grab_focus()
    # button2.grab_focus()
    # textEntry1.grab_focus_without_selecting()
    # textEntry2.grab_focus_without_selecting()
    # notebook.get_tab_label( page ).grab_focus()
    pass


notebook = Gtk.Notebook()
textEntry1, button1 = addPageToNotebook( notebook, "1" )
textEntry2, button2 = addPageToNotebook( notebook, "2" )
notebook.connect( "switch-page", onSwitchPage, textEntry1, button1, textEntry2, button2 )

window = Gtk.Window()
window.connect( "destroy", Gtk.main_quit )
window.add( notebook )
window.show_all()
Gtk.main()

There are other posts about this situation yet nobody seems to have found a solution:

Anyone with ideas please?

Edit: Have logged an issue with GNOME.

Bernmeister
  • 267
  • 9
  • 17

2 Answers2

1

If you want to remove focus from an entry, you should use grab_remove() instead of grab_focus(). So in your example:

def onSwitchPage( notebook, page, pageNumber, textEntry1, button1, textEntry2, button2 ):
    # button1.grab_focus()
    # button2.grab_focus()
    textEntry1.grab_remove()
    textEntry2.grab_remove()
    # notebook.get_tab_label( page ).grab_focus()
    pass

EDIT:

You're right, the above didn't work either. It seems like there should be a way to remove focus, or prevent the Gtk.Entry from grabbing it in the first place, but you would probably need to subclass it and heavily edit it. It seems like the next best thing is to just "tab" the focus out of the entry. To do so you have to create a KEY_PRESS event, and fire it manually.

def onSwitchPage( notebook, page, pageNumber, textEntry1, button1, textEntry2, button2 ):
    focus_widget = notebook.get_toplevel().get_focus()
    if focus_widget in [textEntry1, textEntry2, button1, button2]:
        display = Gdk.Display().get_default()
        seat = display.get_default_seat()
        keyboard = seat.get_keyboard()
        keymap = Gdk.Keymap().get_for_display(display)
        key = keymap.get_entries_for_keyval(Gdk.KEY_Tab)
        event = Gdk.Event().new(Gdk.EventType.KEY_PRESS)
        event.hardware_keycode = key[1][0].keycode
        event.window = notebook.get_window()
        event.set_device(keyboard)
        #Everything commented out isn't necessary for the event to work on my end.
        # event.keyval = Gdk.KEY_Tab
        # event.time = Gdk.CURRENT_TIME
        # event.send_event = True
        # event.type = Gdk.EventType.KEY_PRESS
        # event.length = 0
        # event.is_modifier = 0
        # event.state = 0
        # event.string = ""
        # event.group = key[1][0].group
        #Do it twice so focus doesn't land on button either.
        for i in range(2):
            display.put_event(event)
        #Tried to get rid of the highlight but this didn't help.
        # textEntry1.select_region(0, 0)
        # textEntry2.select_region(0, 0) 

EDIT2:

Unfortunately, this still leaves the entry text highlighted, but that's ok, the event still works. Also it gives the following warning for me:

Gdk-WARNING **: Event with type 8 not holding a GdkDevice. It is most likely synthesized outside Gdk/GTK+

This link you provided https://discourse.gnome.org/t/generating-gdk-gtk-keyboard-events-programmatically/11020 helped me get rid of the warning above. Should've been obvious that you have to use set_device() on the event.

abinitio
  • 96
  • 5
  • Thanks for the suggestion; gave it a try and unfortunately if you press button1, then switch to tab2, textentry2 now has the focus (and text within is highlighted). – Bernmeister Sep 19 '22 at 23:06
  • @Bernmeister thanks for checking it out after so long. I didn't expect you to still be watching this. I edited the answer with an actual solution this time. The text stays highlighted and there might be a way to remove that, but it doesn't have the input focus so it solves the problem you were having. – abinitio Sep 20 '22 at 16:11
  • Just realized it sounds like you have multiple entries in your grid, so maybe this is still not a perfect solution, but you can still use the same principle and try to tab out of whatever entry you find yourself in. – abinitio Sep 20 '22 at 16:38
  • Thanks; tried your latest code; still the same issue happens. I did find a couple of links which describe a similar idea to simulate a key press, https://stackoverflow.com/questions/31307427/sending-fake-event-to-gtkentry and https://discourse.gnome.org/t/generating-gdk-gtk-keyboard-events-programmatically/11020 but still does not work. – Bernmeister Sep 22 '22 at 07:48
  • Interesting. Yeah, those links are synthesizing the event just like I did. Did you get an error, does it give you the same warning as mine but just doesn't actually tab out? As I mentioned, this leaves the entry text highlighted but it doesn't have input focus so key presses don't change the entry text. – abinitio Sep 22 '22 at 14:15
  • I gave your revised code another try and it works! Dunno what I did last time around; late night or copy/paste silliness. Anyway, thank you for your effort; it's a good enough workaround for me! – Bernmeister Sep 26 '22 at 00:09
0

I have come up with an alternate solution, based on the comment to use a thread to change the TextEntry. In summary, when the tab/page is switched, I give focus to the tab.

#!/usr/bin/env python3


import gi
gi.require_version( "Gtk", "3.0" )
from gi.repository import GLib, Gtk


def addPageToNotebook( notebook, text ):
    box = Gtk.Box()

    box.pack_start( Gtk.Label.new( "Label " + text ), False, False, 0 )

    textEntry = Gtk.Entry()
    textEntry.set_text( text )
    box.pack_start( textEntry, True, True, 0 )

    button = Gtk.Button.new_with_label( "Button" + text )
    box.pack_start( button, False, False, 0 )

    notebook.append_page( box, Gtk.Label.new( "Tab " + text ) )
    return textEntry, button


def setFocusOnTab( notebook, pageNumber ):
#    notebook.get_tab_label( notebook.get_nth_page( pageNumber ) ).get_parent().grab_focus() # This is overkill...instead should be
    notebook.grab_focus()
    return False


def onSwitchPage( notebook, page, pageNumber ):
    GLib.idle_add( setFocusOnTab, notebook, pageNumber )


notebook = Gtk.Notebook()
textEntry1, button1 = addPageToNotebook( notebook, "1" )
textEntry2, button2 = addPageToNotebook( notebook, "2" )
notebook.connect( "switch-page", onSwitchPage )

window = Gtk.Window()
window.add( notebook )
window.show_all()
window.connect( "destroy", Gtk.main_quit )
Gtk.main()
Bernmeister
  • 267
  • 9
  • 17
  • Nice! It's true, `idle_add` can help in a lot of situations where you want to update things after they are mapped. – abinitio Sep 26 '22 at 14:16