Hi all,
I'm experimenting with urwid in order to port an old program [1],
written using plain curses, and that nowdays is becoming a bit aged
due to the lack of flexibity of curses (no unicode, ugly UI code,
etc).
I've written a simple and quick proof of concept both to train myself
in urwid and to test the capabilities and speed of the library.
Using 0.9.8.
***test.py*** Lines 227-9. Class Panel, Method Display
I have a Frame whose body is a Columns widget with 2 elements, both identical.
Each element contains a list of Text => "content".
If this Text List is wrapped inside a
ListBox(SimpleListWalker(content), it 's rendered correctly.
But if I embed it inside a Pile(content) or Filler(Pile(content),
height=h) it fails in Text.render method (widget.py:350).
***test2.py***
I've written a small simple code to test it and it works there.
Any idea?
[1] http://inigo.katxi.org/devel/lfm/
Note that this url contains an outdated version of the program.
You can find the last - not public released - at:
http://inigo.katxi.org/misc/lfm.tar.gz
Thanks in advance,
Iñigo Serna
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import os.path
import stat
import time
import urwid
import urwid.raw_display
ui = None
ui_size = None
c1, c2 = None, None
######################################################################
##### Utils
def get_path_contents(path):
# FIXME: try different encodings
if type(path) != type(u' '):
path = unicode(path)
files, dirs = [], []
its = os.listdir(path)
for it in its:
# FIXME: try different encodings
fname = os.path.join(path, it)
st = os.stat(fname)
size = st.st_size
mtime = st.st_mtime
mode = stat.S_IMODE(st.st_mode)
uid = st.st_uid
gid = st.st_gid
if os.path.isdir(fname):
dirs.append((it, size, mtime, mode, uid, gid, '/'))
else:
files.append((it, size, mtime, mode, uid, gid, ' '))
dirs.sort()
files.sort()
st = os.stat(path)
if path != os.sep:
dirs.insert(0, ('..', st.st_size, st.st_mtime,
stat.S_IMODE(st.st_mode),
st.st_uid, st.st_gid, '/'))
return dirs + files
def size2str(size):
if size >= 1000000000L:
size = '%dM' % (size/(1024*1024))
elif size >= 1000000L:
size = '%dK' % (size/1024)
else:
size = str(size)
return size
def time2str(mtime):
if -15552000 < (time.time() - mtime) < 15552000:
# filedate < 6 months from now, past or future
mtime = time.strftime('%d %b %H:%M', time.localtime(mtime))
else:
mtime = time.strftime('%d %b %Y', time.localtime(mtime))
# mtime = time.strftime('%d/%m/%Y %H:%M', time.localtime(mtime))
return mtime
def mode2str(mode):
return oct(mode)
######################################################################
##### MyBox
class MyBox(urwid.WidgetWrap):
def __init__(self, widget, w, h, box_attr=None,
header='', header_attr=None, header_align='left',
footer='', footer_attr=None, footer_align='left' ):
"""Draw a box of (w, h) fixed size around widget, optionally
with header and footer messages."""
assert header_align in ('left', 'center', 'right')
assert footer_align in ('left', 'center', 'right')
hline = u'\u2500' # "â"
vline = u'\u2502' # "â"
tlcorner = u'\u250c' # "â"
trcorner = u'\u2510' # "â"
blcorner = u'\u2514' # "â"
brcorner = u'\u2518' # "â"
if not box_attr:
box_attr = 'body'
if not header_attr:
header_attr = 'body'
if not footer_attr:
footer_attr = 'body'
def use_attr(t):
return urwid.AttrWrap(t, box_attr)
if header:
header = header[:w-4]
if header_align == 'left':
ll = 1
rl = w - 3 - len(header)
elif header_align == 'center':
ll = (w - 2 - len(header)) / 2
rl = ll + 1
elif header_align == 'right':
ll = w - 3 - len(header)
rl = 1
tline = urwid.Text([(box_attr, hline*ll),
(header_attr, header),
(box_attr, hline*rl)],
wrap='clip')
else:
tline = use_attr(urwid.Divider(hline))
if footer:
footer = footer[:w-4]
if footer_align == 'left':
ll = 1
rl = w - 3 - len(footer)
elif footer_align == 'center':
ll = (w - 2 - len(footer)) / 2
rl = ll + 1
elif footer_align == 'right':
ll = w - 3 - len(footer)
rl = 1
bline = urwid.Text([(box_attr, hline*ll),
(footer_attr, footer),
(box_attr, hline*rl)],
wrap='clip')
else:
bline = use_attr(urwid.Divider(hline))
lline = use_attr(urwid.SolidFill(vline))
rline = use_attr(urwid.SolidFill(vline))
tlcorner = use_attr(urwid.Text(tlcorner))
trcorner = use_attr(urwid.Text(trcorner))
blcorner = use_attr(urwid.Text(blcorner))
brcorner = use_attr(urwid.Text(brcorner))
top = urwid.Columns([('fixed', 1, tlcorner),
tline,
('fixed', 1, trcorner)])
middle = urwid.Columns([('fixed', 1, lline),
widget,
('fixed', 1, rline)],
box_columns = [0,2], focus_column = 0)
bottom = urwid.Columns([('fixed', 1, blcorner),
bline,
('fixed', 1, brcorner)])
pile = urwid.Pile([('flow', top), middle, ('flow', bottom)],
focus_item = 0)
urwid.WidgetWrap.__init__(self, pile)
######################################################################
##### Panels
class Panel:
def __init__(self, path, size):
self.active = False
self.path = path
self.files = get_path_contents(path)
self.i = 0
self.numfiles = len(self.files)
self.SELECTED = ('montse.xcf', 'album.py')
self.resize(size)
self.recalc_display_area()
def resize(self, size):
self.w, self.h = size[0], size[1]
self.SSIZE = 7
self.SDATE = 12
self.SNAME = self.w - 4 - self.SSIZE - self.SDATE
self.gap = 3
self.recalc_display_area()
def recalc_display_area(self):
h = self.h - self.gap
n = divmod(self.i, h)[0]
self.a = h * n
self.z = self.a + h
def display(self):
if self.active:
attr_box = 'box'
attr_boxheader = 'boxheader'
else:
attr_box = attr_boxheader = 'body'
vline = u'\u2502' # "â"
content = []
t = urwid.Text([('boxtitle', 'Name'.center(self.SNAME)),
(attr_box, vline),
('boxtitle', 'Size'.center(self.SSIZE)),
(attr_box, vline),
('boxtitle', 'Date'.center(self.SDATE))])
content.append(t)
i = 0
for name, size, mtime, mode, uid, gid, typ in self.files[self.a:self.z+1]:
if self.active and (self.a+i == self.i):
attr = 'cursor'
attr2 = 'boxcursor'
else:
attr = 'body'
attr2 = attr_box
if name in self.SELECTED:
attr += 'selected'
t = urwid.Text([(attr, (typ + name).ljust(self.SNAME)[:self.SNAME]),
(attr2, vline),
(attr, size2str(size).rjust(self.SSIZE)),
(attr2, vline),
(attr, time2str(mtime).ljust(self.SDATE))],
wrap='clip')
content.append(t)
i += 1
while i < self.h:
t = urwid.Text([('body', ' '.ljust(self.SNAME)),
(attr_box, vline),
('body', ' '.ljust(self.SSIZE)),
(attr_box, vline),
('body', ' '.ljust(self.SDATE))],
wrap='clip')
content.append(t)
i += 1
w = urwid.SimpleListWalker(content)
lb = urwid.ListBox(w)
# lb = urwid.Pile(content) # DOES NOT WORK!!!!!!!!!
v = MyBox(lb, self.w, self.h, box_attr=attr_box,
header=self.path, header_attr=attr_boxheader)
return v
def get_filepath(self):
return os.path.join(self.path, self.files[self.i][0])
def toggle_active(self):
self.active = not self.active
######################################################################
##### Main
def init_ui():
palette = [
('header', 'brown', 'default'),
('body', 'light gray', 'default', 'standout'),
('boxheader', 'light red', 'default'),
('boxheader2', 'body'),
('boxtitle', 'white', 'default', ('bold','standout')),
('box', 'dark green', 'default'),
('box2', 'body'),
('boxcursor', 'dark blue', 'dark cyan'),
('bodyselected', 'yellow', 'default'),
('cursor', 'dark blue', 'dark cyan'),
('cursorselected', 'yellow', 'dark cyan')]
ui.register_palette(palette)
def display_ui(panel):
header = urwid.Text(' Testing this stuff ')
header = urwid.AttrWrap(header, 'header')
body = urwid.Columns([c1.display(), c2.display()])
body = urwid.AttrWrap(body, 'body')
txt = urwid.Text('File: %5d of %5d Path: %s' % \
(panel.i+1, panel.numfiles, panel.get_filepath()),
wrap='clip')
cmd = urwid.Edit('[EMAIL PROTECTED] %s]$ ' % (os.path.basename(panel.path) or '/'), '')
cmd = urwid.AttrWrap(cmd, 'body')
pile = urwid.Pile([txt, cmd])
footer = urwid.AttrWrap(pile, 'header')
frame = urwid.Frame(body, header=header, footer=footer)
canvas = frame.render(ui_size, focus=1)
ui.draw_screen(ui_size, canvas)
def parse_args():
import sys
if len(sys.argv) == 1:
path1 = path2 = os.path.expanduser('~')
elif len(sys.argv) == 2:
path1 = path2 = sys.argv[1]
else:
path1 = sys.argv[1]
path2 = sys.argv[2]
return path1, path2
def run():
global c1, c2, ui_size
init_ui()
ui_size = ui.get_cols_rows()
cols, rows = ui_size[0], ui_size[1]
path1, path2 = parse_args()
c1 = Panel(path1, (cols/2, rows-3))
c2 = Panel(path2, (cols/2, rows-3))
c1.toggle_active()
panel = c1
display_ui(panel)
quit = False
while not quit:
keys = None
while not keys:
keys = ui.get_input()
for k in keys:
if k == 'q':
quit = True
break
elif k == 'home':
panel.i = 0
panel.recalc_display_area()
elif k == 'end':
panel.i = len(panel.files)-1
panel.recalc_display_area()
elif k == 'up':
if panel.i > 0:
panel.i -= 1
if panel.i < panel.a:
panel.recalc_display_area()
elif k == 'down':
if panel.i < panel.numfiles-1:
panel.i += 1
if panel.i+1 > panel.z:
panel.recalc_display_area()
elif k == 'page up':
panel.i -= panel.h + panel.gap
if panel.i < 0:
panel.i = 0
panel.recalc_display_area()
elif k == 'page down':
panel.i += panel.h - panel.gap
if panel.i > panel.numfiles - 1:
panel.i = panel.numfiles - 1
panel.recalc_display_area()
elif k == 'tab':
if panel == c1:
panel = c2
else:
panel = c1
c1.toggle_active()
c2.toggle_active()
elif k == 'left':
if panel.path == os.sep:
continue
newpath = os.path.dirname(panel.path)
if panel == c1:
c1 = Panel(newpath, (ui_size[0]/2, ui_size[1]-3))
del(panel)
panel = c1
else:
c2 = Panel(newpath, (ui_size[0]/2, ui_size[1]-3))
del(panel)
panel = c2
panel.toggle_active()
elif k == 'right':
file = panel.files[panel.i]
if file[-1] == '/':
newpath = os.path.join(panel.path, file[0])
newpath = os.path.normpath(newpath)
if panel == c1:
c1 = Panel(newpath, (ui_size[0]/2, ui_size[1]-3))
del(panel)
panel = c1
else:
c2 = Panel(newpath, (ui_size[0]/2, ui_size[1]-3))
del(panel)
panel = c2
panel.toggle_active()
elif k == 'window resize':
ui_size = ui.get_cols_rows()
cols, rows = ui_size[0], ui_size[1]
c1.resize((ui_size[0]/2, ui_size[1]-3))
c2.resize((ui_size[0]/2, ui_size[1]-3))
display_ui(panel)
display_ui(panel)
def init():
global ui, ui_size
ui = urwid.raw_display.Screen()
ui_size = ui.get_cols_rows()
ui.run_wrapper(run)
if __name__ == '__main__':
init()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import urwid
import urwid.raw_display
ui = None
size = None
def display():
contents = []
for i in xrange(15):
t = urwid.Text('Line #%d' % i)
contents.append(t)
header = urwid.Text('Testing this stuff', align='center')
body = urwid.Filler(urwid.Pile(contents))
footer = urwid.Text('More testing')
frame = urwid.Frame(body, header=header, footer=footer)
canvas = frame.render(size)
ui.draw_screen(size, canvas)
def run():
display()
while True:
keys = None
while not keys:
keys = ui.get_input()
for k in keys:
if k == 'q':
return
def init():
global ui, size
ui = urwid.raw_display.Screen()
size = ui.get_cols_rows()
ui.run_wrapper(run)
if __name__ == '__main__':
init()
_______________________________________________
Urwid mailing list
[email protected]
http://lists.excess.org/mailman/listinfo/urwid