Python urwid

From wikinotes


Urwid is a higher level abstraction to the curses library. It introduces a layout and widget system to curses, while still providing access to curses core.


Basic Concepts

Use the Source

Urwid's documentation is useful, but not as complete as I would like it to be. For greater understanding, I recommend installing it into a python virtualenv, and using a debugger to step through the code. As always, grep -rli is always useful.

Widget Types

http://urwid.org/manual/widgets.html#included-widgets

urwid has three primary types of widgets. Flow Widgets, BoxWidgets,and FixedWidgets. Fixed Widgets are not well defined, and currently only encompass 'LargeText' Widgets.
Not all widgets can contain other widget types. If you are receiving the error Too Many Values to Unpack, you might want to try putting your layout in one of the conversion layouts like a Filler.

Flow Widgets widgets whose contents will overlap onto the next line if there is not enough room.
Box Widgets widgets with a fixed height/width. The top-level widget is always a box-widget.
Fixed Widgets ??
pile = urwid.Pile([					## Flow Widget
		urwid.Buton('A'),
		urwid.Button('B'),
	])

filler = urwid.Filler( pile )		## Box Widget
MainLoop( filler ).run()			## (mainloop expects box widgets)

layout.contents

This is how you add/inspect the contents of layouts in urwid. It is universal across all container types. After the object has been created, appending objects requires a tuple of the container's options. These are specific to the layout type.

pile = urwid.Pile([])														## Empty Pile
pile.contents.append(( urwid.Text('A'), pile.options() ))		## Append Single Item
pile.contents.append(( urwid.Text('B'), ('weight', 10) ))		## Append Single Item (manually specifying pile.options() rather than using default )

print pile.contents									## List of Widgets
print len(pile.contents)							## List Number of Widets in layout

print pile.focus 										## Get name of Selected Widget. (Read Only)
print pile.focus_position							## Get/Set selected widget by it's index in it's parent layout.


print main_loop._topmost_widget					## (I believe this is the top layout)
print main_loop.base_widget						## (I believe this is the selected widget)

keypress methods

Every widget/layout contains a keypress method you can override for binding custom actions to keypresses. If subclassing widgets and calling the keypress method using super, make sure to capture the return value as keys, and return it.


The order that keypresses are evaluated are from the top-most layout, to the bottom-most widget. Before processing each layout, if it has contents, it's focused child's keypress is evaluated. The key is then compared against the widget's _command_map._command dict, if a command is bound to it, the command is run (and typically, the keypress returns None). If a command is not bound to it, the key is returned, and the parent layout tries to interpret the key. Occasionally (notably with cursor-movements), a widget returns a different value to be interpreted by the parent. For example: 'cursor down' returns 'down'. You can bind these values directly if you know that is your desired behaviour.

Keypress Life-Cycle

   +Startup+
      |                                     -+
main_loop._reset_input_descriptors           |
      |                                      |
row_display.Screen.hook_event_loop           | Subscribe main_loop._update to each keypress
      |                                      | ( keypresses appear to be logged to a file )
row_display._make_legacy_input_wrapper       |
                                            -+



   +Key Event (from FIFO?)+
       |
main_loop._update                                       -+
 |                                                       | Process each key event
 +-main_loop.input_filter                                |
    |                                                    |
    +-main_loop.process_input                            |
    |  |                                                 |
    |  +-main_loop._topmost_widget.keypress( key, ord )  | -+
    |     |                                              |  | Starting from topmost layout, navigate to
    | 	 +-next_widget.keypress                         |  | bottom widget, try to process key,
    |        |                                           |  | and then each consecutive parent layout
    | 	    +-next_widget.keypress                      |  | processes the return value of it's child.
    |                                                    | -+
    +-main_loop.unhandled_input                          |
                                                        -+

Example Subclass

import urwid

class MyButton(urwid.Button):
	def __init__(self, *args, **kwds):
		super(MyButton, self).__init__( *args, **kwds )

	def keypress( self, size, key ):
		"""
		___________________________________________________________________________________________________________________
		size  | (30, 40)                |  | The Container's size. One of the dimensions
		      |                         |  | is allowed to be undefined. ex: (30,)
		      |                         |  |
		key   | 'meta a', 'ctrl z', 'b' |  | The name of the button pressed. Certain
		      |                         |  | ctrl+key combos are problematic.
		"""

		if key in ('a','b'):
			print 'a or b'

		elif key == 'esc':
			raise urwid.ExitMainLoop()

		else:
			key = super( MyButton, self).keypress( size, key )
		
		return key

urwid.command_map

This builds a dictionary that links keypresses to functions. Most keypress functions use something similar to: if self._command_map[key] == 'cursor up': to process input. All of the default bindings use a variable CURSOR_LEFT, but when parsed they end up as lower-case strings cursor up. I have no idea why this is.

button = urwid.Button('A')
button._command_map['z'] = 'do something'


Access Curses!

urwid not man enough for you? Fall back onto curses and do it yourself. You can access urwid's curses display. (see Curses).

urwid.curses_display.clear()
...

First Examples

Simple EventLoop

Here's a simple example of a program that processes a user's input using urwid.

import urwid

class MyUrwid( object ):
	def __init__(self):
		self.main()

	def main(self):
		pile = urwid.Pile([
				urwid.Button('A'),
				urwid.Button('B'),
				urwid.Button('C'),
			])
		main = urwid.Filler( pile )

		urwid.MainLoop(
				widget          = main          ,
				unhandled_input = self.keypress ,
			).run()

	def keypress(self, key):
		if key in ('q','Q'):
			raise urwid.ExitMainLoop()		## Exit
		else:
			return key

Widgets

Text

Rather than curses where text is just that, inrwid Text() is a textblock.

import urwid

txt = urwid.Text()
txt.set_text('Hello')


Edit/IntEdit

Edit is an urwid.Text(), that accepts input.

Signals
change
Emitted every time the text is modified
qstn = urwid.Edit('How Old Are you?')			## Form, Requesting Input
print qstn.edit_text()								## Form's (current) User-Input
print qstn.text()										## Form's (label) text

Button

Buttons

Signals
click
Emitted whenever a button is clicked on with a mouse, or a button bound to 'activate' is pressed while the button is selected.
## Constructor
okbtn = urwid.Button( 								## All Button's Flags are optional
	label     = 'Okay'                        ,
	on_press  = self.runFunc                  ,
	user_data = 'send this string with signal' 
)


## Signal Sample
urwid.connect_signal( okbtn, 'click', self.function, user_data=None )		## connect signal

## Functions
label = okbtn.get_label()
okbtn.set_label('newLabel')


CheckBox

Signals
change
Emitted every time the text is modified

Divider

A Horizontal Rule used as space between objects. You can choose the character to repeat.

Linebox

A Widget with a border. You can assign a title that will be shown on the top left of the border.

btn = urwid.Button('Test')
urwid.Linebox( btn, title='My Title' )

SolidFill

Fills widget with a single repeated character.

BarGraph

ProgressBar

Terminal

Best For Last. Yes, a fully featured terminal. In your GUI. sexy sexy sexy sexy sexy sexy sexy sexy.



Layouts


General Info

#### Focus
col = urwid.Columns([])

col.focus					## Get selected widget (cannot set)
col.focus_position		## Get/Set the selected widget within a layout
#### Contents
## Every layout also has an attribute called ''contents'', which can be used as a list
## that stores a layout's contents. When appending to contents, each layout type requires
## a different set of options(). This is a tuple that typically consists of <code>('weight', 10)</code>.

col = urwid.Columns([])

col.contents.append(( urwid.Button('A'), col.options() ))		## Append to Layout
print col.contents															## List of Contents
print len(col.contents)														## NumItems in layout
#### Switching Containers
## Switching Containers is fairly simple. The keypress is sent to the 
## parent container, and the next selection is determined by the distance
## from sel.focus_position to either len(self.contents), or 0.

+-TopLayout---------------------------------+
|+--ColumnA--------------------------------+|
|| +--Column1--+-Column2-----+-Column3---+ ||   self.focus_position is 1
|| |           |             |           | ||   ( 2nd index in ColumnA )
|| |           |  (self)     |           | ||
|| |           |             |           | ||
|| |           |             |           | ||
|| +-----------+-------------+-----------+ ||
|+-----------------------------------------+|
+-------------------------------------------+

## left
count down from self.focus_position to 0

## right
count from self.focus_position 
to         len(self.contents)


#### How Parent Layouts are selected
## Each keypress either triggers a function, or returns a key
## when a key is returned, it is passed to the parent layout
## this continues until there is no parent layout.

widget in Column2 is passed to Column2
Column2 intercepts key, or passes to ColumnA
ColumnA intercepts key, or passes to TopLayout

Frame/Pile (Vertical)

Frames/Piles are your QVBoxLayouts. Piles will allow you to change focus with keystrokes, Frames will not. Frames and Piles can be assigned header and footer widgets that are tied to the top/bottom of the frame/pile.


## Adding Widgets
pile = urwid.Pile([
		urwid.Button('A'),
		urwid.Button('B'),
		urwid.Button('C'),
		])
## Appending Widgets
pile = urwid.Pile([])
pile.contents.append( urwid.Text('AAA'), pile.options() )
pile.contents.append( urwid.Text('AAA'), ('weight', 10) )
## Weighted Sizing of Widgets
btnA = urwid.Button('A')
btnB = urwid.Button('B')
btnC = urwid.Button('C')

urwid.Pile([ ('weight', 10, btnA ),  ('weight', 2, btnB), ('weight', 5, btnC) ])
## Resizing Layout
##
##! I can't figure out how to dynamically rezize these yet
##
## Attributes
index  = pile.focus_position()				## Index of Widget Under Focus
widget = pile.focus_item()						## Widget under Focus

widgetList = pile.contents()					## list of pile widgets
numWidgets = len(pile.contents)				## number of pile widgets


Columns (Horizontal)

(QHBoxLayout). Arranges Widgets in Horizontal Columns. Used Interchangeably with Pile.

#### Append to Column
col = urwid.Columns([])
col.contents.append(( urwid.Text('test'), col.options() ))
#### Add List of Widgets
colWidgets = [
		urwid.Button('A'),
		urwid.Button('B'),
		urwid.Button('C'),
	]
column  = urwid.Columns( colWidgets )
#### Size-Weighted Columns
btnA = urwid.Button('A')
btnB = urwid.Button('B')
btnC = urwid.Button('C')

urwid.Columns([ ('weight', 10, btnA ),  ('weight', 2, btnB), ('weight', 5, btnC) ])
##
##! I can't figure out how to dynamically rezize these yet
##

GridFlow

Creates a Flow Grid of items in your database. Based on your set cell_width, your items will be pushed onwards to the next row if there isn't enough room in your terminal window. Arranges so heights/widths match. This IS NOT analogous to a gridLayout in Qt.


## IMPORTANT ##
# GridFlow must be placed in a FlowWidget (like Filler) in order to work.

items = [																## Create List of items to add to ListBox
		urwid.Text('monkey'   ) ,
		urwid.Text('aardvark' ) ,
		urwid.Text('toast'    ) ,
		urwid.Text('monkey'   ) ,
		urwid.Text('aardvark' ) ,
		urwid.Text('toast'    ) 
		]
grid  = urwid.GridFlow( 										## Add Items to GridFlow
		cells      = ([ txt for txt in items ]),
		cell_width = 20      ,
		h_sep      = 3      ,
		v_sep      = 1      ,
		align      = 'left' 
		)

main = urwid.Filler( grid )									## Add GridFlow to Filler
																		 # (so can be main layout in MainLoop()

urwid.MainLoop( main, palette, unhandled_input=keypress ).run()

ListBox

Manages Lists of Objects. Note That ListBoxes are Box Widgets which means they must have a fixed height. In order to add a listBox to a layout, you must first add it to a BoxAdapter.

Signals
modified
(listWalker) Emitted every time the selected widget changes

Attributes

listbox = urwid.ListBox( listWalker )

print listbox.body				## all children of contained listWalker as iterable
print lisbox.body[-1]			## the last object in the listbox

print listbox.contents			## listWalker object itself.

Complete Example
ListBoxes are both important, and can be a little obscure in the official documentation. Here is a simple example of how they actually work. Fortunately, they are MUCH easier to use than my own first-stab at in curses.


import urwid
import pdb

palette = [
		('normal'   , 'black'      , 'light gray' ) ,
		('selected' , 'light gray' , 'black'      ) ,
		]


items = [																## Create List of items to add to ListBox
		urwid.Text('monkey'   ) ,
		urwid.Text('aardvark' ) ,
		urwid.Text('toast'    ) 
		]

content = urwid.SimpleListWalker([								## The AttrMap is applied when objects are
	urwid.AttrMap(w, None, 'selected') for w in items		## marked as selected. SimpleListWalkers manage
	])																		## the contents of a list

listbox = urwid.ListBox( content )								## Assign the SimpleListWalker to the listbox


def keypress(input):													## keypress will be our main eventloop
	pdb.set_trace()
	if input in ('q','Q'):
		raise urwid.ExitMainLoop()

	elif input in ('up', 'k'):
		focus_widget, idx = listbox.get_focus()	# getFocus returns (widget, index)
		if idx > 0:
			idx = idx-1
			listbox.set_focus(idx)

	elif input in ('down','j'):
		focus_widget, idx = listbox.get_focus()
		if idx < len(listbox.body) -1:				# listbox's body attribute is a list of all
			idx = idx+1										# top-level widgets in listWalker (in this case AttrMaps)
			listbox.set_focus(idx)



urwid.MainLoop(														## Run the mainloop, like anywhere else!
		listbox, 
		palette, 
		unhandled_input=keypress ).run()

Example with BoxAdapter:

		## List
		listConts = [
				urwid.Button('a'),
				urwid.Button('b'),
				urwid.Button('c'),
				urwid.Button('d'),
				]

		listWalk = urwid.SimpleListWalker( listConts )
		listbox  = urwid.ListBox( listWalk )
		listBoxAdapt = urwid.BoxAdapter( listbox, height=2 )
		main = urwid.Filler( listbox )


Columns of Listboxes

		# ListboxA
		listboxAConts = [urwid.Text("Some widgets here:"),urwid.Edit("Edits","")]
		listboxA      = urwid.ListBox( listboxAConts )

		# ListboxB
		listConts = []
		for i in xrange(0, 1000):
			listConts.append( urwid.Button(str(i)) )

		listWalk = urwid.SimpleListWalker( listConts )
		listboxB  = vimctl.ListBox( listWalk )


		# ListboxC
		listConts = [ urwid.Button('a'), urwid.Button('b'), urwid.Button('c') ]
		listWalk  = urwid.SimpleListWalker( listConts )
		listboxC  = vimctl.ListBox( listWalk )


		## All Together now!
		body = urwid.Columns([ ('weight',1,listboxA), ('weight',3,listboxB), ('weight',1,listboxC) ])

Filler

Filler Widgets are boxlayouts. They can contain other widgets, and they can be weighted to one side or another.

txt  = urwid.Text('Child Text')
fill = urwid.Filler( child, 'top' )



Signals and Events

Ian is pretty explicit about users not needing to create their own signal instances. They are not even present in the glossary, you need to search to get a description. Regardless, here is how to use existing ones, search signal to find more information on creating your own.

Signals are not well documented in urwid, but they are simple enough to find by searching the source code for signals =

btn = urwid.Button('Exit')

def on_button_click( button ):
	raise urwid.ExitMainLoop()

urwid.connect_signal( btn, 'click', on_button_click )		## Standard Signal
urwid.connect_signal( btn, 'click', one_button_click, listSentOnClick )		## Signal Can be sent with an additional variable to carry data

Colours

Colours can be applied by creating 'palettes', and applying them to the mainloop's constructor. These are essentially named pairs of fg/bg colours. urwid palettes allow you to create fallback colours when you're using more than 16x colours, so that you have a palette for 8 colours, for 88 colours, and for 256 colours.


Named Colours (16x)

## Palette:  NAME     FG         BG
palette = [
			('banner' , 'black' , 'light gray' ) ,
			('streak' , 'black' , 'dark red'   ) ,
			('bg'     , 'black' , 'dark blue'  ) ,]


txt  = urwid.Text(( 'banner', 'Text to be replaced'), align='center')


map1 = urwid.AttrMap(txt, 'streak')          ## AttrMaps work like QSS in Qt. Anything parented under
fill = urwid.Filler( map1 )                  ## an Attrmap will inherit it's colours, unless they have colours
                                             ## chosen for them.
map2 = urwid.AttrMap(fill, 'bg')


loop = urwid.MainLoop( map2,  palette,  unhandled_input=show_or_exit)
loop.run()


Hex Colours

Now we're talking!

# Palette:  NAME    16fg 16bg  mono  256-fg   256-bg
palette = [
			('banner' , '',  '',   '', '#000' , '#333' ) ,				## I don't understand mono's role yet
			('streak' , '',  '',   '', '#000' , '#777' ) ,
			('bg'     , '',  '',   '', '#000' , '#888' ) ,]

loop = urwid.MainLoop( widget,  palette,  unhandled_input=show_or_exit)
loop.screen.set_terminal_properties(colors=256)

Nested Colours within line

Say for example you were doing syntaxhighlighting, and needed some words to be formatted differently than other words. This is how you would do it:

##  name     fg    bg
palette = [	  
	('red',  'red', '' ),									## An empty string means to leave a colour alone.
   ('blue', 'blue','' ),] 

txt = urwid.Text(  'red' 'MyText')						## Entire Line is Red
txt = urwid.Text([  'My', ('red', 'Text')  ])		## My is default, Text is Red

Text Formatting

Text Formatting is also done in the palette, and is applied by adding a comma after the colourName, with a text attribute.

palette = [
	('red_bold',   'red,bold'           ),					## Note that ONLY STANDOUT is available
	('blue_und',   'blue,underline'     ),					## in a TTY.
	('green_stnd', 'dark green,standout'), ]


Alignment

This is going to cover broadly anything related to positioning widgets, both within layouts, and their attributes themselves.

## Widget Alignment
urwid.Text('test',  align='top' )						## center and position at top
urwid.Text('test', valign='top' )						## Widget vertical alignment
urwid.Text('test', halign='left')						## Widget horizontal alignment




Threaded GUI Loading

urwid's main_loop has a hook watch_pipe that listens over a os.pipe() for changes during each iteration of the event loop. This can be used to load a GUI in a separate thread while still maintaining the ability to interact with it.

The following class is broken up by it's methods for readability.

class ThreadedGui( object ):
	def __init__( self ):
		""" Start GUI """
		import threading
	
		self.pile  = urwid.Pile([])
		self.pile.contents.append((  urwid.Button('start'), self.pile.options() ))
		self.main  = urwid.Filler( self.pile )

		self.loading_btn = threading.Event()
		self.quitEvent   = threading.Event()
		self.quitEvent.clear()
		
		self.loop = urwid.MainLoop(
				widget          = self.main     ,
				unhandled_input = self.unhandled_input ,
			)
	
	
		self.loop.run()
	def unhandled_input( self, input ):
		""" 
		keypress func, 
		press 'q' to quit,
		press 'i' to add buttons to the list
		"""
		import threading
		if input in ('q','Q'):
			raise urwid.ExitMainLoop()

		elif input == 'i':
			self.write_fd = self.loop.watch_pipe( self.add_buttons )		## any data written/flushed into write_fd will be passed as
																							#  an argument to self.add_buttons
			threading.Thread(
					target = self.thread_generate_buttons,
					args   = ( self.write_fd, ),
				).start()
	def add_buttons( self, data ):
		""" 
		Adds a Button Widget to the pile every time data is
		received on write_fd.

		Button is named after the value sent.
		"""
		if data:
			self.loading_btn.set()
			self.pile.contents.append((   urwid.Button( data ),  self.pile.options()   ))
			self.loop.draw_screen()
			self.loading_btn.clear()
		else:
			return False
	def thread_generate_buttons(self,  write_fd ):
		""" 
		Run in separate thread, every 'write' to the file descriptor
		is passed as an argument to add_buttons(),
		which adds a new button to the list.
		"""
		import time
		import os
	
		for i in xrange(10000):
			while not self.loading_btn.is_set():
				if self.quitEvent.is_set():
					break
				os.write( write_fd, str(i) )
				time.sleep(0.005)
			if self.quitEvent.is_set():
				break
		
		## PoisonPill, once data is received, the file descriptor is
		## continuously read until data of '' is sent.
		os.write( write_fd, '' )