diff options
| author | Ian Ward <ian@excess.org> | 2012-09-22 20:31:01 -0400 |
|---|---|---|
| committer | Ian Ward <ian@excess.org> | 2012-09-22 20:31:01 -0400 |
| commit | daefdead5fad770775aa584fc61ea55ef3e8ac89 (patch) | |
| tree | 9af532ce1c5c32542f2fa20a311055c40984975f /docs | |
| parent | 9e73e7756f277a5edd6954043729d03fb47c8ca2 (diff) | |
| download | urwid-daefdead5fad770775aa584fc61ea55ef3e8ac89.tar.gz | |
docs: move some sections from tutorial to widget manual page
--HG--
branch : feature-sphinx
rename : docs/tutorial/wanat.py => docs/manual/wanat.py
rename : docs/tutorial/wanat_multi.py => docs/manual/wanat_multi.py
rename : docs/tutorial/wanat_new.py => docs/manual/wanat_new.py
rename : docs/tutorial/wcur1.py => docs/manual/wcur1.py
rename : docs/tutorial/wcur2.py => docs/manual/wcur2.py
rename : docs/tutorial/wmod.py => docs/manual/wmod.py
rename : docs/tutorial/wsel.py => docs/manual/wsel.py
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/manual/wanat.py (renamed from docs/tutorial/wanat.py) | 0 | ||||
| -rw-r--r-- | docs/manual/wanat_multi.py (renamed from docs/tutorial/wanat_multi.py) | 0 | ||||
| -rw-r--r-- | docs/manual/wanat_new.py (renamed from docs/tutorial/wanat_new.py) | 0 | ||||
| -rw-r--r-- | docs/manual/wcur1.py (renamed from docs/tutorial/wcur1.py) | 0 | ||||
| -rw-r--r-- | docs/manual/wcur2.py (renamed from docs/tutorial/wcur2.py) | 0 | ||||
| -rw-r--r-- | docs/manual/widgets.rst | 415 | ||||
| -rw-r--r-- | docs/manual/wmod.py (renamed from docs/tutorial/wmod.py) | 0 | ||||
| -rw-r--r-- | docs/manual/wsel.py (renamed from docs/tutorial/wsel.py) | 0 | ||||
| -rw-r--r-- | docs/tutorial/index.rst | 385 |
9 files changed, 381 insertions, 419 deletions
diff --git a/docs/tutorial/wanat.py b/docs/manual/wanat.py index 723eb90..723eb90 100644 --- a/docs/tutorial/wanat.py +++ b/docs/manual/wanat.py diff --git a/docs/tutorial/wanat_multi.py b/docs/manual/wanat_multi.py index b6d6943..b6d6943 100644 --- a/docs/tutorial/wanat_multi.py +++ b/docs/manual/wanat_multi.py diff --git a/docs/tutorial/wanat_new.py b/docs/manual/wanat_new.py index 5a597b9..5a597b9 100644 --- a/docs/tutorial/wanat_new.py +++ b/docs/manual/wanat_new.py diff --git a/docs/tutorial/wcur1.py b/docs/manual/wcur1.py index 6bf97f7..6bf97f7 100644 --- a/docs/tutorial/wcur1.py +++ b/docs/manual/wcur1.py diff --git a/docs/tutorial/wcur2.py b/docs/manual/wcur2.py index 1225419..1225419 100644 --- a/docs/tutorial/wcur2.py +++ b/docs/manual/wcur2.py diff --git a/docs/manual/widgets.rst b/docs/manual/widgets.rst index 0bea599..f464af4 100644 --- a/docs/manual/widgets.rst +++ b/docs/manual/widgets.rst @@ -229,49 +229,65 @@ an unbounded number of positions, so care should be used with this interface and :class:`ListBox`. -Customizing Widgets -=================== +Pile Widgets +------------ -Widgets in Urwid are easiest to create by extending other widgets. If you are -making a new type of widget that can use other widgets to display its content, -like a new type of button or control, then you should start by extending -:class:`WidgetWrap` and passing the display widget to its constructor. +:class:`Pile` widgets are used to combine multiple widgets by +stacking them vertically. A Pile can manage selectable widgets by keeping track +of which widget is in focus and it can handle moving the focus between widgets +when the user presses the *UP* and *DOWN* keys. A Pile will also work well when +used within a :class:`ListBox`. -The :class:`Widget` interface is described in detail in the -:class:`Widget base class reference <Widget>` and is useful if you're looking to modify -the behavior of an existing widget, -build a new widget class from scratch or just want a better understanding of -the library. +A Pile is selectable only if its focus widget is selectable. If you create a +Pile containing one Text widget and one Edit widget the Pile will choose the +Edit widget as its default focus widget. -One Urwid design choice that stands out is that widgets typically have no -size. Widgets don't store their size on screen, and instead are -passed that information when they need it. -This choice has some advantages: +Columns Widgets +--------------- -* widgets may be reused in different locations -* reused widgets only need to be rendered once per size displayed -* widgets don't need to know their parents -* less data to store and update -* no worrying about widgets that haven't received their size yet -* same widgets could be displayed at different sizes to different users - simultaneously +:class:`Columns` widgets may be used to arrange either flow +widgets or box widgets horizontally into columns. Columns widgets will manage +selectable widgets by keeping track of which column is in focus and it can +handle moving the focus between columns when the user presses the *LEFT* and +*RIGHT* keys. Columns widgets also work well when used within a +:class:`ListBox`. -It also has disadvantages: +Columns widgets are selectable only if the column in focus is selectable. If a +focus column is not specified the first selectable widget will be chosen as the +focus column. -* difficult to determine a widget's size on screen -* more parameters to parse -* duplicated size calculations across methods -For determining a widget's size on screen it is possible to look up the size(s) -it was rendered at in the :class:`CanvasCache`. There are plans -to address some of the duplicated size handling code in the container widgets -in a future Urwid release. +GridFlow Widgets +---------------- -The same holds true for a widget's focus state, so that too is passed in to -functions that need it. +The :class:`GridFlow` widget is a flow widget designed for use +with :class:`Button`, :class:`CheckBox` and +:class:`RadioButton` widgets. It renders all the widgets it +contains the same width and it arranges them from left to right and top to +bottom. + +The GridFlow widget uses Pile, Columns, Padding and Divider widgets to build a +display widget that will handle the keyboard input and rendering. When the +GridFlow widget is resized it regenerates the display widget to accommodate the +new space. + + +Overlay Widgets +--------------- + +The :class:`Overlay` widget is a box widget that contains two +other box widgets. The bottom widget is rendered the full size of the Overlay +widget and the top widget is placed on top, obscuring an area of the bottom +widget. This widget can be used to create effects such as overlapping "windows" +or pop-up menus. + +The Overlay widget always treats the top widget as the one in focus. All +keyboard input will be passed to the top widget. + +If you want to use a flow flow widget for the top widget, first wrap the flow +widget with a :class:`Filler` widget. -.. seealso:: :ref:`creating-custom-widgets` .. _listbox-contents: @@ -284,6 +300,153 @@ Its contents are displayed stacked vertically, and the One of the flow widgets displayed in the :class:`ListBox` is its focus widget. +ListBox Focus and Scrolling +--------------------------- + +The :class:`ListBox` is a box widget that contains flow widgets. +Its contents are displayed stacked vertically, and the +:class:`ListBox` allows the user to scroll through its content. +One of the flow widgets displayed in the :class:`ListBox` is the +focus widget. The :class:`ListBox` passes key presses to the +focus widget to allow the user to interact with it. If the focus widget does +not handle a keypress then the :class:`ListBox` may handle the +keypress by scrolling and/or selecting another widget to become the focus +widget. + +The :class:`ListBox` tries to do the most sensible thing when +scrolling and changing focus. When the widgets displayed are all +:class:`Text` widgets or other unselectable widgets then the +:class:`ListBox` will behave like a web browser does when the +user presses *UP*, *DOWN*, *PAGE UP* and *PAGE DOWN*: new text is immediately +scrolled in from the top or bottom. The :class:`ListBox` chooses +one of the visible widgets as its focus widget when scrolling. When scrolling +up the :class:`ListBox` chooses the topmost widget as the focus, +and when scrolling down the :class:`ListBox` chooses the +bottommost widget as the focus. + +The :class:`ListBox` remembers the location of the widget in +focus as either an "offset" or an "inset". An offset is the number of rows +between the top of the :class:`ListBox` and the beginning of the +focus widget. An offset of zero corresponds to a widget with its top aligned +with the top of the :class:`ListBox`. An inset is the fraction +of rows of the focus widget that are "above" the top of the +:class:`ListBox` and not visible. The +:class:`ListBox` uses this method of remembering the focus +widget location so that when the :class:`ListBox` is resized the +text displayed will stay roughly aligned with the top of the +:class:`ListBox`. + +When there are selectable widgets in the :class:`ListBox` the +focus will move between the selectable widgets, skipping the unselectable +widgets. The :class:`ListBox` will try to scroll all the rows of +a selectable widget into view so that the user can see the new focus widget in +its entirety. This behavior can be used to bring more than a single widget into +view by using composite widgets to combine a selectable widget with other +widgets that should be displayed at the same time. + + +Dynamic ListBox with ListWalker +------------------------------- + +While the :class:`ListBox` stores the location of its focus +widget, it does not directly store the actual focus widget or other contents of +the :class:`ListBox`. The storage of a +:class:`ListBox`'s content is delegated to a "List Walker" +object. If a list of widgets is passed to the :class:`ListBox` +constructor then it creates a :class:`SimpleListWalker` object +to manage the list. + +When the :class:`ListBox` is `rendering a canvas`_ or `handling +input`_ it will: + +.. _rendering a canvas: :meth:`ListBox.render` +.. _handling input: :meth:`ListBox.keypress` + +1. Call the :meth:`get_focus` method of its list walker object. This method + will return the focus widget and a position object. +2. Optionally call the :meth:`get_prev` method of its List Walker object one or + more times, initially passing the focus position and then passing the new + position returned on each successive call. This method will return the + widget and position object "above" the position passed. +3. Optionally call the :meth:`get_next` method of its List Walker object one or + more times, similarly, to collect widgets and position objects "below" the + focus position. +4. Optionally call the :meth:`set_focus` method passing one of the position + objects returned in the previous steps. + +This is the only way the :class:`ListBox` accesses its contents, +and it will not store copies of any of the widgets or position objects beyond +the current rendering or input handling operation. + +The :class:`SimpleListWalker` stores a list of widgets, and uses +integer indexes into this list as its position objects. It stores the focus +position as an integer, so if you insert a widget into the list above the focus +position then you need to remember to increment the focus position in the +:class:`SimpleListWalker` object or the contents of the +:class:`ListBox` will shift. + +A custom List Walker object may be passed to the +:class:`ListBox` constructor instead of a plain list of widgets. +List Walker objects must implement the :ref:`list-walker-interface`. + +The fib.py_ example program demonstrates a custom list walker that doesn't +store any widgets. It uses a tuple of two successive Fibonacci numbers as its +position objects and it generates Text widgets to display the numbers on the +fly. The result is a :class:`ListBox` that can scroll through an +unending list of widgets. + +The edit.py_ example program demonstrates a custom list walker that loads lines +from a text file only as the user scrolls them into view. This allows even +huge files to be opened almost instantly. + +The browse.py_ example program demonstrates a custom list walker that uses a +tuple of strings as position objects, one for the parent directory and one for +the file selected. The widgets are cached in a separate class that is accessed +using a dictionary indexed by parent directory names. This allows the +directories to be read only as required. The custom list walker also allows +directories to be hidden from view when they are "collapsed". + +.. _fib.py: http://excess.org/urwid/browser/examples/fib.py +.. _edit.py: http://excess.org/urwid/browser/examples/edit.py +.. _browse.py: http://excess.org/urwid/browser/examples/browse.py + + +Setting the Focus +----------------- + +The easiest way to change the current :class:`ListBox` focus is +to call the :meth:`ListBox.set_focus` method. This method doesn't +require that you know the :class:`ListBox`'s current dimensions +``(maxcol, maxrow)``. It will wait until the next call to either keypress or +render to complete setting the offset and inset values using the dimensions +passed to that method. + +The position object passed to :meth:`set_focus` must be compatible with the +List Walker object that the :class:`ListBox` is using. For +:class:`SimpleListWalker` the position is the integer index of +the widget within the list. + +The *coming_from* parameter should be set if you know that the old position is +"above" or "below" the previous position. When the +:class:`ListBox` completes setting the offset and inset values +it tries to find the old widget among the visible widgets. If the old widget is +still visible, if will try to avoid causing the :class:`ListBox` +contents to scroll up or down from its previous position. If the widget is not +visible, then the :class:`ListBox` will: + +* Display the new focus at the bottom of the :class:`ListBox` if + *coming_from* is "above". +* Display the new focus at the top of the :class:`ListBox` if + *coming_from* is "below". +* Display the new focus in the middle of the :class:`ListBox` if + coming_from is ``None``. + +If you know exactly where you want to display the new focus widget within the +:class:`ListBox` you may call +:meth:`ListBox.set_focus_valign`. This method lets you specify +the *top*, *bottom*, *middle*, a relative position or the exact number of rows +from the top or bottom of the :class:`ListBox`. + List Walkers ------------ @@ -423,7 +586,191 @@ This behavior can be used to bring more than a single widget into view by using a :class:`Pile` or other container widget to combine a selectable widget with other widgets that should be visible at the same time. -.. seealso:: :ref:`Tutorial chapters covering ListBox usage <zen-listbox>` + +Customizing Widgets +=================== + +Widgets in Urwid are easiest to create by extending other widgets. If you are +making a new type of widget that can use other widgets to display its content, +like a new type of button or control, then you should start by extending +:class:`WidgetWrap` and passing the display widget to its constructor. + +The :class:`Widget` interface is described in detail in the +:class:`Widget base class reference <Widget>` and is useful if you're looking to modify +the behavior of an existing widget, +build a new widget class from scratch or just want a better understanding of +the library. + +One Urwid design choice that stands out is that widgets typically have no +size. Widgets don't store their size on screen, and instead are +passed that information when they need it. + +This choice has some advantages: + +* widgets may be reused in different locations +* reused widgets only need to be rendered once per size displayed +* widgets don't need to know their parents +* less data to store and update +* no worrying about widgets that haven't received their size yet +* same widgets could be displayed at different sizes to different users + simultaneously + +It also has disadvantages: + +* difficult to determine a widget's size on screen +* more parameters to parse +* duplicated size calculations across methods + +For determining a widget's size on screen it is possible to look up the size(s) +it was rendered at in the :class:`CanvasCache`. There are plans +to address some of the duplicated size handling code in the container widgets +in a future Urwid release. + +The same holds true for a widget's focus state, so that too is passed in to +functions that need it. + + +Modifying Existing Widgets +-------------------------- + +The easiest way to create a custom widget is to modify an existing widget. +This can be done by either subclassing the original widget or by wrapping it. +Subclassing is appropriate when you need to interact at a very low level with +the original widget, such as if you are creating a custom edit widget with +different behavior than the usual Edit widgets. If you are creating a custom +widget that doesn't need tight coupling with the original widget, such as a +widget that displays customer address information, then wrapping is more +appropriate. + +The :class:`WidgetWrap` class simplifies wrapping existing +widgets. You can create a custom widget simply by creating a subclass of +WidgetWrap and passing a widget into WidgetWrap's constructor. + + +This is an example of a custom widget that uses WidgetWrap: + +.. literalinclude:: wmod.py + :linenos: + +The above code creates a group of RadioButtons and provides a method to +query the state of the buttons. + + +Anatomy of a Widget +------------------- + +Any object that follows the `Widget interface definition`_ may be used as a +widget. Box widgets must implement selectable_ and render_ methods, and flow +widgets must implement selectable, render and rows_ methods. + +.. _Widget interface definition: :class:`Widget` +.. _selectable: :meth:`Widget.selectable` +.. _render: :meth:`Widget.render` +.. _rows: :meth:`Widget.rows` + +.. literalinclude:: wanat.py + :linenos: + +The above code implements two widget classes. Pudding is a flow widget and +BoxPudding is a box widget. Pudding will render as much "Pudding" as will fit +in a single row, and BoxPudding will render as much "Pudding" as will fit into +the entire area given. + +Note that the rows and render methods' focus parameter must have a default +value of False. Also note that for flow widgets the number of rows returned by +the rows method must match the number of rows rendered by the render method. + +In most cases it is easier to let other widgets handle the rendering and row +calculations for you: + +.. literalinclude:: wanat_new.py + :linenos: + +The NewPudding class behaves the same way as the Pudding class above, but in +NewPudding you can change the way the widget appears by modifying only the +display_widget method, whereas in the Pudding class you may have to modify both +the render and rows methods. + +To improve the efficiency of your Urwid application you should be careful of +how long your rows methods take to execute. The rows methods may be called many +times as part of input handling and rendering operations. If you are using a +display widget that is time consuming to create you should consider caching it +to reduce its impact on performance. + +It is possible to create a widget that will behave as either a flow widget or +box widget depending on what is required: + +.. literalinclude:: wanat_multi.py + :linenos: + +MultiPudding will work in place of either Pudding or BoxPudding above. The +number of elements in the size tuple determines whether the containing widget +is expecting a flow widget or a box widget. + + +Creating a Selectable Widget +---------------------------- + +Selectable widgets such as Edit and Button widgets allow the user to interact +with the application. A widget is selectable if its selectable method returns +True. Selectable widgets must implement the keypress_ method to handle keyboard +input. + +.. _keypress: :meth:`Widget.keypress` + +.. literalinclude:: wsel.py + +The SelectablePudding widget will display its contents in uppercase when it is +in focus, and it allows the user to "eat" the pudding by pressing each of the +letters *P*, *U*, *D*, *D*, *I*, *N* and *G* on the keyboard. When the user has +"eaten" all the pudding the widget will reset to its initial state. + +Note that keys that are unhandled in the keypress method are returned so that +another widget may be able to handle them. This is a good convention to follow +unless you have a very good reason not to. In this case the *UP* and *DOWN* +keys are returned so that if this widget is in a +:class:`ListBox` the :class:`ListBox` will behave +as the user expects and change the focus or scroll the +:class:`ListBox`. + +Widget Displaying the Cursor +---------------------------- + +Widgets that display the cursor must implement the get_cursor_coords_ method. +Similar to the rows method for flow widgets, this method lets other widgets +make layout decisions without rendering the entire widget. The +:class:`ListBox` widget in particular uses get_cursor_coords to +make sure that the cursor is visible within its focus widget. + +.. _get_cursor_coords: :meth:`Widget.get_cursor_coords` + +.. literalinclude:: wcur1.py + :linenos: + +CursorPudding will let the user move the cursor through the widget by pressing +*LEFT* and *RIGHT*. The cursor must only be added to the canvas when the widget +is in focus. The get_cursor_coords method must always return the same cursor +coordinates that render does. + +A widget displaying a cursor may choose to implement get_pref_col. This method +returns the preferred column for the cursor, and is called when the focus is +moving up or down off this widget. + +.. _get_pref_col: :meth:`Widget.get_pref_col` + +Another optional method is move_cursor_to_coords_. This method allows other +widgets to try to position the cursor within this widget. The +:class:`ListBox` widget uses :meth:`move_cursor_to_coords` when +changing focus and when the user pressed *PAGE UP* or *PAGE DOWN*. This method +must return ``True`` on success and ``False`` on failure. If the cursor may be +placed at any position within the row specified (not only at the exact column +specified) then this method must move the cursor to that position and return +``True``. + +.. _move_cursor_to_coords: :meth:`Widget.move_cursor_to_coords` + +.. literalinclude:: wcur2.py + :linenos: Widget Metaclass diff --git a/docs/tutorial/wmod.py b/docs/manual/wmod.py index a066a49..a066a49 100644 --- a/docs/tutorial/wmod.py +++ b/docs/manual/wmod.py diff --git a/docs/tutorial/wsel.py b/docs/manual/wsel.py index 15683d8..15683d8 100644 --- a/docs/tutorial/wsel.py +++ b/docs/manual/wsel.py diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst index 24de181..1e2ef2b 100644 --- a/docs/tutorial/index.rst +++ b/docs/tutorial/index.rst @@ -331,390 +331,5 @@ default decorations .. image:: menu43.png .. image:: menu44.png -.. _zen-listbox: - -Zen of ListBox -============== - -ListBox Focus and Scrolling ---------------------------- - -The :class:`ListBox` is a box widget that contains flow widgets. -Its contents are displayed stacked vertically, and the -:class:`ListBox` allows the user to scroll through its content. -One of the flow widgets displayed in the :class:`ListBox` is the -focus widget. The :class:`ListBox` passes key presses to the -focus widget to allow the user to interact with it. If the focus widget does -not handle a keypress then the :class:`ListBox` may handle the -keypress by scrolling and/or selecting another widget to become the focus -widget. - -The :class:`ListBox` tries to do the most sensible thing when -scrolling and changing focus. When the widgets displayed are all -:class:`Text` widgets or other unselectable widgets then the -:class:`ListBox` will behave like a web browser does when the -user presses *UP*, *DOWN*, *PAGE UP* and *PAGE DOWN*: new text is immediately -scrolled in from the top or bottom. The :class:`ListBox` chooses -one of the visible widgets as its focus widget when scrolling. When scrolling -up the :class:`ListBox` chooses the topmost widget as the focus, -and when scrolling down the :class:`ListBox` chooses the -bottommost widget as the focus. - -When all the widgets displayed are not selectable the user would typically have -no way to tell which widget is in focus, but if we wrap the widgets with -:class:`AttrWrap` we can see what is happening while the -focus changes: - -.. literalinclude:: lbscr.py - :linenos: - -.. image:: lbscr1.png -.. image:: lbscr2.png -.. image:: lbscr3.png -.. image:: lbscr4.png -.. image:: lbscr5.png -.. image:: lbscr6.png - -The :class:`ListBox` remembers the location of the widget in -focus as either an "offset" or an "inset". An offset is the number of rows -between the top of the :class:`ListBox` and the beginning of the -focus widget. An offset of zero corresponds to a widget with its top aligned -with the top of the :class:`ListBox`. An inset is the fraction -of rows of the focus widget that are "above" the top of the -:class:`ListBox` and not visible. The -:class:`ListBox` uses this method of remembering the focus -widget location so that when the :class:`ListBox` is resized the -text displayed will stay roughly aligned with the top of the -:class:`ListBox`. - -.. image:: lbscr7.png -.. image:: lbscr8.png -.. image:: lbscr9.png - -When there are selectable widgets in the :class:`ListBox` the -focus will move between the selectable widgets, skipping the unselectable -widgets. The :class:`ListBox` will try to scroll all the rows of -a selectable widget into view so that the user can see the new focus widget in -its entirety. This behavior can be used to bring more than a single widget into -view by using composite widgets to combine a selectable widget with other -widgets that should be displayed at the same time. - - -Dynamic ListBox with ListWalker -------------------------------- - -While the :class:`ListBox` stores the location of its focus -widget, it does not directly store the actual focus widget or other contents of -the :class:`ListBox`. The storage of a -:class:`ListBox`'s content is delegated to a "List Walker" -object. If a list of widgets is passed to the :class:`ListBox` -constructor then it creates a :class:`SimpleListWalker` object -to manage the list. - -When the :class:`ListBox` is `rendering a canvas`_ or `handling -input`_ it will: - -.. _rendering a canvas: :meth:`ListBox.render` -.. _handling input: :meth:`ListBox.keypress` - -1. Call the :meth:`get_focus` method of its list walker object. This method - will return the focus widget and a position object. -2. Optionally call the :meth:`get_prev` method of its List Walker object one or - more times, initially passing the focus position and then passing the new - position returned on each successive call. This method will return the - widget and position object "above" the position passed. -3. Optionally call the :meth:`get_next` method of its List Walker object one or - more times, similarly, to collect widgets and position objects "below" the - focus position. -4. Optionally call the :meth:`set_focus` method passing one of the position - objects returned in the previous steps. - -This is the only way the :class:`ListBox` accesses its contents, -and it will not store copies of any of the widgets or position objects beyond -the current rendering or input handling operation. - -The :class:`SimpleListWalker` stores a list of widgets, and uses -integer indexes into this list as its position objects. It stores the focus -position as an integer, so if you insert a widget into the list above the focus -position then you need to remember to increment the focus position in the -:class:`SimpleListWalker` object or the contents of the -:class:`ListBox` will shift. - -A custom List Walker object may be passed to the -:class:`ListBox` constructor instead of a plain list of widgets. -List Walker objects must implement the :ref:`list-walker-interface`. - -The fib.py_ example program demonstrates a custom list walker that doesn't -store any widgets. It uses a tuple of two successive Fibonacci numbers as its -position objects and it generates Text widgets to display the numbers on the -fly. The result is a :class:`ListBox` that can scroll through an -unending list of widgets. - -The edit.py_ example program demonstrates a custom list walker that loads lines -from a text file only as the user scrolls them into view. This allows even -huge files to be opened almost instantly. - -The browse.py_ example program demonstrates a custom list walker that uses a -tuple of strings as position objects, one for the parent directory and one for -the file selected. The widgets are cached in a separate class that is accessed -using a dictionary indexed by parent directory names. This allows the -directories to be read only as required. The custom list walker also allows -directories to be hidden from view when they are "collapsed". - -.. _fib.py: http://excess.org/urwid/browser/examples/fib.py -.. _edit.py: http://excess.org/urwid/browser/examples/edit.py -.. _browse.py: http://excess.org/urwid/browser/examples/browse.py - - -Setting the Focus ------------------ - -The easiest way to change the current :class:`ListBox` focus is -to call the :meth:`ListBox.set_focus` method. This method doesn't -require that you know the :class:`ListBox`'s current dimensions -``(maxcol, maxrow)``. It will wait until the next call to either keypress or -render to complete setting the offset and inset values using the dimensions -passed to that method. - -The position object passed to :meth:`set_focus` must be compatible with the -List Walker object that the :class:`ListBox` is using. For -:class:`SimpleListWalker` the position is the integer index of -the widget within the list. - -The *coming_from* parameter should be set if you know that the old position is -"above" or "below" the previous position. When the -:class:`ListBox` completes setting the offset and inset values -it tries to find the old widget among the visible widgets. If the old widget is -still visible, if will try to avoid causing the :class:`ListBox` -contents to scroll up or down from its previous position. If the widget is not -visible, then the :class:`ListBox` will: - -* Display the new focus at the bottom of the :class:`ListBox` if - *coming_from* is "above". -* Display the new focus at the top of the :class:`ListBox` if - *coming_from* is "below". -* Display the new focus in the middle of the :class:`ListBox` if - coming_from is ``None``. - -If you know exactly where you want to display the new focus widget within the -:class:`ListBox` you may call -:meth:`ListBox.set_focus_valign`. This method lets you specify -the *top*, *bottom*, *middle*, a relative position or the exact number of rows -from the top or bottom of the :class:`ListBox`. - - -Combining Widgets -================= - - -Piling Widgets --------------- - -:class:`Pile` widgets are used to combine multiple widgets by -stacking them vertically. A Pile can manage selectable widgets by keeping track -of which widget is in focus and it can handle moving the focus between widgets -when the user presses the *UP* and *DOWN* keys. A Pile will also work well when -used within a :class:`ListBox`. - -A Pile is selectable only if its focus widget is selectable. If you create a -Pile containing one Text widget and one Edit widget the Pile will choose the -Edit widget as its default focus widget. To change the pile's focus widget you -can call :meth:`Pile.set_focus`. - - -Dividing into Columns ---------------------- - -:class:`Columns` widgets may be used to arrange either flow -widgets or box widgets horizontally into columns. Columns widgets will manage -selectable widgets by keeping track of which column is in focus and it can -handle moving the focus between columns when the user presses the *LEFT* and -*RIGHT* keys. Columns widgets also work well when used within a -:class:`ListBox`. - -Columns widgets are selectable only if the column in focus is selectable. If a -focus column is not specified the first selectable widget will be chosen as the -focus column. The :meth:`Columns.set_focus` method may be used -to select the focus column. - - -``GridFlow`` Arrangment ------------------------ - -The :class:`GridFlow` widget is a flow widget designed for use -with :class:`Button`, :class:`CheckBox` and -:class:`RadioButton` widgets. It renders all the widgets it -contains the same width and it arranges them from left to right and top to -bottom. - -The GridFlow widget uses Pile, Columns, Padding and Divider widgets to build a -display widget that will handle the keyboard input and rendering. When the -GridFlow widget is resized it regenerates the display widget to accommodate the -new space. - - -``Overlay`` widgets -------------------- - -The :class:`Overlay` widget is a box widget that contains two -other box widgets. The bottom widget is rendered the full size of the Overlay -widget and the top widget is placed on top, obscuring an area of the bottom -widget. This widget can be used to create effects such as overlapping "windows" -or pop-up menus. - -The Overlay widget always treats the top widget as the one in focus. All -keyboard input will be passed to the top widget. - -If you want to use a flow flow widget for the top widget, first wrap the flow -widget with a :class:`Filler` widget. - - - -.. _creating-custom-widgets: - -Creating Custom Widgets -======================= - - -Modifying Existing Widgets --------------------------- - -The easiest way to create a custom widget is to modify an existing widget. -This can be done by either subclassing the original widget or by wrapping it. -Subclassing is appropriate when you need to interact at a very low level with -the original widget, such as if you are creating a custom edit widget with -different behavior than the usual Edit widgets. If you are creating a custom -widget that doesn't need tight coupling with the original widget, such as a -widget that displays customer address information, then wrapping is more -appropriate. - -The :class:`WidgetWrap` class simplifies wrapping existing -widgets. You can create a custom widget simply by creating a subclass of -WidgetWrap and passing a widget into WidgetWrap's constructor. - -This is an example of a custom widget that uses WidgetWrap: - -.. literalinclude:: wmod.py - :linenos: - -The above code creates a group of RadioButtons and provides a method to -query the state of the buttons. - -Wrapped widgets may also override the standard widget methods. These methods -are described in following sections. - -Anatomy of a Widget -------------------- - -Any object that follows the `Widget interface definition`_ may be used as a -widget. Box widgets must implement selectable_ and render_ methods, and flow -widgets must implement selectable, render and rows_ methods. - -.. _Widget interface definition: :class:`Widget` -.. _selectable: :meth:`Widget.selectable` -.. _render: :meth:`Widget.render` -.. _rows: :meth:`Widget.rows` - -.. literalinclude:: wanat.py - :linenos: -The above code implements two widget classes. Pudding is a flow widget and -BoxPudding is a box widget. Pudding will render as much "Pudding" as will fit -in a single row, and BoxPudding will render as much "Pudding" as will fit into -the entire area given. -Note that the rows and render methods' focus parameter must have a default -value of False. Also note that for flow widgets the number of rows returned by -the rows method must match the number of rows rendered by the render method. - -In most cases it is easier to let other widgets handle the rendering and row -calculations for you: - -.. literalinclude:: wanat_new.py - :linenos: - -The NewPudding class behaves the same way as the Pudding class above, but in -NewPudding you can change the way the widget appears by modifying only the -display_widget method, whereas in the Pudding class you may have to modify both -the render and rows methods. - -To improve the efficiency of your Urwid application you should be careful of -how long your rows methods take to execute. The rows methods may be called many -times as part of input handling and rendering operations. If you are using a -display widget that is time consuming to create you should consider caching it -to reduce its impact on performance. - -It is possible to create a widget that will behave as either a flow widget or -box widget depending on what is required: - -.. literalinclude:: wanat_multi.py - :linenos: - -MultiPudding will work in place of either Pudding or BoxPudding above. The -number of elements in the size tuple determines whether the containing widget -is expecting a flow widget or a box widget. - - -Creating a Selectable Widget ----------------------------- - -Selectable widgets such as Edit and Button widgets allow the user to interact -with the application. A widget is selectable if its selectable method returns -True. Selectable widgets must implement the keypress_ method to handle keyboard -input. - -.. _keypress: :meth:`Widget.keypress` - -.. literalinclude:: wsel.py - -The SelectablePudding widget will display its contents in uppercase when it is -in focus, and it allows the user to "eat" the pudding by pressing each of the -letters *P*, *U*, *D*, *D*, *I*, *N* and *G* on the keyboard. When the user has -"eaten" all the pudding the widget will reset to its initial state. - -Note that keys that are unhandled in the keypress method are returned so that -another widget may be able to handle them. This is a good convention to follow -unless you have a very good reason not to. In this case the *UP* and *DOWN* -keys are returned so that if this widget is in a -:class:`ListBox` the :class:`ListBox` will behave -as the user expects and change the focus or scroll the -:class:`ListBox`. - -Widget Displaying the Cursor ----------------------------- - -Widgets that display the cursor must implement the get_cursor_coords_ method. -Similar to the rows method for flow widgets, this method lets other widgets -make layout decisions without rendering the entire widget. The -:class:`ListBox` widget in particular uses get_cursor_coords to -make sure that the cursor is visible within its focus widget. - -.. _get_cursor_coords: :meth:`Widget.get_cursor_coords` - -.. literalinclude:: wcur1.py - :linenos: - -CursorPudding will let the user move the cursor through the widget by pressing -*LEFT* and *RIGHT*. The cursor must only be added to the canvas when the widget -is in focus. The get_cursor_coords method must always return the same cursor -coordinates that render does. - -A widget displaying a cursor may choose to implement get_pref_col. This method -returns the preferred column for the cursor, and is called when the focus is -moving up or down off this widget. - -.. _get_pref_col: :meth:`Widget.get_pref_col` - -Another optional method is move_cursor_to_coords_. This method allows other -widgets to try to position the cursor within this widget. The -:class:`ListBox` widget uses :meth:`move_cursor_to_coords` when -changing focus and when the user pressed *PAGE UP* or *PAGE DOWN*. This method -must return ``True`` on success and ``False`` on failure. If the cursor may be -placed at any position within the row specified (not only at the exact column -specified) then this method must move the cursor to that position and return -``True``. - -.. _move_cursor_to_coords: :meth:`Widget.move_cursor_to_coords` - -.. literalinclude:: wcur2.py - :linenos: |
