Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I change an urwid.Edit's text from the 'change' signal handler?

Tags:

python

urwid

I want to change an urwid.Edit's text from within its "change" signal handler. However, it doesn't do anything. Minimal working example:

import urwid

input_line = urwid.Edit(multiline=True)

def input_change(widget, text):
        if text.endswith("\n"):
                input_line.set_edit_text('')

urwid.connect_signal(input_line, 'change', input_change)
urwid.MainLoop(urwid.Filler(input_line)).run()

If you press enter, it will actually call .set_edit_text(), but the text remains the same. How do I achieve what I want?

like image 740
Vegard Avatar asked Nov 21 '13 19:11

Vegard


2 Answers

As you can see in the source, the set_edit_text method emits your "change" event, and then immediately afterward, it sets the _edit_text to the actual value.*

You can also verify this by, e.g., logging input_line.edit_text immediately after your set_edit_text to see that you did successfully change it.

What you need to do here is subclass the Edit widget, and override set_edit_text,** not handle the "change" signal. Then it's easy.

For example:

def set_edit_text(self, text):
    if text.endswith('\n'):
        super().set_edit_text('')
    else:
        super().set_edit_text(text)

As mentioned above, there's a very good reason for a GUI framework to have events that fire before the change is applied: that gives your event handler a way to see both the current value and the new value.

Of course there's also a very good reason for a GUI framework to have events that fire after the change is applied.

Some frameworks provide both. For example, in Cocoa, you usually get a fooWillChange: message before the change, and a fooDidChange: message afterward. Also, in some frameworks, the "before" event gives you a way to influence how the event gets handled (replace one of its values, swallow the event so it doesn't get passed up the chain, etc.). And then there's Tkinter, which provides some way to do all of these different things, but they're all completely different from each other, and different from widget to widget…

Is it a bug for a framework to not have all of the possible options? Well, there's a downside to a framework being too big and too general. It's harder to develop and maintain, and, worse, harder to learn. I think urwid made a reasonable choice here. Especially since it's written in relatively simple pure Python with a class class hierarchy that makes it easy to override any behavior you don't like.

However, you could maybe call it a documentation bug that Urwid doesn't tell you which kind of signal logic is uses (immutable "before" events), and offers very little guidance on what to override to customize behavior.


* It's also worth noting that your change handler is getting called in the middle of set_edit_text. In urwid, calling set_edit_text from this handler isn't a problem, but in many other UI libraries it could lead to infinite recursion or bizarre behavior.

** You could of course monkeypatch Edit instead of subclassing, unless you have a particular reason to do that, I wouldn't.

like image 151
abarnert Avatar answered Oct 20 '22 12:10

abarnert


Here is another way to do it by overriding "keypress" and defining your own "done" signal that is emitted when you press enter:

class CustomEdit(urwid.Edit):
    _metaclass_ = urwid.signals.MetaSignals  
    signals = ['done']


    def keypress(self, size, key):
        if key == 'enter':
            urwid.emit_signal(self, 'done', self, self.get_edit_text()) #if you dont need a reference to the CustomEdit instance you can drop the 3rd argument
            super(CustomEdit, self).set_edit_text('')
            return
        elif key == 'esc':
            super(CustomEdit, self).set_edit_text('')
            return
        urwid.Edit.keypress(self, size, key)
like image 20
pldimitrov Avatar answered Oct 20 '22 14:10

pldimitrov