How to format the entries in Gtk.Entry
Asked Answered
T

1

10

For example, the telephone format is +999 99 9999-9999. That is, the GtkEntry automatically add the characters (+,[space] and -) as the user types.

Thant answered 16/10, 2016 at 20:13 Comment(1)
You could try having a validator. This old (2006) discussion can also be useful.Wigging
N
13

1. How to make an entry validator?

In order to do an entry validator in gtk, you need to connect the insert_text signal to a validation method. It goes like so:

class EntryWithValidation(Gtk.Entry):
    """A Gtk.Entry with validation code"""

    def __init__(self):
        Gtk.Entry.__init__(self)
        self.connect("insert_text", self.entryInsert)

    def entryInsert(self, entry, text, length, position):
        # Called when the user inserts some text, by typing or pasting.

        # The `position` argument is not working as expected in Python
        pos = entry.get_position() 

        # Your validation code goes here, outputs are new_text and new_position (cursor)

        if new_text:
            # Set the new text (and block the handler to avoid recursion).
            entry.handler_block_by_func(self.entryInsert)
            entry.set_text(new_text)
            entry.handler_unblock_by_func(self.entryInsert)

            # Can't modify the cursor position from within this handler,
            # so we add it to be done at the end of the main loop:
            GObject.idle_add(entry.set_position, new_pos)

        # We handled the signal so stop it from being processed further.
        entry.stop_emission("insert_text")

This code will generate a warning: Warning: g_value_get_int: assertion 'G_VALUE_HOLDS_INT (value)' failed Gtk.main() because of the incapacity of handling return arguments in the Python bindings of Gtk signals. This question gives details about the bug. As suggested in the accepted answer, you can override the default signal handler like so:

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

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

    def do_insert_text(self, new_text, length, position):

        # Your validation code goes here, outputs are new_text and new_position (cursor)

        if new_text:
            self.set_text(new_text)
            return new_position
        else:
            return position

2.The validation code

You now need to write the validation code. It is a bit fiddly since we need to place the cursor at the end of the inserted text but we may have added some extra characters while formatting.

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

class TelNumberEntry(Gtk.Entry):
    """A Gtk.Entry field for phone numbers"""

    def __init__(self):
        Gtk.Entry.__init__(self)
        self.connect("insert_text", self.entryInsert)

    def entryInsert(self, entry, text, length, position):

        pos = entry.get_position() 
        old_text = entry.get_text()

        # Format entry text

        # First we filter digits in insertion text
        ins_dig = ''.join([c for c in text if c.isdigit()]) 
        # Second we insert digits at pos, truncate extra-digits
        new_text = ''.join([old_text[:pos], ins_dig, old_text[pos:]])[:17] 
        # Third we filter digits in `new_text`, fill the rest with underscores
        new_dig = ''.join([c for c in new_text if c.isdigit()]).ljust(13, '_')
        # We are ready to format 
        new_text = '+{0} {1} {2}-{3}'.format(new_dig[:3], new_dig[3:5], 
                                               new_dig[5:9], new_dig[9:13]).split('_')[0] 

        # Find the new cursor position

        # We get the number of inserted digits
        n_dig_ins = len(ins_dig) 
        # We get the number of digits before
        n_dig_before = len([c for c in old_text[:pos] if c.isdigit()])
        # We get the unadjusted cursor position
        new_pos = pos + n_dig_ins

        # If there was no text in the entry, we added a '+' sign, therefore move cursor
        new_pos += 1 if not old_text else 0 
        # Spacers are before digits 4, 6 and 10
        for i in [4, 6, 10]:
            # Is there spacers in the inserted text?
            if n_dig_before < i <= n_dig_before + n_dig_ins: 
                # If so move cursor
                new_pos += 1

        if new_text:
            entry.handler_block_by_func(self.entryInsert)
            entry.set_text(new_text)
            entry.handler_unblock_by_func(self.entryInsert)

            GObject.idle_add(entry.set_position, new_pos)

        entry.stop_emission("insert_text")

if __name__ == "__main__":
    window = Gtk.Window()
    window.connect("delete-event", Gtk.main_quit)
    entry = TelNumberEntry()
    window.add(entry)
    window.show_all()
    Gtk.main()
Nagel answered 20/10, 2016 at 20:20 Comment(4)
Very good, your example works. But I'm getting the warning: gtkentry_with_formation.py:62: Warning: g_value_get_int: assertion 'G_VALUE_HOLDS_INT (value)' failed Gtk.main()Thant
Glad it works. The warning is due to a bug in handling the position argument in Python. See this question: #38816194Nagel
sorry to go back to that question. Why do you make double inheritance with Gtk.Entry and Gtk.Editable, since Gtk.Entry inherits from Gtk.Editable?Thant
This is to avoid overriding gtk.Editable implementation. See the answer to this question #38816194, the note at the bottom explains this in more detail.Nagel

© 2022 - 2024 — McMap. All rights reserved.