summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrej Shadura <andrew.shadura@collabora.co.uk>2020-04-22 23:03:11 +0200
committerAndrej Shadura <andrew.shadura@collabora.co.uk>2020-04-22 23:03:11 +0200
commit03cc267d37ed8e111d3fe283c7f0e27db34e1be3 (patch)
tree31f8a96554f590b7d472c884db6ee777c7d40846
parent66d785c993e585707bfcd7b5c6789202be5f60ca (diff)
New upstream version 2.1.0
-rw-r--r--PKG-INFO160
-rw-r--r--README.rst166
-rw-r--r--docs/changelog.rst92
-rw-r--r--docs/conf.py4
-rwxr-xr-xdocs/examples/palette_test.py193
-rwxr-xr-xdocs/examples/tour.py6
-rw-r--r--docs/manual/displayattributes.rst18
-rw-r--r--docs/manual/displaymodules.rst2
-rw-r--r--docs/manual/overview.rst12
-rw-r--r--docs/manual/textlayout.rst9
-rw-r--r--docs/reference/constants.rst8
-rw-r--r--docs/reference/deprecated.rst2
-rw-r--r--docs/reference/widget.rst19
-rw-r--r--docs/tools/templates/indexcontent.html6
-rw-r--r--examples/asyncio_socket_server.py27
-rwxr-xr-xexamples/calc.py4
-rwxr-xr-xexamples/dialog.py6
-rwxr-xr-xexamples/lcd_cf635.py14
-rwxr-xr-xexamples/palette_test.py193
-rwxr-xr-xexamples/terminal.py3
-rwxr-xr-xexamples/tour.py6
-rw-r--r--setup.py5
-rw-r--r--urwid.egg-info/PKG-INFO160
-rw-r--r--urwid.egg-info/SOURCES.txt3
-rw-r--r--urwid/__init__.py8
-rw-r--r--urwid/_async_kw_event_loop.py232
-rw-r--r--urwid/canvas.py10
-rwxr-xr-xurwid/container.py83
-rwxr-xr-xurwid/display_common.py99
-rw-r--r--urwid/escape.py6
-rwxr-xr-xurwid/font.py2
-rwxr-xr-xurwid/graphics.py16
-rw-r--r--urwid/listbox.py68
-rwxr-xr-xurwid/main_loop.py32
-rw-r--r--urwid/numedit.py286
-rw-r--r--urwid/raw_display.py66
-rwxr-xr-xurwid/split_repr.py10
-rw-r--r--urwid/tests/test_canvas.py8
-rw-r--r--urwid/tests/test_container.py4
-rw-r--r--urwid/tests/test_doctests.py2
-rw-r--r--urwid/tests/test_escapes.py79
-rw-r--r--urwid/tests/test_event_loops.py74
-rw-r--r--urwid/tests/test_listbox.py8
-rw-r--r--urwid/tests/test_util.py33
-rw-r--r--urwid/tests/util.py2
-rw-r--r--urwid/text_layout.py31
-rw-r--r--urwid/treetools.py10
-rw-r--r--urwid/util.py11
-rw-r--r--urwid/version.py2
-rw-r--r--urwid/vterm.py21
-rw-r--r--urwid/widget.py20
-rwxr-xr-xurwid/wimp.py21
52 files changed, 1980 insertions, 382 deletions
diff --git a/PKG-INFO b/PKG-INFO
index 47b320d..1b608b3 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,11 @@
Metadata-Version: 1.1
Name: urwid
-Version: 2.0.1
+Version: 2.1.0
Summary: A full-featured console (xterm et al.) user interface library
Home-page: http://urwid.org/
Author: Ian Ward
Author-email: ian@excess.org
License: LGPL
-Description-Content-Type: UNKNOWN
Description:
About
=====
@@ -22,12 +21,27 @@ Description:
- 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
+ - 24-bit (true color), 256 color, and 88 color mode support
+ - Compatible with Python 2.7, 3.4+ and PyPy
Home Page:
http://urwid.org/
+ Installation
+ ============
+
+ To install using pip
+
+ .. code:: bash
+
+ pip install urwid
+
+ Alternatively if you are on Debian or Ubuntu
+
+ .. code:: bash
+
+ apt-get install python-urwid
+
Testing
=======
@@ -43,38 +57,111 @@ Description:
tox -e py36 # Test Python 3.6 only
tox -e py27,py36,pypy # Test Python 2.7, Python 3.6 & pypy
+ Supported Python versions
+ =========================
+
+ - 2.7
+ - 3.4
+ - 3.5
+ - 3.6
+ - 3.7
+ - 3.8
+ - pypy
+
+ Authors
+ =======
+
+ Creator
+ -------
+
+ `wardi <//github.com/wardi>`_
+
+ Maintainers
+ -----------
+
+ `and3rson <//github.com/and3rson>`_,
+ `tonycpsu <//github.com/tonycpsu>`_,
+ `ulidtko <//github.com/ulidtko>`_
+
Contributors
- ============
+ ------------
+
+ `aathan <//github.com/aathan>`_,
+ `abadger <//github.com/abadger>`_,
+ `aglyzov <//github.com/aglyzov>`_,
+ `akosthekiss <//github.com/akosthekiss>`_,
+ `alexozer <//github.com/alexozer>`_,
+ `andersk <//github.com/andersk>`_,
+ `aszlig <//github.com/aszlig>`_,
+ `atsampson <//github.com/atsampson>`_,
+ `BkPHcgQL3V <//github.com/BkPHcgQL3V>`_,
+ `BlindB0 <//github.com/BlindB0>`_,
+ `bukzor <//github.com/bukzor>`_,
+ `eevee <//github.com/eevee>`_,
+ `federicotdn <//github.com/federicotdn>`_,
+ `garrison <//github.com/garrison>`_,
+ `geier <//github.com/geier>`_,
+ `grugq <//github.com/grugq>`_,
+ `hkoof <//github.com/hkoof>`_,
+ `hootnot <//github.com/hootnot>`_,
+ `horazont <//github.com/horazont>`_,
+ `inducer <//github.com/inducer>`_,
+ `ismail-s <//github.com/ismail-s>`_,
+ `italomaia-bk <//github.com/italomaia-bk>`_,
+ `ivanov <//github.com/ivanov>`_,
+ `Julian <//github.com/Julian>`_,
+ `jwilk <//github.com/jwilk>`_,
+ `kajojify <//github.com/kajojify>`_,
+ `Kamik423 <//github.com/Kamik423>`_,
+ `kkrolczyk <//github.com/kkrolczyk>`_,
+ `marienz <//github.com/marienz>`_,
+ `matthijskooijman <//github.com/matthijskooijman>`_,
+ `mbarkhau <//github.com/mbarkhau>`_,
+ `mgiusti <//github.com/mgiusti>`_,
+ `mikemccracken <//github.com/mikemccracken>`_,
+ `nocarryr <//github.com/nocarryr>`_,
+ `ntamas <//github.com/ntamas>`_,
+ `olleolleolle <//github.com/olleolleolle>`_,
+ `pazz <//github.com/pazz>`_,
+ `pniedzwiedzinski <//github.com/pniedzwiedzinski>`_,
+ `raek <//github.com/raek>`_,
+ `richrd <//github.com/richrd>`_,
+ `rndusr <//github.com/rndusr>`_,
+ `robla <//github.com/robla>`_,
+ `rr- <//github.com/rr->`_,
+ `seleem1337 <//github.com/seleem1337>`_,
+ `SenchoPens <//github.com/SenchoPens>`_,
+ `shyal <//github.com/shyal>`_,
+ `sitaktif <//github.com/sitaktif>`_,
+ `tdryer <//github.com/tdryer>`_,
+ `techtonik <//github.com/techtonik>`_,
+ `tu500 <//github.com/tu500>`_,
+ `usrlocalben <//github.com/usrlocalben>`_,
+ `wackywendell <//github.com/wackywendell>`_,
+ `wernight <//github.com/wernight>`_,
+ `westurner <//github.com/westurner>`_,
+ `whospal <//github.com/whospal>`_,
+ `Wilfred <//github.com/Wilfred>`_,
+ `winbornejw <//github.com/winbornejw>`_,
+ `xnox <//github.com/xnox>`_,
+ `yanzixiang <//github.com/yanzixiang>`_
+
+
+ .. |pypi| image:: http://img.shields.io/pypi/v/urwid.svg
+ :alt: current version on PyPi
+ :target: https://pypi.python.org/pypi/urwid
+
+ .. |docs| image:: https://readthedocs.org/projects/urwid/badge/
+ :alt: docs link
+ :target: http://urwid.readthedocs.org/en/latest/
+
+ .. |travis| image:: https://travis-ci.org/urwid/urwid.svg?branch=master
+ :alt: build status
+ :target: https://travis-ci.org/urwid/urwid/
- - `wardi <//github.com/wardi>`_
- - `aszlig <//github.com/aszlig>`_
- - `mgiusti <//github.com/mgiusti>`_
- - `and3rson <//github.com/and3rson>`_
- - `pazz <//github.com/pazz>`_
- - `wackywendell <//github.com/wackywendell>`_
- - `eevee <//github.com/eevee>`_
- - `marienz <//github.com/marienz>`_
- - `rndusr <//github.com/rndusr>`_
- - `matthijskooijman <//github.com/matthijskooijman>`_
- - `Julian <//github.com/Julian>`_
- - `techtonik <//github.com/techtonik>`_
- - `garrison <//github.com/garrison>`_
- - `ivanov <//github.com/ivanov>`_
- - `abadger <//github.com/abadger>`_
- - `aglyzov <//github.com/aglyzov>`_
- - `ismail-s <//github.com/ismail-s>`_
- - `horazont <//github.com/horazont>`_
- - `robla <//github.com/robla>`_
- - `usrlocalben <//github.com/usrlocalben>`_
- - `geier <//github.com/geier>`_
- - `federicotdn <//github.com/federicotdn>`_
- - `jwilk <//github.com/jwilk>`_
- - `rr- <//github.com/rr->`_
- - `tonycpsu <//github.com/tonycpsu>`_
- - `westurner <//github.com/westurner>`_
- - `grugq <//github.com/grugq>`_
- - `inducer <//github.com/inducer>`_
- - `winbornejw <//github.com/winbornejw>`_
+ .. |coveralls| image:: https://coveralls.io/repos/github/urwid/urwid/badge.svg
+ :alt: test coverage
+ :target: https://coveralls.io/github/urwid/urwid
Keywords: curses ui widget scroll listbox user interface text layout console ncurses
Platform: unix-like
@@ -89,12 +176,11 @@ Classifier: Operating System :: MacOS :: MacOS X
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Widget Sets
Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.2
-Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: PyPy
diff --git a/README.rst b/README.rst
index 0b30d1a..fadf876 100644
--- a/README.rst
+++ b/README.rst
@@ -1,14 +1,6 @@
-.. image:: https://travis-ci.org/urwid/urwid.png?branch=master
- :alt: build status
- :target: https://travis-ci.org/urwid/urwid/
-
-.. image:: https://coveralls.io/repos/github/urwid/urwid/badge.svg
- :alt: build coverage
- :target: https://coveralls.io/github/urwid/urwid
-
-`Development version documentation <http://urwid.readthedocs.org/en/latest/>`_
-
-**Urwid is looking for new maintainers, please open an issue here to volunteer!**
+Urwid
+=====
+|pypi| |docs| |travis| |coveralls|
.. content-start
@@ -26,12 +18,27 @@ It includes many features useful for text console application developers includi
- 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
+- 24-bit (true color), 256 color, and 88 color mode support
+- Compatible with Python 2.7, 3.4+ and PyPy
Home Page:
http://urwid.org/
+Installation
+============
+
+To install using pip
+
+.. code:: bash
+
+ pip install urwid
+
+Alternatively if you are on Debian or Ubuntu
+
+.. code:: bash
+
+ apt-get install python-urwid
+
Testing
=======
@@ -47,35 +54,108 @@ To test code in all Python versions:
tox -e py36 # Test Python 3.6 only
tox -e py27,py36,pypy # Test Python 2.7, Python 3.6 & pypy
+Supported Python versions
+=========================
+
+- 2.7
+- 3.4
+- 3.5
+- 3.6
+- 3.7
+- 3.8
+- pypy
+
+Authors
+=======
+
+Creator
+-------
+
+`wardi <//github.com/wardi>`_
+
+Maintainers
+-----------
+
+`and3rson <//github.com/and3rson>`_,
+`tonycpsu <//github.com/tonycpsu>`_,
+`ulidtko <//github.com/ulidtko>`_
+
Contributors
-============
+------------
+
+`aathan <//github.com/aathan>`_,
+`abadger <//github.com/abadger>`_,
+`aglyzov <//github.com/aglyzov>`_,
+`akosthekiss <//github.com/akosthekiss>`_,
+`alexozer <//github.com/alexozer>`_,
+`andersk <//github.com/andersk>`_,
+`aszlig <//github.com/aszlig>`_,
+`atsampson <//github.com/atsampson>`_,
+`BkPHcgQL3V <//github.com/BkPHcgQL3V>`_,
+`BlindB0 <//github.com/BlindB0>`_,
+`bukzor <//github.com/bukzor>`_,
+`eevee <//github.com/eevee>`_,
+`federicotdn <//github.com/federicotdn>`_,
+`garrison <//github.com/garrison>`_,
+`geier <//github.com/geier>`_,
+`grugq <//github.com/grugq>`_,
+`hkoof <//github.com/hkoof>`_,
+`hootnot <//github.com/hootnot>`_,
+`horazont <//github.com/horazont>`_,
+`inducer <//github.com/inducer>`_,
+`ismail-s <//github.com/ismail-s>`_,
+`italomaia-bk <//github.com/italomaia-bk>`_,
+`ivanov <//github.com/ivanov>`_,
+`Julian <//github.com/Julian>`_,
+`jwilk <//github.com/jwilk>`_,
+`kajojify <//github.com/kajojify>`_,
+`Kamik423 <//github.com/Kamik423>`_,
+`kkrolczyk <//github.com/kkrolczyk>`_,
+`marienz <//github.com/marienz>`_,
+`matthijskooijman <//github.com/matthijskooijman>`_,
+`mbarkhau <//github.com/mbarkhau>`_,
+`mgiusti <//github.com/mgiusti>`_,
+`mikemccracken <//github.com/mikemccracken>`_,
+`nocarryr <//github.com/nocarryr>`_,
+`ntamas <//github.com/ntamas>`_,
+`olleolleolle <//github.com/olleolleolle>`_,
+`pazz <//github.com/pazz>`_,
+`pniedzwiedzinski <//github.com/pniedzwiedzinski>`_,
+`raek <//github.com/raek>`_,
+`richrd <//github.com/richrd>`_,
+`rndusr <//github.com/rndusr>`_,
+`robla <//github.com/robla>`_,
+`rr- <//github.com/rr->`_,
+`seleem1337 <//github.com/seleem1337>`_,
+`SenchoPens <//github.com/SenchoPens>`_,
+`shyal <//github.com/shyal>`_,
+`sitaktif <//github.com/sitaktif>`_,
+`tdryer <//github.com/tdryer>`_,
+`techtonik <//github.com/techtonik>`_,
+`tu500 <//github.com/tu500>`_,
+`usrlocalben <//github.com/usrlocalben>`_,
+`wackywendell <//github.com/wackywendell>`_,
+`wernight <//github.com/wernight>`_,
+`westurner <//github.com/westurner>`_,
+`whospal <//github.com/whospal>`_,
+`Wilfred <//github.com/Wilfred>`_,
+`winbornejw <//github.com/winbornejw>`_,
+`xnox <//github.com/xnox>`_,
+`yanzixiang <//github.com/yanzixiang>`_
+
+
+.. |pypi| image:: http://img.shields.io/pypi/v/urwid.svg
+ :alt: current version on PyPi
+ :target: https://pypi.python.org/pypi/urwid
+
+.. |docs| image:: https://readthedocs.org/projects/urwid/badge/
+ :alt: docs link
+ :target: http://urwid.readthedocs.org/en/latest/
+
+.. |travis| image:: https://travis-ci.org/urwid/urwid.svg?branch=master
+ :alt: build status
+ :target: https://travis-ci.org/urwid/urwid/
-- `wardi <//github.com/wardi>`_
-- `aszlig <//github.com/aszlig>`_
-- `mgiusti <//github.com/mgiusti>`_
-- `and3rson <//github.com/and3rson>`_
-- `pazz <//github.com/pazz>`_
-- `wackywendell <//github.com/wackywendell>`_
-- `eevee <//github.com/eevee>`_
-- `marienz <//github.com/marienz>`_
-- `rndusr <//github.com/rndusr>`_
-- `matthijskooijman <//github.com/matthijskooijman>`_
-- `Julian <//github.com/Julian>`_
-- `techtonik <//github.com/techtonik>`_
-- `garrison <//github.com/garrison>`_
-- `ivanov <//github.com/ivanov>`_
-- `abadger <//github.com/abadger>`_
-- `aglyzov <//github.com/aglyzov>`_
-- `ismail-s <//github.com/ismail-s>`_
-- `horazont <//github.com/horazont>`_
-- `robla <//github.com/robla>`_
-- `usrlocalben <//github.com/usrlocalben>`_
-- `geier <//github.com/geier>`_
-- `federicotdn <//github.com/federicotdn>`_
-- `jwilk <//github.com/jwilk>`_
-- `rr- <//github.com/rr->`_
-- `tonycpsu <//github.com/tonycpsu>`_
-- `westurner <//github.com/westurner>`_
-- `grugq <//github.com/grugq>`_
-- `inducer <//github.com/inducer>`_
-- `winbornejw <//github.com/winbornejw>`_
+.. |coveralls| image:: https://coveralls.io/repos/github/urwid/urwid/badge.svg
+ :alt: test coverage
+ :target: https://coveralls.io/github/urwid/urwid
diff --git a/docs/changelog.rst b/docs/changelog.rst
index b83b52f..6a1d90d 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -2,6 +2,96 @@
Changelog
---------
+Urwid 2.1.0
+===========
+
+2019-11-13
+
+ * Add support for Python 3.7 and 3.8, drop support for Python 3.3
+
+ * Add 24-bit (true color) support. (by Tony Cebzanov)
+
+ * Add TrioEventLoop (by Tamas Nepusz)
+
+ * Add support for input encoding in Terminal widget (by Tamas Nepusz)
+
+ * Add ability to specify LineBox title attribute (by Tom Pickering)
+
+ * Add custom checkbox symbol (by Krzysztof Królczyk)
+
+ * Add installation instruction to README (by Patryk Niedźwiedziński)
+
+ * Remove PollingListWalker class (by Heiko Noordhof)
+
+ * Change SelectableIcon default cursor_position to 0. (by Werner Beroux)
+
+ * Extended numerical editing: integers and floats (by hootnot)
+
+ * Re-raise coroutine exceptions in AsyncioEventLoop properly (by nocarryr)
+
+ * Fixed locale issue (by Andrew Dunai)
+
+ * Gate SIGWINCH behind GLib 2.54+ (by Nick Chavez)
+
+ * Remove method Text._calc_line_translation() (by rndusr)
+
+ * Fix colon in HalfBlock5x4Font (by Alex Ozer)
+
+ * Don't use deprecated inspect.getargspec() with python3 (by rndusr)
+
+ * Fix issue "Non-integer division in bargraph when using set_bar_width(1)"
+ (by Carlos Jenkins)
+
+ * Fix misleading indentation in Screen._stop() (by Akos Kiss)
+
+ * Fix crash on click-Esc & Esc-click (by Maxim Ivanov)
+
+ * Use 'TimerHandle.cancelled()' if available (by Mohamed Seleem)
+
+ * Break rather than raising exception on shard calculation bug. (by Tony
+ Cebzanov)
+
+ * Increase _idle_emulation_delay. (by Tony Cebzanov)
+
+ * Fix EOF detection for the Terminal widget on Python 3 (by Tamas Nepusz)
+
+ * Fix the asyncio example, and make the raw Screen work without real files (by
+ Eevee)
+
+ * Unbreak python ./examples/treesample HOME END keys. (by Dimitri John Ledkov)
+
+ * Urwid.util: Fix bug in rle_append_beginning_modify (by BkPHcgQL3V)
+
+ * Fix AttributeError on mouse click (by mbarkhau)
+
+ * Fix ProgressBar smoothing on Python 3.x (by Tamas Nepusz)
+
+ * Fix asyncio event loop test on py3.4 (by Maxim Ivanov)
+
+ * Handle case where MainLoop._topmost_widget does not implement mouse_event (by
+ Rasmus Bondesson)
+
+ * Implement `ellipsis` wrapping mode for StandardTextLayout (by Philip Matura)
+
+ * Fix .pack call in Columns.column_widths (by Philip Matura)
+
+ * Use ._selectable member for Edit widget (by Philip Matura)
+
+ * Fix use of ignore_focus, for widgets inheriting from Text (by Philip Matura)
+
+ * Remove some special handling for TreeListBox (by Philip Matura)
+
+ * Make Columns and Pile selectable when any child widget is (by Philip Matura)
+
+ * Implement get_cursor_coords for Frame widget (by Philip Matura)
+
+ * Fix Frame mouse_event when footer is trimmed (by Philip Matura)
+
+ * Fix Python 3.8 SyntaxWarning: 'str' object is not callable (by Anders Kaseorg)
+
+ * README: Use SVG build status badge (by Olle Jonsson)
+
+
Urwid 2.0.1
===========
@@ -132,6 +222,8 @@ Urwid 1.3.0
* Documentation fixes (by Ismail, Matthew Mosesohn)
+ * SelectableIcon using cursor_position=0 by default instead of 1.
+
Urwid 1.2.2
===========
diff --git a/docs/conf.py b/docs/conf.py
index 66f921d..161a7d3 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -156,7 +156,7 @@ html_static_path = ['tools/static']
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
- 'index': 'indexsidebar.html',
+ 'index': ['indexsidebar.html']
}
# Additional templates that should be rendered to pages, maps page names to
@@ -316,4 +316,4 @@ autoclass_content = "both"
autodoc_member_order = "alphabetical"
-autodoc_default_flags = ["members"]
+autodoc_default_options = {"members": True}
diff --git a/docs/examples/palette_test.py b/docs/examples/palette_test.py
index 0c98623..4fa5d19 100755
--- a/docs/examples/palette_test.py
+++ b/docs/examples/palette_test.py
@@ -21,7 +21,7 @@
"""
Palette test. Shows the available foreground and background settings
-in monochrome, 16 color, 88 color and 256 color modes.
+in monochrome, 16 color, 88 color, 256 color, and 24-bit (true) color modes.
"""
import re
@@ -30,6 +30,170 @@ import sys
import urwid
import urwid.raw_display
+CHART_TRUE="""
+#e50000#e51000#e52000#e53000#e54000#e55000#e56000#e57000#e58000#e59000\
+#e5a000#e5b100#e5c100#e5d100#e5e100#d9e500#c9e500#b9e500#a9e500#99e500\
+#89e500#79e500#68e500#58e500#48e500#38e500#28e500#18e500#08e500#00e507\
+#00e517#00e527#00e538#00e548#00e558#00e568#00e578#00e588#00e598#00e5a8\
+#00e5b8#00e5c8#00e5d8#00e1e5#00d1e5#00c1e5#00b1e5#00a1e5#0091e5#0081e5\
+#0071e5#0061e5#0051e5#0040e5#0030e5#0020e5#0010e5#0000e5#0f00e5#1f00e5\
+#2f00e5#3f00e5#4f00e5#5f00e5#7000e5#8000e5#9000e5#a000e5#b000e5#c000e5\
+#d000e5#e000e5#e500da#e500ca#e500b9#e500a9#e50099#e50089
+#da0000#da0f00#da1e00#da2e00#da3d00#da4c00#da5c00#da6b00#da7a00#da8a00\
+#da9900#daa800#dab800#dac700#dad600#cfda00#c0da00#b0da00#a1da00#92da00\
+#82da00#73da00#64da00#54da00#45da00#35da00#26da00#17da00#07da00#00da07\
+#00da16#00da26#00da35#00da44#00da54#00da63#00da72#00da82#00da91#00daa0\
+#00dab0#00dabf#00dace#00d7da#00c8da#00b8da#00a9da#0099da#008ada#007bda\
+#006bda#005cda#004dda#003dda#002eda#001fda#000fda#0000da#0e00da#1e00da\
+#2d00da#3c00da#4c00da#5b00da#6a00da#7a00da#8900da#9800da#a800da#b700da\
+#c600da#d600da#da00cf#da00c0#da00b1#da00a1#da0092#da0083
+#d00000#d00e00#d01d00#d02b00#d03a00#d04800#d05700#d06600#d07400#d08300\
+#d09100#d0a000#d0af00#d0bd00#d0cc00#c5d000#b6d000#a8d000#99d000#8ad000\
+#7cd000#6dd000#5fd000#50d000#41d000#33d000#24d000#16d000#07d000#00d007\
+#00d015#00d024#00d032#00d041#00d04f#00d05e#00d06d#00d07b#00d08a#00d098\
+#00d0a7#00d0b6#00d0c4#00ccd0#00bed0#00afd0#00a1d0#0092d0#0083d0#0075d0\
+#0066d0#0058d0#0049d0#003ad0#002cd0#001dd0#000fd0#0000d0#0e00d0#1c00d0\
+#2b00d0#3900d0#4800d0#5600d0#6500d0#7400d0#8200d0#9100d0#9f00d0#ae00d0\
+#bd00d0#cb00d0#d000c5#d000b7#d000a8#d00099#d0008b#d0007c
+#c50000#c50d00#c51b00#c52900#c53700#c54500#c55300#c56000#c56e00#c57c00\
+#c58a00#c59800#c5a600#c5b300#c5c100#bbc500#adc500#9fc500#91c500#83c500\
+#75c500#68c500#5ac500#4cc500#3ec500#30c500#22c500#15c500#07c500#00c506\
+#00c514#00c522#00c530#00c53e#00c54b#00c559#00c567#00c575#00c583#00c591\
+#00c59e#00c5ac#00c5ba#00c2c5#00b4c5#00a6c5#0098c5#008ac5#007dc5#006fc5\
+#0061c5#0053c5#0045c5#0037c5#002ac5#001cc5#000ec5#0000c5#0d00c5#1b00c5\
+#2800c5#3600c5#4400c5#5200c5#6000c5#6e00c5#7c00c5#8900c5#9700c5#a500c5\
+#b300c5#c100c5#c500bb#c500ad#c5009f#c50092#c50084#c50076
+#ba0000#ba0d00#ba1a00#ba2700#ba3400#ba4100#ba4e00#ba5b00#ba6800#ba7500\
+#ba8200#ba8f00#ba9c00#baaa00#bab700#b0ba00#a3ba00#96ba00#89ba00#7cba00\
+#6fba00#62ba00#55ba00#48ba00#3bba00#2eba00#20ba00#13ba00#06ba00#00ba06\
+#00ba13#00ba20#00ba2d#00ba3a#00ba47#00ba54#00ba61#00ba6e#00ba7c#00ba89\
+#00ba96#00baa3#00bab0#00b7ba#00aaba#009dba#0090ba#0083ba#0076ba#0069ba\
+#005cba#004eba#0041ba#0034ba#0027ba#001aba#000dba#0000ba#0c00ba#1900ba\
+#2600ba#3300ba#4000ba#4e00ba#5b00ba#6800ba#7500ba#8200ba#8f00ba#9c00ba\
+#a900ba#b600ba#ba00b1#ba00a4#ba0097#ba008a#ba007d#ba006f
+#af0000#af0c00#af1800#af2400#af3100#af3d00#af4900#af5600#af6200#af6e00\
+#af7b00#af8700#af9300#afa000#afac00#a6af00#9aaf00#8eaf00#81af00#75af00\
+#69af00#5caf00#50af00#44af00#37af00#2baf00#1faf00#12af00#06af00#00af05\
+#00af12#00af1e#00af2a#00af37#00af43#00af4f#00af5c#00af68#00af74#00af81\
+#00af8d#00af99#00afa6#00adaf#00a0af#0094af#0088af#007baf#006faf#0063af\
+#0056af#004aaf#003eaf#0031af#0025af#0019af#000caf#0000af#0b00af#1800af\
+#2400af#3000af#3d00af#4900af#5500af#6200af#6e00af#7a00af#8700af#9300af\
+#9f00af#ac00af#af00a7#af009a#af008e#af0082#af0075#af0069
+#a50000#a50b00#a51700#a52200#a52e00#a53900#a54500#a55100#a55c00#a56800\
+#a57300#a57f00#a58a00#a59600#a5a200#9ca500#90a500#85a500#79a500#6ea500\
+#62a500#57a500#4ba500#3fa500#34a500#28a500#1da500#11a500#06a500#00a505\
+#00a511#00a51c#00a528#00a533#00a53f#00a54b#00a556#00a562#00a56d#00a579\
+#00a584#00a590#00a59c#00a2a5#0096a5#008ba5#007fa5#0074a5#0068a5#005da5\
+#0051a5#0045a5#003aa5#002ea5#0023a5#0017a5#000ca5#0000a5#0b00a5#1600a5\
+#2200a5#2d00a5#3900a5#4500a5#5000a5#5c00a5#6700a5#7300a5#7e00a5#8a00a5\
+#9600a5#a100a5#a5009c#a50091#a50085#a5007a#a5006e#a50063
+#9a0000#9a0a00#9a1500#9a2000#9a2b00#9a3600#9a4000#9a4b00#9a5600#9a6100\
+#9a6c00#9a7700#9a8100#9a8c00#9a9700#929a00#879a00#7c9a00#719a00#679a00\
+#5c9a00#519a00#469a00#3b9a00#309a00#269a00#1b9a00#109a00#059a00#009a05\
+#009a10#009a1a#009a25#009a30#009a3b#009a46#009a50#009a5b#009a66#009a71\
+#009a7c#009a87#009a91#00979a#008d9a#00829a#00779a#006c9a#00619a#00569a\
+#004c9a#00419a#00369a#002b9a#00209a#00169a#000b9a#00009a#0a009a#15009a\
+#20009a#2a009a#35009a#40009a#4b009a#56009a#61009a#6b009a#76009a#81009a\
+#8c009a#97009a#9a0092#9a0087#9a007d#9a0072#9a0067#9a005c
+#8f0000#8f0a00#8f1400#8f1e00#8f2800#8f3200#8f3c00#8f4600#8f5000#8f5a00\
+#8f6400#8f6e00#8f7800#8f8200#8f8c00#888f00#7e8f00#748f00#698f00#5f8f00\
+#558f00#4b8f00#418f00#378f00#2d8f00#238f00#198f00#0f8f00#058f00#008f04\
+#008f0e#008f18#008f23#008f2d#008f37#008f41#008f4b#008f55#008f5f#008f69\
+#008f73#008f7d#008f87#008d8f#00838f#00798f#006f8f#00658f#005b8f#00508f\
+#00468f#003c8f#00328f#00288f#001e8f#00148f#000a8f#00008f#09008f#13008f\
+#1d008f#27008f#31008f#3c008f#46008f#50008f#5a008f#64008f#6e008f#78008f\
+#82008f#8c008f#8f0088#8f007e#8f0074#8f006a#8f0060#8f0056
+#840000#840900#841200#841b00#842500#842e00#843700#844100#844a00#845300\
+#845d00#846600#846f00#847900#848200#7d8400#748400#6b8400#628400#588400\
+#4f8400#468400#3c8400#338400#2a8400#208400#178400#0e8400#048400#008404\
+#00840d#008417#008420#008429#008433#00843c#008445#00844f#008458#008461\
+#00846a#008474#00847d#008284#007984#007084#006684#005d84#005484#004a84\
+#004184#003884#002e84#002584#001c84#001284#000984#000084#080084#120084\
+#1b0084#240084#2e0084#370084#400084#4a0084#530084#5c0084#660084#6f0084\
+#780084#820084#84007e#840074#84006b#840062#840059#84004f
+#7a0000#7a0800#7a1100#7a1900#7a2200#7a2a00#7a3300#7a3b00#7a4400#7a4d00\
+#7a5500#7a5e00#7a6600#7a6f00#7a7700#737a00#6b7a00#627a00#5a7a00#517a00\
+#487a00#407a00#377a00#2f7a00#267a00#1e7a00#157a00#0d7a00#047a00#007a04\
+#007a0c#007a15#007a1d#007a26#007a2e#007a37#007a40#007a48#007a51#007a59\
+#007a62#007a6a#007a73#00787a#006f7a#00677a#005e7a#00557a#004d7a#00447a\
+#003c7a#00337a#002b7a#00227a#001a7a#00117a#00087a#00007a#08007a#10007a\
+#19007a#21007a#2a007a#33007a#3b007a#44007a#4c007a#55007a#5d007a#66007a\
+#6f007a#77007a#7a0074#7a006b#7a0062#7a005a#7a0051#7a0049
+#6f0000#6f0700#6f0f00#6f1700#6f1f00#6f2700#6f2e00#6f3600#6f3e00#6f4600\
+#6f4e00#6f5500#6f5d00#6f6500#6f6d00#696f00#616f00#596f00#526f00#4a6f00\
+#426f00#3a6f00#326f00#2b6f00#236f00#1b6f00#136f00#0b6f00#046f00#006f03\
+#006f0b#006f13#006f1b#006f23#006f2a#006f32#006f3a#006f42#006f4a#006f51\
+#006f59#006f61#006f69#006d6f#00656f#005e6f#00566f#004e6f#00466f#003e6f\
+#00366f#002f6f#00276f#001f6f#00176f#000f6f#00086f#00006f#07006f#0f006f\
+#17006f#1e006f#26006f#2e006f#36006f#3e006f#46006f#4d006f#55006f#5d006f\
+#65006f#6d006f#6f0069#6f0062#6f005a#6f0052#6f004a#6f0042
+#640000#640700#640e00#641500#641c00#642300#642a00#643100#643800#643f00\
+#644600#644d00#645400#645b00#646200#5f6400#586400#516400#4a6400#436400\
+#3c6400#356400#2e6400#266400#1f6400#186400#116400#0a6400#036400#006403\
+#00640a#006411#006418#00641f#006426#00642d#006434#00643b#006442#006449\
+#006451#006458#00645f#006364#005c64#005464#004d64#004664#003f64#003864\
+#003164#002a64#002364#001c64#001564#000e64#000764#000064#060064#0d0064\
+#140064#1b0064#230064#2a0064#310064#380064#3f0064#460064#4d0064#540064\
+#5b0064#620064#64005f#640058#640051#64004a#640043#64003c
+#590000#590600#590c00#591200#591900#591f00#592500#592c00#593200#593800\
+#593f00#594500#594b00#595100#595800#555900#4e5900#485900#425900#3c5900\
+#355900#2f5900#295900#225900#1c5900#165900#0f5900#095900#035900#005903\
+#005909#00590f#005915#00591c#005922#005928#00592f#005935#00593b#005942\
+#005948#00594e#005955#005859#005259#004b59#004559#003f59#003859#003259\
+#002c59#002659#001f59#001959#001359#000c59#000659#000059#060059#0c0059\
+#120059#180059#1f0059#250059#2b0059#320059#380059#3e0059#450059#4b0059\
+#510059#580059#590055#59004f#590048#590042#59003c#590035
+#4f0000#4f0500#4f0b00#4f1000#4f1600#4f1b00#4f2100#4f2600#4f2c00#4f3100\
+#4f3700#4f3d00#4f4200#4f4800#4f4d00#4b4f00#454f00#3f4f00#3a4f00#344f00\
+#2f4f00#294f00#244f00#1e4f00#194f00#134f00#0d4f00#084f00#024f00#004f02\
+#004f08#004f0d#004f13#004f18#004f1e#004f23#004f29#004f2f#004f34#004f3a\
+#004f3f#004f45#004f4a#004d4f#00484f#00424f#003d4f#00374f#00324f#002c4f\
+#00274f#00214f#001b4f#00164f#00104f#000b4f#00054f#00004f#05004f#0a004f\
+#10004f#16004f#1b004f#21004f#26004f#2c004f#31004f#37004f#3c004f#42004f\
+#47004f#4d004f#4f004b#4f0045#4f0040#4f003a#4f0035#4f002f
+#440000#440400#440900#440e00#441300#441800#441c00#442100#442600#442b00\
+#443000#443400#443900#443e00#444300#404400#3c4400#374400#324400#2d4400\
+#284400#244400#1f4400#1a4400#154400#104400#0c4400#074400#024400#004402\
+#004407#00440b#004410#004415#00441a#00441f#004423#004428#00442d#004432\
+#004437#00443b#004440#004344#003e44#003944#003444#003044#002b44#002644\
+#002144#001c44#001844#001344#000e44#000944#000444#000044#040044#090044\
+#0e0044#130044#170044#1c0044#210044#260044#2b0044#2f0044#340044#390044\
+#3e0044#430044#440041#44003c#440037#440032#44002d#440029
+#390000#390400#390800#390c00#391000#391400#391800#391c00#392000#392400\
+#392800#392c00#393000#393400#393800#363900#323900#2e3900#2a3900#263900\
+#223900#1e3900#1a3900#163900#123900#0e3900#0a3900#063900#023900#003901\
+#003905#00390a#00390e#003912#003916#00391a#00391e#003922#003926#00392a\
+#00392e#003932#003936#003839#003439#003039#002c39#002839#002439#002039\
+#001c39#001839#001439#001039#000c39#000839#000439#000039#030039#070039\
+#0b0039#100039#140039#180039#1c0039#200039#240039#280039#2c0039#300039\
+#340039#380039#390036#390032#39002e#39002a#390026#390022
+#2e0000#2e0300#2e0600#2e0900#2e0d00#2e1000#2e1300#2e1700#2e1a00#2e1d00\
+#2e2000#2e2400#2e2700#2e2a00#2e2e00#2c2e00#292e00#252e00#222e00#1f2e00\
+#1c2e00#182e00#152e00#122e00#0e2e00#0b2e00#082e00#052e00#012e00#002e01\
+#002e04#002e08#002e0b#002e0e#002e12#002e15#002e18#002e1b#002e1f#002e22\
+#002e25#002e29#002e2c#002e2e#002a2e#00272e#00242e#00212e#001d2e#001a2e\
+#00172e#00132e#00102e#000d2e#000a2e#00062e#00032e#00002e#03002e#06002e\
+#09002e#0d002e#10002e#13002e#16002e#1a002e#1d002e#20002e#24002e#27002e\
+#2a002e#2d002e#2e002c#2e0029#2e0026#2e0022#2e001f#2e001c
+#240000#240200#240500#240700#240a00#240c00#240f00#241100#241400#241600\
+#241900#241b00#241e00#242100#242300#222400#1f2400#1d2400#1a2400#182400\
+#152400#132400#102400#0e2400#0b2400#082400#062400#032400#012400#002401\
+#002403#002406#002408#00240b#00240d#002410#002413#002415#002418#00241a\
+#00241d#00241f#002422#002324#002124#001e24#001c24#001924#001624#001424\
+#001124#000f24#000c24#000a24#000724#000524#000224#000024#020024#040024\
+#070024#0a0024#0c0024#0f0024#110024#140024#160024#190024#1b0024#1e0024\
+#200024#230024#240022#24001f#24001d#24001a#240018#240015
+#190000#190100#190300#190500#190700#190800#190a00#190c00#190e00#191000\
+#191100#191300#191500#191700#191900#181900#161900#141900#121900#111900\
+#0f1900#0d1900#0b1900#091900#081900#061900#041900#021900#001900#001900\
+#001902#001904#001906#001908#001909#00190b#00190d#00190f#001910#001912\
+#001914#001916#001918#001919#001719#001519#001319#001119#001019#000e19\
+#000c19#000a19#000919#000719#000519#000319#000119#000019#010019#030019\
+#050019#070019#080019#0a0019#0c0019#0e0019#100019#110019#130019#150019\
+#170019#180019#190018#190016#190014#190012#190011#19000f
+"""
+# raise Exception(CHART_TRUE)
+
CHART_256 = """
brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_
yellow_ light_red light_magenta light_blue light_cyan light_green
@@ -83,10 +247,14 @@ yellow_ light_red light_magenta light_blue light_cyan light_green
black_______ dark_gray___ light_gray__ white_______
"""
-ATTR_RE = re.compile("(?P<whitespace>[ \n]*)(?P<entry>[^ \n]+)")
+ATTR_RE = re.compile("(?P<whitespace>[ \n]*)(?P<entry>(?:#[0-9A-Fa-f]{6})|(?:#[0-9A-Fa-f]{3})|(?:[^ \n]+))")
+LONG_ATTR = 7
SHORT_ATTR = 4 # length of short high-colour descriptions which may
# be packed one after the next
+
+
+
def parse_chart(chart, convert):
"""
Convert string chart into text markup with the correct attributes.
@@ -102,20 +270,28 @@ def parse_chart(chart, convert):
entry = match.group('entry')
entry = entry.replace("_", " ")
while entry:
+ if chart == CHART_TRUE and len(entry) == LONG_ATTR:
+ attrtext = convert(entry[:LONG_ATTR])
+ elen = LONG_ATTR
+ else:
+ elen = SHORT_ATTR
+ attrtext = convert(entry[:SHORT_ATTR])
# try the first four characters
- attrtext = convert(entry[:SHORT_ATTR])
if attrtext:
- elen = SHORT_ATTR
- entry = entry[SHORT_ATTR:].strip()
+ entry = entry[elen:].strip()
else: # try the whole thing
attrtext = convert(entry.strip())
assert attrtext, "Invalid palette entry: %r" % entry
elen = len(entry)
entry = ""
attr, text = attrtext
- out.append((attr, text.ljust(elen)))
+ if chart == CHART_TRUE:
+ out.append((attr, u"\u2584"))
+ else:
+ out.append((attr, text.ljust(elen)))
return out
+
def foreground_chart(chart, background, colors):
"""
Create text markup for a foreground colour chart
@@ -190,7 +366,7 @@ def main():
if colors == 1:
lb[chart_offset] = urwid.Divider()
else:
- chart = {16: CHART_16, 88: CHART_88, 256: CHART_256}[colors]
+ chart = {16: CHART_16, 88: CHART_88, 256: CHART_256, 2**24: CHART_TRUE}[colors]
txt = chart_fn(chart, 'default', colors)
lb[chart_offset] = urwid.Text(txt, wrap='clip')
@@ -220,7 +396,8 @@ def main():
mode_rb("Monochrome", 1),
mode_rb("16-Color", 16, True),
mode_rb("88-Color", 88),
- mode_rb("256-Color", 256),]),
+ mode_rb("256-Color", 256),
+ mode_rb("24-bit Color", 2**24),]),
urwid.Pile([
fcs(urwid.RadioButton(chart_radio_buttons,
"Foreground Colors", True, on_chart_change)),
diff --git a/docs/examples/tour.py b/docs/examples/tour.py
index cf04da2..6d80005 100755
--- a/docs/examples/tour.py
+++ b/docs/examples/tour.py
@@ -53,6 +53,10 @@ def main():
u"Text will be cut off at the left of this widget.")
text_center_clip = (u"Center aligned and clipped widgets will have "
u"text cut off both sides.")
+ text_ellipsis = (u"Text can be clippped using the ellipsis character (…)\n"
+ u"Extra text is discarded and a … mark is shown."
+ u"50-> 55-> 60-> 65-> 70-> 75-> 80-> 85-> 90-> 95-> 100>\n"
+ )
text_any = (u"The 'any' wrap mode will wrap on any character. This "
u"mode will not collapse space characters at the end of the "
u"line but it still honors embedded newline characters.\n"
@@ -150,6 +154,8 @@ def main():
blank,
urwid.Text(text_center_clip, align='center', wrap='clip'),
blank,
+ urwid.Text(text_ellipsis, wrap='ellipsis'),
+ blank,
urwid.Text(text_any, wrap='any'),
blank,
urwid.Padding(urwid.Text(text_padding), ('relative', 20), 40),
diff --git a/docs/manual/displayattributes.rst b/docs/manual/displayattributes.rst
index 4f800f6..5a69d6d 100644
--- a/docs/manual/displayattributes.rst
+++ b/docs/manual/displayattributes.rst
@@ -7,7 +7,7 @@
.. currentmodule:: urwid
Urwid supports a number of common display attributes in monochrome, 16-color,
-88-color and 256-color modes.
+88-color, 256-color, and 24-bit (true color) modes.
You are encouraged to provide support for as many of these modes as you like, while
allowing your interface to degrade gracefully by providing command line arguments
@@ -293,6 +293,22 @@ specify bold. To inhibit this you can try setting ``bright_is_bold=False`` with
.. _high-colors:
+.. _24-bit-foreground-background:
+
+24-Bit Foreground and Background Colors
+------------------------------------------
+
+In 24-bit color mode, any hex color code of the form #rrggbb can be used to
+specify a precise RGB value for foreground and background colors. Support for
+24-bit color mode varies widely among terminal programs. Furthermore, terminal
+multiplexers such as tmux and screen can sometimes interfere with the operation
+of 24-bit color mode unless properly configured.
+
+.. seealso::
+ The palette_test.py_ example program
+
+.. _palette_test.py: http://excess.org/urwid/browser/examples/palette_test.py
+
.. _256-foreground-background:
256-Color Foreground and Background Colors
diff --git a/docs/manual/displaymodules.rst b/docs/manual/displaymodules.rst
index eba84c8..e87b8fa 100644
--- a/docs/manual/displaymodules.rst
+++ b/docs/manual/displaymodules.rst
@@ -57,7 +57,7 @@ optimized C code no YES
compatible with any terminal no YES [1]_
UTF-8 support YES YES [2]_
bright foreground without bold YES [3]_ no
-88- or 256-color support YES no
+88-/256-/24-bit color support YES no
mouse dragging support YES no
external event loop support YES no
============================== =========== ==============
diff --git a/docs/manual/overview.rst b/docs/manual/overview.rst
index a9e953b..4fd8d6a 100644
--- a/docs/manual/overview.rst
+++ b/docs/manual/overview.rst
@@ -33,12 +33,12 @@ comes with a configurable :ref:`text layout <text-layout>` that handles the
most of the common alignment and wrapping modes. If you need more flexibility
you can also write your own text layout classes.
-Urwid supports a range of common :ref:`display attributes
-<display-attributes>`, including 256-color foreground and background settings,
-bold, underline and standout settings for displaying text. Not all of these
-are supported by all terminals, so Urwid helps you write applications that
-support different color modes depending on what the user's terminal supports
-and what they choose to enable.
+Urwid supports a range of common :ref:`display attributes <display-attributes>`,
+including 24-bit and 256-color foreground and background settings, bold,
+underline and standout settings for displaying text. Not all of these are
+supported by all terminals, so Urwid helps you write applications that support
+different color modes depending on what the user's terminal supports and what
+they choose to enable.
:class:`ListBox` is one of Urwid's most powerful widgets,
and you may control of the :ref:`listbox contents <listbox-contents>` by using
diff --git a/docs/manual/textlayout.rst b/docs/manual/textlayout.rst
index 075a3b7..3b4c1d3 100644
--- a/docs/manual/textlayout.rst
+++ b/docs/manual/textlayout.rst
@@ -9,7 +9,8 @@
Mapping a text string to screen coordinates within a widget is called text
layout. The :class:`Text` widget's default layout class supports
aligning text to the left, center or right, and can wrap text on space
-characters, at any location, or clip text that is off the edge.
+characters, at any location, or clip text that is off the edge, optionally
+inserting an ellipsis character.
::
@@ -62,6 +63,12 @@ characters, at any location, or clip text that is off the edge.
|newline | |newline |
+----------------+ +------------------------+
+ wrap='ellipsis'
+ +----------------+ +------------------------+
+ |Showing some di…| |Showing some different …|
+ |newline | |newline |
+ +----------------+ +------------------------+
+
If this is good enough for your application feel free to skip the rest of this
section.
diff --git a/docs/reference/constants.rst b/docs/reference/constants.rst
index 8259731..508fbab 100644
--- a/docs/reference/constants.rst
+++ b/docs/reference/constants.rst
@@ -121,7 +121,13 @@ Text Wrapping Modes
:annotation: = 'clip'
clip before any wide or narrow character that would exceed the available
- screen columns ad don't display the remaining text on the line
+ screen columns and don't display the remaining text on the line
+
+.. data:: ELLIPSIS
+ :annotation: = 'ellipsis'
+
+ clip if text would exceed the available screen columns, add an ellipsis
+ character at the end
Foreground and Background Colors
diff --git a/docs/reference/deprecated.rst b/docs/reference/deprecated.rst
index 25a4f6f..46aae05 100644
--- a/docs/reference/deprecated.rst
+++ b/docs/reference/deprecated.rst
@@ -10,5 +10,3 @@ Deprecated Classes
.. autoclass:: FixedWidget
.. autoclass:: AttrWrap
-
-.. autoclass:: PollingListWalker
diff --git a/docs/reference/widget.rst b/docs/reference/widget.rst
index 7752d57..ec8740f 100644
--- a/docs/reference/widget.rst
+++ b/docs/reference/widget.rst
@@ -68,6 +68,25 @@ TreeWidget
.. autoclass:: TreeWidget
+
+.. currentmodule:: urwid.numedit
+
+Extended Numerical Editing Classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+IntegerEdit
+~~~~~~~~~~~
+
+.. autoclass:: IntegerEdit
+
+FloatEdit
+~~~~~~~~~~~
+
+.. autoclass:: FloatEdit
+
+
+.. currentmodule:: urwid
+
SelectableIcon
~~~~~~~~~~~~~~
diff --git a/docs/tools/templates/indexcontent.html b/docs/tools/templates/indexcontent.html
index 2ece841..3217110 100644
--- a/docs/tools/templates/indexcontent.html
+++ b/docs/tools/templates/indexcontent.html
@@ -7,7 +7,7 @@
<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"
+<li><a href="https://pypi.org/packages/source/u/urwid/urwid-{{ release }}.tar.gz"
>Download: urwid-{{ release }}.tar.gz</a></li>
{% endif %}
<li><a href="changelog.html">Changelog</a>
@@ -18,7 +18,7 @@
<ul>
<li><a href="http://github.com/urwid/urwid">Github page</a></li>
<li><a href="http://github.com/urwid/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="https://gitter.im/urwid/community">IRC: <code>#urwid/community on gitter.im</code></a></li>
<li><a href="https://groups.google.com/a/excess.org/d/forum/urwid">Mailing list</a>
(or email <a href="mailto:urwid+subscribe@excess.org">urwid+subscribe@excess.org</a>)</li>
</ul>
@@ -37,7 +37,7 @@
<div class="section" id="requirements">
<h3>Requirements</h3>
<ul>
-<li>Python 2.6, 2.7, 3.2+ or PyPy</li>
+<li>Python 2.7, 3.4+ 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>
diff --git a/examples/asyncio_socket_server.py b/examples/asyncio_socket_server.py
index 31c9e81..2833faf 100644
--- a/examples/asyncio_socket_server.py
+++ b/examples/asyncio_socket_server.py
@@ -6,14 +6,21 @@ even Python 2 if you install `trollius`!
"""
from __future__ import print_function
-import asyncio
+try:
+ import asyncio
+except ImportError:
+ import trollius as asyncio
+
from datetime import datetime
import sys
import weakref
import urwid
from urwid.raw_display import Screen
+from urwid.display_common import BaseScreen
+import logging
+logging.basicConfig()
loop = asyncio.get_event_loop()
@@ -75,17 +82,21 @@ def demo1():
class AsyncScreen(Screen):
"""An urwid screen that speaks to an asyncio stream, rather than polling
file descriptors.
+
+ This is fairly limited; it can't, for example, determine the size of the
+ remote screen. Fixing that depends on the nature of the stream.
"""
- def __init__(self, reader, writer):
+ def __init__(self, reader, writer, encoding="utf-8"):
self.reader = reader
self.writer = writer
+ self.encoding = encoding
Screen.__init__(self, None, None)
_pending_task = None
def write(self, data):
- self.writer.write(data)
+ self.writer.write(data.encode(self.encoding))
def flush(self):
pass
@@ -113,10 +124,10 @@ class AsyncScreen(Screen):
# stops the screen and the loop
self.writer.abort()
- # asyncio.async() schedules a coroutine without using `yield from`,
- # which would make this code not work on Python 2
- self._pending_task = asyncio.async(
- self.reader.read(1024), loop=event_loop._loop)
+ # create_task() schedules a coroutine without using `yield from` or
+ # `await`, which are syntax errors in Pythons before 3.5
+ self._pending_task = event_loop._loop.create_task(
+ self.reader.read(1024))
self._pending_task.add_done_callback(pump_reader)
pump_reader()
@@ -167,7 +178,7 @@ def demo2():
loop.run_until_complete(coro)
print("OK, good to go! Try this in another terminal (or two):")
print()
- print(" socat TCP:127.0.0.1:12345 STDIN,raw")
+ print(" socat TCP:127.0.0.1:12345 STDIN,rawer")
print()
loop.run_forever()
diff --git a/examples/calc.py b/examples/calc.py
index 39c37f7..7b47fe9 100755
--- a/examples/calc.py
+++ b/examples/calc.py
@@ -639,6 +639,10 @@ class CalcDisplay:
"""Handle a keystroke."""
self.loop.process_input([key])
+
+ if isinstance(key, tuple):
+ # ignore mouse events
+ return
if key.upper() in COLUMN_KEYS:
# column switch
diff --git a/examples/dialog.py b/examples/dialog.py
index b444280..b635933 100755
--- a/examples/dialog.py
+++ b/examples/dialog.py
@@ -111,7 +111,7 @@ class DialogDisplay:
class InputDialogDisplay(DialogDisplay):
def __init__(self, text, height, width):
self.edit = urwid.Edit()
- body = urwid.ListBox([self.edit])
+ body = urwid.ListBox(urwid.SimpleListWalker([self.edit]))
body = urwid.AttrWrap(body, 'selectable','focustext')
DialogDisplay.__init__(self, text, height, width, body)
@@ -138,7 +138,7 @@ class TextDialogDisplay(DialogDisplay):
# read the whole file (being slow, not lazy this time)
for line in open(file).readlines():
l.append( urwid.Text( line.rstrip() ))
- body = urwid.ListBox(l)
+ body = urwid.ListBox(urwid.SimpleListWalker(l))
body = urwid.AttrWrap(body, 'selectable','focustext')
DialogDisplay.__init__(self, None, height, width, body)
@@ -172,7 +172,7 @@ class ListDialogDisplay(DialogDisplay):
w = urwid.AttrWrap(w, 'selectable','focus')
l.append(w)
- lb = urwid.ListBox(l)
+ lb = urwid.ListBox(urwid.SimpleListWalker(l))
lb = urwid.AttrWrap( lb, "selectable" )
DialogDisplay.__init__(self, text, height, width, lb )
diff --git a/examples/lcd_cf635.py b/examples/lcd_cf635.py
index 0377233..c30a3d2 100755
--- a/examples/lcd_cf635.py
+++ b/examples/lcd_cf635.py
@@ -55,8 +55,8 @@ class LCDCheckBox(urwid.CheckBox):
including custom CGRAM character
"""
states = {
- True: urwid.SelectableIcon('\xd0', cursor_position=0),
- False: urwid.SelectableIcon('\x05', cursor_position=0),
+ True: urwid.SelectableIcon('\xd0'),
+ False: urwid.SelectableIcon('\x05'),
}
reserve_columns = 1
@@ -66,8 +66,8 @@ class LCDRadioButton(urwid.RadioButton):
including custom CGRAM character
"""
states = {
- True: urwid.SelectableIcon('\xbb', cursor_position=0),
- False: urwid.SelectableIcon('\x06', cursor_position=0),
+ True: urwid.SelectableIcon('\xbb'),
+ False: urwid.SelectableIcon('\x06'),
}
reserve_columns = 1
@@ -132,9 +132,9 @@ class LCDHorizontalSlider(urwid.WidgetWrap):
def __init__(self, range, value, callback):
self.bar = LCDProgressBar(range, value)
cols = urwid.Columns([
- ('fixed', 1, urwid.SelectableIcon('\x11', cursor_position=0)),
+ ('fixed', 1, urwid.SelectableIcon('\x11')),
self.bar,
- ('fixed', 1, urwid.SelectableIcon('\x04', cursor_position=0)),
+ ('fixed', 1, urwid.SelectableIcon('\x04')),
])
self.__super.__init__(cols)
self.callback = callback
@@ -163,7 +163,7 @@ class MenuOption(urwid.Button):
self.set_label(label)
self._w = urwid.Columns([
- ('fixed', 1, urwid.SelectableIcon('\xdf', cursor_position=0)),
+ ('fixed', 1, urwid.SelectableIcon('\xdf')),
self._label])
urwid.connect_signal(self, 'click',
diff --git a/examples/palette_test.py b/examples/palette_test.py
index 0c98623..4fa5d19 100755
--- a/examples/palette_test.py
+++ b/examples/palette_test.py
@@ -21,7 +21,7 @@
"""
Palette test. Shows the available foreground and background settings
-in monochrome, 16 color, 88 color and 256 color modes.
+in monochrome, 16 color, 88 color, 256 color, and 24-bit (true) color modes.
"""
import re
@@ -30,6 +30,170 @@ import sys
import urwid
import urwid.raw_display
+CHART_TRUE="""
+#e50000#e51000#e52000#e53000#e54000#e55000#e56000#e57000#e58000#e59000\
+#e5a000#e5b100#e5c100#e5d100#e5e100#d9e500#c9e500#b9e500#a9e500#99e500\
+#89e500#79e500#68e500#58e500#48e500#38e500#28e500#18e500#08e500#00e507\
+#00e517#00e527#00e538#00e548#00e558#00e568#00e578#00e588#00e598#00e5a8\
+#00e5b8#00e5c8#00e5d8#00e1e5#00d1e5#00c1e5#00b1e5#00a1e5#0091e5#0081e5\
+#0071e5#0061e5#0051e5#0040e5#0030e5#0020e5#0010e5#0000e5#0f00e5#1f00e5\
+#2f00e5#3f00e5#4f00e5#5f00e5#7000e5#8000e5#9000e5#a000e5#b000e5#c000e5\
+#d000e5#e000e5#e500da#e500ca#e500b9#e500a9#e50099#e50089
+#da0000#da0f00#da1e00#da2e00#da3d00#da4c00#da5c00#da6b00#da7a00#da8a00\
+#da9900#daa800#dab800#dac700#dad600#cfda00#c0da00#b0da00#a1da00#92da00\
+#82da00#73da00#64da00#54da00#45da00#35da00#26da00#17da00#07da00#00da07\
+#00da16#00da26#00da35#00da44#00da54#00da63#00da72#00da82#00da91#00daa0\
+#00dab0#00dabf#00dace#00d7da#00c8da#00b8da#00a9da#0099da#008ada#007bda\
+#006bda#005cda#004dda#003dda#002eda#001fda#000fda#0000da#0e00da#1e00da\
+#2d00da#3c00da#4c00da#5b00da#6a00da#7a00da#8900da#9800da#a800da#b700da\
+#c600da#d600da#da00cf#da00c0#da00b1#da00a1#da0092#da0083
+#d00000#d00e00#d01d00#d02b00#d03a00#d04800#d05700#d06600#d07400#d08300\
+#d09100#d0a000#d0af00#d0bd00#d0cc00#c5d000#b6d000#a8d000#99d000#8ad000\
+#7cd000#6dd000#5fd000#50d000#41d000#33d000#24d000#16d000#07d000#00d007\
+#00d015#00d024#00d032#00d041#00d04f#00d05e#00d06d#00d07b#00d08a#00d098\
+#00d0a7#00d0b6#00d0c4#00ccd0#00bed0#00afd0#00a1d0#0092d0#0083d0#0075d0\
+#0066d0#0058d0#0049d0#003ad0#002cd0#001dd0#000fd0#0000d0#0e00d0#1c00d0\
+#2b00d0#3900d0#4800d0#5600d0#6500d0#7400d0#8200d0#9100d0#9f00d0#ae00d0\
+#bd00d0#cb00d0#d000c5#d000b7#d000a8#d00099#d0008b#d0007c
+#c50000#c50d00#c51b00#c52900#c53700#c54500#c55300#c56000#c56e00#c57c00\
+#c58a00#c59800#c5a600#c5b300#c5c100#bbc500#adc500#9fc500#91c500#83c500\
+#75c500#68c500#5ac500#4cc500#3ec500#30c500#22c500#15c500#07c500#00c506\
+#00c514#00c522#00c530#00c53e#00c54b#00c559#00c567#00c575#00c583#00c591\
+#00c59e#00c5ac#00c5ba#00c2c5#00b4c5#00a6c5#0098c5#008ac5#007dc5#006fc5\
+#0061c5#0053c5#0045c5#0037c5#002ac5#001cc5#000ec5#0000c5#0d00c5#1b00c5\
+#2800c5#3600c5#4400c5#5200c5#6000c5#6e00c5#7c00c5#8900c5#9700c5#a500c5\
+#b300c5#c100c5#c500bb#c500ad#c5009f#c50092#c50084#c50076
+#ba0000#ba0d00#ba1a00#ba2700#ba3400#ba4100#ba4e00#ba5b00#ba6800#ba7500\
+#ba8200#ba8f00#ba9c00#baaa00#bab700#b0ba00#a3ba00#96ba00#89ba00#7cba00\
+#6fba00#62ba00#55ba00#48ba00#3bba00#2eba00#20ba00#13ba00#06ba00#00ba06\
+#00ba13#00ba20#00ba2d#00ba3a#00ba47#00ba54#00ba61#00ba6e#00ba7c#00ba89\
+#00ba96#00baa3#00bab0#00b7ba#00aaba#009dba#0090ba#0083ba#0076ba#0069ba\
+#005cba#004eba#0041ba#0034ba#0027ba#001aba#000dba#0000ba#0c00ba#1900ba\
+#2600ba#3300ba#4000ba#4e00ba#5b00ba#6800ba#7500ba#8200ba#8f00ba#9c00ba\
+#a900ba#b600ba#ba00b1#ba00a4#ba0097#ba008a#ba007d#ba006f
+#af0000#af0c00#af1800#af2400#af3100#af3d00#af4900#af5600#af6200#af6e00\
+#af7b00#af8700#af9300#afa000#afac00#a6af00#9aaf00#8eaf00#81af00#75af00\
+#69af00#5caf00#50af00#44af00#37af00#2baf00#1faf00#12af00#06af00#00af05\
+#00af12#00af1e#00af2a#00af37#00af43#00af4f#00af5c#00af68#00af74#00af81\
+#00af8d#00af99#00afa6#00adaf#00a0af#0094af#0088af#007baf#006faf#0063af\
+#0056af#004aaf#003eaf#0031af#0025af#0019af#000caf#0000af#0b00af#1800af\
+#2400af#3000af#3d00af#4900af#5500af#6200af#6e00af#7a00af#8700af#9300af\
+#9f00af#ac00af#af00a7#af009a#af008e#af0082#af0075#af0069
+#a50000#a50b00#a51700#a52200#a52e00#a53900#a54500#a55100#a55c00#a56800\
+#a57300#a57f00#a58a00#a59600#a5a200#9ca500#90a500#85a500#79a500#6ea500\
+#62a500#57a500#4ba500#3fa500#34a500#28a500#1da500#11a500#06a500#00a505\
+#00a511#00a51c#00a528#00a533#00a53f#00a54b#00a556#00a562#00a56d#00a579\
+#00a584#00a590#00a59c#00a2a5#0096a5#008ba5#007fa5#0074a5#0068a5#005da5\
+#0051a5#0045a5#003aa5#002ea5#0023a5#0017a5#000ca5#0000a5#0b00a5#1600a5\
+#2200a5#2d00a5#3900a5#4500a5#5000a5#5c00a5#6700a5#7300a5#7e00a5#8a00a5\
+#9600a5#a100a5#a5009c#a50091#a50085#a5007a#a5006e#a50063
+#9a0000#9a0a00#9a1500#9a2000#9a2b00#9a3600#9a4000#9a4b00#9a5600#9a6100\
+#9a6c00#9a7700#9a8100#9a8c00#9a9700#929a00#879a00#7c9a00#719a00#679a00\
+#5c9a00#519a00#469a00#3b9a00#309a00#269a00#1b9a00#109a00#059a00#009a05\
+#009a10#009a1a#009a25#009a30#009a3b#009a46#009a50#009a5b#009a66#009a71\
+#009a7c#009a87#009a91#00979a#008d9a#00829a#00779a#006c9a#00619a#00569a\
+#004c9a#00419a#00369a#002b9a#00209a#00169a#000b9a#00009a#0a009a#15009a\
+#20009a#2a009a#35009a#40009a#4b009a#56009a#61009a#6b009a#76009a#81009a\
+#8c009a#97009a#9a0092#9a0087#9a007d#9a0072#9a0067#9a005c
+#8f0000#8f0a00#8f1400#8f1e00#8f2800#8f3200#8f3c00#8f4600#8f5000#8f5a00\
+#8f6400#8f6e00#8f7800#8f8200#8f8c00#888f00#7e8f00#748f00#698f00#5f8f00\
+#558f00#4b8f00#418f00#378f00#2d8f00#238f00#198f00#0f8f00#058f00#008f04\
+#008f0e#008f18#008f23#008f2d#008f37#008f41#008f4b#008f55#008f5f#008f69\
+#008f73#008f7d#008f87#008d8f#00838f#00798f#006f8f#00658f#005b8f#00508f\
+#00468f#003c8f#00328f#00288f#001e8f#00148f#000a8f#00008f#09008f#13008f\
+#1d008f#27008f#31008f#3c008f#46008f#50008f#5a008f#64008f#6e008f#78008f\
+#82008f#8c008f#8f0088#8f007e#8f0074#8f006a#8f0060#8f0056
+#840000#840900#841200#841b00#842500#842e00#843700#844100#844a00#845300\
+#845d00#846600#846f00#847900#848200#7d8400#748400#6b8400#628400#588400\
+#4f8400#468400#3c8400#338400#2a8400#208400#178400#0e8400#048400#008404\
+#00840d#008417#008420#008429#008433#00843c#008445#00844f#008458#008461\
+#00846a#008474#00847d#008284#007984#007084#006684#005d84#005484#004a84\
+#004184#003884#002e84#002584#001c84#001284#000984#000084#080084#120084\
+#1b0084#240084#2e0084#370084#400084#4a0084#530084#5c0084#660084#6f0084\
+#780084#820084#84007e#840074#84006b#840062#840059#84004f
+#7a0000#7a0800#7a1100#7a1900#7a2200#7a2a00#7a3300#7a3b00#7a4400#7a4d00\
+#7a5500#7a5e00#7a6600#7a6f00#7a7700#737a00#6b7a00#627a00#5a7a00#517a00\
+#487a00#407a00#377a00#2f7a00#267a00#1e7a00#157a00#0d7a00#047a00#007a04\
+#007a0c#007a15#007a1d#007a26#007a2e#007a37#007a40#007a48#007a51#007a59\
+#007a62#007a6a#007a73#00787a#006f7a#00677a#005e7a#00557a#004d7a#00447a\
+#003c7a#00337a#002b7a#00227a#001a7a#00117a#00087a#00007a#08007a#10007a\
+#19007a#21007a#2a007a#33007a#3b007a#44007a#4c007a#55007a#5d007a#66007a\
+#6f007a#77007a#7a0074#7a006b#7a0062#7a005a#7a0051#7a0049
+#6f0000#6f0700#6f0f00#6f1700#6f1f00#6f2700#6f2e00#6f3600#6f3e00#6f4600\
+#6f4e00#6f5500#6f5d00#6f6500#6f6d00#696f00#616f00#596f00#526f00#4a6f00\
+#426f00#3a6f00#326f00#2b6f00#236f00#1b6f00#136f00#0b6f00#046f00#006f03\
+#006f0b#006f13#006f1b#006f23#006f2a#006f32#006f3a#006f42#006f4a#006f51\
+#006f59#006f61#006f69#006d6f#00656f#005e6f#00566f#004e6f#00466f#003e6f\
+#00366f#002f6f#00276f#001f6f#00176f#000f6f#00086f#00006f#07006f#0f006f\
+#17006f#1e006f#26006f#2e006f#36006f#3e006f#46006f#4d006f#55006f#5d006f\
+#65006f#6d006f#6f0069#6f0062#6f005a#6f0052#6f004a#6f0042
+#640000#640700#640e00#641500#641c00#642300#642a00#643100#643800#643f00\
+#644600#644d00#645400#645b00#646200#5f6400#586400#516400#4a6400#436400\
+#3c6400#356400#2e6400#266400#1f6400#186400#116400#0a6400#036400#006403\
+#00640a#006411#006418#00641f#006426#00642d#006434#00643b#006442#006449\
+#006451#006458#00645f#006364#005c64#005464#004d64#004664#003f64#003864\
+#003164#002a64#002364#001c64#001564#000e64#000764#000064#060064#0d0064\
+#140064#1b0064#230064#2a0064#310064#380064#3f0064#460064#4d0064#540064\
+#5b0064#620064#64005f#640058#640051#64004a#640043#64003c
+#590000#590600#590c00#591200#591900#591f00#592500#592c00#593200#593800\
+#593f00#594500#594b00#595100#595800#555900#4e5900#485900#425900#3c5900\
+#355900#2f5900#295900#225900#1c5900#165900#0f5900#095900#035900#005903\
+#005909#00590f#005915#00591c#005922#005928#00592f#005935#00593b#005942\
+#005948#00594e#005955#005859#005259#004b59#004559#003f59#003859#003259\
+#002c59#002659#001f59#001959#001359#000c59#000659#000059#060059#0c0059\
+#120059#180059#1f0059#250059#2b0059#320059#380059#3e0059#450059#4b0059\
+#510059#580059#590055#59004f#590048#590042#59003c#590035
+#4f0000#4f0500#4f0b00#4f1000#4f1600#4f1b00#4f2100#4f2600#4f2c00#4f3100\
+#4f3700#4f3d00#4f4200#4f4800#4f4d00#4b4f00#454f00#3f4f00#3a4f00#344f00\
+#2f4f00#294f00#244f00#1e4f00#194f00#134f00#0d4f00#084f00#024f00#004f02\
+#004f08#004f0d#004f13#004f18#004f1e#004f23#004f29#004f2f#004f34#004f3a\
+#004f3f#004f45#004f4a#004d4f#00484f#00424f#003d4f#00374f#00324f#002c4f\
+#00274f#00214f#001b4f#00164f#00104f#000b4f#00054f#00004f#05004f#0a004f\
+#10004f#16004f#1b004f#21004f#26004f#2c004f#31004f#37004f#3c004f#42004f\
+#47004f#4d004f#4f004b#4f0045#4f0040#4f003a#4f0035#4f002f
+#440000#440400#440900#440e00#441300#441800#441c00#442100#442600#442b00\
+#443000#443400#443900#443e00#444300#404400#3c4400#374400#324400#2d4400\
+#284400#244400#1f4400#1a4400#154400#104400#0c4400#074400#024400#004402\
+#004407#00440b#004410#004415#00441a#00441f#004423#004428#00442d#004432\
+#004437#00443b#004440#004344#003e44#003944#003444#003044#002b44#002644\
+#002144#001c44#001844#001344#000e44#000944#000444#000044#040044#090044\
+#0e0044#130044#170044#1c0044#210044#260044#2b0044#2f0044#340044#390044\
+#3e0044#430044#440041#44003c#440037#440032#44002d#440029
+#390000#390400#390800#390c00#391000#391400#391800#391c00#392000#392400\
+#392800#392c00#393000#393400#393800#363900#323900#2e3900#2a3900#263900\
+#223900#1e3900#1a3900#163900#123900#0e3900#0a3900#063900#023900#003901\
+#003905#00390a#00390e#003912#003916#00391a#00391e#003922#003926#00392a\
+#00392e#003932#003936#003839#003439#003039#002c39#002839#002439#002039\
+#001c39#001839#001439#001039#000c39#000839#000439#000039#030039#070039\
+#0b0039#100039#140039#180039#1c0039#200039#240039#280039#2c0039#300039\
+#340039#380039#390036#390032#39002e#39002a#390026#390022
+#2e0000#2e0300#2e0600#2e0900#2e0d00#2e1000#2e1300#2e1700#2e1a00#2e1d00\
+#2e2000#2e2400#2e2700#2e2a00#2e2e00#2c2e00#292e00#252e00#222e00#1f2e00\
+#1c2e00#182e00#152e00#122e00#0e2e00#0b2e00#082e00#052e00#012e00#002e01\
+#002e04#002e08#002e0b#002e0e#002e12#002e15#002e18#002e1b#002e1f#002e22\
+#002e25#002e29#002e2c#002e2e#002a2e#00272e#00242e#00212e#001d2e#001a2e\
+#00172e#00132e#00102e#000d2e#000a2e#00062e#00032e#00002e#03002e#06002e\
+#09002e#0d002e#10002e#13002e#16002e#1a002e#1d002e#20002e#24002e#27002e\
+#2a002e#2d002e#2e002c#2e0029#2e0026#2e0022#2e001f#2e001c
+#240000#240200#240500#240700#240a00#240c00#240f00#241100#241400#241600\
+#241900#241b00#241e00#242100#242300#222400#1f2400#1d2400#1a2400#182400\
+#152400#132400#102400#0e2400#0b2400#082400#062400#032400#012400#002401\
+#002403#002406#002408#00240b#00240d#002410#002413#002415#002418#00241a\
+#00241d#00241f#002422#002324#002124#001e24#001c24#001924#001624#001424\
+#001124#000f24#000c24#000a24#000724#000524#000224#000024#020024#040024\
+#070024#0a0024#0c0024#0f0024#110024#140024#160024#190024#1b0024#1e0024\
+#200024#230024#240022#24001f#24001d#24001a#240018#240015
+#190000#190100#190300#190500#190700#190800#190a00#190c00#190e00#191000\
+#191100#191300#191500#191700#191900#181900#161900#141900#121900#111900\
+#0f1900#0d1900#0b1900#091900#081900#061900#041900#021900#001900#001900\
+#001902#001904#001906#001908#001909#00190b#00190d#00190f#001910#001912\
+#001914#001916#001918#001919#001719#001519#001319#001119#001019#000e19\
+#000c19#000a19#000919#000719#000519#000319#000119#000019#010019#030019\
+#050019#070019#080019#0a0019#0c0019#0e0019#100019#110019#130019#150019\
+#170019#180019#190018#190016#190014#190012#190011#19000f
+"""
+# raise Exception(CHART_TRUE)
+
CHART_256 = """
brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_
yellow_ light_red light_magenta light_blue light_cyan light_green
@@ -83,10 +247,14 @@ yellow_ light_red light_magenta light_blue light_cyan light_green
black_______ dark_gray___ light_gray__ white_______
"""
-ATTR_RE = re.compile("(?P<whitespace>[ \n]*)(?P<entry>[^ \n]+)")
+ATTR_RE = re.compile("(?P<whitespace>[ \n]*)(?P<entry>(?:#[0-9A-Fa-f]{6})|(?:#[0-9A-Fa-f]{3})|(?:[^ \n]+))")
+LONG_ATTR = 7
SHORT_ATTR = 4 # length of short high-colour descriptions which may
# be packed one after the next
+
+
+
def parse_chart(chart, convert):
"""
Convert string chart into text markup with the correct attributes.
@@ -102,20 +270,28 @@ def parse_chart(chart, convert):
entry = match.group('entry')
entry = entry.replace("_", " ")
while entry:
+ if chart == CHART_TRUE and len(entry) == LONG_ATTR:
+ attrtext = convert(entry[:LONG_ATTR])
+ elen = LONG_ATTR
+ else:
+ elen = SHORT_ATTR
+ attrtext = convert(entry[:SHORT_ATTR])
# try the first four characters
- attrtext = convert(entry[:SHORT_ATTR])
if attrtext:
- elen = SHORT_ATTR
- entry = entry[SHORT_ATTR:].strip()
+ entry = entry[elen:].strip()
else: # try the whole thing
attrtext = convert(entry.strip())
assert attrtext, "Invalid palette entry: %r" % entry
elen = len(entry)
entry = ""
attr, text = attrtext
- out.append((attr, text.ljust(elen)))
+ if chart == CHART_TRUE:
+ out.append((attr, u"\u2584"))
+ else:
+ out.append((attr, text.ljust(elen)))
return out
+
def foreground_chart(chart, background, colors):
"""
Create text markup for a foreground colour chart
@@ -190,7 +366,7 @@ def main():
if colors == 1:
lb[chart_offset] = urwid.Divider()
else:
- chart = {16: CHART_16, 88: CHART_88, 256: CHART_256}[colors]
+ chart = {16: CHART_16, 88: CHART_88, 256: CHART_256, 2**24: CHART_TRUE}[colors]
txt = chart_fn(chart, 'default', colors)
lb[chart_offset] = urwid.Text(txt, wrap='clip')
@@ -220,7 +396,8 @@ def main():
mode_rb("Monochrome", 1),
mode_rb("16-Color", 16, True),
mode_rb("88-Color", 88),
- mode_rb("256-Color", 256),]),
+ mode_rb("256-Color", 256),
+ mode_rb("24-bit Color", 2**24),]),
urwid.Pile([
fcs(urwid.RadioButton(chart_radio_buttons,
"Foreground Colors", True, on_chart_change)),
diff --git a/examples/terminal.py b/examples/terminal.py
index ff30a93..c50656a 100755
--- a/examples/terminal.py
+++ b/examples/terminal.py
@@ -22,7 +22,8 @@
import urwid
def main():
- term = urwid.Terminal(None)
+ urwid.set_encoding('utf8')
+ term = urwid.Terminal(None, encoding='utf-8')
mainframe = urwid.LineBox(
urwid.Pile([
diff --git a/examples/tour.py b/examples/tour.py
index cf04da2..6d80005 100755
--- a/examples/tour.py
+++ b/examples/tour.py
@@ -53,6 +53,10 @@ def main():
u"Text will be cut off at the left of this widget.")
text_center_clip = (u"Center aligned and clipped widgets will have "
u"text cut off both sides.")
+ text_ellipsis = (u"Text can be clippped using the ellipsis character (…)\n"
+ u"Extra text is discarded and a … mark is shown."
+ u"50-> 55-> 60-> 65-> 70-> 75-> 80-> 85-> 90-> 95-> 100>\n"
+ )
text_any = (u"The 'any' wrap mode will wrap on any character. This "
u"mode will not collapse space characters at the end of the "
u"line but it still honors embedded newline characters.\n"
@@ -150,6 +154,8 @@ def main():
blank,
urwid.Text(text_center_clip, align='center', wrap='clip'),
blank,
+ urwid.Text(text_ellipsis, wrap='ellipsis'),
+ blank,
urwid.Text(text_any, wrap='any'),
blank,
urwid.Padding(urwid.Text(text_padding), ('relative', 20), 40),
diff --git a/setup.py b/setup.py
index 60fdb02..d79c23f 100644
--- a/setup.py
+++ b/setup.py
@@ -63,14 +63,13 @@ setup_d = {
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Widget Sets",
"Programming Language :: Python :: 2",
- "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 :: 3.5",
"Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: Implementation :: PyPy",
],
}
diff --git a/urwid.egg-info/PKG-INFO b/urwid.egg-info/PKG-INFO
index 47b320d..1b608b3 100644
--- a/urwid.egg-info/PKG-INFO
+++ b/urwid.egg-info/PKG-INFO
@@ -1,12 +1,11 @@
Metadata-Version: 1.1
Name: urwid
-Version: 2.0.1
+Version: 2.1.0
Summary: A full-featured console (xterm et al.) user interface library
Home-page: http://urwid.org/
Author: Ian Ward
Author-email: ian@excess.org
License: LGPL
-Description-Content-Type: UNKNOWN
Description:
About
=====
@@ -22,12 +21,27 @@ Description:
- 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
+ - 24-bit (true color), 256 color, and 88 color mode support
+ - Compatible with Python 2.7, 3.4+ and PyPy
Home Page:
http://urwid.org/
+ Installation
+ ============
+
+ To install using pip
+
+ .. code:: bash
+
+ pip install urwid
+
+ Alternatively if you are on Debian or Ubuntu
+
+ .. code:: bash
+
+ apt-get install python-urwid
+
Testing
=======
@@ -43,38 +57,111 @@ Description:
tox -e py36 # Test Python 3.6 only
tox -e py27,py36,pypy # Test Python 2.7, Python 3.6 & pypy
+ Supported Python versions
+ =========================
+
+ - 2.7
+ - 3.4
+ - 3.5
+ - 3.6
+ - 3.7
+ - 3.8
+ - pypy
+
+ Authors
+ =======
+
+ Creator
+ -------
+
+ `wardi <//github.com/wardi>`_
+
+ Maintainers
+ -----------
+
+ `and3rson <//github.com/and3rson>`_,
+ `tonycpsu <//github.com/tonycpsu>`_,
+ `ulidtko <//github.com/ulidtko>`_
+
Contributors
- ============
+ ------------
+
+ `aathan <//github.com/aathan>`_,
+ `abadger <//github.com/abadger>`_,
+ `aglyzov <//github.com/aglyzov>`_,
+ `akosthekiss <//github.com/akosthekiss>`_,
+ `alexozer <//github.com/alexozer>`_,
+ `andersk <//github.com/andersk>`_,
+ `aszlig <//github.com/aszlig>`_,
+ `atsampson <//github.com/atsampson>`_,
+ `BkPHcgQL3V <//github.com/BkPHcgQL3V>`_,
+ `BlindB0 <//github.com/BlindB0>`_,
+ `bukzor <//github.com/bukzor>`_,
+ `eevee <//github.com/eevee>`_,
+ `federicotdn <//github.com/federicotdn>`_,
+ `garrison <//github.com/garrison>`_,
+ `geier <//github.com/geier>`_,
+ `grugq <//github.com/grugq>`_,
+ `hkoof <//github.com/hkoof>`_,
+ `hootnot <//github.com/hootnot>`_,
+ `horazont <//github.com/horazont>`_,
+ `inducer <//github.com/inducer>`_,
+ `ismail-s <//github.com/ismail-s>`_,
+ `italomaia-bk <//github.com/italomaia-bk>`_,
+ `ivanov <//github.com/ivanov>`_,
+ `Julian <//github.com/Julian>`_,
+ `jwilk <//github.com/jwilk>`_,
+ `kajojify <//github.com/kajojify>`_,
+ `Kamik423 <//github.com/Kamik423>`_,
+ `kkrolczyk <//github.com/kkrolczyk>`_,
+ `marienz <//github.com/marienz>`_,
+ `matthijskooijman <//github.com/matthijskooijman>`_,
+ `mbarkhau <//github.com/mbarkhau>`_,
+ `mgiusti <//github.com/mgiusti>`_,
+ `mikemccracken <//github.com/mikemccracken>`_,
+ `nocarryr <//github.com/nocarryr>`_,
+ `ntamas <//github.com/ntamas>`_,
+ `olleolleolle <//github.com/olleolleolle>`_,
+ `pazz <//github.com/pazz>`_,
+ `pniedzwiedzinski <//github.com/pniedzwiedzinski>`_,
+ `raek <//github.com/raek>`_,
+ `richrd <//github.com/richrd>`_,
+ `rndusr <//github.com/rndusr>`_,
+ `robla <//github.com/robla>`_,
+ `rr- <//github.com/rr->`_,
+ `seleem1337 <//github.com/seleem1337>`_,
+ `SenchoPens <//github.com/SenchoPens>`_,
+ `shyal <//github.com/shyal>`_,
+ `sitaktif <//github.com/sitaktif>`_,
+ `tdryer <//github.com/tdryer>`_,
+ `techtonik <//github.com/techtonik>`_,
+ `tu500 <//github.com/tu500>`_,
+ `usrlocalben <//github.com/usrlocalben>`_,
+ `wackywendell <//github.com/wackywendell>`_,
+ `wernight <//github.com/wernight>`_,
+ `westurner <//github.com/westurner>`_,
+ `whospal <//github.com/whospal>`_,
+ `Wilfred <//github.com/Wilfred>`_,
+ `winbornejw <//github.com/winbornejw>`_,
+ `xnox <//github.com/xnox>`_,
+ `yanzixiang <//github.com/yanzixiang>`_
+
+
+ .. |pypi| image:: http://img.shields.io/pypi/v/urwid.svg
+ :alt: current version on PyPi
+ :target: https://pypi.python.org/pypi/urwid
+
+ .. |docs| image:: https://readthedocs.org/projects/urwid/badge/
+ :alt: docs link
+ :target: http://urwid.readthedocs.org/en/latest/
+
+ .. |travis| image:: https://travis-ci.org/urwid/urwid.svg?branch=master
+ :alt: build status
+ :target: https://travis-ci.org/urwid/urwid/
- - `wardi <//github.com/wardi>`_
- - `aszlig <//github.com/aszlig>`_
- - `mgiusti <//github.com/mgiusti>`_
- - `and3rson <//github.com/and3rson>`_
- - `pazz <//github.com/pazz>`_
- - `wackywendell <//github.com/wackywendell>`_
- - `eevee <//github.com/eevee>`_
- - `marienz <//github.com/marienz>`_
- - `rndusr <//github.com/rndusr>`_
- - `matthijskooijman <//github.com/matthijskooijman>`_
- - `Julian <//github.com/Julian>`_
- - `techtonik <//github.com/techtonik>`_
- - `garrison <//github.com/garrison>`_
- - `ivanov <//github.com/ivanov>`_
- - `abadger <//github.com/abadger>`_
- - `aglyzov <//github.com/aglyzov>`_
- - `ismail-s <//github.com/ismail-s>`_
- - `horazont <//github.com/horazont>`_
- - `robla <//github.com/robla>`_
- - `usrlocalben <//github.com/usrlocalben>`_
- - `geier <//github.com/geier>`_
- - `federicotdn <//github.com/federicotdn>`_
- - `jwilk <//github.com/jwilk>`_
- - `rr- <//github.com/rr->`_
- - `tonycpsu <//github.com/tonycpsu>`_
- - `westurner <//github.com/westurner>`_
- - `grugq <//github.com/grugq>`_
- - `inducer <//github.com/inducer>`_
- - `winbornejw <//github.com/winbornejw>`_
+ .. |coveralls| image:: https://coveralls.io/repos/github/urwid/urwid/badge.svg
+ :alt: test coverage
+ :target: https://coveralls.io/github/urwid/urwid
Keywords: curses ui widget scroll listbox user interface text layout console ncurses
Platform: unix-like
@@ -89,12 +176,11 @@ Classifier: Operating System :: MacOS :: MacOS X
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: Widget Sets
Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.2
-Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: Implementation :: PyPy
diff --git a/urwid.egg-info/SOURCES.txt b/urwid.egg-info/SOURCES.txt
index fd72eeb..1ba3cb4 100644
--- a/urwid.egg-info/SOURCES.txt
+++ b/urwid.egg-info/SOURCES.txt
@@ -173,6 +173,7 @@ examples/twisted_serve_ssh.py
examples/twisted_serve_ssh.tac
source/str_util.c
urwid/__init__.py
+urwid/_async_kw_event_loop.py
urwid/canvas.py
urwid/command_map.py
urwid/compat.py
@@ -188,6 +189,7 @@ urwid/lcd_display.py
urwid/listbox.py
urwid/main_loop.py
urwid/monitored_list.py
+urwid/numedit.py
urwid/old_str_util.py
urwid/raw_display.py
urwid/signals.py
@@ -210,6 +212,7 @@ urwid/tests/test_canvas.py
urwid/tests/test_container.py
urwid/tests/test_decoration.py
urwid/tests/test_doctests.py
+urwid/tests/test_escapes.py
urwid/tests/test_event_loops.py
urwid/tests/test_graphics.py
urwid/tests/test_listbox.py
diff --git a/urwid/__init__.py b/urwid/__init__.py
index 32484de..3eb4e99 100644
--- a/urwid/__init__.py
+++ b/urwid/__init__.py
@@ -37,8 +37,8 @@ from urwid.container import (GridFlowError, GridFlow, OverlayError, Overlay,
WidgetContainerMixin)
from urwid.wimp import (SelectableIcon, CheckBoxError, CheckBox, RadioButton,
Button, PopUpLauncher, PopUpTarget)
-from urwid.listbox import (ListWalkerError, ListWalker, PollingListWalker,
- SimpleListWalker, SimpleFocusListWalker, ListBoxError, ListBox)
+from urwid.listbox import (ListWalkerError, ListWalker, SimpleListWalker,
+ SimpleFocusListWalker, ListBoxError, ListBox)
from urwid.graphics import (BigText, LineBox, BarGraphMeta, BarGraphError,
BarGraph, GraphVScale, ProgressBar, scale_bar_values)
from urwid.canvas import (CanvasCache, CanvasError, Canvas, TextCanvas,
@@ -60,6 +60,10 @@ try:
from urwid.main_loop import TwistedEventLoop
except ImportError:
pass
+try:
+ from urwid.main_loop import TrioEventLoop
+except ImportError:
+ pass
from urwid.text_layout import (TextLayout, StandardTextLayout, default_layout,
LayoutSegment)
from urwid.display_common import (UPDATE_PALETTE_ENTRY, DEFAULT, BLACK,
diff --git a/urwid/_async_kw_event_loop.py b/urwid/_async_kw_event_loop.py
new file mode 100644
index 0000000..cc7728f
--- /dev/null
+++ b/urwid/_async_kw_event_loop.py
@@ -0,0 +1,232 @@
+#!/usr/bin/python
+#
+# Urwid main loop code using Python-3.5 features (Trio, Curio, etc)
+# Copyright (C) 2018 Toshio Kuratomi
+# Copyright (C) 2019 Tamas Nepusz
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Urwid web site: http://excess.org/urwid/
+
+from .main_loop import EventLoop, ExitMainLoop
+
+
+class TrioEventLoop(EventLoop):
+ """
+ Event loop based on the ``trio`` module.
+
+ ``trio`` is an async library for Python 3.5 and later.
+ """
+
+ def __init__(self, nursery=None):
+ """Constructor.
+
+ Parameters:
+ nursery: the Trio nursery in which the asynchronous tasks will
+ execute. `None` will make the event loop use `trio.run()` when
+ the loop is started and force it to create a nursery on its
+ own.
+ """
+ import trio
+
+ self._idle_handle = 0
+ self._idle_callbacks = {}
+ self._pending_tasks = []
+
+ self._trio = trio
+ self._nursery = None
+
+ self._sleep = trio.sleep
+ self._wait_readable = trio.hazmat.wait_readable
+
+ def alarm(self, seconds, callback):
+ """Calls `callback()` a given time from now. No parameters are passed
+ to the callback.
+
+ Parameters:
+ seconds: time in seconds to wait before calling the callback
+ callback: function to call from the event loop
+
+ Returns:
+ a handle that may be passed to `remove_alarm()`
+ """
+ return self._start_task(self._alarm_task, seconds, callback)
+
+ def enter_idle(self, callback):
+ """Calls `callback()` when the event loop enters the idle state.
+
+ There is no such thing as being idle in a Trio event loop so we
+ simulate it by repeatedly calling `callback()` with a short delay.
+ """
+ self._idle_handle += 1
+ self._idle_callbacks[self._idle_handle] = callback
+ return self._idle_handle
+
+ def remove_alarm(self, handle):
+ """Removes an alarm.
+
+ Parameters:
+ handle: the handle of the alarm to remove
+ """
+ return self._cancel_scope(handle)
+
+ def remove_enter_idle(self, handle):
+ """Removes an idle callback.
+
+ Parameters:
+ handle: the handle of the idle callback to remove
+ """
+ try:
+ del self._idle_callbacks[handle]
+ except KeyError:
+ return False
+ return True
+
+ def remove_watch_file(self, handle):
+ """Removes a file descriptor being watched for input.
+
+ Parameters:
+ handle: the handle of the file descriptor callback to remove
+
+ Returns:
+ True if the file descriptor was watched, False otherwise
+ """
+ return self._cancel_scope(handle)
+
+ def _cancel_scope(self, scope):
+ """Cancels the given Trio cancellation scope.
+
+ Returns:
+ True if the scope was cancelled, False if it was cancelled already
+ before invoking this function
+ """
+ existed = not scope.cancel_called
+ scope.cancel()
+ return existed
+
+ def run(self):
+ """Starts the event loop. Exits the loop when any callback raises an
+ exception. If ExitMainLoop is raised, exits cleanly.
+ """
+
+ idle_callbacks = self._idle_callbacks
+
+ def _exception_handler(exc):
+ idle_callbacks.clear()
+ if isinstance(exc, ExitMainLoop):
+ return None
+ else:
+ return exc
+
+ class TrioIdleCallbackInstrument(self._trio.abc.Instrument):
+ def before_io_wait(self, timeout):
+ if timeout > 0:
+ for idle_callback in idle_callbacks.values():
+ idle_callback()
+
+ emulate_idle_callbacks = TrioIdleCallbackInstrument()
+
+ with self._trio.MultiError.catch(_exception_handler):
+ if not self._nursery:
+ self._trio.run(self._main_task, instruments=[emulate_idle_callbacks])
+ else:
+ self._trio.hazmat.add_instrument(emulate_idle_callbacks)
+ try:
+ self._nursery.start_soon(self._main_task)
+ finally:
+ self._trio.hazmat.remove_instrument(emulate_idle_callbacks)
+
+ def watch_file(self, fd, callback):
+ """Calls `callback()` when the given file descriptor has some data
+ to read. No parameters are passed to the callback.
+
+ Parameters:
+ fd: file descriptor to watch for input
+ callback: function to call when some input is available
+
+ Returns:
+ a handle that may be passed to `remove_watch_file()`
+ """
+ return self._start_task(self._watch_task, fd, callback)
+
+ async def _alarm_task(self, scope, seconds, callback):
+ """Asynchronous task that sleeps for a given number of seconds and then
+ calls the given callback.
+
+ Parameters:
+ scope: the cancellation scope that can be used to cancel the task
+ seconds: the number of seconds to wait
+ callback: the callback to call
+ """
+ with scope:
+ await self._sleep(seconds)
+ callback()
+
+ async def _main_task(self):
+ """Main Trio task that opens a nursery and then sleeps until the user
+ exits the app by raising ExitMainLoop.
+ """
+
+ if self._nursery:
+ self._schedule_pending_tasks()
+ await self._trio.sleep_forever()
+ else:
+ try:
+ async with self._trio.open_nursery() as self._nursery:
+ self._schedule_pending_tasks()
+ await self._trio.sleep_forever()
+ finally:
+ self._nursery = None
+
+ def _schedule_pending_tasks(self):
+ """Schedules all pending asynchronous tasks that were created before
+ the nursery to be executed on the nursery soon.
+ """
+ for task, scope, args in self._pending_tasks:
+ self._nursery.start_soon(task, scope, *args)
+ del self._pending_tasks[:]
+
+ def _start_task(self, task, *args):
+ """Starts an asynchronous task in the Trio nursery managed by the
+ main loop. If the nursery has not started yet, store a reference to
+ the task and the arguments so we can start the task when the nursery
+ is open.
+
+ Parameters:
+ task: a Trio task to run
+
+ Returns:
+ a cancellation scope for the Trio task
+ """
+ scope = self._trio.CancelScope()
+ if self._nursery:
+ self._nursery.start_soon(task, scope, *args)
+ else:
+ self._pending_tasks.append((task, scope, args))
+ return scope
+
+ async def _watch_task(self, scope, fd, callback):
+ """Asynchronous task that watches the given file descriptor and calls
+ the given callback whenever the file descriptor becomes readable.
+
+ Parameters:
+ scope: the cancellation scope that can be used to cancel the task
+ fd: the file descriptor to watch
+ callback: the callback to call
+ """
+ with scope:
+ while True:
+ await self._wait_readable(fd)
+ callback()
diff --git a/urwid/canvas.py b/urwid/canvas.py
index 207ee21..3c96bbe 100644
--- a/urwid/canvas.py
+++ b/urwid/canvas.py
@@ -930,8 +930,7 @@ def shard_body(cviews, shard_tail, create_iter=True, iter_default=None):
try:
cview = next(cviews_iter)
except StopIteration:
- raise CanvasError("cviews do not fill gaps in"
- " shard_tail!")
+ break
(trim_left, trim_top, cols, rows, attr_map, canv) = \
cview[:6]
col += cols
@@ -1229,7 +1228,8 @@ def apply_text_layout(text, attr, ls, maxcol):
aw.k = 0
aw.off = 0
o = []
- while aw.off < end_offs:
+ # the loop should run at least once, the '=' part ensures that
+ while aw.off <= end_offs:
if len(attr)<=aw.k:
# run out of attributes
o.append((None,end_offs-max(start_offs,aw.off)))
@@ -1313,7 +1313,3 @@ def apply_text_layout(text, attr, ls, maxcol):
c.append(linec)
return TextCanvas(t, a, c, maxcol=maxcol)
-
-
-
-
diff --git a/urwid/container.py b/urwid/container.py
index ed4ee59..98d057a 100755
--- a/urwid/container.py
+++ b/urwid/container.py
@@ -1158,7 +1158,7 @@ class Frame(Widget, WidgetContainerMixin):
if not hasattr(self.footer, 'mouse_event'):
return False
return self.footer.mouse_event( (maxcol,), event,
- button, col, row-maxrow+frows, focus )
+ button, col, row-maxrow+ftrim, focus )
# within body
focus = focus and self.focus_part == 'body'
@@ -1171,6 +1171,33 @@ class Frame(Widget, WidgetContainerMixin):
return self.body.mouse_event( (maxcol, maxrow-htrim-ftrim),
event, button, col, row-htrim, focus )
+ def get_cursor_coords(self, size):
+ """Return the cursor coordinates of the focus widget."""
+ if not self.focus.selectable():
+ return None
+ if not hasattr(self.focus, 'get_cursor_coords'):
+ return None
+
+ fp = self.focus_position
+ (maxcol, maxrow) = size
+ (hrows, frows), _ = self.frame_top_bottom(size, True)
+
+ if fp == 'header':
+ row_adjust = 0
+ coords = self.header.get_cursor_coords((maxcol,))
+ elif fp == 'body':
+ row_adjust = hrows
+ coords = self.body.get_cursor_coords((maxcol, maxrow-hrows-frows))
+ else:
+ row_adjust = maxrow - frows
+ coords = self.footer.get_cursor_coords((maxcol,))
+
+ if coords is None:
+ return None
+
+ x, y = coords
+ return x, y + row_adjust
+
def __iter__(self):
"""
Return an iterator over the positions in this Frame top to bottom.
@@ -1227,11 +1254,12 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
.. note:: If the Pile is treated as a box widget there must be at least
one ``'weight'`` tuple in :attr:`widget_list`.
"""
+ self._selectable = False
self.__super.__init__()
self._contents = MonitoredFocusList()
- self._contents.set_modified_callback(self._invalidate)
+ self._contents.set_modified_callback(self._contents_modified)
self._contents.set_focus_changed_callback(lambda f: self._invalidate())
- self._contents.set_validate_contents_modified(self._contents_modified)
+ self._contents.set_validate_contents_modified(self._validate_contents_modified)
focus_item = focus_item
for i, original in enumerate(widget_list):
@@ -1261,7 +1289,15 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
self.pref_col = 0
- def _contents_modified(self, slc, new_items):
+ def _contents_modified(self):
+ """
+ Recalculate whether this widget should be selectable whenever the
+ contents has been changed.
+ """
+ self._selectable = any(w.selectable() for w, o in self.contents)
+ self._invalidate()
+
+ def _validate_contents_modified(self, slc, new_items):
for item in new_items:
try:
w, (t, n) = item
@@ -1361,11 +1397,6 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
raise PileError('invalid height_type: %r' % (height_type,))
return (height_type, height_amount)
- def selectable(self):
- """Return True if the focus item is selectable."""
- w = self.focus
- return w is not None and w.selectable()
-
def set_focus(self, item):
"""
Set the item in focus, for backwards compatibility.
@@ -1736,11 +1767,12 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
*box_columns* will be displayed with this calculated number of rows,
filling the full height.
"""
+ self._selectable = False
self.__super.__init__()
self._contents = MonitoredFocusList()
- self._contents.set_modified_callback(self._invalidate)
+ self._contents.set_modified_callback(self._contents_modified)
self._contents.set_focus_changed_callback(lambda f: self._invalidate())
- self._contents.set_validate_contents_modified(self._contents_modified)
+ self._contents.set_validate_contents_modified(self._validate_contents_modified)
box_columns = set(box_columns or ())
@@ -1772,13 +1804,19 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
if self.contents and focus_column is not None:
self.focus_position = focus_column
- if focus_column is None:
- focus_column = 0
self.pref_col = None
self.min_width = min_width
self._cache_maxcol = None
- def _contents_modified(self, slc, new_items):
+ def _contents_modified(self):
+ """
+ Recalculate whether this widget should be selectable whenever the
+ contents has been changed.
+ """
+ self._selectable = any(w.selectable() for w, o in self.contents)
+ self._invalidate()
+
+ def _validate_contents_modified(self, slc, new_items):
for item in new_items:
try:
w, (t, n, b) = item
@@ -2014,7 +2052,7 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
elif t == PACK:
# FIXME: should be able to pack with a different
# maxcol value
- static_w = w.pack((maxcol,), focus)[0]
+ static_w = w.pack((maxcol,), focus and i == self.focus_position)[0]
else:
static_w = self.min_width
@@ -2265,10 +2303,11 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
if self._command_map[key] not in ('cursor up', 'cursor down',
'cursor page up', 'cursor page down'):
self.pref_col = None
- if len(size) == 1 and b:
- key = w.keypress((mc, self.rows(size, True)), key)
- else:
- key = w.keypress((mc,) + size[1:], key)
+ if w.selectable():
+ if len(size) == 1 and b:
+ key = w.keypress((mc, self.rows(size, True)), key)
+ else:
+ key = w.keypress((mc,) + size[1:], key)
if self._command_map[key] not in ('cursor left', 'cursor right'):
return key
@@ -2287,12 +2326,6 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
return key
- def selectable(self):
- """Return the selectable value of the focus column."""
- w = self.focus
- return w is not None and w.selectable()
-
-
diff --git a/urwid/display_common.py b/urwid/display_common.py
index e447682..9717b61 100755
--- a/urwid/display_common.py
+++ b/urwid/display_common.py
@@ -80,20 +80,23 @@ _COLOR_VALUES_88 = (_BASIC_COLOR_VALUES +
assert len(_COLOR_VALUES_256) == 256
assert len(_COLOR_VALUES_88) == 88
-_FG_COLOR_MASK = 0x000000ff
-_BG_COLOR_MASK = 0x0000ff00
-_FG_BASIC_COLOR = 0x00010000
-_FG_HIGH_COLOR = 0x00020000
-_BG_BASIC_COLOR = 0x00040000
-_BG_HIGH_COLOR = 0x00080000
-_BG_SHIFT = 8
-_HIGH_88_COLOR = 0x00100000
-_STANDOUT = 0x02000000
-_UNDERLINE = 0x04000000
-_BOLD = 0x08000000
-_BLINK = 0x10000000
-_ITALICS = 0x20000000
-_STRIKETHROUGH = 0x40000000
+_FG_COLOR_MASK = 0x000000ffffff
+_BG_COLOR_MASK = 0xffffff000000
+_FG_BASIC_COLOR = 0x1000000000000
+_FG_HIGH_COLOR = 0x2000000000000
+_FG_TRUE_COLOR = 0x4000000000000
+_BG_BASIC_COLOR = 0x8000000000000
+_BG_HIGH_COLOR = 0x10000000000000
+_BG_TRUE_COLOR = 0x20000000000000
+_BG_SHIFT = 24
+_HIGH_88_COLOR = 0x40000000000000
+_HIGH_TRUE_COLOR = 0x80000000000000
+_STANDOUT = 0x100000000000000
+_UNDERLINE = 0x200000000000000
+_BOLD = 0x400000000000000
+_BLINK = 0x800000000000000
+_ITALICS = 0x1000000000000000
+_STRIKETHROUGH = 0x2000000000000000
_FG_MASK = (_FG_COLOR_MASK | _FG_BASIC_COLOR | _FG_HIGH_COLOR |
_STANDOUT | _UNDERLINE | _BLINK | _BOLD | _ITALICS | _STRIKETHROUGH)
_BG_MASK = _BG_COLOR_MASK | _BG_BASIC_COLOR | _BG_HIGH_COLOR
@@ -229,6 +232,10 @@ def _gray_num_88(gnum):
return _GRAY_START_88 + gnum
+def _color_desc_true(num):
+
+ return "#%06x" %(num)
+
def _color_desc_256(num):
"""
Return a string description of color number num.
@@ -294,6 +301,23 @@ def _color_desc_88(num):
_CUBE_STEPS_88_16[b])
return 'g%d' % _GRAY_STEPS_88_101[num - _GRAY_START_88]
+def _parse_color_true(desc):
+
+ c = _parse_color_256(desc)
+ if c is not None:
+ (r, g, b) = _COLOR_VALUES_256[c]
+ return (r << 16) + (g << 8) + b
+
+ if not desc.startswith("#"):
+ return None
+ if len(desc) == 7:
+ h = desc[1:]
+ return int(h, 16)
+ elif len(desc) == 4:
+ h = "0x%s0%s0%s" %(desc[1], desc[2], desc[3])
+ return int(h, 16)
+ return None
+
def _parse_color_256(desc):
"""
Return a color number for the description desc.
@@ -382,6 +406,8 @@ def _parse_color_88(desc):
>>> _parse_color_88('g#80')
83
"""
+ if len(desc) == 7:
+ desc = desc[0:2] + desc[3] + desc[5]
if len(desc) > 4:
# keep the length within reason before parsing
return None
@@ -449,6 +475,7 @@ class AttrSpec(object):
High-color example values:
'#009' (0% red, 0% green, 60% red, like HTML colors)
+ '#23facc' (RRGGBB hex color code)
'#fcc' (100% red, 80% green, 80% blue)
'g40' (40% gray, decimal), 'g#cc' (80% gray, hex),
'#000', 'g0', 'g#00' (black),
@@ -477,8 +504,8 @@ class AttrSpec(object):
colors -- the maximum colors available for the specification
- Valid values include: 1, 16, 88 and 256. High-color
- values are only usable with 88 or 256 colors. With
+ Valid values include: 1, 16, 88, 256, and 2**24. High-color
+ values are only usable with 88, 256, or 2**24 colors. With
1 color only the foreground settings may be used.
>>> AttrSpec('dark red', 'light gray', 16)
@@ -490,9 +517,9 @@ class AttrSpec(object):
>>> AttrSpec('#ddb', '#004', 88)
AttrSpec('#ccc', '#000', colors=88)
"""
- if colors not in (1, 16, 88, 256):
+ if colors not in (1, 16, 88, 256, 2**24):
raise AttrSpecError('invalid number of colors (%d).' % colors)
- self._value = 0 | _HIGH_88_COLOR * (colors == 88)
+ self._value = 0 | _HIGH_88_COLOR * (colors == 88) | _HIGH_TRUE_COLOR * (colors == 2**24)
self.foreground = fg
self.background = bg
if self.colors > colors:
@@ -502,9 +529,11 @@ class AttrSpec(object):
foreground_basic = property(lambda s: s._value & _FG_BASIC_COLOR != 0)
foreground_high = property(lambda s: s._value & _FG_HIGH_COLOR != 0)
+ foreground_true = property(lambda s: s._value & _FG_TRUE_COLOR != 0)
foreground_number = property(lambda s: s._value & _FG_COLOR_MASK)
background_basic = property(lambda s: s._value & _BG_BASIC_COLOR != 0)
background_high = property(lambda s: s._value & _BG_HIGH_COLOR != 0)
+ background_true = property(lambda s: s._value & _BG_TRUE_COLOR != 0)
background_number = property(lambda s: (s._value & _BG_COLOR_MASK)
>> _BG_SHIFT)
italics = property(lambda s: s._value & _ITALICS != 0)
@@ -524,6 +553,8 @@ class AttrSpec(object):
return 88
if self._value & (_BG_HIGH_COLOR | _FG_HIGH_COLOR):
return 256
+ if self._value & (_BG_TRUE_COLOR | _FG_TRUE_COLOR):
+ return 2**24
if self._value & (_BG_BASIC_COLOR | _BG_BASIC_COLOR):
return 16
return 1
@@ -542,12 +573,14 @@ class AttrSpec(object):
def _foreground_color(self):
"""Return only the color component of the foreground."""
- if not (self.foreground_basic or self.foreground_high):
+ if not (self.foreground_basic or self.foreground_high or self.foreground_true):
return 'default'
if self.foreground_basic:
return _BASIC_COLORS[self.foreground_number]
if self.colors == 88:
return _color_desc_88(self.foreground_number)
+ if self.colors == 2**24:
+ return _color_desc_true(self.foreground_number)
return _color_desc_256(self.foreground_number)
def _foreground(self):
@@ -579,6 +612,9 @@ class AttrSpec(object):
elif self._value & _HIGH_88_COLOR:
scolor = _parse_color_88(part)
flags |= _FG_HIGH_COLOR
+ elif self._value & _HIGH_TRUE_COLOR:
+ scolor = _parse_color_true(part)
+ flags |= _FG_TRUE_COLOR
else:
scolor = _parse_color_256(part)
flags |= _FG_HIGH_COLOR
@@ -598,12 +634,14 @@ class AttrSpec(object):
def _background(self):
"""Return the background color."""
- if not (self.background_basic or self.background_high):
+ if not (self.background_basic or self.background_high or self.background_true):
return 'default'
if self.background_basic:
return _BASIC_COLORS[self.background_number]
if self._value & _HIGH_88_COLOR:
return _color_desc_88(self.background_number)
+ if self.colors == 2**24:
+ return _color_desc_true(self.background_number)
return _color_desc_256(self.background_number)
def _set_background(self, background):
@@ -616,6 +654,9 @@ class AttrSpec(object):
elif self._value & _HIGH_88_COLOR:
color = _parse_color_88(background)
flags |= _BG_HIGH_COLOR
+ elif self._value & _HIGH_TRUE_COLOR:
+ color = _parse_color_true(background)
+ flags |= _BG_TRUE_COLOR
else:
color = _parse_color_256(background)
flags |= _BG_HIGH_COLOR
@@ -640,19 +681,25 @@ class AttrSpec(object):
>>> AttrSpec('default', 'g92').get_rgb_values()
(None, None, None, 238, 238, 238)
"""
- if not (self.foreground_basic or self.foreground_high):
+ if not (self.foreground_basic or self.foreground_high or self.foreground_true):
vals = (None, None, None)
elif self.colors == 88:
assert self.foreground_number < 88, "Invalid AttrSpec _value"
vals = _COLOR_VALUES_88[self.foreground_number]
+ elif self.colors == 2**24:
+ h = "%06x" %(self.foreground_number)
+ vals = tuple([int(x, 16) for x in [h[0:2], h[2:4], h[4:6]]])
else:
vals = _COLOR_VALUES_256[self.foreground_number]
- if not (self.background_basic or self.background_high):
+ if not (self.background_basic or self.background_high or self.background_true):
return vals + (None, None, None)
elif self.colors == 88:
assert self.background_number < 88, "Invalid AttrSpec _value"
return vals + _COLOR_VALUES_88[self.background_number]
+ elif self.colors == 2**24:
+ h = "%06x" %(self.background_number)
+ return vals + tuple([int(x, 16) for x in [h[0:2], h[2:4], h[4:6]]])
else:
return vals + _COLOR_VALUES_256[self.background_number]
@@ -873,7 +920,7 @@ class BaseScreen(with_metaclass(signals.MetaSignals, object)):
foreground_high = foreground
if background_high is None:
background_high = background
- high_256 = AttrSpec(foreground_high, background_high, 256)
+ high_true = AttrSpec(foreground_high, background_high, 2**24)
# 'hX' where X > 15 are different in 88/256 color, use
# basic colors for 88-color mode if high colors are specified
@@ -882,7 +929,7 @@ class BaseScreen(with_metaclass(signals.MetaSignals, object)):
if not desc.startswith('h'):
return False
if ',' in desc:
- desc = desc.split(',',1)[0]
+ desc = desc.split(',',1)[0]
num = int(desc[1:], 10)
return num > 15
if large_h(foreground_high) or large_h(background_high):
@@ -891,8 +938,8 @@ class BaseScreen(with_metaclass(signals.MetaSignals, object)):
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)
+ name, basic, mono, high_88, high_true)
+ self._palette[name] = (basic, mono, high_88, high_true)
def _test():
diff --git a/urwid/escape.py b/urwid/escape.py
index b047fb0..be9cfd0 100644
--- a/urwid/escape.py
+++ b/urwid/escape.py
@@ -35,6 +35,10 @@ except ImportError:
from urwid.compat import bytes, bytes3
+# NOTE: because of circular imports (urwid.util -> urwid.escape -> urwid.util)
+# from urwid.util import is_mouse_event -- will not work here
+import urwid.util
+
within_double_byte = str_util.within_double_byte
SO = "\x0e"
@@ -384,6 +388,8 @@ def process_keyqueue(codes, more_available):
# Meta keys -- ESC+Key form
run, remaining_codes = process_keyqueue(codes[1:],
more_available)
+ if urwid.util.is_mouse_event(run[0]):
+ return ['esc'] + run, remaining_codes
if run[0] == "esc" or run[0].find("meta ") >= 0:
return ['esc']+run, remaining_codes
return ['meta '+run[0]]+run[1:], remaining_codes
diff --git a/urwid/font.py b/urwid/font.py
index e7bfe6e..30749a6 100755
--- a/urwid/font.py
+++ b/urwid/font.py
@@ -184,7 +184,7 @@ class HalfBlock5x4Font(Font):
█ █ █ ▄▀ ▄ █ █ █ █ █ █ █ █ █ ▀
▀▀ ▀▀▀ ▀▀▀▀ ▀▀ ▀ ▀▀▀ ▀▀ ▀ ▀▀ ▀▀ ▀
""", u'''
-"""######$$$$$$%%%%%&&&&&((()))******++++++,,,-----..////:::;;
+"""######$$$$$$%%%%%&&&&&((()))******++++++,,,-----..////::;;;
█▐▌ █ █ ▄▀█▀▄ ▐▌▐▌ ▄▀▄ █ █ ▄ ▄ ▄ ▐▌
▀█▀█▀ ▀▄█▄ █ ▀▄▀ ▐▌ ▐▌ ▄▄█▄▄ ▄▄█▄▄ ▄▄▄▄ █ ▀ ▀
▀█▀█▀ ▄ █ █ ▐▌▄ █ ▀▄▌▐▌ ▐▌ ▄▀▄ █ ▐▌ ▀ ▄▀
diff --git a/urwid/graphics.py b/urwid/graphics.py
index 25cd2dd..6ac0508 100755
--- a/urwid/graphics.py
+++ b/urwid/graphics.py
@@ -22,7 +22,7 @@
from __future__ import division, print_function
-from urwid.compat import with_metaclass
+from urwid.compat import ord2, with_metaclass
from urwid.util import decompose_tagmarkup, get_encoding_mode
from urwid.canvas import CompositeCanvas, CanvasJoin, TextCanvas, \
CanvasCombine, SolidCanvas
@@ -99,7 +99,8 @@ class BigText(Widget):
class LineBox(WidgetDecoration, WidgetWrap):
- def __init__(self, original_widget, title="", title_align="center",
+ def __init__(self, original_widget, title="",
+ title_align="center", title_attr=None,
tlcorner=u'┌', tline=u'─', lline=u'│',
trcorner=u'┐', blcorner=u'└', rline=u'│',
bline=u'─', brcorner=u'┘'):
@@ -109,6 +110,8 @@ class LineBox(WidgetDecoration, WidgetWrap):
Use 'title' to set an initial title text with will be centered
on top of the box.
+ Use `title_attr` to apply a specific attribute to the title text.
+
Use `title_align` to align the title to the 'left', 'right', or 'center'.
The default is 'center'.
@@ -141,7 +144,10 @@ class LineBox(WidgetDecoration, WidgetWrap):
if not tline and title:
raise ValueError('Cannot have a title when tline is empty string')
- self.title_widget = Text(self.format_title(title))
+ if title_attr:
+ self.title_widget = Text((title_attr, self.format_title(title)))
+ else:
+ self.title_widget = Text(self.format_title(title))
if tline:
if title_align not in ('left', 'center', 'right'):
@@ -394,7 +400,7 @@ class BarGraph(with_metaclass(BarGraphMeta, Widget)):
if self.bar_width is not None:
return [self.bar_width] * min(
- len(bardata), maxcol / self.bar_width)
+ len(bardata), maxcol // self.bar_width)
if len(bardata) >= maxcol:
return [1] * maxcol
@@ -936,7 +942,7 @@ class ProgressBar(Widget):
c._attr = [[(self.normal, maxcol)]]
elif ccol >= maxcol:
c._attr = [[(self.complete, maxcol)]]
- elif cs and c._text[0][ccol] == " ":
+ elif cs and ord2(c._text[0][ccol]) == 32:
t = c._text[0]
cenc = self.eighths[cs].encode("utf-8")
c._text[0] = t[:ccol] + cenc + t[ccol + 1:]
diff --git a/urwid/listbox.py b/urwid/listbox.py
index 802b1d6..d2a339f 100644
--- a/urwid/listbox.py
+++ b/urwid/listbox.py
@@ -82,61 +82,14 @@ class ListWalker(with_metaclass(signals.MetaSignals, object)):
return None, None
-class PollingListWalker(object): # NOT ListWalker subclass
- def __init__(self, contents):
- """
- contents -- list to poll for changes
-
- This class is deprecated. Use SimpleFocusListWalker instead.
- """
- import warnings
- warnings.warn("PollingListWalker is deprecated, "
- "use SimpleFocusListWalker instead.", DeprecationWarning)
-
- self.contents = contents
- if not getattr(contents, '__getitem__', None):
- raise ListWalkerError("PollingListWalker expecting list like "
- "object, got: %r" % (contents,))
- self.focus = 0
-
- def _clamp_focus(self):
- if self.focus >= len(self.contents):
- self.focus = len(self.contents)-1
-
- def get_focus(self):
- """Return (focus widget, focus position)."""
- if len(self.contents) == 0: return None, None
- self._clamp_focus()
- return self.contents[self.focus], self.focus
-
- def set_focus(self, position):
- """Set focus position."""
- # this class is deprecated, otherwise I might have fixed this:
- assert type(position) == int
- self.focus = position
-
- def get_next(self, start_from):
- """
- Return (widget after start_from, position after start_from).
- """
- pos = start_from + 1
- if len(self.contents) <= pos: return None, None
- return self.contents[pos],pos
-
- def get_prev(self, start_from):
- """
- Return (widget before start_from, position before start_from).
- """
- pos = start_from - 1
- if pos < 0: return None, None
- return self.contents[pos],pos
-
-
class SimpleListWalker(MonitoredList, ListWalker):
def __init__(self, contents):
"""
contents -- list to copy into this object
+ This class inherits :class:`MonitoredList` which means
+ it can be treated as a list.
+
Changes made to this object (when it is treated as a list) are
detected automatically and will cause ListBox objects using
this list walker to be updated.
@@ -210,6 +163,9 @@ class SimpleFocusListWalker(ListWalker, MonitoredFocusList):
"""
contents -- list to copy into this object
+ This class inherits :class:`MonitoredList` which means
+ it can be treated as a list.
+
Changes made to this object (when it is treated as a list) are
detected automatically and will cause ListBox objects using
this list walker to be updated.
@@ -280,7 +236,7 @@ class ListBox(Widget, WidgetContainerMixin):
widgets to be displayed inside the list box
:type body: ListWalker
"""
- self.body = body
+ self._set_body(body)
try:
connect_signal(self._body, "modified", self._invalidate)
except NameError:
@@ -1019,19 +975,19 @@ class ListBox(Widget, WidgetContainerMixin):
return actual_key(self._keypress_page_down((maxcol, maxrow)))
if self._command_map[key] == CURSOR_MAX_LEFT:
- return actual_key(self._keypress_max_left())
+ return actual_key(self._keypress_max_left((maxcol, maxrow)))
if self._command_map[key] == CURSOR_MAX_RIGHT:
- return actual_key(self._keypress_max_right())
+ return actual_key(self._keypress_max_right((maxcol, maxrow)))
return key
- def _keypress_max_left(self):
+ def _keypress_max_left(self, size):
self.focus_position = next(iter(self.body.positions()))
self.set_focus_valign('top')
return True
- def _keypress_max_right(self):
+ def _keypress_max_right(self, size):
self.focus_position = next(iter(self.body.positions(reverse=True)))
self.set_focus_valign('bottom')
return True
@@ -1691,5 +1647,3 @@ class ListBox(Widget, WidgetContainerMixin):
yield pos
w, pos = self._body.get_next(pos)
if not w: break
-
-
diff --git a/urwid/main_loop.py b/urwid/main_loop.py
index 10ea5e1..5149505 100755
--- a/urwid/main_loop.py
+++ b/urwid/main_loop.py
@@ -28,6 +28,7 @@ import heapq
import select
import os
import signal
+import sys
from functools import wraps
from itertools import count
from weakref import WeakKeyDictionary
@@ -504,9 +505,10 @@ class MainLoop(object):
continue
if is_mouse_event(k):
event, button, col, row = k
- if self._topmost_widget.mouse_event(self.screen_size,
- event, button, col, row, focus=True ):
- k = None
+ if hasattr(self._topmost_widget, "mouse_event"):
+ if self._topmost_widget.mouse_event(self.screen_size,
+ event, button, col, row, focus=True):
+ k = None
elif self._topmost_widget.selectable():
k = self._topmost_widget.keypress(self.screen_size, k)
if k:
@@ -888,9 +890,12 @@ class GLibEventLoop(EventLoop):
signal.SIGTERM,
signal.SIGUSR1,
signal.SIGUSR2,
- signal.SIGWINCH
]
+ # GLib supports SIGWINCH as of version 2.54.
+ if not self.GLib.check_version(2, 54, 0):
+ glib_signals.append(signal.SIGWINCH)
+
if signum not in glib_signals:
# The GLib event loop supports only the signals listed above
return
@@ -1086,6 +1091,7 @@ class TornadoEventLoop(EventLoop):
return # we already patched this instance
cls._ioloop_registry[ioloop] = idle_map = {}
+ # TODO: Add support for Tornado>=5.0.0
ioloop._impl = cls.PollProxy(ioloop._impl, idle_map)
def __init__(self, ioloop=None):
@@ -1377,7 +1383,7 @@ class AsyncioEventLoop(EventLoop):
"""
_we_started_event_loop = False
- _idle_emulation_delay = 1.0/256 # a short time (in seconds)
+ _idle_emulation_delay = 1.0/30 # a short time (in seconds)
def __init__(self, **kwargs):
if 'loop' in kwargs:
@@ -1404,7 +1410,12 @@ class AsyncioEventLoop(EventLoop):
Returns True if the alarm exists, False otherwise
"""
- existed = not handle._cancelled
+ cancelled = (
+ handle.cancelled()
+ if getattr(handle, 'cancelled', None)
+ else handle._cancelled
+ )
+ existed = not cancelled
handle.cancel()
return existed
@@ -1466,8 +1477,7 @@ class AsyncioEventLoop(EventLoop):
loop.stop()
if not isinstance(exc, ExitMainLoop):
# Store the exc_info so we can re-raise after the loop stops
- import sys
- self._exc_info = sys.exc_info()
+ self._exc_info = (type(exc), exc, exc.__traceback__)
else:
loop.default_exception_handler(context)
@@ -1484,6 +1494,12 @@ class AsyncioEventLoop(EventLoop):
reraise(*exc_info)
+# Import Trio's event loop only if we are on Python 3.5 or above (async def is
+# not supported in earlier versions).
+if sys.version_info >= (3, 5):
+ from ._async_kw_event_loop import TrioEventLoop
+
+
def _refl(name, rval=None, exit=False):
"""
This function is used to test the main loop classes.
diff --git a/urwid/numedit.py b/urwid/numedit.py
new file mode 100644
index 0000000..3af4e17
--- /dev/null
+++ b/urwid/numedit.py
@@ -0,0 +1,286 @@
+# -*- coding: utf-8 -*-
+#
+# Urwid basic widget classes
+# Copyright (C) 2004-2012 Ian Ward
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Urwid web site: http://excess.org/urwid/
+
+
+from urwid import Edit
+from decimal import Decimal
+import re
+
+
+class NumEdit(Edit):
+ """NumEdit - edit numerical types
+
+ based on the characters in 'allowed' different numerical types
+ can be edited:
+ + regular int: 0123456789
+ + regular float: 0123456789.
+ + regular oct: 01234567
+ + regular hex: 0123456789abcdef
+ """
+ ALLOWED = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+ def __init__(self, allowed, caption, default, trimLeadingZeros=True):
+ super(NumEdit, self).__init__(caption, default)
+ self._allowed = allowed
+ self.trimLeadingZeros = trimLeadingZeros
+
+ def valid_char(self, ch):
+ """
+ Return true for allowed characters.
+ """
+ return len(ch) == 1 and ch.upper() in self._allowed
+
+ def keypress(self, size, key):
+ """
+ Handle editing keystrokes. Remove leading zeros.
+
+ >>> e, size = NumEdit("0123456789", u"", "5002"), (10,)
+ >>> e.keypress(size, 'home')
+ >>> e.keypress(size, 'delete')
+ >>> assert e.edit_text == "002"
+ >>> e.keypress(size, 'end')
+ >>> assert e.edit_text == "2"
+ >>> # binary only
+ >>> e, size = NumEdit("01", u"", ""), (10,)
+ >>> assert e.edit_text == ""
+ >>> e.keypress(size, '1')
+ >>> e.keypress(size, '0')
+ >>> e.keypress(size, '1')
+ >>> assert e.edit_text == "101"
+ """
+ (maxcol,) = size
+ unhandled = Edit.keypress(self, (maxcol,), key)
+
+ if not unhandled:
+ if self.trimLeadingZeros:
+ # trim leading zeros
+ while self.edit_pos > 0 and self.edit_text[:1] == "0":
+ self.set_edit_pos(self.edit_pos - 1)
+ self.set_edit_text(self.edit_text[1:])
+
+ return unhandled
+
+
+class IntegerEdit(NumEdit):
+ """Edit widget for integer values"""
+
+ def __init__(self, caption="", default=None, base=10):
+ """
+ caption -- caption markup
+ default -- default edit value
+
+ >>> IntegerEdit(u"", 42)
+ <IntegerEdit selectable flow widget '42' edit_pos=2>
+ >>> e, size = IntegerEdit(u"", "5002"), (10,)
+ >>> e.keypress(size, 'home')
+ >>> e.keypress(size, 'delete')
+ >>> assert e.edit_text == "002"
+ >>> e.keypress(size, 'end')
+ >>> assert e.edit_text == "2"
+ >>> e.keypress(size, '9')
+ >>> e.keypress(size, '0')
+ >>> assert e.edit_text == "290"
+ >>> e, size = IntegerEdit("", ""), (10,)
+ >>> assert e.value() is None
+ >>> # binary
+ >>> e, size = IntegerEdit(u"", "1010", base=2), (10,)
+ >>> e.keypress(size, 'end')
+ >>> e.keypress(size, '1')
+ >>> assert e.edit_text == "10101"
+ >>> assert e.value() == Decimal("21")
+ >>> # HEX
+ >>> e, size = IntegerEdit(u"", "10", base=16), (10,)
+ >>> e.keypress(size, 'end')
+ >>> e.keypress(size, 'F')
+ >>> e.keypress(size, 'f')
+ >>> assert e.edit_text == "10Ff"
+ >>> assert e.keypress(size, 'G') == 'G' # unhandled key
+ >>> assert e.edit_text == "10Ff"
+ >>> # keep leading 0's when not base 10
+ >>> e, size = IntegerEdit(u"", "10FF", base=16), (10,)
+ >>> assert e.edit_text == "10FF"
+ >>> assert e.value() == Decimal("4351")
+ >>> e.keypress(size, 'home')
+ >>> e.keypress(size, 'delete')
+ >>> e.keypress(size, '0')
+ >>> assert e.edit_text == "00FF"
+ >>> # test exception on incompatable value for base
+ >>> e, size = IntegerEdit(u"", "10FG", base=16), (10,)
+ Traceback (most recent call last):
+ ...
+ ValueError: invalid value: 10FG for base 16
+ >>> # test exception on float init value
+ >>> e, size = IntegerEdit(u"", 10.0), (10,)
+ Traceback (most recent call last):
+ ...
+ ValueError: default: Only 'str', 'int', 'long' or Decimal input allowed
+ >>> e, size = IntegerEdit(u"", Decimal("10.0")), (10,)
+ Traceback (most recent call last):
+ ...
+ ValueError: not an 'integer Decimal' instance
+ """
+ self.base = base
+ val = ""
+ allowed_chars = self.ALLOWED[:self.base]
+ if default is not None:
+ if not isinstance(default, (int, str, Decimal)):
+ raise ValueError("default: Only 'str', 'int', "
+ "'long' or Decimal input allowed")
+
+ # convert to a long first, this will raise a ValueError
+ # in case a float is passed or some other error
+ if isinstance(default, str) and len(default):
+ # check if it is a valid initial value
+ validation_re = "^[{}]+$".format(allowed_chars)
+ if not re.match(validation_re, str(default), re.IGNORECASE):
+ raise ValueError("invalid value: {} for base {}".format(
+ default, base))
+
+ elif isinstance(default, Decimal):
+ # a Decimal instance with no fractional part
+ if default.as_tuple()[2] != 0:
+ raise ValueError("not an 'integer Decimal' instance")
+
+ # convert possible int, long or Decimal to str
+ val = str(default)
+
+ super(IntegerEdit, self).__init__(allowed_chars, caption, val,
+ trimLeadingZeros=(self.base == 10))
+
+ def value(self):
+ """
+ Return the numeric value of self.edit_text.
+
+ >>> e, size = IntegerEdit(), (10,)
+ >>> e.keypress(size, '5')
+ >>> e.keypress(size, '1')
+ >>> assert e.value() == 51
+ """
+ if self.edit_text:
+ return Decimal(int(self.edit_text, self.base))
+
+ return None
+
+
+class FloatEdit(NumEdit):
+ """Edit widget for float values."""
+
+ def __init__(self, caption="", default=None,
+ preserveSignificance=True, decimalSeparator='.'):
+ """
+ caption -- caption markup
+ default -- default edit value
+ preserveSignificance -- return value has the same signif. as default
+ decimalSeparator -- use '.' as separator by default, optionally a ','
+
+ >>> FloatEdit(u"", "1.065434")
+ <FloatEdit selectable flow widget '1.065434' edit_pos=8>
+ >>> e, size = FloatEdit(u"", "1.065434"), (10,)
+ >>> e.keypress(size, 'home')
+ >>> e.keypress(size, 'delete')
+ >>> assert e.edit_text == ".065434"
+ >>> e.keypress(size, 'end')
+ >>> e.keypress(size, 'backspace')
+ >>> assert e.edit_text == ".06543"
+ >>> e, size = FloatEdit(), (10,)
+ >>> e.keypress(size, '5')
+ >>> e.keypress(size, '1')
+ >>> e.keypress(size, '.')
+ >>> e.keypress(size, '5')
+ >>> e.keypress(size, '1')
+ >>> assert e.value() == Decimal("51.51")
+ >>> e, size = FloatEdit(decimalSeparator=":"), (10,)
+ Traceback (most recent call last):
+ ...
+ ValueError: invalid decimalSeparator: :
+ >>> e, size = FloatEdit(decimalSeparator=","), (10,)
+ >>> e.keypress(size, '5')
+ >>> e.keypress(size, '1')
+ >>> e.keypress(size, ',')
+ >>> e.keypress(size, '5')
+ >>> e.keypress(size, '1')
+ >>> assert e.edit_text == "51,51"
+ >>> e, size = FloatEdit("", "3.1415", preserveSignificance=True), (10,)
+ >>> e.keypress(size, 'end')
+ >>> e.keypress(size, 'backspace')
+ >>> e.keypress(size, 'backspace')
+ >>> assert e.edit_text == "3.14"
+ >>> assert e.value() == Decimal("3.1400")
+ >>> e.keypress(size, '1')
+ >>> e.keypress(size, '5')
+ >>> e.keypress(size, '9')
+ >>> assert e.value() == Decimal("3.1416")
+ >>> e, size = FloatEdit("", ""), (10,)
+ >>> assert e.value() is None
+ >>> e, size = FloatEdit(u"", 10.0), (10,)
+ Traceback (most recent call last):
+ ...
+ ValueError: default: Only 'str', 'int', 'long' or Decimal input allowed
+ """
+ self.significance = None
+ self._decimalSeparator = decimalSeparator
+ if decimalSeparator not in ['.', ',']:
+ raise ValueError("invalid decimalSeparator: {}".format(
+ decimalSeparator))
+
+ val = ""
+ if default is not None and default is not "":
+ if not isinstance(default, (int, str, Decimal)):
+ raise ValueError("default: Only 'str', 'int', "
+ "'long' or Decimal input allowed")
+
+ if isinstance(default, str) and len(default):
+ # check if it is a float, raises a ValueError otherwise
+ float(default)
+ default = Decimal(default)
+
+ if preserveSignificance:
+ self.significance = abs(default.as_tuple()[2])
+
+ val = str(default)
+
+ super(FloatEdit, self).__init__(self.ALLOWED[0:10] + decimalSeparator,
+ caption, val)
+
+ def value(self):
+ """
+ Return the numeric value of self.edit_text.
+ """
+ if self.edit_text:
+ # integer part (before .) and fractional part (after .)
+ fmt = "{ip}.{fp}"
+ if self.significance:
+ # in case of preserved significance, construct the
+ # format string to fill with trailing 0
+ fmt = "{{ip}}.{{fp:<0{sig}d}}".format(sig=self.significance)
+
+ # get the ip and fp, handles also the case that there is no '.'
+ ip, fp = ([v for v in
+ self.edit_text.split(self._decimalSeparator)] + [0])[0:2]
+
+ # in case the fp part surpasses the significance we round it
+ if self.significance and len(str(fp)) > self.significance:
+ fp = float(fp) / 10**(len(str(fp)) - self.significance)
+ fp = int(round(fp))
+
+ return Decimal(fmt.format(ip=ip, fp=int(fp)))
+
+ return None
diff --git a/urwid/raw_display.py b/urwid/raw_display.py
index e5275bd..f07c50d 100644
--- a/urwid/raw_display.py
+++ b/urwid/raw_display.py
@@ -91,9 +91,17 @@ class Screen(BaseScreen, RealTerminal):
self._resize_pipe_rd, self._resize_pipe_wr = os.pipe()
fcntl.fcntl(self._resize_pipe_rd, fcntl.F_SETFL, os.O_NONBLOCK)
+ def _input_fileno(self):
+ """Returns the fileno of the input stream, or None if it doesn't have one. A stream without a fileno can't participate in whatever.
+ """
+ if hasattr(self._term_input_file, 'fileno'):
+ return self._term_input_file.fileno()
+ else:
+ return None
+
def _on_update_palette_entry(self, name, *attrspecs):
# copy the attribute to a dictionary containing the escape seqences
- a = attrspecs[{16:0,1:1,88:2,256:3}[self.colors]]
+ a = attrspecs[{16:0,1:1,88:2,256:3,2**24:3}[self.colors]]
self._pal_attrspec[name] = a
self._pal_escape[name] = self._attrspec_to_escape(a)
@@ -216,8 +224,8 @@ class Screen(BaseScreen, RealTerminal):
else:
self._rows_used = 0
- fd = self._term_input_file.fileno()
- if os.isatty(fd):
+ fd = self._input_fileno()
+ if fd is not None and os.isatty(fd):
self._old_termios_settings = termios.tcgetattr(fd)
tty.setcbreak(fd)
@@ -244,10 +252,9 @@ class Screen(BaseScreen, RealTerminal):
self.signal_restore()
- fd = self._term_input_file.fileno()
- if os.isatty(fd):
- termios.tcsetattr(fd, termios.TCSADRAIN,
- self._old_termios_settings)
+ fd = self._input_fileno()
+ if fd is not None and os.isatty(fd):
+ termios.tcsetattr(fd, termios.TCSADRAIN, self._old_termios_settings)
self._mouse_tracking(False)
@@ -367,11 +374,17 @@ class Screen(BaseScreen, RealTerminal):
polled in external event loops to check for user input.
Use this method if you are implementing your own event loop.
+
+ This method is only called by `hook_event_loop`, so if you override
+ that, you can safely ignore this.
"""
if not self._started:
return []
- fd_list = [self._term_input_file.fileno(), self._resize_pipe_rd]
+ fd_list = [self._resize_pipe_rd]
+ fd = self._input_fileno()
+ if fd is not None:
+ fd_list.append(fd)
if self.gpm_mev is not None:
fd_list.append(self.gpm_mev.stdout.fileno())
return fd_list
@@ -525,7 +538,10 @@ class Screen(BaseScreen, RealTerminal):
def _wait_for_input_ready(self, timeout):
ready = None
- fd_list = [self._term_input_file.fileno()]
+ fd_list = []
+ fd = self._input_fileno()
+ if fd is not None:
+ fd_list.append(fd)
if self.gpm_mev is not None:
fd_list.append(self.gpm_mev.stdout.fileno())
while True:
@@ -550,8 +566,9 @@ class Screen(BaseScreen, RealTerminal):
if self.gpm_mev is not None:
if self.gpm_mev.stdout.fileno() in ready:
self.gpm_event_pending = True
- if self._term_input_file.fileno() in ready:
- return ord(os.read(self._term_input_file.fileno(), 1))
+ fd = self._input_fileno()
+ if fd is not None and fd in ready:
+ return ord(os.read(fd, 1))
return -1
def _encode_gpm_event( self ):
@@ -648,9 +665,10 @@ class Screen(BaseScreen, RealTerminal):
"""Return the terminal dimensions (num columns, num rows)."""
y, x = 24, 80
try:
- buf = fcntl.ioctl(self._term_output_file.fileno(),
- termios.TIOCGWINSZ, ' '*4)
- y, x = struct.unpack('hh', buf)
+ if hasattr(self._term_output_file, 'fileno'):
+ 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
@@ -931,7 +949,9 @@ class Screen(BaseScreen, RealTerminal):
bg = escape.ESC + '[2;%d}' % (a.background_number,)
return fg + bg
- if a.foreground_high:
+ if a.foreground_true:
+ fg = "38;2;%d;%d;%d" %(a.get_rgb_values()[0:3])
+ elif a.foreground_high:
fg = "38;5;%d" % a.foreground_number
elif a.foreground_basic:
if a.foreground_number > 7:
@@ -946,7 +966,9 @@ class Screen(BaseScreen, RealTerminal):
st = ("1;" * a.bold + "3;" * a.italics +
"4;" * a.underline + "5;" * a.blink +
"7;" * a.standout + "9;" * a.strikethrough)
- if a.background_high:
+ if a.background_true:
+ bg = "48;2;%d;%d;%d" %(a.get_rgb_values()[3:6])
+ elif a.background_high:
bg = "48;5;%d" % a.background_number
elif a.background_basic:
if a.background_number > 7:
@@ -965,7 +987,7 @@ class Screen(BaseScreen, RealTerminal):
def set_terminal_properties(self, colors=None, bright_is_bold=None,
has_underline=None):
"""
- colors -- number of colors terminal supports (1, 16, 88 or 256)
+ colors -- number of colors terminal supports (1, 16, 88, 256, or 2**24)
or None to leave unchanged
bright_is_bold -- set to True if this terminal uses the bold
setting to create bright colors (numbers 8-15), set to False
@@ -1005,15 +1027,19 @@ class Screen(BaseScreen, RealTerminal):
"""
if self.colors == 1:
return
+ elif self.colors == 2**24:
+ colors = 256
+ else:
+ colors = self.colors
def rgb_values(n):
- if self.colors == 16:
+ if colors == 16:
aspec = AttrSpec("h%d"%n, "", 256)
else:
- aspec = AttrSpec("h%d"%n, "", self.colors)
+ aspec = AttrSpec("h%d"%n, "", colors)
return aspec.get_rgb_values()[:3]
- entries = [(n,) + rgb_values(n) for n in range(self.colors)]
+ entries = [(n,) + rgb_values(n) for n in range(min(colors, 256))]
self.modify_terminal_palette(entries)
diff --git a/urwid/split_repr.py b/urwid/split_repr.py
index 3d7cbeb..42d4b31 100755
--- a/urwid/split_repr.py
+++ b/urwid/split_repr.py
@@ -21,8 +21,11 @@
from __future__ import division, print_function
-from inspect import getargspec
from urwid.compat import PYTHON3, bytes
+if not PYTHON3:
+ from inspect import getargspec
+else:
+ from inspect import getfullargspec
def split_repr(self):
"""
@@ -122,7 +125,10 @@ def remove_defaults(d, fn):
>>> Foo()
<Foo object>
"""
- args, varargs, varkw, defaults = getargspec(fn)
+ if not PYTHON3:
+ args, varargs, varkw, defaults = getargspec(fn)
+ else:
+ args, varargs, varkw, defaults, _, _, _ = getfullargspec(fn)
# ignore *varargs and **kwargs
if varkw:
diff --git a/urwid/tests/test_canvas.py b/urwid/tests/test_canvas.py
index 40a303b..0391593 100644
--- a/urwid/tests/test_canvas.py
+++ b/urwid/tests/test_canvas.py
@@ -138,15 +138,15 @@ class ShardBodyTest(unittest.TestCase):
class ShardsTrimTest(unittest.TestCase):
def sttop(self, shards, top, expected):
result = canvas.shards_trim_top(shards, top)
- assert result == expected, "got: %r expected: %r" (result, expected)
+ assert result == expected, "got: %r expected: %r" % (result, expected)
def strows(self, shards, rows, expected):
result = canvas.shards_trim_rows(shards, rows)
- assert result == expected, "got: %r expected: %r" (result, expected)
+ assert result == expected, "got: %r expected: %r" % (result, expected)
def stsides(self, shards, left, cols, expected):
result = canvas.shards_trim_sides(shards, left, cols)
- assert result == expected, "got: %r expected: %r" (result, expected)
+ assert result == expected, "got: %r expected: %r" % (result, expected)
def test1(self):
@@ -229,7 +229,7 @@ class ShardsTrimTest(unittest.TestCase):
class ShardsJoinTest(unittest.TestCase):
def sjt(self, shard_lists, expected):
result = canvas.shards_join(shard_lists)
- assert result == expected, "got: %r expected: %r" (result, expected)
+ assert result == expected, "got: %r expected: %r" % (result, expected)
def test(self):
shards1 = [(5, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz")]),
diff --git a/urwid/tests/test_container.py b/urwid/tests/test_container.py
index 6ddb909..a8ed15b 100644
--- a/urwid/tests/test_container.py
+++ b/urwid/tests/test_container.py
@@ -314,8 +314,8 @@ class WidgetSquishTest(unittest.TestCase):
t(1, True)
def test_listbox(self):
- self.wstest(urwid.ListBox([]))
- self.wstest(urwid.ListBox([urwid.Text("hello")]))
+ self.wstest(urwid.ListBox(urwid.SimpleListWalker([])))
+ self.wstest(urwid.ListBox(urwid.SimpleListWalker([urwid.Text("hello")])))
def test_bargraph(self):
self.wstest(urwid.BarGraph(['foo','bar']))
diff --git a/urwid/tests/test_doctests.py b/urwid/tests/test_doctests.py
index 611baf3..9b72070 100644
--- a/urwid/tests/test_doctests.py
+++ b/urwid/tests/test_doctests.py
@@ -2,6 +2,7 @@ import unittest
import doctest
import urwid
+import urwid.numedit
def load_tests(loader, tests, ignore):
module_doctests = [
@@ -10,6 +11,7 @@ def load_tests(loader, tests, ignore):
urwid.decoration,
urwid.display_common,
urwid.main_loop,
+ urwid.numedit,
urwid.monitored_list,
urwid.raw_display,
'urwid.split_repr', # override function with same name
diff --git a/urwid/tests/test_escapes.py b/urwid/tests/test_escapes.py
new file mode 100644
index 0000000..7b1fe53
--- /dev/null
+++ b/urwid/tests/test_escapes.py
@@ -0,0 +1,79 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+""" Tests covering escape sequences processing """
+
+import unittest
+
+import urwid.escape
+
+
+class InputEscapeSequenceParserTest(unittest.TestCase):
+ """ Tests for parser of input escape sequences """
+
+ def test_bare_escape(self):
+ codes = [27]
+ expected = ['esc']
+ actual, rest = urwid.escape.process_keyqueue(codes, more_available=False)
+ self.assertListEqual(expected, actual)
+ self.assertListEqual([], rest)
+
+ def test_meta(self):
+ codes = [27, ord('4'), ord('2')]
+ expected = ['meta 4']
+ actual, rest = urwid.escape.process_keyqueue(codes, more_available=False)
+ self.assertListEqual(expected, actual)
+ self.assertListEqual([ord('2')], rest)
+
+ def test_shift_arrows(self):
+ codes = [27, ord('['), ord('a')]
+ expected = ['shift up']
+ actual, rest = urwid.escape.process_keyqueue(codes, more_available=False)
+ self.assertListEqual(expected, actual)
+ self.assertListEqual([], rest)
+
+ def test_ctrl_pgup(self):
+ codes = [27, 91, 53, 59, 53, 126]
+ expected = ['ctrl page up']
+ actual, rest = urwid.escape.process_keyqueue(codes, more_available=False)
+ self.assertListEqual(expected, actual)
+ self.assertListEqual([], rest)
+
+ def test_esc_meta_1(self):
+ codes = [27, 27, 49]
+ expected = ['esc', 'meta 1']
+ actual, rest = urwid.escape.process_keyqueue(codes, more_available=False)
+ self.assertListEqual(expected, actual)
+ self.assertListEqual([], rest)
+
+ def test_midsequence(self):
+ # '[11~' is F1, '[12~' is F2, etc
+ codes = [27, ord('['), ord('1')]
+
+ with self.assertRaises(urwid.escape.MoreInputRequired):
+ urwid.escape.process_keyqueue(codes, more_available=True)
+
+ actual, rest = urwid.escape.process_keyqueue(codes, more_available=False)
+ self.assertListEqual(['meta ['], actual)
+ self.assertListEqual([ord('1')], rest)
+
+ def test_mouse_press(self):
+ codes = [27, 91, 77, 32, 41, 48]
+ expected = [('mouse press', 1.0, 8, 15)]
+ actual, rest = urwid.escape.process_keyqueue(codes, more_available=False)
+ self.assertListEqual(expected, actual)
+ self.assertListEqual([], rest)
+
+ def test_bug_104(self):
+ """ GH #104: click-Esc & Esc-click crashes urwid apps """
+ codes = [27, 27, 91, 77, 32, 127, 59]
+ expected = ['esc', ('mouse press', 1.0, 94, 26)]
+ actual, rest = urwid.escape.process_keyqueue(codes, more_available=False)
+ self.assertListEqual(expected, actual)
+ self.assertListEqual([], rest)
+
+ codes = [27, 27, 91, 77, 35, 120, 59]
+ expected = ['esc', ('mouse release', 0, 87, 26)]
+ actual, rest = urwid.escape.process_keyqueue(codes, more_available=False)
+ self.assertListEqual(expected, actual)
+ self.assertListEqual([], rest)
diff --git a/urwid/tests/test_event_loops.py b/urwid/tests/test_event_loops.py
index fb12819..d4afe9c 100644
--- a/urwid/tests/test_event_loops.py
+++ b/urwid/tests/test_event_loops.py
@@ -25,16 +25,24 @@ class EventLoopTestMixin(object):
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 step1():
+ self.assertTrue(evl.remove_alarm(handle))
+ self.assertFalse(evl.remove_alarm(handle))
+ raise urwid.ExitMainLoop
+ evl.alarm(0, step1)
+ evl.run()
def test_remove_watch_file(self):
evl = self.evl
fd_r, fd_w = os.pipe()
try:
handle = evl.watch_file(fd_r, lambda: None)
- self.assertTrue(evl.remove_watch_file(handle))
- self.assertFalse(evl.remove_watch_file(handle))
+ def step1():
+ self.assertTrue(evl.remove_watch_file(handle))
+ self.assertFalse(evl.remove_watch_file(handle))
+ raise urwid.ExitMainLoop
+ evl.alarm(0, step1)
+ evl.run()
finally:
os.close(fd_r)
os.close(fd_w)
@@ -62,7 +70,7 @@ class EventLoopTestMixin(object):
self.assertEqual(idle_handle, 1)
evl.run()
self.assertTrue("hello" in out, out)
- self.assertTrue("clean exit"in out, out)
+ self.assertTrue("clean exit" in out, out)
handle = evl.watch_file(rd, exit_clean)
del out[:]
evl.run()
@@ -118,6 +126,12 @@ else:
def test_event_loop(self):
pass
+ def test_remove_alarm(self):
+ pass
+
+ def test_remove_watch_file(self):
+ pass
+
def test_run(self):
evl = self.evl
out = []
@@ -129,6 +143,21 @@ else:
out.append("hello")
def say_waiting():
out.append("waiting")
+ def test_remove_alarm():
+ handle = evl.alarm(50, lambda: None)
+ self.assertTrue(evl.remove_alarm(handle))
+ self.assertFalse(evl.remove_alarm(handle))
+ out.append("remove_alarm ok")
+ def test_remove_watch_file():
+ fd_r, fd_w = os.pipe()
+ try:
+ handle = evl.watch_file(fd_r, lambda: None)
+ self.assertTrue(evl.remove_watch_file(handle))
+ self.assertFalse(evl.remove_watch_file(handle))
+ finally:
+ os.close(fd_r)
+ os.close(fd_w)
+ out.append("remove_watch_file ok")
def exit_clean():
out.append("clean exit")
raise urwid.ExitMainLoop
@@ -137,11 +166,14 @@ else:
handle = evl.watch_file(rd, step2)
handle = evl.alarm(0.1, exit_clean)
handle = evl.alarm(0.05, say_hello)
+ handle = evl.alarm(0.06, test_remove_alarm)
+ handle = evl.alarm(0.07, test_remove_watch_file)
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("remove_alarm ok" in out, out)
self.assertTrue("clean exit" in out, out)
def test_error(self):
@@ -154,6 +186,10 @@ try:
except ImportError:
pass
else:
+ if not hasattr(asyncio, 'ensure_future'):
+ #-- Python < 3.4.4 (e.g. Debian Jessie)
+ asyncio.ensure_future = getattr(asyncio, 'async')
+
class AsyncioEventLoopTest(unittest.TestCase, EventLoopTestMixin):
def setUp(self):
self.evl = urwid.AsyncioEventLoop()
@@ -164,3 +200,31 @@ else:
evl = self.evl
evl.alarm(0.5, lambda: 1 / 0) # Simulate error in event loop
self.assertRaises(ZeroDivisionError, evl.run)
+
+ def test_coroutine_error(self):
+ evl = self.evl
+
+ @asyncio.coroutine
+ def error_coro():
+ result = 1 / 0 # Simulate error in coroutine
+ yield result
+
+ asyncio.ensure_future(error_coro())
+ self.assertRaises(ZeroDivisionError, evl.run)
+
+
+try:
+ import trio
+except ImportError:
+ pass
+else:
+ class TrioEventLoopTest(unittest.TestCase, EventLoopTestMixin):
+ def setUp(self):
+ self.evl = urwid.TrioEventLoop()
+
+ _expected_idle_handle = None
+
+ def test_error(self):
+ evl = self.evl
+ evl.alarm(0.5, lambda: 1 / 0) # Simulate error in event loop
+ self.assertRaises(ZeroDivisionError, evl.run)
diff --git a/urwid/tests/test_listbox.py b/urwid/tests/test_listbox.py
index 9ed93c2..adeb8d5 100644
--- a/urwid/tests/test_listbox.py
+++ b/urwid/tests/test_listbox.py
@@ -9,7 +9,7 @@ class ListBoxCalculateVisibleTest(unittest.TestCase):
def cvtest(self, desc, body, focus, offset_rows, inset_fraction,
exp_offset_inset, exp_cur ):
- lbox = urwid.ListBox(body)
+ lbox = urwid.ListBox(urwid.SimpleListWalker(body))
lbox.body.set_focus( focus )
lbox.offset_rows = offset_rows
lbox.inset_fraction = inset_fraction
@@ -99,7 +99,7 @@ class ListBoxChangeFocusTest(unittest.TestCase):
coming_from, cursor, snap_rows,
exp_offset_rows, exp_inset_fraction, exp_cur ):
- lbox = urwid.ListBox(body)
+ lbox = urwid.ListBox(urwid.SimpleListWalker(body))
lbox.change_focus( (4,5), pos, offset_inset, coming_from,
cursor, snap_rows )
@@ -207,7 +207,7 @@ class ListBoxChangeFocusTest(unittest.TestCase):
class ListBoxRenderTest(unittest.TestCase):
def ltest(self,desc,body,focus,offset_inset_rows,exp_text,exp_cur):
exp_text = [B(t) for t in exp_text]
- lbox = urwid.ListBox(body)
+ lbox = urwid.ListBox(urwid.SimpleListWalker(body))
lbox.body.set_focus( focus )
lbox.shift_focus((4,10), offset_inset_rows )
canvas = lbox.render( (4,5), focus=1 )
@@ -305,7 +305,7 @@ class ListBoxKeypressTest(unittest.TestCase):
exp_focus, exp_offset_inset, exp_cur, lbox = None):
if lbox is None:
- lbox = urwid.ListBox(body)
+ lbox = urwid.ListBox(urwid.SimpleListWalker(body))
lbox.body.set_focus( focus )
lbox.shift_focus((4,10), offset_inset )
diff --git a/urwid/tests/test_util.py b/urwid/tests/test_util.py
index 5f0531d..7e0acdb 100644
--- a/urwid/tests/test_util.py
+++ b/urwid/tests/test_util.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import unittest
+import locale
import urwid
from urwid import util
@@ -176,3 +177,35 @@ class TagMarkupTest(unittest.TestCase):
def test_bad_type(self):
self.assertRaises(urwid.TagMarkupException, lambda:
urwid.decompose_tagmarkup(5))
+
+class RleTest(unittest.TestCase):
+ def test_rle_prepend(self):
+ rle0 = [('A', 10), ('B', 15)]
+ # the rle functions are mutating, so make a few copies of rle0
+ rle1, rle2 = rle0[:], rle0[:]
+ util.rle_prepend_modify(rle1, ('A', 3))
+ util.rle_prepend_modify(rle2, ('X', 2))
+ self.assertListEqual(rle1, [('A', 13), ('B', 15)])
+ self.assertListEqual(rle2, [('X', 2), ('A', 10), ('B', 15)])
+
+ def test_rle_append(self):
+ rle0 = [('A', 10), ('B', 15)]
+ rle3, rle4 = rle0[:], rle0[:]
+ util.rle_append_modify(rle3, ('B', 5))
+ util.rle_append_modify(rle4, ('K', 1))
+ self.assertListEqual(rle3, [('A', 10), ('B', 20)])
+ self.assertListEqual(rle4, [('A', 10), ('B', 15), ('K', 1)])
+
+class PortabilityTest(unittest.TestCase):
+ def test_locale(self):
+ initial = locale.getlocale()
+
+ locale.setlocale(locale.LC_ALL, (None, None))
+ util.detect_encoding()
+ self.assertEqual(locale.getlocale(), (None, None))
+
+ locale.setlocale(locale.LC_ALL, ('en_US', 'UTF-8'))
+ util.detect_encoding()
+ self.assertEqual(locale.getlocale(), ('en_US', 'UTF-8'))
+
+ locale.setlocale(locale.LC_ALL, initial)
diff --git a/urwid/tests/util.py b/urwid/tests/util.py
index 9808e2c..0c8c928 100644
--- a/urwid/tests/util.py
+++ b/urwid/tests/util.py
@@ -2,7 +2,7 @@ import urwid
class SelectableText(urwid.Text):
def selectable(self):
- return 1
+ return True
def keypress(self, size, key):
return key
diff --git a/urwid/text_layout.py b/urwid/text_layout.py
index d8663b6..d136021 100644
--- a/urwid/text_layout.py
+++ b/urwid/text_layout.py
@@ -1,4 +1,5 @@
#!/usr/bin/python
+# -*- coding: utf-8 -*-
#
# Urwid Text Layout classes
# Copyright (C) 2004-2011 Ian Ward
@@ -76,8 +77,8 @@ class StandardTextLayout(TextLayout):
"""Return True if align is 'left', 'center' or 'right'."""
return align in ('left', 'center', 'right')
def supports_wrap_mode(self, wrap):
- """Return True if wrap is 'any', 'space' or 'clip'."""
- return wrap in ('any', 'space', 'clip')
+ """Return True if wrap is 'any', 'space', 'clip' or 'ellipsis'."""
+ return wrap in ('any', 'space', 'clip', 'ellipsis')
def layout(self, text, width, align, wrap ):
"""Return a layout structure for text."""
try:
@@ -136,16 +137,34 @@ class StandardTextLayout(TextLayout):
sp_o = ord(sp_o)
b = []
p = 0
- if wrap == 'clip':
+ if wrap in ('clip', 'ellipsis'):
# no wrapping to calculate, so it's easy.
while p<=len(text):
n_cr = text.find(nl, p)
if n_cr == -1:
n_cr = len(text)
sc = calc_width(text, p, n_cr)
- l = [(0,n_cr)]
- if p!=n_cr:
- l = [(sc, p, n_cr)] + l
+
+ # trim line to max width if needed, add ellipsis if trimmed
+ if wrap == 'ellipsis' and sc > width:
+ trimmed = True
+ spos, n_end, pad_left, pad_right = calc_trim_text(text, p, n_cr, 0, width-1)
+ # pad_left should be 0, because the start_col parameter was 0 (no trimming on the left)
+ # similarly spos should not be changed from p
+ assert pad_left == 0
+ assert spos == p
+ sc = width - 1 - pad_right
+ else:
+ trimmed = False
+ n_end = n_cr
+ pad_right = 0
+
+ l = []
+ if p!=n_end:
+ l += [(sc, p, n_end)]
+ if trimmed:
+ l += [(1, n_end, u'…'.encode())]
+ l += [(pad_right,n_end)]
b.append(l)
p = n_cr+1
return b
diff --git a/urwid/treetools.py b/urwid/treetools.py
index f2b1aad..2d31731 100644
--- a/urwid/treetools.py
+++ b/urwid/treetools.py
@@ -427,10 +427,6 @@ class TreeListBox(urwid.ListBox):
self.move_focus_to_parent(size)
elif input == '-':
self.collapse_focus_parent(size)
- elif input == 'home':
- self.focus_home(size)
- elif input == 'end':
- self.focus_end(size)
else:
return input
@@ -467,6 +463,12 @@ class TreeListBox(urwid.ListBox):
self.change_focus(size, pos.get_parent())
+ def _keypress_max_left(self, size):
+ return self.focus_home(size)
+
+ def _keypress_max_right(self, size):
+ return self.focus_end(size)
+
def focus_home(self, size):
"""Move focus to very top."""
diff --git a/urwid/util.py b/urwid/util.py
index f57c898..17c049d 100644
--- a/urwid/util.py
+++ b/urwid/util.py
@@ -41,6 +41,7 @@ within_double_byte = str_util.within_double_byte
def detect_encoding():
# Try to determine if using a supported double-byte encoding
import locale
+ initial = locale.getlocale()
try:
try:
locale.setlocale(locale.LC_ALL, "")
@@ -53,6 +54,8 @@ def detect_encoding():
return ""
else:
raise
+ finally:
+ locale.setlocale(locale.LC_ALL, initial)
if 'detected_encoding' not in locals():
detected_encoding = detect_encoding()
@@ -223,8 +226,8 @@ def trim_text_attr_cs( text, attr, cs, start_col, end_col ):
cstr = rle_subseg( cs, spos, epos )
if pad_left:
al = rle_get_at( attr, spos-1 )
- rle_append_beginning_modify( attrtr, (al, 1) )
- rle_append_beginning_modify( cstr, (None, 1) )
+ rle_prepend_modify( attrtr, (al, 1) )
+ rle_prepend_modify( cstr, (None, 1) )
if pad_right:
al = rle_get_at( attr, epos )
rle_append_modify( attrtr, (al, 1) )
@@ -283,7 +286,7 @@ def rle_len( rle ):
run += r
return run
-def rle_append_beginning_modify(rle, a_r):
+def rle_prepend_modify(rle, a_r):
"""
Append (a, r) (unpacked from *a_r*) to BEGINNING of rle.
Merge with first run when possible
@@ -298,7 +301,7 @@ def rle_append_beginning_modify(rle, a_r):
if a == al:
rle[0] = (a,run+r)
else:
- rle[0:0] = [(al, r)]
+ rle[0:0] = [(a, r)]
def rle_append_modify(rle, a_r):
diff --git a/urwid/version.py b/urwid/version.py
index 5bd4210..e8544cd 100644
--- a/urwid/version.py
+++ b/urwid/version.py
@@ -1,4 +1,4 @@
from __future__ import division, print_function
-VERSION = (2, 0, 1)
+VERSION = (2, 1, 0)
__version__ = ''.join(['-.'[type(x) == int]+str(x) for x in VERSION])[1:]
diff --git a/urwid/vterm.py b/urwid/vterm.py
index 0c91ca5..05d0c83 100644
--- a/urwid/vterm.py
+++ b/urwid/vterm.py
@@ -47,6 +47,7 @@ from urwid.widget import Widget, BOX
from urwid.display_common import AttrSpec, RealTerminal, _BASIC_COLORS
from urwid.compat import ord2, chr2, B, bytes, PYTHON3, xrange
+EOF = B('')
ESC = chr(27)
KEY_TRANSLATIONS = {
@@ -1324,7 +1325,8 @@ class Terminal(Widget):
signals = ['closed', 'beep', 'leds', 'title']
- def __init__(self, command, env=None, main_loop=None, escape_sequence=None):
+ def __init__(self, command, env=None, main_loop=None, escape_sequence=None,
+ encoding='ascii'):
"""
A terminal emulator within a widget.
@@ -1342,6 +1344,13 @@ class Terminal(Widget):
'escape_sequence' is the urwid key symbol which should be used to break
out of the terminal widget. If it's not specified, "ctrl a" is used.
+
+ 'encoding' specifies the encoding that is being used when local
+ keypresses in Unicode are encoded into raw bytes. The default encoding
+ is 'ascii' for backwards compatibility with urwid versions <= 2.0.1.
+ Set this to the encoding of your terminal (typically 'utf8') if you
+ want to be able to transmit non-ASCII characters to the spawned process.
+ Applies to Python 3.x only.
"""
self.__super.__init__()
@@ -1360,6 +1369,8 @@ class Terminal(Widget):
else:
self.command = command
+ self.encoding = encoding
+
self.keygrab = False
self.last_key = None
@@ -1530,19 +1541,19 @@ class Terminal(Widget):
self.feed()
def feed(self):
- data = ''
+ data = EOF
try:
data = os.read(self.master, 4096)
except OSError as e:
if e.errno == 5: # End Of File
- data = ''
+ data = EOF
elif e.errno == errno.EWOULDBLOCK: # empty buffer
return
else:
raise
- if data == '': # EOF on BSD
+ if data == EOF: # EOF on BSD
self.terminate()
self._emit('closed')
return
@@ -1623,6 +1634,6 @@ class Terminal(Widget):
key += "\x0a"
if PYTHON3:
- key = key.encode('ascii')
+ key = key.encode(self.encoding, 'ignore')
os.write(self.master, key)
diff --git a/urwid/widget.py b/urwid/widget.py
index 97cc4b9..b908d9f 100644
--- a/urwid/widget.py
+++ b/urwid/widget.py
@@ -59,6 +59,7 @@ BOTTOM = 'bottom'
SPACE = 'space'
ANY = 'any'
CLIP = 'clip'
+ELLIPSIS = 'ellipsis'
# Width and Height settings
PACK = 'pack'
@@ -813,7 +814,7 @@ class Text(Widget):
:type markup: :ref:`text-markup`
:param align: typically ``'left'``, ``'center'`` or ``'right'``
:type align: text alignment mode
- :param wrap: typically ``'space'``, ``'any'`` or ``'clip'``
+ :param wrap: typically ``'space'``, ``'any'``, ``'clip'`` or ``'ellipsis'``
:type wrap: text wrapping mode
:param layout: defaults to a shared :class:`StandardTextLayout` instance
:type layout: text layout instance
@@ -937,7 +938,7 @@ class Text(Widget):
Set text wrapping mode. Supported modes depend on text layout
object in use but defaults to a :class:`StandardTextLayout` instance
- :param mode: typically ``'space'``, ``'any'`` or ``'clip'``
+ :param mode: typically ``'space'``, ``'any'``, ``'clip'`` or ``'ellipsis'``
:type mode: text wrapping mode
>>> t = Text(u"some words")
@@ -966,7 +967,7 @@ class Text(Widget):
the same time.
:type align: text alignment mode
- :param wrap: typically 'space', 'any' or 'clip'
+ :param wrap: typically 'space', 'any', 'clip' or 'ellipsis'
:type wrap: text wrapping mode
:param layout: defaults to a shared :class:`StandardTextLayout` instance
:type layout: text layout instance
@@ -1039,13 +1040,8 @@ class Text(Widget):
else:
text, attr = self.get_text()
self._cache_maxcol = maxcol
- self._cache_translation = self._calc_line_translation(
- text, maxcol )
-
- def _calc_line_translation(self, text, maxcol ):
- return self.layout.layout(
- text, self._cache_maxcol,
- self._align_mode, self._wrap_mode )
+ self._cache_translation = self.layout.layout(
+ text, maxcol, self._align_mode, self._wrap_mode)
def pack(self, size=None, focus=False):
"""
@@ -1111,6 +1107,8 @@ class Edit(Text):
handler to guard against this case (for instance, by not changing the
text if it is signaled for for text that it has already changed once).
"""
+ _selectable = True
+ ignore_focus = False
# (this variable is picked up by the MetaSignals metaclass)
signals = ["change", "postchange"]
@@ -1125,8 +1123,6 @@ class Edit(Text):
"""
return is_wide_char(ch,0) or (len(ch)==1 and ord(ch) >= 32)
- def selectable(self): return True
-
def __init__(self, caption=u"", edit_text=u"", multiline=False,
align=LEFT, wrap=SPACE, allow_tab=False,
edit_pos=None, layout=None, mask=None):
diff --git a/urwid/wimp.py b/urwid/wimp.py
index 62a0819..3dfbf39 100755
--- a/urwid/wimp.py
+++ b/urwid/wimp.py
@@ -34,8 +34,9 @@ from urwid.decoration import WidgetDecoration
from urwid.command_map import ACTIVATE
class SelectableIcon(Text):
+ ignore_focus = False
_selectable = True
- def __init__(self, text, cursor_position=1):
+ def __init__(self, text, cursor_position=0):
"""
:param text: markup for this widget; see :class:`Text` for
description of text markup
@@ -58,7 +59,7 @@ class SelectableIcon(Text):
>>> si
<SelectableIcon selectable flow widget '[!]'>
>>> si.render((4,), focus=True).cursor
- (1, 0)
+ (0, 0)
>>> si = SelectableIcon("((*))", 2)
>>> si.render((8,), focus=True).cursor
(2, 0)
@@ -103,9 +104,9 @@ class CheckBox(WidgetWrap):
return frozenset([FLOW])
states = {
- True: SelectableIcon("[X]"),
- False: SelectableIcon("[ ]"),
- 'mixed': SelectableIcon("[#]") }
+ True: SelectableIcon("[X]", 1),
+ False: SelectableIcon("[ ]", 1),
+ 'mixed': SelectableIcon("[#]", 1) }
reserve_columns = 4
# allow users of this class to listen for change events
@@ -114,7 +115,7 @@ class CheckBox(WidgetWrap):
signals = ["change", 'postchange']
def __init__(self, label, state=False, has_mixed=False,
- on_state_change=None, user_data=None):
+ on_state_change=None, user_data=None, checked_symbol=None):
"""
:param label: markup for check box label
:param state: False, True or "mixed"
@@ -148,6 +149,8 @@ class CheckBox(WidgetWrap):
self._label = Text("")
self.has_mixed = has_mixed
self._state = None
+ if checked_symbol:
+ self.states[True] = SelectableIcon(u"[%s]" % checked_symbol, 1)
# The old way of listening for a change was to pass the callback
# in to the constructor. Just convert it to the new way:
if on_state_change:
@@ -322,9 +325,9 @@ class CheckBox(WidgetWrap):
class RadioButton(CheckBox):
states = {
- True: SelectableIcon("(X)"),
- False: SelectableIcon("( )"),
- 'mixed': SelectableIcon("(#)") }
+ True: SelectableIcon("(X)", 1),
+ False: SelectableIcon("( )", 1),
+ 'mixed': SelectableIcon("(#)", 1) }
reserve_columns = 4
def __init__(self, group, label, state="first True",