diff options
122 files changed, 2747 insertions, 1802 deletions
diff --git a/.hgignore b/.hgignore deleted file mode 100644 index 951902a..0000000 --- a/.hgignore +++ /dev/null @@ -1,11 +0,0 @@ -syntax: glob - -*.pyc -*.so -urwid.egg-info -.*.sw? -tmp/ -tags - -syntax: regexp -^build diff --git a/.hgtags b/.hgtags deleted file mode 100644 index 3b24199..0000000 --- a/.hgtags +++ /dev/null @@ -1,31 +0,0 @@ -00ecfc18e7a3e57d9ddfe2cf02dd596b9f1aecc9 release-0.9.0 -0836e10d9971aa20b04ab6ae17b8faeadd359f6d release-0.8.5 -20d334bf7f990ab83a4cf8182d7915038bab97c5 release-0.8.7 -3e41e992e58d5d8e1917b8c2e06ddb4875a0c3f1 release-0.9.3 -4a94ee84f10ace1d46bdaf64e7ec19d070e41960 release-0.8.6 -4d54f0d3d0921990cebde9f91897b30f50b1c1f0 release-0.9.0-pre3 -51ae19b2d0710ddaf2abed41173678f3ea353ba5 release-0.9.7 -55a4dbaf0f4c7864a0fd74b3399b5bb084225292 release-0.9.4 -6940f8e182d1cc5082c7e46f3701dce2559ad8f1 release-0.9.7.1 -724fa210dbd7c43670178baff9314b05b7dab420 release-0.9.5 -75e410b77de0675ec7ff8a595b97e21e21908e65 release-0.8.10 -77ca4911a81a9a6f6edd900c8759ccd5b775d41b release-0.8.0 -77fad9b941bd1b157d93bfaa29bcb1bc4f39b019 release-0.9.2 -80fb799e57f69d970c77e307d866f14d64c5e481 release-0.9.7.2 -8635df78aaf5eb3ca05f28380e3dc5c136c6566a release-0.8.1 -9488661c4e884ec28784e44da7f70d4917cb46b3 release-0.8.8 -9fc455b9845b892680efc2cfc94d08cbfc0ecaa6 release-0.8.4 -a32c3d617c3c03c5cbaab92d00087162bffbe393 release-0.9.0-pre2 -b08a823827c33811e7741e4d99aafc14c6204987 release-0.8.9 -bac46c2f40104277388c154a4554dabae08b967c release-0.8.3 -d8ccab6cab528c25c4701366094741c2b14ee96f release-0.9.0-pre1 -e985496ab59f788be7ff29b563aee92d2950bc81 release-0.9.6 -ea35d3af1d66baaac26816e1467cad3749c2e996 release-0.8.2 -f523745d15d437c58da48aaaef0b50b62f17216d release-0.9.1 -f71120f89f4a8ded108a98dc4a7a1fc16d3bee58 release-0.9.8 -167d78b81824d3533e64757172a979675f806c34 release-0.9.9 -f445b883a200348fdbfbd025628d4d8a347100ef release-0.9.9.1 -4701bc9ea3f18869ea536cbfec888e99c887a353 release-0.9.9.2 -36a3475de9564133c744f02d8bf4dd1694cc9599 release-1.0.0 -0b46d54a527e8e360274a5cac7c138f20908d679 release-1.0.1 -19012001f1522250d1e4b66541ffc055651f562d release-1.1.0 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..34a8809 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: python +virtualenv: + system_site_packages: true +python: + - "2.6" + - "2.7" + - "3.2" + - "3.3" + - "pypy" +before_install: + - "sudo apt-get install python-gi python3-gi" +install: + - "pip install --use-mirrors setuptools twisted tornado" +script: + - "DEPS=\"$TRAVIS_PYTHON_VERSION `python bin/deps.py`\"; echo $DEPS" + - "[ \"$DEPS\" == '2.6 tornado twisted' -o + \"$DEPS\" == '2.7 pygobject tornado twisted' -o + \"$DEPS\" == '3.2 pygobject tornado twisted' -o + \"$DEPS\" == '3.3 tornado twisted' -o + \"$DEPS\" == 'pypy tornado twisted' ]" + - "python setup.py test" diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 5115869..0000000 --- a/CHANGELOG +++ /dev/null @@ -1,883 +0,0 @@ - -*- coding: utf-8 -*- - -Urwid 1.1.0 - - * New common container API: focus, focus_position, contents, - options(), get_focus_path(), set_focus_path(), __getitem__, - __iter__(), __reversed__() implemented across all included - container widgets - - A full description doesn't fit here, see the Container Widgets - section in the manual for details - - * New Sphinx-based documentation now included in source: - Tutorial rewritten, manual revised and new reference based - on updated docstrings (by Marco Giusti, Patrick Totzke) - - * New list walker SimpleFocusListWalker like SimpleListWalker but - updates focus position as items are inserted or removed - - * New decoration widget WidgetDisable to disable interaction - with the widgets it wraps - - * SelectableIcon selectable text widget used by button widgets is - now documented (available since 0.9.9) - - * Columns widget now tries to keep column in focus visible, hiding - columns on the left when necessary - - * Padding widget now defaults to ('relative', 100) instead of - 'pack' so that left and right parameters are more useful and more - child widgets are supported - - * New list walker "API Version 2" that is simpler for many list - walker uses; "API Version 1" will still continue to be supported - - * List walkers may now allow iteration from the absolute top or - bottom of the list if they provide a positions() method - - * raw_display now erases to the end of the line with EL escape - sequence to improve copy+paste behavior for some terminals - - * Filler now has top and bottom parameters like Padding's left and - right parameters and accepts 'pack' instead of None as a height - value for widgets that calculate their own number of rows - - * Pile and Columns now accepts 'pack' instead of 'flow' for widgets - that calculate their own number of rows or columns - - * Pile and Columns now accept 'given' instead of 'fixed' for - cases where the number of rows or columns are specified by the - container options - - * Pile and Columns widgets now accept any iterable to their - __init__() methods - - * Widget now has a default focus_position property that raises - an IndexError when read to be consistent with new common container - API - - * GridFlow now supports multiple cell widths within the same widget - - * BoxWidget, FlowWidget and FixedWidget are deprecated, instead - use the sizing() function or _sizing attribute to specify the - supported sizing modes for your custom widgets - - * Some new shift+arrow and numpad input sequences from RXVT and - xterm are now recognized - - * Fix for alarms when used with a screen event loop (e.g. - curses_display) - - * Fix for raw_display when terminal width is 1 column - - * Fixes for a Columns.get_cursor_coords() regression and a - SelectableIcon.get_cursor_coords() bug - - * Fixes for incorrect handling of box columns in a number of - Columns methods when that column is selectable - - * Fix for Terminal widget input handling with Python 3 - - -Urwid 1.0.2 - - * Fix for bug when entering Unicode text into Edit widget with - bytes caption - - * Fix a regression when not running in UTF-8 mode - - * Fix for a MainLoop.remove_watch_pipe() bug - - * Fix for a bug when packing empty Edit widgets - - * Fix for a ListBox "contents too long" error with very large - Edit widgets - - * Prevent ListBoxes from selecting 0-height selectable widgets - when moving up or down - - * Fix a number of bugs caused by 0-height widgets in a ListBox - - -Urwid 1.0.1 - - * Fix for Terminal widget in BSD/OSX - - * Fix for a Filler mouse_event() position bug - - * Fix support for mouse positions up to x=255, y=255 - - * Fixes for a number of string encoding issues under Python 3 - - * Fix for a LineBox border __init__() parameters - - * Fix input input of UTF-8 in tour.py example by converting captions - to unicode - - * Fix tutorial examples' use of TextCanvas and switch to using - unicode literals - - * Prevent raw_display from calling tcseattr() or tcgetattr() on - non-ttys - - * Disable curses_display external event loop support: screen resizing - and gpm events are not properly supported - - * Mark PollingListWalker as deprecated - - -Urwid 1.0.0 - - * New support for Python 3.2 from the same 2.x code base, - requires distribute instead of setuptools (by Kirk McDonald, - Wendell, Marien Zwart) everything except TwistedEventLoop and - GLibEventLoop is supported - - * New experimental Terminal widget with xterm emulation and - terminal.py example program (by aszlig) - - * Edit widget now supports a mask (for passwords), has a - insert_text_result() method for full-field validation and - normalizes input text to Unicode or bytes based on the caption - type used - - * New TreeWidget, TreeNode, ParentNode, TreeWalker - and TreeListBox classes for lazy expanding/collapsing tree - views factored out of browse.py example program, with new - treesample.py example program (by Rob Lanphier) - - * MainLoop now calls draw_screen() just before going idle, so extra - calls to draw_screen() in user code may now be removed - - * New MainLoop.watch_pipe() method for subprocess or threaded - communication with the process/thread updating the UI, and new - subproc.py example demonstrating its use - - * New PopUpLauncher and PopUpTarget widgets and MainLoop option - for creating pop-ups and drop-downs, and new pop_up.py example - program - - * New twisted_serve_ssh.py example (by Ali Afshar) that serves - multiple displays over ssh from the same application using - Twisted and the TwistedEventLoop - - * ListBox now includes a get_cursor_coords() method, allowing - nested ListBox widgets - - * Columns widget contents may now be marked to always be treated - as flow widgets for mixing flow and box widgets more easily - - * New lcd_display module with support for CF635 USB LCD panel and - lcd_cf635.py example program with menus, slider controls and a custom - font - - * Shared command_map instance is now stored as Widget._command_map - class attribute and may be overridden in subclasses or individual - widgets for more control over special keystrokes - - * Overlay widget parameters may now be adjusted after creation with - set_overlay_parameters() method - - * New WidgetPlaceholder widget useful for swapping widgets without - having to manipulate a container widget's contents - - * LineBox widgets may now include title text - - * ProgressBar text content and alignment may now be overridden - - * Use reactor.stop() in TwistedEventLoop and document that Twisted's - reactor is not designed to be stopped then restarted - - * curses_display now supports AttrSpec and external event loops - (Twisted or GLib) just like raw_display - - * raw_display and curses_display now support the IBMPC character - set (currently only used by Terminal widget) - - * Fix for a gpm_mev bug preventing user input when on the console - - * Fix for leaks of None objects in str_util extension - - * Fix for WidgetWrap and AttrMap not working with fixed widgets - - * Fix for a lock up when attempting to wrap text containing wide - characters into a single character column - - -Urwid 0.9.9.2 - - * Fix for an Overlay get_cursor_coords(), and Text top-widget bug - - * Fix for a Padding rows() bug when used with width=PACK - - * Fix for a bug with large flow widgets used in an Overlay - - * Fix for a gpm_mev bug - - * Fix for Pile and GraphVScale when rendered with no contents - - * Fix for a Python 2.3 incompatibility (0.9.9 is the last release - to claim support Python 2.3) - - -Urwid 0.9.9.1 - - * Fix for ListBox snapping to selectable widgets taller than the - ListBox itself - - * raw_display switching to alternate buffer now works properly with - Terminal.app - - * Fix for BoxAdapter backwards incompatibility introduced in 0.9.9 - - * Fix for a doctest failure under powerpc - - * Fix for systems with gpm_mev installed but not running gpm - - -Urwid 0.9.9 - - * New support for 256 and 88 color terminals with raw_display - and html_fragment display modules - - * New palette_test example program to demonstrate high color - modes - - * New AttrSpec class for specifying specific colors instead of - using attributes defined in the screen's palette - - * New MainLoop class ties together widgets, user input, screen - display and one of a number of new event loops, removing the - need for tedious, error-prone boilerplate code - - * New GLibEventLoop allows running Urwid applications with GLib - (makes D-Bus integration easier) - - * New TwistedEventLoop allows running Urwid with a Twisted reactor - - * Added new docstrings and doctests to many widget classes - - * New AttrMap widget supports mapping any attribute to any other - attribute, replaces AttrWrap widget - - * New WidgetDecoration base class for AttrMap, BoxAdapter, Padding, - Filler and LineBox widgets creates a common method for accessing - and updating their contained widgets - - * New left and right values may be specified in Padding widgets - - * New command_map for specifying which keys cause actions such as - clicking Button widgets and scrolling ListBox widgets - - * New tty_signal_keys() method of raw_display.Screen and - curses_display.Screen allows changing or disabling the keys used - to send signals to the application - - * Added helpful __repr__ for many widget classes - - * Updated all example programs to use MainLoop class - - * Updated tutorial with MainLoop usage and improved examples - - * Renamed WidgetWrap.w to _w, indicating its intended use as a way - to implement a widget with other widgets, not necessarily as - a container for other widgets - - * Replaced all tabs with 4 spaces, code is now more aerodynamic - (and PEP 8 compliant) - - * Added saving of stdin and stdout in raw_display module allowing - the originals to be redirected - - * Updated BigText widget's HalfBlock5x4Font - - * Fixed graph example CPU usage when animation is stopped - - * Fixed a memory leak related to objects listening for signals - - * Fixed a Popen3 deprecation warning - - -Urwid 0.9.8.4 - - * Fixed incompatibilities with Python 2.6 (by Friedrich Weber) - - * Fixed a SimpleListWalker with emptied list bug (found by Walter - Mundt) - - * Fixed a curses_display stop()/start() bug (found by Christian - Scharkus) - - * Fixed an is_wide_character() segfault on bad input data bug - (by Andrew Psaltis) - - * Fixed a CanvasCache with render() used in both a widget and its - superclass bug (found by Andrew Psaltis) - - * Fixed a ListBox.ends_visible() on empty list bug (found by Marc - Hartstein) - - * Fixed a tutorial example bug (found by Kurtis D. Rader) - - * Fixed an Overlay.keypress() bug (found by Andreas Klöckner) - - * Fixed setuptools configuration (by Andreas Klöckner) - - -Urwid 0.9.8.3 - - * Fixed a canvas cache memory leak affecting 0.9.8, 0.9.8.1 and - 0.9.8.2 (found by John Goodfellow) - - * Fixed a canvas fill_attr() bug (found by Joern Koerner) - - -Urwid 0.9.8.2 - - * Fixed incompatibilities with Python 2.3 - - * Fixed Pile cursor pref_col bug, WidgetWrap rows caching bug, Button - mouse_event with no callback bug, Filler body bug triggered by the - tutorial and a LineBox lline parameter typo. - - -Urwid 0.9.8.1 - - * Fixed a Filler render() bug, a raw_display start()/stop() bug and a - number of problems triggered by very small terminal window sizes. - - -Urwid 0.9.8 - - * Rendering is now significantly faster. - - * New Widget base class for all widgets. It includes automatic caching - of rows() and render() methods. It also adds a new __super attribute - for accessing methods in superclasses. - - Widgets must now call self._invalidate() to notify the cache when - their content has changed. - - To disable caching in a widget set the class variable no_cache to a - list that includes the string "render". - - * Canvas classes have been reorganized: Canvas has been renamed to - TextCanvas and Canvas is now the base class for all canvases. New - canvas classes include BlankCanvas, SolidCanvas and CompositeCanvas. - - * External event loops may now be used with the raw_display module. The - new methods get_input_descriptors() and get_input_nonblocking() - should be used instead of get_input() to allow input processing - without blocking. - - * The Columns, Pile and ListBox widgets now choose their first - selectable child widget as the focus widget by defaut. - - * New ListWalker base class for list walker classes. - - * New Signals class that will be used to improve the existing event - callbacks. Currently it is used for ListWalker objects to notify - their ListBox when their content has changed. - - * SimpleListWalker now behaves as a list and supports all list - operations. This class now detects when changes are made to the list - and notifies the ListBox object. New code should use this class to - wrap lists of widgets before passing them to the ListBox - constructor. - - * New PollingListWalker class is now the default list walker that is - used when passing a simple list to the ListBox constructor. This - class is intended for backwards compatibility only. When this class - is used the ListBox object is unable to cache its render() method. - - * The curses_display module can now draw in the lower-right corner of - the screen. - - * All display modules now have start() and stop() methods that may be - used instead of calling run_wrapper(). - - * The raw_display module now uses an alternate buffer so that the - original screen can be restored on exit. The old behaviour is - available by seting the alternate_buffer parameter of start() or - run_wrapper() to False. - - * Many internal string processing functions have been rewritten in C to - improve their performance. - - * Compatible with Python >= 2.2. Python 2.1 is no longer supported. - - -Urwid 0.9.7.2 - - * Improved performance in UTF-8 mode when ASCII text is used. - - * Fixed a UTF-8 input bug. - - * Added a clear() function to the the display modules to force the - screen to be repainted on the next draw_screen() call. - - -Urwid 0.9.7.1 - - * Fixed bugs in Padding and Overlay widgets introduced in 0.9.7. - - -Urwid 0.9.7 - - * Added initial support for fixed widgets - widgets that have a fixed - size on screen. Fixed widgets expect a size parameter equal to (). - Fixed widgets must implement the pack(..) function to return their - size. - - * New BigText class that draws text with fonts made of grids of - character cells. BigText is a fixed widget and doesn't do any - alignment or wrapping. It is intended for banners and number readouts - that need to stand out on the screen. - - Fonts: Thin3x3Font, Thin4x3Font, Thin6x6Font (full ascii) - - UTF-8 only fonts: HalfBlock5x4Font, HalfBlock6x5Font, - HalfBlockHeavy6x5Font, HalfBlock7x7Font (full ascii) - - New function get_all_fonts() may be used to get a list of the - available fonts. - - * New example program bigtext.py demonstrates use of BigText. - - * Padding class now has a clipping mode that pads or clips fixed - widgets to make them behave as flow widgets. - - * Overlay class can now accept a fixed widget as the widget to display - "on top". - - * New Canvas functions: pad_trim() and pad_trim_left_right(). - - * Fixed a bug in Filler.get_cursor_coords() that causes a crash if the - contained widget's get_cursor_coords() function returns None. - - * Fixed a bug in Text.pack() that caused an infinite loop when the text - contained a newline. This function is not currently used by Urwid. - - * Edit.__init__() now calls set_edit_text() to initialize its text. - - * Overlay.calculate_padding_filler() and Padding.padding_values() now - include focus parameters. - - -Urwid 0.9.6 - - * Fixed Unicode conversion and locale issues when using Urwid with - Python < 2.4. The graph.py example program should now work properly - with older versions of Python. - - * The docgen_tutorial.py script can now write out the tutorial example - programs as individual files. - - * Updated reference documentation table of contents to show which - widgets are flow and/or box widgets. - - * Columns.set_focus(..) will now accept an integer or a widget as its - parameter. - - * Added detection for rxvt's HOME and END escape sequences. - - * Added support for setuptools (improved distutils). - - -Urwid 0.9.5 - - * Some Unicode characters are now converted to use the G1 alternate - character set with DEC special and line drawing characters. These - Unicode characters should now "just work" in almost all terminals and - encodings. - - When Urwid is run with the UTF-8 encoding the characters are left as - UTF-8 and not converted. - - The characters converted are: - - \u00A3 (£), \u00B0 (°), \u00B1 (±), \u00B7 (·), \u03C0 (π), - \u2260 (≠), \u2264 (≤), \u2265 (≥), \u23ba (⎺), \u23bb (⎻), - \u23bc (⎼), \u23bd (⎽), \u2500 (─), \u2502 (│), \u250c (┌), - \u2510 (┐), \u2514 (└), \u2518 (┘), \u251c (├), \u2524 (┤), - \u252c (┬), \u2534 (┴), \u253c (┼), \u2592 (▒), \u25c6 (◆) - - * New SolidFill class for filling an area with a single character. - - * New LineBox class for wrapping widgets in a box made of line- drawing - characters. May be used as a box widget or a flow widget. - - * New example program graph.py demonstrates use of BarGraph, LineBox, - ProgressBar and SolidFill. - - * Pile class may now be used as a box widget and contain a mix of box - and flow widgets. - - * Columns class may now contain a mix of box and flow widgets. The box - widgets will take their height from the maximum height of the flow - widgets. - - * Improved the smoothness of resizing with raw_display module. The - module will now try to stop updating the screen when a resize event - occurs during the update. - - * The Edit and IntEdit classes now use their set_edit_text() and - set_edit_pos() functions when handling keypresses, so those functions - may be overridden to catch text modification. - - * The set_state() functions in the CheckBox and RadioButton classes now - have a do_callback parameter that determines if the callback function - registered will be called. - - * Fixed a newly introduced incompatibility with python < 2.3. - - * Fixed a missing symbol in curses_display when python is linked - against libcurses. - - * Fixed mouse handling bugs in the Frame and Overlay classes. - - * Fixed a Padding bug when the left or right has no padding. - - -Urwid 0.9.4 - - * Enabled mouse handling across the Urwid library. - - Added a new mouse_event() method to the Widget interface definition - and to the following widgets: Edit, CheckBox, RadioButton, Button, - GridFlow, Padding, Filler, Overlay, Frame, Pile, Columns, BoxAdapter - and ListBox. - - Updated example programs browse.py, calc.py, dialog.py, edit.py and - tour.py to support mouse input. - - * Released the files used to generate the reference and tutorial - documentation: docgen_reference.py, docgen_tutorial.py and - tmpl_tutorial.html. The "docgen" scripts write the documentation to - stdout. docgen_tutorial.py requires the Templayer HTML templating - library to run: http://excess.org/templayer/ - - * Improved Widget and List Walker interface documentation. - - * Fixed a bug in the handling of invalid UTF-8 data. All invalid - characters are now replaced with '?' characters when displayed. - - -Urwid 0.9.3 - - * Improved mouse reporting. - - The raw_display module now detects gpm mouse events by reading - /usr/bin/mev output. The curses_display module already supports gpm - directly. - - Mouse drag events are now reported by raw_display in terminals that - provide button event tracking and on the console with gpm. Note that - gpm may report coordinates off the screen if the user drags the mouse - off the edge. - - Button release events now report which button was released if that - information is available, currently only on the console with gpm. - - * Added display of raw keycodes to the input_test.py example program. - - * Fixed a text layout bug affecting clipped text with blank lines, and - another related to wrapped text starting with a space character. - - * Fixed a Frame.keypress() bug that caused it to call keypress on - unselectable widgets. - - -Urwid 0.9.2 - - * Preliminary mouse support was added to the raw_display and - curses_display modules. A new Screen.set_mouse_tracking() method was - added to enable mouse tracking. Mouse events are returned alongside - keystrokes from the Screen.get_input() method. - - The widget interface does not yet include mouse handling. This will - be addressed in the next release. - - * A new convenience function is_mouse_event() was added to help in - separating mouse events from keystrokes. - - * Added a new example program input_test.py. This program displays the - keyboard and mouse input it receives. It may be run as a CGI script - or from the command line. On the command line it defaults to using - the curses_display module, use input_test.py raw to use the - raw_display module instead. - - * Fixed an Edit.render() bug that caused it to render the cursor in a - different location than that reported by Edit.get_cursor_coords() in - some circumstances. - - * Fixed a bug preventing use of UTF-8 characters with Divider widgets. - - -Urwid 0.9.1 - - * BarGraph and ProgressBar can now display data more accurately by - using the UTF-8 vertical and horizontal eighth characters. This - behavior will be enabled when the UTF-8 encoding is detected and - "smoothed" attributes are passed to the BarGraph or ProgressBar - constructors. - - * New get_encoding_mode() function to determine how Urwid will treat - raw string data. - - * New raw_display.signal_init() and raw_display.signal_restore() - methods that may be overridden by threaded applications that need to - call signal.signal() from their main thread. - - * Fixed a bug that prevented the use of UTF-8 strings in text markup. - - * Removed some forgotten asserts that broke 8-bit and CJK input. - - -Urwid 0.9.0 - - * New support for UTF-8 encoding including input, display and editing - of narrow and wide (CJK) characters. - - Preliminary combining (zero-width) character support is included, but - full support will require terminal behavior detection. - - Right-to-Left input and display are not implemented. - - * New raw_display module that handles console display without relying - on external libraries. This module was written as a work around for - the lack of UTF-8 support in the standard version of ncurses. - - Eliminates "dead corner" in the bottom right of the screen. - - Avoids use of bold text in xterm and gnome-terminal for improved - text legibility. - - * Fixed Overlay bug related to UTF-8 handling. - - * Fixed Edit.move_cursor_to_coords(..) bug related to wide characters - in UTF-8 encoding. - - -Urwid 0.9.0-pre3 - - * Fixed Canvas attribute padding bug related to -pre1 changes. - - -Urwid 0.9.0-pre2 - - * Replaced the custom align and wrap modes in example program calc.py - with a new layout class. - - * Fixed Overlay class call to Canvas.overlay() broken by -pre1 changes. - - * Fixed Padding bug related to Canvas -pre1 changes. - - -Urwid 0.9.0-pre1 - - * New support for UTF-8 encoding. Unicode strings may be used and will - be converted to the current encoding when output. Regular strings in - the current encoding may still be used. - - PLEASE NOTE: There are issues related to displaying UTF-8 characters - with the curses_display module that have not yet been resolved. - - * New set_encoding() function replaces util.set_double_byte_encoding(). - - * New supports_unicode() function to query if unicode strings with - characters outside the ascii range may be used with the current - encoding. - - * New TextLayout and StandardTextLayout classes to perform text - wrapping and alignment. Text widgets now have a layout parameter to - allow use of custom TextLayout objects. - - * New layout structure replaces line translation structure. Layout - structure now allows arbitrary reordering/positioning of text - segments, inclusion of UTF-8 characters and insertion of text not - found in the original text string. - - * Removed util.register_align_mode() and util.register_wrap_mode(). - Their functionality has been replaced by the new layout classes. - - -Urwid 0.8.10 - - * Expanded tutorial to cover advanced ListBox usage, custom widget - classes and the Pile, BoxAdapter, Columns, GridFlow and Overlay - classes. - - * Added escape sequence for "shift tab" to curses_display. - - * Added ListBox.set_focus_valign() to allow positioning of the focus - widget within the ListBox. - - * Added WidgetWrap class for extending existing widgets without - inheriting their complete namespace. - - * Fixed web_display/mozilla breakage from 0.8.9. Fixed crash on invalid - locale setting. Fixed ListBox slide-back bug. Fixed improper space - trimming in calculate_alignment(). Fixed browse.py example program - rows bug. Fixed sum definition, use of long ints for python2.1. Fixed - warnings with python2.1. Fixed Padding.get_pref_col() bug. Fixed - Overlay splitting CJK characters bug. - - -Urwid 0.8.9 - - * New Overlay class for drawing widgets that obscure parts of other - widgets. May be used for drop down menus, combo boxes, overlapping - "windows", caption text etc. - - * New BarGraph, GraphVScale and ProgressBar classes for graphical - display of data in Urwid applications. - - * New method for configuring keyboard input timeouts and delays: - curses_display.Screen.set_input_timeouts(). - - * Fixed a ListBox.set_focus() bug. - - -Urwid 0.8.8 - - * New web_display module that emulates a console display within a web - browser window. Application must be run as a CGI script under Apache. - - Supports font/window resizing, keepalive for long-lived connections, - limiting maximum concurrent connections, polling and connected update - methods. Tested with Mozilla Firefox and Internet Explorer. - - * New BoxAdapter class for using box widgets in places that usually - expect flow widgets. - - * New curses_display input handling with better ESC key detection and - broader escape code support. - - * Shortened resize timeout on gradual resize to improve responsiveness. - - -Urwid 0.8.7 - - * New widget classes: Button, RadioButton, CheckBox. - - * New layout widget classes: Padding, GridFlow. - - * New dialog.py example program that behaves like dialog(1) command. - - * Pile widgets now support selectable items, focus changing with up and - down keys and setting the cursor position. - - * Frame widgets now support selectable items in the header and footer. - - * Columns widgets now support fixed width and relative width columns, a - minimum width for all columns, selectable items within columns - containing flow widgets (already supported for box widgets), focus - changing with left and right keys and setting the cursor position. - - * Filler widgets may now wrap box widgets and have more alignment options. - - * Updated tour.py example program to show new widget types and - features. - - * Avoid hogging cpu on gradual window resize and fix for slow resize - with cygwin's broken curses implementation. - - * Fixed minor CJK problem and curs_set() crash under MacOSX and Cygwin. - - * Fixed crash when deleting cells in calc.py example program. - - -Urwid 0.8.6 - - * Improved support for CJK double-byte encodings: BIG5, UHC, GBK, - GB2312, CN-GB, EUC-KR, EUC-CN, EUC-JP (JISX 0208 only) and EUC-TW - (CNS 11643 plain 1 only) - - * Added support for ncurses' use_default_colors() function to - curses_display module (Python >= 2.4). - - register_palette() and register_palette_entry() now accept "default" - as foreground and/or background. If the terminal's default attributes - cannot be detected black on light gray will be used to accomodate - terminals with always-black cursors. - - "default" is now the default for text with no attributes. This means - that areas with no attributes will change from light grey on black - (curses default) to black on light gray or the terminal's default. - - * Modified examples to not use black as background of Edit widgets. - - * Fixed curses_display curs_set() call so that cursor is hidden when - widget in focus has no cursor position. - - -Urwid 0.8.5 - - * New tutorial covering basic operation of: curses_display.Screen, - Canvas, Text, FlowWidget, Filler, BoxWidget, AttrWrap, Edit, ListBox - and Frame classes - - * New widget class: Filler - - * New ListBox functions: get_focus(), set_focus() - - * Debian packages for Python 2.4. - - * Fixed curses_display bug affecting text with no attributes. - - -Urwid 0.8.4 - - * Improved support for Cyrillic and other simple 8-bit encodings. - - * Added new functions to simplify taking screenshots: - html_fragment.screenshot_init() and - html_fragment.screenshot_collect() - - * Improved urwid/curses_display.py input debugging - - * Fixed cursor in screenshots of CJK text. Fixed "end" key in Edit - boxes with CJK text. - - -Urwid 0.8.3 - - * Added support for CJK double-byte encodings. - - Word wrapping mode "space" will wrap on edges of double width - characters. Wrapping and clipping will not split double width - characters. - - curses_display.Screen.get_input() may now return double width - characters. Text and Edit classes will work with a mix of regular and - double width characters. - - * Use new method Edit.set_edit_text() instead of Edit.update_text(). - - * Minor improvements to edit.py example program. - - -Urwid 0.8.2 - - * Re-released under GNU Lesser General Public License. - - -Urwid 0.8.1 - - * Added support for monochrome terminals. see - curses_display.Screen.register_palette_entry() and example programs. - set TERM=xterm-mono to test programs in monochrome mode. - - * Added unit testing code test_urwid.py to the examples. - - * Can now run urwid/curses_display.py to test your terminal's input and - colour rendering. - - * Fixed an OSX browse.py compatibility issue. Added some OSX keycodes. - - -Urwid 0.8.0 - - * Initial Release diff --git a/MANIFEST.in b/MANIFEST.in index 597296f..796f967 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,3 @@ recursive-include examples *.py *.tac recursive-include docs *.rst *.py *.py.xdotool *.png *.html *.sh Makefile -include COPYING CHANGELOG +include COPYING CHANGELOG README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..cfc4af8 --- /dev/null +++ b/README.rst @@ -0,0 +1,24 @@ +.. image:: https://travis-ci.org/wardi/urwid.png?branch=master + :alt: build status + :target: https://travis-ci.org/wardi/urwid/ + +`Development version documentation <http://urwid.readthedocs.org/en/latest/>`_ + +.. content-start + +Urwid is a console user interface library for Python. +It includes many features useful for text console application developers including: + +- Applcations resize quickly and smoothly +- Automatic, programmable text alignment and wrapping +- Simple markup for setting text attributes within blocks of text +- Powerful list box with programmable content for scrolling all widget types +- Your choice of event loops: Twisted, Glib, Tornado or select-based loop +- Pre-built widgets include edit boxes, buttons, check boxes and radio buttons +- Display modules include raw, curses, and experimental LCD and web displays +- Support for UTF-8, simple 8-bit and CJK encodings +- 256 and 88 color mode support +- Compatible with Python 2.6, 2.7, 3.2+ and PyPy + +Home Page: + http://urwid.org/ diff --git a/bin/build-pages.sh b/bin/build-pages.sh new file mode 100755 index 0000000..6e27158 --- /dev/null +++ b/bin/build-pages.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +cd "`dirname $0`/.." +rm -r build/sphinx || true +python setup.py build_sphinx + +git checkout gh-pages +git rm `git ls-files` +git checkout HEAD CNAME +git checkout HEAD .nojekyll +cp -r build/sphinx/html/. . +git add `find build/sphinx/html | cut -c 19-` +git status diff --git a/bin/deps.py b/bin/deps.py new file mode 100755 index 0000000..01489d6 --- /dev/null +++ b/bin/deps.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +""" +show optional Urwid dependencies installed +""" +deps = [] + +try: + import gi.repository + deps.append("pygobject") +except ImportError: + pass + +try: + import tornado + deps.append("tornado") +except ImportError: + pass + +try: + import twisted + deps.append("twisted") +except ImportError: + pass + +print(" ".join(deps)) diff --git a/docs/Makefile b/docs/Makefile index 9c9ef20..7ec3bdb 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,5 +1,5 @@ -PYTHON_SOURCE=$(shell echo tutorial/*.py.xdotool) +PYTHON_SOURCE=$(shell echo */*.py.xdotool) TARGETS=${PYTHON_SOURCE:.py.xdotool=1.png} TOOLS=tools/compile_pngs.sh tools/screenshots.sh diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..0bfb7c5 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,1095 @@ + +Changelog +--------- + + +Urwid 1.2.1 +=========== + +2014-04-04 + + * Fix false failures of event loop tests + + * Remove extra newline generated on exit of raw_display + + * Documentation fixes (by Paul Ivanov) + + +Urwid 1.2.0 +=========== + +2014-02-09 + + * Add support for PyPy, drop support for Python 2.4, 2.5 + + * Signals now support using weakly referenced arguments to help + avoid leaking objects when a signal consumer is no longer + referenced (by Matthijs Kooijman) + + * Add TornadoEventLoop class (by Alexander Glyzov) + + * Update GlibEventLoop to use python-gi for Python3 compatibility + (by Israel Garcia) + + * Automate testing with Python 2.6, 2.7, 3.2, 3.3 and PyPy using + travis-ci + + * New container method get_focus_widgets() (by Matthijs Kooijman) + + * Add support for double and triple click mouse events + (by Igor Kotrasiński) + + * Allow disabling and re-enabling of mouse tracking + (by Jim Garrison) + + * Create section in docs for example program screenshots generated + as images like the tutorial examples + + * Add suggested basic color combination images to manual + + * Fall back to 80x24 if screen size detection fails + + * Fix screen.stop(), screen.start() disabling mouse events + + * Fix to make GridFlow v_sep argument behave as documented + + * Fix for registering high palette entries in the form "hX" where + X > 15 so that basic colors are applied in 88-color mode + + * Fix for raw_display clear-right escape not working with + standout attribute on some terminals + + * Fix for Terminal widget select loop: retry when interrupted + + +Urwid 1.1.2 +=========== + +2013-12-30 + + * Move to urwid.org and use sphinx docs for generating whole site, + move changelog to docs/changelog.rst + + * Fix encoding exceptions when unicode used on non-UTF-8 terminal + + * Fix for suspend and resume applications with ^Z + + * Fix for tmux and screen missing colors on right bug + + * Fix Pile zero-weighted items and mouse_event when empty + + * Fix Terminal select() not retrying when interrupted by signal + + * Fix for Padding.align and width change not invalidating + + +Urwid 1.1.1 +=========== + +2012-11-15 + + * Fix for Pile not changing focus on mouse events + + * Fix for Overlay.get_cursor_coords() + + +Urwid 1.1.0 +=========== + +2012-10-23 + + * New common container API: focus, focus_position, contents, + options(), get_focus_path(), set_focus_path(), __getitem__, + __iter__(), __reversed__() implemented across all included + container widgets + + A full description doesn't fit here, see the Container Widgets + section in the manual for details + + * New Sphinx-based documentation now included in source: + Tutorial rewritten, manual revised and new reference based + on updated docstrings (by Marco Giusti, Patrick Totzke) + + * New list walker SimpleFocusListWalker like SimpleListWalker but + updates focus position as items are inserted or removed + + * New decoration widget WidgetDisable to disable interaction + with the widgets it wraps + + * SelectableIcon selectable text widget used by button widgets is + now documented (available since 0.9.9) + + * Columns widget now tries to keep column in focus visible, hiding + columns on the left when necessary + + * Padding widget now defaults to ('relative', 100) instead of + 'pack' so that left and right parameters are more useful and more + child widgets are supported + + * New list walker "API Version 2" that is simpler for many list + walker uses; "API Version 1" will still continue to be supported + + * List walkers may now allow iteration from the absolute top or + bottom of the list if they provide a positions() method + + * raw_display now erases to the end of the line with EL escape + sequence to improve copy+paste behavior for some terminals + + * Filler now has top and bottom parameters like Padding's left and + right parameters and accepts 'pack' instead of None as a height + value for widgets that calculate their own number of rows + + * Pile and Columns now accepts 'pack' instead of 'flow' for widgets + that calculate their own number of rows or columns + + * Pile and Columns now accept 'given' instead of 'fixed' for + cases where the number of rows or columns are specified by the + container options + + * Pile and Columns widgets now accept any iterable to their + __init__() methods + + * Widget now has a default focus_position property that raises + an IndexError when read to be consistent with new common container + API + + * GridFlow now supports multiple cell widths within the same widget + + * BoxWidget, FlowWidget and FixedWidget are deprecated, instead + use the sizing() function or _sizing attribute to specify the + supported sizing modes for your custom widgets + + * Some new shift+arrow and numpad input sequences from RXVT and + xterm are now recognized + + * Fix for alarms when used with a screen event loop (e.g. + curses_display) + + * Fix for raw_display when terminal width is 1 column + + * Fixes for a Columns.get_cursor_coords() regression and a + SelectableIcon.get_cursor_coords() bug + + * Fixes for incorrect handling of box columns in a number of + Columns methods when that column is selectable + + * Fix for Terminal widget input handling with Python 3 + + +Urwid 1.0.3 +=========== + +2012-11-15 + + * Fix for alarms when used with a screen event loop (e.g. + curses_display) + + * Fix for Overlay.get_cursor_coords() + + +Urwid 1.0.2 +=========== + +2012-07-13 + + * Fix for bug when entering Unicode text into Edit widget with + bytes caption + + * Fix a regression when not running in UTF-8 mode + + * Fix for a MainLoop.remove_watch_pipe() bug + + * Fix for a bug when packing empty Edit widgets + + * Fix for a ListBox "contents too long" error with very large + Edit widgets + + * Prevent ListBoxes from selecting 0-height selectable widgets + when moving up or down + + * Fix a number of bugs caused by 0-height widgets in a ListBox + + +Urwid 1.0.1 +=========== + +2011-11-28 + + * Fix for Terminal widget in BSD/OSX + + * Fix for a Filler mouse_event() position bug + + * Fix support for mouse positions up to x=255, y=255 + + * Fixes for a number of string encoding issues under Python 3 + + * Fix for a LineBox border __init__() parameters + + * Fix input input of UTF-8 in tour.py example by converting captions + to unicode + + * Fix tutorial examples' use of TextCanvas and switch to using + unicode literals + + * Prevent raw_display from calling tcseattr() or tcgetattr() on + non-ttys + + * Disable curses_display external event loop support: screen resizing + and gpm events are not properly supported + + * Mark PollingListWalker as deprecated + + +Urwid 1.0.0 +=========== + +2011-09-22 + + * New support for Python 3.2 from the same 2.x code base, + requires distribute instead of setuptools (by Kirk McDonald, + Wendell, Marien Zwart) everything except TwistedEventLoop and + GLibEventLoop is supported + + * New experimental Terminal widget with xterm emulation and + terminal.py example program (by aszlig) + + * Edit widget now supports a mask (for passwords), has a + insert_text_result() method for full-field validation and + normalizes input text to Unicode or bytes based on the caption + type used + + * New TreeWidget, TreeNode, ParentNode, TreeWalker + and TreeListBox classes for lazy expanding/collapsing tree + views factored out of browse.py example program, with new + treesample.py example program (by Rob Lanphier) + + * MainLoop now calls draw_screen() just before going idle, so extra + calls to draw_screen() in user code may now be removed + + * New MainLoop.watch_pipe() method for subprocess or threaded + communication with the process/thread updating the UI, and new + subproc.py example demonstrating its use + + * New PopUpLauncher and PopUpTarget widgets and MainLoop option + for creating pop-ups and drop-downs, and new pop_up.py example + program + + * New twisted_serve_ssh.py example (by Ali Afshar) that serves + multiple displays over ssh from the same application using + Twisted and the TwistedEventLoop + + * ListBox now includes a get_cursor_coords() method, allowing + nested ListBox widgets + + * Columns widget contents may now be marked to always be treated + as flow widgets for mixing flow and box widgets more easily + + * New lcd_display module with support for CF635 USB LCD panel and + lcd_cf635.py example program with menus, slider controls and a custom + font + + * Shared command_map instance is now stored as Widget._command_map + class attribute and may be overridden in subclasses or individual + widgets for more control over special keystrokes + + * Overlay widget parameters may now be adjusted after creation with + set_overlay_parameters() method + + * New WidgetPlaceholder widget useful for swapping widgets without + having to manipulate a container widget's contents + + * LineBox widgets may now include title text + + * ProgressBar text content and alignment may now be overridden + + * Use reactor.stop() in TwistedEventLoop and document that Twisted's + reactor is not designed to be stopped then restarted + + * curses_display now supports AttrSpec and external event loops + (Twisted or GLib) just like raw_display + + * raw_display and curses_display now support the IBMPC character + set (currently only used by Terminal widget) + + * Fix for a gpm_mev bug preventing user input when on the console + + * Fix for leaks of None objects in str_util extension + + * Fix for WidgetWrap and AttrMap not working with fixed widgets + + * Fix for a lock up when attempting to wrap text containing wide + characters into a single character column + + +Urwid 0.9.9.2 +============= + +2011-07-13 + + * Fix for an Overlay get_cursor_coords(), and Text top-widget bug + + * Fix for a Padding rows() bug when used with width=PACK + + * Fix for a bug with large flow widgets used in an Overlay + + * Fix for a gpm_mev bug + + * Fix for Pile and GraphVScale when rendered with no contents + + * Fix for a Python 2.3 incompatibility (0.9.9 is the last release + to claim support Python 2.3) + + +Urwid 0.9.9.1 +============= + +2010-01-25 + + * Fix for ListBox snapping to selectable widgets taller than the + ListBox itself + + * raw_display switching to alternate buffer now works properly with + Terminal.app + + * Fix for BoxAdapter backwards incompatibility introduced in 0.9.9 + + * Fix for a doctest failure under powerpc + + * Fix for systems with gpm_mev installed but not running gpm + + +Urwid 0.9.9 +=========== + +2009-11-15 + + * New support for 256 and 88 color terminals with raw_display + and html_fragment display modules + + * New palette_test example program to demonstrate high color + modes + + * New AttrSpec class for specifying specific colors instead of + using attributes defined in the screen's palette + + * New MainLoop class ties together widgets, user input, screen + display and one of a number of new event loops, removing the + need for tedious, error-prone boilerplate code + + * New GLibEventLoop allows running Urwid applications with GLib + (makes D-Bus integration easier) + + * New TwistedEventLoop allows running Urwid with a Twisted reactor + + * Added new docstrings and doctests to many widget classes + + * New AttrMap widget supports mapping any attribute to any other + attribute, replaces AttrWrap widget + + * New WidgetDecoration base class for AttrMap, BoxAdapter, Padding, + Filler and LineBox widgets creates a common method for accessing + and updating their contained widgets + + * New left and right values may be specified in Padding widgets + + * New command_map for specifying which keys cause actions such as + clicking Button widgets and scrolling ListBox widgets + + * New tty_signal_keys() method of raw_display.Screen and + curses_display.Screen allows changing or disabling the keys used + to send signals to the application + + * Added helpful __repr__ for many widget classes + + * Updated all example programs to use MainLoop class + + * Updated tutorial with MainLoop usage and improved examples + + * Renamed WidgetWrap.w to _w, indicating its intended use as a way + to implement a widget with other widgets, not necessarily as + a container for other widgets + + * Replaced all tabs with 4 spaces, code is now more aerodynamic + (and PEP 8 compliant) + + * Added saving of stdin and stdout in raw_display module allowing + the originals to be redirected + + * Updated BigText widget's HalfBlock5x4Font + + * Fixed graph example CPU usage when animation is stopped + + * Fixed a memory leak related to objects listening for signals + + * Fixed a Popen3 deprecation warning + + +Urwid 0.9.8.4 +============= + +2009-03-13 + + * Fixed incompatibilities with Python 2.6 (by Friedrich Weber) + + * Fixed a SimpleListWalker with emptied list bug (found by Walter + Mundt) + + * Fixed a curses_display stop()/start() bug (found by Christian + Scharkus) + + * Fixed an is_wide_character() segfault on bad input data bug + (by Andrew Psaltis) + + * Fixed a CanvasCache with render() used in both a widget and its + superclass bug (found by Andrew Psaltis) + + * Fixed a ListBox.ends_visible() on empty list bug (found by Marc + Hartstein) + + * Fixed a tutorial example bug (found by Kurtis D. Rader) + + * Fixed an Overlay.keypress() bug (found by Andreas Klöckner) + + * Fixed setuptools configuration (by Andreas Klöckner) + + +Urwid 0.9.8.3 +============= + +2008-07-14 + + * Fixed a canvas cache memory leak affecting 0.9.8, 0.9.8.1 and + 0.9.8.2 (found by John Goodfellow) + + * Fixed a canvas fill_attr() bug (found by Joern Koerner) + + +Urwid 0.9.8.2 +============= + +2008-05-19 + + * Fixed incompatibilities with Python 2.3 + + * Fixed Pile cursor pref_col bug, WidgetWrap rows caching bug, Button + mouse_event with no callback bug, Filler body bug triggered by the + tutorial and a LineBox lline parameter typo. + + +Urwid 0.9.8.1 +============= + +2007-06-21 + + * Fixed a Filler render() bug, a raw_display start()/stop() bug and a + number of problems triggered by very small terminal window sizes. + + +Urwid 0.9.8 +=========== + +2007-03-23 + + * Rendering is now significantly faster. + + * New Widget base class for all widgets. It includes automatic caching + of rows() and render() methods. It also adds a new __super attribute + for accessing methods in superclasses. + + Widgets must now call self._invalidate() to notify the cache when + their content has changed. + + To disable caching in a widget set the class variable no_cache to a + list that includes the string "render". + + * Canvas classes have been reorganized: Canvas has been renamed to + TextCanvas and Canvas is now the base class for all canvases. New + canvas classes include BlankCanvas, SolidCanvas and CompositeCanvas. + + * External event loops may now be used with the raw_display module. The + new methods get_input_descriptors() and get_input_nonblocking() + should be used instead of get_input() to allow input processing + without blocking. + + * The Columns, Pile and ListBox widgets now choose their first + selectable child widget as the focus widget by defaut. + + * New ListWalker base class for list walker classes. + + * New Signals class that will be used to improve the existing event + callbacks. Currently it is used for ListWalker objects to notify + their ListBox when their content has changed. + + * SimpleListWalker now behaves as a list and supports all list + operations. This class now detects when changes are made to the list + and notifies the ListBox object. New code should use this class to + wrap lists of widgets before passing them to the ListBox + constructor. + + * New PollingListWalker class is now the default list walker that is + used when passing a simple list to the ListBox constructor. This + class is intended for backwards compatibility only. When this class + is used the ListBox object is unable to cache its render() method. + + * The curses_display module can now draw in the lower-right corner of + the screen. + + * All display modules now have start() and stop() methods that may be + used instead of calling run_wrapper(). + + * The raw_display module now uses an alternate buffer so that the + original screen can be restored on exit. The old behaviour is + available by seting the alternate_buffer parameter of start() or + run_wrapper() to False. + + * Many internal string processing functions have been rewritten in C to + improve their performance. + + * Compatible with Python >= 2.2. Python 2.1 is no longer supported. + + +Urwid 0.9.7.2 +============= + +2007-01-03 + + * Improved performance in UTF-8 mode when ASCII text is used. + + * Fixed a UTF-8 input bug. + + * Added a clear() function to the the display modules to force the + screen to be repainted on the next draw_screen() call. + + +Urwid 0.9.7.1 +============= + +2006-10-03 + + * Fixed bugs in Padding and Overlay widgets introduced in 0.9.7. + + +Urwid 0.9.7 +=========== + +2006-10-01 + + * Added initial support for fixed widgets - widgets that have a fixed + size on screen. Fixed widgets expect a size parameter equal to (). + Fixed widgets must implement the pack(..) function to return their + size. + + * New BigText class that draws text with fonts made of grids of + character cells. BigText is a fixed widget and doesn't do any + alignment or wrapping. It is intended for banners and number readouts + that need to stand out on the screen. + + Fonts: Thin3x3Font, Thin4x3Font, Thin6x6Font (full ascii) + + UTF-8 only fonts: HalfBlock5x4Font, HalfBlock6x5Font, + HalfBlockHeavy6x5Font, HalfBlock7x7Font (full ascii) + + New function get_all_fonts() may be used to get a list of the + available fonts. + + * New example program bigtext.py demonstrates use of BigText. + + * Padding class now has a clipping mode that pads or clips fixed + widgets to make them behave as flow widgets. + + * Overlay class can now accept a fixed widget as the widget to display + "on top". + + * New Canvas functions: pad_trim() and pad_trim_left_right(). + + * Fixed a bug in Filler.get_cursor_coords() that causes a crash if the + contained widget's get_cursor_coords() function returns None. + + * Fixed a bug in Text.pack() that caused an infinite loop when the text + contained a newline. This function is not currently used by Urwid. + + * Edit.__init__() now calls set_edit_text() to initialize its text. + + * Overlay.calculate_padding_filler() and Padding.padding_values() now + include focus parameters. + + +Urwid 0.9.6 +=========== + +2006-08-22 + + * Fixed Unicode conversion and locale issues when using Urwid with + Python < 2.4. The graph.py example program should now work properly + with older versions of Python. + + * The docgen_tutorial.py script can now write out the tutorial example + programs as individual files. + + * Updated reference documentation table of contents to show which + widgets are flow and/or box widgets. + + * Columns.set_focus(..) will now accept an integer or a widget as its + parameter. + + * Added detection for rxvt's HOME and END escape sequences. + + * Added support for setuptools (improved distutils). + + +Urwid 0.9.5 +=========== + +2006-06-14 + + * Some Unicode characters are now converted to use the G1 alternate + character set with DEC special and line drawing characters. These + Unicode characters should now "just work" in almost all terminals and + encodings. + + When Urwid is run with the UTF-8 encoding the characters are left as + UTF-8 and not converted. + + The characters converted are: + + \u00A3 (£), \u00B0 (°), \u00B1 (±), \u00B7 (·), \u03C0 (π), + \u2260 (≠), \u2264 (≤), \u2265 (≥), \u23ba (⎺), \u23bb (⎻), + \u23bc (⎼), \u23bd (⎽), \u2500 (─), \u2502 (│), \u250c (┌), + \u2510 (┐), \u2514 (└), \u2518 (┘), \u251c (├), \u2524 (┤), + \u252c (┬), \u2534 (┴), \u253c (┼), \u2592 (▒), \u25c6 (◆) + + * New SolidFill class for filling an area with a single character. + + * New LineBox class for wrapping widgets in a box made of line- drawing + characters. May be used as a box widget or a flow widget. + + * New example program graph.py demonstrates use of BarGraph, LineBox, + ProgressBar and SolidFill. + + * Pile class may now be used as a box widget and contain a mix of box + and flow widgets. + + * Columns class may now contain a mix of box and flow widgets. The box + widgets will take their height from the maximum height of the flow + widgets. + + * Improved the smoothness of resizing with raw_display module. The + module will now try to stop updating the screen when a resize event + occurs during the update. + + * The Edit and IntEdit classes now use their set_edit_text() and + set_edit_pos() functions when handling keypresses, so those functions + may be overridden to catch text modification. + + * The set_state() functions in the CheckBox and RadioButton classes now + have a do_callback parameter that determines if the callback function + registered will be called. + + * Fixed a newly introduced incompatibility with python < 2.3. + + * Fixed a missing symbol in curses_display when python is linked + against libcurses. + + * Fixed mouse handling bugs in the Frame and Overlay classes. + + * Fixed a Padding bug when the left or right has no padding. + + +Urwid 0.9.4 +=========== + +2006-05-30 + + * Enabled mouse handling across the Urwid library. + + Added a new mouse_event() method to the Widget interface definition + and to the following widgets: Edit, CheckBox, RadioButton, Button, + GridFlow, Padding, Filler, Overlay, Frame, Pile, Columns, BoxAdapter + and ListBox. + + Updated example programs browse.py, calc.py, dialog.py, edit.py and + tour.py to support mouse input. + + * Released the files used to generate the reference and tutorial + documentation: docgen_reference.py, docgen_tutorial.py and + tmpl_tutorial.html. The "docgen" scripts write the documentation to + stdout. docgen_tutorial.py requires the Templayer HTML templating + library to run: http://excess.org/templayer/ + + * Improved Widget and List Walker interface documentation. + + * Fixed a bug in the handling of invalid UTF-8 data. All invalid + characters are now replaced with '?' characters when displayed. + + +Urwid 0.9.3 +=========== + +2006-05-14 + + * Improved mouse reporting. + + The raw_display module now detects gpm mouse events by reading + /usr/bin/mev output. The curses_display module already supports gpm + directly. + + Mouse drag events are now reported by raw_display in terminals that + provide button event tracking and on the console with gpm. Note that + gpm may report coordinates off the screen if the user drags the mouse + off the edge. + + Button release events now report which button was released if that + information is available, currently only on the console with gpm. + + * Added display of raw keycodes to the input_test.py example program. + + * Fixed a text layout bug affecting clipped text with blank lines, and + another related to wrapped text starting with a space character. + + * Fixed a Frame.keypress() bug that caused it to call keypress on + unselectable widgets. + + +Urwid 0.9.2 +=========== + +2006-03-18 + + * Preliminary mouse support was added to the raw_display and + curses_display modules. A new Screen.set_mouse_tracking() method was + added to enable mouse tracking. Mouse events are returned alongside + keystrokes from the Screen.get_input() method. + + The widget interface does not yet include mouse handling. This will + be addressed in the next release. + + * A new convenience function is_mouse_event() was added to help in + separating mouse events from keystrokes. + + * Added a new example program input_test.py. This program displays the + keyboard and mouse input it receives. It may be run as a CGI script + or from the command line. On the command line it defaults to using + the curses_display module, use input_test.py raw to use the + raw_display module instead. + + * Fixed an Edit.render() bug that caused it to render the cursor in a + different location than that reported by Edit.get_cursor_coords() in + some circumstances. + + * Fixed a bug preventing use of UTF-8 characters with Divider widgets. + + +Urwid 0.9.1 +=========== + +2006-03-06 + + * BarGraph and ProgressBar can now display data more accurately by + using the UTF-8 vertical and horizontal eighth characters. This + behavior will be enabled when the UTF-8 encoding is detected and + "smoothed" attributes are passed to the BarGraph or ProgressBar + constructors. + + * New get_encoding_mode() function to determine how Urwid will treat + raw string data. + + * New raw_display.signal_init() and raw_display.signal_restore() + methods that may be overridden by threaded applications that need to + call signal.signal() from their main thread. + + * Fixed a bug that prevented the use of UTF-8 strings in text markup. + + * Removed some forgotten asserts that broke 8-bit and CJK input. + + +Urwid 0.9.0 +=========== + +2006-02-18 + + * New support for UTF-8 encoding including input, display and editing + of narrow and wide (CJK) characters. + + Preliminary combining (zero-width) character support is included, but + full support will require terminal behavior detection. + + Right-to-Left input and display are not implemented. + + * New raw_display module that handles console display without relying + on external libraries. This module was written as a work around for + the lack of UTF-8 support in the standard version of ncurses. + + Eliminates "dead corner" in the bottom right of the screen. + + Avoids use of bold text in xterm and gnome-terminal for improved + text legibility. + + * Fixed Overlay bug related to UTF-8 handling. + + * Fixed Edit.move_cursor_to_coords(..) bug related to wide characters + in UTF-8 encoding. + + +Urwid 0.9.0-pre3 +================ + +2006-02-13 + + * Fixed Canvas attribute padding bug related to -pre1 changes. + + +Urwid 0.9.0-pre2 +================ + +2006-02-10 + + * Replaced the custom align and wrap modes in example program calc.py + with a new layout class. + + * Fixed Overlay class call to Canvas.overlay() broken by -pre1 changes. + + * Fixed Padding bug related to Canvas -pre1 changes. + + +Urwid 0.9.0-pre1 +================ + +2006-02-08 + + * New support for UTF-8 encoding. Unicode strings may be used and will + be converted to the current encoding when output. Regular strings in + the current encoding may still be used. + + PLEASE NOTE: There are issues related to displaying UTF-8 characters + with the curses_display module that have not yet been resolved. + + * New set_encoding() function replaces util.set_double_byte_encoding(). + + * New supports_unicode() function to query if unicode strings with + characters outside the ascii range may be used with the current + encoding. + + * New TextLayout and StandardTextLayout classes to perform text + wrapping and alignment. Text widgets now have a layout parameter to + allow use of custom TextLayout objects. + + * New layout structure replaces line translation structure. Layout + structure now allows arbitrary reordering/positioning of text + segments, inclusion of UTF-8 characters and insertion of text not + found in the original text string. + + * Removed util.register_align_mode() and util.register_wrap_mode(). + Their functionality has been replaced by the new layout classes. + + +Urwid 0.8.10 +============ + +2005-11-27 + + * Expanded tutorial to cover advanced ListBox usage, custom widget + classes and the Pile, BoxAdapter, Columns, GridFlow and Overlay + classes. + + * Added escape sequence for "shift tab" to curses_display. + + * Added ListBox.set_focus_valign() to allow positioning of the focus + widget within the ListBox. + + * Added WidgetWrap class for extending existing widgets without + inheriting their complete namespace. + + * Fixed web_display/mozilla breakage from 0.8.9. Fixed crash on invalid + locale setting. Fixed ListBox slide-back bug. Fixed improper space + trimming in calculate_alignment(). Fixed browse.py example program + rows bug. Fixed sum definition, use of long ints for python2.1. Fixed + warnings with python2.1. Fixed Padding.get_pref_col() bug. Fixed + Overlay splitting CJK characters bug. + + +Urwid 0.8.9 +=========== + +2005-10-21 + + * New Overlay class for drawing widgets that obscure parts of other + widgets. May be used for drop down menus, combo boxes, overlapping + "windows", caption text etc. + + * New BarGraph, GraphVScale and ProgressBar classes for graphical + display of data in Urwid applications. + + * New method for configuring keyboard input timeouts and delays: + curses_display.Screen.set_input_timeouts(). + + * Fixed a ListBox.set_focus() bug. + + +Urwid 0.8.8 +=========== + +2005-06-13 + + * New web_display module that emulates a console display within a web + browser window. Application must be run as a CGI script under Apache. + + Supports font/window resizing, keepalive for long-lived connections, + limiting maximum concurrent connections, polling and connected update + methods. Tested with Mozilla Firefox and Internet Explorer. + + * New BoxAdapter class for using box widgets in places that usually + expect flow widgets. + + * New curses_display input handling with better ESC key detection and + broader escape code support. + + * Shortened resize timeout on gradual resize to improve responsiveness. + + +Urwid 0.8.7 +=========== + +2005-05-21 + + * New widget classes: Button, RadioButton, CheckBox. + + * New layout widget classes: Padding, GridFlow. + + * New dialog.py example program that behaves like dialog(1) command. + + * Pile widgets now support selectable items, focus changing with up and + down keys and setting the cursor position. + + * Frame widgets now support selectable items in the header and footer. + + * Columns widgets now support fixed width and relative width columns, a + minimum width for all columns, selectable items within columns + containing flow widgets (already supported for box widgets), focus + changing with left and right keys and setting the cursor position. + + * Filler widgets may now wrap box widgets and have more alignment options. + + * Updated tour.py example program to show new widget types and + features. + + * Avoid hogging cpu on gradual window resize and fix for slow resize + with cygwin's broken curses implementation. + + * Fixed minor CJK problem and curs_set() crash under MacOSX and Cygwin. + + * Fixed crash when deleting cells in calc.py example program. + + +Urwid 0.8.6 +=========== + +2005-01-03 + + * Improved support for CJK double-byte encodings: BIG5, UHC, GBK, + GB2312, CN-GB, EUC-KR, EUC-CN, EUC-JP (JISX 0208 only) and EUC-TW + (CNS 11643 plain 1 only) + + * Added support for ncurses' use_default_colors() function to + curses_display module (Python >= 2.4). + + register_palette() and register_palette_entry() now accept "default" + as foreground and/or background. If the terminal's default attributes + cannot be detected black on light gray will be used to accomodate + terminals with always-black cursors. + + "default" is now the default for text with no attributes. This means + that areas with no attributes will change from light grey on black + (curses default) to black on light gray or the terminal's default. + + * Modified examples to not use black as background of Edit widgets. + + * Fixed curses_display curs_set() call so that cursor is hidden when + widget in focus has no cursor position. + + +Urwid 0.8.5 +=========== + +2004-12-15 + + * New tutorial covering basic operation of: curses_display.Screen, + Canvas, Text, FlowWidget, Filler, BoxWidget, AttrWrap, Edit, ListBox + and Frame classes + + * New widget class: Filler + + * New ListBox functions: get_focus(), set_focus() + + * Debian packages for Python 2.4. + + * Fixed curses_display bug affecting text with no attributes. + + +Urwid 0.8.4 +=========== + +2004-11-20 + + * Improved support for Cyrillic and other simple 8-bit encodings. + + * Added new functions to simplify taking screenshots: + html_fragment.screenshot_init() and + html_fragment.screenshot_collect() + + * Improved urwid/curses_display.py input debugging + + * Fixed cursor in screenshots of CJK text. Fixed "end" key in Edit + boxes with CJK text. + + +Urwid 0.8.3 +=========== + +2004-11-15 + + * Added support for CJK double-byte encodings. + + Word wrapping mode "space" will wrap on edges of double width + characters. Wrapping and clipping will not split double width + characters. + + curses_display.Screen.get_input() may now return double width + characters. Text and Edit classes will work with a mix of regular and + double width characters. + + * Use new method Edit.set_edit_text() instead of Edit.update_text(). + + * Minor improvements to edit.py example program. + + +Urwid 0.8.2 +=========== + +2004-11-08 + + * Re-released under GNU Lesser General Public License. + + +Urwid 0.8.1 +=========== + +2004-10-29 + + * Added support for monochrome terminals. see + curses_display.Screen.register_palette_entry() and example programs. + set TERM=xterm-mono to test programs in monochrome mode. + + * Added unit testing code test_urwid.py to the examples. + + * Can now run urwid/curses_display.py to test your terminal's input and + colour rendering. + + * Fixed an OSX browse.py compatibility issue. Added some OSX keycodes. + + +Urwid 0.8.0 +=========== + +2004-10-17 + + * Initial Release diff --git a/docs/conf.py b/docs/conf.py index e612f89..302dff0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,7 +41,7 @@ master_doc = 'index' # General information about the project. project = u'Urwid' -copyright = u'2012, Ian Ward' +copyright = u'2014, Ian Ward et al' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -119,7 +119,7 @@ html_theme_options = { # The name for this set of Sphinx documents. If None, it defaults to # "<project> v<release> documentation". -html_title = "Urwid %s Documentation" % (release,) +html_title = "Urwid %s" % (release,) # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None @@ -268,7 +268,7 @@ texinfo_documents = [ epub_title = u'Urwid' epub_author = u'Ian Ward' epub_publisher = u'Ian Ward' -epub_copyright = u'2012, Ian Ward' +epub_copyright = u'2014, Ian Ward et al' # The language of the text. It defaults to the language option # or en if the language is not set. diff --git a/docs/examples/bigtext.py b/docs/examples/bigtext.py new file mode 120000 index 0000000..119650d --- /dev/null +++ b/docs/examples/bigtext.py @@ -0,0 +1 @@ +../../examples/bigtext.py
\ No newline at end of file diff --git a/docs/examples/bigtext.py.xdotool b/docs/examples/bigtext.py.xdotool new file mode 100644 index 0000000..d245b08 --- /dev/null +++ b/docs/examples/bigtext.py.xdotool @@ -0,0 +1,3 @@ +windowsize --usehints $RXVTWINDOWID 39 21 +key --window $RXVTWINDOWID BackSpace BackSpace BackSpace BackSpace BackSpace BackSpace BackSpace BackSpace BackSpace BackSpace BackSpace BackSpace BackSpace BackSpace BackSpace e x a m p l e Down Down Down Down Down Down Return +key --window $RXVTWINDOWID Up Up Up Return diff --git a/docs/examples/bigtext1.png b/docs/examples/bigtext1.png Binary files differnew file mode 100644 index 0000000..2598916 --- /dev/null +++ b/docs/examples/bigtext1.png diff --git a/docs/examples/bigtext2.png b/docs/examples/bigtext2.png Binary files differnew file mode 100644 index 0000000..1e9906b --- /dev/null +++ b/docs/examples/bigtext2.png diff --git a/docs/examples/bigtext3.png b/docs/examples/bigtext3.png Binary files differnew file mode 100644 index 0000000..5ab0e37 --- /dev/null +++ b/docs/examples/bigtext3.png diff --git a/docs/examples/browse.py b/docs/examples/browse.py new file mode 100755 index 0000000..2bbcc34 --- /dev/null +++ b/docs/examples/browse.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +import real_browse +import os +os.chdir('/usr/share/doc/python') +real_browse.main() diff --git a/docs/examples/browse.py.xdotool b/docs/examples/browse.py.xdotool new file mode 100644 index 0000000..c722435 --- /dev/null +++ b/docs/examples/browse.py.xdotool @@ -0,0 +1,2 @@ +windowsize --usehints $RXVTWINDOWID 79 19 +key --window $RXVTWINDOWID Down plus Down space Down space Down Down Down diff --git a/docs/examples/browse1.png b/docs/examples/browse1.png Binary files differnew file mode 100644 index 0000000..857903a --- /dev/null +++ b/docs/examples/browse1.png diff --git a/docs/examples/browse2.png b/docs/examples/browse2.png Binary files differnew file mode 100644 index 0000000..3dc5ba2 --- /dev/null +++ b/docs/examples/browse2.png diff --git a/docs/examples/edit.py b/docs/examples/edit.py new file mode 100755 index 0000000..7eacc86 --- /dev/null +++ b/docs/examples/edit.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +import sys +import os +import real_edit + +real_edit.EditDisplay(os.path.join( + os.path.dirname(sys.argv[0]), 'edit_text.txt')).main() diff --git a/docs/examples/edit.py.xdotool b/docs/examples/edit.py.xdotool new file mode 100644 index 0000000..70ba7a8 --- /dev/null +++ b/docs/examples/edit.py.xdotool @@ -0,0 +1,2 @@ +windowsize --usehints $RXVTWINDOWID 39 20 +key --window $RXVTWINDOWID Return Return a d d i n g space s o m e space t e x t space diff --git a/docs/examples/edit1.png b/docs/examples/edit1.png Binary files differnew file mode 100644 index 0000000..6a3ba42 --- /dev/null +++ b/docs/examples/edit1.png diff --git a/docs/examples/edit2.png b/docs/examples/edit2.png Binary files differnew file mode 100644 index 0000000..165695f --- /dev/null +++ b/docs/examples/edit2.png diff --git a/docs/examples/edit_text.txt b/docs/examples/edit_text.txt new file mode 100644 index 0000000..c0947b3 --- /dev/null +++ b/docs/examples/edit_text.txt @@ -0,0 +1,11 @@ +替洼渎溏潺瀚灯烫虫调达逯遘醋长闫阚顺驼髓 +공곽껫끓뇽늙등뗍뛴룸많맹뫘볶 +ტექსტების დამუშავებასა და მრავალენოვან კომპიუტერულ სისტემებში. + ╔══════════════════════╗ + ║ • “smart quotes” ║ + ║ ╭─────────╮ ║ + ║ • euro: │ € 14.95 │ ║ + ║ ╰─────────╯ ║ + ╚══════════════════════╝ +Math: ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β) + diff --git a/docs/examples/graph.py b/docs/examples/graph.py new file mode 120000 index 0000000..ffbbea0 --- /dev/null +++ b/docs/examples/graph.py @@ -0,0 +1 @@ +../../examples/graph.py
\ No newline at end of file diff --git a/docs/examples/graph.py.xdotool b/docs/examples/graph.py.xdotool new file mode 100644 index 0000000..0abaa5a --- /dev/null +++ b/docs/examples/graph.py.xdotool @@ -0,0 +1,2 @@ +windowsize --usehints $RXVTWINDOWID 79 24 +key --window $RXVTWINDOWID Down Down Down Return diff --git a/docs/examples/graph1.png b/docs/examples/graph1.png Binary files differnew file mode 100644 index 0000000..1cf14fa --- /dev/null +++ b/docs/examples/graph1.png diff --git a/docs/examples/graph2.png b/docs/examples/graph2.png Binary files differnew file mode 100644 index 0000000..888c610 --- /dev/null +++ b/docs/examples/graph2.png diff --git a/docs/examples/index.rst b/docs/examples/index.rst new file mode 100644 index 0000000..0141b64 --- /dev/null +++ b/docs/examples/index.rst @@ -0,0 +1,80 @@ +.. _example-programs: + +******************** + Example Programs +******************** + +.. currentmodule:: urwid + +These example programs may be found in the examples directory of your +Urwid distribution. + +tour.py +------- + +.. image:: tour1.png +.. image:: tour2.png + +Show how many of the standard widgets may be arranged +on the screen + + +graph.py +-------- + +.. image:: graph2.png + +Demonstrate BarGraph widget and alarms used for animation + + +edit.py +------- + +.. image:: edit1.png +.. image:: edit2.png + +A simple text editor with lazy loading + + +browse.py +--------- + +.. image:: browse2.png + +A lazy directory browser with file selection, tree-view, custom widgets +and list walker + + +subproc.py +---------- + +.. image:: subproc1.png +.. image:: subproc2.png + +Monitor and display results from a subprocess + + +palette_test.py +--------------- + +.. image:: palette_test2.png + +Show available colors in various screen modes + + +pop_up.py +--------- + +.. image:: pop_up1.png +.. image:: pop_up2.png + +Create a pop-up/drop-down/window anchored to another widget + + +bigtext.py +---------- + +.. image:: bigtext2.png +.. image:: bigtext3.png + +demonstrate the BigText widget diff --git a/docs/examples/palette_test.py b/docs/examples/palette_test.py new file mode 120000 index 0000000..0ab2544 --- /dev/null +++ b/docs/examples/palette_test.py @@ -0,0 +1 @@ +../../examples/palette_test.py
\ No newline at end of file diff --git a/docs/examples/palette_test.py.xdotool b/docs/examples/palette_test.py.xdotool new file mode 100644 index 0000000..ed97821 --- /dev/null +++ b/docs/examples/palette_test.py.xdotool @@ -0,0 +1,2 @@ +windowsize --usehints $RXVTWINDOWID 79 34 +key --window $RXVTWINDOWID Down Down Down Return Right Down Return diff --git a/docs/examples/palette_test1.png b/docs/examples/palette_test1.png Binary files differnew file mode 100644 index 0000000..4a5c392 --- /dev/null +++ b/docs/examples/palette_test1.png diff --git a/docs/examples/palette_test2.png b/docs/examples/palette_test2.png Binary files differnew file mode 100644 index 0000000..64a9e46 --- /dev/null +++ b/docs/examples/palette_test2.png diff --git a/docs/examples/pop_up.py b/docs/examples/pop_up.py new file mode 120000 index 0000000..0e32494 --- /dev/null +++ b/docs/examples/pop_up.py @@ -0,0 +1 @@ +../../examples/pop_up.py
\ No newline at end of file diff --git a/docs/examples/pop_up.py.xdotool b/docs/examples/pop_up.py.xdotool new file mode 100644 index 0000000..486ab8d --- /dev/null +++ b/docs/examples/pop_up.py.xdotool @@ -0,0 +1,2 @@ +windowsize --usehints $RXVTWINDOWID 39 18 +key --window $RXVTWINDOWID space diff --git a/docs/examples/pop_up1.png b/docs/examples/pop_up1.png Binary files differnew file mode 100644 index 0000000..9371d54 --- /dev/null +++ b/docs/examples/pop_up1.png diff --git a/docs/examples/pop_up2.png b/docs/examples/pop_up2.png Binary files differnew file mode 100644 index 0000000..ac0bd53 --- /dev/null +++ b/docs/examples/pop_up2.png diff --git a/docs/examples/real_browse.py b/docs/examples/real_browse.py new file mode 120000 index 0000000..a817177 --- /dev/null +++ b/docs/examples/real_browse.py @@ -0,0 +1 @@ +../../examples/browse.py
\ No newline at end of file diff --git a/docs/examples/real_edit.py b/docs/examples/real_edit.py new file mode 120000 index 0000000..16bf719 --- /dev/null +++ b/docs/examples/real_edit.py @@ -0,0 +1 @@ +../../examples/edit.py
\ No newline at end of file diff --git a/docs/examples/subproc.py b/docs/examples/subproc.py new file mode 120000 index 0000000..b3410f7 --- /dev/null +++ b/docs/examples/subproc.py @@ -0,0 +1 @@ +../../examples/subproc.py
\ No newline at end of file diff --git a/docs/examples/subproc.py.xdotool b/docs/examples/subproc.py.xdotool new file mode 100644 index 0000000..4f8e186 --- /dev/null +++ b/docs/examples/subproc.py.xdotool @@ -0,0 +1,2 @@ +windowsize --usehints $RXVTWINDOWID 39 18 +key --window $RXVTWINDOWID a n y t h i n g question diff --git a/docs/examples/subproc1.png b/docs/examples/subproc1.png Binary files differnew file mode 100644 index 0000000..6e515fa --- /dev/null +++ b/docs/examples/subproc1.png diff --git a/docs/examples/subproc2.png b/docs/examples/subproc2.png Binary files differnew file mode 100644 index 0000000..86fb0ec --- /dev/null +++ b/docs/examples/subproc2.png diff --git a/docs/examples/subproc2.py b/docs/examples/subproc2.py new file mode 100644 index 0000000..76c8090 --- /dev/null +++ b/docs/examples/subproc2.py @@ -0,0 +1,10 @@ + +import time +time.sleep(1) + +print """factor: 1 +factor: 1000003 +factor: 2000029 +factor: 3000073 +factor: 4000159 +factor: 5000101""" diff --git a/docs/examples/tour.py b/docs/examples/tour.py new file mode 120000 index 0000000..84d7931 --- /dev/null +++ b/docs/examples/tour.py @@ -0,0 +1 @@ +../../examples/tour.py
\ No newline at end of file diff --git a/docs/examples/tour.py.xdotool b/docs/examples/tour.py.xdotool new file mode 100644 index 0000000..8c1ddea --- /dev/null +++ b/docs/examples/tour.py.xdotool @@ -0,0 +1,2 @@ +windowsize --usehints $RXVTWINDOWID 39 25 +key --window $RXVTWINDOWID Return Page_Down Page_Down Page_Down Page_Down Page_Down diff --git a/docs/examples/tour1.png b/docs/examples/tour1.png Binary files differnew file mode 100644 index 0000000..d97c8b8 --- /dev/null +++ b/docs/examples/tour1.png diff --git a/docs/examples/tour2.png b/docs/examples/tour2.png Binary files differnew file mode 100644 index 0000000..d5c4ab8 --- /dev/null +++ b/docs/examples/tour2.png diff --git a/docs/index.rst b/docs/index.rst index 4a6bb3d..66050c9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,6 +4,8 @@ Urwid |release| Documentation .. toctree:: + examples/index tutorial/index manual/index reference/index + changelog diff --git a/docs/manual/bright_combinations.py b/docs/manual/bright_combinations.py new file mode 100755 index 0000000..4ff350c --- /dev/null +++ b/docs/manual/bright_combinations.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +import urwid + +RED_FGS = ('black', 'light gray', 'white', 'light cyan', 'light red', + 'light green', 'yellow', 'light magenta') +GREEN_FGS = ('black', 'light gray', 'dark blue', 'white', 'light cyan', + 'light red', 'light green', 'yellow') +BROWN_FGS = ('black', 'dark blue', 'dark gray', 'white', + 'light blue', 'light cyan', 'light red', 'light green', 'yellow') +MAGENTA_FGS = ('black', 'light gray', 'dark blue', 'white', 'light cyan', + 'light red', 'light green', 'yellow', 'light magenta') + +BG_FGS =[ + ('dark red', RED_FGS), + ('dark green', GREEN_FGS), + ('brown', BROWN_FGS), + ('dark magenta', MAGENTA_FGS), + ] + +body = urwid.SimpleFocusListWalker([]) + +for bg, fgs in BG_FGS: + spec = urwid.AttrSpec(fgs[0], bg) + def s(w): + return urwid.AttrMap(w, spec) + body.append(s(urwid.Divider())) + body.append(s( + urwid.GridFlow( + [urwid.AttrMap(urwid.Text("'{0}' on '{1}'".format(fg, bg)), + urwid.AttrSpec(fg, bg)) for fg in fgs], + 35, 0, 0, 'left'))) + body.append(s(urwid.Divider())) + +urwid.MainLoop(urwid.ListBox(body)).run() diff --git a/docs/manual/bright_combinations.py.xdotool b/docs/manual/bright_combinations.py.xdotool new file mode 100644 index 0000000..fd929d8 --- /dev/null +++ b/docs/manual/bright_combinations.py.xdotool @@ -0,0 +1 @@ +windowsize --usehints $RXVTWINDOWID 70 26 diff --git a/docs/manual/bright_combinations1.png b/docs/manual/bright_combinations1.png Binary files differnew file mode 100644 index 0000000..d46c920 --- /dev/null +++ b/docs/manual/bright_combinations1.png diff --git a/docs/manual/canvascache.rst b/docs/manual/canvascache.rst index 0dd4271..bb5f094 100644 --- a/docs/manual/canvascache.rst +++ b/docs/manual/canvascache.rst @@ -12,7 +12,7 @@ visible, unchanged canvases so that not all of the visible widgets need to be rendered for each update. The :class:`Widget` base class uses some metaclass magic to -capture the canvas objects returned :meth:`Widget.render` is called and return +capture the canvas objects returned when :meth:`Widget.render` is called and return them the next time :meth:`Widget.render` is called again with the same parameters. The :meth:`Widget._invalidate` method is provided as a way to remove cached widgets so that changes to the widget are visible the next time the screen is redrawn. @@ -26,7 +26,7 @@ Composite Canvases When container and decoration widgets are rendered, they collect the canvases returned by their children and arrange them into a composite canvas. Composite -canvases may are nested to form a tree with the topmost widget's :meth:`Widget.render` +canvases are nested to form a tree with the topmost widget's :meth:`Widget.render` method returning the root of the tree. That canvas is sent to the display module to be rendered on the screen. diff --git a/docs/manual/displayattributes.rst b/docs/manual/displayattributes.rst index 9492869..44b7753 100644 --- a/docs/manual/displayattributes.rst +++ b/docs/manual/displayattributes.rst @@ -334,3 +334,30 @@ terminal, so this feature shouldn't be relied on. :meth:`raw_display.Screen.reset_default_terminal_palette` is used to reset the palette in the ``palette_test.py`` example program when switching modes. + +Recommended Combinations +======================== + +Neutral Backgrounds +------------------- + +.. image:: safe_combinations1.png + +Choose colors that are fairly neutral with medium contrast for most of +your application. It is good to use one background as a default for text, +another for edit boxes and a third for selected edit boxes. + +Foreground colors shown here in bold text will appear as bold text on +many terminals. Bold fonts are often more difficult to read so those +foreground colours should be used sparingly. + +Bright Backgrounds +------------------ + +.. image:: bright_combinations1.png + +Use bright colors to draw attention to small areas with important +information. They are good for buttons and selected widgets (other than +edit boxes). + + diff --git a/docs/manual/displaymodules.rst b/docs/manual/displaymodules.rst index c31d417..eba84c8 100644 --- a/docs/manual/displaymodules.rst +++ b/docs/manual/displaymodules.rst @@ -92,7 +92,7 @@ Screenshot Display Module ``html_fragment`` Screenshots of Urwid interfaces can be rendered in plain HTML. The :class:`html_fragment.HtmlGenerator` display module lets you do this by simulating user input and capturing the screen as fragments of HTML each time -:meth:`html_fragemnt.HtmlGenerator.draw_screen` is +:meth:`html_fragment.HtmlGenerator.draw_screen` is called. These fragments may be included in HTML documents. They will be rendered diff --git a/docs/manual/mainloop.rst b/docs/manual/mainloop.rst index e34c6c5..ee3f62a 100644 --- a/docs/manual/mainloop.rst +++ b/docs/manual/mainloop.rst @@ -59,7 +59,8 @@ Event Loops Urwid's event loop classes handle waiting for things for the :class:`MainLoop`. The different event loops allow you to -integrate with Twisted_ or Glib_ libraries, or use a simple ``select``-based +integrate with Twisted_, Glib_, Tornado_ libraries, +or use a simple ``select``-based loop. Event loop classes abstract the particulars of waiting for input and calling functions as a result of timeouts. @@ -69,10 +70,12 @@ you have more than one :class:`MainLoop` running. You can add your own files to watch to your event loop, with the :meth:`watch_file() <SelectEventLoop.watch_file>` method. Using this interface gives you the special handling -of :exc:`ExitMainLoop` and other exceptions when using Glib_ or Twisted_. +of :exc:`ExitMainLoop` and other exceptions when using Glib_, Twisted_ or +Tornado_ callbacks. .. _Twisted: http://twistedmatrix.com/trac/ .. _Glib: http://developer.gnome.org/glib/stable/ +.. _Tornado: http://www.tornadoweb.org/ ``SelectEventLoop`` ------------------- @@ -120,3 +123,18 @@ application on Twisted. .. seealso:: :class:`GLibEventLoop reference <GLibEventLoop>` + +``TornadoEventLoop`` +-------------------- + +This event loop integrates with Tornado. + +:: + + from tornado.ioloop import IOLoop + evl = urwid.TornadoEventLoop(IOLoop() + loop = urwid.MainLoop(widget, event_loop=evl) + +.. seealso:: + + :class`TornadoEventLoop reference <TornadoEventLoop>` diff --git a/docs/manual/safe_combinations.py b/docs/manual/safe_combinations.py new file mode 100755 index 0000000..5a02159 --- /dev/null +++ b/docs/manual/safe_combinations.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +import urwid + +BLACK_FGS = ('light gray', 'dark cyan', 'dark red', 'dark green', + 'dark magenta', 'white', 'light blue', 'light cyan', 'light red', + 'light green', 'yellow', 'light magenta') +GRAY_FGS = ('black', 'dark blue', 'dark cyan', 'dark red', 'dark green', + 'dark magenta', 'white', 'light red', 'yellow', + 'light magenta') +BLUE_FGS = ('light gray', 'dark cyan', 'white', + 'light cyan', 'light red', 'light green', 'yellow', + 'light magenta') +CYAN_FGS = ('black', 'light gray', 'dark blue', 'white', 'light cyan', + 'light green', 'yellow') + +BG_FGS =[ + ('black', BLACK_FGS), + ('light gray', GRAY_FGS), + ('dark blue', BLUE_FGS), + ('dark cyan', CYAN_FGS), + ] + +body = urwid.SimpleFocusListWalker([]) + +for bg, fgs in BG_FGS: + spec = urwid.AttrSpec(fgs[0], bg) + def s(w): + return urwid.AttrMap(w, spec) + body.append(s(urwid.Divider())) + body.append(s( + urwid.GridFlow( + [urwid.AttrMap(urwid.Text("'{0}' on '{1}'".format(fg, bg)), + urwid.AttrSpec(fg, bg)) for fg in fgs], + 35, 0, 0, 'left'))) + body.append(s(urwid.Divider())) + +urwid.MainLoop(urwid.ListBox(body)).run() diff --git a/docs/manual/safe_combinations.py.xdotool b/docs/manual/safe_combinations.py.xdotool new file mode 100644 index 0000000..c1b7590 --- /dev/null +++ b/docs/manual/safe_combinations.py.xdotool @@ -0,0 +1 @@ +windowsize --usehints $RXVTWINDOWID 70 27 diff --git a/docs/manual/safe_combinations1.png b/docs/manual/safe_combinations1.png Binary files differnew file mode 100644 index 0000000..a5c7d7e --- /dev/null +++ b/docs/manual/safe_combinations1.png diff --git a/docs/manual/widgets.rst b/docs/manual/widgets.rst index 0e42c52..44fe7d3 100644 --- a/docs/manual/widgets.rst +++ b/docs/manual/widgets.rst @@ -29,7 +29,7 @@ screen. When we render the topmost widget: 7. *(a)* possibly modifies the canvas from *(b)* and returns it Widgets *(a)*, *(b)* and *(e)* are called container widgets because they -contain other widgets. Container widgets choose the size and position their +contain other widgets. Container widgets choose the size and position of their contained widgets. Container widgets must also keep track of which one of their contained widgets @@ -315,7 +315,7 @@ 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 +If you want to use a flow widget for the top widget, first wrap the flow widget with a :class:`Filler` widget. diff --git a/docs/reference/main_loop.rst b/docs/reference/main_loop.rst index dfde825..48b1c4f 100644 --- a/docs/reference/main_loop.rst +++ b/docs/reference/main_loop.rst @@ -23,3 +23,8 @@ TwistedEventLoop .. autoclass:: TwistedEventLoop +TornadoEventLoop +---------------- + +.. autoclass:: TornadoEventLoop + diff --git a/docs/reference/signals.rst b/docs/reference/signals.rst index cbcbf37..9e10f94 100644 --- a/docs/reference/signals.rst +++ b/docs/reference/signals.rst @@ -7,11 +7,15 @@ The :func:`urwid.\*_signal` functions use a shared Signals object instance for tracking registered and connected signals. There is no reason to instantiate your own Signals object. -.. function:: connect_signal(obj, name, callback, user_arg=None) +.. function:: connect_signal(obj, name, callback, user_arg=None, weak_args=None, user_args=None) .. automethod:: Signals.connect -.. function:: disconnect_signal(obj, name, callback, user_arg=None) +.. function:: disconnect_by_key(obj, name, key) + +.. automethod:: Signals.disconnect_by_key + +.. function:: disconnect_signal(obj, name, callback, user_arg=None, weak_args=None, user_args=None) .. automethod:: Signals.disconnect diff --git a/docs/tools/compile_pngs.sh b/docs/tools/compile_pngs.sh index 1d078a8..f9135e9 100755 --- a/docs/tools/compile_pngs.sh +++ b/docs/tools/compile_pngs.sh @@ -2,7 +2,7 @@ # args: scripts to capture -DISPLAYNUM=1 +DISPLAYNUM=5 SCREENSHOTS=`dirname $0`/screenshots.sh XVFB=$(which Xvfb) @@ -14,6 +14,7 @@ if [ -n $XVFB ]; then fi for script in $@; do + echo "doing $script" if [ -f "${script}.xdotool" ]; then "$SCREENSHOTS" "$script" < "${script}.xdotool" fi diff --git a/docs/tools/screenshots.sh b/docs/tools/screenshots.sh index 5d2b04d..db7ca11 100755 --- a/docs/tools/screenshots.sh +++ b/docs/tools/screenshots.sh @@ -20,6 +20,7 @@ c=1 while read -r line; do # the echo trick is needed to expand RXVTWINDOWID variable echo $line | xdotool - + echo "sending $line" import -window "$RXVTWINDOWID" "${image}$c.png" (( c++ )) done diff --git a/docs/tools/templates/indexcontent.html b/docs/tools/templates/indexcontent.html index 13dc2b5..784cd83 100644 --- a/docs/tools/templates/indexcontent.html +++ b/docs/tools/templates/indexcontent.html @@ -1,29 +1,73 @@ {% extends "!defindex.html" %} {% block body %} -<h1>Urwid {{ release }} Documentation</h1> +<h1>Urwid{% if 'dev' in release %} development version{% endif %}</h1> +<div style="float: left; width: 500px;"> +<p>Console user interface library for Python</p> -<div class="section" id="tutorial"> -<h2><a class="reference internal" href="tutorial/index.html#urwid-tutorial"><em>Tutorial</em></a><a class="headerlink" href="#tutorial" title="Permalink to this headline">¶</a></h2> -<a class="reference external image-reference" href="tutorial/index.html#urwid-tutorial"><img alt="_images/highcolors1.png" src="_images/highcolors1.png" width="200" align="left" style="padding-right: 15px" /></a> -<p>The <a class="reference internal" href="tutorial/index.html#urwid-tutorial"><em>Urwid Tutorial</em></a> covers example Urwid applications -from basic to moderate complexity. Each new concept is explained along the way.</p> +<ul> +{% if 'dev' in release %}<li>Stable release available at <a href="http://urwid.org/">http://urwid.org</a></li> +{% else %} +<li><a href="https://pypi.python.org/packages/source/u/urwid/urwid-{{ release }}.tar.gz" +>Download: urwid-{{ release }}.tar.gz</a></li> +{% endif %} +<li><a href="changelog.html">Changelog</a> +</ul> + +<div class="hilight"><pre>git clone https://github.com/wardi/urwid.git</pre></div> + +<ul> +<li><a href="http://github.com/wardi/urwid">Github page</a></li> +<li><a href="http://github.com/wardi/urwid/issues">Issues</a></li> +<li><a href="http://webchat.oftc.net/?channels=%23urwid">IRC: <code>#urwid on irc.oftc.net</code></a></li> +<li><a href="http://lists.excess.org/mailman/listinfo/urwid">Mailing list</a> +(<a href="http://dir.gmane.org/gmane.comp.lib.urwid">gmane</a>)</li> +</ul> + +<p>Wiki:</p> + +<ul> +<li><a href="http://github.com/wardi/urwid/wiki/Installation-instructions">Installation instructions</a></li> +<li><a href="http://github.com/wardi/urwid/wiki/FAQ">FAQ</a></li> +<li><a href="http://github.com/wardi/urwid/wiki/Application-list">Applications built with Urwid</a></li> +<li><a href="http://github.com/wardi/urwid/wiki/How-you-can-help">How you can help</a></li> +<li><a href="http://github.com/wardi/urwid/wiki/Coding-style">Coding style</a></li> +<li><a href="http://github.com/wardi/urwid/wiki/Planned-development">Planned development</a></li> +</ul> + +<div class="section" id="requirements"> +<h3>Requirements</h3> +<ul> +<li>Python 2.6, 2.7, 3.2+ or PyPy</li> +<li>Linux, OSX, Cygwin or other unix-like OS</li> +<li>python-gi for GlibEventLoop (optional)</li> +<li>Twisted for TwistedEventLoop (optional)</li> +<li>Tornado for TornadoEventLoop (optional)</li> +<li>Apache for web_display module (optional)</li> +<li>ncurses for curses_display module (optional)</li> +</ul> +</div> + +<div class="section" id="similar"> +<h3>Similar projects</h3> +<ul> +<li><a href="http://www.npcole.com/npyscreen/">npyscreen</a></li> +</ul> </div> -<div class="section" id="manual" style="clear:both; padding-top: 8px"> -<h2><a class="reference internal" href="manual/index.html#urwid-manual"><em>Manual</em></a><a class="headerlink" href="#manual" title="Permalink to this headline">¶</a></h2> -<a class="reference external image-reference" href="manual/index.html#urwid-manual"><img alt="_images/introduction.png" src="_images/introduction.png" width="200" align="left" style="padding-right: 15px" /></a> -<p>The <a class="reference internal" href="manual/index.html#urwid-manual"><em>Urwid Manual</em></a> discusses each part of the library and how -everything fits together.</p> </div> -<div class="section" id="reference" style="clear:both; padding-top: 8px"> -<h2><a class="reference internal" href="reference/index.html#urwid-reference"><em>Reference</em></a><a class="headerlink" href="#reference" title="Permalink to this headline">¶</a></h2> -<a class="reference external image-reference" href="reference/index.html#urwid-reference"><img alt="_images/urwid_widgets_1.png" src="_images/urwid_widgets_1.png" width="200" align="left" style="padding-right: 15px" /></a> -<p>The <a class="reference internal" href="reference/index.html#urwid-reference"><em>Urwid Reference</em></a> documents all the classes and functions in the -library individually.</p> + +<div style="padding-left: 560px;"> +<div class="section" id="big-buttons"> +<p><a class="reference external image-reference" href="examples/index.html"><img alt="_images/graph2.png" src="_images/graph2.png" width="200" style="padding-bottom: 5px" /></br> +Examples</a></p> +<p><a class="reference external image-reference" href="tutorial/index.html"><img alt="_images/highcolors1.png" src="_images/highcolors1.png" width="200" style="padding-bottom: 5px" /></br> +Tutorial</a></p> +<p><a class="reference external image-reference" href="manual/index.html"><img alt="_images/introduction.png" src="_images/introduction.png" width="200" style="padding-bottom: 5px" /></br> +Manual</a></p> +<p><a class="reference external image-reference" href="reference/index.html"><img alt="_images/urwid_widgets_1.png" src="_images/urwid_widgets_1.png" width="200" style="padding-bottom: 5px" /></br> +Reference</a></p> +<p><a class="reference internal" href="genindex.html">Index</a></p> +<p><a class="reference internal" href="search.html">Search Page</a></p> </div> -<div class="section" id="indices-and-tables" style="clear: both; padding-top: 8px"> -<h2>Indices and tables<a class="headerlink" href="#indices-and-tables" title="Permalink to this headline">¶</a></h2> -<ul class="simple"> -<li><a class="reference internal" href="genindex.html"><em>Index</em></a></li> -<li><a class="reference internal" href="search.html"><em>Search Page</em></a></li> </div> +<div style="clear:both"> </div> {% endblock %} diff --git a/docs/tools/templates/indexsidebar.html b/docs/tools/templates/indexsidebar.html index cdff25a..252d115 100644 --- a/docs/tools/templates/indexsidebar.html +++ b/docs/tools/templates/indexsidebar.html @@ -1,7 +1,8 @@ -<h3>Table of Contents</h4> +<h3>Documentation</h3> <div style="padding: 10px 0 16px 10px"> -<h4><a class="reference internal" href="tutorial/index.html#urwid-tutorial">Tutorial</a></h4> -<h4><a class="reference internal" href="manual/index.html#urwid-manual">Manual</a></h4> -<h4><a class="reference internal" href="reference/index.html#urwid-reference">Reference</a></h4> -<h4 style="padding-left: 8px"><a class="reference internal" href="genindex.html"><em>Index</em></a></h4> +<h4><a class="reference internal" href="examples/index.html">Examples</a></h4> +<h4><a class="reference internal" href="tutorial/index.html">Tutorial</a></h4> +<h4><a class="reference internal" href="manual/index.html">Manual</a></h4> +<h4><a class="reference internal" href="reference/index.html">Reference</a></h4> +<h4><a class="reference internal" href="genindex.html">Index</a></h4> </div> diff --git a/docs/tools/templates/layout.html b/docs/tools/templates/layout.html deleted file mode 100644 index bba7e42..0000000 --- a/docs/tools/templates/layout.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "!layout.html" %} -{% block rootrellink %} - <li><a href="http://excess.org/urwid/">Urwid Homepage</a> »</li> - {{ super() }} -{% endblock %} diff --git a/docs/tools/templates/localtoc.html b/docs/tools/templates/localtoc.html new file mode 100644 index 0000000..0e9bf8a --- /dev/null +++ b/docs/tools/templates/localtoc.html @@ -0,0 +1,18 @@ +{%- if pagename.startswith('manual/') -%} + <h3>Table of contents</a></h3> + <ul><li><a href="index.html">Manual</a></li> + {%- if display_toc -%} + {{ toc }} + {%- endif %} + </ul> +{%- elif pagename.startswith('reference/') -%} + <h3>Table of contents</a></h3> + <ul><li><a href="index.html">Reference</a></li> + {%- if display_toc -%} + {{ toc }} + {%- endif %} + </ul> +{%- elif display_toc -%} + <h3>Table of contents</a></h3> + {{ toc }} +{% endif %} diff --git a/docs/tutorial/lbscr.py b/docs/tutorial/lbscr.py deleted file mode 100644 index 9536549..0000000 --- a/docs/tutorial/lbscr.py +++ /dev/null @@ -1,30 +0,0 @@ -import urwid - -def show_all_input(keys, raw): - """make keys pressed visible to the user""" - show_key.set_text(u"Pressed: " + u" ".join([ - unicode(k) for k in keys])) - return keys - -def exit_on_cr(key): - if key == 'enter': - raise urwid.ExitMainLoop() - -palette = [('header', 'white', 'black'), - ('reveal focus', 'black', 'dark cyan', 'standout'),] - -div = urwid.Divider(u"-") -content = urwid.SimpleListWalker([ - urwid.AttrMap(w, None, 'reveal focus') for w in [ - urwid.Text(u"This is a text string that is fairly long"), - urwid.Divider(u"-"),] + [ - urwid.Text(u"Numbers %d" % i) for i in range(40)] + [ - urwid.Divider(u"-"), - urwid.Text(u"The end."),]]) -listbox = urwid.ListBox(content) -show_key = urwid.Text(u"", wrap='clip') -head = urwid.AttrMap(show_key, 'header') -top = urwid.Frame(listbox, head) -loop = urwid.MainLoop(top, palette, input_filter=show_all_input, - unhandled_input=exit_on_cr) -loop.run() diff --git a/docs/tutorial/lbscr.py.xdotool b/docs/tutorial/lbscr.py.xdotool deleted file mode 100644 index 9f733dd..0000000 --- a/docs/tutorial/lbscr.py.xdotool +++ /dev/null @@ -1,9 +0,0 @@ -windowsize --usehints $RXVTWINDOWID 15 7 -key --window $RXVTWINDOWID Down -key --window $RXVTWINDOWID Down -key --window $RXVTWINDOWID Down -key --window $RXVTWINDOWID Up -key --window $RXVTWINDOWID Down -windowsize --usehints $RXVTWINDOWID 20 9 -windowsize --usehints $RXVTWINDOWID 25 7 -windowsize --usehints $RXVTWINDOWID 11 13 diff --git a/docs/tutorial/lbscr1.png b/docs/tutorial/lbscr1.png Binary files differdeleted file mode 100644 index a938a66..0000000 --- a/docs/tutorial/lbscr1.png +++ /dev/null diff --git a/docs/tutorial/lbscr2.png b/docs/tutorial/lbscr2.png Binary files differdeleted file mode 100644 index 130032e..0000000 --- a/docs/tutorial/lbscr2.png +++ /dev/null diff --git a/docs/tutorial/lbscr3.png b/docs/tutorial/lbscr3.png Binary files differdeleted file mode 100644 index 8adf3d9..0000000 --- a/docs/tutorial/lbscr3.png +++ /dev/null diff --git a/docs/tutorial/lbscr4.png b/docs/tutorial/lbscr4.png Binary files differdeleted file mode 100644 index 81aa77d..0000000 --- a/docs/tutorial/lbscr4.png +++ /dev/null diff --git a/docs/tutorial/lbscr5.png b/docs/tutorial/lbscr5.png Binary files differdeleted file mode 100644 index 9861422..0000000 --- a/docs/tutorial/lbscr5.png +++ /dev/null diff --git a/docs/tutorial/lbscr6.png b/docs/tutorial/lbscr6.png Binary files differdeleted file mode 100644 index 81aa77d..0000000 --- a/docs/tutorial/lbscr6.png +++ /dev/null diff --git a/docs/tutorial/lbscr7.png b/docs/tutorial/lbscr7.png Binary files differdeleted file mode 100644 index 9ccac3d..0000000 --- a/docs/tutorial/lbscr7.png +++ /dev/null diff --git a/docs/tutorial/lbscr8.png b/docs/tutorial/lbscr8.png Binary files differdeleted file mode 100644 index 61e7752..0000000 --- a/docs/tutorial/lbscr8.png +++ /dev/null diff --git a/docs/tutorial/lbscr9.png b/docs/tutorial/lbscr9.png Binary files differdeleted file mode 100644 index 9a5f337..0000000 --- a/docs/tutorial/lbscr9.png +++ /dev/null diff --git a/docs/tutorial/urwid b/docs/tutorial/urwid deleted file mode 120000 index 2657fa1..0000000 --- a/docs/tutorial/urwid +++ /dev/null @@ -1 +0,0 @@ -../../urwid
\ No newline at end of file diff --git a/examples/calc.py b/examples/calc.py index a40259f..f132172 100755 --- a/examples/calc.py +++ b/examples/calc.py @@ -734,7 +734,7 @@ class CalcDisplay: # keep only the non-children self.col_list[i:] = keep_right_cols - # fix the letter assigmnents + # fix the letter assignments for j in range(i, len(self.col_list)): col = self.col_list[j] # fix the column heading diff --git a/examples/dialog.py b/examples/dialog.py index 7de86d5..2de61c0 100755 --- a/examples/dialog.py +++ b/examples/dialog.py @@ -206,7 +206,7 @@ class ListDialogDisplay(DialogDisplay): class CheckListDialogDisplay(ListDialogDisplay): def on_exit(self, exitcode): """ - Mimick dialog(1)'s --checklist exit. + Mimic dialog(1)'s --checklist exit. Put each checked item in double quotes with a trailing space. """ if exitcode != 0: diff --git a/examples/fib.py b/examples/fib.py index 4439129..7f8d4b7 100755 --- a/examples/fib.py +++ b/examples/fib.py @@ -32,7 +32,7 @@ import urwid class FibonacciWalker(urwid.ListWalker): """ListWalker-compatible class for browsing fibonacci set. - positions returned are (value at position-1, value at poistion) tuples. + positions returned are (value at position-1, value at position) tuples. """ def __init__(self): self.focus = (0L,1L) diff --git a/examples/lcd_cf635.py b/examples/lcd_cf635.py index e01ead2..77e8f17 100755 --- a/examples/lcd_cf635.py +++ b/examples/lcd_cf635.py @@ -104,7 +104,7 @@ class LCDProgressBar(urwid.FlowWidget): Update and return the value one step +ve or -ve, based on the size of the displayed bar. - directon -- 1 for +ve, 0 for -ve + direction -- 1 for +ve, 0 for -ve """ steps = self.get_steps(size) filled = urwid.int_scale(self.value, self.range, steps) @@ -249,7 +249,7 @@ def build_menus(): urwid.Text("This is a demo of Urwid's CF635Display " "module. If you need an interface for a limited " "character display device this should serve as a " - "good example for implmenting your own display " + "good example for implementing your own display " "module and menu-driven application."), ]) ] diff --git a/examples/subproc.py b/examples/subproc.py index 28f5497..64eb072 100755 --- a/examples/subproc.py +++ b/examples/subproc.py @@ -2,8 +2,11 @@ import subprocess import urwid +import os +import sys factor_me = 362923067964327863989661926737477737673859044111968554257667 +run_me = os.path.join(os.path.dirname(sys.argv[0]), 'subproc2.py') output_widget = urwid.Text("Factors of %d:\n" % factor_me) edit_widget = urwid.Edit("Type anything or press enter to exit:") @@ -22,7 +25,7 @@ def received_output(data): write_fd = loop.watch_pipe(received_output) proc = subprocess.Popen( - ['python', '-u', 'subproc2.py', str(factor_me)], + ['python', '-u', run_me, str(factor_me)], stdout=write_fd, close_fds=True) diff --git a/examples/tour.py b/examples/tour.py index d0a9a48..fedfb59 100755 --- a/examples/tour.py +++ b/examples/tour.py @@ -122,7 +122,7 @@ def main(): text_cb_list = [u"Wax", u"Wash", u"Buff", u"Clear Coat", u"Dry", u"Racing Stripe"] text_rb_list = [u"Morning", u"Afternoon", u"Evening", u"Weekend"] - text_listbox = [u"All these widgets have been diplayed " + text_listbox = [u"All these widgets have been displayed " u"with the help of a ", ('important', u"ListBox"), u" widget. " u"ListBox widgets handle scrolling and changing focus. A ", ('important', u"Frame"), u" widget is used to keep the " diff --git a/examples/twisted_serve_ssh.py b/examples/twisted_serve_ssh.py index 9562bc6..36396dd 100644 --- a/examples/twisted_serve_ssh.py +++ b/examples/twisted_serve_ssh.py @@ -168,7 +168,7 @@ class TwistedScreen(urwid.BaseScreen): 1. Input 2. Output - Input is achieved in normal urwid by passing a lsit of available readable + Input is achieved in normal urwid by passing a list of available readable file descriptors to the event loop for polling/selecting etc. In the Twisted situation, this is not necessary because Twisted polls the input descriptors itself. Urwid allows this by being driven using the main loop @@ -221,14 +221,17 @@ class TwistedScreen(urwid.BaseScreen): self.terminal.cursorPosition(*cursor) # XXX from base screen - def set_mouse_tracking(self): + def set_mouse_tracking(self, enable=True): """ - Enable mouse tracking. + Enable (or disable) mouse tracking. After calling this function get_input will include mouse click events along with keystrokes. """ - self.write(urwid.escape.MOUSE_TRACKING_ON) + if enable: + self.write(urwid.escape.MOUSE_TRACKING_ON) + else: + self.write(urwid.escape.MOUSE_TRACKING_OFF) # twisted handles polling, so we don't need the loop to do it, we just # push what we get to the loop from dataReceived. @@ -262,7 +265,7 @@ class TwistedScreen(urwid.BaseScreen): # Private def _on_update_palette_entry(self, name, *attrspecs): - # copy the attribute to a dictionary containing the escape seqences + # copy the attribute to a dictionary containing the escape sequences self._pal_escape[name] = self._attrspec_to_escape( attrspecs[{16:0,1:1,88:2,256:3}[self.colors]]) diff --git a/examples/urwid b/examples/urwid deleted file mode 120000 index 1272936..0000000 --- a/examples/urwid +++ /dev/null @@ -1 +0,0 @@ -../urwid
\ No newline at end of file @@ -1,7 +1,7 @@ #!/usr/bin/python # # Urwid setup.py exports the useful bits -# Copyright (C) 2004-2012 Ian Ward +# Copyright (C) 2004-2014 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -44,37 +44,13 @@ setup_d = { 'author':"Ian Ward", 'author_email':"ian@excess.org", 'ext_modules':[Extension('urwid.str_util', sources=['source/str_util.c'])], - 'packages':['urwid'], - 'url':"http://excess.org/urwid/", - 'download_url':"http://excess.org/urwid/urwid-%s.tar.gz"%release, + 'packages':['urwid', 'urwid.tests'], + 'url':"http://urwid.org/", 'license':"LGPL", 'keywords':"curses ui widget scroll listbox user interface text layout console ncurses", 'platforms':"unix-like", 'description': "A full-featured console (xterm et al.) user interface library", - 'long_description':""" -Urwid is a console user interface library. It includes many features -useful for text console application developers including: - -- Applcations resize quickly and smoothly -- Automatic, programmable text alignment and wrapping -- Simple markup for setting text attributes within blocks of text -- Powerful list box with programmable content for scrolling all widget types -- Your choice of event loops: Twisted, Glib or built-in select-based loop -- Pre-built widgets include edit boxes, buttons, check boxes and radio buttons -- Display modules include raw, curses, and experimental LCD and web displays -- Support for UTF-8, simple 8-bit and CJK encodings -- 256 and 88 color mode support -- Python 3.2 support - -Home Page: - http://excess.org/urwid/ - -Documentation: - http://excess.org/urwid/docs/ - -Example Program Screenshots: - http://excess.org/urwid/examples.html -"""[1:], + 'long_description':open("README.rst").read().split('.. content-start\n',1)[1], 'classifiers':[ "Development Status :: 5 - Production/Stable", "Environment :: Console", @@ -87,13 +63,13 @@ Example Program Screenshots: "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Widget Sets", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.4", - "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: Implementation :: PyPy", ], } diff --git a/urwid/__init__.py b/urwid/__init__.py index 899a4da..a6cacf8 100644 --- a/urwid/__init__.py +++ b/urwid/__init__.py @@ -52,9 +52,10 @@ from urwid.command_map import (CommandMap, command_map, REDRAW_SCREEN, CURSOR_UP, CURSOR_DOWN, CURSOR_LEFT, CURSOR_RIGHT, CURSOR_PAGE_UP, CURSOR_PAGE_DOWN, CURSOR_MAX_LEFT, CURSOR_MAX_RIGHT, ACTIVATE) -from urwid.main_loop import ExitMainLoop, MainLoop, SelectEventLoop +from urwid.main_loop import (ExitMainLoop, MainLoop, SelectEventLoop, + GLibEventLoop, TornadoEventLoop) try: - from urwid.main_loop import GLibEventLoop, TwistedEventLoop + from urwid.main_loop import TwistedEventLoop except ImportError: pass from urwid.text_layout import (TextLayout, StandardTextLayout, default_layout, diff --git a/urwid/canvas.py b/urwid/canvas.py index 593b252..d27e7ab 100644 --- a/urwid/canvas.py +++ b/urwid/canvas.py @@ -972,7 +972,7 @@ def shards_trim_top(shards, top): shard_tail = shard_body_tail(num_rows, sbody) top -= num_rows else: - raise CanvasError("tried to trim shards out of existance") + raise CanvasError("tried to trim shards out of existence") sbody = shard_body(cviews, shard_tail, False) shard_tail = shard_body_tail(num_rows, sbody) diff --git a/urwid/container.py b/urwid/container.py index e0fede7..aa81614 100755 --- a/urwid/container.py +++ b/urwid/container.py @@ -360,6 +360,10 @@ class GridFlow(WidgetWrap, WidgetContainerMixin, WidgetContainerListContentsMixi pad.original_widget=w pad.width = used_space - self.h_sep + if self.v_sep: + # remove first divider + del p.contents[:1] + return p def _set_focus_from_display_widget(self): @@ -976,7 +980,7 @@ class Frame(Widget, WidgetContainerMixin): This object may be used to read or update the contents of the Frame. - The values are similar to the the list-like .contents objects used + The values are similar to the list-like .contents objects used in other containers with (:class:`Widget`, options) tuples, but are constrained to keys for each of the three usual parts of a Frame. When other keys are used a :exc:`KeyError` will be raised. @@ -1477,9 +1481,11 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin): elif f == GIVEN: l.append(height) remaining -= height - else: + elif height: l.append(None) wtotal += height + else: + l.append(0) # zero-weighted items treated as ('given', 0) if wtotal == 0: raise PileError, "No weighted widgets found for Pile treated as a box widget" @@ -1661,6 +1667,8 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin): if wrow + r > row: break wrow += r + else: + return False focus = focus and self.focus_item == w if is_mouse_press(event) and button == 1: @@ -1716,7 +1724,7 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin): are treated as box widgets, and *box_columns* is ignored. If the Columns widget is treated as a flow widget then the rows - are calcualated as the largest rows() returned from all columns + are calculated as the largest rows() returned from all columns except the ones listed in *box_columns*. The box widgets in *box_columns* will be displayed with this calculated number of rows, filling the full height. @@ -1964,7 +1972,7 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin): raise IndexError, "No Columns child widget at position %s" % (position,) self.contents.focus = position focus_position = property(_get_focus_position, _set_focus_position, doc=""" - index of child widget in focus. Raises IndexError if read when + index of child widget in focus. Raises :exc:`IndexError` if read when Columns is empty, or when set to an invalid index. """) @@ -1985,7 +1993,8 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin): maxcol = size[0] # FIXME: get rid of this check and recalculate only when # a 'pack' widget has been modified. - if maxcol == self._cache_maxcol and not PACK in self.column_types: + if maxcol == self._cache_maxcol and not any( + t == PACK for w, (t, n, b) in self.contents): return self._cache_column_widths widths = [] diff --git a/urwid/curses_display.py b/urwid/curses_display.py index dba940b..b7f1cea 100755 --- a/urwid/curses_display.py +++ b/urwid/curses_display.py @@ -70,23 +70,37 @@ class Screen(BaseScreen, RealTerminal): self.prev_input_resize = 0 self.set_input_timeouts() self.last_bstate = 0 + self._mouse_tracking_enabled = False self.register_palette_entry(None, 'default','default') - def set_mouse_tracking(self): + def set_mouse_tracking(self, enable=True): """ Enable mouse tracking. After calling this function get_input will include mouse click events along with keystrokes. """ - curses.mousemask(0 - | curses.BUTTON1_PRESSED | curses.BUTTON1_RELEASED - | curses.BUTTON2_PRESSED | curses.BUTTON2_RELEASED - | curses.BUTTON3_PRESSED | curses.BUTTON3_RELEASED - | curses.BUTTON4_PRESSED | curses.BUTTON4_RELEASED - | curses.BUTTON_SHIFT | curses.BUTTON_ALT - | curses.BUTTON_CTRL) + enable = bool(enable) + if enable == self._mouse_tracking_enabled: + return + + if enable: + curses.mousemask(0 + | curses.BUTTON1_PRESSED | curses.BUTTON1_RELEASED + | curses.BUTTON2_PRESSED | curses.BUTTON2_RELEASED + | curses.BUTTON3_PRESSED | curses.BUTTON3_RELEASED + | curses.BUTTON4_PRESSED | curses.BUTTON4_RELEASED + | curses.BUTTON1_DOUBLE_CLICKED | curses.BUTTON1_TRIPLE_CLICKED + | curses.BUTTON2_DOUBLE_CLICKED | curses.BUTTON2_TRIPLE_CLICKED + | curses.BUTTON3_DOUBLE_CLICKED | curses.BUTTON3_TRIPLE_CLICKED + | curses.BUTTON4_DOUBLE_CLICKED | curses.BUTTON4_TRIPLE_CLICKED + | curses.BUTTON_SHIFT | curses.BUTTON_ALT + | curses.BUTTON_CTRL) + else: + raise NotImplementedError() + + self._mouse_tracking_enabled = enable def start(self): """ @@ -397,6 +411,24 @@ class Screen(BaseScreen, RealTerminal): append_button( 64 + escape.MOUSE_RELEASE_FLAG ) next &= ~ 8 + if bstate & curses.BUTTON1_DOUBLE_CLICKED: + append_button( 0 + escape.MOUSE_MULTIPLE_CLICK_FLAG ) + if bstate & curses.BUTTON2_DOUBLE_CLICKED: + append_button( 1 + escape.MOUSE_MULTIPLE_CLICK_FLAG ) + if bstate & curses.BUTTON3_DOUBLE_CLICKED: + append_button( 2 + escape.MOUSE_MULTIPLE_CLICK_FLAG ) + if bstate & curses.BUTTON4_DOUBLE_CLICKED: + append_button( 64 + escape.MOUSE_MULTIPLE_CLICK_FLAG ) + + if bstate & curses.BUTTON1_TRIPLE_CLICKED: + append_button( 0 + escape.MOUSE_MULTIPLE_CLICK_FLAG*2 ) + if bstate & curses.BUTTON2_TRIPLE_CLICKED: + append_button( 1 + escape.MOUSE_MULTIPLE_CLICK_FLAG*2 ) + if bstate & curses.BUTTON3_TRIPLE_CLICKED: + append_button( 2 + escape.MOUSE_MULTIPLE_CLICK_FLAG*2 ) + if bstate & curses.BUTTON4_TRIPLE_CLICKED: + append_button( 64 + escape.MOUSE_MULTIPLE_CLICK_FLAG*2 ) + self.last_bstate = next return l diff --git a/urwid/decoration.py b/urwid/decoration.py index 190c402..731eb91 100755 --- a/urwid/decoration.py +++ b/urwid/decoration.py @@ -310,7 +310,7 @@ class AttrWrap(AttrMap): """ Call getattr on wrapped widget. This has been the longstanding behaviour of AttrWrap, but is discouraged. New code should be - using AttrMap and .base_widget or .original_widget instad. + using AttrMap and .base_widget or .original_widget instead. """ return getattr(self._original_widget, name) @@ -447,7 +447,7 @@ class Padding(WidgetDecoration): :param left: a fixed number of columns to pad on the left :type left: int - :param right: a fixed number of columns to pad on thr right + :param right: a fixed number of columns to pad on the right :type right: int Clipping Mode: (width= ``'clip'``) @@ -537,11 +537,12 @@ class Padding(WidgetDecoration): """ self._align_type, self._align_amount = normalize_align(align, PaddingError) + self._invalidate() align = property(_get_align, _set_align) def _get_width(self): """ - Return the padding widthment setting. + Return the padding width. """ return simplify_width(self._width_type, self._width_amount) def _set_width(self, width): @@ -550,6 +551,7 @@ class Padding(WidgetDecoration): """ self._width_type, self._width_amount = normalize_width(width, PaddingError) + self._invalidate() width = property(_get_width, _set_width) def render(self, size, focus=False): diff --git a/urwid/display_common.py b/urwid/display_common.py index 8e8c0a8..cbe433f 100755 --- a/urwid/display_common.py +++ b/urwid/display_common.py @@ -657,7 +657,7 @@ class RealTerminal(object): def tty_signal_keys(self, intr=None, quit=None, start=None, stop=None, susp=None, fileno=None): """ - Read and/or set the tty's signal charater settings. + Read and/or set the tty's signal character settings. This function returns the current settings as a tuple. Use the string 'undefined' to unmap keys from their signals. @@ -820,18 +820,29 @@ class BaseScreen(object): if mono is None: mono = DEFAULT mono = AttrSpec(mono, DEFAULT, 1) - + if foreground_high is None: foreground_high = foreground if background_high is None: background_high = background - high_88 = AttrSpec(foreground_high, background_high, 88) high_256 = AttrSpec(foreground_high, background_high, 256) + # 'hX' where X > 15 are different in 88/256 color, use + # basic colors for 88-color mode if high colors are specified + # in this way (also avoids crash when X > 87) + def large_h(desc): + if not desc.startswith('h'): + return False + num = int(desc[1:], 10) + return num > 15 + if large_h(foreground_high) or large_h(background_high): + high_88 = basic + else: + high_88 = AttrSpec(foreground_high, background_high, 88) + signals.emit_signal(self, UPDATE_PALETTE_ENTRY, name, basic, mono, high_88, high_256) self._palette[name] = (basic, mono, high_88, high_256) - def _test(): diff --git a/urwid/escape.py b/urwid/escape.py index 6df3d86..bf34921 100644 --- a/urwid/escape.py +++ b/urwid/escape.py @@ -180,6 +180,8 @@ class KeyqueueTrie(object): if b & 4: prefix = prefix + "shift " if b & 8: prefix = prefix + "meta " if b & 16: prefix = prefix + "ctrl " + if (b & MOUSE_MULTIPLE_CLICK_MASK)>>9 == 1: prefix = prefix + "double " + if (b & MOUSE_MULTIPLE_CLICK_MASK)>>9 == 2: prefix = prefix + "triple " # 0->1, 1->2, 2->3, 64->4, 65->5 button = ((b&64)/64*3) + (b & 3) + 1 @@ -191,6 +193,8 @@ class KeyqueueTrie(object): action = "release" elif b & MOUSE_DRAG_FLAG: action = "drag" + elif b & MOUSE_MULTIPLE_CLICK_MASK: + action = "click" else: action = "press" @@ -251,6 +255,16 @@ class KeyqueueTrie(object): # and raw_display when we know which button was released. NON-STANDARD MOUSE_RELEASE_FLAG = 2048 +# This 2-bit mask is used to check if the mouse release from curses or gpm +# is a double or triple release. 00 means single click, 01 double, +# 10 triple. NON-STANDARD +MOUSE_MULTIPLE_CLICK_MASK = 1536 + +# This is added to button value at mouse release to differentiate between +# single, double and triple press. Double release adds this times one, +# triple release adds this times two. NON-STANDARD +MOUSE_MULTIPLE_CLICK_FLAG = 512 + # xterm adds this to the button value to signal a mouse drag event MOUSE_DRAG_FLAG = 32 diff --git a/urwid/graphics.py b/urwid/graphics.py index adcff72..d3dd277 100755 --- a/urwid/graphics.py +++ b/urwid/graphics.py @@ -802,7 +802,7 @@ class ProgressBar(Widget): def __init__(self, normal, complete, current=0, done=100, satt=None): """ - :param normal: display attribute for uncomplete part of progress bar + :param normal: display attribute for incomplete part of progress bar :param complete: display attribute for complete part of progress bar :param current: current progress :param done: progress amount at 100% diff --git a/urwid/html_fragment.py b/urwid/html_fragment.py index 9aba906..7ba835e 100755 --- a/urwid/html_fragment.py +++ b/urwid/html_fragment.py @@ -66,7 +66,7 @@ class HtmlGenerator(BaseScreen): self.bright_is_bold = bright_is_bold self.has_underline = has_underline - def set_mouse_tracking(self): + def set_mouse_tracking(self, enable=True): """Not yet implemented""" pass diff --git a/urwid/lcd_display.py b/urwid/lcd_display.py index c66338d..71a1cfa 100644 --- a/urwid/lcd_display.py +++ b/urwid/lcd_display.py @@ -30,7 +30,7 @@ class LCDScreen(BaseScreen): has_underline=None): pass - def set_mouse_tracking(self): + def set_mouse_tracking(self, enable=True): pass def start(self): diff --git a/urwid/listbox.py b/urwid/listbox.py index e954f72..5370e23 100644 --- a/urwid/listbox.py +++ b/urwid/listbox.py @@ -821,7 +821,7 @@ class ListBox(Widget, WidgetContainerMixin): of the focus widget is aligned with the top edge of the listbox (default if unspecified) :type offset_inset: int - :param coming_from: eiter 'above', 'below' or unspecified `None` + :param coming_from: either 'above', 'below' or unspecified `None` :type coming_from: str :param cursor_coords: (x, y) tuple indicating the desired column and row for the cursor, a (x,) tuple indicating only diff --git a/urwid/main_loop.py b/urwid/main_loop.py index 4e83113..cfc8f20 100755 --- a/urwid/main_loop.py +++ b/urwid/main_loop.py @@ -26,6 +26,8 @@ import time import heapq import select import os +from functools import wraps +from weakref import WeakKeyDictionary try: import fcntl @@ -163,7 +165,7 @@ class MainLoop(object): def set_alarm_at(self, tm, callback, user_data=None): """ Schedule an alarm at *tm* time that will call *callback* from the - within the :meth`run` function. Returns a handle that may be passed to + within the :meth:`run` function. Returns a handle that may be passed to :meth:`remove_alarm`. :param tm: time to call callback e.g. ``time.time() + 5`` @@ -568,9 +570,6 @@ class MainLoop(object): self.screen.draw_screen(self.screen_size, canvas) - - - class SelectEventLoop(object): """ Event loop based on :func:`select.select` @@ -582,24 +581,6 @@ class SelectEventLoop(object): self._idle_handle = 0 self._idle_callbacks = {} - def _test_event_loop(self): - """ - >>> import os - >>> rd, wr = os.pipe() - >>> evl = SelectEventLoop() - >>> def step1(): - ... print "writing" - ... os.write(wr, "hi".encode('ascii')) - >>> def step2(): - ... print os.read(rd, 2).decode('ascii') - ... raise ExitMainLoop - >>> handle = evl.alarm(0, step1) - >>> handle = evl.watch_file(rd, step2) - >>> evl.run() - writing - hi - """ - def alarm(self, seconds, callback): """ Call callback() given time from from now. No parameters are @@ -627,16 +608,6 @@ class SelectEventLoop(object): except ValueError: return False - def _test_remove_alarm(self): - """ - >>> evl = SelectEventLoop() - >>> handle = evl.alarm(50, lambda: None) - >>> evl.remove_alarm(handle) - True - >>> evl.remove_alarm(handle) - False - """ - def watch_file(self, fd, callback): """ Call callback() when fd has some data to read. No parameters @@ -661,16 +632,6 @@ class SelectEventLoop(object): return True return False - def _test_remove_watch_file(self): - """ - >>> evl = SelectEventLoop() - >>> handle = evl.watch_file(5, lambda: None) - >>> evl.remove_watch_file(handle) - True - >>> evl.remove_watch_file(handle) - False - """ - def enter_idle(self, callback): """ Add a callback for entering idle. @@ -717,48 +678,6 @@ class SelectEventLoop(object): except ExitMainLoop: pass - def _test_run(self): - """ - >>> import os - >>> rd, wr = os.pipe() - >>> os.write(wr, "data".encode('ascii')) # something to read from rd - 4 - >>> evl = SelectEventLoop() - >>> def say_hello(): - ... print "hello" - >>> def say_waiting(): - ... print "waiting" - >>> def exit_clean(): - ... print "clean exit" - ... raise ExitMainLoop - >>> def exit_error(): - ... 1/0 - >>> handle = evl.alarm(0.01, exit_clean) - >>> handle = evl.alarm(0.005, say_hello) - >>> evl.enter_idle(say_waiting) - 1 - >>> evl.run() - waiting - hello - waiting - clean exit - >>> handle = evl.watch_file(rd, exit_clean) - >>> evl.run() - clean exit - >>> evl.remove_watch_file(handle) - True - >>> handle = evl.alarm(0, exit_error) - >>> evl.run() - Traceback (most recent call last): - ... - ZeroDivisionError: integer division or modulo by zero - >>> handle = evl.watch_file(rd, exit_error) - >>> evl.run() - Traceback (most recent call last): - ... - ZeroDivisionError: integer division or modulo by zero - """ - def _loop(self): """ A single iteration of the event loop @@ -792,484 +711,493 @@ class SelectEventLoop(object): self._did_something = True -if not PYTHON3: - class GLibEventLoop(object): +class GLibEventLoop(object): + """ + Event loop based on GLib.MainLoop + """ + + def __init__(self): + from gi.repository import GLib + self.GLib = GLib + self._alarms = [] + self._watch_files = {} + self._idle_handle = 0 + self._glib_idle_enabled = False # have we called glib.idle_add? + self._idle_callbacks = {} + self._loop = GLib.MainLoop() + self._exc_info = None + self._enable_glib_idle() + + def alarm(self, seconds, callback): + """ + Call callback() given time from from now. No parameters are + passed to callback. + + Returns a handle that may be passed to remove_alarm() + + seconds -- floating point time to wait before calling callback + callback -- function to call from event loop """ - Event loop based on gobject.MainLoop + @self.handle_exit + def ret_false(): + callback() + self._enable_glib_idle() + return False + fd = self.GLib.timeout_add(int(seconds*1000), ret_false) + self._alarms.append(fd) + return (fd, callback) + + def remove_alarm(self, handle): """ + Remove an alarm. - def __init__(self): - import gobject - self.gobject = gobject - self._alarms = [] - self._watch_files = {} - self._idle_handle = 0 - self._glib_idle_enabled = False # have we called glib.idle_add? - self._idle_callbacks = {} - self._loop = self.gobject.MainLoop() - self._exc_info = None + Returns True if the alarm exists, False otherwise + """ + try: + self._alarms.remove(handle[0]) + self.GLib.source_remove(handle[0]) + return True + except ValueError: + return False + + def watch_file(self, fd, callback): + """ + Call callback() when fd has some data to read. No parameters + are passed to callback. + + Returns a handle that may be passed to remove_watch_file() + + fd -- file descriptor to watch for input + callback -- function to call when input is available + """ + @self.handle_exit + def io_callback(source, cb_condition): + callback() self._enable_glib_idle() + return True + self._watch_files[fd] = \ + self.GLib.io_add_watch(fd,self.GLib.IO_IN,io_callback) + return fd - def _test_event_loop(self): - """ - >>> import os - >>> rd, wr = os.pipe() - >>> evl = GLibEventLoop() - >>> def step1(): - ... print "writing" - ... os.write(wr, "hi") - >>> def step2(): - ... print os.read(rd, 2) - ... raise ExitMainLoop - >>> handle = evl.alarm(0, step1) - >>> handle = evl.watch_file(rd, step2) - >>> evl.run() - writing - hi - """ - - def alarm(self, seconds, callback): - """ - Call callback() given time from from now. No parameters are - passed to callback. - - Returns a handle that may be passed to remove_alarm() - - seconds -- floating point time to wait before calling callback - callback -- function to call from event loop - """ - @self.handle_exit - def ret_false(): - callback() - self._enable_glib_idle() - return False - fd = self.gobject.timeout_add(int(seconds*1000), ret_false) - self._alarms.append(fd) - return (fd, callback) - - def remove_alarm(self, handle): - """ - Remove an alarm. - - Returns True if the alarm exists, False otherwise - """ + def remove_watch_file(self, handle): + """ + Remove an input file. + + Returns True if the input file exists, False otherwise + """ + if handle in self._watch_files: + self.GLib.source_remove(self._watch_files[handle]) + del self._watch_files[handle] + return True + return False + + def enter_idle(self, callback): + """ + Add a callback for entering idle. + + Returns a handle that may be passed to remove_enter_idle() + """ + self._idle_handle += 1 + self._idle_callbacks[self._idle_handle] = callback + return self._idle_handle + + def _enable_glib_idle(self): + if self._glib_idle_enabled: + return + self.GLib.idle_add(self._glib_idle_callback) + self._glib_idle_enabled = True + + def _glib_idle_callback(self): + for callback in self._idle_callbacks.values(): + callback() + self._glib_idle_enabled = False + return False # ask glib not to call again (or we would be called + + def remove_enter_idle(self, handle): + """ + Remove an idle callback. + + Returns True if the handle was removed. + """ + try: + del self._idle_callbacks[handle] + except KeyError: + return False + return True + + def run(self): + """ + Start the event loop. Exit the loop when any callback raises + an exception. If ExitMainLoop is raised, exit cleanly. + """ + try: + self._loop.run() + finally: + if self._loop.is_running(): + self._loop.quit() + if self._exc_info: + # An exception caused us to exit, raise it now + exc_info = self._exc_info + self._exc_info = None + raise exc_info[0], exc_info[1], exc_info[2] + + def handle_exit(self,f): + """ + Decorator that cleanly exits the :class:`GLibEventLoop` if + :exc:`ExitMainLoop` is thrown inside of the wrapped function. Store the + exception info if some other exception occurs, it will be reraised after + the loop quits. + + *f* -- function to be wrapped + """ + def wrapper(*args,**kargs): try: - self._alarms.remove(handle[0]) - self.gobject.source_remove(handle[0]) - return True - except ValueError: - return False - - def _test_remove_alarm(self): - """ - >>> evl = GLibEventLoop() - >>> handle = evl.alarm(50, lambda: None) - >>> evl.remove_alarm(handle) - True - >>> evl.remove_alarm(handle) - False - """ - - def watch_file(self, fd, callback): - """ - Call callback() when fd has some data to read. No parameters - are passed to callback. - - Returns a handle that may be passed to remove_watch_file() - - fd -- file descriptor to watch for input - callback -- function to call when input is available - """ - @self.handle_exit - def io_callback(source, cb_condition): - callback() - self._enable_glib_idle() - return True - self._watch_files[fd] = \ - self.gobject.io_add_watch(fd,self.gobject.IO_IN,io_callback) - return fd - - def remove_watch_file(self, handle): - """ - Remove an input file. - - Returns True if the input file exists, False otherwise - """ - if handle in self._watch_files: - self.gobject.source_remove(self._watch_files[handle]) - del self._watch_files[handle] - return True + return f(*args,**kargs) + except ExitMainLoop: + self._loop.quit() + except: + import sys + self._exc_info = sys.exc_info() + if self._loop.is_running(): + self._loop.quit() return False + return wrapper - def _test_remove_watch_file(self): - """ - >>> evl = GLibEventLoop() - >>> handle = evl.watch_file(1, lambda: None) - >>> evl.remove_watch_file(handle) - True - >>> evl.remove_watch_file(handle) - False - """ - - def enter_idle(self, callback): - """ - Add a callback for entering idle. - - Returns a handle that may be passed to remove_enter_idle() - """ - self._idle_handle += 1 - self._idle_callbacks[self._idle_handle] = callback - return self._idle_handle - - def _enable_glib_idle(self): - if self._glib_idle_enabled: - return - self.gobject.idle_add(self._glib_idle_callback) - self._glib_idle_enabled = True - - def _glib_idle_callback(self): - for callback in self._idle_callbacks.values(): - callback() - self._glib_idle_enabled = False - return False # ask glib not to call again (or we would be called - def remove_enter_idle(self, handle): - """ - Remove an idle callback. +class TornadoEventLoop(object): + """ This is an Urwid-specific event loop to plug into its MainLoop. + It acts as an adaptor for Tornado's IOLoop which does all + heavy lifting except idle-callbacks. - Returns True if the handle was removed. - """ + Notice, since Tornado has no concept of idle callbacks we + monkey patch ioloop._impl.poll() function to be able to detect + potential idle periods. + """ + _ioloop_registry = WeakKeyDictionary() # {<ioloop> : {<handle> : <idle_func>}} + _max_idle_handle = 0 + + class PollProxy(object): + """ A simple proxy for a Python's poll object that wraps the .poll() method + in order to detect idle periods and call Urwid callbacks + """ + def __init__(self, poll_obj, idle_map): + self.__poll_obj = poll_obj + self.__idle_map = idle_map + self._idle_done = False + self._prev_timeout = 0 + + def __getattr__(self, name): + return getattr(self.__poll_obj, name) + + def poll(self, timeout): + if timeout > self._prev_timeout: + # if timeout increased we assume a timer event was handled + self._idle_done = False + self._prev_timeout = timeout + start = time.time() + + # any IO pending wins + events = self.__poll_obj.poll(0) + if events: + self._idle_done = False + return events + + # our chance to enter idle + if not self._idle_done: + for callback in self.__idle_map.values(): + callback() + self._idle_done = True + + # then complete the actual request (adjusting timeout) + timeout = max(0, min(timeout, timeout + start - time.time())) + events = self.__poll_obj.poll(timeout) + if events: + self._idle_done = False + return events + + @classmethod + def _patch_poll_impl(cls, ioloop): + """ Wraps original poll object in the IOLoop's poll object + """ + if ioloop in cls._ioloop_registry: + return # we already patched this instance + + cls._ioloop_registry[ioloop] = idle_map = {} + ioloop._impl = cls.PollProxy(ioloop._impl, idle_map) + + def __init__(self, ioloop=None): + if not ioloop: + from tornado.ioloop import IOLoop + ioloop = IOLoop.instance() + self._ioloop = ioloop + self._patch_poll_impl(ioloop) + self._pending_alarms = {} + self._watch_handles = {} # {<watch_handle> : <file_descriptor>} + self._max_watch_handle = 0 + self._exception = None + + def alarm(self, secs, callback): + ioloop = self._ioloop + def wrapped(): try: - del self._idle_callbacks[handle] + del self._pending_alarms[handle] except KeyError: - return False + pass + self.handle_exit(callback)() + handle = ioloop.add_timeout(ioloop.time() + secs, wrapped) + self._pending_alarms[handle] = 1 + return handle + + def remove_alarm(self, handle): + self._ioloop.remove_timeout(handle) + try: + del self._pending_alarms[handle] + except KeyError: + return False + else: return True + def watch_file(self, fd, callback): + from tornado.ioloop import IOLoop + handler = lambda fd,events: self.handle_exit(callback)() + self._ioloop.add_handler(fd, handler, IOLoop.READ) + self._max_watch_handle += 1 + handle = self._max_watch_handle + self._watch_handles[handle] = fd + return handle - def run(self): - """ - Start the event loop. Exit the loop when any callback raises - an exception. If ExitMainLoop is raised, exit cleanly. - """ + def remove_watch_file(self, handle): + fd = self._watch_handles.pop(handle, None) + if fd is None: + return False + else: + self._ioloop.remove_handler(fd) + return True + + def enter_idle(self, callback): + self._max_idle_handle += 1 + handle = self._max_idle_handle + idle_map = self._ioloop_registry[self._ioloop] + idle_map[handle] = callback + return handle + + def remove_enter_idle(self, handle): + idle_map = self._ioloop_registry[self._ioloop] + cb = idle_map.pop(handle, None) + return cb is not None + + def handle_exit(self, func): + @wraps(func) + def wrapper(*args, **kw): try: - self._loop.run() - finally: - if self._loop.is_running(): - self._loop.quit() - if self._exc_info: - # An exception caused us to exit, raise it now - exc_info = self._exc_info - self._exc_info = None - raise exc_info[0], exc_info[1], exc_info[2] - - def _test_run(self): - """ - >>> import os - >>> rd, wr = os.pipe() - >>> os.write(wr, "data") # something to read from rd - 4 - >>> evl = GLibEventLoop() - >>> def say_hello(): - ... print "hello" - >>> def say_waiting(): - ... print "waiting" - >>> def exit_clean(): - ... print "clean exit" - ... raise ExitMainLoop - >>> def exit_error(): - ... 1/0 - >>> handle = evl.alarm(0.01, exit_clean) - >>> handle = evl.alarm(0.005, say_hello) - >>> evl.enter_idle(say_waiting) - 1 - >>> evl.run() - waiting - hello - waiting - clean exit - >>> handle = evl.watch_file(rd, exit_clean) - >>> evl.run() - clean exit - >>> evl.remove_watch_file(handle) - True - >>> handle = evl.alarm(0, exit_error) - >>> evl.run() - Traceback (most recent call last): - ... - ZeroDivisionError: integer division or modulo by zero - >>> handle = evl.watch_file(rd, exit_error) - >>> evl.run() - Traceback (most recent call last): - ... - ZeroDivisionError: integer division or modulo by zero - """ - - def handle_exit(self,f): - """ - Decorator that cleanly exits the :class:`GLibEventLoop` if - :exc:`ExitMainLoop` is thrown inside of the wrapped function. Store the - exception info if some other exception occurs, it will be reraised after - the loop quits. - - *f* -- function to be wrapped - """ - def wrapper(*args,**kargs): - try: - return f(*args,**kargs) - except ExitMainLoop: - self._loop.quit() - except: - import sys - self._exc_info = sys.exc_info() - if self._loop.is_running(): - self._loop.quit() - return False - return wrapper - - - try: - from twisted.internet.abstract import FileDescriptor - except ImportError: - FileDescriptor = object - - class TwistedInputDescriptor(FileDescriptor): - def __init__(self, reactor, fd, cb): - self._fileno = fd - self.cb = cb - FileDescriptor.__init__(self, reactor) - - def fileno(self): - return self._fileno - - def doRead(self): - return self.cb() - - - - class TwistedEventLoop(object): - """ - Event loop based on Twisted_ - """ - _idle_emulation_delay = 1.0/256 # a short time (in seconds) - - def __init__(self, reactor=None, manage_reactor=True): - """ - :param reactor: reactor to use - :type reactor: :class:`twisted.internet.reactor`. - :param: manage_reactor: `True` if you want this event loop to run - and stop the reactor. - :type manage_reactor: boolean - - .. WARNING:: - Twisted's reactor doesn't like to be stopped and run again. If you - need to stop and run your :class:`MainLoop`, consider setting - ``manage_reactor=False`` and take care of running/stopping the reactor - at the beginning/ending of your program yourself. - - .. _Twisted: http://twistedmatrix.com/trac/ - """ - if reactor is None: - import twisted.internet.reactor - reactor = twisted.internet.reactor - self.reactor = reactor - self._alarms = [] - self._watch_files = {} - self._idle_handle = 0 - self._twisted_idle_enabled = False - self._idle_callbacks = {} - self._exc_info = None - self.manage_reactor = manage_reactor - self._enable_twisted_idle() + return func(*args, **kw) + except ExitMainLoop: + self._ioloop.stop() + except Exception as exc: + self._exception = exc + self._ioloop.stop() + return False + return wrapper - def alarm(self, seconds, callback): - """ - Call callback() given time from from now. No parameters are - passed to callback. + def run(self): + self._ioloop.start() + if self._exception: + exc, self._exception = self._exception, None + raise exc - Returns a handle that may be passed to remove_alarm() - seconds -- floating point time to wait before calling callback - callback -- function to call from event loop - """ - handle = self.reactor.callLater(seconds, self.handle_exit(callback)) - return handle +try: + from twisted.internet.abstract import FileDescriptor +except ImportError: + FileDescriptor = object - def remove_alarm(self, handle): - """ - Remove an alarm. +class TwistedInputDescriptor(FileDescriptor): + def __init__(self, reactor, fd, cb): + self._fileno = fd + self.cb = cb + FileDescriptor.__init__(self, reactor) - Returns True if the alarm exists, False otherwise - """ - from twisted.internet.error import AlreadyCancelled, AlreadyCalled - try: - handle.cancel() - return True - except AlreadyCancelled: - return False - except AlreadyCalled: - return False - - def _test_remove_alarm(self): - """ - >>> evl = TwistedEventLoop() - >>> handle = evl.alarm(50, lambda: None) - >>> evl.remove_alarm(handle) - True - >>> evl.remove_alarm(handle) - False - """ - - def watch_file(self, fd, callback): - """ - Call callback() when fd has some data to read. No parameters - are passed to callback. - - Returns a handle that may be passed to remove_watch_file() - - fd -- file descriptor to watch for input - callback -- function to call when input is available - """ - ind = TwistedInputDescriptor(self.reactor, fd, - self.handle_exit(callback)) - self._watch_files[fd] = ind - self.reactor.addReader(ind) - return fd - - def remove_watch_file(self, handle): - """ - Remove an input file. - - Returns True if the input file exists, False otherwise - """ - if handle in self._watch_files: - self.reactor.removeReader(self._watch_files[handle]) - del self._watch_files[handle] - return True + def fileno(self): + return self._fileno + + def doRead(self): + return self.cb() + + +class TwistedEventLoop(object): + """ + Event loop based on Twisted_ + """ + _idle_emulation_delay = 1.0/256 # a short time (in seconds) + + def __init__(self, reactor=None, manage_reactor=True): + """ + :param reactor: reactor to use + :type reactor: :class:`twisted.internet.reactor`. + :param: manage_reactor: `True` if you want this event loop to run + and stop the reactor. + :type manage_reactor: boolean + + .. WARNING:: + Twisted's reactor doesn't like to be stopped and run again. If you + need to stop and run your :class:`MainLoop`, consider setting + ``manage_reactor=False`` and take care of running/stopping the reactor + at the beginning/ending of your program yourself. + + .. _Twisted: http://twistedmatrix.com/trac/ + """ + if reactor is None: + import twisted.internet.reactor + reactor = twisted.internet.reactor + self.reactor = reactor + self._alarms = [] + self._watch_files = {} + self._idle_handle = 0 + self._twisted_idle_enabled = False + self._idle_callbacks = {} + self._exc_info = None + self.manage_reactor = manage_reactor + self._enable_twisted_idle() + + def alarm(self, seconds, callback): + """ + Call callback() given time from from now. No parameters are + passed to callback. + + Returns a handle that may be passed to remove_alarm() + + seconds -- floating point time to wait before calling callback + callback -- function to call from event loop + """ + handle = self.reactor.callLater(seconds, self.handle_exit(callback)) + return handle + + def remove_alarm(self, handle): + """ + Remove an alarm. + + Returns True if the alarm exists, False otherwise + """ + from twisted.internet.error import AlreadyCancelled, AlreadyCalled + try: + handle.cancel() + return True + except AlreadyCancelled: + return False + except AlreadyCalled: return False - def _test_remove_watch_file(self): - """ - >>> evl = TwistedEventLoop() - >>> handle = evl.watch_file(1, lambda: None) - >>> evl.remove_watch_file(handle) - True - >>> evl.remove_watch_file(handle) - False - """ - - def enter_idle(self, callback): - """ - Add a callback for entering idle. - - Returns a handle that may be passed to remove_enter_idle() - """ - self._idle_handle += 1 - self._idle_callbacks[self._idle_handle] = callback - return self._idle_handle - - def _enable_twisted_idle(self): - """ - Twisted's reactors don't have an idle or enter-idle callback - so the best we can do for now is to set a timer event in a very - short time to approximate an enter-idle callback. - - .. WARNING:: - This will perform worse than the other event loops until we can find a - fix or workaround - """ - if self._twisted_idle_enabled: - return - self.reactor.callLater(self._idle_emulation_delay, - self.handle_exit(self._twisted_idle_callback, enable_idle=False)) - self._twisted_idle_enabled = True - - def _twisted_idle_callback(self): - for callback in self._idle_callbacks.values(): - callback() - self._twisted_idle_enabled = False + def watch_file(self, fd, callback): + """ + Call callback() when fd has some data to read. No parameters + are passed to callback. - def remove_enter_idle(self, handle): - """ - Remove an idle callback. + Returns a handle that may be passed to remove_watch_file() - Returns True if the handle was removed. - """ - try: - del self._idle_callbacks[handle] - except KeyError: - return False + fd -- file descriptor to watch for input + callback -- function to call when input is available + """ + ind = TwistedInputDescriptor(self.reactor, fd, + self.handle_exit(callback)) + self._watch_files[fd] = ind + self.reactor.addReader(ind) + return fd + + def remove_watch_file(self, handle): + """ + Remove an input file. + + Returns True if the input file exists, False otherwise + """ + if handle in self._watch_files: + self.reactor.removeReader(self._watch_files[handle]) + del self._watch_files[handle] return True + return False - def run(self): - """ - Start the event loop. Exit the loop when any callback raises - an exception. If ExitMainLoop is raised, exit cleanly. - """ - if not self.manage_reactor: - return - self.reactor.run() - if self._exc_info: - # An exception caused us to exit, raise it now - exc_info = self._exc_info - self._exc_info = None - raise exc_info[0], exc_info[1], exc_info[2] - - def _test_run(self): - """ - >>> import os - >>> rd, wr = os.pipe() - >>> os.write(wr, "data") # something to read from rd - 4 - >>> evl = TwistedEventLoop() - >>> def say_hello_data(): - ... print "hello data" - ... os.read(rd, 4) - >>> def say_waiting(): - ... print "waiting" - >>> def say_hello(): - ... print "hello" - >>> handle = evl.watch_file(rd, say_hello_data) - >>> def say_being_twisted(): - ... print "oh I'm messed up" - ... raise ExitMainLoop - >>> handle = evl.alarm(0.0625, say_being_twisted) - >>> handle = evl.alarm(0.03125, say_hello) - >>> evl.enter_idle(say_waiting) - 1 - >>> evl.run() - hello data - waiting - hello - waiting - oh I'm messed up - """ - - def handle_exit(self, f, enable_idle=True): - """ - Decorator that cleanly exits the :class:`TwistedEventLoop` if - :class:`ExitMainLoop` is thrown inside of the wrapped function. Store the - exception info if some other exception occurs, it will be reraised after - the loop quits. - - *f* -- function to be wrapped - """ - def wrapper(*args,**kargs): - rval = None - try: - rval = f(*args,**kargs) - except ExitMainLoop: - if self.manage_reactor: - self.reactor.stop() - except: - import sys - print sys.exc_info() - self._exc_info = sys.exc_info() - if self.manage_reactor: - self.reactor.crash() - if enable_idle: - self._enable_twisted_idle() - return rval - return wrapper + def enter_idle(self, callback): + """ + Add a callback for entering idle. + + Returns a handle that may be passed to remove_enter_idle() + """ + self._idle_handle += 1 + self._idle_callbacks[self._idle_handle] = callback + return self._idle_handle + + def _enable_twisted_idle(self): + """ + Twisted's reactors don't have an idle or enter-idle callback + so the best we can do for now is to set a timer event in a very + short time to approximate an enter-idle callback. + + .. WARNING:: + This will perform worse than the other event loops until we can find a + fix or workaround + """ + if self._twisted_idle_enabled: + return + self.reactor.callLater(self._idle_emulation_delay, + self.handle_exit(self._twisted_idle_callback, enable_idle=False)) + self._twisted_idle_enabled = True + + def _twisted_idle_callback(self): + for callback in self._idle_callbacks.values(): + callback() + self._twisted_idle_enabled = False + + def remove_enter_idle(self, handle): + """ + Remove an idle callback. + + Returns True if the handle was removed. + """ + try: + del self._idle_callbacks[handle] + except KeyError: + return False + return True + + def run(self): + """ + Start the event loop. Exit the loop when any callback raises + an exception. If ExitMainLoop is raised, exit cleanly. + """ + if not self.manage_reactor: + return + self.reactor.run() + if self._exc_info: + # An exception caused us to exit, raise it now + exc_info = self._exc_info + self._exc_info = None + raise exc_info[0], exc_info[1], exc_info[2] + + def handle_exit(self, f, enable_idle=True): + """ + Decorator that cleanly exits the :class:`TwistedEventLoop` if + :class:`ExitMainLoop` is thrown inside of the wrapped function. Store the + exception info if some other exception occurs, it will be reraised after + the loop quits. + + *f* -- function to be wrapped + """ + def wrapper(*args,**kargs): + rval = None + try: + rval = f(*args,**kargs) + except ExitMainLoop: + if self.manage_reactor: + self.reactor.stop() + except: + import sys + print sys.exc_info() + self._exc_info = sys.exc_info() + if self.manage_reactor: + self.reactor.crash() + if enable_idle: + self._enable_twisted_idle() + return rval + return wrapper diff --git a/urwid/monitored_list.py b/urwid/monitored_list.py index 1a098c8..86d749e 100755 --- a/urwid/monitored_list.py +++ b/urwid/monitored_list.py @@ -139,7 +139,9 @@ class MonitoredFocusList(MonitoredList): the index passed is ignored. This function may call self._focus_changed when the focus - is actually modified. That method may be overridden on the + is modified, passing the new focus position to the + callback just before changing the old focus setting. + That method may be overridden on the instance with set_focus_changed_callback(). >>> ml = MonitoredFocusList([9, 10, 11]) diff --git a/urwid/old_str_util.py b/urwid/old_str_util.py index f2b9287..bb993f6 100755 --- a/urwid/old_str_util.py +++ b/urwid/old_str_util.py @@ -297,7 +297,7 @@ def within_double_byte(text, line_start, pos): Return values: 0 -- not within dbe char, or double_byte_encoding == False 1 -- pos is on the 1st half of a dbe char - 2 -- pos is on the 2nd half og a dbe char + 2 -- pos is on the 2nd half of a dbe char """ assert isinstance(text, bytes) v = ord2(text[pos]) diff --git a/urwid/raw_display.py b/urwid/raw_display.py index f267e4f..58b3112 100644 --- a/urwid/raw_display.py +++ b/urwid/raw_display.py @@ -54,6 +54,7 @@ class Screen(BaseScreen, RealTerminal): """ super(Screen, self).__init__() self._pal_escape = {} + self._pal_attrspec = {} signals.connect_signal(self, UPDATE_PALETTE_ENTRY, self._on_update_palette_entry) self.colors = 16 # FIXME: detect this @@ -68,11 +69,14 @@ class Screen(BaseScreen, RealTerminal): self.maxrow = None self.gpm_mev = None self.gpm_event_pending = False + self._mouse_tracking_enabled = False self.last_bstate = 0 self._setup_G1_done = False self._rows_used = None self._cy = 0 - self.bright_is_bold = os.environ.get('TERM',None) != "xterm" + term = os.environ.get('TERM', '') + self.bright_is_bold = not term.startswith("xterm") + self.back_color_erase = not term.startswith("screen") self._next_timeout = None self._term_output_file = sys.stdout self._term_input_file = sys.stdin @@ -82,8 +86,9 @@ class Screen(BaseScreen, RealTerminal): def _on_update_palette_entry(self, name, *attrspecs): # copy the attribute to a dictionary containing the escape seqences - self._pal_escape[name] = self._attrspec_to_escape( - attrspecs[{16:0,1:1,88:2,256:3}[self.colors]]) + a = attrspecs[{16:0,1:1,88:2,256:3}[self.colors]] + self._pal_attrspec[name] = a + self._pal_escape[name] = self._attrspec_to_escape(a) def set_input_timeouts(self, max_wait=None, complete_wait=0.125, resize_wait=0.125): @@ -115,38 +120,56 @@ class Screen(BaseScreen, RealTerminal): os.write(self._resize_pipe_wr, B('R')) self._resized = True self.screen_buf = None - + + def _sigcont_handler(self, signum, frame): + self.stop() + self.start() + self._sigwinch_handler(None, None) + def signal_init(self): """ - Called in the startup of run wrapper to set the SIGWINCH - signal handler to self._sigwinch_handler. + Called in the startup of run wrapper to set the SIGWINCH + and SIGCONT signal handlers. Override this function to call from main thread in threaded applications. """ signal.signal(signal.SIGWINCH, self._sigwinch_handler) - + signal.signal(signal.SIGCONT, self._sigcont_handler) + def signal_restore(self): """ Called in the finally block of run wrapper to restore the - SIGWINCH handler to the default handler. + SIGWINCH and SIGCONT signal handlers. Override this function to call from main thread in threaded applications. """ + signal.signal(signal.SIGCONT, signal.SIG_DFL) signal.signal(signal.SIGWINCH, signal.SIG_DFL) - - def set_mouse_tracking(self): + + def set_mouse_tracking(self, enable=True): """ - Enable mouse tracking. - + Enable (or disable) mouse tracking. + After calling this function get_input will include mouse click events along with keystrokes. """ - self._term_output_file.write(escape.MOUSE_TRACKING_ON) + enable = bool(enable) + if enable == self._mouse_tracking_enabled: + return + + self._mouse_tracking(enable) + self._mouse_tracking_enabled = enable + + def _mouse_tracking(self, enable): + if enable: + self._term_output_file.write(escape.MOUSE_TRACKING_ON) + self._start_gpm_tracking() + else: + self._term_output_file.write(escape.MOUSE_TRACKING_OFF) + self._stop_gpm_tracking() - self._start_gpm_tracking() - def _start_gpm_tracking(self): if not os.path.isfile("/usr/bin/mev"): return @@ -158,12 +181,14 @@ class Screen(BaseScreen, RealTerminal): close_fds=True) fcntl.fcntl(m.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) self.gpm_mev = m - + def _stop_gpm_tracking(self): + if not self.gpm_mev: + return os.kill(self.gpm_mev.pid, signal.SIGINT) os.waitpid(self.gpm_mev.pid, 0) self.gpm_mev = None - + def start(self, alternate_buffer=True): """ Initialize the screen and input mode. @@ -193,7 +218,8 @@ class Screen(BaseScreen, RealTerminal): super(Screen, self).start() signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED) - + # restore mouse tracking to previous state + self._mouse_tracking(self._mouse_tracking_enabled) def stop(self): """ @@ -212,19 +238,19 @@ class Screen(BaseScreen, RealTerminal): termios.tcsetattr(fd, termios.TCSADRAIN, self._old_termios_settings) + self._mouse_tracking(False) + move_cursor = "" - if self.gpm_mev: - self._stop_gpm_tracking() if self._alternate_buffer: move_cursor = escape.RESTORE_NORMAL_BUFFER elif self.maxrow is not None: - move_cursor = escape.set_cursor_position( + move_cursor = escape.set_cursor_position( 0, self.maxrow) - self._term_output_file.write(self._attrspec_to_escape(AttrSpec('','')) + self._term_output_file.write( + self._attrspec_to_escape(AttrSpec('','')) + escape.SI - + escape.MOUSE_TRACKING_OFF - + escape.SHOW_CURSOR - + move_cursor + "\n" + escape.SHOW_CURSOR ) + + move_cursor + + escape.SHOW_CURSOR) self._input_iter = self._fake_input_iter() if self._old_signal_keys: @@ -235,7 +261,7 @@ class Screen(BaseScreen, RealTerminal): def run_wrapper(self, fn, alternate_buffer=True): """ - Call start to initialize screen, then call fn. + Call start to initialize screen, then call fn. When fn exits call stop to restore the screen to normal. alternate_buffer -- use alternate screen buffer and restore @@ -328,7 +354,7 @@ class Screen(BaseScreen, RealTerminal): Return a list of integer file descriptors that should be polled in external event loops to check for user input. - Use this method if you are implementing yout own event loop. + Use this method if you are implementing your own event loop. """ if not self._started: return [] @@ -357,7 +383,7 @@ class Screen(BaseScreen, RealTerminal): def _run_input_iter(self): def empty_resize_pipe(): # clean out the pipe used to signal external event loops - # that a resize has occured + # that a resize has occurred try: while True: os.read(self._resize_pipe_rd, 1) except OSError: @@ -484,7 +510,18 @@ class Screen(BaseScreen, RealTerminal): b |= mod l.extend([ 27, ord('['), ord('M'), b+32, x+32, y+32 ]) - if ev == 20: # press + def determine_button_release( flag ): + if b & 4 and last & 1: + append_button( 0 + flag ) + next |= 1 + if b & 2 and last & 2: + append_button( 1 + flag ) + next |= 2 + if b & 1 and last & 4: + append_button( 2 + flag ) + next |= 4 + + if ev == 20 or ev == 36 or ev == 52: # press if b & 4 and last & 1 == 0: append_button( 0 ) next |= 1 @@ -511,7 +548,21 @@ class Screen(BaseScreen, RealTerminal): if b & 1 and last & 4: append_button( 2 + escape.MOUSE_RELEASE_FLAG ) next &= ~ 4 - + if ev == 40: # double click (release) + if b & 4 and last & 1: + append_button( 0 + escape.MOUSE_MULTIPLE_CLICK_FLAG ) + if b & 2 and last & 2: + append_button( 1 + escape.MOUSE_MULTIPLE_CLICK_FLAG ) + if b & 1 and last & 4: + append_button( 2 + escape.MOUSE_MULTIPLE_CLICK_FLAG ) + elif ev == 52: + if b & 4 and last & 1: + append_button( 0 + escape.MOUSE_MULTIPLE_CLICK_FLAG*2 ) + if b & 2 and last & 2: + append_button( 1 + escape.MOUSE_MULTIPLE_CLICK_FLAG*2 ) + if b & 1 and last & 4: + append_button( 2 + escape.MOUSE_MULTIPLE_CLICK_FLAG*2 ) + self.last_bstate = next return l @@ -521,8 +572,14 @@ class Screen(BaseScreen, RealTerminal): def get_cols_rows(self): """Return the terminal dimensions (num columns, num rows).""" - buf = fcntl.ioctl(0, termios.TIOCGWINSZ, ' '*4) - y, x = struct.unpack('hh', buf) + y, x = 80, 24 + try: + buf = fcntl.ioctl(self._term_output_file.fileno(), + termios.TIOCGWINSZ, ' '*4) + y, x = struct.unpack('hh', buf) + except IOError: + # Term size could not be determined + pass self.maxrow = y return x, y @@ -616,6 +673,10 @@ class Screen(BaseScreen, RealTerminal): return self._attrspec_to_escape( AttrSpec('default','default')) + def using_standout(a): + a = self._pal_attrspec.get(a, a) + return isinstance(a, AttrSpec) and a.standout + ins = None o.append(set_cursor_home()) cy = 0 @@ -644,12 +705,14 @@ class Screen(BaseScreen, RealTerminal): cy = y whitespace_at_end = False - if row and row[-1][2][-1:] == B(' '): - whitespace_at_end = True + if row: a, cs, run = row[-1] - row = row[:-1] + [(a, cs, run.rstrip(B(' ')))] - elif y == maxrow-1 and maxcol>1: - row, back, ins = self._last_row(row) + if (run[-1:] == B(' ') and self.back_color_erase + and not using_standout(a)): + whitespace_at_end = True + row = row[:-1] + [(a, cs, run.rstrip(B(' ')))] + elif y == maxrow-1 and maxcol > 1: + row, back, ins = self._last_row(row) first = True lasta = lastcs = None @@ -872,7 +935,7 @@ class Screen(BaseScreen, RealTerminal): """ entries - list of (index, red, green, blue) tuples. - Attempt to set part of the terminal pallette (this does not work + Attempt to set part of the terminal palette (this does not work on all terminals.) The changes are sent as a single escape sequence so they should all take effect at the same time. diff --git a/urwid/signals.py b/urwid/signals.py index f360b27..80b6646 100644 --- a/urwid/signals.py +++ b/urwid/signals.py @@ -20,6 +20,8 @@ # Urwid web site: http://excess.org/urwid/ +import itertools +import weakref class MetaSignals(type): @@ -43,6 +45,12 @@ def setdefaultattr(obj, name, value): setattr(obj, name, value) return value +class Key(object): + """ + Minimal class, whose only purpose is to produce objects with a + unique hash + """ + __slots__ = [] class Signals(object): _signal_attr = '_urwid_signals' # attribute to attach to signal senders @@ -63,7 +71,7 @@ class Signals(object): """ self._supported[sig_cls] = signals - def connect(self, obj, name, callback, user_arg=None): + def connect(self, obj, name, callback, user_arg=None, weak_args=None, user_args=None): """ :param obj: the object sending a signal :type obj: object @@ -71,32 +79,169 @@ class Signals(object): :type name: signal name :param callback: the function to call when that signal is sent :type callback: function - :param user_arg: optional additional argument to callback, if None - no arguments will be added + :param user_arg: deprecated additional argument to callback (appended + after the arguments passed when the signal is + emitted). If None no arguments will be added. + Don't use this argument, use user_args instead. + :param weak_args: additional arguments passed to the callback + (before any arguments passed when the signal + is emitted and before any user_args). + + These arguments are stored as weak references + (but converted back into their original value + before passing them to callback) to prevent + any objects referenced (indirectly) from + weak_args from being kept alive just because + they are referenced by this signal handler. + + Use this argument only as a keyword argument, + since user_arg might be removed in the future. + :type weak_args: iterable + :param user_args: additional arguments to pass to the callback, + (before any arguments passed when the signal + is emitted but after any weak_args). + + Use this argument only as a keyword argument, + since user_arg might be removed in the future. + :type user_args: iterable + + When a matching signal is sent, callback will be called. The + arguments it receives will be the user_args passed at connect + time (as individual arguments) followed by all the positional + parameters sent with the signal. + + As an example of using weak_args, consider the following snippet: + + >>> import urwid + >>> debug = urwid.Text('') + >>> def handler(widget, newtext): + ... debug.set_text("Edit widget changed to %s" % newtext) + >>> edit = urwid.Edit('') + >>> key = urwid.connect_signal(edit, 'change', handler) + + If you now build some interface using "edit" and "debug", the + "debug" widget will show whatever you type in the "edit" widget. + However, if you remove all references to the "debug" widget, it + will still be kept alive by the signal handler. This because the + signal handler is a closure that (implicitly) references the + "edit" widget. If you want to allow the "debug" widget to be + garbage collected, you can create a "fake" or "weak" closure + (it's not really a closure, since it doesn't reference any + outside variables, so it's just a dynamic function): + + >>> debug = urwid.Text('') + >>> def handler(weak_debug, widget, newtext): + ... weak_debug.set_text("Edit widget changed to %s" % newtext) + >>> edit = urwid.Edit('') + >>> key = urwid.connect_signal(edit, 'change', handler, weak_args=[debug]) - When a matching signal is sent, callback will be called with - all the positional parameters sent with the signal. If user_arg - is not None it will be sent added to the end of the positional - parameters sent to callback. + Here the weak_debug parameter in print_debug is the value passed + in the weak_args list to connect_signal. Note that the + weak_debug value passed is not a weak reference anymore, the + signals code transparently dereferences the weakref parameter + before passing it to print_debug. + + Returns a key associated by this signal handler, which can be + used to disconnect the signal later on using + urwid.disconnect_signal_by_key. Alternatively, the signal + handler can also be disconnected by calling + urwid.disconnect_signal, which doesn't need this key. """ + sig_cls = obj.__class__ if not name in self._supported.get(sig_cls, []): raise NameError, "No such signal %r for object %r" % \ (name, obj) - d = setdefaultattr(obj, self._signal_attr, {}) - d.setdefault(name, []).append((callback, user_arg)) - def disconnect(self, obj, name, callback, user_arg=None): + # Just generate an arbitrary (but unique) key + key = Key() + + signals = setdefaultattr(obj, self._signal_attr, {}) + handlers = signals.setdefault(name, []) + + # Remove the signal handler when any of the weakref'd arguments + # are garbage collected. Note that this means that the handlers + # dictionary can be modified _at any time_, so it should never + # be iterated directly (e.g. iterate only over .keys() and + # .items(), never over .iterkeys(), .iteritems() or the object + # itself). + # We let the callback keep a weakref to the object as well, to + # prevent a circular reference between the handler and the + # object (via the weakrefs, which keep strong references to + # their callbacks) from existing. + obj_weak = weakref.ref(obj) + def weakref_callback(weakref): + o = obj_weak() + if o: + try: + del getattr(o, self._signal_attr, {})[name][key] + except KeyError: + pass + + user_args = self._prepare_user_args(weak_args, user_args, weakref_callback) + handlers.append((key, callback, user_arg, user_args)) + + return key + + def _prepare_user_args(self, weak_args, user_args, callback = None): + # Turn weak_args into weakrefs and prepend them to user_args + return [weakref.ref(a, callback) for a in (weak_args or [])] + (user_args or []) + + + def disconnect(self, obj, name, callback, user_arg=None, weak_args=None, user_args=None): """ + :param obj: the object to disconnect the signal from + :type obj: object + :param name: the signal to disconnect, typically a string + :type name: signal name + :param callback: the callback function passed to connect_signal + :type callback: function + :param user_arg: the user_arg parameter passed to connect_signal + :param weak_args: the weak_args parameter passed to connect_signal + :param user_args: the weak_args parameter passed to connect_signal + This function will remove a callback from the list connected - to a signal with connect_signal(). + to a signal with connect_signal(). The arguments passed should + be exactly the same as those passed to connect_signal(). + + If the callback is not connected or already disconnected, this + function will simply do nothing. """ - d = setdefaultattr(obj, self._signal_attr, {}) - if name not in d: - return - if (callback, user_arg) not in d[name]: + signals = setdefaultattr(obj, self._signal_attr, {}) + if name not in signals: return - d[name].remove((callback, user_arg)) + + handlers = signals[name] + + # Do the same processing as in connect, so we can compare the + # resulting tuple. + user_args = self._prepare_user_args(weak_args, user_args) + + # Remove the given handler + for h in handlers: + if h[1:] == (callback, user_arg, user_args): + return self.disconnect_by_key(obj, name, h[0]) + + def disconnect_by_key(self, obj, name, key): + """ + :param obj: the object to disconnect the signal from + :type obj: object + :param name: the signal to disconnect, typically a string + :type name: signal name + :param key: the key for this signal handler, as returned by + connect_signal(). + :type key: Key + + This function will remove a callback from the list connected + to a signal with connect_signal(). The key passed should be the + value returned by connect_signal(). + + If the callback is not connected or already disconnected, this + function will simply do nothing. + """ + signals = setdefaultattr(obj, self._signal_attr, {}) + handlers = signals.get(name, []) + handlers[:] = [h for h in handlers if h[0] is not key] def emit(self, obj, name, *args): """ @@ -113,17 +258,45 @@ class Signals(object): This function returns True if any of the callbacks returned True. """ result = False - d = getattr(obj, self._signal_attr, {}) - for callback, user_arg in d.get(name, []): - args_copy = args - if user_arg is not None: - args_copy = args + (user_arg,) - result |= bool(callback(*args_copy)) + signals = getattr(obj, self._signal_attr, {}) + handlers = signals.get(name, []) + for key, callback, user_arg, user_args in handlers: + result |= self._call_callback(callback, user_arg, user_args, args) return result + def _call_callback(self, callback, user_arg, user_args, emit_args): + if user_args: + args_to_pass = [] + for arg in user_args: + if isinstance(arg, weakref.ReferenceType): + arg = arg() + if arg is None: + # If the weakref is None, the referenced object + # was cleaned up. We just skip the entire + # callback in this case. The weakref cleanup + # handler will have removed the callback when + # this happens, so no need to actually remove + # the callback here. + return False + args_to_pass.append(arg) + + args_to_pass.extend(emit_args) + else: + # Optimization: Don't create a new list when there are + # no user_args + args_to_pass = emit_args + + # The deprecated user_arg argument was added to the end + # instead of the beginning. + if user_arg is not None: + args_to_pass = itertools.chain(args_to_pass, (user_arg,)) + + return bool(callback(*args_to_pass)) + _signals = Signals() emit_signal = _signals.emit register_signal = _signals.register connect_signal = _signals.connect disconnect_signal = _signals.disconnect +disconnect_signal_by_key = _signals.disconnect_by_key diff --git a/urwid/split_repr.py b/urwid/split_repr.py index 1438995..8b5e24c 100755 --- a/urwid/split_repr.py +++ b/urwid/split_repr.py @@ -128,7 +128,7 @@ def remove_defaults(d, fn): if varargs: del args[-1] - # create adictionary of args with default values + # create a dictionary of args with default values ddict = dict(zip(args[len(args) - len(defaults):], defaults)) for k, v in d.items(): diff --git a/urwid/tests/test_container.py b/urwid/tests/test_container.py index caa0ef4..6ddb909 100644 --- a/urwid/tests/test_container.py +++ b/urwid/tests/test_container.py @@ -118,9 +118,21 @@ class PileTest(unittest.TestCase): def test_change_focus_with_mouse(self): p = urwid.Pile([urwid.Edit(), urwid.Edit()]) - self.assertEquals(p.focus_position, 0) + self.assertEqual(p.focus_position, 0) p.mouse_event((10,), 'button press', 1, 1, 1, True) - self.assertEquals(p.focus_position, 1) + self.assertEqual(p.focus_position, 1) + + def test_zero_weight(self): + p = urwid.Pile([ + urwid.SolidFill('a'), + ('weight', 0, urwid.SolidFill('d')), + ]) + p.render((5, 4)) + + def test_mouse_event_in_empty_pile(self): + p = urwid.Pile([]) + p.mouse_event((5,), 'button press', 1, 1, 1, False) + p.mouse_event((5,), 'button press', 1, 1, 1, True) class ColumnsTest(unittest.TestCase): @@ -228,9 +240,9 @@ class ColumnsTest(unittest.TestCase): def test_old_attributes(self): c = urwid.Columns([urwid.Text(u'a'), urwid.SolidFill(u'x')], box_columns=[1]) - self.assertEquals(c.box_columns, [1]) + self.assertEqual(c.box_columns, [1]) c.box_columns=[] - self.assertEquals(c.box_columns, []) + self.assertEqual(c.box_columns, []) def test_box_column(self): c = urwid.Columns([urwid.Filler(urwid.Edit()),urwid.Text('')], @@ -248,18 +260,18 @@ class OverlayTest(unittest.TestCase): o1 = urwid.Overlay(urwid.SolidFill(u'X'), urwid.SolidFill(u'O'), ('fixed left', 5), ('fixed right', 4), ('fixed top', 3), ('fixed bottom', 2),) - self.assertEquals(o1.contents[1][1], ( + self.assertEqual(o1.contents[1][1], ( 'left', None, 'relative', 100, None, 5, 4, 'top', None, 'relative', 100, None, 3, 2)) o2 = urwid.Overlay(urwid.SolidFill(u'X'), urwid.SolidFill(u'O'), ('fixed right', 5), ('fixed left', 4), ('fixed bottom', 3), ('fixed top', 2),) - self.assertEquals(o2.contents[1][1], ( + self.assertEqual(o2.contents[1][1], ( 'right', None, 'relative', 100, None, 4, 5, 'bottom', None, 'relative', 100, None, 2, 3)) def test_get_cursor_coords(self): - self.assertEquals(urwid.Overlay(urwid.Filler(urwid.Edit()), + self.assertEqual(urwid.Overlay(urwid.Filler(urwid.Edit()), urwid.SolidFill(u'B'), 'right', 1, 'bottom', 1).get_cursor_coords((2,2)), (1,1)) @@ -267,11 +279,15 @@ class OverlayTest(unittest.TestCase): class GridFlowTest(unittest.TestCase): def test_cell_width(self): gf = urwid.GridFlow([], 5, 0, 0, 'left') - self.assertEquals(gf.cell_width, 5) + self.assertEqual(gf.cell_width, 5) def test_basics(self): repr(urwid.GridFlow([], 5, 0, 0, 'left')) # should not fail + def test_v_sep(self): + gf = urwid.GridFlow([urwid.Text("test")], 10, 3, 1, "center") + self.assertEqual(gf.rows((40,), False), 1) + class WidgetSquishTest(unittest.TestCase): def wstest(self, w): @@ -352,7 +368,7 @@ class CommonContainerTest(unittest.TestCase): t3 = urwid.Text(u'three') sf = urwid.SolidFill('x') p = urwid.Pile([]) - self.assertEquals(p.focus, None) + self.assertEqual(p.focus, None) self.assertRaises(IndexError, lambda: getattr(p, 'focus_position')) self.assertRaises(IndexError, lambda: setattr(p, 'focus_position', None)) @@ -361,20 +377,20 @@ class CommonContainerTest(unittest.TestCase): (sf, ('given', 3)), (t3, ('pack', None))] p.focus_position = 1 del p.contents[0] - self.assertEquals(p.focus_position, 0) + self.assertEqual(p.focus_position, 0) p.contents[0:0] = [(t3, ('pack', None)), (t2, ('pack', None))] p.contents.insert(3, (t1, ('pack', None))) - self.assertEquals(p.focus_position, 2) + self.assertEqual(p.focus_position, 2) self.assertRaises(urwid.PileError, lambda: p.contents.append(t1)) self.assertRaises(urwid.PileError, lambda: p.contents.append((t1, None))) self.assertRaises(urwid.PileError, lambda: p.contents.append((t1, 'given'))) p = urwid.Pile([t1, t2]) - self.assertEquals(p.focus, t1) - self.assertEquals(p.focus_position, 0) + self.assertEqual(p.focus, t1) + self.assertEqual(p.focus_position, 0) p.focus_position = 1 - self.assertEquals(p.focus, t2) - self.assertEquals(p.focus_position, 1) + self.assertEqual(p.focus, t2) + self.assertEqual(p.focus_position, 1) p.focus_position = 0 self.assertRaises(IndexError, lambda: setattr(p, 'focus_position', -1)) self.assertRaises(IndexError, lambda: setattr(p, 'focus_position', 2)) @@ -383,27 +399,27 @@ class CommonContainerTest(unittest.TestCase): self.assertRaises(IndexError, lambda: p.set_focus(-1)) self.assertRaises(IndexError, lambda: p.set_focus(2)) p.set_focus(t2) - self.assertEquals(p.focus_position, 1) + self.assertEqual(p.focus_position, 1) self.assertRaises(ValueError, lambda: p.set_focus('nonexistant')) - self.assertEquals(p.widget_list, [t1, t2]) - self.assertEquals(p.item_types, [('weight', 1), ('weight', 1)]) + self.assertEqual(p.widget_list, [t1, t2]) + self.assertEqual(p.item_types, [('weight', 1), ('weight', 1)]) p.widget_list = [t2, t1] - self.assertEquals(p.widget_list, [t2, t1]) - self.assertEquals(p.contents, [(t2, ('weight', 1)), (t1, ('weight', 1))]) - self.assertEquals(p.focus_position, 1) # focus unchanged + self.assertEqual(p.widget_list, [t2, t1]) + self.assertEqual(p.contents, [(t2, ('weight', 1)), (t1, ('weight', 1))]) + self.assertEqual(p.focus_position, 1) # focus unchanged p.item_types = [('flow', None), ('weight', 2)] - self.assertEquals(p.item_types, [('flow', None), ('weight', 2)]) - self.assertEquals(p.contents, [(t2, ('pack', None)), (t1, ('weight', 2))]) - self.assertEquals(p.focus_position, 1) # focus unchanged + self.assertEqual(p.item_types, [('flow', None), ('weight', 2)]) + self.assertEqual(p.contents, [(t2, ('pack', None)), (t1, ('weight', 2))]) + self.assertEqual(p.focus_position, 1) # focus unchanged p.widget_list = [t1] - self.assertEquals(len(p.contents), 1) - self.assertEquals(p.focus_position, 0) + self.assertEqual(len(p.contents), 1) + self.assertEqual(p.focus_position, 0) p.widget_list.extend([t2, t1]) - self.assertEquals(len(p.contents), 3) - self.assertEquals(p.item_types, [ + self.assertEqual(len(p.contents), 3) + self.assertEqual(p.item_types, [ ('flow', None), ('weight', 1), ('weight', 1)]) p.item_types[:] = [('weight', 2)] - self.assertEquals(len(p.contents), 1) + self.assertEqual(len(p.contents), 1) def test_columns(self): t1 = urwid.Text(u'one') @@ -411,7 +427,7 @@ class CommonContainerTest(unittest.TestCase): t3 = urwid.Text(u'three') sf = urwid.SolidFill('x') c = urwid.Columns([]) - self.assertEquals(c.focus, None) + self.assertEqual(c.focus, None) self.assertRaises(IndexError, lambda: getattr(c, 'focus_position')) self.assertRaises(IndexError, lambda: setattr(c, 'focus_position', None)) @@ -423,22 +439,22 @@ class CommonContainerTest(unittest.TestCase): (t3, ('given', 10, False))] c.focus_position = 1 del c.contents[0] - self.assertEquals(c.focus_position, 0) + self.assertEqual(c.focus_position, 0) c.contents[0:0] = [ (t3, ('given', 10, False)), (t2, ('weight', 1, False))] c.contents.insert(3, (t1, ('pack', None, False))) - self.assertEquals(c.focus_position, 2) + self.assertEqual(c.focus_position, 2) self.assertRaises(urwid.ColumnsError, lambda: c.contents.append(t1)) self.assertRaises(urwid.ColumnsError, lambda: c.contents.append((t1, None))) self.assertRaises(urwid.ColumnsError, lambda: c.contents.append((t1, 'given'))) c = urwid.Columns([t1, t2]) - self.assertEquals(c.focus, t1) - self.assertEquals(c.focus_position, 0) + self.assertEqual(c.focus, t1) + self.assertEqual(c.focus_position, 0) c.focus_position = 1 - self.assertEquals(c.focus, t2) - self.assertEquals(c.focus_position, 1) + self.assertEqual(c.focus, t2) + self.assertEqual(c.focus_position, 1) c.focus_position = 0 self.assertRaises(IndexError, lambda: setattr(c, 'focus_position', -1)) self.assertRaises(IndexError, lambda: setattr(c, 'focus_position', 2)) @@ -448,47 +464,47 @@ class CommonContainerTest(unittest.TestCase): self.assertRaises(IndexError, lambda: c.set_focus(-1)) self.assertRaises(IndexError, lambda: c.set_focus(3)) c.set_focus(t2) - self.assertEquals(c.focus_position, 1) + self.assertEqual(c.focus_position, 1) self.assertRaises(ValueError, lambda: c.set_focus('nonexistant')) - self.assertEquals(c.widget_list, [t1, t2, sf]) - self.assertEquals(c.column_types, [ + self.assertEqual(c.widget_list, [t1, t2, sf]) + self.assertEqual(c.column_types, [ ('weight', 1), ('weight', 3), ('weight', 1)]) - self.assertEquals(c.box_columns, [2]) + self.assertEqual(c.box_columns, [2]) c.widget_list = [t2, t1, sf] - self.assertEquals(c.widget_list, [t2, t1, sf]) - self.assertEquals(c.box_columns, [2]) + self.assertEqual(c.widget_list, [t2, t1, sf]) + self.assertEqual(c.box_columns, [2]) - self.assertEquals(c.contents, [ + self.assertEqual(c.contents, [ (t2, ('weight', 1, False)), (t1, ('weight', 3, False)), (sf, ('weight', 1, True))]) - self.assertEquals(c.focus_position, 1) # focus unchanged + self.assertEqual(c.focus_position, 1) # focus unchanged c.column_types = [ ('flow', None), # use the old name ('weight', 2), ('fixed', 5)] - self.assertEquals(c.column_types, [ + self.assertEqual(c.column_types, [ ('flow', None), ('weight', 2), ('fixed', 5)]) - self.assertEquals(c.contents, [ + self.assertEqual(c.contents, [ (t2, ('pack', None, False)), (t1, ('weight', 2, False)), (sf, ('given', 5, True))]) - self.assertEquals(c.focus_position, 1) # focus unchanged + self.assertEqual(c.focus_position, 1) # focus unchanged c.widget_list = [t1] - self.assertEquals(len(c.contents), 1) - self.assertEquals(c.focus_position, 0) + self.assertEqual(len(c.contents), 1) + self.assertEqual(c.focus_position, 0) c.widget_list.extend([t2, t1]) - self.assertEquals(len(c.contents), 3) - self.assertEquals(c.column_types, [ + self.assertEqual(len(c.contents), 3) + self.assertEqual(c.column_types, [ ('flow', None), ('weight', 1), ('weight', 1)]) c.column_types[:] = [('weight', 2)] - self.assertEquals(len(c.contents), 1) + self.assertEqual(len(c.contents), 1) def test_list_box(self): lb = urwid.ListBox(urwid.SimpleFocusListWalker([])) - self.assertEquals(lb.focus, None) + self.assertEqual(lb.focus, None) self.assertRaises(IndexError, lambda: getattr(lb, 'focus_position')) self.assertRaises(IndexError, lambda: setattr(lb, 'focus_position', None)) @@ -497,39 +513,39 @@ class CommonContainerTest(unittest.TestCase): t1 = urwid.Text(u'one') t2 = urwid.Text(u'two') lb = urwid.ListBox(urwid.SimpleListWalker([t1, t2])) - self.assertEquals(lb.focus, t1) - self.assertEquals(lb.focus_position, 0) + self.assertEqual(lb.focus, t1) + self.assertEqual(lb.focus_position, 0) lb.focus_position = 1 - self.assertEquals(lb.focus, t2) - self.assertEquals(lb.focus_position, 1) + self.assertEqual(lb.focus, t2) + self.assertEqual(lb.focus_position, 1) lb.focus_position = 0 self.assertRaises(IndexError, lambda: setattr(lb, 'focus_position', -1)) self.assertRaises(IndexError, lambda: setattr(lb, 'focus_position', 2)) def test_grid_flow(self): gf = urwid.GridFlow([], 5, 1, 0, 'left') - self.assertEquals(gf.focus, None) - self.assertEquals(gf.contents, []) + self.assertEqual(gf.focus, None) + self.assertEqual(gf.contents, []) self.assertRaises(IndexError, lambda: getattr(gf, 'focus_position')) self.assertRaises(IndexError, lambda: setattr(gf, 'focus_position', None)) self.assertRaises(IndexError, lambda: setattr(gf, 'focus_position', 0)) - self.assertEquals(gf.options(), ('given', 5)) - self.assertEquals(gf.options(width_amount=9), ('given', 9)) + self.assertEqual(gf.options(), ('given', 5)) + self.assertEqual(gf.options(width_amount=9), ('given', 9)) self.assertRaises(urwid.GridFlowError, lambda: gf.options( 'pack', None)) t1 = urwid.Text(u'one') t2 = urwid.Text(u'two') gf = urwid.GridFlow([t1, t2], 5, 1, 0, 'left') - self.assertEquals(gf.focus, t1) - self.assertEquals(gf.focus_position, 0) - self.assertEquals(gf.contents, [(t1, ('given', 5)), (t2, ('given', 5))]) + self.assertEqual(gf.focus, t1) + self.assertEqual(gf.focus_position, 0) + self.assertEqual(gf.contents, [(t1, ('given', 5)), (t2, ('given', 5))]) gf.focus_position = 1 - self.assertEquals(gf.focus, t2) - self.assertEquals(gf.focus_position, 1) + self.assertEqual(gf.focus, t2) + self.assertEqual(gf.focus_position, 1) gf.contents.insert(0, (t2, ('given', 5))) - self.assertEquals(gf.focus_position, 2) + self.assertEqual(gf.focus_position, 2) self.assertRaises(urwid.GridFlowError, lambda: gf.contents.append(())) self.assertRaises(urwid.GridFlowError, lambda: gf.contents.insert(1, (t1, ('pack', None)))) @@ -541,7 +557,7 @@ class CommonContainerTest(unittest.TestCase): self.assertRaises(IndexError, lambda: gf.set_focus(-1)) self.assertRaises(IndexError, lambda: gf.set_focus(3)) gf.set_focus(t1) - self.assertEquals(gf.focus_position, 1) + self.assertEqual(gf.focus_position, 1) self.assertRaises(ValueError, lambda: gf.set_focus('nonexistant')) def test_overlay(self): @@ -549,15 +565,15 @@ class CommonContainerTest(unittest.TestCase): s2 = urwid.SolidFill(u'2') o = urwid.Overlay(s1, s2, 'center', ('relative', 50), 'middle', ('relative', 50)) - self.assertEquals(o.focus, s1) - self.assertEquals(o.focus_position, 1) + self.assertEqual(o.focus, s1) + self.assertEqual(o.focus_position, 1) self.assertRaises(IndexError, lambda: setattr(o, 'focus_position', None)) self.assertRaises(IndexError, lambda: setattr(o, 'focus_position', 2)) - self.assertEquals(o.contents[0], (s2, + self.assertEqual(o.contents[0], (s2, urwid.Overlay._DEFAULT_BOTTOM_OPTIONS)) - self.assertEquals(o.contents[1], (s1, ( + self.assertEqual(o.contents[1], (s1, ( 'center', None, 'relative', 50, None, 0, 0, 'middle', None, 'relative', 50, None, 0, 0))) @@ -565,8 +581,8 @@ class CommonContainerTest(unittest.TestCase): s1 = urwid.SolidFill(u'1') f = urwid.Frame(s1) - self.assertEquals(f.focus, s1) - self.assertEquals(f.focus_position, 'body') + self.assertEqual(f.focus, s1) + self.assertEqual(f.focus_position, 'body') self.assertRaises(IndexError, lambda: setattr(f, 'focus_position', None)) self.assertRaises(IndexError, lambda: setattr(f, 'focus_position', @@ -576,19 +592,19 @@ class CommonContainerTest(unittest.TestCase): t2 = urwid.Text(u'two') t3 = urwid.Text(u'three') f = urwid.Frame(s1, t1, t2, 'header') - self.assertEquals(f.focus, t1) - self.assertEquals(f.focus_position, 'header') + self.assertEqual(f.focus, t1) + self.assertEqual(f.focus_position, 'header') f.focus_position = 'footer' - self.assertEquals(f.focus, t2) - self.assertEquals(f.focus_position, 'footer') + self.assertEqual(f.focus, t2) + self.assertEqual(f.focus_position, 'footer') self.assertRaises(IndexError, lambda: setattr(f, 'focus_position', -1)) self.assertRaises(IndexError, lambda: setattr(f, 'focus_position', 2)) del f.contents['footer'] - self.assertEquals(f.footer, None) - self.assertEquals(f.focus_position, 'body') + self.assertEqual(f.footer, None) + self.assertEqual(f.focus_position, 'body') f.contents.update(footer=(t3, None), header=(t2, None)) - self.assertEquals(f.header, t2) - self.assertEquals(f.footer, t3) + self.assertEqual(f.header, t2) + self.assertEqual(f.footer, t3) def set1(): f.contents['body'] = t1 self.assertRaises(urwid.FrameError, set1) @@ -611,12 +627,12 @@ class CommonContainerTest(unittest.TestCase): g.focus_position = 4 f = urwid.Frame(lb, header=t, footer=g) - self.assertEquals(f.get_focus_path(), ['body', 1, 2, 1]) + self.assertEqual(f.get_focus_path(), ['body', 1, 2, 1]) f.set_focus_path(['footer']) # same as f.focus_position = 'footer' - self.assertEquals(f.get_focus_path(), ['footer', 4]) + self.assertEqual(f.get_focus_path(), ['footer', 4]) f.set_focus_path(['body', 1, 2, 2]) - self.assertEquals(f.get_focus_path(), ['body', 1, 2, 2]) + self.assertEqual(f.get_focus_path(), ['body', 1, 2, 2]) self.assertRaises(IndexError, lambda: f.set_focus_path([0, 1, 2])) self.assertRaises(IndexError, lambda: f.set_focus_path(['body', 2, 2])) f.set_focus_path(['body', 2]) # focus the overlay - self.assertEquals(f.get_focus_path(), ['body', 2, 1]) + self.assertEqual(f.get_focus_path(), ['body', 2, 1]) diff --git a/urwid/tests/test_decoration.py b/urwid/tests/test_decoration.py index 5add164..8ab1acd 100644 --- a/urwid/tests/test_decoration.py +++ b/urwid/tests/test_decoration.py @@ -78,16 +78,16 @@ class PaddingTest(unittest.TestCase): # though, so this might not get fixed for a while p = urwid.Padding(urwid.Edit(u'',u''), width='pack', left=4) - self.assertEquals(p.render((10,), True).cursor, None) - self.assertEquals(p.get_cursor_coords((10,)), None) - self.assertEquals(p.render((4,), True).cursor, None) - self.assertEquals(p.get_cursor_coords((4,)), None) + self.assertEqual(p.render((10,), True).cursor, None) + self.assertEqual(p.get_cursor_coords((10,)), None) + self.assertEqual(p.render((4,), True).cursor, None) + self.assertEqual(p.get_cursor_coords((4,)), None) p = urwid.Padding(urwid.Edit(u'',u''), width=('relative', 100), left=4) - self.assertEquals(p.render((10,), True).cursor, (4, 0)) - self.assertEquals(p.get_cursor_coords((10,)), (4, 0)) - self.assertEquals(p.render((4,), True).cursor, None) - self.assertEquals(p.get_cursor_coords((4,)), None) + self.assertEqual(p.render((10,), True).cursor, (4, 0)) + self.assertEqual(p.get_cursor_coords((10,)), (4, 0)) + self.assertEqual(p.render((4,), True).cursor, None) + self.assertEqual(p.get_cursor_coords((4,)), None) class FillerTest(unittest.TestCase): diff --git a/urwid/tests/test_doctests.py b/urwid/tests/test_doctests.py new file mode 100644 index 0000000..1720a48 --- /dev/null +++ b/urwid/tests/test_doctests.py @@ -0,0 +1,22 @@ +import unittest +import doctest + +import urwid + +def load_tests(loader, tests, ignore): + module_doctests = [ + urwid.widget, + urwid.wimp, + urwid.decoration, + urwid.display_common, + urwid.main_loop, + urwid.monitored_list, + urwid.raw_display, + 'urwid.split_repr', # override function with same name + urwid.util, + urwid.signals, + ] + for m in module_doctests: + tests.addTests(doctest.DocTestSuite(m, + optionflags=doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL)) + return tests diff --git a/urwid/tests/test_event_loops.py b/urwid/tests/test_event_loops.py new file mode 100644 index 0000000..0793602 --- /dev/null +++ b/urwid/tests/test_event_loops.py @@ -0,0 +1,131 @@ +import os +import unittest +import platform + +import urwid +from urwid.compat import PYTHON3 + + +class EventLoopTestMixin(object): + def test_event_loop(self): + rd, wr = os.pipe() + evl = self.evl + out = [] + def step1(): + out.append("writing") + os.write(wr, "hi".encode('ascii')) + def step2(): + out.append(os.read(rd, 2).decode('ascii')) + raise urwid.ExitMainLoop + handle = evl.alarm(0, step1) + handle = evl.watch_file(rd, step2) + evl.run() + self.assertEqual(out, ["writing", "hi"]) + + def test_remove_alarm(self): + evl = self.evl + handle = evl.alarm(50, lambda: None) + self.assertTrue(evl.remove_alarm(handle)) + self.assertFalse(evl.remove_alarm(handle)) + + def test_remove_watch_file(self): + evl = self.evl + handle = evl.watch_file(5, lambda: None) + self.assertTrue(evl.remove_watch_file(handle)) + self.assertFalse(evl.remove_watch_file(handle)) + + def test_run(self): + evl = self.evl + out = [] + rd, wr = os.pipe() + self.assertEqual(os.write(wr, "data".encode('ascii')), 4) + def say_hello(): + out.append("hello") + def say_waiting(): + out.append("waiting") + def exit_clean(): + out.append("clean exit") + raise urwid.ExitMainLoop + def exit_error(): + 1/0 + handle = evl.alarm(0.01, exit_clean) + handle = evl.alarm(0.005, say_hello) + self.assertEqual(evl.enter_idle(say_waiting), 1) + evl.run() + self.assertTrue("hello" in out, out) + self.assertTrue("clean exit"in out, out) + handle = evl.watch_file(rd, exit_clean) + del out[:] + evl.run() + self.assertEqual(out, ["clean exit"]) + self.assertTrue(evl.remove_watch_file(handle)) + handle = evl.alarm(0, exit_error) + self.assertRaises(ZeroDivisionError, evl.run) + handle = evl.watch_file(rd, exit_error) + self.assertRaises(ZeroDivisionError, evl.run) + + +class SelectEventLoopTest(unittest.TestCase, EventLoopTestMixin): + def setUp(self): + self.evl = urwid.SelectEventLoop() + + +try: + import gi.repository +except ImportError: + pass +else: + class GLibEventLoopTest(unittest.TestCase, EventLoopTestMixin): + def setUp(self): + self.evl = urwid.GLibEventLoop() + + +try: + import tornado +except ImportError: + pass +else: + class TornadoEventLoopTest(unittest.TestCase, EventLoopTestMixin): + def setUp(self): + from tornado.ioloop import IOLoop + self.evl = urwid.TornadoEventLoop(IOLoop()) + + +try: + import twisted +except ImportError: + pass +else: + class TwistedEventLoopTest(unittest.TestCase, EventLoopTestMixin): + def setUp(self): + self.evl = urwid.TwistedEventLoop() + + # can't restart twisted reactor, so use shortened tests + def test_event_loop(self): + pass + + def test_run(self): + evl = self.evl + out = [] + rd, wr = os.pipe() + self.assertEqual(os.write(wr, "data".encode('ascii')), 4) + def step2(): + out.append(os.read(rd, 2).decode('ascii')) + def say_hello(): + out.append("hello") + def say_waiting(): + out.append("waiting") + def exit_clean(): + out.append("clean exit") + raise urwid.ExitMainLoop + def exit_error(): + 1/0 + handle = evl.watch_file(rd, step2) + handle = evl.alarm(0.01, exit_clean) + handle = evl.alarm(0.005, say_hello) + self.assertEqual(evl.enter_idle(say_waiting), 1) + evl.run() + self.assertTrue("da" in out, out) + self.assertTrue("ta" in out, out) + self.assertTrue("hello" in out, out) + self.assertTrue("clean exit" in out, out) diff --git a/urwid/tests/test_listbox.py b/urwid/tests/test_listbox.py index d796dc6..8afeb97 100644 --- a/urwid/tests/test_listbox.py +++ b/urwid/tests/test_listbox.py @@ -770,27 +770,27 @@ class ZeroHeightContentsTest(unittest.TestCase): lb = urwid.ListBox(urwid.SimpleListWalker( [urwid.Text(u'above'), urwid.Pile([])])) lb.keypress((40,10), 'page down') - self.assertEquals(lb.get_focus()[1], 0) + self.assertEqual(lb.get_focus()[1], 0) lb.keypress((40,10), 'page down') # second one caused ListBox failure - self.assertEquals(lb.get_focus()[1], 0) + self.assertEqual(lb.get_focus()[1], 0) def test_listbox_text_pile_page_up(self): lb = urwid.ListBox(urwid.SimpleListWalker( [urwid.Pile([]), urwid.Text(u'below')])) lb.set_focus(1) lb.keypress((40,10), 'page up') - self.assertEquals(lb.get_focus()[1], 1) + self.assertEqual(lb.get_focus()[1], 1) lb.keypress((40,10), 'page up') # second one caused pile failure - self.assertEquals(lb.get_focus()[1], 1) + self.assertEqual(lb.get_focus()[1], 1) def test_listbox_text_pile_down(self): sp = urwid.Pile([]) sp.selectable = lambda: True # abuse our Pile lb = urwid.ListBox(urwid.SimpleListWalker([urwid.Text(u'above'), sp])) lb.keypress((40,10), 'down') - self.assertEquals(lb.get_focus()[1], 0) + self.assertEqual(lb.get_focus()[1], 0) lb.keypress((40,10), 'down') - self.assertEquals(lb.get_focus()[1], 0) + self.assertEqual(lb.get_focus()[1], 0) def test_listbox_text_pile_up(self): sp = urwid.Pile([]) @@ -798,7 +798,7 @@ class ZeroHeightContentsTest(unittest.TestCase): lb = urwid.ListBox(urwid.SimpleListWalker([sp, urwid.Text(u'below')])) lb.set_focus(1) lb.keypress((40,10), 'up') - self.assertEquals(lb.get_focus()[1], 1) + self.assertEqual(lb.get_focus()[1], 1) lb.keypress((40,10), 'up') - self.assertEquals(lb.get_focus()[1], 1) + self.assertEqual(lb.get_focus()[1], 1) diff --git a/urwid/tests/test_str_util.py b/urwid/tests/test_str_util.py index 0a0b61c..f7ad1d9 100644 --- a/urwid/tests/test_str_util.py +++ b/urwid/tests/test_str_util.py @@ -1,7 +1,7 @@ import unittest from urwid.compat import B -from urwid import str_util +from urwid.escape import str_util class DecodeOneTest(unittest.TestCase): diff --git a/urwid/tests/test_widget.py b/urwid/tests/test_widget.py index 1f6dfbe..3ae9a93 100644 --- a/urwid/tests/test_widget.py +++ b/urwid/tests/test_widget.py @@ -32,6 +32,12 @@ class TextTest(unittest.TestCase): got = self.t.render((18,))._text assert got == expected, "got: %r expected: %r" % (got, expected) + def test5_encode_error(self): + urwid.set_encoding("ascii") + expected = [B("? ")] + got = urwid.Text(u'û').render((3,))._text + assert got == expected, "got: %r expected: %r" % (got, expected) + class EditTest(unittest.TestCase): def setUp(self): @@ -86,9 +92,9 @@ class EditTest(unittest.TestCase): urwid.set_encoding("utf-8") self.t1.set_edit_text('') self.t1.keypress((12,), u'û') - self.assertEquals(self.t1.edit_text, u'û'.encode('utf-8')) + self.assertEqual(self.t1.edit_text, u'û'.encode('utf-8')) self.t4.keypress((12,), u'û') - self.assertEquals(self.t4.edit_text, u'û') + self.assertEqual(self.t4.edit_text, u'û') class EditRenderTest(unittest.TestCase): diff --git a/urwid/text_layout.py b/urwid/text_layout.py index 4a76cc8..302c504 100644 --- a/urwid/text_layout.py +++ b/urwid/text_layout.py @@ -125,7 +125,7 @@ class StandardTextLayout(TextLayout): width - number of available screen columns wrap - wrapping mode used - Returns a layout structure without aligmnent applied. + Returns a layout structure without alignment applied. """ nl, nl_o, sp_o = "\n", "\n", " " if PYTHON3 and isinstance(text, bytes): @@ -320,7 +320,7 @@ def line_width( segs ): Return the screen column width of one line of a text layout structure. This function ignores any existing shift applied to the line, - represended by an (amount, None) tuple at the start of the line. + represented by an (amount, None) tuple at the start of the line. """ sc = 0 seglist = segs @@ -353,7 +353,7 @@ def shift_line( segs, amount ): def trim_line( segs, text, start, end ): """ Return a trimmed line of a text layout structure. - text -- text to which this layout structre applies + text -- text to which this layout structure applies start -- starting screen column end -- ending screen column """ diff --git a/urwid/treetools.py b/urwid/treetools.py index 2f65e49..26f0914 100644 --- a/urwid/treetools.py +++ b/urwid/treetools.py @@ -69,7 +69,7 @@ class TreeWidget(urwid.WidgetWrap): def update_expanded_icon(self): """Update display widget text for parent widgets""" - # icon is first element in colums indented widget + # icon is first element in columns indented widget self._w.base_widget.widget_list[0] = [ self.unexpanded_icon, self.expanded_icon][self.expanded] diff --git a/urwid/util.py b/urwid/util.py index e7acdd3..12becca 100644 --- a/urwid/util.py +++ b/urwid/util.py @@ -23,6 +23,8 @@ from urwid import escape from urwid.compat import bytes +import codecs + str_util = escape.str_util # bring str_util functions into our namespace @@ -115,10 +117,10 @@ def apply_target_encoding( s ): for c, alt in zip(escape.DEC_SPECIAL_CHARS, escape.ALT_DEC_SPECIAL_CHARS): s = s.replace( c, escape.SO+alt+escape.SI ) - + if type(s) == unicode: - s = s.replace( escape.SI+escape.SO, u"" ) # remove redundant shifts - s = s.encode( _target_encoding ) + s = s.replace(escape.SI+escape.SO, u"") # remove redundant shifts + s = codecs.encode(s, _target_encoding, 'replace') assert isinstance(s, bytes) SO = escape.SO.encode('ascii') @@ -437,7 +439,7 @@ class MetaSuper(type): def int_scale(val, val_range, out_range): """ Scale val in the range [0, val_range-1] to an integer in the range - [0, out_range-1]. This implementaton uses the "round-half-up" rounding + [0, out_range-1]. This implementation uses the "round-half-up" rounding method. >>> "%x" % int_scale(0x7, 0x10, 0x10000) diff --git a/urwid/version.py b/urwid/version.py index 3d2d3a1..0cfb892 100644 --- a/urwid/version.py +++ b/urwid/version.py @@ -1,5 +1,5 @@ -VERSION = (1, 2, 0, 'dev') +VERSION = (1, 2, 2, 'dev') __version__ = ''.join(['-.'[type(x) == int]+str(x) for x in VERSION])[1:] diff --git a/urwid/vterm.py b/urwid/vterm.py index c473d65..212094c 100644 --- a/urwid/vterm.py +++ b/urwid/vterm.py @@ -1518,7 +1518,13 @@ class Terminal(Widget): return True def wait_and_feed(self, timeout=1.0): - select.select([self.master], [], [], timeout) + while True: + try: + select.select([self.master], [], [], timeout) + break + except select.error, e: + if e.args[0] != 4: + raise self.feed() def feed(self): diff --git a/urwid/web_display.py b/urwid/web_display.py index f22e104..69aa1ad 100755 --- a/urwid/web_display.py +++ b/urwid/web_display.py @@ -614,7 +614,7 @@ class Screen: background = "light gray" self.palette[name] = (foreground, background, mono) - def set_mouse_tracking(self): + def set_mouse_tracking(self, enable=True): """Not yet implemented""" pass diff --git a/urwid/widget.py b/urwid/widget.py index d1f3c33..44abd2c 100644 --- a/urwid/widget.py +++ b/urwid/widget.py @@ -353,7 +353,7 @@ class Widget(object): :param size: See :meth:`Widget.render` for details. :type size: widget size :param event: Values such as ``'mouse press'``, ``'ctrl mouse press'``, - ``'mouse relase'``, ``'meta mouse release'``, + ``'mouse release'``, ``'meta mouse release'``, ``'mouse drag'``; see :ref:`mouse-input` :type event: mouse event :param button: 1 through 5 for press events, often 0 for release events diff --git a/urwid/wimp.py b/urwid/wimp.py index d8a47c6..03a3613 100755 --- a/urwid/wimp.py +++ b/urwid/wimp.py @@ -208,8 +208,8 @@ class CheckBox(WidgetWrap): >>> def callback_b(cb, state): ... changes.append("B %r" % state) >>> cb = CheckBox('test', False, False) - >>> connect_signal(cb, 'change', callback_a, "user_a") - >>> connect_signal(cb, 'change', callback_b) + >>> key1 = connect_signal(cb, 'change', callback_a, "user_a") + >>> key2 = connect_signal(cb, 'change', callback_b) >>> cb.set_state(True) # both callbacks will be triggered >>> cb.state True @@ -390,7 +390,7 @@ class RadioButton(CheckBox): (False, True, False) >>> def relabel_button(radio_button, new_state): ... radio_button.set_label(u"Think Harder!") - >>> connect_signal(b3, 'change', relabel_button) + >>> key = connect_signal(b3, 'change', relabel_button) >>> b3 <RadioButton selectable flow widget 'Unsure' state=False> >>> b3.set_state(True) # this will trigger the callback @@ -523,7 +523,7 @@ class Button(WidgetWrap): >>> clicked_buttons = [] >>> def handle_click(button): ... clicked_buttons.append(button.label) - >>> connect_signal(b, 'click', handle_click) + >>> key = connect_signal(b, 'click', handle_click) >>> b.keypress(size, 'enter') >>> b.keypress(size, ' ') >>> clicked_buttons # ... = u in Python 2 @@ -543,7 +543,7 @@ class Button(WidgetWrap): >>> clicked_buttons = [] >>> def handle_click(button): ... clicked_buttons.append(button.label) - >>> connect_signal(b, 'click', handle_click) + >>> key = connect_signal(b, 'click', handle_click) >>> b.mouse_event(size, 'mouse press', 1, 4, 0, True) True >>> b.mouse_event(size, 'mouse press', 2, 4, 0, True) # ignored @@ -566,7 +566,7 @@ class PopUpLauncher(delegate_to_widget_mixin('_original_widget'), def create_pop_up(self): """ - Subclass must override this method and have is return a widget + Subclass must override this method and return a widget to be used for the pop-up. This method is called once each time the pop-up is opened. """ |