Matheus Saraiva Matheus Saraiva - 1 month ago 5
Python Question

How to format the entries in Gtk.Entry

For example, the telephone format is

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

Answer

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:

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):
        # Called when the user inserts some text, by typing or pasting.

        #The `position` is updated at the end of signal emission, we choose to ignore it
        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")

if __name__ == "__main__":
    window = Gtk.Window()
    window.connect("delete-event", Gtk.main_quit)
    entry = TelNumberEntry()
    window.add(entry)
    window.show_all()
    Gtk.main()

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 answer, you can override the default signal handler like so:

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):

        #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

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

Gtk.main()

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()
Comments