Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

preferred method to dynamically change the urwid.MainLoop widget

I was looking over a bit of code rooted in urwid:

import urwid
from functools import partial
from random import randint

class State(object):

    def __init__(self, main_widget):
        self.main_widget = main_widget

def handle_keystroke(app_state, key):
        if key in ('q', 'Q'):
            raise urwid.ExitMainLoop()
        else:
            loop.widget = urwid.Filler(urwid.Button('new rand int:' + str(randint(0, 100))))

app_state = State(urwid.Filler(urwid.Button('original widget')))

callback = partial(handle_keystroke, app_state)

loop = urwid.MainLoop(app_state.main_widget, unhandled_input=callback)
loop.run()

and noticed that loop is referenced in the function unhandled_input before it's defined. Furthermore, it's not passed as a parameter, it's just hard coded into the function by name. 1) Why is this possible, and: 2) is there a clearer alternative? It is difficult to do otherwise, as there is a circular dependencies of loop, app_state and callback.

like image 605
anon01 Avatar asked Sep 03 '25 06:09

anon01


1 Answers

I'm not sure how much of your sample code represents the original code, but it looks like you may want to get familiar with the technique of using urwid's custom widgets wrapping text widgets, as shown in the answer with an example widget that displays a text content one line at the time.

Here is an example of writing something similar to the sample code you provided, in a design that fits urwid and Python a bit better:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function, absolute_import, division
import urwid
from random import randint


class RandomNumberWidget(urwid.WidgetWrap):
    def __init__(self):
        self.random_number = None
        self.text_widget = urwid.Text(u'')
        super(RandomNumberWidget, self).__init__(self.text_widget)

    def roll(self):
        self.random_number = randint(0, 100)
        self.update()

    def update(self):
        """Update UI
        """
        if self.random_number is None:
            self.text_widget.set_text('No number set')
        else:
            self.text_widget.set_text('Random number: %s' % self.random_number)


class App(object):
    def __init__(self):
        self.random_number_widget = RandomNumberWidget()
        top_message = 'Press any key to get a random number, or q to quit\n\n\n'
        widget = urwid.Pile([
            urwid.Padding(urwid.Text(top_message),
                          'center', width=('relative', len(top_message))),
            self.random_number_widget,
        ])
        self.widget = urwid.Filler(widget, 'top')

    def play(self):
        self.random_number_widget.roll()

    def play_or_exit(self, key):
        if key in ('q', 'Q', 'esc'):
            raise urwid.ExitMainLoop()
        app.play()


if __name__ == '__main__':
    app = App()
    loop = urwid.MainLoop(app.widget, unhandled_input=app.play_or_exit)
    loop.run()

Depending also on what you actually want to do, it could make sense to make the custom widgets respond to the keyboard events, instead of doing it all in the global handler (which is totally fine for simple programs, IMO).

like image 187
Elias Dorneles Avatar answered Sep 04 '25 18:09

Elias Dorneles