Gtk 3 position attribute on insert-text signal from Gtk.Entry is always 0
Asked Answered
R

1

7

I am having trouble in managing the insert-text signal emitted by the Gtk.Entry widget. Consider the following example:

from gi.repository import Gtk

def on_insert_text(entry, new_text, new_text_length, position):
    print(position)

entry = Gtk.Entry()
entry.connect('insert-text', on_insert_text)

window = Gtk.Window()
window.connect("destroy", lambda q: Gtk.main_quit())
window.add(entry)
window.show_all()
Gtk.main()

The position attribute I am receiving on the signal handler is always 0. Unless I am misunderstanding this should it not be the position where the next text should be inserted?

In the end what I want to do is to validate the entry of text in the widget to restrict the characters that will be accepted. The way I plan to do this is similar to the example provided in the documentation in which all characters are transformed to uppercase.

Raquel answered 7/8, 2016 at 15:31 Comment(5)
Just ran your code I also get that issue, but if you really need the position just use entry.get_position(). To which example in the documentation are you referring by the way?Kristofer
In the class description of Gtk.Editable there is a reference to the "insert-text" signal and an example of a possible handler for that signal. In the docs the text is converted to lowercase, in my case I would be checking it against a regular expression and decide whether to insert or not but the structure of the handler would exactly the one shown there. By the way, entry.get_posisition() does the job I think so I will probably use that.Raquel
I think that the behaviour we are seeing may be due to the python bindings not handling everything correctly. When you ran my sample code above did you also get the following warning? Warning: g_value_get_int: assertion 'G_VALUE_HOLDS_INT (value)' failed Gtk.main()Raquel
I also get that warning the first time I run it, I guess you are correct that it is a bug in the python binding, so it might be a good idea to report it :)Kristofer
I ran into a further issue with return values and found the underlying bug in pygobject. See the answer I posted below alongside a possible solution. Thanks for your help.Raquel
R
11

The handler of 'insert-text' is expected to update the value received in the position parameter (which we have seen in incorrect) to reflect the position from which future text should be inserted and return it. This is important so the cursor is changed to the right place after the signal handler returns (this is done by gtk). If you don't update and return then the cursor remains at position 0.

After following the suggestion of using entry.get_position() to obtain the right position value I found out that the update and return of position in my handler was being ignored by pygobject. It behaved as if I was not returning anything (the cursor remained at position 0). Setting the position inside the handler did not help, because gtk would change it back again to 0 after the handler returned.

After some further investigation I learned that the issue lies with the handling of in/out parameters in pygobject which works well in most cases but not with signals (see bug 644927)

If you use connect to attach a handler to the signal and the signal has an in/out parameter you may not receive what you expect in the handler and even if you return a value this value will probably not be handled correctly by pygobject either. Anything that depends on that value will probably not work as expected (e.g. advance the cursor to the new position)

There is a solution though which is to override the associated vfunc (the default handler) instead of connecting with connect(). This solution implies deriving from the base class but it does work.

You can use this method for input validation/transformation on Gtk.Entry. An example handling my use case would be:

import re
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk


class MyEntry(Gtk.Entry, Gtk.Editable):

    def __init__(self):
        super(MyEntry, self).__init__()

    def do_insert_text(self, new_text, length, position):
        regexp = re.compile('^(\d*\.?\d*)$')

        if new_text == '.' and '.' in self.get_text():
            return position
        elif regexp.match(new_text) is not None:
            self.get_buffer().insert_text(position, new_text, length)
            return position + length

        return position

entry = MyEntry()
window = Gtk.Window()
window.connect("destroy", lambda q: Gtk.main_quit())
window.add(entry)
window.show_all()

Gtk.main()

In this case the position parameter is received correctly and the return value is seen and used by pygobject so the cursor is correctly positioned.

Important Note You have to inherit from Gtk.Editable in addition to Gtk.Entry. If you do not do so you will start seeing the validation or whatever you do inside do_insert_text applying to every other Gtk.Entry in your application. If you do not inherit you are overriding the base implementation provided by Gtk.Editable which is called by all other Gtk.Entry widgets in your application. By inheriting from Gtk.Editable you override only the 'local' copy of the base implementation which only applies to your custom class.

Raquel answered 8/8, 2016 at 14:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.