Question

import urwid

palette = [('header', 'white', 'black'),
('reveal focus', 'black', 'dark cyan', 'standout'),]
content = urwid.SimpleListWalker([
        urwid.AttrMap(w, None, 'reveal focus') for w in [
            urwid.Text("This is a text string that is fairly long"),
            urwid.Divider("-"),
            urwid.Text("Short one"),
            urwid.Text("Another"),
            urwid.Divider("-"),
            urwid.Text("What could be after this?"),
            urwid.Text("The end."),]
    ])

listbox = urwid.ListBox(content)
show_key = urwid.Text("", wrap='clip')
head = urwid.AttrMap(show_key, 'header')
top = urwid.Frame(listbox, head)

def show_all_input(input, raw):
    show_key.set_text("Pressed: " + " ".join([
        unicode(i) for i in input]))
    return input

def exit_on_cr(input):
    if input == 'enter':
        raise urwid.ExitMainLoop()

loop = urwid.MainLoop(top, palette, input_filter=show_all_input, unhandled_input=exit_on_cr)
loop.run()

Pressing up or down should scroll the list but in my case it doesn't. Is there something wrong ?

EDIT 1

Code works perfectly in fact I didn't understand what scrollable lists are. I thought pressing up or down would select item in the list but it doesn't. What this does is just scroll your terminal when it doesn't have enough space to display all items at once. You must resize your terminal to a very small size to understand what this code does.

EDIT 2

To change focus when pressing up or down, a strange API has to be used. I would have loved to see an API like listbox.focus_next() / listbox.focus_previous(), but you have to handle the positions yourself. Of course, you can create your own functions (or subclass ListBox) to offer a better API

def handle_input(input):
    if input == "esc":
        raise urwid.ExitMainLoop()
    head.original_widget.set_text("key pressed: %s" % input)
    lw = listbox.body        
    if input == "up":
        # this can raise an error if we scroll past first position
        try:
            lw.set_focus(lw.get_prev(lw.get_focus()[1])[1])
        except:
            pass
    elif input == "down":
        # this can raise an error if we scroll past last position
        try: 
            lw.set_focus(lw.get_next(lw.get_focus()[1])[1])
        except:
            pass

EDIT 3

A better API :

def urwid_test():
    """
            'black', 'dark red', 'dark green', 'brown', 'dark blue',
            'dark magenta', 'dark cyan', 'light gray', 'dark gray',
            'light red', 'light green', 'yellow', 'light blue', 
            'light magenta', 'light cyan', 'white'
    """

    class MyListBox(urwid.ListBox):
        def focus_next(self):
            try: 
                self.body.set_focus(self.body.get_next(self.body.get_focus()[1])[1])
            except:
                pass
        def focus_previous(self):
            try: 
                self.body.set_focus(self.body.get_prev(self.body.get_focus()[1])[1])
            except:
                pass            

    def handle_input(input):
        if input == "esc":
            raise urwid.ExitMainLoop()
        head.original_widget.set_text("key pressed: %s" % input)
        if input == "up":
            listbox.focus_previous()
        elif input == "down":
            listbox.focus_next()
    palette = [("top","white","black"),
               ("line","light green","dark green","standout"),
               ("frame","dark magenta","white"),
               ]
    widgets = [urwid.AttrMap(widget,None,"line") for widget in
                [
                    urwid.Text("Chemma!"),
                    urwid.Divider("-"),
                    urwid.Text("Another text widget!"),
                    urwid.Divider("-"),                   
                    urwid.Text("What is your name"),
                    urwid.Divider("-"),                   
                    urwid.Text("Boy ?"),                                                            
                ]
              ]
    head    = urwid.AttrMap(urwid.Text("key pressed :",wrap="clip"),"top")
    L       = urwid.SimpleListWalker(widgets)
    listbox = MyListBox(L)
    top     = urwid.AttrMap(urwid.Frame(listbox,head),"frame")
    loop    = urwid.MainLoop(top,palette,unhandled_input=handle_input)
    loop.screen.set_terminal_properties(colors=256)
    loop.run()

if __name__ == "__main__":
    urwid_test()
Was it helpful?

Solution

Amazing how long it took for your question to get an answer, but here it is. I was after the same effect and found this other question which is unrelated but shows code that does what we want.

Long story short, you have to make your widgets selectable, otherwise they won't get focus and you won't be able to select them. Here is the relevant docs part.

In summary, you have to extend Text (or whatever widget you need to select) and set _selectable to True, while also implementing keypress():

class SelectableText(urwid.Text):
    _selectable = True

    def keypress(self, size, key):
        return key

Then just pass a list of SelectableText objects to your SimpleListWalker and it will magically work.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top