Konsolenapplikationen mit urwid Teil 1

Will man mit Python eine Anwendung entwickelt, welche über ein Text User Interface (TUI) verfügt, so hat man im Großen und Ganzen zwei vernünftige Möglichkeiten:

curses

Der große Vorteil an curses ist, dass es mit Python unter Linux mitgeliefert wird. Ansonsten ist es sehr sehr Umständlich mit dieser Bibliothek zu arbeiten, da quasi nichts an Widgets mitgeliefert wird. Standardmäßig gibt es quasi nur ein Eingabefeld für Text und die Möglichkeit gezeichnete Boxen zu umrahmen.

Hat man vor ein Projekt mit einer gewissen Größe im Frontend umzusetzen, sollte man sich im Klaren sein, dass man mit curses ein ziemlich großes Stück Arbeit vor sich hat. Wer sich dennoch interessiert, der kann sich in diesem Wikibook ansehen, wie man mit curses umgeht. Wenigstens ist der Code recht übersichtlich und gut lesbar.

urwid

Urwid selbst basiert auf curses und ist eine Bibliothek zum einfacheren und schnelleren Erstellen von TUIs. Es existiert ein Basissatz von oft benötigten Widgets (Buttons, Inputs, Checkboxen…) und einfache Möglichkeiten diese zu platzieren. Zusätzlich kann man auch anhand des Basiswidgets eigene Widgets erstellen. Leider gibt es keine oder nur wenige vernünftige Tutorials zu urwid und die Dokumentation ist auch etwas dürftig, womit man schnell auf der Strecke bleiben kann, wenn man an einer der vielen Eigenheiten der Bibliothek hängen bleibt.

(urwid ist bei Archlinux sowie Ubuntu unter dem Namen »python-urwid« in der Paketverwaltung zu finden)

Erste Schritte

Für jede gewünschte Aktion stellt urwid eine Methode bereit, die man ausführen muss. Nach dem Import erstellen wir also ein Textwidget. Danach wird es in einen Filler gesteckt, der den Platz oberhalb und unterhalb des Widgets ausfüllt, und somit eine eindeutige Positionierung des Widgets ermöglicht.

Mit urwid erstellte Programme laufen, wie von vielen anderen Nutzeroberflächen schon bekannt, in einer MainLoop. Diese wird mit unserem Widget befüllt und danach gestartet. Unser erstes Programm ist fertig!

Wem beim Abschreiben des ersten Codes Fehler unterlaufen sind, der wird einen weiteren großen Nachteil an urwid erkannt haben: Die Fehlermeldungen sind kaum nützlich bis völlig unbrauchbar.

import urwid

text = urwid.Text("Hello World")
text = urwid.Filler(text)
loop = urwid.MainLoop(text)
loop.run()

Eingaben behandeln

Sämtliche Eingaben kann man abfangen, indem der MainLoop das Argument »unhandled_input=eine_funktion« übergibt. Somit wird der Funktion eine_funktion die gedrückte Taste übergeben. Urwid gibt Sondertasten wie Enter brauchbare und lesbare Aliase, was den Umgang erheblich erleichtert.

import sys
import urwid

def handle_input(input):
    if input == "esc":
        sys.exit(0)
    pass

text = urwid.Text("Hello World")
text = urwid.Filler(text)
loop = urwid.MainLoop(text, unhandled_input=handle_input)
loop.run()

Es kommt Farbe ins Spiel

Farben werden über eine Palette realisiert, in der für jeden Farbsatz jeweils eine Vordergrundfarbe und eine Hintergrundfarbe definiert werden muss. Mittels der Methode AttrWrap() weist man einem Widget eine Farbe zu.

palette = [('p1', 'light green', 'black'),
           ('p2', 'white', 'dark red')]

text = urwid.Text("Hello World")
text = urwid.AttrWrap(text, "p1")
text = urwid.Filler(text)
loop = urwid.MainLoop(text, palette)
loop.run()

Komplexere Layouts

Um mehr Information auf den Bildschirm zu bringen ist das Filler-Widget vielleicht etwas zu starr und unpraktisch. Hierfür bietet sich eher das Frame-Widget an. Es besteht aus einem Header, einem Body und einem Footer. In unserem Beispiel möchten wir einen starren Header und Footer haben, der Bodybereich soll jedoch scrollbar sein.

Dazu erstellen wir zunächst ein wenig Dummyinhalt. Dieser wird vom Widget Divider() voneinander getrennt. Der Inhalt für den Body wird dann in einer Liste zusammengefasst:

t0 = urwid.Divider()
t1 = urwid.AttrWrap(urwid.Text("Hello World"), "p1")
t2 = urwid.Divider("+")
t3 = urwid.AttrWrap(urwid.Text('''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas porta porta nunc eget dignissim. Morbi ante nibh, varius vitae rhoncus id, fermentum in nisl. Curabitur metus diam, pharetra vel varius non, vulputate eget lectus. Sed et nisl sed nisi sollicitudin egestas. Curabitur in ligula elit. Duis tristique porttitor porttitor. Cras rutrum lectus quis sem rutrum vulputate suscipit orci bibendum. Nam consectetur turpis purus, at suscipit tellus. Aenean diam lorem, tristique vel consequat in, rhoncus ac felis. Nam scelerisque justo nunc.

Suspendisse ut dolor vehicula felis gravida egestas. Nullam porttitor pulvinar mauris id tempor. Ut eu risus eu tellus laoreet pulvinar vitae ac nisl. Fusce bibendum consequat mauris, a lacinia turpis hendrerit eu. Nunc a arcu eget ante cursus dignissim vestibulum in magna. Nunc tempor accumsan augue eget cursus. Nullam interdum arcu eget metus placerat tempus. Morbi id lacus et diam tempus auctor. Morbi vehicula mi id mauris faucibus vitae pharetra purus rhoncus. Aliquam erat volutpat. Vestibulum nisi dolor, tincidunt quis interdum vitae, mattis quis nunc. Maecenas velit tortor, luctus non aliquet vel, euismod non arcu. Nunc ante lorem, bibendum eget pretium id, accumsan id odio. Aliquam odio lectus, porttitor dignissim egestas eget, vestibulum ut sapien. Pellentesque neque dolor, ultrices vel eleifend in, tincidunt vel libero.

Aliquam pulvinar est sit amet ligula viverra eleifend. Nullam porttitor sem sit amet massa egestas at ultricies lorem vestibulum. Donec eu libero dui. Sed in fringilla arcu. Nunc porta tincidunt lorem, eu dignissim lorem tempus ut. Integer faucibus magna non libero varius elementum blandit dolor lobortis. Maecenas a pharetra neque. Sed massa augue, placerat a varius eget, lobortis in nunc. Praesent non leo neque, non imperdiet felis. Aenean sed orci quis justo rutrum ornare vel nec erat. Vestibulum posuere elementum justo, in cursus neque varius a. In vel blandit nunc. Mauris blandit ligula eget felis lacinia at faucibus sem adipiscing. Sed malesuada lorem eget neque ornare eget condimentum nunc scelerisque.'''), "p1")
t4 = urwid.Divider("-")
t5 = urwid.AttrWrap(urwid.Text("Hello Arch!"), "p1")
t6 = urwid.Divider("8")

header = urwid.AttrWrap(urwid.Text("Dies ist der Header!"), "p2")
footer = urwid.AttrWrap(urwid.Text("Under hier ist der Footer!"), "p2")

content = [t0, t1, t2, t3, t4, t5, t6]

Um den Body scrollbar zu machen, wird der Inhalt zuerst in einen SimpleListWalker gesteckt, danach in eine ListBox. Dies hat das Ergebnis, dass der Fokus Widget für Widget verschoben werden kann. Danach wird ein Framewidget erstellt, welches standardmäßig den Body als erstes Widget annimmt. Danach werden Header und Footer übergeben und das FrameWidget in die MainLoop gesteckt.

content = urwid.ListBox(urwid.SimpleListWalker(content))

main_frame = urwid.Frame(content)

main_frame.set_header(header)
main_frame.set_footer(footer)

loop = urwid.MainLoop(main_frame, palette)
loop.run()

Urwid SimpleListWalker

Weiter geht es…

…im nächsten Teil mit Formularen, wo urwid seine Stärken ausspielen kann. Kommentare oder Anregungen? Ab in die Kommentare!

2 comments

  • Stefano posted on 12.04.2011 at 20:39:

    Hi, wo finde ich den zweiten Teil?
    Ich hab ihn gesucht aber nicht gefunden.

    • 5in4 posted on 12.04.2011 at 20:46:

      Den gibt es leider noch nicht, tut mir leid :(