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 Urwid@lists.excess.org http://lists.excess.org/mailman/listinfo/urwid