diff options
-rw-r--r-- | reference.html | 100 | ||||
-rw-r--r-- | setup.py | 4 | ||||
-rw-r--r-- | tutorial.html | 646 | ||||
-rwxr-xr-x | urwid/curses_display.py | 5 | ||||
-rwxr-xr-x | urwid/html_fragment.py | 5 | ||||
-rw-r--r-- | urwid/listbox.py | 85 | ||||
-rwxr-xr-x | urwid/widget.py | 71 |
7 files changed, 869 insertions, 47 deletions
diff --git a/reference.html b/reference.html index fa39600..551fb85 100644 --- a/reference.html +++ b/reference.html @@ -14,11 +14,12 @@ <div style="text-align: center;"> <a href="http://excess.org/urwid/">Urwid Home Page</a> / <a href="http://excess.org/urwid/examples.html">Example Screenshots</a> / -<a href="http://excess.org/urwid/wexamples.html">Wide Character Screenshots</a> / +<a href="http://excess.org/urwid/wexamples.zh-cn.html">Wide Character Screenshots</a> / +<a href="tutorial.html">Tutorial</a> / Reference </div> <br> -<div class="l1">User interface library wrapper</div><div class="l2"><a href="#curses_display.Screen">curses_display.Screen</a></div><div class="l1">Top-level widgets</div><div class="l2"><a href="#BoxWidget">BoxWidget</a></div><div class="l2"><a href="#Frame">Frame</a></div><div class="l2"><a href="#ListBox">ListBox</a></div><div class="l2"><a href="#SimpleListWalker">SimpleListWalker</a></div><div class="l1">Content widgets</div><div class="l2"><a href="#FlowWidget">FlowWidget</a></div><div class="l2"><a href="#Text">Text</a></div><div class="l2"><a href="#Edit">Edit</a></div><div class="l2"><a href="#IntEdit">IntEdit</a></div><div class="l1">Composite widgets</div><div class="l2"><a href="#Columns">Columns</a></div><div class="l2"><a href="#Pile">Pile</a></div><div class="l1">Decorations</div><div class="l2"><a href="#AttrWrap">AttrWrap</a></div><div class="l2"><a href="#Divider">Divider</a></div><div class="l1">Canvas painting</div><div class="l2"><a href="#canvas.Canvas">canvas.Canvas</a></div><div class="l2"><a href="#-canvas.CanvasCombine">canvas.CanvasCombine</a></div><div class="l2"><a href="#-canvas.CanvasJoin">canvas.CanvasJoin</a></div><div class="l1">Custom formatting rules</div><div class="l2"><a href="#-util.register_align_mode">util.register_align_mode</a></div><div class="l2"><a href="#-util.register_wrap_mode">util.register_wrap_mode</a></div><div class="l2"><a href="#-util.set_double_byte_encoding">util.set_double_byte_encoding</a></div><div class="l1">Screen capture</div><div class="l2"><a href="#-html_fragment.screenshot_init">html_fragment.screenshot_init</a></div><div class="l2"><a href="#-html_fragment.screenshot_collect">html_fragment.screenshot_collect</a></div><div class="l2"><a href="#html_fragment.HtmlGenerator">html_fragment.HtmlGenerator</a></div><h2>User interface library wrapper</h2><h3><strong>curses_display.Screen</strong> = <a name="curses_display.Screen">class Screen</a></h3>Methods defined here:<br> +<table width="100%"><tr><td width="33%" valign="top"><div class="l1">User interface wrapper</div><div class="l2"><a href="#curses_display.Screen">curses_display.Screen</a></div><div class="l1">Top-level widgets</div><div class="l2"><a href="#BoxWidget">BoxWidget</a></div><div class="l2"><a href="#Frame">Frame</a></div><div class="l2"><a href="#Filler">Filler</a></div><div class="l2"><a href="#ListBox">ListBox</a></div><div class="l2"><a href="#SimpleListWalker">SimpleListWalker</a></div><div class="l1">Decorations</div><div class="l2"><a href="#AttrWrap">AttrWrap</a></div><div class="l2"><a href="#Divider">Divider</a></div></td><td width="33%" valign="top"><div class="l1">Content widgets</div><div class="l2"><a href="#FlowWidget">FlowWidget</a></div><div class="l2"><a href="#Text">Text</a></div><div class="l2"><a href="#Edit">Edit</a></div><div class="l2"><a href="#IntEdit">IntEdit</a></div><div class="l1">Composite widgets</div><div class="l2"><a href="#Columns">Columns</a></div><div class="l2"><a href="#Pile">Pile</a></div><div class="l1">Canvas painting</div><div class="l2"><a href="#Canvas">Canvas</a></div><div class="l2"><a href="#-CanvasCombine">CanvasCombine</a></div><div class="l2"><a href="#-CanvasJoin">CanvasJoin</a></div></td><td width="33%" valign="top"><div class="l1">Custom formatting rules</div><div class="l2"><a href="#-util.register_align_mode">util.register_align_mode</a></div><div class="l2"><a href="#-util.register_wrap_mode">util.register_wrap_mode</a></div><div class="l2"><a href="#-util.set_double_byte_encoding">util.set_double_byte_encoding</a></div><div class="l1">Screen capture</div><div class="l2"><a href="#-html_fragment.screenshot_init">html_fragment.screenshot_init</a></div><div class="l2"><a href="#-html_fragment.screenshot_collect">html_fragment.screenshot_collect</a></div><div class="l2"><a href="#html_fragment.HtmlGenerator">html_fragment.HtmlGenerator</a></div></td></tr></table><h2>User interface wrapper</h2><h3><strong>curses_display.Screen</strong> = <a name="curses_display.Screen">class Screen</a></h3>Methods defined here:<br> <dl><dt><a name="Screen-__init__"><strong>__init__</strong></a>(self)</dt></dl> <dl><dt><a name="Screen-draw_screen"><strong>draw_screen</strong></a>(self, (cols, rows), r)</dt><dd><tt>Paint screen with rendered canvas.</tt></dd></dl> @@ -53,7 +54,8 @@ Double-byte characters: "\xa1\xea", "\xb2\xd4"</tt></dd></d <dl><dt><a name="Screen-register_palette"><strong>register_palette</strong></a>(self, l)</dt><dd><tt>Register a list of palette entries.<br> <br> -l -- list of (name, foreground, background) or<br> +l -- list of (name, foreground, background, mono),<br> + (name, foreground, background) or<br> (name, same_as_other_name) palette entries.<br> <br> calls self.<strong>register_palette_entry</strong> for each item in l</tt></dd></dl> @@ -90,6 +92,16 @@ footer -- a flow widget for below the bo <dl><dt><a name="Frame-render"><strong>render</strong></a>(self, (maxcol, maxrow), focus<font color="#909090">=False</font>)</dt><dd><tt>Render frame and return it.</tt></dd></dl> +<h3><a name="Filler">class <strong>Filler</strong></a>(BoxWidget)</h3>Methods defined here:<br> +<dl><dt><a name="Filler-__init__"><strong>__init__</strong></a>(self, body, valign<font color="#909090">='middle'</font>)</dt><dd><tt>body -- a flow widget to be filled around<br> +valign -- vertical alignment: "top", "middle" or "bottom"</tt></dd></dl> + +<dl><dt><a name="Filler-body_position"><strong>body_position</strong></a>(self, (maxcol, maxrow), focus, rows<font color="#909090">=None</font>, cy<font color="#909090">=None</font>)</dt><dd><tt>Return the row offset (+ve) or reduction (-ve) of self.<strong>body</strong>.</tt></dd></dl> + +<dl><dt><a name="Filler-keypress"><strong>keypress</strong></a>(self, (maxcol, maxrow), key)</dt><dd><tt>Pass keypress to self.<strong>body</strong>.</tt></dd></dl> + +<dl><dt><a name="Filler-render"><strong>render</strong></a>(self, (maxcol, maxrow), focus<font color="#909090">=False</font>)</dt><dd><tt>Render self.<strong>body</strong> with space around it to fill box size.</tt></dd></dl> + <h3><a name="ListBox">class <strong>ListBox</strong></a>(BoxWidget)</h3>Methods defined here:<br> <dl><dt><a name="ListBox-__init__"><strong>__init__</strong></a>(self, body)</dt><dd><tt>body -- list or a SimpleListWalker-like object that contains<br> widgets to be displayed inside the list box</tt></dd></dl> @@ -127,6 +139,8 @@ snap_rows -- the maximum number of extra rows convenience function for checking whether the top and bottom<br> of the list are visible</tt></dd></dl> +<dl><dt><a name="ListBox-get_focus"><strong>get_focus</strong></a>(self)</dt><dd><tt>Return a (focus widget, focus position) tuple.</tt></dd></dl> + <dl><dt><a name="ListBox-keypress"><strong>keypress</strong></a>(self, (maxcol, maxrow), key)</dt><dd><tt>Move selection through the list elements scrolling when <br> necessary. 'up' and 'down' are first passed to widget in focus<br> in case that widget can handle them. 'page up' and 'page down'<br> @@ -140,6 +154,12 @@ Keystrokes handled by this widget are:<br> <dl><dt><a name="ListBox-render"><strong>render</strong></a>(self, (maxcol, maxrow), focus<font color="#909090">=False</font>)</dt><dd><tt>Render listbox and return canvas.</tt></dd></dl> +<dl><dt><a name="ListBox-set_focus"><strong>set_focus</strong></a>(self, position, coming_from<font color="#909090">=None</font>)</dt><dd><tt>Set the focus position and try to keep the old focus in view.<br> + <br> +position -- a position compatible with self.<strong>body</strong>.set_focus<br> +coming_from -- set to 'above' or 'below' if you know that<br> + old position is above or below the new position.</tt></dd></dl> + <dl><dt><a name="ListBox-shift_focus"><strong>shift_focus</strong></a>(self, (maxcol, maxrow), offset_inset)</dt><dd><tt>Move the location of the current focus relative to the top.<br> <br> offset_inset -- either the number of rows between the <br> @@ -162,6 +182,40 @@ offset_inset -- either the number of rows bet <dl><dt><a name="SimpleListWalker-set_focus"><strong>set_focus</strong></a>(self, position)</dt><dd><tt>Set focus position.</tt></dd></dl> +<h2>Decorations</h2><h3><a name="AttrWrap">class <strong>AttrWrap</strong></a></h3>Methods defined here:<br> +<dl><dt><a name="AttrWrap-__init__"><strong>__init__</strong></a>(self, w, attr, focus_attr<font color="#909090">=None</font>)</dt><dd><tt>w -- widget to wrap<br> +attr -- attribute to apply to w<br> +focus_attr -- attribute to apply when in focus, if None use attr<br> + <br> +Copy w.get_cursor_coords, w.move_cursor_to_coords, <br> +w.get_pref_col functions to this widget if they exist.</tt></dd></dl> + +<dl><dt><a name="AttrWrap-keypress"><strong>keypress</strong></a>(self, maxvals, key)</dt><dd><tt>Pass keypress to self.<strong>w</strong>.</tt></dd></dl> + +<dl><dt><a name="AttrWrap-render"><strong>render</strong></a>(self, size, focus<font color="#909090">=False</font>)</dt><dd><tt>Render self.<strong>w</strong> and apply attribute. Return canvas.<br> + <br> +size -- (maxcol,) if self.<strong>w</strong> contains a flow widget or<br> + (maxcol, maxrow) if it contains a box widget.</tt></dd></dl> + +<dl><dt><a name="AttrWrap-rows"><strong>rows</strong></a>(self, (maxcol,), focus<font color="#909090">=False</font>)</dt><dd><tt>Return the rows needed for self.<strong>w</strong>.</tt></dd></dl> + +<dl><dt><a name="AttrWrap-selectable"><strong>selectable</strong></a>(self)</dt><dd><tt>Return the selectable value of self.<strong>w</strong>.</tt></dd></dl> + +<h3><a name="Divider">class <strong>Divider</strong></a>(FlowWidget)</h3>Methods defined here:<br> +<dl><dt><a name="Divider-__init__"><strong>__init__</strong></a>(self, div_char<font color="#909090">=' '</font>, top<font color="#909090">=0</font>, bottom<font color="#909090">=0</font>)</dt><dd><tt>div_char -- character to repeat across line<br> +top -- number of blank lines above<br> +bottom -- number of blank lines below</tt></dd></dl> + +<dl><dt><a name="Divider-render"><strong>render</strong></a>(self, (maxcol,), focus<font color="#909090">=False</font>)</dt><dd><tt>Render the divider as a canvas and return it.</tt></dd></dl> + +<dl><dt><a name="Divider-rows"><strong>rows</strong></a>(self, (maxcol,), focus<font color="#909090">=False</font>)</dt><dd><tt>Return the number of lines that will be rendered.</tt></dd></dl> + +<hr> +Methods inherited from FlowWidget:<br> +<dl><dt><a name="Divider-keypress"><strong>keypress</strong></a>(self, (maxcol,), key)</dt><dd><tt>Return key. No keys are handled by default.</tt></dd></dl> + +<dl><dt><a name="Divider-selectable"><strong>selectable</strong></a>(self)</dt><dd><tt>Return False. Not selectable by default.</tt></dd></dl> + <h2>Content widgets</h2><h3><a name="FlowWidget">class <strong>FlowWidget</strong></a></h3>Methods defined here:<br> <dl><dt><a name="FlowWidget-keypress"><strong>keypress</strong></a>(self, (maxcol,), key)</dt><dd><tt>Return key. No keys are handled by default.</tt></dd></dl> @@ -430,41 +484,7 @@ Methods inherited from FlowWidget:<br> <dl><dt><a name="Pile-selectable"><strong>selectable</strong></a>(self)</dt><dd><tt>Return False. Not selectable by default.</tt></dd></dl> -<h2>Decorations</h2><h3><a name="AttrWrap">class <strong>AttrWrap</strong></a></h3>Methods defined here:<br> -<dl><dt><a name="AttrWrap-__init__"><strong>__init__</strong></a>(self, w, attr, focus_attr<font color="#909090">=None</font>)</dt><dd><tt>w -- widget to wrap<br> -attr -- attribute to apply to w<br> -focus_attr -- attribute to apply when in focus, if None use attr<br> - <br> -Copy w.get_cursor_coords, w.move_cursor_to_coords, <br> -w.get_pref_col functions to this widget if they exist.</tt></dd></dl> - -<dl><dt><a name="AttrWrap-keypress"><strong>keypress</strong></a>(self, maxvals, key)</dt><dd><tt>Pass keypress to self.<strong>w</strong>.</tt></dd></dl> - -<dl><dt><a name="AttrWrap-render"><strong>render</strong></a>(self, size, focus<font color="#909090">=False</font>)</dt><dd><tt>Render self.<strong>w</strong> and apply attribute. Return canvas.<br> - <br> -size -- (maxcol,) if self.<strong>w</strong> contains a flow widget or<br> - (maxcol, maxrow) if it contains a box widget.</tt></dd></dl> - -<dl><dt><a name="AttrWrap-rows"><strong>rows</strong></a>(self, (maxcol,), focus<font color="#909090">=False</font>)</dt><dd><tt>Return the rows needed for self.<strong>w</strong>.</tt></dd></dl> - -<dl><dt><a name="AttrWrap-selectable"><strong>selectable</strong></a>(self)</dt><dd><tt>Return the selectable value of self.<strong>w</strong>.</tt></dd></dl> - -<h3><a name="Divider">class <strong>Divider</strong></a>(FlowWidget)</h3>Methods defined here:<br> -<dl><dt><a name="Divider-__init__"><strong>__init__</strong></a>(self, div_char<font color="#909090">=' '</font>, top<font color="#909090">=0</font>, bottom<font color="#909090">=0</font>)</dt><dd><tt>div_char -- character to repeat across line<br> -top -- number of blank lines above<br> -bottom -- number of blank lines below</tt></dd></dl> - -<dl><dt><a name="Divider-render"><strong>render</strong></a>(self, (maxcol,), focus<font color="#909090">=False</font>)</dt><dd><tt>Render the divider as a canvas and return it.</tt></dd></dl> - -<dl><dt><a name="Divider-rows"><strong>rows</strong></a>(self, (maxcol,), focus<font color="#909090">=False</font>)</dt><dd><tt>Return the number of lines that will be rendered.</tt></dd></dl> - -<hr> -Methods inherited from FlowWidget:<br> -<dl><dt><a name="Divider-keypress"><strong>keypress</strong></a>(self, (maxcol,), key)</dt><dd><tt>Return key. No keys are handled by default.</tt></dd></dl> - -<dl><dt><a name="Divider-selectable"><strong>selectable</strong></a>(self)</dt><dd><tt>Return False. Not selectable by default.</tt></dd></dl> - -<h2>Canvas painting</h2><h3><strong>canvas.Canvas</strong> = <a name="canvas.Canvas">class Canvas</a></h3>Methods defined here:<br> +<h2>Canvas painting</h2><h3><a name="Canvas">class <strong>Canvas</strong></a></h3>Methods defined here:<br> <dl><dt><a name="Canvas-__init__"><strong>__init__</strong></a>(self, text<font color="#909090">=None</font>, attr<font color="#909090">=None</font>, cursor<font color="#909090">=None</font>)</dt><dd><tt>text -- list of strings, one for each line<br> attr -- list of run length encoded attributes for text<br> cursor -- (x,y) of cursor or None</tt></dd></dl> @@ -482,8 +502,8 @@ count -- number of lines to keep, or Non <br> end -- number of lines to remove from the end</tt></dd></dl> -<dl><dt><a name="-canvas.CanvasCombine"><strong>canvas.CanvasCombine</strong></a> = CanvasCombine(l)</dt><dd><tt>Stack canvases in l vertically and return resulting canvas.</tt></dd></dl> -<dl><dt><a name="-canvas.CanvasJoin"><strong>canvas.CanvasJoin</strong></a> = CanvasJoin(l)</dt><dd><tt>Join canvases in l horizontally. Return result.<br> +<dl><dt><a name="-CanvasCombine"><strong>CanvasCombine</strong></a>(l)</dt><dd><tt>Stack canvases in l vertically and return resulting canvas.</tt></dd></dl> +<dl><dt><a name="-CanvasJoin"><strong>CanvasJoin</strong></a>(l)</dt><dd><tt>Join canvases in l horizontally. Return result.<br> <br> l -- [canvas1, colnum2, canvas2, ... ,colnumN, canvasN]<br> colnumX is the column number to start for canvasX</tt></dd></dl> @@ -23,7 +23,7 @@ from distutils.core import setup import os -release = "0.8.4" #os.popen("make -s release").read().strip() +release = "0.8.5" #os.popen("make -s release").read().strip() setup_d = { 'name':"urwid", @@ -35,7 +35,7 @@ setup_d = { 'license':"LGPL", 'keywords':"curses ui widget scroll listbox interface text layout", 'platforms':"unix-like", - 'description':"A curses-based UI library featuring fluid interface resizing, CJK suppport, multiple text layouts, simple attribute markup, powerful scrolling list boxes and flexible edit boxes.", + 'description':"A curses-based UI library featuring fluid interface resizing, CJK support, multiple text layouts, simple attribute markup, powerful scrolling list boxes and flexible edit boxes.", 'long_description':""" Urwid is a curses-based user interface library. It includes many features useful for text console application developers including: diff --git a/tutorial.html b/tutorial.html new file mode 100644 index 0000000..7191728 --- /dev/null +++ b/tutorial.html @@ -0,0 +1,646 @@ +<html> +<head> +<title>Urwid Tutorial</title> +<style type="text/css"> + h1 { text-align: center; } + h2 { margin: 40px 0 0 0; padding: 10px; background: #6d96e8;} + h3 { margin: 0 0 3px 0; padding: 12px 6px 6px 6px; background: #efef96;} + .code { background: #dddddd; padding: 5px; margin: 7px 20px; } + .l1 { margin: 12px 0 0 0; } + .l2 { margin-left: 20px; } + .shot { padding: 5px 20px 5px 0px; float: left; } +</style> +<body> +<h1>Urwid Tutorial</h1> + +<div style="text-align: center;"> +<a href="http://excess.org/urwid/">Urwid Home Page</a> / +<a href="http://excess.org/urwid/examples.html">Example Screenshots</a> / +<a href="http://excess.org/urwid/wexamples.zh-cn.html">Wide Character Screenshots</a> / +Tutorial / +<a href="reference.html">Reference</a> +</div> +<br> +<b>Note:</b> This tutorial requires Urwid 1.8.5 or later.<br> + +<table width="100%"><tr><td width="50%" valign="top"> +<div class="l1">1. Hello World</div> +<div class="l2"><a href="#1.1">1.1. Minimal Urwid Application</a></div> +<div class="l2"><a href="#1.2">1.2. Text and Filler Widgets</a></div> +<div class="l2"><a href="#1.3">1.3. AttrWrap Widgets and Text Attributes</a></div> +<div class="l2"><a href="#1.4">1.4. Live Resizing</a></div> +</td><td width="50%" valign="top"> +<div class="l1">2. Conversation</div> +<div class="l2"><a href="#2.1">2.1. Edit Widgets</a></div> +<div class="l2"><a href="#2.2">2.2. Frame and ListBox Widgets</a></div> +<div class="l2"><a href="#2.3">2.3. ListBox Widgets Continued</a></div> +</td></tr></table> + +<h2>1. Hello World</h2> + +<h3><a name="1.1">1.1. Minimal Urwid Application</a></h3> + +This program displays the string "Hello World" in the top left corner +of the screen and waits for a keypress before exiting. + +<pre class="code"> +import urwid.curses_display +import urwid + +ui = urwid.curses_display.Screen() + +def run(): + canvas = urwid.Canvas( ["Hello World"] ) + ui.draw_screen( (20, 1), canvas ) + + while not ui.get_input(): + pass + +ui.run_wrapper( run ) +</pre> + +<ul> +<li>The <a href="reference.html#curses_display.Screen">curses_display.Screen</a> +class provides access to the curses library. Its member function +<a href="reference.html#Screen-run_wrapper">run_wrapper</a> initializes +curses full-screen mode and then calls the "run" function passed. It will +also take care of restoring the screen when the "run" function exits. +<li>A <a href="reference.html#Canvas">Canvas</a> is created +containing one row with the string "Hello World". +<li>The canvas is passed to the +<a href="reference.html#Screen-draw_screen">draw_screen</a> function along +with a fixed screen size of 20 columns and 1 row. It is likely that the +terminal window this program is run from is larger than 20 by 1, so the +text will appear in the top left corner. +<li>The <a href="reference.html#Screen-get_input">get_input</a> function +is then called until it returns something. It must be called in a loop +because it will time out after one second and return an empty list. +</ul> + +Creating canvases directly is generally only done when +writing custom widget classes. Note that the draw_screen +function must be passed a canvas and a screen size that matches it. + +<div align="center"><pre><span style="color:silver;background:black">Hello World </span> +</pre></div> +<br> +<br> + +<h3><a name="1.2">1.2. Text and Filler Widgets</a></h3> + +This program displays the string "Hello World" in the center of the screen +and waits for a keypress before exiting. + +<pre class="code"> +import urwid.curses_display +import urwid + +ui = urwid.curses_display.Screen() + +def run(): + cols, rows = ui.get_cols_rows() + + txt = urwid.Text("Hello World", align="center") + fill = urwid.Filler( txt ) + + canvas = fill.render( (cols, rows) ) + ui.draw_screen( (cols, rows), canvas ) + + while not ui.get_input(): + pass + +ui.run_wrapper( run ) +</pre> + +<ul> +<li><a href="reference.html#Screen-get_cols_rows">get_cols_rows</a> +is used to get the dimensions from the terminal and store them as "cols" +and "rows". +<li>A <a href="reference.html#Text">Text</a> widget is created containing +the string "Hello World". It is set to display with "center" alignment. +Text widgets are a kind of <a href="reference.html#FlowWidget">FlowWidget</a>. +Flow widgets can fill one or more rows, depending on their content and +the number of columns available. Text widgets use more than one row when +they contain newline characters or when the text must be split across rows. +<li>A <a href="reference.html#Filler">Filler</a> widget is created to +wrap the text widget. Filler widgets are a kind of +<a href="reference.html#BoxWidget">BoxWidget</a>. Box widgets have a fixed +number of columns and rows displayed. This widget will pad the "Hello World" +text widget until it fills the required number of rows. +<li>A canvas is created by calling the +<a href="reference.html#Filler-render">render</a> function on the topmost +widget. The filler render function will call the render function +of the "Hello World" text widget and combine its canvas with +padding rows to fill the terminal window. +</ul> + +Flow widgets and box widgets are not interchangeable. The first parameter +of the render function of a box widget is a two-element tuple (columns, +rows) and the first parameter of the render function of a flow widget is +a one-element tuple (columns, ). +This difference makes sure that when the wrong type of widget is used, +such as a box widget inside a filler widget, a ValueError exception will +be thrown. + +<div align="center"><pre><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span><span style="color:silver;background:black">Hello World </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +</pre></div> +<br> +<br> + +<h3><a name="1.3">1.3. AttrWrap Widgets and Text Attributes</a></h3> + +This program displays the string "Hello World" in the center of the screen. +It uses different attributes used for the text, the space on either side +of the text and the space above and below the text. It then and waits for +a keypress before exiting. + +<pre class="code"> +import urwid.curses_display +import urwid + +ui = urwid.curses_display.Screen() + +ui.register_palette( [ + ('banner', 'black', 'light gray', ('standout', 'underline')), + ('streak', 'black', 'dark red', 'standout'), + ('bg', 'black', 'dark blue'), + ] ) + +def run(): + cols, rows = ui.get_cols_rows() + + txt = urwid.Text(('banner', " Hello World "), align="center") + wrap1 = urwid.AttrWrap( txt, 'streak' ) + fill = urwid.Filler( wrap1 ) + wrap2 = urwid.AttrWrap( fill, 'bg' ) + + canvas = wrap2.render( (cols, rows) ) + ui.draw_screen( (cols, rows), canvas ) + + while not ui.get_input(): + pass + +ui.run_wrapper( run ) +</pre> + +<ul> +<li>After creating the +<a href="reference.html#curses_display.Screen">curses_display.Screen</a> object +and before calling <a href="reference.html#Screen-run_wrapper">run_wrapper</a>, +<a href="reference.html#Screen-register_palette">register_palette</a> is called +to set up some attributes: + <ul> + <li>"banner" is black text on a light gray background, or reversed attributes + and underlined in monochrome mode + <li>"streak" is black text on a dark red background, or reversed attributes + in monochrome mode + <li>"bg" is black text on a dark blue background, or normal in + monochrome mode + </ul> +<li>A <a href="reference.html#Text">Text</a> widget is created containing +the string " Hello World " with attribute "banner". The attributes of text +in a Text widget is set by using a (attribute, text) tuple instead of a +simple text string. +<li>An <a href="reference.html#AttrWrap">AttrWrap</a> widget is created to +wrap the text widget with attribute "streak". AttrWrap widgets will set +the attribute of everything that they wrap that does not already have an +attribute set. In this case the text has an attribute, so only the areas +around the text used for alignment will be have the new attribute. +<li>A <a href="reference.html#Filler">Filler</a> widget is created to +wrap the AttrWrap widget and fill the rows above and below it. +<li>A second <a href="reference.html#AttrWrap">AttrWrap</a> widget is created to +wrap the filler widget with attribute "bg". +<li>A canvas is created by calling the +<a href="reference.html#AttrWrap-render">render</a> function on the topmost +widget. +</ul> + +AttrWrap widgets will behave like flow widgets or box widgets depending on +how they are called. The filler widget treats the first AttrWrap widget as +a flow widget when calling its render function, so the AttrWrap widget calls +the text widget's render function the same way. The second AttrWrap is +used as the topmost widget and treated as a box widget, so it calls the +filler render function in the same way. + +<div align="center"><pre><span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#c00000"> </span><span style="color:black;background:silver"> Hello World </span><span style="color:black;background:#c00000"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +</pre></div> +<br> +<br> + +<h3><a name="1.4">1.4. Live Resizing</a></h3> + +This program displays the string "Hello World" in the center of the screen. +It uses different attributes used for the text, the space on either side +of the text and the space above and below the text. When the window is +resized it will repaint the screen, and it will exit when "Q" is pressed. + +<pre class="code"> +import urwid.curses_display +import urwid + +ui = urwid.curses_display.Screen() + +ui.register_palette( [ + ('banner', 'black', 'light gray', ('standout', 'underline')), + ('streak', 'black', 'dark red', 'standout'), + ('bg', 'black', 'dark blue'), + ] ) + +def run(): + cols, rows = ui.get_cols_rows() + + txt = urwid.Text(('banner', " Hello World "), align="center") + wrap1 = urwid.AttrWrap( txt, 'streak' ) + fill = urwid.Filler( wrap1 ) + wrap2 = urwid.AttrWrap( fill, 'bg' ) + + while True: + canvas = wrap2.render( (cols, rows) ) + ui.draw_screen( (cols, rows), canvas ) + + keys = ui.get_input() + if "q" in keys or "Q" in keys: + break + if "window resize" in keys: + cols, rows = ui.get_cols_rows() + +ui.run_wrapper( run ) +</pre> + +The <a href="reference.html#Screen-get_input">get_input</a> function will +return "window resize" among keys pressed when the window is resized. It +is a good idea to check for uppercase and lowercase letters on input +because it is easy to users to forget that Caps Lock is on. + +<div class="shot"><pre><span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#c00000"> </span><span style="color:black;background:silver"> Hello World </span><span style="color:black;background:#c00000"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +</pre></div> +<div class="shot"><pre><span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#c00000"> </span><span style="color:black;background:silver"> Hello</span><span style="color:black;background:#c00000"> </span> +<span style="color:black;background:#c00000"> </span><span style="color:black;background:silver">World </span><span style="color:black;background:#c00000"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#0000c0"> </span> +</pre></div> +<div class="shot"><pre><span style="color:black;background:#0000c0"> </span> +<span style="color:black;background:#c00000"> </span><span style="color:black;background:silver"> Hello World </span><span style="color:black;background:#c00000"> </span> +<span style="color:black;background:#0000c0"> </span> +</pre></div> +<div class="shot"><pre><span style="color:black;background:#c00000"> </span><span style="color:black;background:silver"> Hello World </span><span style="color:black;background:#c00000"> </span> +<span style="color:black;background:#0000c0"> </span> +</pre></div> +<br clear="left"> +<br> + +<h2>2. Conversation</h2> + +<h3><a name="2.1">2.1. Edit Widgets</a></h3> + +This program asks for your name then responds "Nice to meet you, (your name)." + +<pre class="code"> +import urwid.curses_display +import urwid + +ui = urwid.curses_display.Screen() + +def run(): + cols, rows = ui.get_cols_rows() + + ask = urwid.Edit("What is your name?\n") + fill = urwid.Filler( ask ) + reply = None + + while True: + canvas = fill.render( (cols, rows), focus=True ) + ui.draw_screen( (cols, rows), canvas ) + + keys = ui.get_input() + for k in keys: + if k == "window resize": + cols, rows = ui.get_cols_rows() + continue + if reply is not None: + return + if k == "enter": + reply = urwid.Text( "Nice to meet you,\n"+ + ask.edit_text+"." ) + fill.body = reply + fill.keypress( (cols, rows), k ) + +ui.run_wrapper( run ) +</pre> + +<ul> +<li>An <a href="reference.html#Edit">Edit</a> widget is created with the +caption "What is your name?". A newline at the end of the caption makes +the user input start on the next row. +<li>A <a href="reference.html#Filler">Filler</a> widget is created to wrap +the edit widget. Its <a href="reference.html#Filler-render">render</a> +function is called to create the canvas. The render function is called with +the optional parameter "focus" set to True. This parameter allows the +wrapped Edit widget to render its cursor. +<li>Keys are processed one at a time. Most keys are sent to the Filler widget's +<a href="reference.html#Filler-keypress">keypress</a> function which will +call the Edit widget's <a href="reference.html#Edit-keypress">keypress</a> +function to handle the key. +<li>Once the ENTER key is pressed the wrapped object in the Filler widget +is changed to a reply text. +<li>Any keypress then causes the program to exit. +</ul> + +The Edit widget has many capabilities. It lets you make corrections and move +the cursor around with the HOME, END and arrow keys. It is based on the Text +widget so it supports the same wrapping and alignment modes. + +<div class="shot"><pre><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">What is your name? </span> +<span style="color:silver;background:black"><u> </u> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +</pre></div> +<div class="shot"><pre><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">What is your name? </span> +<span style="color:silver;background:black">Arthur, King of the </span> +<span style="color:silver;background:black">Britons<u> </u> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +</pre></div> +<div class="shot"><pre><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">Nice to meet you, </span> +<span style="color:silver;background:black">Arthur, King of the </span> +<span style="color:silver;background:black">Britons. </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +</pre></div> +<br clear="left"> +<br> + +<h3><a name="2.2">2.2. Frame and ListBox Widgets</a></h3> + +This program asks for your name and responds "Nice to meet you, (your name)" +while you type your name. F1 exits. + +<pre class="code"> +import urwid.curses_display +import urwid + +class Conversation: + def __init__(self): + self.items = [ self.new_question() ] + self.listbox = urwid.ListBox( self.items ) + instruct = urwid.Text("Press F1 to exit.") + header = urwid.AttrWrap( instruct, 'header' ) + self.top = urwid.Frame(self.listbox, header) + + def main(self): + self.ui = urwid.curses_display.Screen() + self.ui.register_palette([ + ('header', 'black', 'dark cyan', 'standout'), + ('I say', 'white', 'black', 'bold'), + ]) + self.ui.run_wrapper( self.run ) + + def run(self): + size = self.ui.get_cols_rows() + + while True: + self.draw_screen( size ) + keys = self.ui.get_input() + if "f1" in keys: + break + for k in keys: + if k == "window resize": + size = self.ui.get_cols_rows() + continue + self.top.keypress( size, k ) + if keys: + name = self.items[0].edit_text + self.items[1:2] = [self.new_answer(name)] + + def draw_screen(self, size): + canvas = self.top.render( size, focus=True ) + self.ui.draw_screen( size, canvas ) + + def new_question(self): + return urwid.Edit(('I say',"What is your name?\n")) + + def new_answer(self, name): + return urwid.Text(('I say',"Nice to meet you, "+name+"\n")) + + +Conversation().main() +</pre> + +<ul> +<li>In the __init__ function a list called self.items is created. It +contains an Edit widget with the caption "What is your name?". +<li>A <a href="reference.html#ListBox">ListBox</a> widget called +self.listbox is created that is passed the self.items list. +This ListBox widget will display and scroll through the widgets in that list. +ListBox widgets default to using the first item in the list as the focus. +<li>A <a href="reference.html#Frame">Frame</a> widget called self.top +is created that contains self.listbox and some header text. Frame widgets +wrap around a box widget and may have header and footer flow widgets. +The header and footer are always displayed. The contained box widget uses the +remaining space in between. +<li>When a key is pressed the reply text is inserted or updated in +self.items. This updated text will be displayed by self.listbox. +</ul> + +When changing the contents of ListBox widgets remember to use in-place +editing operations on the list, eg. "list = list + [something]" will not work, +use "list += [something]" instead. + +<div class="shot"><pre><span style="color:black;background:teal">Press F1 to exit. </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"><u> </u> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +</pre></div> +<div class="shot"><pre><span style="color:black;background:teal">Press F1 to exit. </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">Tim t<u> </u> </span> +<span style="color:white;background:black">Nice to meet you, Tim</span> +<span style="color:white;background:black">t</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +</pre></div> +<div class="shot"><pre><span style="color:black;background:teal">Press F1 to exit. </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">Tim the Ench<u> </u> </span> +<span style="color:white;background:black">Nice to meet you, Tim</span> +<span style="color:white;background:black">the Ench</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +</pre></div> +<div class="shot"><pre><span style="color:black;background:teal">Press F1 to exit. </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">Tim the Enchanter<u> </u> </span> +<span style="color:white;background:black">Nice to meet you, Tim</span> +<span style="color:white;background:black">the Enchanter</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +</pre></div> +<br clear="left"> +<br> + +<h3><a name="2.3">2.3. ListBox Widgets Continued</a></h3> + +This program asks for your name and responds "Nice to meet you, (your name)." +It then asks again, and again. Old values may be changed and the responses +will be updated when you press ENTER. F1 exits. +<br><br> +Update the <a href="#2.2">2.2 program</a> with this code: +<pre class="code"> + def run(self): + size = self.ui.get_cols_rows() + + while True: + self.draw_screen( size ) + keys = self.ui.get_input() + if "f1" in keys: + break + for k in keys: + if k == "window resize": + size = self.ui.get_cols_rows() + continue + self.keypress( size, k ) + + def keypress(self, size, k): + if k == "enter": + widget, pos = self.listbox.get_focus() + if not hasattr(widget,'edit_text'): + return + + answer = self.new_answer( widget.edit_text ) + + if pos == len(self.items)-1: + self.items.append( answer ) + self.items.append( self.new_question() ) + else: + self.items[pos+1:pos+2] = [answer] + + self.listbox.set_focus( pos+2, coming_from='above' ) + widget, pos = self.listbox.get_focus() + widget.set_edit_pos(0) + else: + self.top.keypress( size, k ) +</pre> + +<ul> +<li>In this version only the ENTER key receives special attention. When the +user presses ENTER: + <ul> + <li>The widget in focus and its current position is retrieved by calling the + <a href="reference.html#ListBox-get_focus">get_focus</a> function. + <li>If the widget in focus does not have an edit_text attribute, then it + is not one of the Edit widgets we are interested in. + One of the Text widgets might receive focus + if it covers the entire visible area of the ListBox widget and there is + no Edit widget to take focus. While this is unlikely, it would cause the + program to crash. + <li>If the current position is at the end of the list, a response is + appended followed by a new question. If the current position is not at the + end then a previous response is replaced with an updated one. + <li>The focus is moved down two positions to the next question by calling + <a href="reference.html#ListBox-set_focus">set_focus</a>. + <li>Finally, the cursor position within the new focus widget it moved to + the far left by calling + <a href="reference.html#Edit-set_edit_pos">set_edit_pos</a>. + </ul> +<li>All other keys are passed to the top widget to handle. The ListBox widget +does most of the hard work: + <ul> + <li>UP and DOWN will change the focus and/or scroll the widgets in the list + box. + <li>PAGE UP and PAGE DOWN will try to move the focus one screen up or down. + <li>The cursor's column is maintained as best as possible when moving + from one Edit widget to another. + </ul> +</ul> + +The ListBox widget tries to do the most sensible thing when scrolling and +changing focus. When the widgets displayed are all unselectable +the ListBox widget will always scroll. When some widgets +are selectable it will try changing focus before scrolling, possibly +scrolling a few lines to bring in a full selectable widget. When all the +widgets are selectable it will only scroll when the cursor reaches the +top or bottom edge. + +<div class="shot"><pre><span style="color:black;background:teal">Press F1 to exit. </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">Abe </span> +<span style="color:white;background:black">Nice to meet you, Abe</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">Bob<u> </u> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +</pre></div> +<div class="shot"><pre><span style="color:black;background:teal">Press F1 to exit. </span> +<span style="color:white;background:black">Nice to meet you, Abe</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">Bob </span> +<span style="color:white;background:black">Nice to meet you, Bob</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">Carl </span> +<span style="color:white;background:black">Nice to meet you, Carl</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"><u> </u> </span> +</pre></div> +<div class="shot"><pre><span style="color:black;background:teal">Press F1 to exit. </span> +<span style="color:white;background:black">Nice to meet you, Bob</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">Carl </span> +<span style="color:white;background:black">Nice to meet you, Carl</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black">Dave </span> +<span style="color:white;background:black">Nice to meet you, Dave</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"> </span> +<span style="color:white;background:black">What is your name?</span><span style="color:silver;background:black"> </span> +<span style="color:silver;background:black"><u> </u> </span> +</pre></div> +<br clear="left"> +<br> + + + + +</body> +</html> diff --git a/urwid/curses_display.py b/urwid/curses_display.py index 5426060..7c04eec 100755 --- a/urwid/curses_display.py +++ b/urwid/curses_display.py @@ -95,7 +95,8 @@ class Screen: def register_palette( self, l ): """Register a list of palette entries. - l -- list of (name, foreground, background) or + l -- list of (name, foreground, background, mono), + (name, foreground, background) or (name, same_as_other_name) palette entries. calls self.register_palette_entry for each item in l @@ -443,6 +444,8 @@ class Screen: for y in range(len(lines)): line = lines[y].translate( _trans_table ) + if len(line) < cols: + line += " "*(cols-len(line)) if y == rows-1: # don't draw in the lower right corner line = line[:cols-1] diff --git a/urwid/html_fragment.py b/urwid/html_fragment.py index e72ed69..e934139 100755 --- a/urwid/html_fragment.py +++ b/urwid/html_fragment.py @@ -140,11 +140,12 @@ class HtmlGenerator: if cols > col: fg,bg,mono = "light gray", "black", None + end = line[col:]+" "*(cols-len(line)) if y == cy: - l.append( html_span( " "*(cols-col), + l.append( html_span( end, fg, bg, cx-col )) else: - l.append( html_span( " "*(cols-col), + l.append( html_span( end, fg, bg )) l.append("\n") diff --git a/urwid/listbox.py b/urwid/listbox.py index 47c5d9a..493db01 100644 --- a/urwid/listbox.py +++ b/urwid/listbox.py @@ -98,6 +98,10 @@ class ListBox(BoxWidget): # pref_col is the preferred column for the cursor when moving # between widgets that use the cursor (edit boxes etc.) self.pref_col = 0 + + # variable for delayed focus change used by set_focus + self.set_focus_pending = None + def calculate_visible(self, (maxcol, maxrow), focus=False ): """ Return (middle,top,bottom) or None,None,None. @@ -112,8 +116,12 @@ class ListBox(BoxWidget): list of (widget, position, rows) tuples below focus in order from top to bottom ) """ - - # 1. start by rendering focus widget + + # 0. set the focus if a change is pending + if self.set_focus_pending: + self._set_focus_complete( (maxcol, maxrow), focus ) + + # 1. start with the focus widget focus_widget, focus_pos = self.body.get_focus() if focus_widget is None: #list box is empty? return None,None,None @@ -282,7 +290,77 @@ class ListBox(BoxWidget): + def set_focus(self, position, coming_from=None): + """ + Set the focus position and try to keep the old focus in view. + + position -- a position compatible with self.body.set_focus + coming_from -- set to 'above' or 'below' if you know that + old position is above or below the new position. + """ + assert coming_from in ('above', 'below', None) + focus_widget, focus_pos = self.body.get_focus() + + self.set_focus_pending = coming_from, focus_widget, focus_pos + self.body.set_focus( position ) + + def get_focus(self): + """ + Return a (focus widget, focus position) tuple. + """ + return self.body.get_focus() + + def _set_focus_complete(self, (maxcol, maxrow), focus): + """ + Finish setting the position now that we have maxcol & maxrow. + """ + coming_from, focus_widget, focus_pos = self.set_focus_pending + self.set_focus_pending = None + + # new position + new_focus_widget, position = self.body.get_focus() + if focus_pos == position: + # do nothing + return + + # restore old focus temporarily + self.body.set_focus(focus_pos) + middle,top,bottom=self.calculate_visible((maxcol,maxrow),focus) + focus_offset, focus_widget, focus_pos, focus_rows, cursor=middle + trim_top, fill_above = top + trim_bottom, fill_below = bottom + + offset = focus_offset + for widget, pos, rows in fill_above: + offset -= rows + if pos == position: + self.change_focus((maxcol, maxrow), pos, + offset, 'below' ) + return + + offset = focus_offset + focus_rows + for widget, pos, rows in fill_below: + if pos == position: + self.change_focus((maxcol, maxrow), pos, + offset, 'above' ) + return + offset += rows + + # failed to find widget among visible widgets + self.body.set_focus( position ) + widget, position = self.body.get_focus() + rows = widget.rows((maxcol,), focus) + + if coming_from=='below': + offset = 0 + elif coming_from=='above': + offset = maxrow-rows + else: + offset = maxrow-rows/2 + self.shift_focus((maxcol, maxrow), offset) + + def shift_focus(self, (maxcol,maxrow), offset_inset ): """Move the location of the current focus relative to the top. @@ -434,6 +512,9 @@ class ListBox(BoxWidget): 'page down' move cursor down one listbox length """ + if self.set_focus_pending: + self._set_focus_complete( (maxcol,maxrow), focus=True ) + focus_widget, pos = self.body.get_focus() if focus_widget is None: # empty listbox, can't do anything return key diff --git a/urwid/widget.py b/urwid/widget.py index d57b7de..9bbc366 100755 --- a/urwid/widget.py +++ b/urwid/widget.py @@ -534,6 +534,77 @@ class IntEdit(Edit): return 0 +class Filler(BoxWidget): + def __init__(self, body, valign="middle"): + """ + body -- a flow widget to be filled around + valign -- vertical alignment: "top", "middle" or "bottom" + """ + self.body = body + assert valign in ("top","middle","bottom") + self.valign = valign + + def render(self, (maxcol,maxrow), focus=False): + """Render self.body with space around it to fill box size.""" + c = self.body.render( (maxcol,), focus ) + + cy = None + if c.cursor is not None: + cx, cy = c.cursor + + pos = self.body_position((maxcol,maxrow), focus, c.rows(), cy ) + + # need to trim top of self.body + if pos < 0: + c.trim( -pos, maxrow ) + return c + top = Canvas(["" for i in range(pos)]) + + # may need to trim bottom of self.body + if pos + c.rows() >= maxrow: + return CanvasCombine( [top, c] ).trim(0, maxrow) + + # add padding to bottom + bottom = Canvas(["" for i in range(maxrow-c.rows()-pos)]) + return CanvasCombine( [top, c, bottom] ) + + + def keypress(self, (maxcol,maxrow), key): + """Pass keypress to self.body.""" + return self.body.keypress( (maxcol,), key ) + + + def body_position(self, (maxcol, maxrow), focus, rows=None, cy=None): + """ + Return the row offset (+ve) or reduction (-ve) of self.body. + """ + + if cy is None and focus: + if hasattr(self.body,'get_cursor_coords'): + cx, cy = self.body.get_cursor_coords((maxcol,)) + + # need to trim rows on top + if cy is not None and cy >= maxrow: + return maxrow-cy-1 + + if self.valign == "top": + return 0 + + if rows is None: + rows = self.body.rows((maxcol,), focus) + if rows >= maxrow: + return 0 + + remaining = maxrow - rows + + if self.valign == "middle": + return remaining / 2 + else: # valign == "bottom" + return remaining + + + + class Frame(BoxWidget): |