Urwid Examples

Author: Nicolas Seriot
Date: 2021-09-18 22:15:10
Urwid: http://urwid.org/
GitHub: https://github.com/nst/UrwidExamples

Content

  1. Layout
  2. Signals
  3. Overlays
  4. Colors
  5. Async
1_layout columns_focus.py
import urwid as u

class MyListBox(u.ListBox):

    def keypress(self, size, key):
        if key in ('tab',):
            self.focus_position = (self.focus_position + 1) % len(self.body.contents)
            if self.focus_position == 0:
                return key
        else:
            return key

    def selectable(self):
        return len(self.body.contents) > 0

class App(object):

    def handle_key(self, key):
        if key in ('q',):
            raise u.ExitMainLoop()
        if key in ('tab',):
        
            next_focus = (self.columns.focus_position + 1) % len(self.columns.contents)
            self.columns.set_focus(next_focus)

    def __init__(self):
        
        s1 = "asd ad as d" * 10
        s2 = "a sd sdf sf" * 10
        s3 = "yxc d dsa a" * 10
        
        t1 = u.LineBox(u.AttrWrap(u.Text(s1), "normal", "selected"))
        t2 = u.LineBox(u.AttrWrap(u.Text(s2), "normal", "selected"))
        t3 = u.LineBox(u.AttrWrap(u.Text(s3), "normal", "selected"))
        
        lw1 = u.SimpleListWalker([t1])
        lw2 = u.SimpleListWalker([t2, t3])
        
        lb1 = MyListBox(lw1)
        lb2 = MyListBox(lw2)
        
        lb1 = u.LineBox(lb1)
        lb2 = u.LineBox(lb2)
        
        ba1 = u.BoxAdapter(lb1, height=20)
        ba2 = u.BoxAdapter(lb2, height=20)
        
        self.columns = u.Columns([ba1, ba2])
        
        filler = u.Filler(self.columns, 'top')
        
        PALETTE = [("normal", "black", "white"),
                   ("selected", "black", "light cyan")]
        
        loop = u.MainLoop(filler, PALETTE, unhandled_input=self.handle_key)
        
        loop.run()

if __name__ == '__main__':

    app = App()
1_layout list.py
import urwid as u

PALETTE = [("normal", "black", "white"),
           ("highlighted", "black", "dark cyan")]

l = "abcdefghijklmnopqrstuvwxyz"

contents = [u.AttrMap(u.Button(x), "normal", "highlighted") for x in l]

walker = u.SimpleFocusListWalker(contents)

listbox = u.ListBox(walker)

box_adapter = u.BoxAdapter(listbox, height=10)

filler = u.Filler(box_adapter, 'top')

loop = u.MainLoop(filler, PALETTE)

loop.run()
1_layout list_details.py
import urwid as u

class ListItem(u.WidgetWrap):
    
    def __init__ (self, country):
                
        self.content = country
        
        name = country["name"]
        
        t = u.AttrWrap(u.Text(name), "country", "country_selected")

        u.WidgetWrap.__init__(self, t)

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

class ListView(u.WidgetWrap):

    def __init__(self):

        u.register_signal(self.__class__, ['show_details'])

        self.walker = u.SimpleFocusListWalker([])

        lb = u.ListBox(self.walker)
        
        u.WidgetWrap.__init__(self, lb)

    def modified(self):
    
        focus_w, _ = self.walker.get_focus()

        u.emit_signal(self, 'show_details', focus_w.content)

    def set_data(self, countries):
        
        countries_widgets = [ListItem(c) for c in countries]

        u.disconnect_signal(self.walker, 'modified', self.modified)

        while len(self.walker) > 0:
            self.walker.pop()
        
        self.walker.extend(countries_widgets)

        u.connect_signal(self.walker, "modified", self.modified)

        self.walker.set_focus(0)

class DetailView(u.WidgetWrap):
    
    def __init__ (self):
        t = u.Text("")
        u.WidgetWrap.__init__(self, t)
        
    def set_country(self, c):
        s = f'Name: {c["name"]}\nPop:  {c["pop"]}\nGDP:  {c["gdp"]}'
        self._w.set_text(s)

class App(object):
    
    def unhandled_input(self, key):
        if key in ('q',):
            raise u.ExitMainLoop()

    def show_details(self, country):
        self.detail_view.set_country(country)
        
    def __init__(self):

        self.palette = {
            ("bg",               "black",       "white"),
            ("country",          "black",       "white"),
            ("country_selected", "black",       "yellow"),
            ("footer",           "white, bold", "dark red")
        }

        self.list_view = ListView()
        self.detail_view = DetailView()

        u.connect_signal(self.list_view, 'show_details', self.show_details)

        footer = u.AttrWrap(u.Text(" Q to exit"), "footer") 
        
        col_rows = u.raw_display.Screen().get_cols_rows()
        h = col_rows[0] - 2
                
        f1 = u.Filler(self.list_view, valign='top', height=h)
        f2 = u.Filler(self.detail_view, valign='top')

        c_list = u.LineBox(f1, title="Countries")
        c_details = u.LineBox(f2, title="Country Details")
        
        columns = u.Columns([('weight', 30, c_list), ('weight', 70, c_details)])            

        frame = u.AttrMap(u.Frame(body=columns, footer=footer), 'bg')
        
        self.loop = u.MainLoop(frame, self.palette, unhandled_input=self.unhandled_input)

    def update_data(self):
        
        l = [] # https://databank.worldbank.org/embed/Population-and-GDP-by-Country/id/29c4df41
        l.append({"name":"USA",     "pop":"325,084,756",   "gdp":"$ 19.485 trillion"})
        l.append({"name":"China",   "pop":"1,421,021,791", "gdp":"$ 12.238 trillion"})
        l.append({"name":"Japan",   "pop":"127,502,725",   "gdp":"$ 4.872 trillion"})
        l.append({"name":"Germany", "pop":"82,658,409",    "gdp":"$ 3.693 trillion"})
        l.append({"name":"India",   "pop":"1,338,676,785", "gdp":"$ 2.651 trillion"})
        
        self.list_view.set_data(l)

    def start(self):

        self.update_data()
        self.loop.run()

if __name__ == '__main__':

    app = App()
    app.start()
1_layout list_update.py
import urwid as u

class App(object):

    def handle_key(self, key):
        if key in ('a',):
            x = u.AttrMap(u.Button("x"), "normal", "highlighted")
            self.walker.append(x)
        if key in ('q',):
            raise u.ExitMainLoop()

    def __init__(self):
        
        l = "abcde"
        
        contents = [u.AttrMap(u.Button(x), "normal", "highlighted") for x in l]
    
        self.walker = u.SimpleFocusListWalker(contents)
        
        listbox = u.ListBox(self.walker)
        
        box_adapter = u.BoxAdapter(listbox, height=10)
        
        filler = u.Filler(box_adapter, 'top')
        
        t = u.Text("a to append item, q to exit")
        frame = u.Frame(header=u.AttrMap(t, "header"), body=filler)
        
        PALETTE = [("header",      "white", "dark red"),
                   ("normal",      "black", "white"),
                   ("highlighted", "black", "dark cyan")]
        
        loop = u.MainLoop(frame, PALETTE, unhandled_input=self.handle_key)
        
        loop.run()

app = App()
1_layout minimal.py
import urwid as u

t = u.Text(u"Hello World")
f = u.Filler(t, 'top')
loop = u.MainLoop(f)
loop.run()
1_layout visual_debug.py
import urwid as u

DEBUG = True

def _(widget):
    if DEBUG:
        s = widget.__class__.__name__
        return u.LineBox(widget, title=s, title_align='left')
    else:
        return widget

def exit_on_(key):
    if key in ("q", "Q", "esc"):
        raise u.ExitMainLoop()

footer = u.Text("Q to exit")

t = u.Text("asd")
lb = u.LineBox(t)

content = u.SimpleListWalker([lb, lb, lb])
listbox = u.ListBox(content)
col_1 = _(u.Pile([listbox]))

filler = u.LineBox(u.Filler(t, 'top'))
col_2 = _(u.Pile([filler]))

columns = _(u.Columns([col_1, col_2]))

filler = _(u.Filler(columns, height=20, valign="top"))
frame = _(u.Frame(body=filler, footer=footer))
loop = u.MainLoop(frame, palette={}, unhandled_input=exit_on_)
loop.run()
2_signals sigwinch.py
import urwid as u
import asyncio
import signal

t = u.Text("")
f = u.Filler(t)

def show_screen_size():
    scr = u.raw_display.Screen()
    cols, rows = scr.get_cols_rows()
    s = "%d x %d" % (cols, rows)
    t.set_text(s)

def handle_sigwinch():
    show_screen_size()

aloop = asyncio.get_event_loop()
aloop.add_signal_handler(signal.SIGWINCH, handle_sigwinch)
ev_loop = u.AsyncioEventLoop(loop=aloop)

show_screen_size()

loop = u.MainLoop(f, event_loop=ev_loop)
loop.run()
3_overlays overlay.py
import urwid as u

def key_handler(key):
    if key in ('o',):
        toggle_overlay()
    elif key in ('q',):
        raise u.ExitMainLoop()

def toggle_overlay():
    if loop.widget == mainframe:
        t = u.Text("Overlay")
        f = u.Filler(t)
        l = u.LineBox(f)
        o = u.Overlay(l, mainframe, 'center', 30, 'middle', 10)
        loop.widget = o
    else:
        loop.widget = mainframe

h = u.Text("o = overlay, q = quit")
t = u.Text("xxx")
f = u.Filler(t, 'top')
mainframe = u.Frame(f, header=h)
loop = u.MainLoop(mainframe, unhandled_input=key_handler)
loop.run()
4_colors color_picker.py
import urwid as u

def create_palette():

    palette = []
    
    for r in range(16):
        for g in range(16):
            for b in range(16):
                c = "#%x%x%x" % (r,g,b)    
                t = "white" if r+g+b < 16 else "black"
                palette.append((c, "", "", "", t, c))

    return palette

palette = create_palette()

colors = [p[0] for p in palette]

contents = []

for i in range(128):
    step = 32
    subcolors = colors[i*step:i*step+step]
    l = []
    for c in subcolors:
        l.append((c,c))
        t = u.Text(l)
    contents.append(t)
    
grid = u.GridFlow(contents, cell_width=4, h_sep=0, v_sep=0, align='left')

filler = u.Filler(grid, 'top')

loop = u.MainLoop(filler, palette)

loop.screen.set_terminal_properties(colors=256)

loop.run()
4_colors colors_16.py
import urwid as u

palette = [
    ('black',         'white', 'black'),
    ('dark red',      'white', 'dark red'),
    ('dark green',    'white', 'dark green'),
    ('brown',         'white', 'brown'),
    ('dark blue',     'white', 'dark blue'),
    ('dark magenta',  'white', 'dark magenta'),
    ('dark cyan',     'white', 'dark cyan'),
    ('light gray',    'black', 'light gray'),
    ('dark gray',     'white', 'dark gray'),
    ('light red',     'white', 'light red'),
    ('light green',   'white', 'light green'),
    ('yellow',        'black', 'yellow'),
    ('light blue',    'white', 'light blue'),
    ('light magenta', 'white', 'light magenta'),
    ('light cyan',    'black', 'light cyan'),
    ('white',         'black', 'white')
]

colors = [p[0] for p in palette]

texts = [u.AttrMap(u.Text(c), c) for c in colors]

filler = u.Filler(u.Pile(texts), 'top')

loop = u.MainLoop(filler, palette)

loop.run()
4_colors rainbow_256.py
import urwid as u

palette = [
    ('red',          '', '', '', '', '#f00'),
    ('orange',       '', '', '', '', '#f80'),
    ('yellow',       '', '', '', '', '#ff0'),
    ('green',        '', '', '', '', '#0f0'),
    ('blue',         '', '', '', '', '#00f'),
    ('purple_dark',  '', '', '', '', '#508'),
    ('purple_light', '', '', '', '', '#90f')]

div = u.Divider()

div_1 = u.AttrMap(div, 'red')

colors = [p[0] for p in palette]

divs = [u.AttrMap(div, c) for c in colors]

filler = u.Filler(u.Pile(divs), 'top')

loop = u.MainLoop(filler, palette)

loop.screen.set_terminal_properties(colors=256)

loop.run()
5_async async_requests.py
import asyncio
import requests
import urwid as u

class App(object):

    def m(self, d):
        return requests.post('http://www.httpbin.org/post', json=d)
        
    async def fetch_data(self):
        r = await self.aloop.run_in_executor(None, self.m, {"key":123})
        self.t.set_text(r.text)

    def handle_key(self, key):
        if key in ('d',):
            self.aloop.create_task(self.fetch_data())
        if key in ('q',):
            raise u.ExitMainLoop()

    def __init__(self):
        
        self.t = u.Text("")
        filler = u.Filler(self.t, 'top')
        footer = u.Text("D to start download, Q to exit")
        frame = u.Frame(body=filler, footer=footer)
        
        self.aloop = asyncio.get_event_loop()
        
        ev_loop = u.AsyncioEventLoop(loop=self.aloop)
        
        u_loop = u.MainLoop(frame,
                            palette=[],
                            unhandled_input=self.handle_key,
                            event_loop=ev_loop)
        
        u_loop.run()

app = App()