summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSophie Brun <sophie@freexian.com>2018-03-06 10:55:24 +0100
committerSophie Brun <sophie@freexian.com>2018-03-06 10:55:24 +0100
commit66d785c993e585707bfcd7b5c6789202be5f60ca (patch)
tree82873fbd2f30e205683b9cdddff43ff13b928ba6
parent48c1126b815dfea6bbc5057cea98e167a6acfd77 (diff)
New upstream version 2.0.1
-rw-r--r--PKG-INFO56
-rw-r--r--README.rst61
-rw-r--r--docs/changelog.rst100
-rw-r--r--docs/conf.py10
-rwxr-xr-x[l---------]docs/examples/bigtext.py162
-rwxr-xr-x[l---------]docs/examples/graph.py357
-rwxr-xr-x[l---------]docs/examples/palette_test.py253
-rwxr-xr-x[l---------]docs/examples/pop_up.py42
-rwxr-xr-x[l---------]docs/examples/real_browse.py391
-rwxr-xr-x[l---------]docs/examples/real_edit.py256
-rwxr-xr-x[l---------]docs/examples/subproc.py34
-rwxr-xr-x[l---------]docs/examples/tour.py331
-rw-r--r--docs/manual/canvascache.rst2
-rw-r--r--docs/manual/displayattributes.rst20
-rw-r--r--docs/manual/overview.rst2
-rw-r--r--docs/manual/widgets.rst4
-rw-r--r--docs/tools/templates/indexcontent.html22
-rw-r--r--docs/tutorial/index.rst6
-rw-r--r--docs/tutorial/new/adventure.py83
-rw-r--r--docs/tutorial/new/adventure.py.xdotool4
-rw-r--r--docs/tutorial/new/adventure1.pngbin818 -> 0 bytes
-rw-r--r--docs/tutorial/new/adventure2.pngbin1218 -> 0 bytes
-rw-r--r--docs/tutorial/new/adventure3.pngbin1435 -> 0 bytes
-rw-r--r--docs/tutorial/new/adventure4.pngbin1321 -> 0 bytes
-rw-r--r--docs/tutorial/new/lbscr1.pngbin747 -> 0 bytes
-rw-r--r--docs/tutorial/new/minimal1.pngbin309 -> 0 bytes
-rw-r--r--examples/asyncio_socket_server.py1
-rwxr-xr-xexamples/bigtext.py2
-rwxr-xr-xexamples/browse.py8
-rwxr-xr-xexamples/calc.py44
-rwxr-xr-xexamples/dialog.py8
-rwxr-xr-xexamples/edit.py2
-rwxr-xr-xexamples/fib.py4
-rwxr-xr-xexamples/graph.py4
-rwxr-xr-xexamples/input_test.py2
-rwxr-xr-xexamples/lcd_cf635.py2
-rwxr-xr-xexamples/palette_test.py2
-rwxr-xr-xexamples/pop_up.py2
-rwxr-xr-xexamples/subproc.py4
-rw-r--r--examples/subproc2.py12
-rwxr-xr-xexamples/terminal.py2
-rwxr-xr-xexamples/tour.py2
-rwxr-xr-xexamples/treesample.py2
-rw-r--r--examples/twisted_serve_ssh.py9
-rw-r--r--setup.cfg1
-rw-r--r--setup.py7
-rw-r--r--source/str_util.c6
-rw-r--r--urwid.egg-info/PKG-INFO56
-rw-r--r--urwid.egg-info/SOURCES.txt8
-rw-r--r--urwid/__init__.py2
-rw-r--r--urwid/canvas.py18
-rw-r--r--urwid/command_map.py2
-rw-r--r--urwid/compat.py63
-rwxr-xr-xurwid/container.py24
-rwxr-xr-xurwid/curses_display.py12
-rwxr-xr-xurwid/decoration.py21
-rwxr-xr-xurwid/display_common.py27
-rw-r--r--urwid/escape.py2
-rwxr-xr-xurwid/font.py38
-rwxr-xr-xurwid/graphics.py144
-rwxr-xr-xurwid/html_fragment.py18
-rw-r--r--urwid/lcd_display.py5
-rw-r--r--urwid/listbox.py205
-rwxr-xr-xurwid/main_loop.py187
-rwxr-xr-xurwid/monitored_list.py20
-rwxr-xr-xurwid/old_str_util.py11
-rw-r--r--urwid/raw_display.py73
-rw-r--r--urwid/signals.py7
-rwxr-xr-xurwid/split_repr.py8
-rw-r--r--urwid/tests/test_doctests.py1
-rw-r--r--urwid/tests/test_event_loops.py29
-rw-r--r--urwid/tests/test_listbox.py2
-rw-r--r--urwid/tests/test_vterm.py18
-rw-r--r--urwid/tests/test_widget.py10
-rw-r--r--urwid/text_layout.py8
-rw-r--r--urwid/treetools.py6
-rw-r--r--urwid/util.py10
-rw-r--r--urwid/version.py5
-rw-r--r--urwid/vterm.py6
-rwxr-xr-xurwid/web_display.py8
-rw-r--r--urwid/widget.py88
-rwxr-xr-xurwid/wimp.py27
82 files changed, 2979 insertions, 512 deletions
diff --git a/PKG-INFO b/PKG-INFO
index 01183c4..47b320d 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,16 @@
Metadata-Version: 1.1
Name: urwid
-Version: 1.3.1
+Version: 2.0.1
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
+ =====
+
Urwid is a console user interface library for Python.
It includes many features useful for text console application developers including:
@@ -24,6 +28,54 @@ Description:
Home Page:
http://urwid.org/
+ Testing
+ =======
+
+ To run tests locally, install & run `tox`. You must have
+ appropriate Python versions installed to run `tox` for
+ each of them.
+
+ To test code in all Python versions:
+
+ .. code:: bash
+
+ tox # Test all versions specified in tox.ini:
+ tox -e py36 # Test Python 3.6 only
+ tox -e py27,py36,pypy # Test Python 2.7, Python 3.6 & pypy
+
+ Contributors
+ ============
+
+ - `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>`_
+
Keywords: curses ui widget scroll listbox user interface text layout console ncurses
Platform: unix-like
Classifier: Development Status :: 5 - Production/Stable
@@ -43,4 +95,6 @@ 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 :: Implementation :: PyPy
diff --git a/README.rst b/README.rst
index e0d063e..0b30d1a 100644
--- a/README.rst
+++ b/README.rst
@@ -1,11 +1,20 @@
-.. image:: https://travis-ci.org/wardi/urwid.png?branch=master
+.. image:: https://travis-ci.org/urwid/urwid.png?branch=master
:alt: build status
- :target: https://travis-ci.org/wardi/urwid/
+ :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!**
+
.. content-start
+About
+=====
+
Urwid is a console user interface library for Python.
It includes many features useful for text console application developers including:
@@ -22,3 +31,51 @@ It includes many features useful for text console application developers includi
Home Page:
http://urwid.org/
+
+Testing
+=======
+
+To run tests locally, install & run `tox`. You must have
+appropriate Python versions installed to run `tox` for
+each of them.
+
+To test code in all Python versions:
+
+.. code:: bash
+
+ tox # Test all versions specified in tox.ini:
+ tox -e py36 # Test Python 3.6 only
+ tox -e py27,py36,pypy # Test Python 2.7, Python 3.6 & pypy
+
+Contributors
+============
+
+- `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>`_
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 676a5d3..b83b52f 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -2,6 +2,94 @@
Changelog
---------
+Urwid 2.0.1
+===========
+
+2018-01-21
+
+ * #275: Late fix for proper exception reraising from within main loop
+ (by Andrew Dunai & Adam Sampson)
+
+Urwid 2.0.0
+===========
+
+2018-01-17
+
+ * Full Python 2.x/3.x support (by Andrew Dunai)
+
+ * Proper handling & customization of OS signals by GLib event loop
+ (by Federico T)
+
+ * vterm: Fix handling of NUL characters (by aszlig)
+
+ * Add 256-color support for fbterm (by Benjamin Yates)
+
+ * Italics support (by Ian D. Scott)
+
+ * Store envron's TERM value as a Screen attribute (by Benjamin Yates)
+
+ * Replaced hashbangs to use proper Python binary (by Douglas La Rocca)
+
+ * Post-change signal for Edit, CheckBox and RadioButton widgets
+ (by Toshio Kuratomi)
+
+ * ListBox.body update (by Random User)
+
+ * SimpleListWalker is now default when setting ListBox.body (by Random User)
+
+ * #246, #234: SelectEventLoop alarm improvements (by Dave Jones)
+
+ * #211: Title align & borderless sides for LineBox (by Toshio Kuratomi)
+
+ * Support for 'home' and 'end' keys in ListBox (by Random User)
+
+ * Various code cleanups (by Jordan Speicher, Marin Atanasov Nikolov)
+
+ * CI fixes (by Marlox, Ian Ward, Anatoly Techtonik, Tony Cebzanov &
+ Ondřej Súkup)
+
+ * Example fixes (by Kenneth Nielsen)
+
+ * Documentation fixes (by anatoly techtonik, Marcin Kurczewski, mobyte0,
+ Christian Geier & xndcn)
+
+ * Code cleanup & typo fixes (by Jakub Wilk & Boris Feld)
+
+ * Integration of tox for easier Python cross-version testing (by Andrew Dunai)
+
+ * Test fixes (by Michael Hudson-Doyle, Mike Gilbert & Andrew Dunai)
+
+ * Correct error messages in Decoration (by Marcin Kurczewski)
+
+ * #141: Fix for StandardTextLayout.calculate_text_segments
+ (by Grzegorz Aksamit)
+
+ * #221: Fix for raw display should release file descriptors (by Alain Leufroy)
+
+ * #261: Fix issues with unicode characters in ProgressBar (by Andrew Dunai)
+
+ * Fix for 'page up' and 'page down' in ListBox when having focusable children
+ (by Random User)
+
+ * Fixes for examples compatibility with Python 3 (by Lars Kellogg-Stedman)
+
+ * Fix default screen size on raw display (by Andreas Klöckner)
+
+ * Fix underlining for padded text (by Random User)
+
+ * Fix for terminal widget crash with Python 3 (by Sjc1000)
+
+ * Fix for string formatting error (by Jakub Wilk)
+
+ * Fix for iterator in WidgetContainerListContentsMixin (by Marlox)
+
+ * Fix for missing `modified` signal in SimpleFocusListWalker
+ (by Michael Hansen)
+
+ * Dropped Python 3.2 support
+
+ * Test coverage is now collected
+
Urwid 1.3.1
===========
@@ -279,7 +367,7 @@ Urwid 1.0.1
* Fix for a LineBox border __init__() parameters
- * Fix input input of UTF-8 in tour.py example by converting captions
+ * Fix input of UTF-8 in tour.py example by converting captions
to unicode
* Fix tutorial examples' use of TextCanvas and switch to using
@@ -307,7 +395,7 @@ Urwid 1.0.0
* New experimental Terminal widget with xterm emulation and
terminal.py example program (by aszlig)
- * Edit widget now supports a mask (for passwords), has a
+ * Edit widget now supports a mask (for passwords), has an
insert_text_result() method for full-field validation and
normalizes input text to Unicode or bytes based on the caption
type used
@@ -566,7 +654,7 @@ Urwid 0.9.8
without blocking.
* The Columns, Pile and ListBox widgets now choose their first
- selectable child widget as the focus widget by defaut.
+ selectable child widget as the focus widget by default.
* New ListWalker base class for list walker classes.
@@ -593,7 +681,7 @@ Urwid 0.9.8
* The raw_display module now uses an alternate buffer so that the
original screen can be restored on exit. The old behaviour is
- available by seting the alternate_buffer parameter of start() or
+ available by setting the alternate_buffer parameter of start() or
run_wrapper() to False.
* Many internal string processing functions have been rewritten in C to
@@ -611,7 +699,7 @@ Urwid 0.9.7.2
* Fixed a UTF-8 input bug.
- * Added a clear() function to the the display modules to force the
+ * Added a clear() function to the display modules to force the
screen to be repainted on the next draw_screen() call.
@@ -1048,7 +1136,7 @@ Urwid 0.8.6
register_palette() and register_palette_entry() now accept "default"
as foreground and/or background. If the terminal's default attributes
- cannot be detected black on light gray will be used to accomodate
+ cannot be detected black on light gray will be used to accommodate
terminals with always-black cursors.
"default" is now the default for text with no attributes. This means
diff --git a/docs/conf.py b/docs/conf.py
index 302dff0..66f921d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -46,10 +46,18 @@ copyright = u'2014, Ian Ward et al'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
-FILE_PATH = os.path.dirname(__file__).decode('utf-8')
+if sys.version_info > (3, 0):
+ FILE_PATH = os.path.dirname(__file__)
+else:
+ FILE_PATH = os.path.dirname(__file__).decode('utf-8')
VERSION_MODULE = os.path.abspath(os.path.join(FILE_PATH,
'../urwid/version.py'))
VERSION_VARS = {}
+if sys.version_info > (3, 0):
+ def execfile(module, global_vars):
+ with open(module) as f:
+ code = compile(f.read(), "somefile.py", 'exec')
+ exec(code, global_vars)
execfile(VERSION_MODULE, VERSION_VARS)
# The short X.Y version.
version = '.'.join([str(x) for x in VERSION_VARS['VERSION'][:2]])
diff --git a/docs/examples/bigtext.py b/docs/examples/bigtext.py
index 119650d..6a088ea 120000..100755
--- a/docs/examples/bigtext.py
+++ b/docs/examples/bigtext.py
@@ -1 +1,161 @@
-../../examples/bigtext.py \ No newline at end of file
+#!/usr/bin/env python
+#
+# Urwid BigText example program
+# Copyright (C) 2004-2009 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/
+
+"""
+Urwid example demonstrating use of the BigText widget.
+"""
+
+import urwid
+import urwid.raw_display
+
+
+class SwitchingPadding(urwid.Padding):
+ def padding_values(self, size, focus):
+ maxcol = size[0]
+ width, ignore = self.original_widget.pack(size, focus=focus)
+ if maxcol > width:
+ self.align = "left"
+ else:
+ self.align = "right"
+ return urwid.Padding.padding_values(self, size, focus)
+
+
+class BigTextDisplay:
+ palette = [
+ ('body', 'black', 'light gray', 'standout'),
+ ('header', 'white', 'dark red', 'bold'),
+ ('button normal','light gray', 'dark blue', 'standout'),
+ ('button select','white', 'dark green'),
+ ('button disabled','dark gray','dark blue'),
+ ('edit', 'light gray', 'dark blue'),
+ ('bigtext', 'white', 'black'),
+ ('chars', 'light gray', 'black'),
+ ('exit', 'white', 'dark cyan'),
+ ]
+
+ def create_radio_button(self, g, name, font, fn):
+ w = urwid.RadioButton(g, name, False, on_state_change=fn)
+ w.font = font
+ w = urwid.AttrWrap(w, 'button normal', 'button select')
+ return w
+
+ def create_disabled_radio_button(self, name):
+ w = urwid.Text(" " + name + " (UTF-8 mode required)")
+ w = urwid.AttrWrap(w, 'button disabled')
+ return w
+
+ def create_edit(self, label, text, fn):
+ w = urwid.Edit(label, text)
+ urwid.connect_signal(w, 'change', fn)
+ fn(w, text)
+ w = urwid.AttrWrap(w, 'edit')
+ return w
+
+ def set_font_event(self, w, state):
+ if state:
+ self.bigtext.set_font(w.font)
+ self.chars_avail.set_text(w.font.characters())
+
+ def edit_change_event(self, widget, text):
+ self.bigtext.set_text(text)
+
+ def setup_view(self):
+ fonts = urwid.get_all_fonts()
+ # setup mode radio buttons
+ self.font_buttons = []
+ group = []
+ utf8 = urwid.get_encoding_mode() == "utf8"
+ for name, fontcls in fonts:
+ font = fontcls()
+ if font.utf8_required and not utf8:
+ rb = self.create_disabled_radio_button(name)
+ else:
+ rb = self.create_radio_button(group, name, font,
+ self.set_font_event)
+ if fontcls == urwid.Thin6x6Font:
+ chosen_font_rb = rb
+ exit_font = font
+ self.font_buttons.append( rb )
+
+ # Create BigText
+ self.bigtext = urwid.BigText("", None)
+ bt = SwitchingPadding(self.bigtext, 'left', None)
+ bt = urwid.AttrWrap(bt, 'bigtext')
+ bt = urwid.Filler(bt, 'bottom', None, 7)
+ bt = urwid.BoxAdapter(bt, 7)
+
+ # Create chars_avail
+ cah = urwid.Text("Characters Available:")
+ self.chars_avail = urwid.Text("", wrap='any')
+ ca = urwid.AttrWrap(self.chars_avail, 'chars')
+
+ chosen_font_rb.set_state(True) # causes set_font_event call
+
+ # Create Edit widget
+ edit = self.create_edit("", "Urwid "+urwid.__version__,
+ self.edit_change_event)
+
+ # ListBox
+ chars = urwid.Pile([cah, ca])
+ fonts = urwid.Pile([urwid.Text("Fonts:")] + self.font_buttons,
+ focus_item=1)
+ col = urwid.Columns([('fixed',16,chars), fonts], 3,
+ focus_column=1)
+ bt = urwid.Pile([bt, edit], focus_item=1)
+ l = [bt, urwid.Divider(), col]
+ w = urwid.ListBox(urwid.SimpleListWalker(l))
+
+ # Frame
+ w = urwid.AttrWrap(w, 'body')
+ hdr = urwid.Text("Urwid BigText example program - F8 exits.")
+ hdr = urwid.AttrWrap(hdr, 'header')
+ w = urwid.Frame(header=hdr, body=w)
+
+ # Exit message
+ exit = urwid.BigText(('exit'," Quit? "), exit_font)
+ exit = urwid.Overlay(exit, w, 'center', None, 'middle', None)
+ return w, exit
+
+
+ def main(self):
+ self.view, self.exit_view = self.setup_view()
+ self.loop = urwid.MainLoop(self.view, self.palette,
+ unhandled_input=self.unhandled_input)
+ self.loop.run()
+
+ def unhandled_input(self, key):
+ if key == 'f8':
+ self.loop.widget = self.exit_view
+ return True
+ if self.loop.widget != self.exit_view:
+ return
+ if key in ('y', 'Y'):
+ raise urwid.ExitMainLoop()
+ if key in ('n', 'N'):
+ self.loop.widget = self.view
+ return True
+
+
+def main():
+ BigTextDisplay().main()
+
+if '__main__'==__name__:
+ main()
diff --git a/docs/examples/graph.py b/docs/examples/graph.py
index ffbbea0..4a5b5dd 120000..100755
--- a/docs/examples/graph.py
+++ b/docs/examples/graph.py
@@ -1 +1,356 @@
-../../examples/graph.py \ No newline at end of file
+#!/usr/bin/env python
+#
+# Urwid graphics example program
+# Copyright (C) 2004-2011 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/
+
+"""
+Urwid example demonstrating use of the BarGraph widget and creating a
+floating-window appearance. Also shows use of alarms to create timed
+animation.
+"""
+
+import urwid
+
+import math
+import time
+
+UPDATE_INTERVAL = 0.2
+
+def sin100( x ):
+ """
+ A sin function that returns values between 0 and 100 and repeats
+ after x == 100.
+ """
+ return 50 + 50 * math.sin( x * math.pi / 50 )
+
+class GraphModel:
+ """
+ A class responsible for storing the data that will be displayed
+ on the graph, and keeping track of which mode is enabled.
+ """
+
+ data_max_value = 100
+
+ def __init__(self):
+ data = [ ('Saw', list(range(0,100,2))*2),
+ ('Square', [0]*30 + [100]*30),
+ ('Sine 1', [sin100(x) for x in range(100)] ),
+ ('Sine 2', [(sin100(x) + sin100(x*2))/2
+ for x in range(100)] ),
+ ('Sine 3', [(sin100(x) + sin100(x*3))/2
+ for x in range(100)] ),
+ ]
+ self.modes = []
+ self.data = {}
+ for m, d in data:
+ self.modes.append(m)
+ self.data[m] = d
+
+ def get_modes(self):
+ return self.modes
+
+ def set_mode(self, m):
+ self.current_mode = m
+
+ def get_data(self, offset, r):
+ """
+ Return the data in [offset:offset+r], the maximum value
+ for items returned, and the offset at which the data
+ repeats.
+ """
+ l = []
+ d = self.data[self.current_mode]
+ while r:
+ offset = offset % len(d)
+ segment = d[offset:offset+r]
+ r -= len(segment)
+ offset += len(segment)
+ l += segment
+ return l, self.data_max_value, len(d)
+
+
+class GraphView(urwid.WidgetWrap):
+ """
+ A class responsible for providing the application's interface and
+ graph display.
+ """
+ palette = [
+ ('body', 'black', 'light gray', 'standout'),
+ ('header', 'white', 'dark red', 'bold'),
+ ('screen edge', 'light blue', 'dark cyan'),
+ ('main shadow', 'dark gray', 'black'),
+ ('line', 'black', 'light gray', 'standout'),
+ ('bg background','light gray', 'black'),
+ ('bg 1', 'black', 'dark blue', 'standout'),
+ ('bg 1 smooth', 'dark blue', 'black'),
+ ('bg 2', 'black', 'dark cyan', 'standout'),
+ ('bg 2 smooth', 'dark cyan', 'black'),
+ ('button normal','light gray', 'dark blue', 'standout'),
+ ('button select','white', 'dark green'),
+ ('line', 'black', 'light gray', 'standout'),
+ ('pg normal', 'white', 'black', 'standout'),
+ ('pg complete', 'white', 'dark magenta'),
+ ('pg smooth', 'dark magenta','black')
+ ]
+
+ graph_samples_per_bar = 10
+ graph_num_bars = 5
+ graph_offset_per_second = 5
+
+ def __init__(self, controller):
+ self.controller = controller
+ self.started = True
+ self.start_time = None
+ self.offset = 0
+ self.last_offset = None
+ urwid.WidgetWrap.__init__(self, self.main_window())
+
+ def get_offset_now(self):
+ if self.start_time is None:
+ return 0
+ if not self.started:
+ return self.offset
+ tdelta = time.time() - self.start_time
+ return int(self.offset + (tdelta*self.graph_offset_per_second))
+
+ def update_graph(self, force_update=False):
+ o = self.get_offset_now()
+ if o == self.last_offset and not force_update:
+ return False
+ self.last_offset = o
+ gspb = self.graph_samples_per_bar
+ r = gspb * self.graph_num_bars
+ d, max_value, repeat = self.controller.get_data( o, r )
+ l = []
+ for n in range(self.graph_num_bars):
+ value = sum(d[n*gspb:(n+1)*gspb])/gspb
+ # toggle between two bar types
+ if n & 1:
+ l.append([0,value])
+ else:
+ l.append([value,0])
+ self.graph.set_data(l,max_value)
+
+ # also update progress
+ if (o//repeat)&1:
+ # show 100% for first half, 0 for second half
+ if o%repeat > repeat//2:
+ prog = 0
+ else:
+ prog = 1
+ else:
+ prog = float(o%repeat) / repeat
+ self.animate_progress.set_completion( prog )
+ return True
+
+ def on_animate_button(self, button):
+ """Toggle started state and button text."""
+ if self.started: # stop animation
+ button.set_label("Start")
+ self.offset = self.get_offset_now()
+ self.started = False
+ self.controller.stop_animation()
+ else:
+ button.set_label("Stop")
+ self.started = True
+ self.start_time = time.time()
+ self.controller.animate_graph()
+
+
+ def on_reset_button(self, w):
+ self.offset = 0
+ self.start_time = time.time()
+ self.update_graph(True)
+
+ def on_mode_button(self, button, state):
+ """Notify the controller of a new mode setting."""
+ if state:
+ # The new mode is the label of the button
+ self.controller.set_mode( button.get_label() )
+ self.last_offset = None
+
+ def on_mode_change(self, m):
+ """Handle external mode change by updating radio buttons."""
+ for rb in self.mode_buttons:
+ if rb.get_label() == m:
+ rb.set_state(True, do_callback=False)
+ break
+ self.last_offset = None
+
+ def on_unicode_checkbox(self, w, state):
+ self.graph = self.bar_graph( state )
+ self.graph_wrap._w = self.graph
+ self.animate_progress = self.progress_bar( state )
+ self.animate_progress_wrap._w = self.animate_progress
+ self.update_graph( True )
+
+
+ def main_shadow(self, w):
+ """Wrap a shadow and background around widget w."""
+ bg = urwid.AttrWrap(urwid.SolidFill(u"\u2592"), 'screen edge')
+ shadow = urwid.AttrWrap(urwid.SolidFill(u" "), 'main shadow')
+
+ bg = urwid.Overlay( shadow, bg,
+ ('fixed left', 3), ('fixed right', 1),
+ ('fixed top', 2), ('fixed bottom', 1))
+ w = urwid.Overlay( w, bg,
+ ('fixed left', 2), ('fixed right', 3),
+ ('fixed top', 1), ('fixed bottom', 2))
+ return w
+
+ def bar_graph(self, smooth=False):
+ satt = None
+ if smooth:
+ satt = {(1,0): 'bg 1 smooth', (2,0): 'bg 2 smooth'}
+ w = urwid.BarGraph(['bg background','bg 1','bg 2'], satt=satt)
+ return w
+
+ def button(self, t, fn):
+ w = urwid.Button(t, fn)
+ w = urwid.AttrWrap(w, 'button normal', 'button select')
+ return w
+
+ def radio_button(self, g, l, fn):
+ w = urwid.RadioButton(g, l, False, on_state_change=fn)
+ w = urwid.AttrWrap(w, 'button normal', 'button select')
+ return w
+
+ def progress_bar(self, smooth=False):
+ if smooth:
+ return urwid.ProgressBar('pg normal', 'pg complete',
+ 0, 1, 'pg smooth')
+ else:
+ return urwid.ProgressBar('pg normal', 'pg complete',
+ 0, 1)
+
+ def exit_program(self, w):
+ raise urwid.ExitMainLoop()
+
+ def graph_controls(self):
+ modes = self.controller.get_modes()
+ # setup mode radio buttons
+ self.mode_buttons = []
+ group = []
+ for m in modes:
+ rb = self.radio_button( group, m, self.on_mode_button )
+ self.mode_buttons.append( rb )
+ # setup animate button
+ self.animate_button = self.button( "", self.on_animate_button)
+ self.on_animate_button( self.animate_button )
+ self.offset = 0
+ self.animate_progress = self.progress_bar()
+ animate_controls = urwid.GridFlow( [
+ self.animate_button,
+ self.button("Reset", self.on_reset_button),
+ ], 9, 2, 0, 'center')
+
+ if urwid.get_encoding_mode() == "utf8":
+ unicode_checkbox = urwid.CheckBox(
+ "Enable Unicode Graphics",
+ on_state_change=self.on_unicode_checkbox)
+ else:
+ unicode_checkbox = urwid.Text(
+ "UTF-8 encoding not detected")
+
+ self.animate_progress_wrap = urwid.WidgetWrap(
+ self.animate_progress)
+
+ l = [ urwid.Text("Mode",align="center"),
+ ] + self.mode_buttons + [
+ urwid.Divider(),
+ urwid.Text("Animation",align="center"),
+ animate_controls,
+ self.animate_progress_wrap,
+ urwid.Divider(),
+ urwid.LineBox( unicode_checkbox ),
+ urwid.Divider(),
+ self.button("Quit", self.exit_program ),
+ ]
+ w = urwid.ListBox(urwid.SimpleListWalker(l))
+ return w
+
+ def main_window(self):
+ self.graph = self.bar_graph()
+ self.graph_wrap = urwid.WidgetWrap( self.graph )
+ vline = urwid.AttrWrap( urwid.SolidFill(u'\u2502'), 'line')
+ c = self.graph_controls()
+ w = urwid.Columns([('weight',2,self.graph_wrap),
+ ('fixed',1,vline), c],
+ dividechars=1, focus_column=2)
+ w = urwid.Padding(w,('fixed left',1),('fixed right',0))
+ w = urwid.AttrWrap(w,'body')
+ w = urwid.LineBox(w)
+ w = urwid.AttrWrap(w,'line')
+ w = self.main_shadow(w)
+ return w
+
+
+class GraphController:
+ """
+ A class responsible for setting up the model and view and running
+ the application.
+ """
+ def __init__(self):
+ self.animate_alarm = None
+ self.model = GraphModel()
+ self.view = GraphView( self )
+ # use the first mode as the default
+ mode = self.get_modes()[0]
+ self.model.set_mode( mode )
+ # update the view
+ self.view.on_mode_change( mode )
+ self.view.update_graph(True)
+
+ def get_modes(self):
+ """Allow our view access to the list of modes."""
+ return self.model.get_modes()
+
+ def set_mode(self, m):
+ """Allow our view to set the mode."""
+ rval = self.model.set_mode( m )
+ self.view.update_graph(True)
+ return rval
+
+ def get_data(self, offset, range):
+ """Provide data to our view for the graph."""
+ return self.model.get_data( offset, range )
+
+
+ def main(self):
+ self.loop = urwid.MainLoop(self.view, self.view.palette)
+ self.loop.run()
+
+ def animate_graph(self, loop=None, user_data=None):
+ """update the graph and schedule the next update"""
+ self.view.update_graph()
+ self.animate_alarm = self.loop.set_alarm_in(
+ UPDATE_INTERVAL, self.animate_graph)
+
+ def stop_animation(self):
+ """stop animating the graph"""
+ if self.animate_alarm:
+ self.loop.remove_alarm(self.animate_alarm)
+ self.animate_alarm = None
+
+
+def main():
+ GraphController().main()
+
+if '__main__'==__name__:
+ main()
diff --git a/docs/examples/palette_test.py b/docs/examples/palette_test.py
index 0ab2544..0c98623 120000..100755
--- a/docs/examples/palette_test.py
+++ b/docs/examples/palette_test.py
@@ -1 +1,252 @@
-../../examples/palette_test.py \ No newline at end of file
+#!/usr/bin/env python
+#
+# Urwid Palette Test. Showing off highcolor support
+# Copyright (C) 2004-2009 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/
+
+"""
+Palette test. Shows the available foreground and background settings
+in monochrome, 16 color, 88 color and 256 color modes.
+"""
+
+import re
+import sys
+
+import urwid
+import urwid.raw_display
+
+CHART_256 = """
+brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_
+yellow_ light_red light_magenta light_blue light_cyan light_green
+
+ #00f#06f#08f#0af#0df#0ff black_______ dark_gray___
+ #60f#00d#06d#08d#0ad#0dd#0fd light_gray__ white_______
+ #80f#60d#00a#06a#08a#0aa#0da#0fa
+ #a0f#80d#60a#008#068#088#0a8#0d8#0f8
+ #d0f#a0d#80d#608#006#066#086#0a6#0d6#0f6
+ #f0f#d0d#a0a#808#606#000#060#080#0a0#0d0#0f0#0f6#0f8#0fa#0fd#0ff
+ #f0d#d0a#a08#806#600#660#680#6a0#6d0#6f0#6f6#6f8#6fa#6fd#6ff#0df
+ #f0a#d08#a06#800#860#880#8a0#8d0#8f0#8f6#8f8#8fa#8fd#8ff#6df#0af
+ #f08#d06#a00#a60#a80#aa0#ad0#af0#af6#af8#afa#afd#aff#8df#6af#08f
+ #f06#d00#d60#d80#da0#dd0#df0#df6#df8#dfa#dfd#dff#adf#8af#68f#06f
+ #f00#f60#f80#fa0#fd0#ff0#ff6#ff8#ffa#ffd#fff#ddf#aaf#88f#66f#00f
+ #fd0#fd6#fd8#fda#fdd#fdf#daf#a8f#86f#60f
+ #66d#68d#6ad#6dd #fa0#fa6#fa8#faa#fad#faf#d8f#a6f#80f
+ #86d#66a#68a#6aa#6da #f80#f86#f88#f8a#f8d#f8f#d6f#a0f
+ #a6d#86a#668#688#6a8#6d8 #f60#f66#f68#f6a#f6d#f6f#d0f
+#d6d#a6a#868#666#686#6a6#6d6#6d8#6da#6dd #f00#f06#f08#f0a#f0d#f0f
+ #d6a#a68#866#886#8a6#8d6#8d8#8da#8dd#6ad
+ #d68#a66#a86#aa6#ad6#ad8#ada#add#8ad#68d
+ #d66#d86#da6#dd6#dd8#dda#ddd#aad#88d#66d g78_g82_g85_g89_g93_g100
+ #da6#da8#daa#dad#a8d#86d g52_g58_g62_g66_g70_g74_
+ #88a#8aa #d86#d88#d8a#d8d#a6d g27_g31_g35_g38_g42_g46_g50_
+ #a8a#888#8a8#8aa #d66#d68#d6a#d6d g0__g3__g7__g11_g15_g19_g23_
+ #a88#aa8#aaa#88a
+ #a88#a8a
+"""
+
+CHART_88 = """
+brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_
+yellow_ light_red light_magenta light_blue light_cyan light_green
+
+ #00f#08f#0cf#0ff black_______ dark_gray___
+ #80f#00c#08c#0cc#0fc light_gray__ white_______
+ #c0f#80c#008#088#0c8#0f8
+#f0f#c0c#808#000#080#0c0#0f0#0f8#0fc#0ff #88c#8cc
+ #f0c#c08#800#880#8c0#8f0#8f8#8fc#8ff#0cf #c8c#888#8c8#8cc
+ #f08#c00#c80#cc0#cf0#cf8#cfc#cff#8cf#08f #c88#cc8#ccc#88c
+ #f00#f80#fc0#ff0#ff8#ffc#fff#ccf#88f#00f #c88#c8c
+ #fc0#fc8#fcc#fcf#c8f#80f
+ #f80#f88#f8c#f8f#c0f g62_g74_g82_g89_g100
+ #f00#f08#f0c#f0f g0__g19_g35_g46_g52
+"""
+
+CHART_16 = """
+brown__ dark_red_ dark_magenta_ dark_blue_ dark_cyan_ dark_green_
+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]+)")
+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.
+
+ chart -- palette chart as a string
+ convert -- function that converts a single palette entry to an
+ (attr, text) tuple, or None if no match is found
+ """
+ out = []
+ for match in re.finditer(ATTR_RE, chart):
+ if match.group('whitespace'):
+ out.append(match.group('whitespace'))
+ entry = match.group('entry')
+ entry = entry.replace("_", " ")
+ while entry:
+ # try the first four characters
+ attrtext = convert(entry[:SHORT_ATTR])
+ if attrtext:
+ elen = SHORT_ATTR
+ entry = entry[SHORT_ATTR:].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)))
+ return out
+
+def foreground_chart(chart, background, colors):
+ """
+ Create text markup for a foreground colour chart
+
+ chart -- palette chart as string
+ background -- colour to use for background of chart
+ colors -- number of colors (88 or 256)
+ """
+ def convert_foreground(entry):
+ try:
+ attr = urwid.AttrSpec(entry, background, colors)
+ except urwid.AttrSpecError:
+ return None
+ return attr, entry
+ return parse_chart(chart, convert_foreground)
+
+def background_chart(chart, foreground, colors):
+ """
+ Create text markup for a background colour chart
+
+ chart -- palette chart as string
+ foreground -- colour to use for foreground of chart
+ colors -- number of colors (88 or 256)
+
+ This will remap 8 <= colour < 16 to high-colour versions
+ in the hopes of greater compatibility
+ """
+ def convert_background(entry):
+ try:
+ attr = urwid.AttrSpec(foreground, entry, colors)
+ except urwid.AttrSpecError:
+ return None
+ # fix 8 <= colour < 16
+ if colors > 16 and attr.background_basic and \
+ attr.background_number >= 8:
+ # use high-colour with same number
+ entry = 'h%d'%attr.background_number
+ attr = urwid.AttrSpec(foreground, entry, colors)
+ return attr, entry
+ return parse_chart(chart, convert_background)
+
+
+def main():
+ palette = [
+ ('header', 'black,underline', 'light gray', 'standout,underline',
+ 'black,underline', '#88a'),
+ ('panel', 'light gray', 'dark blue', '',
+ '#ffd', '#00a'),
+ ('focus', 'light gray', 'dark cyan', 'standout',
+ '#ff8', '#806'),
+ ]
+
+ screen = urwid.raw_display.Screen()
+ screen.register_palette(palette)
+
+ lb = urwid.SimpleListWalker([])
+ chart_offset = None # offset of chart in lb list
+
+ mode_radio_buttons = []
+ chart_radio_buttons = []
+
+ def fcs(widget):
+ # wrap widgets that can take focus
+ return urwid.AttrMap(widget, None, 'focus')
+
+ def set_mode(colors, is_foreground_chart):
+ # set terminal mode and redraw chart
+ screen.set_terminal_properties(colors)
+ screen.reset_default_terminal_palette()
+
+ chart_fn = (background_chart, foreground_chart)[is_foreground_chart]
+ if colors == 1:
+ lb[chart_offset] = urwid.Divider()
+ else:
+ chart = {16: CHART_16, 88: CHART_88, 256: CHART_256}[colors]
+ txt = chart_fn(chart, 'default', colors)
+ lb[chart_offset] = urwid.Text(txt, wrap='clip')
+
+ def on_mode_change(rb, state, colors):
+ # if this radio button is checked
+ if state:
+ is_foreground_chart = chart_radio_buttons[0].state
+ set_mode(colors, is_foreground_chart)
+
+ def mode_rb(text, colors, state=False):
+ # mode radio buttons
+ rb = urwid.RadioButton(mode_radio_buttons, text, state)
+ urwid.connect_signal(rb, 'change', on_mode_change, colors)
+ return fcs(rb)
+
+ def on_chart_change(rb, state):
+ # handle foreground check box state change
+ set_mode(screen.colors, state)
+
+ def click_exit(button):
+ raise urwid.ExitMainLoop()
+
+ lb.extend([
+ urwid.AttrMap(urwid.Text("Urwid Palette Test"), 'header'),
+ urwid.AttrMap(urwid.Columns([
+ urwid.Pile([
+ mode_rb("Monochrome", 1),
+ mode_rb("16-Color", 16, True),
+ mode_rb("88-Color", 88),
+ mode_rb("256-Color", 256),]),
+ urwid.Pile([
+ fcs(urwid.RadioButton(chart_radio_buttons,
+ "Foreground Colors", True, on_chart_change)),
+ fcs(urwid.RadioButton(chart_radio_buttons,
+ "Background Colors")),
+ urwid.Divider(),
+ fcs(urwid.Button("Exit", click_exit)),
+ ]),
+ ]),'panel')
+ ])
+
+ chart_offset = len(lb)
+ lb.extend([
+ urwid.Divider() # placeholder for the chart
+ ])
+
+ set_mode(16, True) # displays the chart
+
+ def unhandled_input(key):
+ if key in ('Q','q','esc'):
+ raise urwid.ExitMainLoop()
+
+ urwid.MainLoop(urwid.ListBox(lb), screen=screen,
+ unhandled_input=unhandled_input).run()
+
+if __name__ == "__main__":
+ main()
+
+
diff --git a/docs/examples/pop_up.py b/docs/examples/pop_up.py
index 0e32494..f88c717 120000..100755
--- a/docs/examples/pop_up.py
+++ b/docs/examples/pop_up.py
@@ -1 +1,41 @@
-../../examples/pop_up.py \ No newline at end of file
+#!/usr/bin/env python
+
+import urwid
+
+class PopUpDialog(urwid.WidgetWrap):
+ """A dialog that appears with nothing but a close button """
+ signals = ['close']
+ def __init__(self):
+ close_button = urwid.Button("that's pretty cool")
+ urwid.connect_signal(close_button, 'click',
+ lambda button:self._emit("close"))
+ pile = urwid.Pile([urwid.Text(
+ "^^ I'm attached to the widget that opened me. "
+ "Try resizing the window!\n"), close_button])
+ fill = urwid.Filler(pile)
+ self.__super.__init__(urwid.AttrWrap(fill, 'popbg'))
+
+
+class ThingWithAPopUp(urwid.PopUpLauncher):
+ def __init__(self):
+ self.__super.__init__(urwid.Button("click-me"))
+ urwid.connect_signal(self.original_widget, 'click',
+ lambda button: self.open_pop_up())
+
+ def create_pop_up(self):
+ pop_up = PopUpDialog()
+ urwid.connect_signal(pop_up, 'close',
+ lambda button: self.close_pop_up())
+ return pop_up
+
+ def get_pop_up_parameters(self):
+ return {'left':0, 'top':1, 'overlay_width':32, 'overlay_height':7}
+
+
+fill = urwid.Filler(urwid.Padding(ThingWithAPopUp(), 'center', 15))
+loop = urwid.MainLoop(
+ fill,
+ [('popbg', 'white', 'dark blue')],
+ pop_ups=True)
+loop.run()
+
diff --git a/docs/examples/real_browse.py b/docs/examples/real_browse.py
index a817177..6c0eb37 120000..100755
--- a/docs/examples/real_browse.py
+++ b/docs/examples/real_browse.py
@@ -1 +1,390 @@
-../../examples/browse.py \ No newline at end of file
+#!/usr/bin/env python
+#
+# Urwid example lazy directory browser / tree view
+# Copyright (C) 2004-2011 Ian Ward
+# Copyright (C) 2010 Kirk McDonald
+# Copyright (C) 2010 Rob Lanphier
+#
+# 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/
+
+"""
+Urwid example lazy directory browser / tree view
+
+Features:
+- custom selectable widgets for files and directories
+- custom message widgets to identify access errors and empty directories
+- custom list walker for displaying widgets in a tree fashion
+- outputs a quoted list of files and directories "selected" on exit
+"""
+
+from __future__ import print_function
+
+import itertools
+import re
+import os
+
+import urwid
+
+
+class FlagFileWidget(urwid.TreeWidget):
+ # apply an attribute to the expand/unexpand icons
+ unexpanded_icon = urwid.AttrMap(urwid.TreeWidget.unexpanded_icon,
+ 'dirmark')
+ expanded_icon = urwid.AttrMap(urwid.TreeWidget.expanded_icon,
+ 'dirmark')
+
+ def __init__(self, node):
+ self.__super.__init__(node)
+ # insert an extra AttrWrap for our own use
+ self._w = urwid.AttrWrap(self._w, None)
+ self.flagged = False
+ self.update_w()
+
+ def selectable(self):
+ return True
+
+ def keypress(self, size, key):
+ """allow subclasses to intercept keystrokes"""
+ key = self.__super.keypress(size, key)
+ if key:
+ key = self.unhandled_keys(size, key)
+ return key
+
+ def unhandled_keys(self, size, key):
+ """
+ Override this method to intercept keystrokes in subclasses.
+ Default behavior: Toggle flagged on space, ignore other keys.
+ """
+ if key == " ":
+ self.flagged = not self.flagged
+ self.update_w()
+ else:
+ return key
+
+ def update_w(self):
+ """Update the attributes of self.widget based on self.flagged.
+ """
+ if self.flagged:
+ self._w.attr = 'flagged'
+ self._w.focus_attr = 'flagged focus'
+ else:
+ self._w.attr = 'body'
+ self._w.focus_attr = 'focus'
+
+
+class FileTreeWidget(FlagFileWidget):
+ """Widget for individual files."""
+ def __init__(self, node):
+ self.__super.__init__(node)
+ path = node.get_value()
+ add_widget(path, self)
+
+ def get_display_text(self):
+ return self.get_node().get_key()
+
+
+
+class EmptyWidget(urwid.TreeWidget):
+ """A marker for expanded directories with no contents."""
+ def get_display_text(self):
+ return ('flag', '(empty directory)')
+
+
+class ErrorWidget(urwid.TreeWidget):
+ """A marker for errors reading directories."""
+
+ def get_display_text(self):
+ return ('error', "(error/permission denied)")
+
+
+class DirectoryWidget(FlagFileWidget):
+ """Widget for a directory."""
+ def __init__(self, node):
+ self.__super.__init__(node)
+ path = node.get_value()
+ add_widget(path, self)
+ self.expanded = starts_expanded(path)
+ self.update_expanded_icon()
+
+ def get_display_text(self):
+ node = self.get_node()
+ if node.get_depth() == 0:
+ return "/"
+ else:
+ return node.get_key()
+
+
+class FileNode(urwid.TreeNode):
+ """Metadata storage for individual files"""
+
+ def __init__(self, path, parent=None):
+ depth = path.count(dir_sep())
+ key = os.path.basename(path)
+ urwid.TreeNode.__init__(self, path, key=key, parent=parent, depth=depth)
+
+ def load_parent(self):
+ parentname, myname = os.path.split(self.get_value())
+ parent = DirectoryNode(parentname)
+ parent.set_child_node(self.get_key(), self)
+ return parent
+
+ def load_widget(self):
+ return FileTreeWidget(self)
+
+
+class EmptyNode(urwid.TreeNode):
+ def load_widget(self):
+ return EmptyWidget(self)
+
+
+class ErrorNode(urwid.TreeNode):
+ def load_widget(self):
+ return ErrorWidget(self)
+
+
+class DirectoryNode(urwid.ParentNode):
+ """Metadata storage for directories"""
+
+ def __init__(self, path, parent=None):
+ if path == dir_sep():
+ depth = 0
+ key = None
+ else:
+ depth = path.count(dir_sep())
+ key = os.path.basename(path)
+ urwid.ParentNode.__init__(self, path, key=key, parent=parent,
+ depth=depth)
+
+ def load_parent(self):
+ parentname, myname = os.path.split(self.get_value())
+ parent = DirectoryNode(parentname)
+ parent.set_child_node(self.get_key(), self)
+ return parent
+
+ def load_child_keys(self):
+ dirs = []
+ files = []
+ try:
+ path = self.get_value()
+ # separate dirs and files
+ for a in os.listdir(path):
+ if os.path.isdir(os.path.join(path,a)):
+ dirs.append(a)
+ else:
+ files.append(a)
+ except OSError as e:
+ depth = self.get_depth() + 1
+ self._children[None] = ErrorNode(self, parent=self, key=None,
+ depth=depth)
+ return [None]
+
+ # sort dirs and files
+ dirs.sort(key=alphabetize)
+ files.sort(key=alphabetize)
+ # store where the first file starts
+ self.dir_count = len(dirs)
+ # collect dirs and files together again
+ keys = dirs + files
+ if len(keys) == 0:
+ depth=self.get_depth() + 1
+ self._children[None] = EmptyNode(self, parent=self, key=None,
+ depth=depth)
+ keys = [None]
+ return keys
+
+ def load_child_node(self, key):
+ """Return either a FileNode or DirectoryNode"""
+ index = self.get_child_index(key)
+ if key is None:
+ return EmptyNode(None)
+ else:
+ path = os.path.join(self.get_value(), key)
+ if index < self.dir_count:
+ return DirectoryNode(path, parent=self)
+ else:
+ path = os.path.join(self.get_value(), key)
+ return FileNode(path, parent=self)
+
+ def load_widget(self):
+ return DirectoryWidget(self)
+
+
+class DirectoryBrowser:
+ palette = [
+ ('body', 'black', 'light gray'),
+ ('flagged', 'black', 'dark green', ('bold','underline')),
+ ('focus', 'light gray', 'dark blue', 'standout'),
+ ('flagged focus', 'yellow', 'dark cyan',
+ ('bold','standout','underline')),
+ ('head', 'yellow', 'black', 'standout'),
+ ('foot', 'light gray', 'black'),
+ ('key', 'light cyan', 'black','underline'),
+ ('title', 'white', 'black', 'bold'),
+ ('dirmark', 'black', 'dark cyan', 'bold'),
+ ('flag', 'dark gray', 'light gray'),
+ ('error', 'dark red', 'light gray'),
+ ]
+
+ footer_text = [
+ ('title', "Directory Browser"), " ",
+ ('key', "UP"), ",", ('key', "DOWN"), ",",
+ ('key', "PAGE UP"), ",", ('key', "PAGE DOWN"),
+ " ",
+ ('key', "SPACE"), " ",
+ ('key', "+"), ",",
+ ('key', "-"), " ",
+ ('key', "LEFT"), " ",
+ ('key', "HOME"), " ",
+ ('key', "END"), " ",
+ ('key', "Q"),
+ ]
+
+
+ def __init__(self):
+ cwd = os.getcwd()
+ store_initial_cwd(cwd)
+ self.header = urwid.Text("")
+ self.listbox = urwid.TreeListBox(urwid.TreeWalker(DirectoryNode(cwd)))
+ self.listbox.offset_rows = 1
+ self.footer = urwid.AttrWrap(urwid.Text(self.footer_text),
+ 'foot')
+ self.view = urwid.Frame(
+ urwid.AttrWrap(self.listbox, 'body'),
+ header=urwid.AttrWrap(self.header, 'head'),
+ footer=self.footer)
+
+ def main(self):
+ """Run the program."""
+
+ self.loop = urwid.MainLoop(self.view, self.palette,
+ unhandled_input=self.unhandled_input)
+ self.loop.run()
+
+ # on exit, write the flagged filenames to the console
+ names = [escape_filename_sh(x) for x in get_flagged_names()]
+ print(" ".join(names))
+
+ def unhandled_input(self, k):
+ # update display of focus directory
+ if k in ('q','Q'):
+ raise urwid.ExitMainLoop()
+
+
+def main():
+ DirectoryBrowser().main()
+
+
+
+
+#######
+# global cache of widgets
+_widget_cache = {}
+
+def add_widget(path, widget):
+ """Add the widget for a given path"""
+
+ _widget_cache[path] = widget
+
+def get_flagged_names():
+ """Return a list of all filenames marked as flagged."""
+
+ l = []
+ for w in _widget_cache.values():
+ if w.flagged:
+ l.append(w.get_node().get_value())
+ return l
+
+
+
+######
+# store path components of initial current working directory
+_initial_cwd = []
+
+def store_initial_cwd(name):
+ """Store the initial current working directory path components."""
+
+ global _initial_cwd
+ _initial_cwd = name.split(dir_sep())
+
+def starts_expanded(name):
+ """Return True if directory is a parent of initial cwd."""
+
+ if name is '/':
+ return True
+
+ l = name.split(dir_sep())
+ if len(l) > len(_initial_cwd):
+ return False
+
+ if l != _initial_cwd[:len(l)]:
+ return False
+
+ return True
+
+
+def escape_filename_sh(name):
+ """Return a hopefully safe shell-escaped version of a filename."""
+
+ # check whether we have unprintable characters
+ for ch in name:
+ if ord(ch) < 32:
+ # found one so use the ansi-c escaping
+ return escape_filename_sh_ansic(name)
+
+ # all printable characters, so return a double-quoted version
+ name.replace('\\','\\\\')
+ name.replace('"','\\"')
+ name.replace('`','\\`')
+ name.replace('$','\\$')
+ return '"'+name+'"'
+
+
+def escape_filename_sh_ansic(name):
+ """Return an ansi-c shell-escaped version of a filename."""
+
+ out =[]
+ # gather the escaped characters into a list
+ for ch in name:
+ if ord(ch) < 32:
+ out.append("\\x%02x"% ord(ch))
+ elif ch == '\\':
+ out.append('\\\\')
+ else:
+ out.append(ch)
+
+ # slap them back together in an ansi-c quote $'...'
+ return "$'" + "".join(out) + "'"
+
+SPLIT_RE = re.compile(r'[a-zA-Z]+|\d+')
+def alphabetize(s):
+ L = []
+ for isdigit, group in itertools.groupby(SPLIT_RE.findall(s), key=lambda x: x.isdigit()):
+ if isdigit:
+ for n in group:
+ L.append(('', int(n)))
+ else:
+ L.append((''.join(group).lower(), 0))
+ return L
+
+def dir_sep():
+ """Return the separator used in this os."""
+ return getattr(os.path,'sep','/')
+
+
+if __name__=="__main__":
+ main()
+
diff --git a/docs/examples/real_edit.py b/docs/examples/real_edit.py
index 16bf719..e2cc2fa 120000..100755
--- a/docs/examples/real_edit.py
+++ b/docs/examples/real_edit.py
@@ -1 +1,255 @@
-../../examples/edit.py \ No newline at end of file
+#!/usr/bin/env python
+#
+# Urwid example lazy text editor suitable for tabbed and format=flowed text
+# Copyright (C) 2004-2009 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/
+
+"""
+Urwid example lazy text editor suitable for tabbed and flowing text
+
+Features:
+- custom list walker for lazily loading text file
+
+Usage:
+edit.py <filename>
+
+"""
+
+import sys
+
+import urwid
+
+
+class LineWalker(urwid.ListWalker):
+ """ListWalker-compatible class for lazily reading file contents."""
+
+ def __init__(self, name):
+ self.file = open(name)
+ self.lines = []
+ self.focus = 0
+
+ def get_focus(self):
+ return self._get_at_pos(self.focus)
+
+ def set_focus(self, focus):
+ self.focus = focus
+ self._modified()
+
+ def get_next(self, start_from):
+ return self._get_at_pos(start_from + 1)
+
+ def get_prev(self, start_from):
+ return self._get_at_pos(start_from - 1)
+
+ def read_next_line(self):
+ """Read another line from the file."""
+
+ next_line = self.file.readline()
+
+ if not next_line or next_line[-1:] != '\n':
+ # no newline on last line of file
+ self.file = None
+ else:
+ # trim newline characters
+ next_line = next_line[:-1]
+
+ expanded = next_line.expandtabs()
+
+ edit = urwid.Edit("", expanded, allow_tab=True)
+ edit.set_edit_pos(0)
+ edit.original_text = next_line
+ self.lines.append(edit)
+
+ return next_line
+
+
+ def _get_at_pos(self, pos):
+ """Return a widget for the line number passed."""
+
+ if pos < 0:
+ # line 0 is the start of the file, no more above
+ return None, None
+
+ if len(self.lines) > pos:
+ # we have that line so return it
+ return self.lines[pos], pos
+
+ if self.file is None:
+ # file is closed, so there are no more lines
+ return None, None
+
+ assert pos == len(self.lines), "out of order request?"
+
+ self.read_next_line()
+
+ return self.lines[-1], pos
+
+ def split_focus(self):
+ """Divide the focus edit widget at the cursor location."""
+
+ focus = self.lines[self.focus]
+ pos = focus.edit_pos
+ edit = urwid.Edit("",focus.edit_text[pos:], allow_tab=True)
+ edit.original_text = ""
+ focus.set_edit_text(focus.edit_text[:pos])
+ edit.set_edit_pos(0)
+ self.lines.insert(self.focus+1, edit)
+
+ def combine_focus_with_prev(self):
+ """Combine the focus edit widget with the one above."""
+
+ above, ignore = self.get_prev(self.focus)
+ if above is None:
+ # already at the top
+ return
+
+ focus = self.lines[self.focus]
+ above.set_edit_pos(len(above.edit_text))
+ above.set_edit_text(above.edit_text + focus.edit_text)
+ del self.lines[self.focus]
+ self.focus -= 1
+
+ def combine_focus_with_next(self):
+ """Combine the focus edit widget with the one below."""
+
+ below, ignore = self.get_next(self.focus)
+ if below is None:
+ # already at bottom
+ return
+
+ focus = self.lines[self.focus]
+ focus.set_edit_text(focus.edit_text + below.edit_text)
+ del self.lines[self.focus+1]
+
+
+class EditDisplay:
+ palette = [
+ ('body','default', 'default'),
+ ('foot','dark cyan', 'dark blue', 'bold'),
+ ('key','light cyan', 'dark blue', 'underline'),
+ ]
+
+ footer_text = ('foot', [
+ "Text Editor ",
+ ('key', "F5"), " save ",
+ ('key', "F8"), " quit",
+ ])
+
+ def __init__(self, name):
+ self.save_name = name
+ self.walker = LineWalker(name)
+ self.listbox = urwid.ListBox(self.walker)
+ self.footer = urwid.AttrWrap(urwid.Text(self.footer_text),
+ "foot")
+ self.view = urwid.Frame(urwid.AttrWrap(self.listbox, 'body'),
+ footer=self.footer)
+
+ def main(self):
+ self.loop = urwid.MainLoop(self.view, self.palette,
+ unhandled_input=self.unhandled_keypress)
+ self.loop.run()
+
+ def unhandled_keypress(self, k):
+ """Last resort for keypresses."""
+
+ if k == "f5":
+ self.save_file()
+ elif k == "f8":
+ raise urwid.ExitMainLoop()
+ elif k == "delete":
+ # delete at end of line
+ self.walker.combine_focus_with_next()
+ elif k == "backspace":
+ # backspace at beginning of line
+ self.walker.combine_focus_with_prev()
+ elif k == "enter":
+ # start new line
+ self.walker.split_focus()
+ # move the cursor to the new line and reset pref_col
+ self.loop.process_input(["down", "home"])
+ elif k == "right":
+ w, pos = self.walker.get_focus()
+ w, pos = self.walker.get_next(pos)
+ if w:
+ self.listbox.set_focus(pos, 'above')
+ self.loop.process_input(["home"])
+ elif k == "left":
+ w, pos = self.walker.get_focus()
+ w, pos = self.walker.get_prev(pos)
+ if w:
+ self.listbox.set_focus(pos, 'below')
+ self.loop.process_input(["end"])
+ else:
+ return
+ return True
+
+
+ def save_file(self):
+ """Write the file out to disk."""
+
+ l = []
+ walk = self.walker
+ for edit in walk.lines:
+ # collect the text already stored in edit widgets
+ if edit.original_text.expandtabs() == edit.edit_text:
+ l.append(edit.original_text)
+ else:
+ l.append(re_tab(edit.edit_text))
+
+ # then the rest
+ while walk.file is not None:
+ l.append(walk.read_next_line())
+
+ # write back to disk
+ outfile = open(self.save_name, "w")
+
+ prefix = ""
+ for line in l:
+ outfile.write(prefix + line)
+ prefix = "\n"
+
+def re_tab(s):
+ """Return a tabbed string from an expanded one."""
+ l = []
+ p = 0
+ for i in range(8, len(s), 8):
+ if s[i-2:i] == " ":
+ # collapse two or more spaces into a tab
+ l.append(s[p:i].rstrip() + "\t")
+ p = i
+
+ if p == 0:
+ return s
+ else:
+ l.append(s[p:])
+ return "".join(l)
+
+
+
+def main():
+ try:
+ name = sys.argv[1]
+ assert open(name, "a")
+ except:
+ sys.stderr.write(__doc__)
+ return
+ EditDisplay(name).main()
+
+
+if __name__=="__main__":
+ main()
diff --git a/docs/examples/subproc.py b/docs/examples/subproc.py
index b3410f7..f04e7b0 120000..100755
--- a/docs/examples/subproc.py
+++ b/docs/examples/subproc.py
@@ -1 +1,33 @@
-../../examples/subproc.py \ No newline at end of file
+#!/usr/bin/env python
+
+import subprocess
+import urwid
+import os
+import sys
+
+factor_me = 362923067964327863989661926737477737673859044111968554257667
+run_me = os.path.join(os.path.dirname(sys.argv[0]), 'subproc2.py')
+
+output_widget = urwid.Text("Factors of %d:\n" % factor_me)
+edit_widget = urwid.Edit("Type anything or press enter to exit:")
+frame_widget = urwid.Frame(
+ header=edit_widget,
+ body=urwid.Filler(output_widget, valign='bottom'),
+ focus_part='header')
+
+def exit_on_enter(key):
+ if key == 'enter': raise urwid.ExitMainLoop()
+
+loop = urwid.MainLoop(frame_widget, unhandled_input=exit_on_enter)
+
+def received_output(data):
+ output_widget.set_text(output_widget.text + data.decode('utf8'))
+
+write_fd = loop.watch_pipe(received_output)
+proc = subprocess.Popen(
+ ['python', '-u', run_me, str(factor_me)],
+ stdout=write_fd,
+ close_fds=True)
+
+loop.run()
+proc.kill()
diff --git a/docs/examples/tour.py b/docs/examples/tour.py
index 84d7931..cf04da2 120000..100755
--- a/docs/examples/tour.py
+++ b/docs/examples/tour.py
@@ -1 +1,330 @@
-../../examples/tour.py \ No newline at end of file
+#!/usr/bin/env python
+#
+# Urwid tour. It slices, it dices..
+# Copyright (C) 2004-2011 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/
+
+"""
+Urwid tour. Shows many of the standard widget types and features.
+"""
+
+import urwid
+import urwid.raw_display
+import urwid.web_display
+
+def main():
+ text_header = (u"Welcome to the urwid tour! "
+ u"UP / DOWN / PAGE UP / PAGE DOWN scroll. F8 exits.")
+ text_intro = [('important', u"Text"),
+ u" widgets are the most common in "
+ u"any urwid program. This Text widget was created "
+ u"without setting the wrap or align mode, so it "
+ u"defaults to left alignment with wrapping on space "
+ u"characters. ",
+ ('important', u"Change the window width"),
+ u" to see how the widgets on this page react. "
+ u"This Text widget is wrapped with a ",
+ ('important', u"Padding"),
+ u" widget to keep it indented on the left and right."]
+ text_right = (u"This Text widget is right aligned. Wrapped "
+ u"words stay to the right as well. ")
+ text_center = u"This one is center aligned."
+ text_clip = (u"Text widgets may be clipped instead of wrapped.\n"
+ u"Extra text is discarded instead of wrapped to the next line. "
+ u"65-> 70-> 75-> 80-> 85-> 90-> 95-> 100>\n"
+ u"Newlines embedded in the string are still respected.")
+ text_right_clip = (u"This is a right aligned and clipped Text widget.\n"
+ u"<100 <-95 <-90 <-85 <-80 <-75 <-70 <-65 "
+ 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_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"
+ u"Like this one.")
+ text_padding = (u"Padding widgets have many options. This "
+ u"is a standard Text widget wrapped with a Padding widget "
+ u"with the alignment set to relative 20% and with its width "
+ u"fixed at 40.")
+ text_divider = [u"The ", ('important', u"Divider"),
+ u" widget repeats the same character across the whole line. "
+ u"It can also add blank lines above and below."]
+ text_edit = [u"The ", ('important', u"Edit"),
+ u" widget is a simple text editing widget. It supports cursor "
+ u"movement and tries to maintain the current column when focus "
+ u"moves to another edit widget. It wraps and aligns the same "
+ u"way as Text widgets." ]
+ text_edit_cap1 = ('editcp', u"This is a caption. Edit here: ")
+ text_edit_text1 = u"editable stuff"
+ text_edit_cap2 = ('editcp', u"This one supports newlines: ")
+ text_edit_text2 = (u"line one starts them all\n"
+ u"== line 2 == with some more text to edit.. words.. whee..\n"
+ u"LINE III, the line to end lines one and two, unless you "
+ u"change something.")
+ text_edit_cap3 = ('editcp', u"This one is clipped, try "
+ u"editing past the edge: ")
+ text_edit_text3 = u"add some text here -> -> -> ...."
+ text_edit_alignments = u"Different Alignments:"
+ text_edit_left = u"left aligned (default)"
+ text_edit_center = u"center aligned"
+ text_edit_right = u"right aligned"
+ text_intedit = ('editcp', [('important', u"IntEdit"),
+ u" allows only numbers: "])
+ text_edit_padding = ('editcp', u"Edit widget within a Padding widget ")
+ text_columns1 = [('important', u"Columns"),
+ u" are used to share horizontal screen space. "
+ u"This one splits the space into two parts with "
+ u"three characters between each column. The "
+ u"contents of each column is a single widget."]
+ text_columns2 = [u"When you need to put more than one "
+ u"widget into a column you can use a ",('important',
+ u"Pile"), u" to combine two or more widgets."]
+ text_col_columns = u"Columns may be placed inside other columns."
+ text_col_21 = u"Col 2.1"
+ text_col_22 = u"Col 2.2"
+ text_col_23 = u"Col 2.3"
+ text_column_widths = (u"Columns may also have uneven relative "
+ u"weights or fixed widths. Use a minimum width so that "
+ u"columns don't become too small.")
+ text_weight = u"Weight %d"
+ text_fixed_9 = u"<Fixed 9>" # should be 9 columns wide
+ text_fixed_14 = u"<--Fixed 14-->" # should be 14 columns wide
+ text_edit_col_cap1 = ('editcp', u"Edit widget within Columns")
+ text_edit_col_text1 = u"here's\nsome\ninfo"
+ text_edit_col_cap2 = ('editcp', u"and within Pile ")
+ text_edit_col_text2 = u"more"
+ text_edit_col_cap3 = ('editcp', u"another ")
+ text_edit_col_text3 = u"still more"
+ text_gridflow = [u"A ",('important', u"GridFlow"), u" widget "
+ u"may be used to display a list of flow widgets with equal "
+ u"widths. Widgets that don't fit on the first line will "
+ u"flow to the next. This is useful for small widgets that "
+ u"you want to keep together such as ", ('important', u"Button"),
+ u", ",('important', u"CheckBox"), u" and ",
+ ('important', u"RadioButton"), u" widgets." ]
+ text_button_list = [u"Yes", u"No", u"Perhaps", u"Certainly", u"Partially",
+ u"Tuesdays Only", u"Help"]
+ text_cb_list = [u"Wax", u"Wash", u"Buff", u"Clear Coat", u"Dry",
+ u"Racing Stripe"]
+ text_rb_list = [u"Morning", u"Afternoon", u"Evening", u"Weekend"]
+ text_listbox = [u"All these widgets have been displayed "
+ u"with the help of a ", ('important', u"ListBox"), u" widget. "
+ u"ListBox widgets handle scrolling and changing focus. A ",
+ ('important', u"Frame"), u" widget is used to keep the "
+ u"instructions at the top of the screen."]
+
+
+ def button_press(button):
+ frame.footer = urwid.AttrWrap(urwid.Text(
+ [u"Pressed: ", button.get_label()]), 'header')
+
+ radio_button_group = []
+
+ blank = urwid.Divider()
+ listbox_content = [
+ blank,
+ urwid.Padding(urwid.Text(text_intro), left=2, right=2, min_width=20),
+ blank,
+ urwid.Text(text_right, align='right'),
+ blank,
+ urwid.Text(text_center, align='center'),
+ blank,
+ urwid.Text(text_clip, wrap='clip'),
+ blank,
+ urwid.Text(text_right_clip, align='right', wrap='clip'),
+ blank,
+ urwid.Text(text_center_clip, align='center', wrap='clip'),
+ blank,
+ urwid.Text(text_any, wrap='any'),
+ blank,
+ urwid.Padding(urwid.Text(text_padding), ('relative', 20), 40),
+ blank,
+ urwid.AttrWrap(urwid.Divider("=", 1), 'bright'),
+ urwid.Padding(urwid.Text(text_divider), left=2, right=2, min_width=20),
+ urwid.AttrWrap(urwid.Divider("-", 0, 1), 'bright'),
+ blank,
+ urwid.Padding(urwid.Text(text_edit), left=2, right=2, min_width=20),
+ blank,
+ urwid.AttrWrap(urwid.Edit(text_edit_cap1, text_edit_text1),
+ 'editbx', 'editfc'),
+ blank,
+ urwid.AttrWrap(urwid.Edit(text_edit_cap2, text_edit_text2,
+ multiline=True ), 'editbx', 'editfc'),
+ blank,
+ urwid.AttrWrap(urwid.Edit(text_edit_cap3, text_edit_text3,
+ wrap='clip' ), 'editbx', 'editfc'),
+ blank,
+ urwid.Text(text_edit_alignments),
+ urwid.AttrWrap(urwid.Edit("", text_edit_left, align='left'),
+ 'editbx', 'editfc' ),
+ urwid.AttrWrap(urwid.Edit("", text_edit_center,
+ align='center'), 'editbx', 'editfc' ),
+ urwid.AttrWrap(urwid.Edit("", text_edit_right, align='right'),
+ 'editbx', 'editfc' ),
+ blank,
+ urwid.AttrWrap(urwid.IntEdit(text_intedit, 123),
+ 'editbx', 'editfc' ),
+ blank,
+ urwid.Padding(urwid.AttrWrap(urwid.Edit(text_edit_padding, ""),
+ 'editbx','editfc' ), left=10, width=50),
+ blank,
+ blank,
+ urwid.AttrWrap(urwid.Columns([
+ urwid.Divider("."),
+ urwid.Divider(","),
+ urwid.Divider("."),
+ ]), 'bright'),
+ blank,
+ urwid.Columns([
+ urwid.Padding(urwid.Text(text_columns1), left=2, right=0,
+ min_width=20),
+ urwid.Pile([
+ urwid.Divider("~"),
+ urwid.Text(text_columns2),
+ urwid.Divider("_")])
+ ], 3),
+ blank,
+ blank,
+ urwid.Columns([
+ urwid.Text(text_col_columns),
+ urwid.Columns([
+ urwid.Text(text_col_21),
+ urwid.Text(text_col_22),
+ urwid.Text(text_col_23),
+ ], 1),
+ ], 2),
+ blank,
+ urwid.Padding(urwid.Text(text_column_widths), left=2, right=2,
+ min_width=20),
+ blank,
+ urwid.Columns( [
+ urwid.AttrWrap(urwid.Text(text_weight % 1),'reverse'),
+ ('weight', 2, urwid.Text(text_weight % 2)),
+ ('weight', 3, urwid.AttrWrap(urwid.Text(
+ text_weight % 3), 'reverse')),
+ ('weight', 4, urwid.Text(text_weight % 4)),
+ ('weight', 5, urwid.AttrWrap(urwid.Text(
+ text_weight % 5), 'reverse')),
+ ('weight', 6, urwid.Text(text_weight % 6)),
+ ], 0, min_width=8),
+ blank,
+ urwid.Columns([
+ ('weight', 2, urwid.AttrWrap(urwid.Text(
+ text_weight % 2), 'reverse')),
+ ('fixed', 9, urwid.Text(text_fixed_9)),
+ ('weight', 3, urwid.AttrWrap(urwid.Text(
+ text_weight % 2), 'reverse')),
+ ('fixed', 14, urwid.Text(text_fixed_14)),
+ ], 0, min_width=8),
+ blank,
+ urwid.Columns([
+ urwid.AttrWrap(urwid.Edit(text_edit_col_cap1,
+ text_edit_col_text1, multiline=True),
+ 'editbx','editfc'),
+ urwid.Pile([
+ urwid.AttrWrap(urwid.Edit(
+ text_edit_col_cap2,
+ text_edit_col_text2),
+ 'editbx','editfc'),
+ blank,
+ urwid.AttrWrap(urwid.Edit(
+ text_edit_col_cap3,
+ text_edit_col_text3),
+ 'editbx','editfc'),
+ ]),
+ ], 1),
+ blank,
+ urwid.AttrWrap(urwid.Columns([
+ urwid.Divider("'"),
+ urwid.Divider('"'),
+ urwid.Divider("~"),
+ urwid.Divider('"'),
+ urwid.Divider("'"),
+ ]), 'bright'),
+ blank,
+ blank,
+ urwid.Padding(urwid.Text(text_gridflow), left=2, right=2,
+ min_width=20),
+ blank,
+ urwid.Padding(urwid.GridFlow(
+ [urwid.AttrWrap(urwid.Button(txt, button_press),
+ 'buttn','buttnf') for txt in text_button_list],
+ 13, 3, 1, 'left'),
+ left=4, right=3, min_width=13),
+ blank,
+ urwid.Padding(urwid.GridFlow(
+ [urwid.AttrWrap(urwid.CheckBox(txt),'buttn','buttnf')
+ for txt in text_cb_list],
+ 10, 3, 1, 'left') ,
+ left=4, right=3, min_width=10),
+ blank,
+ urwid.Padding(urwid.GridFlow(
+ [urwid.AttrWrap(urwid.RadioButton(radio_button_group,
+ txt), 'buttn','buttnf')
+ for txt in text_rb_list],
+ 13, 3, 1, 'left') ,
+ left=4, right=3, min_width=13),
+ blank,
+ blank,
+ urwid.Padding(urwid.Text(text_listbox), left=2, right=2,
+ min_width=20),
+ blank,
+ blank,
+ ]
+
+ header = urwid.AttrWrap(urwid.Text(text_header), 'header')
+ listbox = urwid.ListBox(urwid.SimpleListWalker(listbox_content))
+ frame = urwid.Frame(urwid.AttrWrap(listbox, 'body'), header=header)
+
+ palette = [
+ ('body','black','light gray', 'standout'),
+ ('reverse','light gray','black'),
+ ('header','white','dark red', 'bold'),
+ ('important','dark blue','light gray',('standout','underline')),
+ ('editfc','white', 'dark blue', 'bold'),
+ ('editbx','light gray', 'dark blue'),
+ ('editcp','black','light gray', 'standout'),
+ ('bright','dark gray','light gray', ('bold','standout')),
+ ('buttn','black','dark cyan'),
+ ('buttnf','white','dark blue','bold'),
+ ]
+
+
+ # use appropriate Screen class
+ if urwid.web_display.is_web_request():
+ screen = urwid.web_display.Screen()
+ else:
+ screen = urwid.raw_display.Screen()
+
+ def unhandled(key):
+ if key == 'f8':
+ raise urwid.ExitMainLoop()
+
+ urwid.MainLoop(frame, palette, screen,
+ unhandled_input=unhandled).run()
+
+def setup():
+ urwid.web_display.set_preferences("Urwid Tour")
+ # try to handle short web requests quickly
+ if urwid.web_display.handle_short_request():
+ return
+
+ main()
+
+if '__main__'==__name__ or urwid.web_display.is_web_request():
+ setup()
diff --git a/docs/manual/canvascache.rst b/docs/manual/canvascache.rst
index bb5f094..a2d0bd4 100644
--- a/docs/manual/canvascache.rst
+++ b/docs/manual/canvascache.rst
@@ -53,7 +53,7 @@ will remain in the cache, and others will be garbage collected.
Future Work
===========
-A updating method that invalidates regions of the display without redrawing
+An updating method that invalidates regions of the display without redrawing
parent widgets would be more efficient for the common case of a single change
on the screen that does not affect the screen layout. Send an email to the
mailing list if you're interested in helping with this or other display
diff --git a/docs/manual/displayattributes.rst b/docs/manual/displayattributes.rst
index 44b7753..4f800f6 100644
--- a/docs/manual/displayattributes.rst
+++ b/docs/manual/displayattributes.rst
@@ -159,6 +159,21 @@ Foreground and Background Settings
- YES
- standout
- widely supported
+ * - :ref:`italics <bold-underline-standout>`
+ - YES
+ - YES
+ - NO
+ - widely supported
+ * - :ref:`blink <bold-underline-standout>`
+ - YES/NO
+ - NO
+ - NO
+ - some support
+ * - :ref:`strikethrough <bold-underline-standout>`
+ - YES
+ - NO
+ - NO
+ - some supported
* - :ref:`"bright" background colors <bright-background>`
- YES
- urxvt
@@ -239,9 +254,12 @@ Bold, Underline, Standout
* ``'bold'``
* ``'underline'``
* ``'standout'``
+* ``'blink'``
+* ``'italics'``
+* ``'strikethrough'``
These settings may be tagged on to foreground colors using commas, eg: ``'light
-gray,underline,bold'``
+gray,underline,bold,strikethrough'``
For monochrome mode combinations of these are the only values that may be used.
diff --git a/docs/manual/overview.rst b/docs/manual/overview.rst
index 9360147..a9e953b 100644
--- a/docs/manual/overview.rst
+++ b/docs/manual/overview.rst
@@ -35,7 +35,7 @@ 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 standount settings for displaying text. Not all of these
+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.
diff --git a/docs/manual/widgets.rst b/docs/manual/widgets.rst
index 44fe7d3..9fabb97 100644
--- a/docs/manual/widgets.rst
+++ b/docs/manual/widgets.rst
@@ -150,7 +150,7 @@ return ``None``.
container.focus_position
is a read/write property that provides access to the position of the
-container's widget in focus. This will often be a integer value but may be
+container's widget in focus. This will often be an integer value but may be
any object.
:class:`Columns`, :class:`Pile`, :class:`GridFlow`, :class:`Overlay` and
:class:`ListBox` with a :class:`SimpleListWalker` or :class:`SimpleFocusListWalker`
@@ -237,7 +237,7 @@ and proceeding along each child widget until reaching a leaf
(non-container) widget.
Note that the list does not contain the topmost container widget
-(i.e, on which this method is called), but does include the
+(i.e., on which this method is called), but does include the
lowest leaf widget.
::
diff --git a/docs/tools/templates/indexcontent.html b/docs/tools/templates/indexcontent.html
index 851e7d1..2ece841 100644
--- a/docs/tools/templates/indexcontent.html
+++ b/docs/tools/templates/indexcontent.html
@@ -13,25 +13,25 @@
<li><a href="changelog.html">Changelog</a>
</ul>
-<div class="hilight"><pre>git clone https://github.com/wardi/urwid.git</pre></div>
+<div class="hilight"><pre>git clone https://github.com/urwid/urwid.git</pre></div>
<ul>
-<li><a href="http://github.com/wardi/urwid">Github page</a></li>
-<li><a href="http://github.com/wardi/urwid/issues">Issues</a></li>
+<li><a href="http://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="http://lists.excess.org/mailman/listinfo/urwid">Mailing list</a>
-(<a href="http://dir.gmane.org/gmane.comp.lib.urwid">gmane</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>
<p>Wiki:</p>
<ul>
-<li><a href="http://github.com/wardi/urwid/wiki/Installation-instructions">Installation instructions</a></li>
-<li><a href="http://github.com/wardi/urwid/wiki/FAQ">FAQ</a></li>
-<li><a href="http://github.com/wardi/urwid/wiki/Application-list">Applications built with Urwid</a></li>
-<li><a href="http://github.com/wardi/urwid/wiki/How-you-can-help">How you can help</a></li>
-<li><a href="http://github.com/wardi/urwid/wiki/Coding-style">Coding style</a></li>
-<li><a href="http://github.com/wardi/urwid/wiki/Planned-development">Planned development</a></li>
+<li><a href="http://github.com/urwid/urwid/wiki/Installation-instructions">Installation instructions</a></li>
+<li><a href="http://github.com/urwid/urwid/wiki/FAQ">FAQ</a></li>
+<li><a href="http://github.com/urwid/urwid/wiki/Application-list">Applications built with Urwid</a></li>
+<li><a href="http://github.com/urwid/urwid/wiki/How-you-can-help">How you can help</a></li>
+<li><a href="http://github.com/urwid/urwid/wiki/Coding-style">Coding style</a></li>
+<li><a href="http://github.com/urwid/urwid/wiki/Planned-development">Planned development</a></li>
</ul>
<div class="section" id="requirements">
diff --git a/docs/tutorial/index.rst b/docs/tutorial/index.rst
index 1a84bb6..b4a6553 100644
--- a/docs/tutorial/index.rst
+++ b/docs/tutorial/index.rst
@@ -151,7 +151,7 @@ a widget before the correct one has been created.
assigning to its :attr:`MainLoop.widget` property.
* :ref:`decoration-widgets` like :class:`AttrMap` have an
- ``original_widget`` property that we can assign to to change the widget they wrap.
+ ``original_widget`` property that we can assign to change the widget they wrap.
* :class:`Divider` widgets are used to create blank lines,
colored with :class:`AttrMap`.
@@ -317,9 +317,9 @@ return to previous menus by pressing *ESC*.
:linenos:
* *menu_button()* returns an :class:`AttrMap`-decorated :class:`Button`
- and attaches a *callback* to the the its ``'click'`` signal. This function is
+ and attaches a *callback* to its ``'click'`` signal. This function is
used for both sub-menus and final selection buttons.
-* *sub_menu()* creates a menu button and a closure that will open the the
+* *sub_menu()* creates a menu button and a closure that will open the
menu when that button is clicked. Notice that
:ref:`text markup <text-markup>` is used to add ``'...'`` to the end of
the *caption* passed to *menu_button()*.
diff --git a/docs/tutorial/new/adventure.py b/docs/tutorial/new/adventure.py
deleted file mode 100644
index 129ff50..0000000
--- a/docs/tutorial/new/adventure.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import urwid
-
-class ActionButton(urwid.Button):
- def __init__(self, caption, callback):
- super(ActionButton, self).__init__("")
- urwid.connect_signal(self, 'click', callback)
- self._w = urwid.AttrMap(urwid.SelectableIcon(caption, 1),
- None, focus_map='reversed')
-
-class Place(urwid.WidgetWrap):
- def __init__(self, name, choices):
- super(Place, self).__init__(
- ActionButton([u" > go to ", name], self.enter_place))
- self.heading = urwid.Text([u"\nLocation: ", name, "\n"])
- self.choices = choices
- # create links back to ourself
- for child in choices:
- getattr(child, 'choices', []).insert(0, self)
-
- def enter_place(self, button):
- game.update_place(self)
-
-class Thing(urwid.WidgetWrap):
- def __init__(self, name):
- super(Thing, self).__init__(
- ActionButton([u" * take ", name], self.take_thing))
- self.name = name
-
- def take_thing(self, button):
- self._w = urwid.Text(u" - %s (taken)" % self.name)
- game.take_thing(self)
-
-def exit_program(button):
- raise urwid.ExitMainLoop()
-
-map_top = Place(u'porch', [
- Place(u'kitchen', [
- Place(u'refrigerator', []),
- Place(u'cupboard', [
- Thing(u'jug'),
- ]),
- ]),
- Place(u'garden', [
- Place(u'tree', [
- Thing(u'lemon'),
- Thing(u'bird'),
- ]),
- ]),
- Place(u'street', [
- Place(u'store', [
- Thing(u'sugar'),
- ]),
- Place(u'lake', [
- Place(u'beach', []),
- ]),
- ]),
-])
-
-class AdventureGame(object):
- def __init__(self):
- self.log = urwid.SimpleFocusListWalker([])
- self.top = urwid.ListBox(self.log)
- self.inventory = set()
- self.update_place(map_top)
-
- def update_place(self, place):
- if self.log: # disable interaction with previous place
- self.log[-1] = urwid.WidgetDisable(self.log[-1])
- self.log.append(urwid.Pile([place.heading] + place.choices))
- self.top.focus_position = len(self.log) - 1
- self.place = place
-
- def take_thing(self, thing):
- self.inventory.add(thing.name)
- if self.inventory >= set([u'sugar', u'lemon', u'jug']):
- response = urwid.Text(u'You can make lemonade!\n')
- done = ActionButton(u' - Joy', exit_program)
- self.log[:] = [response, done]
- else:
- self.update_place(self.place)
-
-game = AdventureGame()
-urwid.MainLoop(game.top, palette=[('reversed', 'standout', '')]).run()
diff --git a/docs/tutorial/new/adventure.py.xdotool b/docs/tutorial/new/adventure.py.xdotool
deleted file mode 100644
index a09d0fd..0000000
--- a/docs/tutorial/new/adventure.py.xdotool
+++ /dev/null
@@ -1,4 +0,0 @@
-windowsize --usehints $RXVTWINDOWID 23 16
-key --window $RXVTWINDOWID Return Down Down
-key --window $RXVTWINDOWID Return Down
-key --window $RXVTWINDOWID Return
diff --git a/docs/tutorial/new/adventure1.png b/docs/tutorial/new/adventure1.png
deleted file mode 100644
index 5714ed2..0000000
--- a/docs/tutorial/new/adventure1.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorial/new/adventure2.png b/docs/tutorial/new/adventure2.png
deleted file mode 100644
index ad3b819..0000000
--- a/docs/tutorial/new/adventure2.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorial/new/adventure3.png b/docs/tutorial/new/adventure3.png
deleted file mode 100644
index b8042b9..0000000
--- a/docs/tutorial/new/adventure3.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorial/new/adventure4.png b/docs/tutorial/new/adventure4.png
deleted file mode 100644
index d3023f4..0000000
--- a/docs/tutorial/new/adventure4.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorial/new/lbscr1.png b/docs/tutorial/new/lbscr1.png
deleted file mode 100644
index 79db363..0000000
--- a/docs/tutorial/new/lbscr1.png
+++ /dev/null
Binary files differ
diff --git a/docs/tutorial/new/minimal1.png b/docs/tutorial/new/minimal1.png
deleted file mode 100644
index 4fb2188..0000000
--- a/docs/tutorial/new/minimal1.png
+++ /dev/null
Binary files differ
diff --git a/examples/asyncio_socket_server.py b/examples/asyncio_socket_server.py
index 87592d3..31c9e81 100644
--- a/examples/asyncio_socket_server.py
+++ b/examples/asyncio_socket_server.py
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
"""Demo of using urwid with Python 3.4's asyncio.
This code works on older Python 3.x if you install `asyncio` from PyPI, and
diff --git a/examples/bigtext.py b/examples/bigtext.py
index 8fc448c..6a088ea 100755
--- a/examples/bigtext.py
+++ b/examples/bigtext.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid BigText example program
# Copyright (C) 2004-2009 Ian Ward
diff --git a/examples/browse.py b/examples/browse.py
index d5a5f16..6c0eb37 100755
--- a/examples/browse.py
+++ b/examples/browse.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid example lazy directory browser / tree view
# Copyright (C) 2004-2011 Ian Ward
@@ -31,6 +31,8 @@ Features:
- outputs a quoted list of files and directories "selected" on exit
"""
+from __future__ import print_function
+
import itertools
import re
import os
@@ -184,7 +186,7 @@ class DirectoryNode(urwid.ParentNode):
dirs.append(a)
else:
files.append(a)
- except OSError, e:
+ except OSError as e:
depth = self.get_depth() + 1
self._children[None] = ErrorNode(self, parent=self, key=None,
depth=depth)
@@ -274,7 +276,7 @@ class DirectoryBrowser:
# on exit, write the flagged filenames to the console
names = [escape_filename_sh(x) for x in get_flagged_names()]
- print " ".join(names)
+ print(" ".join(names))
def unhandled_input(self, k):
# update display of focus directory
diff --git a/examples/calc.py b/examples/calc.py
index 2ac324a..39c37f7 100755
--- a/examples/calc.py
+++ b/examples/calc.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid advanced example column calculator application
# Copyright (C) 2004-2009 Ian Ward
@@ -31,6 +31,8 @@ Features:
- outputs commands that may be used to recreate expression on exit
"""
+from __future__ import print_function
+
import urwid
import urwid.raw_display
import urwid.web_display
@@ -60,7 +62,7 @@ OPERATORS = {
COLUMN_KEYS = list( "?ABCDEF" )
# these lists are used to determine when to display errors
-EDIT_KEYS = OPERATORS.keys() + COLUMN_KEYS + ['backspace','delete']
+EDIT_KEYS = list(OPERATORS.keys()) + COLUMN_KEYS + ['backspace','delete']
MOVEMENT_KEYS = ['up','down','left','right','page up','page down']
# Event text
@@ -144,7 +146,7 @@ class Cell:
if self.child is not None:
return self.child.get_result()
else:
- return long("0"+self.edit.edit_text)
+ return int("0"+self.edit.edit_text)
def get_result(self):
"""Return the numeric result of this cell's operation."""
@@ -153,7 +155,7 @@ class Cell:
return self.get_value()
if self.result.text == "":
return None
- return long(self.result.text)
+ return int(self.result.text)
def set_result(self, result):
"""Set the numeric result for this cell."""
@@ -212,7 +214,7 @@ class ParentEdit(urwid.Edit):
if key == "backspace":
raise ColumnDeleteEvent(self.letter, from_parent=True)
elif key in list("0123456789"):
- raise CalcEvent, E_invalid_in_parent_cell
+ raise CalcEvent(E_invalid_in_parent_cell)
else:
return key
@@ -344,7 +346,7 @@ class CellColumn( urwid.WidgetWrap ):
# cell above is parent
if edit.edit_text:
# ..and current not empty, no good
- raise CalcEvent, E_cant_combine
+ raise CalcEvent(E_cant_combine)
above_pos = 0
else:
# above is normal number cell
@@ -366,13 +368,13 @@ class CellColumn( urwid.WidgetWrap ):
below = self.walker.get_cell(i+1)
if cell.child is not None:
# this cell is a parent
- raise CalcEvent, E_cant_combine
+ raise CalcEvent(E_cant_combine)
if below is None:
# nothing below
return key
if below.child is not None:
# cell below is a parent
- raise CalcEvent, E_cant_combine
+ raise CalcEvent(E_cant_combine)
edit = self.walker.get_cell(i).edit
edit.set_edit_text( edit.edit_text +
@@ -453,9 +455,9 @@ class CellColumn( urwid.WidgetWrap ):
cell = self.walker.get_cell(i)
if cell.child is not None:
- raise CalcEvent, E_new_col_cell_not_empty
+ raise CalcEvent(E_new_col_cell_not_empty)
if cell.edit.edit_text:
- raise CalcEvent, E_new_col_cell_not_empty
+ raise CalcEvent(E_new_col_cell_not_empty)
child = CellColumn( letter )
cell.become_parent( child, letter )
@@ -579,9 +581,9 @@ class CalcDisplay:
# on exit write the formula and the result to the console
expression, result = self.get_expression_result()
- print "Paste this expression into a new Column Calculator session to continue editing:"
- print expression
- print "Result:", result
+ print( "Paste this expression into a new Column Calculator session to continue editing:")
+ print( expression)
+ print( "Result:", result)
def input_filter(self, input, raw_input):
if 'q' in input or 'Q' in input:
@@ -593,7 +595,7 @@ class CalcDisplay:
self.wrap_keypress(k)
self.event = None
self.view.footer = None
- except CalcEvent, e:
+ except CalcEvent as e:
# display any message
self.event = e
self.view.footer = e.widget()
@@ -607,7 +609,7 @@ class CalcDisplay:
try:
key = self.keypress(key)
- except ColumnDeleteEvent, e:
+ except ColumnDeleteEvent as e:
if e.letter == COLUMN_KEYS[1]:
# cannot delete the first column, ignore key
return
@@ -619,7 +621,7 @@ class CalcDisplay:
raise e
self.delete_column(e.letter)
- except UpdateParentEvent, e:
+ except UpdateParentEvent as e:
self.update_parent_columns()
return
@@ -628,10 +630,10 @@ class CalcDisplay:
if self.columns.get_focus_column() == 0:
if key not in ('up','down','page up','page down'):
- raise CalcEvent, E_invalid_in_help_col
+ raise CalcEvent(E_invalid_in_help_col)
if key not in EDIT_KEYS and key not in MOVEMENT_KEYS:
- raise CalcEvent, E_invalid_key % key.upper()
+ raise CalcEvent(E_invalid_key % key.upper())
def keypress(self, key):
"""Handle a keystroke."""
@@ -642,13 +644,13 @@ class CalcDisplay:
# column switch
i = COLUMN_KEYS.index(key.upper())
if i >= len( self.col_list ):
- raise CalcEvent, E_no_such_column % key.upper()
+ raise CalcEvent(E_no_such_column % key.upper())
self.columns.set_focus_column( i )
return
elif key == "(":
# open a new column
if len( self.col_list ) >= len(COLUMN_KEYS):
- raise CalcEvent, E_no_more_columns
+ raise CalcEvent(E_no_more_columns)
i = self.columns.get_focus_column()
if i == 0:
# makes no sense in help column
@@ -672,7 +674,7 @@ class CalcDisplay:
parent, pcol = self.get_parent( col )
if parent is None:
# column has no parent
- raise CalcEvent, E_no_parent_column
+ raise CalcEvent(E_no_parent_column)
new_i = self.col_list.index( pcol )
self.columns.set_focus_column( new_i )
diff --git a/examples/dialog.py b/examples/dialog.py
index 7f3a4d5..b444280 100755
--- a/examples/dialog.py
+++ b/examples/dialog.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid example similar to dialog(1) program
# Copyright (C) 2004-2009 Ian Ward
@@ -100,7 +100,7 @@ class DialogDisplay:
self.loop = urwid.MainLoop(self.view, self.palette)
try:
self.loop.run()
- except DialogExit, e:
+ except DialogExit as e:
return self.on_exit( e.args[0] )
def on_exit(self, exitcode):
@@ -230,12 +230,12 @@ class MenuItem(urwid.Text):
def keypress(self,size,key):
if key == "enter":
self.state = True
- raise DialogExit, 0
+ raise DialogExit(0)
return key
def mouse_event(self,size,event,button,col,row,focus):
if event=='mouse release':
self.state = True
- raise DialogExit, 0
+ raise DialogExit(0)
return False
def get_state(self):
return self.state
diff --git a/examples/edit.py b/examples/edit.py
index e4fb4b0..e2cc2fa 100755
--- a/examples/edit.py
+++ b/examples/edit.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid example lazy text editor suitable for tabbed and format=flowed text
# Copyright (C) 2004-2009 Ian Ward
diff --git a/examples/fib.py b/examples/fib.py
index e3262b4..875c8fc 100755
--- a/examples/fib.py
+++ b/examples/fib.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid example fibonacci sequence viewer / unbounded data demo
# Copyright (C) 2004-2007 Ian Ward
@@ -35,7 +35,7 @@ class FibonacciWalker(urwid.ListWalker):
positions returned are (value at position-1, value at position) tuples.
"""
def __init__(self):
- self.focus = (0L,1L)
+ self.focus = (0,1)
self.numeric_layout = NumericLayout()
def _get_at_pos(self, pos):
diff --git a/examples/graph.py b/examples/graph.py
index c21c9a9..4a5b5dd 100755
--- a/examples/graph.py
+++ b/examples/graph.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid graphics example program
# Copyright (C) 2004-2011 Ian Ward
@@ -48,7 +48,7 @@ class GraphModel:
data_max_value = 100
def __init__(self):
- data = [ ('Saw', range(0,100,2)*2),
+ data = [ ('Saw', list(range(0,100,2))*2),
('Square', [0]*30 + [100]*30),
('Sine 1', [sin100(x) for x in range(100)] ),
('Sine 2', [(sin100(x) + sin100(x*2))/2
diff --git a/examples/input_test.py b/examples/input_test.py
index 2c154bc..0a8eed9 100755
--- a/examples/input_test.py
+++ b/examples/input_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid keyboard input test app
# Copyright (C) 2004-2009 Ian Ward
diff --git a/examples/lcd_cf635.py b/examples/lcd_cf635.py
index 09f66ef..0377233 100755
--- a/examples/lcd_cf635.py
+++ b/examples/lcd_cf635.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
"""
The crystalfontz 635 has these characters in ROM:
diff --git a/examples/palette_test.py b/examples/palette_test.py
index 820feb5..0c98623 100755
--- a/examples/palette_test.py
+++ b/examples/palette_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid Palette Test. Showing off highcolor support
# Copyright (C) 2004-2009 Ian Ward
diff --git a/examples/pop_up.py b/examples/pop_up.py
index 37e2258..f88c717 100755
--- a/examples/pop_up.py
+++ b/examples/pop_up.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
import urwid
diff --git a/examples/subproc.py b/examples/subproc.py
index 64eb072..f04e7b0 100755
--- a/examples/subproc.py
+++ b/examples/subproc.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
import subprocess
import urwid
@@ -21,7 +21,7 @@ def exit_on_enter(key):
loop = urwid.MainLoop(frame_widget, unhandled_input=exit_on_enter)
def received_output(data):
- output_widget.set_text(output_widget.text + data)
+ output_widget.set_text(output_widget.text + data.decode('utf8'))
write_fd = loop.watch_pipe(received_output)
proc = subprocess.Popen(
diff --git a/examples/subproc2.py b/examples/subproc2.py
index 79c73b2..a60aa72 100644
--- a/examples/subproc2.py
+++ b/examples/subproc2.py
@@ -1,8 +1,16 @@
+#!/usr/bin/env python
# this is part of the subproc.py example
+from __future__ import print_function
+
import sys
+try:
+ range = xrange
+except NameError:
+ pass
+
num = int(sys.argv[1])
-for c in xrange(1,10000000):
+for c in range(1,10000000):
if num % c == 0:
- print "factor:", c
+ print("factor:", c)
diff --git a/examples/terminal.py b/examples/terminal.py
index 18edba1..ff30a93 100755
--- a/examples/terminal.py
+++ b/examples/terminal.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid terminal emulation widget example app
# Copyright (C) 2010 aszlig
diff --git a/examples/tour.py b/examples/tour.py
index fedfb59..cf04da2 100755
--- a/examples/tour.py
+++ b/examples/tour.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid tour. It slices, it dices..
# Copyright (C) 2004-2011 Ian Ward
diff --git a/examples/treesample.py b/examples/treesample.py
index f6f771d..7b4f42f 100755
--- a/examples/treesample.py
+++ b/examples/treesample.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Trivial data browser
# This version:
diff --git a/examples/twisted_serve_ssh.py b/examples/twisted_serve_ssh.py
index aad63f9..b2e0206 100644
--- a/examples/twisted_serve_ssh.py
+++ b/examples/twisted_serve_ssh.py
@@ -1,4 +1,3 @@
-# encoding: utf-8
"""
Twisted integration for Urwid.
@@ -31,6 +30,8 @@ Portions Copyright: 2010, Ian Ward <ian@excess.org>
Licence: LGPL <http://opensource.org/licenses/lgpl-2.1.php>
"""
+from __future__ import print_function
+
import os
import urwid
@@ -198,7 +199,7 @@ class TwistedScreen(Screen):
"""
return self.terminalProtocol.width, self.terminalProtocol.height
- def draw_screen(self, (maxcol, maxrow), r ):
+ def draw_screen(self, maxres, r ):
"""Render a canvas to the terminal.
The canvas contains all the information required to render the Urwid
@@ -206,6 +207,7 @@ class TwistedScreen(Screen):
tuples. This very simple implementation iterates each row and simply
writes it out.
"""
+ (maxcol, maxrow) = maxres
#self.terminal.eraseDisplay()
lasta = None
for i, row in enumerate(r.content()):
@@ -409,9 +411,10 @@ class UrwidTerminalSession(TerminalSession):
IConchUser(self.original),
self.height, self.width)
- def windowChanged(self, (h, w, x, y)):
+ def windowChanged(self, dimensions):
"""Called when the window size has changed.
"""
+ (h, w, x, y) = dimensions
self.chained_protocol.terminalProtocol.terminalSize(h, w)
diff --git a/setup.cfg b/setup.cfg
index 861a9f5..8bfd5a1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,4 @@
[egg_info]
tag_build =
tag_date = 0
-tag_svn_revision = 0
diff --git a/setup.py b/setup.py
index 6a4d61b..60fdb02 100644
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
#
# Urwid setup.py exports the useful bits
# Copyright (C) 2004-2014 Ian Ward
@@ -69,6 +69,8 @@ setup_d = {
"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 :: Implementation :: PyPy",
],
}
@@ -77,9 +79,6 @@ if have_setuptools:
setup_d['zip_safe'] = False
setup_d['test_suite'] = 'urwid.tests'
-if PYTHON3:
- setup_d['use_2to3'] = True
-
if __name__ == "__main__":
try:
setup(**setup_d)
diff --git a/source/str_util.c b/source/str_util.c
index 425a74a..5ed1356 100644
--- a/source/str_util.c
+++ b/source/str_util.c
@@ -561,7 +561,7 @@ static Py_ssize_t Py_MovePrevChar(PyObject *text, Py_ssize_t start_offs,
if (byte_encoding == ENC_UTF8) //encoding is utf8
{
position = end_offs - 1;
- while ((str[position]&0xc0) == 0x80)
+ while ((position > start_offs) && (str[position]&0xc0) == 0x80)
position -=1;
return position;
}
@@ -715,7 +715,7 @@ static PyObject * calc_width(PyObject *self, PyObject *args)
return NULL;
ret = Py_CalcWidth(text, start_offs, end_offs);
- if (ret==-1) //an error occured
+ if (ret==-1) //an error occurred
return NULL;
return Py_BuildValue("l", ret);
@@ -843,7 +843,7 @@ static PyObject * calc_text_pos(PyObject *self, PyObject *args)
return NULL;
err = Py_CalcTextPos(text, start_offs, end_offs, pref_col, ret);
- if (err==-1) //an error occured
+ if (err==-1) //an error occurred
return NULL;
return Py_BuildValue("(" FMT_N FMT_N ")", ret[0], ret[1]);
diff --git a/urwid.egg-info/PKG-INFO b/urwid.egg-info/PKG-INFO
index 01183c4..47b320d 100644
--- a/urwid.egg-info/PKG-INFO
+++ b/urwid.egg-info/PKG-INFO
@@ -1,12 +1,16 @@
Metadata-Version: 1.1
Name: urwid
-Version: 1.3.1
+Version: 2.0.1
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
+ =====
+
Urwid is a console user interface library for Python.
It includes many features useful for text console application developers including:
@@ -24,6 +28,54 @@ Description:
Home Page:
http://urwid.org/
+ Testing
+ =======
+
+ To run tests locally, install & run `tox`. You must have
+ appropriate Python versions installed to run `tox` for
+ each of them.
+
+ To test code in all Python versions:
+
+ .. code:: bash
+
+ tox # Test all versions specified in tox.ini:
+ tox -e py36 # Test Python 3.6 only
+ tox -e py27,py36,pypy # Test Python 2.7, Python 3.6 & pypy
+
+ Contributors
+ ============
+
+ - `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>`_
+
Keywords: curses ui widget scroll listbox user interface text layout console ncurses
Platform: unix-like
Classifier: Development Status :: 5 - Production/Stable
@@ -43,4 +95,6 @@ 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 :: Implementation :: PyPy
diff --git a/urwid.egg-info/SOURCES.txt b/urwid.egg-info/SOURCES.txt
index eaa899c..fd72eeb 100644
--- a/urwid.egg-info/SOURCES.txt
+++ b/urwid.egg-info/SOURCES.txt
@@ -152,14 +152,6 @@ docs/tutorial/smenu.py.xdotool
docs/tutorial/smenu1.png
docs/tutorial/smenu2.png
docs/tutorial/smenu3.png
-docs/tutorial/new/adventure.py
-docs/tutorial/new/adventure.py.xdotool
-docs/tutorial/new/adventure1.png
-docs/tutorial/new/adventure2.png
-docs/tutorial/new/adventure3.png
-docs/tutorial/new/adventure4.png
-docs/tutorial/new/lbscr1.png
-docs/tutorial/new/minimal1.png
examples/asyncio_socket_server.py
examples/bigtext.py
examples/browse.py
diff --git a/urwid/__init__.py b/urwid/__init__.py
index bc5170e..32484de 100644
--- a/urwid/__init__.py
+++ b/urwid/__init__.py
@@ -20,6 +20,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
from urwid.version import VERSION, __version__
from urwid.widget import (FLOW, BOX, FIXED, LEFT, RIGHT, CENTER, TOP, MIDDLE,
BOTTOM, SPACE, ANY, CLIP, PACK, GIVEN, RELATIVE, RELATIVE_100, WEIGHT,
diff --git a/urwid/canvas.py b/urwid/canvas.py
index 4a51d3e..207ee21 100644
--- a/urwid/canvas.py
+++ b/urwid/canvas.py
@@ -19,6 +19,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
import weakref
from urwid.util import rle_len, rle_append_modify, rle_join_modify, rle_product, \
@@ -828,7 +830,7 @@ def shard_body_row(sbody):
row = []
for done_rows, content_iter, cview in sbody:
if content_iter:
- row.extend(content_iter.next())
+ row.extend(next(content_iter))
else:
# need to skip this unchanged canvas
if row and type(row[-1]) == int:
@@ -867,10 +869,10 @@ def shards_delta(shards, other_shards):
done = other_done = 0
for num_rows, cviews in shards:
if other_num_rows is None:
- other_num_rows, other_cviews = other_shards_iter.next()
+ other_num_rows, other_cviews = next(other_shards_iter)
while other_done < done:
other_done += other_num_rows
- other_num_rows, other_cviews = other_shards_iter.next()
+ other_num_rows, other_cviews = next(other_shards_iter)
if other_done > done:
yield (num_rows, cviews)
done += num_rows
@@ -889,10 +891,10 @@ def shard_cviews_delta(cviews, other_cviews):
cols = other_cols = 0
for cv in cviews:
if other_cv is None:
- other_cv = other_cviews_iter.next()
+ other_cv = next(other_cviews_iter)
while other_cols < cols:
other_cols += other_cv[2]
- other_cv = other_cviews_iter.next()
+ other_cv = next(other_cviews_iter)
if other_cols > cols:
yield cv
cols += cv[2]
@@ -926,7 +928,7 @@ def shard_body(cviews, shard_tail, create_iter=True, iter_default=None):
for col_gap, done_rows, content_iter, tail_cview in shard_tail:
while col_gap:
try:
- cview = cviews_iter.next()
+ cview = next(cviews_iter)
except StopIteration:
raise CanvasError("cviews do not fill gaps in"
" shard_tail!")
@@ -1057,7 +1059,7 @@ def shards_join(shard_lists):
All shards lists must have the same number of rows.
"""
shards_iters = [iter(sl) for sl in shard_lists]
- shards_current = [i.next() for i in shards_iters]
+ shards_current = [next(i) for i in shards_iters]
new_shards = []
while True:
@@ -1078,7 +1080,7 @@ def shards_join(shard_lists):
for i in range(len(shards_current)):
if shards_current[i][0] > 0:
continue
- shards_current[i] = shards_iters[i].next()
+ shards_current[i] = next(shards_iters[i])
except StopIteration:
break
return new_shards
diff --git a/urwid/command_map.py b/urwid/command_map.py
index 15633f8..e6965f7 100644
--- a/urwid/command_map.py
+++ b/urwid/command_map.py
@@ -19,6 +19,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
REDRAW_SCREEN = 'redraw screen'
CURSOR_UP = 'cursor up'
CURSOR_DOWN = 'cursor down'
diff --git a/urwid/compat.py b/urwid/compat.py
index 686d703..4dde62f 100644
--- a/urwid/compat.py
+++ b/urwid/compat.py
@@ -20,6 +20,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
import sys
try: # python 2.4 and 2.5 compat
@@ -39,10 +41,71 @@ if PYTHON3:
chr2 = lambda x: bytes([x])
B = lambda x: x.encode('iso8859-1')
bytes3 = bytes
+ text_type = str
+ xrange = range
+ text_types = (str,)
+
+ def reraise(tp, value, tb=None):
+ """
+ Reraise an exception.
+ Taken from "six" library (https://pythonhosted.org/six/).
+ """
+ try:
+ if value is None:
+ value = tp()
+ if value.__traceback__ is not tb:
+ raise value.with_traceback(tb)
+ raise value
+ finally:
+ value = None
+ tb = None
else:
ord2 = ord
chr2 = chr
B = lambda x: x
bytes3 = lambda x: bytes().join([chr(c) for c in x])
+ text_type = unicode
+ xrange = xrange
+ text_types = (str, unicode)
+
+ """
+ Reraise an exception.
+ Taken from "six" library (https://pythonhosted.org/six/).
+ """
+ def exec_(_code_, _globs_=None, _locs_=None):
+ """Execute code in a namespace."""
+ if _globs_ is None:
+ frame = sys._getframe(1)
+ _globs_ = frame.f_globals
+ if _locs_ is None:
+ _locs_ = frame.f_locals
+ del frame
+ elif _locs_ is None:
+ _locs_ = _globs_
+ exec("""exec _code_ in _globs_, _locs_""")
+
+ exec_("""def reraise(tp, value, tb=None):
+ try:
+ raise tp, value, tb
+ finally:
+ tb = None
+""")
+
+def with_metaclass(meta, *bases):
+ """
+ Create a base class with a metaclass.
+ Taken from "six" library (https://pythonhosted.org/six/).
+ """
+ # This requires a bit of explanation: the basic idea is to make a dummy
+ # metaclass for one level of class instantiation that replaces itself with
+ # the actual metaclass.
+ class metaclass(type):
+
+ def __new__(cls, name, this_bases, d):
+ return meta(name, bases, d)
+ @classmethod
+ def __prepare__(cls, name, this_bases):
+ return meta.__prepare__(name, bases)
+ return type.__new__(metaclass, 'temporary_class', (), {})
diff --git a/urwid/container.py b/urwid/container.py
index 3999f28..ed4ee59 100755
--- a/urwid/container.py
+++ b/urwid/container.py
@@ -19,7 +19,10 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
from itertools import chain, repeat
+from urwid.compat import xrange
from urwid.util import is_mouse_press
from urwid.widget import (Widget, Divider, FLOW, FIXED, PACK, BOX, WidgetWrap,
@@ -92,7 +95,7 @@ class WidgetContainerMixin(object):
(non-container) widget.
Note that the list does not contain the topmost container widget
- (i.e, on which this method is called), but does include the
+ (i.e., on which this method is called), but does include the
lowest leaf widget.
"""
out = []
@@ -113,14 +116,14 @@ class WidgetContainerListContentsMixin(object):
Return an iterable of positions for this container from first
to last.
"""
- return xrange(len(self.contents))
+ return iter(xrange(len(self.contents)))
def __reversed__(self):
"""
Return an iterable of positions for this container from last
to first.
"""
- return xrange(len(self.contents) - 1, -1, -1)
+ return iter(xrange(len(self.contents) - 1, -1, -1))
class GridFlowError(Exception):
@@ -1589,9 +1592,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
return key
if self._command_map[key] == 'cursor up':
- candidates = range(i-1, -1, -1) # count backwards to 0
+ candidates = list(range(i-1, -1, -1)) # count backwards to 0
else: # self._command_map[key] == 'cursor down'
- candidates = range(i+1, len(self.contents))
+ candidates = list(range(i+1, len(self.contents)))
if not item_rows:
item_rows = self.get_item_rows(size, focus=True)
@@ -1607,9 +1610,9 @@ class Pile(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
rows = item_rows[j]
if self._command_map[key] == 'cursor up':
- rowlist = range(rows-1, -1, -1)
+ rowlist = list(range(rows-1, -1, -1))
else: # self._command_map[key] == 'cursor down'
- rowlist = range(rows)
+ rowlist = list(range(rows))
for row in rowlist:
tsize = self.get_item_size(size, j, True, item_rows)
if self.focus_item.move_cursor_to_coords(
@@ -1718,7 +1721,7 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
is an int
(``'pack'``, *widget*)
call :meth:`pack() <Widget.pack>` to calculate the width of this column
- (``'weight'``, *weight*, *widget*)`
+ (``'weight'``, *weight*, *widget*)
give this column a relative *weight* (number) to calculate its width from the
screen columns remaining
@@ -1771,7 +1774,6 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
self.focus_position = focus_column
if focus_column is None:
focus_column = 0
- self.dividechars = dividechars
self.pref_col = None
self.min_width = min_width
self._cache_maxcol = None
@@ -2272,9 +2274,9 @@ class Columns(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
return key
if self._command_map[key] == 'cursor left':
- candidates = range(i-1, -1, -1) # count backwards to 0
+ candidates = list(range(i-1, -1, -1)) # count backwards to 0
else: # key == 'right'
- candidates = range(i+1, len(self.contents))
+ candidates = list(range(i+1, len(self.contents)))
for j in candidates:
if not self.contents[j][0].selectable():
diff --git a/urwid/curses_display.py b/urwid/curses_display.py
index 441042e..0aaa2f1 100755
--- a/urwid/curses_display.py
+++ b/urwid/curses_display.py
@@ -19,6 +19,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
"""
Curses-based UI implementation
"""
@@ -30,7 +32,7 @@ from urwid import escape
from urwid.display_common import BaseScreen, RealTerminal, AttrSpec, \
UNPRINTABLE_TRANS_TABLE
-from urwid.compat import bytes, PYTHON3
+from urwid.compat import bytes, PYTHON3, text_type, xrange
KEY_RESIZE = 410 # curses.KEY_RESIZE (sometimes not defined)
KEY_MOUSE = 409 # curses.KEY_MOUSE
@@ -481,10 +483,12 @@ class Screen(BaseScreen, RealTerminal):
self.s.attrset(attr)
- def draw_screen(self, (cols, rows), r ):
+ def draw_screen(self, size, r ):
"""Paint screen with rendered canvas."""
assert self._started
+ cols, rows = size
+
assert r.rows() == rows, "canvas size and passed size don't match"
y = -1
@@ -558,7 +562,7 @@ class Screen(BaseScreen, RealTerminal):
class _test:
def __init__(self):
self.ui = Screen()
- self.l = _curses_colours.keys()
+ self.l = list(_curses_colours.keys())
self.l.sort()
for c in self.l:
self.ui.register_palette( [
@@ -602,7 +606,7 @@ class _test:
t = ""
a = []
for k in keys:
- if type(k) == unicode: k = k.encode("utf-8")
+ if type(k) == text_type: k = k.encode("utf-8")
t += "'"+k + "' "
a += [(None,1), ('yellow on dark blue',len(k)),
(None,2)]
diff --git a/urwid/decoration.py b/urwid/decoration.py
index 731eb91..9c18028 100755
--- a/urwid/decoration.py
+++ b/urwid/decoration.py
@@ -19,6 +19,7 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
from urwid.util import int_scale
from urwid.widget import (Widget, WidgetError,
@@ -129,14 +130,14 @@ class AttrMap(delegate_to_widget_mixin('_original_widget'), WidgetDecoration):
<AttrMap selectable flow widget <Edit selectable flow widget '' edit_pos=0> attr_map={None: 'notfocus'} focus_map={None: 'focus'}>
>>> size = (5,)
>>> am = AttrMap(Text(u"hi"), 'greeting', 'fgreet')
- >>> am.render(size, focus=False).content().next() # ... = b in Python 3
+ >>> next(am.render(size, focus=False).content()) # ... = b in Python 3
[('greeting', None, ...'hi ')]
- >>> am.render(size, focus=True).content().next()
+ >>> next(am.render(size, focus=True).content())
[('fgreet', None, ...'hi ')]
>>> am2 = AttrMap(Text(('word', u"hi")), {'word':'greeting', None:'bg'})
>>> am2
<AttrMap flow widget <Text flow widget 'hi'> attr_map={'word': 'greeting', None: 'bg'}>
- >>> am2.render(size).content().next()
+ >>> next(am2.render(size).content())
[('greeting', None, ...'hi'), ('bg', None, ...' ')]
"""
self.__super.__init__(w)
@@ -247,9 +248,9 @@ class AttrWrap(AttrMap):
<AttrWrap selectable flow widget <Edit selectable flow widget '' edit_pos=0> attr='notfocus' focus_attr='focus'>
>>> size = (5,)
>>> aw = AttrWrap(Text(u"hi"), 'greeting', 'fgreet')
- >>> aw.render(size, focus=False).content().next()
+ >>> next(aw.render(size, focus=False).content())
[('greeting', None, ...'hi ')]
- >>> aw.render(size, focus=True).content().next()
+ >>> next(aw.render(size, focus=True).content())
[('fgreet', None, ...'hi ')]
"""
self.__super.__init__(w, attr, focus_attr)
@@ -460,7 +461,7 @@ class Padding(WidgetDecoration):
>>> size = (7,)
>>> def pr(w):
... for t in w.render(size).text:
- ... print "|%s|" % (t.decode('ascii'),)
+ ... print("|%s|" % (t.decode('ascii'),))
>>> pr(Padding(Text(u"Head"), ('relative', 20), 'pack'))
| Head |
>>> pr(Padding(Divider(u"-"), left=2, right=1))
@@ -731,14 +732,14 @@ class Filler(WidgetDecoration):
if isinstance(height, tuple):
if height[0] == 'fixed top':
if not isinstance(valign, tuple) or valign[0] != 'fixed bottom':
- raise FillerError("fixed bottom height may only be used "
- "with fixed top valign")
+ raise FillerError("fixed top height may only be used "
+ "with fixed bottom valign")
top = height[1]
height = RELATIVE_100
elif height[0] == 'fixed bottom':
if not isinstance(valign, tuple) or valign[0] != 'fixed top':
- raise FillerError("fixed top height may only be used "
- "with fixed bottom valign")
+ raise FillerError("fixed bottom height may only be used "
+ "with fixed top valign")
bottom = height[1]
height = RELATIVE_100
if isinstance(valign, tuple):
diff --git a/urwid/display_common.py b/urwid/display_common.py
index 7ff4eef..e447682 100755
--- a/urwid/display_common.py
+++ b/urwid/display_common.py
@@ -18,6 +18,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
import os
import sys
@@ -28,10 +30,10 @@ except ImportError:
from urwid.util import StoppingContext, int_scale
from urwid import signals
-from urwid.compat import B, bytes3
+from urwid.compat import B, bytes3, xrange, with_metaclass
# for replacing unprintable bytes with '?'
-UNPRINTABLE_TRANS_TABLE = B("?") * 32 + bytes3(range(32,256))
+UNPRINTABLE_TRANS_TABLE = B("?") * 32 + bytes3(list(xrange(32,256)))
# signals sent by BaseScreen
@@ -90,8 +92,10 @@ _STANDOUT = 0x02000000
_UNDERLINE = 0x04000000
_BOLD = 0x08000000
_BLINK = 0x10000000
+_ITALICS = 0x20000000
+_STRIKETHROUGH = 0x40000000
_FG_MASK = (_FG_COLOR_MASK | _FG_BASIC_COLOR | _FG_HIGH_COLOR |
- _STANDOUT | _UNDERLINE | _BLINK | _BOLD)
+ _STANDOUT | _UNDERLINE | _BLINK | _BOLD | _ITALICS | _STRIKETHROUGH)
_BG_MASK = _BG_COLOR_MASK | _BG_BASIC_COLOR | _BG_HIGH_COLOR
DEFAULT = 'default'
@@ -133,9 +137,11 @@ _BASIC_COLORS = [
_ATTRIBUTES = {
'bold': _BOLD,
+ 'italics': _ITALICS,
'underline': _UNDERLINE,
'blink': _BLINK,
'standout': _STANDOUT,
+ 'strikethrough': _STRIKETHROUGH,
}
def _value_lookup_table(values, size):
@@ -450,7 +456,8 @@ class AttrSpec(object):
'h8' (color number 8), 'h255' (color number 255)
Setting:
- 'bold', 'underline', 'blink', 'standout'
+ 'bold', 'italics', 'underline', 'blink', 'standout',
+ 'strikethrough'
Some terminals use 'bold' for bright colors. Most terminals
ignore the 'blink' setting. If the color is not given then
@@ -500,10 +507,12 @@ class AttrSpec(object):
background_high = property(lambda s: s._value & _BG_HIGH_COLOR != 0)
background_number = property(lambda s: (s._value & _BG_COLOR_MASK)
>> _BG_SHIFT)
+ italics = property(lambda s: s._value & _ITALICS != 0)
bold = property(lambda s: s._value & _BOLD != 0)
underline = property(lambda s: s._value & _UNDERLINE != 0)
blink = property(lambda s: s._value & _BLINK != 0)
standout = property(lambda s: s._value & _STANDOUT != 0)
+ strikethrough = property(lambda s: s._value & _STRIKETHROUGH != 0)
def _colors(self):
"""
@@ -543,8 +552,9 @@ class AttrSpec(object):
def _foreground(self):
return (self._foreground_color() +
- ',bold' * self.bold + ',standout' * self.standout +
- ',blink' * self.blink + ',underline' * self.underline)
+ ',bold' * self.bold + ',italics' * self.italics +
+ ',standout' * self.standout + ',blink' * self.blink +
+ ',underline' * self.underline + ',strikethrough' * self.strikethrough)
def _set_foreground(self, foreground):
color = None
@@ -711,11 +721,10 @@ class RealTerminal(object):
class ScreenError(Exception):
pass
-class BaseScreen(object):
+class BaseScreen(with_metaclass(signals.MetaSignals, object)):
"""
Base class for Screen classes (raw_display.Screen, .. etc)
"""
- __metaclass__ = signals.MetaSignals
signals = [UPDATE_PALETTE_ENTRY, INPUT_DESCRIPTORS_CHANGED]
def __init__(self):
@@ -810,7 +819,7 @@ class BaseScreen(object):
'light magenta', 'light cyan', 'white'
Settings:
- 'bold', 'underline', 'blink', 'standout'
+ 'bold', 'underline', 'blink', 'standout', 'strikethrough'
Some terminals use 'bold' for bright colors. Most terminals
ignore the 'blink' setting. If the color is not given then
diff --git a/urwid/escape.py b/urwid/escape.py
index 683466c..b047fb0 100644
--- a/urwid/escape.py
+++ b/urwid/escape.py
@@ -20,6 +20,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
"""
Terminal Escape Sequences for input and display
"""
diff --git a/urwid/font.py b/urwid/font.py
index bf0c2b1..e7bfe6e 100755
--- a/urwid/font.py
+++ b/urwid/font.py
@@ -20,9 +20,12 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
from urwid.escape import SAFE_ASCII_DEC_SPECIAL_RE
from urwid.util import apply_target_encoding, str_util
from urwid.canvas import TextCanvas
+from urwid.compat import text_type
def separate_glyphs(gdata, height):
@@ -96,9 +99,16 @@ class Font(object):
self.char = {}
self.canvas = {}
self.utf8_required = False
- for gdata in self.data:
+ data = [self._to_text(block) for block in self.data]
+ for gdata in data:
self.add_glyphs(gdata)
+ @staticmethod
+ def _to_text(obj, encoding='utf-8', errors='strict'):
+ if isinstance(obj, text_type):
+ return obj
+ elif isinstance(obj, bytes):
+ return obj.decode(encoding, errors)
def add_glyphs(self, gdata):
d, utf8_required = separate_glyphs(gdata, self.height)
@@ -106,7 +116,7 @@ class Font(object):
self.utf8_required |= utf8_required
def characters(self):
- l = self.char.keys()
+ l = list(self.char.keys())
l.sort()
return "".join(l)
@@ -147,7 +157,7 @@ class Thin3x3Font(Font):
┌─┐ ┐ ┌─┐┌─┐ ┐┌─ ┌─ ┌─┐┌─┐┌─┐ │
│ │ │ ┌─┘ ─┤└─┼└─┐├─┐ ┼├─┤└─┤ │
└─┘ ┴ └─ └─┘ ┴ ─┘└─┘ ┴└─┘ ─┘ .
-""", ur"""
+""", r"""
"###$$$%%%'*++,--.///:;==???[[\\\]]^__`
" ┼┼┌┼┐O /' /.. _┌─┐┌ \ ┐^ `
┼┼└┼┐ / * ┼ ─ / ., _ ┌┘│ \ │
@@ -179,7 +189,7 @@ class HalfBlock5x4Font(Font):
▀█▀█▀ ▀▄█▄ █ ▀▄▀ ▐▌ ▐▌ ▄▄█▄▄ ▄▄█▄▄ ▄▄▄▄ █ ▀ ▀
▀█▀█▀ ▄ █ █ ▐▌▄ █ ▀▄▌▐▌ ▐▌ ▄▀▄ █ ▐▌ ▀ ▄▀
▀ ▀ ▀▀▀ ▀ ▀ ▀▀ ▀ ▀ ▄▀ ▀ ▀
-''', ur"""
+''', r"""
<<<<<=====>>>>>?????@@@@@@[[[[\\\\]]]]^^^^____```{{{{||}}}}~~~~''´´´
▄▀ ▀▄ ▄▀▀▄ ▄▀▀▀▄ █▀▀ ▐▌ ▀▀█ ▄▀▄ ▀▄ ▄▀ █ ▀▄ ▄ █ ▄▀
▄▀ ▀▀▀▀ ▀▄ ▄▀ █ █▀█ █ █ █ ▄▀ █ ▀▄ ▐▐▌▌
@@ -258,7 +268,7 @@ class Thin6x6Font(Font):
│ │ │ │ │ │ │ │ │ │ │ │ │
└───┘ ┴ └─── └───┘ ┴ ───┘ └───┘ ┴ └───┘ ───┘
-""", ur'''
+""", r'''
!! """######$$$$$$%%%%%%&&&&&&((()))******++++++
│ ││ ┌ ┌ ┌─┼─┐ ┌┐ / ┌─┐ / \
│ ─┼─┼─ │ │ └┘ / │ │ │ │ \ / │
@@ -266,7 +276,7 @@ class Thin6x6Font(Font):
│ ─┼─┼─ │ │ / ┌┐ │ \, │ │ / \ │
. ┘ ┘ └─┼─┘ / └┘ └───\ \ /
-''', ur"""
+''', r"""
,,-----..//////::;;<<<<=====>>>>??????@@@@@@
/ ┌───┐ ┌───┐
/ . . / ──── \ │ │┌──┤
@@ -274,7 +284,7 @@ class Thin6x6Font(Font):
/ . , \ ──── / │ │└──┘
, . / \ / . └───┘
-""", ur"""
+""", r"""
[[\\\\\\]]^^^____``{{||}}~~~~~~
┌ \ ┐ /\ \ ┌ │ ┐
│ \ │ │ │ │ ┌─┐
@@ -363,7 +373,7 @@ class HalfBlock7x7Font(Font):
█▌ ▀ ▀█▌ ▐█▀ ▐█ ▀▀▀
▐█ ▐█ ▐█ █▌ ▀███▀
-""", ur"""
+""", r"""
[[[[\\\\\]]]]^^^^^^^_____```{{{{{|||}}}}}~~~~~~~´´´
▐██▌▐█ ▐██▌ ▐█▌ ▐█ █▌▐█ ▐█ █▌
▐█ █▌ █▌ ▐█ █▌ █▌ █▌ ▐█ ▐█ ▄▄ ▐█
@@ -433,18 +443,18 @@ add_font("Half Block 7x7",HalfBlock7x7Font)
if __name__ == "__main__":
l = get_all_fonts()
all_ascii = "".join([chr(x) for x in range(32, 127)])
- print "Available Fonts: (U) = UTF-8 required"
- print "----------------"
+ print("Available Fonts: (U) = UTF-8 required")
+ print("----------------")
for n,cls in l:
f = cls()
u = ""
if f.utf8_required:
u = "(U)"
- print ("%-20s %3s " % (n,u)),
+ print(("%-20s %3s " % (n,u)), end=' ')
c = f.characters()
if c == all_ascii:
- print "Full ASCII"
+ print("Full ASCII")
elif c.startswith(all_ascii):
- print "Full ASCII + " + c[len(all_ascii):]
+ print("Full ASCII + " + c[len(all_ascii):])
else:
- print "Characters: " + c
+ print("Characters: " + c)
diff --git a/urwid/graphics.py b/urwid/graphics.py
index cd03d33..25cd2dd 100755
--- a/urwid/graphics.py
+++ b/urwid/graphics.py
@@ -20,6 +20,9 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
+from urwid.compat import with_metaclass
from urwid.util import decompose_tagmarkup, get_encoding_mode
from urwid.canvas import CompositeCanvas, CanvasJoin, TextCanvas, \
CanvasCombine, SolidCanvas
@@ -96,7 +99,7 @@ class BigText(Widget):
class LineBox(WidgetDecoration, WidgetWrap):
- def __init__(self, original_widget, title="",
+ def __init__(self, original_widget, title="", title_align="center",
tlcorner=u'┌', tline=u'─', lline=u'│',
trcorner=u'┐', blcorner=u'└', rline=u'│',
bline=u'─', brcorner=u'┘'):
@@ -106,6 +109,9 @@ class LineBox(WidgetDecoration, WidgetWrap):
Use 'title' to set an initial title text with will be centered
on top of the box.
+ Use `title_align` to align the title to the 'left', 'right', or 'center'.
+ The default is 'center'.
+
You can also override the widgets used for the lines/corners:
tline: top line
bline: bottom line
@@ -116,37 +122,77 @@ class LineBox(WidgetDecoration, WidgetWrap):
blcorner: bottom left corner
brcorner: bottom right corner
+ If empty string is specified for one of the lines/corners, then no
+ character will be output there. This allows for seamless use of
+ adjoining LineBoxes.
"""
- tline, bline = Divider(tline), Divider(bline)
- lline, rline = SolidFill(lline), SolidFill(rline)
+ if tline:
+ tline = Divider(tline)
+ if bline:
+ bline = Divider(bline)
+ if lline:
+ lline = SolidFill(lline)
+ if rline:
+ rline = SolidFill(rline)
tlcorner, trcorner = Text(tlcorner), Text(trcorner)
blcorner, brcorner = Text(blcorner), Text(brcorner)
+ if not tline and title:
+ raise ValueError('Cannot have a title when tline is empty string')
+
self.title_widget = Text(self.format_title(title))
- self.tline_widget = Columns([
- tline,
- ('flow', self.title_widget),
- tline,
- ])
-
- top = Columns([
- ('fixed', 1, tlcorner),
- self.tline_widget,
- ('fixed', 1, trcorner)
- ])
-
- middle = Columns([
- ('fixed', 1, lline),
- original_widget,
- ('fixed', 1, rline),
- ], box_columns=[0, 2], focus_column=1)
-
- bottom = Columns([
- ('fixed', 1, blcorner), bline, ('fixed', 1, brcorner)
- ])
-
- pile = Pile([('flow', top), middle, ('flow', bottom)], focus_item=1)
+
+ if tline:
+ if title_align not in ('left', 'center', 'right'):
+ raise ValueError('title_align must be one of "left", "right", or "center"')
+ if title_align == 'left':
+ tline_widgets = [('flow', self.title_widget), tline]
+ else:
+ tline_widgets = [tline, ('flow', self.title_widget)]
+ if title_align == 'center':
+ tline_widgets.append(tline)
+ self.tline_widget = Columns(tline_widgets)
+ top = Columns([
+ ('fixed', 1, tlcorner),
+ self.tline_widget,
+ ('fixed', 1, trcorner)
+ ])
+
+ else:
+ self.tline_widget = None
+ top = None
+
+ middle_widgets = []
+ if lline:
+ middle_widgets.append(('fixed', 1, lline))
+ else:
+ # Note: We need to define a fixed first widget (even if it's 0 width) so that the other
+ # widgets have something to anchor onto
+ middle_widgets.append(('fixed', 0, SolidFill(u"")))
+ middle_widgets.append(original_widget)
+ focus_col = len(middle_widgets) - 1
+ if rline:
+ middle_widgets.append(('fixed', 1, rline))
+
+ middle = Columns(middle_widgets,
+ box_columns=[0, 2], focus_column=focus_col)
+
+ if bline:
+ bottom = Columns([
+ ('fixed', 1, blcorner), bline, ('fixed', 1, brcorner)
+ ])
+ else:
+ bottom = None
+
+ pile_widgets = []
+ if top:
+ pile_widgets.append(('flow', top))
+ pile_widgets.append(middle)
+ focus_pos = len(pile_widgets) - 1
+ if bottom:
+ pile_widgets.append(('flow', bottom))
+ pile = Pile(pile_widgets, focus_item=focus_pos)
WidgetDecoration.__init__(self, original_widget)
WidgetWrap.__init__(self, pile)
@@ -158,6 +204,8 @@ class LineBox(WidgetDecoration, WidgetWrap):
return ""
def set_title(self, text):
+ if not self.title_widget:
+ raise ValueError('Cannot set title when tline is unset')
self.title_widget.set_text(self.format_title(text))
self.tline_widget._invalidate()
@@ -192,9 +240,7 @@ def nocache_bargraph_get_data(self, get_data_fn):
class BarGraphError(Exception):
pass
-class BarGraph(Widget):
- __metaclass__ = BarGraphMeta
-
+class BarGraph(with_metaclass(BarGraphMeta, Widget)):
_sizing = frozenset([BOX])
ignore_focus = True
@@ -481,7 +527,8 @@ class BarGraph(Widget):
o = []
r = 0 # row remainder
- def seg_combine((bt1, w1), (bt2, w2)):
+ def seg_combine(a, b):
+ (bt1, w1), (bt2, w2) = a, b
if (bt1, w1) == (bt2, w2):
return (bt1, w1), None, None
wmin = min(w1, w2)
@@ -811,6 +858,28 @@ class ProgressBar(Widget):
foreground of satt corresponds to the normal part and the
background corresponds to the complete part. If satt
is ``None`` then no smoothing will be done.
+
+ >>> pb = ProgressBar('a', 'b')
+ >>> pb
+ <ProgressBar flow widget>
+ >>> print(pb.get_text())
+ 0 %
+ >>> pb.set_completion(34.42)
+ >>> print(pb.get_text())
+ 34 %
+ >>> class CustomProgressBar(ProgressBar):
+ ... def get_text(self):
+ ... return u'Foobar'
+ >>> cpb = CustomProgressBar('a', 'b')
+ >>> print(cpb.get_text())
+ Foobar
+ >>> for x in range(101):
+ ... cpb.set_completion(x)
+ ... s = cpb.render((10, ))
+ >>> cpb2 = CustomProgressBar('a', 'b', satt='c')
+ >>> for x in range(101):
+ ... cpb2.set_completion(x)
+ ... s = cpb2.render((10, ))
"""
self.normal = normal
self.complete = complete
@@ -840,6 +909,7 @@ class ProgressBar(Widget):
def get_text(self):
"""
Return the progress bar percentage text.
+ You can override this method to display custom text.
"""
percent = min(100, max(0, int(self.current * 100 / self.done)))
return str(percent) + " %"
@@ -853,7 +923,12 @@ class ProgressBar(Widget):
c = txt.render((maxcol,))
cf = float(self.current) * maxcol / self.done
- ccol = int(cf)
+ ccol_dirty = int(cf)
+ ccol = len(c._text[0][:ccol_dirty].decode(
+ 'utf-8', 'ignore'
+ ).encode(
+ 'utf-8'
+ ))
cs = 0
if self.satt is not None:
cs = int((cf - ccol) * 8)
@@ -909,3 +984,10 @@ class PythonLogo(Widget):
"""
fixed_size(size)
return self._canvas
+
+def _test():
+ import doctest
+ doctest.testmod()
+
+if __name__=='__main__':
+ _test()
diff --git a/urwid/html_fragment.py b/urwid/html_fragment.py
index 380d1d3..5df9273 100755
--- a/urwid/html_fragment.py
+++ b/urwid/html_fragment.py
@@ -19,6 +19,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
"""
HTML PRE-based UI implementation
"""
@@ -76,13 +78,15 @@ class HtmlGenerator(BaseScreen):
def reset_default_terminal_palette(self, *args):
pass
- def draw_screen(self, (cols, rows), r ):
+ def draw_screen(self, size, r ):
"""Create an html fragment from the render object.
Append it to HtmlGenerator.fragments list.
"""
# collect output in l
l = []
+ cols, rows = size
+
assert r.rows() == rows
if r.cursor is not None:
@@ -96,6 +100,8 @@ class HtmlGenerator(BaseScreen):
col = 0
for a, cs, run in row:
+ if not str is bytes:
+ run = run.decode()
run = run.translate(_trans_table)
if isinstance(a, AttrSpec):
aspec = a
@@ -132,7 +138,7 @@ class HtmlGenerator(BaseScreen):
def get_cols_rows(self):
"""Return the next screen size in HtmlGenerator.sizes."""
if not self.sizes:
- raise HtmlGeneratorSimulationError, "Ran out of screen sizes to return!"
+ raise HtmlGeneratorSimulationError("Ran out of screen sizes to return!")
return self.sizes.pop(0)
def get_input(self, raw_keys=False):
@@ -217,7 +223,7 @@ def screenshot_init( sizes, keys ):
assert type(row) == int
assert row>0 and col>0
except (AssertionError, ValueError):
- raise Exception, "sizes must be in the form [ (col1,row1), (col2,row2), ...]"
+ raise Exception("sizes must be in the form [ (col1,row1), (col2,row2), ...]")
try:
for l in keys:
@@ -225,11 +231,11 @@ def screenshot_init( sizes, keys ):
for k in l:
assert type(k) == str
except (AssertionError, ValueError):
- raise Exception, "keys must be in the form [ [keyA1, keyA2, ..], [keyB1, ..], ...]"
+ raise Exception("keys must be in the form [ [keyA1, keyA2, ..], [keyB1, ..], ...]")
- import curses_display
+ from . import curses_display
curses_display.Screen = HtmlGenerator
- import raw_display
+ from . import raw_display
raw_display.Screen = HtmlGenerator
HtmlGenerator.sizes = sizes
diff --git a/urwid/lcd_display.py b/urwid/lcd_display.py
index 4f62173..e189d9a 100644
--- a/urwid/lcd_display.py
+++ b/urwid/lcd_display.py
@@ -20,8 +20,9 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
-from display_common import BaseScreen
+from .display_common import BaseScreen
import time
@@ -39,7 +40,7 @@ class LCDScreen(BaseScreen):
def reset_default_terminal_palette(self, *args):
pass
- def draw_screen(self, (cols, rows), r ):
+ def draw_screen(self, size, r ):
pass
def clear(self):
diff --git a/urwid/listbox.py b/urwid/listbox.py
index 5370e23..802b1d6 100644
--- a/urwid/listbox.py
+++ b/urwid/listbox.py
@@ -19,6 +19,9 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
+from urwid.compat import xrange, with_metaclass
from urwid.util import is_mouse_press
from urwid.canvas import SolidCanvas, CanvasCombine
from urwid.widget import Widget, nocache_widget_render_instance, BOX, GIVEN
@@ -28,14 +31,12 @@ from urwid.signals import connect_signal
from urwid.monitored_list import MonitoredList, MonitoredFocusList
from urwid.container import WidgetContainerMixin
from urwid.command_map import (CURSOR_UP, CURSOR_DOWN,
- CURSOR_PAGE_UP, CURSOR_PAGE_DOWN)
+ CURSOR_PAGE_UP, CURSOR_PAGE_DOWN, CURSOR_MAX_LEFT, CURSOR_MAX_RIGHT)
class ListWalkerError(Exception):
pass
-class ListWalker(object):
- __metaclass__ = signals.MetaSignals
-
+class ListWalker(with_metaclass(signals.MetaSignals, object)):
signals = ["modified"]
def _modified(self):
@@ -141,7 +142,7 @@ class SimpleListWalker(MonitoredList, ListWalker):
this list walker to be updated.
"""
if not getattr(contents, '__getitem__', None):
- raise ListWalkerError, "SimpleListWalker expecting list like object, got: %r"%(contents,)
+ raise ListWalkerError("SimpleListWalker expecting list like object, got: %r"%(contents,))
MonitoredList.__init__(self, contents)
self.focus = 0
@@ -175,7 +176,7 @@ class SimpleListWalker(MonitoredList, ListWalker):
if position < 0 or position >= len(self):
raise ValueError
except (TypeError, ValueError):
- raise IndexError, "No widget at position %s" % (position,)
+ raise IndexError("No widget at position %s" % (position,))
self.focus = position
self._modified()
@@ -235,6 +236,7 @@ class SimpleFocusListWalker(ListWalker, MonitoredFocusList):
def set_focus(self, position):
"""Set focus position."""
self.focus = position
+ self._modified()
def next_position(self, position):
"""
@@ -278,13 +280,9 @@ class ListBox(Widget, WidgetContainerMixin):
widgets to be displayed inside the list box
:type body: ListWalker
"""
- if getattr(body, 'get_focus', None):
- self.body = body
- else:
- self.body = PollingListWalker(body)
-
+ self.body = body
try:
- connect_signal(self.body, "modified", self._invalidate)
+ connect_signal(self._body, "modified", self._invalidate)
except NameError:
# our list walker has no modified signal so we must not
# cache our canvases because we don't know when our
@@ -310,6 +308,22 @@ class ListBox(Widget, WidgetContainerMixin):
self.set_focus_valign_pending = None
+ def _get_body(self):
+ return self._body
+
+ def _set_body(self, body):
+ if getattr(body, 'get_focus', None):
+ self._body = body
+ else:
+ self._body = SimpleListWalker(body)
+ self._invalidate()
+
+ body = property(_get_body, _set_body, doc="""
+ a ListWalker subclass such as :class:`SimpleFocusListWalker` that contains
+ widgets to be displayed inside the list box
+ """)
+
+
def calculate_visible(self, size, focus=False ):
"""
Returns the widgets that would be displayed in
@@ -339,7 +353,7 @@ class ListBox(Widget, WidgetContainerMixin):
self._set_focus_complete( (maxcol, maxrow), focus )
# 1. start with the focus widget
- focus_widget, focus_pos = self.body.get_focus()
+ focus_widget, focus_pos = self._body.get_focus()
if focus_widget is None: #list box is empty?
return None,None,None
top_pos = focus_pos
@@ -377,7 +391,7 @@ class ListBox(Widget, WidgetContainerMixin):
fill_above = []
top_pos = pos
while fill_lines > 0:
- prev, pos = self.body.get_prev( pos )
+ prev, pos = self._body.get_prev( pos )
if prev is None: # run out of widgets above?
offset_rows -= fill_lines
break
@@ -399,7 +413,7 @@ class ListBox(Widget, WidgetContainerMixin):
fill_lines = maxrow - focus_rows - offset_rows + inset_rows
fill_below = []
while fill_lines > 0:
- next, pos = self.body.get_next( pos )
+ next, pos = self._body.get_next( pos )
if next is None: # run out of widgets below?
break
@@ -426,7 +440,7 @@ class ListBox(Widget, WidgetContainerMixin):
trim_top = 0
pos = top_pos
while fill_lines > 0:
- prev, pos = self.body.get_prev( pos )
+ prev, pos = self._body.get_prev( pos )
if prev is None:
break
@@ -468,17 +482,17 @@ class ListBox(Widget, WidgetContainerMixin):
for widget,w_pos,w_rows in fill_above:
canvas = widget.render((maxcol,))
if w_rows != canvas.rows():
- raise ListBoxError, "Widget %r at position %r within listbox calculated %d rows but rendered %d!"% (widget,w_pos,w_rows, canvas.rows())
+ raise ListBoxError("Widget %r at position %r within listbox calculated %d rows but rendered %d!"% (widget,w_pos,w_rows, canvas.rows()))
rows += w_rows
combinelist.append((canvas, w_pos, False))
focus_canvas = focus_widget.render((maxcol,), focus=focus)
if focus_canvas.rows() != focus_rows:
- raise ListBoxError, "Focus Widget %r at position %r within listbox calculated %d rows but rendered %d!"% (focus_widget,focus_pos,focus_rows, focus_canvas.rows())
+ raise ListBoxError("Focus Widget %r at position %r within listbox calculated %d rows but rendered %d!"% (focus_widget,focus_pos,focus_rows, focus_canvas.rows()))
c_cursor = focus_canvas.cursor
if cursor != c_cursor:
- raise ListBoxError, "Focus Widget %r at position %r within listbox calculated cursor coords %r but rendered cursor coords %r!" %(focus_widget,focus_pos,cursor,c_cursor)
+ raise ListBoxError("Focus Widget %r at position %r within listbox calculated cursor coords %r but rendered cursor coords %r!" %(focus_widget,focus_pos,cursor,c_cursor))
rows += focus_rows
combinelist.append((focus_canvas, focus_pos, True))
@@ -486,7 +500,7 @@ class ListBox(Widget, WidgetContainerMixin):
for widget,w_pos,w_rows in fill_below:
canvas = widget.render((maxcol,))
if w_rows != canvas.rows():
- raise ListBoxError, "Widget %r at position %r within listbox calculated %d rows but rendered %d!"% (widget,w_pos,w_rows, canvas.rows())
+ raise ListBoxError("Widget %r at position %r within listbox calculated %d rows but rendered %d!"% (widget,w_pos,w_rows, canvas.rows()))
rows += w_rows
combinelist.append((canvas, w_pos, False))
@@ -500,13 +514,13 @@ class ListBox(Widget, WidgetContainerMixin):
rows -= trim_bottom
if rows > maxrow:
- raise ListBoxError, "Listbox contents too long! Probably urwid's fault (please report): %r" % ((top,middle,bottom),)
+ raise ListBoxError("Listbox contents too long! Probably urwid's fault (please report): %r" % ((top,middle,bottom),))
if rows < maxrow:
bottom_pos = focus_pos
if fill_below: bottom_pos = fill_below[-1][1]
- if trim_bottom != 0 or self.body.get_next(bottom_pos) != (None,None):
- raise ListBoxError, "Listbox contents too short! Probably urwid's fault (please report): %r" % ((top,middle,bottom),)
+ if trim_bottom != 0 or self._body.get_next(bottom_pos) != (None,None):
+ raise ListBoxError("Listbox contents too short! Probably urwid's fault (please report): %r" % ((top,middle,bottom),))
final_canvas.pad_trim_top_bottom(0, maxrow - rows)
return final_canvas
@@ -551,7 +565,7 @@ class ListBox(Widget, WidgetContainerMixin):
"""
Set the focus position and try to keep the old focus in view.
- :param position: a position compatible with :meth:`self.body.set_focus`
+ :param position: a position compatible with :meth:`self._body.set_focus`
:param coming_from: set to 'above' or 'below' if you know that
old position is above or below the new position.
:type coming_from: str
@@ -559,12 +573,12 @@ class ListBox(Widget, WidgetContainerMixin):
if coming_from not in ('above', 'below', None):
raise ListBoxError("coming_from value invalid: %r" %
(coming_from,))
- focus_widget, focus_pos = self.body.get_focus()
+ focus_widget, focus_pos = self._body.get_focus()
if focus_widget is None:
raise IndexError("Can't set focus, ListBox is empty")
self.set_focus_pending = coming_from, focus_widget, focus_pos
- self.body.set_focus(position)
+ self._body.set_focus(position)
def get_focus(self):
"""
@@ -572,13 +586,13 @@ class ListBox(Widget, WidgetContainerMixin):
compatibility. You may also use the new standard container
properties :attr:`focus` and :attr:`focus_position` to read these values.
"""
- return self.body.get_focus()
+ return self._body.get_focus()
def _get_focus(self):
"""
Return the widget in focus according to our :obj:`list walker <ListWalker>`.
"""
- return self.body.get_focus()[0]
+ return self._body.get_focus()[0]
focus = property(_get_focus,
doc="the child widget in focus or None when ListBox is empty")
@@ -588,9 +602,9 @@ class ListBox(Widget, WidgetContainerMixin):
of value returned depends on the :obj:`list walker <ListWalker>`.
"""
- w, pos = self.body.get_focus()
+ w, pos = self._body.get_focus()
if w is None:
- raise IndexError, "No focus_position, ListBox is empty"
+ raise IndexError("No focus_position, ListBox is empty")
return pos
focus_position = property(_get_focus_position, set_focus, doc="""
the position of child widget in focus. The valid values for this
@@ -605,22 +619,22 @@ class ListBox(Widget, WidgetContainerMixin):
return ListBoxContents()
def _contents__getitem__(self, key):
# try list walker protocol v2 first
- getitem = getattr(self.body, '__getitem__', None)
+ getitem = getattr(self._body, '__getitem__', None)
if getitem:
try:
return (getitem(key), None)
except (IndexError, KeyError):
raise KeyError("ListBox.contents key not found: %r" % (key,))
# fall back to v1
- w, old_focus = self.body.get_focus()
+ w, old_focus = self._body.get_focus()
try:
try:
- self.body.set_focus(key)
- return self.body.get_focus()[0]
+ self._body.set_focus(key)
+ return self._body.get_focus()[0]
except (IndexError, KeyError):
raise KeyError("ListBox.contents key not found: %r" % (key,))
finally:
- self.body.set_focus(old_focus)
+ self._body.set_focus(old_focus)
contents = property(lambda self: self._contents, doc="""
An object that allows reading widgets from the ListBox's list
walker as a `(widget, options)` tuple. `None` is currently the only
@@ -652,7 +666,7 @@ class ListBox(Widget, WidgetContainerMixin):
self.set_focus_valign_pending = None
self.set_focus_pending = None
- focus_widget, focus_pos = self.body.get_focus()
+ focus_widget, focus_pos = self._body.get_focus()
if focus_widget is None:
return
@@ -687,7 +701,7 @@ class ListBox(Widget, WidgetContainerMixin):
new_row_offset = row_offset + focus_rows
for widget, pos, rows in fill_below:
if widget.selectable():
- self.body.set_focus(pos)
+ self._body.set_focus(pos)
self.shift_focus((maxcol, maxrow),
new_row_offset)
return
@@ -709,13 +723,13 @@ class ListBox(Widget, WidgetContainerMixin):
self.set_focus_pending = None
# new position
- new_focus_widget, position = self.body.get_focus()
+ new_focus_widget, position = self._body.get_focus()
if focus_pos == position:
# do nothing
return
# restore old focus temporarily
- self.body.set_focus(focus_pos)
+ self._body.set_focus(focus_pos)
middle,top,bottom=self.calculate_visible((maxcol,maxrow),focus)
focus_offset, focus_widget, focus_pos, focus_rows, cursor=middle
@@ -739,8 +753,8 @@ class ListBox(Widget, WidgetContainerMixin):
offset += rows
# failed to find widget among visible widgets
- self.body.set_focus( position )
- widget, position = self.body.get_focus()
+ self._body.set_focus( position )
+ widget, position = self._body.get_focus()
rows = widget.rows((maxcol,), focus)
if coming_from=='below':
@@ -772,14 +786,14 @@ class ListBox(Widget, WidgetContainerMixin):
if offset_inset >= 0:
if offset_inset >= maxrow:
- raise ListBoxError, "Invalid offset_inset: %r, only %r rows in list box"% (offset_inset, maxrow)
+ raise ListBoxError("Invalid offset_inset: %r, only %r rows in list box"% (offset_inset, maxrow))
self.offset_rows = offset_inset
self.inset_fraction = (0,1)
else:
- target, _ignore = self.body.get_focus()
+ target, _ignore = self._body.get_focus()
tgt_rows = target.rows( (maxcol,), True )
if offset_inset + tgt_rows <= 0:
- raise ListBoxError, "Invalid offset_inset: %r, only %r rows in target!" %(offset_inset, tgt_rows)
+ raise ListBoxError("Invalid offset_inset: %r, only %r rows in target!" %(offset_inset, tgt_rows))
self.offset_rows = 0
self.inset_fraction = (-offset_inset,tgt_rows)
self._invalidate()
@@ -789,7 +803,7 @@ class ListBox(Widget, WidgetContainerMixin):
# TODO: should this not be private?
(maxcol, maxrow) = size
- widget, old_pos = self.body.get_focus()
+ widget, old_pos = self._body.get_focus()
if widget is None: return
pref_col = None
@@ -813,7 +827,7 @@ class ListBox(Widget, WidgetContainerMixin):
See also :meth:`.set_focus`.
:param size: see :meth:`Widget.render` for details
- :param position: a position compatible with :meth:`self.body.set_focus`
+ :param position: a position compatible with :meth:`self._body.set_focus`
:param offset_inset: either the number of rows between the
top of the listbox and the start of the focus widget (+ve
value) or the number of lines of the focus widget hidden off
@@ -840,8 +854,8 @@ class ListBox(Widget, WidgetContainerMixin):
self.update_pref_col_from_focus((maxcol,maxrow))
self._invalidate()
- self.body.set_focus(position)
- target, _ignore = self.body.get_focus()
+ self._body.set_focus(position)
+ target, _ignore = self._body.get_focus()
tgt_rows = target.rows( (maxcol,), True)
if snap_rows is None:
snap_rows = maxrow - 1
@@ -876,7 +890,7 @@ class ListBox(Widget, WidgetContainerMixin):
self.inset_fraction = (0,1)
else:
if offset_inset + tgt_rows <= 0:
- raise ListBoxError, "Invalid offset_inset: %s, only %s rows in target!" %(offset_inset, tgt_rows)
+ raise ListBoxError("Invalid offset_inset: %s, only %s rows in target!" %(offset_inset, tgt_rows))
self.offset_rows = 0
self.inset_fraction = (-offset_inset,tgt_rows)
@@ -904,7 +918,7 @@ class ListBox(Widget, WidgetContainerMixin):
# start from preferred row and move back to closest edge
(pref_col, pref_row) = cursor_coords
if pref_row < 0 or pref_row >= tgt_rows:
- raise ListBoxError, "cursor_coords row outside valid range for target. pref_row:%r target_rows:%r"%(pref_row,tgt_rows)
+ raise ListBoxError("cursor_coords row outside valid range for target. pref_row:%r target_rows:%r"%(pref_row,tgt_rows))
if coming_from=='above':
attempt_rows = range( pref_row, -1, -1 )
@@ -920,17 +934,17 @@ class ListBox(Widget, WidgetContainerMixin):
def get_focus_offset_inset(self, size):
"""Return (offset rows, inset rows) for focus widget."""
(maxcol, maxrow) = size
- focus_widget, pos = self.body.get_focus()
+ focus_widget, pos = self._body.get_focus()
focus_rows = focus_widget.rows((maxcol,), True)
offset_rows = self.offset_rows
inset_rows = 0
if offset_rows == 0:
inum, iden = self.inset_fraction
if inum < 0 or iden < 0 or inum >= iden:
- raise ListBoxError, "Invalid inset_fraction: %r"%(self.inset_fraction,)
+ raise ListBoxError("Invalid inset_fraction: %r"%(self.inset_fraction,))
inset_rows = focus_rows * inum // iden
if inset_rows and inset_rows >= focus_rows:
- raise ListBoxError, "urwid inset_fraction error (please report)"
+ raise ListBoxError("urwid inset_fraction error (please report)")
return offset_rows, inset_rows
@@ -938,7 +952,7 @@ class ListBox(Widget, WidgetContainerMixin):
"""Shift the focus widget so that its cursor is visible."""
(maxcol, maxrow) = size
- focus_widget, pos = self.body.get_focus()
+ focus_widget, pos = self._body.get_focus()
if focus_widget is None:
return
if not focus_widget.selectable():
@@ -963,31 +977,29 @@ class ListBox(Widget, WidgetContainerMixin):
def keypress(self, size, key):
"""Move selection through the list elements scrolling when
- necessary. 'up' and 'down' are first passed to widget in focus
- in case that widget can handle them. 'page up' and 'page down'
- are always handled by the ListBox.
+ necessary. Keystrokes are first passed to widget in focus
+ in case that widget can handle them.
Keystrokes handled by this widget are:
'up' up one line (or widget)
'down' down one line (or widget)
- 'page up' move cursor up one listbox length
- 'page down' move cursor down one listbox length
+ 'page up' move cursor up one listbox length (or widget)
+ 'page down' move cursor down one listbox length (or widget)
"""
(maxcol, maxrow) = size
if self.set_focus_pending or self.set_focus_valign_pending:
self._set_focus_complete( (maxcol,maxrow), focus=True )
- focus_widget, pos = self.body.get_focus()
+ focus_widget, pos = self._body.get_focus()
if focus_widget is None: # empty listbox, can't do anything
return key
- if self._command_map[key] not in [CURSOR_PAGE_UP, CURSOR_PAGE_DOWN]:
- if focus_widget.selectable():
- key = focus_widget.keypress((maxcol,),key)
+ if focus_widget.selectable():
+ key = focus_widget.keypress((maxcol,),key)
if key is None:
self.make_cursor_visible((maxcol,maxrow))
- return
+ return None
def actual_key(unhandled):
if unhandled:
@@ -1006,8 +1018,23 @@ class ListBox(Widget, WidgetContainerMixin):
if self._command_map[key] == CURSOR_PAGE_DOWN:
return actual_key(self._keypress_page_down((maxcol, maxrow)))
+ if self._command_map[key] == CURSOR_MAX_LEFT:
+ return actual_key(self._keypress_max_left())
+
+ if self._command_map[key] == CURSOR_MAX_RIGHT:
+ return actual_key(self._keypress_max_right())
+
return key
+ def _keypress_max_left(self):
+ self.focus_position = next(iter(self.body.positions()))
+ self.set_focus_valign('top')
+ return True
+
+ def _keypress_max_right(self):
+ self.focus_position = next(iter(self.body.positions(reverse=True)))
+ self.set_focus_valign('bottom')
+ return True
def _keypress_up(self, size):
(maxcol, maxrow) = size
@@ -1038,7 +1065,7 @@ class ListBox(Widget, WidgetContainerMixin):
while row_offset > 0:
# need to scroll in another candidate widget
- widget, pos = self.body.get_prev(pos)
+ widget, pos = self._body.get_prev(pos)
if widget is None:
# cannot scroll any further
return True # keypress not handled
@@ -1068,7 +1095,7 @@ class ListBox(Widget, WidgetContainerMixin):
# choose another focus
if widget is None:
# try harder to get prev widget
- widget, pos = self.body.get_prev(pos)
+ widget, pos = self._body.get_prev(pos)
if widget is None:
return # can't do anything
rows = widget.rows((maxcol,), True)
@@ -1116,7 +1143,7 @@ class ListBox(Widget, WidgetContainerMixin):
while row_offset < maxrow:
# need to scroll in another candidate widget
- widget, pos = self.body.get_next(pos)
+ widget, pos = self._body.get_next(pos)
if widget is None:
# cannot scroll any further
return True # keypress not handled
@@ -1150,7 +1177,7 @@ class ListBox(Widget, WidgetContainerMixin):
# choose another focus
if widget is None:
# try harder to get next widget
- widget, pos = self.body.get_next(pos)
+ widget, pos = self._body.get_next(pos)
if widget is None:
return # can't do anything
else:
@@ -1220,7 +1247,7 @@ class ListBox(Widget, WidgetContainerMixin):
# add newly visible ones, including within snap_rows
snap_region_start = len(t)
while row_offset > -snap_rows:
- widget, pos = self.body.get_prev(pos)
+ widget, pos = self._body.get_prev(pos)
if widget is None: break
rows = widget.rows((maxcol,))
row_offset -= rows
@@ -1246,8 +1273,8 @@ class ListBox(Widget, WidgetContainerMixin):
# choose the topmost selectable and (newly) visible widget
# search within snap_rows then visible region
- search_order = ( range( snap_region_start, len(t))
- + range( snap_region_start-1, -1, -1 ) )
+ search_order = (list(xrange(snap_region_start, len(t)))
+ + list(xrange(snap_region_start-1, -1, -1)))
#assert 0, repr((t, search_order))
bad_choices = []
cut_off_selectable_chosen = 0
@@ -1274,7 +1301,7 @@ class ListBox(Widget, WidgetContainerMixin):
(self.pref_col, pref_row), snap_rows )
# if we're as far up as we can scroll, take this one
- if (fill_above and self.body.get_prev(fill_above[-1][1])
+ if (fill_above and self._body.get_prev(fill_above[-1][1])
== (None,None) ):
pass #return
@@ -1306,7 +1333,7 @@ class ListBox(Widget, WidgetContainerMixin):
if fill_above and focus_widget.selectable():
# if we're at the top and have a selectable, return
- if self.body.get_prev(fill_above[-1][1]) == (None,None):
+ if self._body.get_prev(fill_above[-1][1]) == (None,None):
pass #return
# if still none found choose the topmost widget
@@ -1343,7 +1370,7 @@ class ListBox(Widget, WidgetContainerMixin):
if not t:
return
_ign1, _ign2, pos, _ign3 = t[-1]
- widget, pos = self.body.get_prev(pos)
+ widget, pos = self._body.get_prev(pos)
if widget is None:
# no dice, we're stuck here
return
@@ -1405,7 +1432,7 @@ class ListBox(Widget, WidgetContainerMixin):
# add newly visible ones, including within snap_rows
snap_region_start = len(t)
while row_offset < maxrow+snap_rows:
- widget, pos = self.body.get_next(pos)
+ widget, pos = self._body.get_next(pos)
if widget is None: break
rows = widget.rows((maxcol,))
t.append( (row_offset, widget, pos, rows) )
@@ -1431,8 +1458,8 @@ class ListBox(Widget, WidgetContainerMixin):
# choose the bottommost selectable and (newly) visible widget
# search within snap_rows then visible region
- search_order = ( range( snap_region_start, len(t))
- + range( snap_region_start-1, -1, -1 ) )
+ search_order = (list(xrange(snap_region_start, len(t)))
+ + list(xrange(snap_region_start-1, -1, -1)))
#assert 0, repr((t, search_order))
bad_choices = []
cut_off_selectable_chosen = 0
@@ -1519,7 +1546,7 @@ class ListBox(Widget, WidgetContainerMixin):
if not t:
return
_ign1, _ign2, pos, _ign3 = t[-1]
- widget, pos = self.body.get_next(pos)
+ widget, pos = self._body.get_next(pos)
if widget is None:
# no dice, we're stuck here
return
@@ -1594,14 +1621,14 @@ class ListBox(Widget, WidgetContainerMixin):
row_offset += rows
if row_offset < maxrow:
l.append('bottom')
- elif self.body.get_next(pos) == (None,None):
+ elif self._body.get_next(pos) == (None,None):
l.append('bottom')
if trim_top == 0:
row_offset, w, pos, rows, c = middle
for w, pos, rows in above:
row_offset -= rows
- if self.body.get_prev(pos) == (None,None):
+ if self._body.get_prev(pos) == (None,None):
l.insert(0, 'top')
return l
@@ -1610,28 +1637,28 @@ class ListBox(Widget, WidgetContainerMixin):
"""
Return an iterator over the positions in this ListBox.
- If self.body does not implement positions() then iterate
+ If self._body does not implement positions() then iterate
from the focus widget down to the bottom, then from above
the focus up to the top. This is the best we can do with
a minimal list walker implementation.
"""
- positions_fn = getattr(self.body, 'positions', None)
+ positions_fn = getattr(self._body, 'positions', None)
if positions_fn:
for pos in positions_fn():
yield pos
return
- focus_widget, focus_pos = self.body.get_focus()
+ focus_widget, focus_pos = self._body.get_focus()
if focus_widget is None:
return
pos = focus_pos
while True:
yield pos
- w, pos = self.body.get_next(pos)
+ w, pos = self._body.get_next(pos)
if not w: break
pos = focus_pos
while True:
- w, pos = self.body.get_prev(pos)
+ w, pos = self._body.get_prev(pos)
if not w: break
yield pos
@@ -1645,24 +1672,24 @@ class ListBox(Widget, WidgetContainerMixin):
reverse of what `__iter__()` produces, but this is the best we can
do with a minimal list walker implementation.
"""
- positions_fn = getattr(self.body, 'positions', None)
+ positions_fn = getattr(self._body, 'positions', None)
if positions_fn:
for pos in positions_fn(reverse=True):
yield pos
return
- focus_widget, focus_pos = self.body.get_focus()
+ focus_widget, focus_pos = self._body.get_focus()
if focus_widget is None:
return
pos = focus_pos
while True:
- w, pos = self.body.get_prev(pos)
+ w, pos = self._body.get_prev(pos)
if not w: break
yield pos
pos = focus_pos
while True:
yield pos
- w, pos = self.body.get_next(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 28577b2..10ea5e1 100755
--- a/urwid/main_loop.py
+++ b/urwid/main_loop.py
@@ -21,12 +21,15 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
import time
import heapq
import select
import os
+import signal
from functools import wraps
+from itertools import count
from weakref import WeakKeyDictionary
try:
@@ -35,7 +38,7 @@ except ImportError:
pass # windows
from urwid.util import StoppingContext, is_mouse_event
-from urwid.compat import PYTHON3
+from urwid.compat import PYTHON3, reraise
from urwid.command_map import command_map, REDRAW_SCREEN
from urwid.wimp import PopUpTarget
from urwid import signals
@@ -127,6 +130,11 @@ class MainLoop(object):
event_loop = SelectEventLoop()
self.event_loop = event_loop
+ if hasattr(self.screen, 'signal_handler_setter'):
+ # Tell the screen what function it must use to set
+ # signal handlers
+ self.screen.signal_handler_setter = self.event_loop.set_signal_handler
+
self._watch_pipes = {}
def _set_widget(self, widget):
@@ -442,7 +450,7 @@ class MainLoop(object):
sec = next_alarm[0] - time.time()
if sec > 0:
break
- tm, callback = next_alarm
+ tm, tie_break, callback = next_alarm
callback()
if self.event_loop._alarms:
@@ -579,7 +587,103 @@ class MainLoop(object):
self.screen.draw_screen(self.screen_size, canvas)
-class SelectEventLoop(object):
+class EventLoop(object):
+ """
+ Abstract class representing an event loop to be used by :class:`MainLoop`.
+ """
+
+ def alarm(self, seconds, callback):
+ """
+ Call callback() a given time from now. No parameters are
+ passed to callback.
+
+ This method has no default implementation.
+
+ Returns a handle that may be passed to remove_alarm()
+
+ seconds -- floating point time to wait before calling callback
+ callback -- function to call from event loop
+ """
+ raise NotImplementedError()
+
+ def enter_idle(self, callback):
+ """
+ Add a callback for entering idle.
+
+ This method has no default implementation.
+
+ Returns a handle that may be passed to remove_idle()
+ """
+ raise NotImplementedError()
+
+ def remove_alarm(self, handle):
+ """
+ Remove an alarm.
+
+ This method has no default implementation.
+
+ Returns True if the alarm exists, False otherwise
+ """
+ raise NotImplementedError()
+
+ def remove_enter_idle(self, handle):
+ """
+ Remove an idle callback.
+
+ This method has no default implementation.
+
+ Returns True if the handle was removed.
+ """
+ raise NotImplementedError()
+
+ def remove_watch_file(self, handle):
+ """
+ Remove an input file.
+
+ This method has no default implementation.
+
+ Returns True if the input file exists, False otherwise
+ """
+ raise NotImplementedError()
+
+ def run(self):
+ """
+ Start the event loop. Exit the loop when any callback raises
+ an exception. If ExitMainLoop is raised, exit cleanly.
+
+ This method has no default implementation.
+ """
+ raise NotImplementedError()
+
+ def watch_file(self, fd, callback):
+ """
+ Call callback() when fd has some data to read. No parameters
+ are passed to callback.
+
+ This method has no default implementation.
+
+ Returns a handle that may be passed to remove_watch_file()
+
+ fd -- file descriptor to watch for input
+ callback -- function to call when input is available
+ """
+ raise NotImplementedError()
+
+ def set_signal_handler(self, signum, handler):
+ """
+ Sets the signal handler for signal signum.
+
+ The default implementation of :meth:`set_signal_handler`
+ is simply a proxy function that calls :func:`signal.signal()`
+ and returns the resulting value.
+
+ signum -- signal number
+ handler -- function (taking signum as its single argument),
+ or `signal.SIG_IGN`, or `signal.SIG_DFL`
+ """
+ return signal.signal(signum, handler)
+
+class SelectEventLoop(EventLoop):
"""
Event loop based on :func:`select.select`
"""
@@ -589,6 +693,7 @@ class SelectEventLoop(object):
self._watch_files = {}
self._idle_handle = 0
self._idle_callbacks = {}
+ self._tie_break = count()
def alarm(self, seconds, callback):
"""
@@ -601,8 +706,9 @@ class SelectEventLoop(object):
callback -- function to call from event loop
"""
tm = time.time() + seconds
- heapq.heappush(self._alarms, (tm, callback))
- return (tm, callback)
+ handle = (tm, next(self._tie_break), callback)
+ heapq.heappush(self._alarms, handle)
+ return handle
def remove_alarm(self, handle):
"""
@@ -691,7 +797,7 @@ class SelectEventLoop(object):
"""
A single iteration of the event loop
"""
- fds = self._watch_files.keys()
+ fds = list(self._watch_files.keys())
if self._alarms or self._did_something:
if self._alarms:
tm = self._alarms[0][0]
@@ -711,7 +817,7 @@ class SelectEventLoop(object):
self._did_something = False
elif tm is not None:
# must have been a timeout
- tm, alarm_callback = self._alarms.pop(0)
+ tm, tie_break, alarm_callback = heapq.heappop(self._alarms)
alarm_callback()
self._did_something = True
@@ -720,7 +826,7 @@ class SelectEventLoop(object):
self._did_something = True
-class GLibEventLoop(object):
+class GLibEventLoop(EventLoop):
"""
Event loop based on GLib.MainLoop
"""
@@ -736,6 +842,7 @@ class GLibEventLoop(object):
self._loop = GLib.MainLoop()
self._exc_info = None
self._enable_glib_idle()
+ self._signal_handlers = {}
def alarm(self, seconds, callback):
"""
@@ -756,6 +863,53 @@ class GLibEventLoop(object):
self._alarms.append(fd)
return (fd, callback)
+ def set_signal_handler(self, signum, handler):
+ """
+ Sets the signal handler for signal signum.
+
+ .. WARNING::
+ Because this method uses the `GLib`-specific `unix_signal_add`
+ function, its behaviour is different than `signal.signal().`
+
+ If `signum` is not `SIGHUP`, `SIGINT`, `SIGTERM`, `SIGUSR1`,
+ `SIGUSR2` or `SIGWINCH`, this method performs no actions and
+ immediately returns None.
+
+ Returns None in all cases (unlike :func:`signal.signal()`).
+ ..
+
+ signum -- signal number
+ handler -- function (taking signum as its single argument),
+ or `signal.SIG_IGN`, or `signal.SIG_DFL`
+ """
+ glib_signals = [
+ signal.SIGHUP,
+ signal.SIGINT,
+ signal.SIGTERM,
+ signal.SIGUSR1,
+ signal.SIGUSR2,
+ signal.SIGWINCH
+ ]
+
+ if signum not in glib_signals:
+ # The GLib event loop supports only the signals listed above
+ return
+
+ if signum in self._signal_handlers:
+ self.GLib.source_remove(self._signal_handlers.pop(signum))
+
+ if handler == signal.SIG_IGN:
+ handler = lambda x: None
+ elif handler == signal.SIG_DFL:
+ return
+
+ def final_handler(signal_number):
+ handler(signal_number)
+ return self.GLib.SOURCE_CONTINUE
+
+ source = self.GLib.unix_signal_add(self.GLib.PRIORITY_DEFAULT, signum, final_handler, signum)
+ self._signal_handlers[signum] = source
+
def remove_alarm(self, handle):
"""
Remove an alarm.
@@ -848,7 +1002,7 @@ class GLibEventLoop(object):
# An exception caused us to exit, raise it now
exc_info = self._exc_info
self._exc_info = None
- raise exc_info[0], exc_info[1], exc_info[2]
+ reraise(*exc_info)
def handle_exit(self,f):
"""
@@ -873,7 +1027,7 @@ class GLibEventLoop(object):
return wrapper
-class TornadoEventLoop(object):
+class TornadoEventLoop(EventLoop):
""" This is an Urwid-specific event loop to plug into its MainLoop.
It acts as an adaptor for Tornado's IOLoop which does all
heavy lifting except idle-callbacks.
@@ -1033,7 +1187,7 @@ class TwistedInputDescriptor(FileDescriptor):
return self.cb()
-class TwistedEventLoop(object):
+class TwistedEventLoop(EventLoop):
"""
Event loop based on Twisted_
"""
@@ -1183,7 +1337,7 @@ class TwistedEventLoop(object):
# An exception caused us to exit, raise it now
exc_info = self._exc_info
self._exc_info = None
- raise exc_info[0], exc_info[1], exc_info[2]
+ reraise(*exc_info)
def handle_exit(self, f, enable_idle=True):
"""
@@ -1203,7 +1357,7 @@ class TwistedEventLoop(object):
self.reactor.stop()
except:
import sys
- print sys.exc_info()
+ print(sys.exc_info())
self._exc_info = sys.exc_info()
if self.manage_reactor:
self.reactor.crash()
@@ -1213,7 +1367,7 @@ class TwistedEventLoop(object):
return wrapper
-class AsyncioEventLoop(object):
+class AsyncioEventLoop(EventLoop):
"""
Event loop based on the standard library ``asyncio`` module.
@@ -1325,8 +1479,9 @@ class AsyncioEventLoop(object):
self._loop.set_exception_handler(self._exception_handler)
self._loop.run_forever()
if self._exc_info:
- raise self._exc_info[0], self._exc_info[1], self._exc_info[2]
+ exc_info = self._exc_info
self._exc_info = None
+ reraise(*exc_info)
def _refl(name, rval=None, exit=False):
@@ -1354,7 +1509,7 @@ def _refl(name, rval=None, exit=False):
if args and argd:
args = args + ", "
args = args + ", ".join([k+"="+repr(v) for k,v in argd.items()])
- print self._name+"("+args+")"
+ print(self._name+"("+args+")")
if exit:
raise ExitMainLoop()
return self._rval
diff --git a/urwid/monitored_list.py b/urwid/monitored_list.py
index dc67c84..c159064 100755
--- a/urwid/monitored_list.py
+++ b/urwid/monitored_list.py
@@ -19,7 +19,9 @@
#
# Urwid web site: http://excess.org/urwid/
-from urwid.compat import PYTHON3
+from __future__ import division, print_function
+
+from urwid.compat import PYTHON3, xrange
def _call_modified(fn):
@@ -238,7 +240,7 @@ class MonitoredFocusList(MonitoredList):
"""
num_new_items = len(new_items)
start, stop, step = indices = slc.indices(len(self))
- num_removed = len(range(*indices))
+ num_removed = len(list(xrange(*indices)))
focus = self._validate_contents_modified(indices, new_items)
if focus is not None:
@@ -255,11 +257,11 @@ class MonitoredFocusList(MonitoredList):
else:
if not num_new_items:
# extended slice being removed
- if focus in range(start, stop, step):
+ if focus in xrange(start, stop, step):
focus += 1
# adjust for removed items
- focus -= len(range(start, min(focus, stop), step))
+ focus -= len(list(xrange(start, min(focus, stop), step)))
return min(focus, len(self) + num_new_items - num_removed -1)
@@ -303,7 +305,7 @@ class MonitoredFocusList(MonitoredList):
def __setitem__(self, i, y):
"""
>>> def modified(indices, new_items):
- ... print "range%r <- %r" % (indices, new_items)
+ ... print("range%r <- %r" % (indices, new_items))
>>> ml = MonitoredFocusList([0,1,2,3], focus=2)
>>> ml.set_validate_contents_modified(modified)
>>> ml[0] = 9
@@ -347,7 +349,7 @@ class MonitoredFocusList(MonitoredList):
def __imul__(self, n):
"""
>>> def modified(indices, new_items):
- ... print "range%r <- %r" % (indices, list(new_items))
+ ... print("range%r <- %r" % (indices, list(new_items)))
>>> ml = MonitoredFocusList([0,1,2], focus=2)
>>> ml.set_validate_contents_modified(modified)
>>> ml *= 3
@@ -356,7 +358,7 @@ class MonitoredFocusList(MonitoredList):
MonitoredFocusList([0, 1, 2, 0, 1, 2, 0, 1, 2], focus=2)
>>> ml *= 0
range(0, 9, 1) <- []
- >>> print ml.focus
+ >>> print(ml.focus)
None
"""
if n > 0:
@@ -371,7 +373,7 @@ class MonitoredFocusList(MonitoredList):
def append(self, item):
"""
>>> def modified(indices, new_items):
- ... print "range%r <- %r" % (indices, new_items)
+ ... print("range%r <- %r" % (indices, new_items))
>>> ml = MonitoredFocusList([0,1,2], focus=2)
>>> ml.set_validate_contents_modified(modified)
>>> ml.append(6)
@@ -386,7 +388,7 @@ class MonitoredFocusList(MonitoredList):
def extend(self, items):
"""
>>> def modified(indices, new_items):
- ... print "range%r <- %r" % (indices, list(new_items))
+ ... print("range%r <- %r" % (indices, list(new_items)))
>>> ml = MonitoredFocusList([0,1,2], focus=2)
>>> ml.set_validate_contents_modified(modified)
>>> ml.extend((6,7,8))
diff --git a/urwid/old_str_util.py b/urwid/old_str_util.py
index 83190f5..2c6d1e0 100755
--- a/urwid/old_str_util.py
+++ b/urwid/old_str_util.py
@@ -19,11 +19,12 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Urwid web site: http://excess.org/urwid/
-from __future__ import print_function
+
+from __future__ import division, print_function
import re
-from urwid.compat import bytes, B, ord2
+from urwid.compat import bytes, B, ord2, text_type
SAFE_ASCII_RE = re.compile(u"^[ -~]*$")
SAFE_ASCII_BYTES_RE = re.compile(B("^[ -~]*$"))
@@ -241,7 +242,7 @@ def is_wide_char(text, offs):
text may be unicode or a byte string in the target _byte_encoding
"""
- if isinstance(text, unicode):
+ if isinstance(text, text_type):
o = ord(text[offs])
return get_width(o) == 2
assert isinstance(text, bytes)
@@ -257,7 +258,7 @@ def move_prev_char(text, start_offs, end_offs):
Return the position of the character before end_offs.
"""
assert start_offs < end_offs
- if isinstance(text, unicode):
+ if isinstance(text, text_type):
return end_offs-1
assert isinstance(text, bytes)
if _byte_encoding == "utf8":
@@ -275,7 +276,7 @@ def move_next_char(text, start_offs, end_offs):
Return the position of the character after start_offs.
"""
assert start_offs < end_offs
- if isinstance(text, unicode):
+ if isinstance(text, text_type):
return start_offs+1
assert isinstance(text, bytes)
if _byte_encoding == "utf8":
diff --git a/urwid/raw_display.py b/urwid/raw_display.py
index b304709..e5275bd 100644
--- a/urwid/raw_display.py
+++ b/urwid/raw_display.py
@@ -19,6 +19,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
"""
Direct terminal UI implementation
"""
@@ -59,7 +61,6 @@ class Screen(BaseScreen, RealTerminal):
self._on_update_palette_entry)
self.colors = 16 # FIXME: detect this
self.has_underline = True # FIXME: detect this
- self.register_palette_entry( None, 'default','default')
self._keyqueue = []
self.prev_input_resize = 0
self.set_input_timeouts()
@@ -74,11 +75,13 @@ class Screen(BaseScreen, RealTerminal):
self._setup_G1_done = False
self._rows_used = None
self._cy = 0
- term = os.environ.get('TERM', '')
- self.fg_bright_is_bold = not term.startswith("xterm")
- self.bg_bright_is_blink = (term == "linux")
- self.back_color_erase = not term.startswith("screen")
+ self.term = os.environ.get('TERM', '')
+ self.fg_bright_is_bold = not self.term.startswith("xterm")
+ self.bg_bright_is_blink = (self.term == "linux")
+ self.back_color_erase = not self.term.startswith("screen")
+ self.register_palette_entry( None, 'default','default')
self._next_timeout = None
+ self.signal_handler_setter = signal.signal
# Our connections to the world
self._term_output_file = output
@@ -119,13 +122,21 @@ class Screen(BaseScreen, RealTerminal):
self.complete_wait = complete_wait
self.resize_wait = resize_wait
- def _sigwinch_handler(self, signum, frame):
+ def _sigwinch_handler(self, signum, frame=None):
+ """
+ frame -- will always be None when the GLib event loop is being used.
+ """
+
if not self._resized:
os.write(self._resize_pipe_wr, B('R'))
self._resized = True
self.screen_buf = None
- def _sigcont_handler(self, signum, frame):
+ def _sigcont_handler(self, signum, frame=None):
+ """
+ frame -- will always be None when the GLib event loop is being used.
+ """
+
self.stop()
self.start()
self._sigwinch_handler(None, None)
@@ -138,8 +149,8 @@ class Screen(BaseScreen, RealTerminal):
Override this function to call from main thread in threaded
applications.
"""
- signal.signal(signal.SIGWINCH, self._sigwinch_handler)
- signal.signal(signal.SIGCONT, self._sigcont_handler)
+ self.signal_handler_setter(signal.SIGWINCH, self._sigwinch_handler)
+ self.signal_handler_setter(signal.SIGCONT, self._sigcont_handler)
def signal_restore(self):
"""
@@ -149,8 +160,8 @@ class Screen(BaseScreen, RealTerminal):
Override this function to call from main thread in threaded
applications.
"""
- signal.signal(signal.SIGCONT, signal.SIG_DFL)
- signal.signal(signal.SIGWINCH, signal.SIG_DFL)
+ self.signal_handler_setter(signal.SIGCONT, signal.SIG_DFL)
+ self.signal_handler_setter(signal.SIGWINCH, signal.SIG_DFL)
def set_mouse_tracking(self, enable=True):
"""
@@ -392,9 +403,7 @@ class Screen(BaseScreen, RealTerminal):
wrapper = lambda: self.parse_input(
event_loop, callback, self.get_available_raw_input())
fds = self.get_input_descriptors()
- handles = []
- for fd in fds:
- event_loop.watch_file(fd, wrapper)
+ handles = [event_loop.watch_file(fd, wrapper) for fd in fds]
self._current_event_loop_handles = handles
_input_timeout = None
@@ -637,7 +646,7 @@ class Screen(BaseScreen, RealTerminal):
def get_cols_rows(self):
"""Return the terminal dimensions (num columns, num rows)."""
- y, x = 80, 24
+ y, x = 24, 80
try:
buf = fcntl.ioctl(self._term_output_file.fileno(),
termios.TIOCGWINSZ, ' '*4)
@@ -665,8 +674,11 @@ class Screen(BaseScreen, RealTerminal):
self._setup_G1_done = True
- def draw_screen(self, (maxcol, maxrow), r ):
+ def draw_screen(self, maxres, r ):
"""Paint screen with rendered canvas."""
+
+ (maxcol, maxrow) = maxres
+
assert self._started
assert maxrow == r.rows()
@@ -738,9 +750,9 @@ class Screen(BaseScreen, RealTerminal):
return self._attrspec_to_escape(
AttrSpec('default','default'))
- def using_standout(a):
+ def using_standout_or_underline(a):
a = self._pal_attrspec.get(a, a)
- return isinstance(a, AttrSpec) and a.standout
+ return isinstance(a, AttrSpec) and (a.standout or a.underline)
ins = None
o.append(set_cursor_home())
@@ -773,7 +785,7 @@ class Screen(BaseScreen, RealTerminal):
if row:
a, cs, run = row[-1]
if (run[-1:] == B(' ') and self.back_color_erase
- and not using_standout(a)):
+ and not using_standout_or_underline(a)):
whitespace_at_end = True
row = row[:-1] + [(a, cs, run.rstrip(B(' ')))]
elif y == maxrow-1 and maxcol > 1:
@@ -834,7 +846,7 @@ class Screen(BaseScreen, RealTerminal):
try:
for l in o:
if isinstance(l, bytes) and PYTHON3:
- l = l.decode('utf-8')
+ l = l.decode('utf-8', 'replace')
self.write(l)
self.flush()
except IOError as e:
@@ -914,6 +926,11 @@ class Screen(BaseScreen, RealTerminal):
>>> a2e(s.AttrSpec('#fea,underline', '#d0d'))
'\\x1b[0;38;5;229;4;48;5;164m'
"""
+ if self.term == 'fbterm':
+ fg = escape.ESC + '[1;%d}' % (a.foreground_number,)
+ bg = escape.ESC + '[2;%d}' % (a.background_number,)
+ return fg + bg
+
if a.foreground_high:
fg = "38;5;%d" % a.foreground_number
elif a.foreground_basic:
@@ -926,8 +943,9 @@ class Screen(BaseScreen, RealTerminal):
fg = "%d" % (a.foreground_number + 30)
else:
fg = "39"
- st = ("1;" * a.bold + "4;" * a.underline +
- "5;" * a.blink + "7;" * a.standout)
+ st = ("1;" * a.bold + "3;" * a.italics +
+ "4;" * a.underline + "5;" * a.blink +
+ "7;" * a.standout + "9;" * a.strikethrough)
if a.background_high:
bg = "48;5;%d" % a.background_number
elif a.background_basic:
@@ -1011,9 +1029,14 @@ class Screen(BaseScreen, RealTerminal):
0 <= red, green, blue < 256
"""
- modify = ["%d;rgb:%02x/%02x/%02x" % (index, red, green, blue)
- for index, red, green, blue in entries]
- self.write("\x1b]4;"+";".join(modify)+"\x1b\\")
+ if self.term == 'fbterm':
+ modify = ["%d;%d;%d;%d" % (index, red, green, blue)
+ for index, red, green, blue in entries]
+ self.write("\x1b[3;"+";".join(modify)+"}")
+ else:
+ modify = ["%d;rgb:%02x/%02x/%02x" % (index, red, green, blue)
+ for index, red, green, blue in entries]
+ self.write("\x1b]4;"+";".join(modify)+"\x1b\\")
self.flush()
diff --git a/urwid/signals.py b/urwid/signals.py
index b716939..0269dfd 100644
--- a/urwid/signals.py
+++ b/urwid/signals.py
@@ -19,6 +19,7 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
import itertools
import weakref
@@ -26,14 +27,14 @@ import weakref
class MetaSignals(type):
"""
- register the list of signals in the class varable signals,
+ register the list of signals in the class variable signals,
including signals in superclasses.
"""
def __init__(cls, name, bases, d):
signals = d.get("signals", [])
for superclass in cls.__bases__:
signals.extend(getattr(superclass, 'signals', []))
- signals = dict([(x,None) for x in signals]).keys()
+ signals = list(dict([(x,None) for x in signals]).keys())
d["signals"] = signals
register_signal(cls, signals)
super(MetaSignals, cls).__init__(name, bases, d)
@@ -67,7 +68,7 @@ class Signals(object):
:type signals: signal names
This function must be called for a class before connecting any
- signal callbacks or emiting any signals from that class' objects
+ signal callbacks or emitting any signals from that class' objects
"""
self._supported[sig_cls] = signals
diff --git a/urwid/split_repr.py b/urwid/split_repr.py
index fb108b5..3d7cbeb 100755
--- a/urwid/split_repr.py
+++ b/urwid/split_repr.py
@@ -19,6 +19,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
from inspect import getargspec
from urwid.compat import PYTHON3, bytes
@@ -129,12 +131,12 @@ def remove_defaults(d, fn):
del args[-1]
# create a dictionary of args with default values
- ddict = dict(zip(args[len(args) - len(defaults):], defaults))
+ ddict = dict(list(zip(args[len(args) - len(defaults):], defaults)))
- for k, v in d.items():
+ for k in list(d.keys()):
if k in ddict:
# remove values that match their defaults
- if ddict[k] == v:
+ if ddict[k] == d[k]:
del d[k]
return d
diff --git a/urwid/tests/test_doctests.py b/urwid/tests/test_doctests.py
index 1720a48..611baf3 100644
--- a/urwid/tests/test_doctests.py
+++ b/urwid/tests/test_doctests.py
@@ -15,6 +15,7 @@ def load_tests(loader, tests, ignore):
'urwid.split_repr', # override function with same name
urwid.util,
urwid.signals,
+ urwid.graphics,
]
for m in module_doctests:
tests.addTests(doctest.DocTestSuite(m,
diff --git a/urwid/tests/test_event_loops.py b/urwid/tests/test_event_loops.py
index c85bbed..fb12819 100644
--- a/urwid/tests/test_event_loops.py
+++ b/urwid/tests/test_event_loops.py
@@ -30,9 +30,14 @@ class EventLoopTestMixin(object):
def test_remove_watch_file(self):
evl = self.evl
- handle = evl.watch_file(5, lambda: None)
- self.assertTrue(evl.remove_watch_file(handle))
- self.assertFalse(evl.remove_watch_file(handle))
+ 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)
_expected_idle_handle = 1
@@ -83,6 +88,11 @@ else:
def setUp(self):
self.evl = urwid.GLibEventLoop()
+ def test_error(self):
+ evl = self.evl
+ evl.alarm(0.5, lambda: 1 / 0) # Simulate error in event loop
+ self.assertRaises(ZeroDivisionError, evl.run)
+
try:
import tornado
@@ -125,8 +135,8 @@ else:
def exit_error():
1/0
handle = evl.watch_file(rd, step2)
- handle = evl.alarm(0.01, exit_clean)
- handle = evl.alarm(0.005, say_hello)
+ handle = evl.alarm(0.1, exit_clean)
+ handle = evl.alarm(0.05, say_hello)
self.assertEqual(evl.enter_idle(say_waiting), 1)
evl.run()
self.assertTrue("da" in out, out)
@@ -134,6 +144,10 @@ else:
self.assertTrue("hello" in out, out)
self.assertTrue("clean exit" in out, out)
+ def test_error(self):
+ evl = self.evl
+ evl.alarm(0.5, lambda: 1 / 0) # Simulate error in event loop
+ self.assertRaises(ZeroDivisionError, evl.run)
try:
import asyncio
@@ -145,3 +159,8 @@ else:
self.evl = urwid.AsyncioEventLoop()
_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 8afeb97..9ed93c2 100644
--- a/urwid/tests/test_listbox.py
+++ b/urwid/tests/test_listbox.py
@@ -36,7 +36,7 @@ class ListBoxCalculateVisibleTest(unittest.TestCase):
self.cvtest( "simple middle position",
l, 3, 1, (0,1), 1, None )
- self.cvtest( "simple bottom postion",
+ self.cvtest( "simple bottom position",
l, 3, 2, (0,1), 2, None )
self.cvtest( "straddle top edge",
diff --git a/urwid/tests/test_vterm.py b/urwid/tests/test_vterm.py
index 59fe166..e47398c 100644
--- a/urwid/tests/test_vterm.py
+++ b/urwid/tests/test_vterm.py
@@ -18,6 +18,7 @@
#
# Urwid web site: http://excess.org/urwid/
+import errno
import os
import sys
import unittest
@@ -28,7 +29,6 @@ from urwid import vterm
from urwid import signals
from urwid.compat import B
-
class DummyCommand(object):
QUITSTRING = B('|||quit|||')
@@ -41,12 +41,20 @@ class DummyCommand(object):
stdout.write(B('\x1bc'))
while True:
- data = os.read(self.reader, 1024)
+ data = self.read(1024)
if self.QUITSTRING == data:
break
stdout.write(data)
stdout.flush()
+ def read(self, size):
+ while True:
+ try:
+ return os.read(self.reader, size)
+ except OSError as e:
+ if e.errno != errno.EINTR:
+ raise
+
def write(self, data):
os.write(self.writer, data)
@@ -143,6 +151,10 @@ class TermTest(unittest.TestCase):
self.write('1\n2\n3\n4\e[2;1f\e[2M')
self.expect('1\n4')
+ def test_nul(self):
+ self.write('a\0b')
+ self.expect('ab')
+
def test_movement(self):
self.write('\e[10;20H11\e[10;0f\e[20C\e[K')
self.expect('\n' * 9 + ' ' * 19 + '1')
@@ -179,7 +191,7 @@ class TermTest(unittest.TestCase):
self.edgewall()
self.expect('1-' + ' ' * 76 + '-2' + '\n' * 22
+ '3-' + ' ' * 76 + '-4')
- for y in xrange(23, 1, -1):
+ for y in range(23, 1, -1):
self.resize(80, y, soft=True)
self.write('\e[%df\e[J3-\e[%d;%df-4' % (y, y, 79))
desc = "try to rescale to 80x%d." % y
diff --git a/urwid/tests/test_widget.py b/urwid/tests/test_widget.py
index 3ae9a93..3f28bc1 100644
--- a/urwid/tests/test_widget.py
+++ b/urwid/tests/test_widget.py
@@ -10,25 +10,25 @@ class TextTest(unittest.TestCase):
self.t = urwid.Text("I walk the\ncity in the night")
def test1_wrap(self):
- expected = [B(t) for t in "I walk the","city in ","the night "]
+ expected = [B(t) for t in ("I walk the","city in ","the night ")]
got = self.t.render((10,))._text
assert got == expected, "got: %r expected: %r" % (got, expected)
def test2_left(self):
self.t.set_align_mode('left')
- expected = [B(t) for t in "I walk the ","city in the night "]
+ expected = [B(t) for t in ("I walk the ","city in the night ")]
got = self.t.render((18,))._text
assert got == expected, "got: %r expected: %r" % (got, expected)
def test3_right(self):
self.t.set_align_mode('right')
- expected = [B(t) for t in " I walk the"," city in the night"]
+ expected = [B(t) for t in (" I walk the"," city in the night")]
got = self.t.render((18,))._text
assert got == expected, "got: %r expected: %r" % (got, expected)
def test4_center(self):
self.t.set_align_mode('center')
- expected = [B(t) for t in " I walk the "," city in the night"]
+ expected = [B(t) for t in (" I walk the "," city in the night")]
got = self.t.render((18,))._text
assert got == expected, "got: %r expected: %r" % (got, expected)
@@ -50,7 +50,7 @@ class EditTest(unittest.TestCase):
got= e.keypress((12,),key)
assert got == expected, "%s. got: %r expected:%r" % (desc, got,
expected)
- assert e.edit_pos == pos, "%s. pos: %r expected pos: " % (
+ assert e.edit_pos == pos, "%s. pos: %r expected pos: %r" % (
desc, e.edit_pos, pos)
def test1_left(self):
diff --git a/urwid/text_layout.py b/urwid/text_layout.py
index f09372b..d8663b6 100644
--- a/urwid/text_layout.py
+++ b/urwid/text_layout.py
@@ -19,9 +19,11 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
from urwid.util import calc_width, calc_text_pos, calc_trim_text, is_wide_char, \
move_prev_char, move_next_char
-from urwid.compat import bytes, PYTHON3, B
+from urwid.compat import bytes, PYTHON3, B, xrange
class TextLayout:
def supports_align_mode(self, align):
@@ -456,8 +458,8 @@ def calc_pos( text, layout, pref_col, row ):
if pos is not None:
return pos
- rows_above = range(row-1,-1,-1)
- rows_below = range(row+1,len(layout))
+ rows_above = list(xrange(row-1,-1,-1))
+ rows_below = list(xrange(row+1,len(layout)))
while rows_above and rows_below:
if rows_above:
r = rows_above.pop(0)
diff --git a/urwid/treetools.py b/urwid/treetools.py
index 5b56d52..f2b1aad 100644
--- a/urwid/treetools.py
+++ b/urwid/treetools.py
@@ -20,6 +20,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
"""
Urwid tree view
@@ -313,8 +315,8 @@ class ParentNode(TreeNode):
def set_child_node(self, key, node):
"""Set the child node for a given key. Useful for bottom-up, lazy
- population of a tree.."""
- self._children[key]=node
+ population of a tree."""
+ self._children[key] = node
def change_child_key(self, oldkey, newkey):
if newkey in self._children:
diff --git a/urwid/util.py b/urwid/util.py
index 3569f8c..f57c898 100644
--- a/urwid/util.py
+++ b/urwid/util.py
@@ -20,8 +20,10 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
from urwid import escape
-from urwid.compat import bytes
+from urwid.compat import bytes, text_type, text_types
import codecs
@@ -108,7 +110,7 @@ def apply_target_encoding( s ):
"""
Return (encoded byte string, character set rle).
"""
- if _use_dec_special and type(s) == unicode:
+ if _use_dec_special and type(s) == text_type:
# first convert drawing characters
try:
s = s.translate( escape.DEC_SPECIAL_CHARMAP )
@@ -118,7 +120,7 @@ def apply_target_encoding( s ):
escape.ALT_DEC_SPECIAL_CHARS):
s = s.replace( c, escape.SO+alt+escape.SI )
- if type(s) == unicode:
+ if type(s) == text_type:
s = s.replace(escape.SI+escape.SO, u"") # remove redundant shifts
s = codecs.encode(s, _target_encoding, 'replace')
@@ -412,7 +414,7 @@ def _tagmarkup_recurse( tm, attr ):
attr, element = tm
return _tagmarkup_recurse( element, attr )
- if not isinstance(tm,(basestring, bytes)):
+ if not isinstance(tm, text_types + (bytes,)):
raise TagMarkupException("Invalid markup element: %r" % tm)
# text
diff --git a/urwid/version.py b/urwid/version.py
index e34283f..5bd4210 100644
--- a/urwid/version.py
+++ b/urwid/version.py
@@ -1,5 +1,4 @@
+from __future__ import division, print_function
-VERSION = (1, 3, 1)
+VERSION = (2, 0, 1)
__version__ = ''.join(['-.'[type(x) == int]+str(x) for x in VERSION])[1:]
-
-
diff --git a/urwid/vterm.py b/urwid/vterm.py
index cc4eb7f..0c91ca5 100644
--- a/urwid/vterm.py
+++ b/urwid/vterm.py
@@ -20,6 +20,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
import os
import sys
import time
@@ -43,7 +45,7 @@ from urwid.escape import DEC_SPECIAL_CHARS, ALT_DEC_SPECIAL_CHARS
from urwid.canvas import Canvas
from urwid.widget import Widget, BOX
from urwid.display_common import AttrSpec, RealTerminal, _BASIC_COLORS
-from urwid.compat import ord2, chr2, B, bytes, PYTHON3
+from urwid.compat import ord2, chr2, B, bytes, PYTHON3, xrange
ESC = chr(27)
@@ -671,7 +673,7 @@ class TermCanvas(Canvas):
self.widget.beep()
elif not dc and char in B("\x18\x1a"): # CAN/SUB
self.leave_escape()
- elif not dc and char == B("\x7f"): # DEL
+ elif not dc and char in B("\x00\x7f"): # NUL/DEL
pass # this is ignored
elif self.within_escape:
self.parse_escape(char)
diff --git a/urwid/web_display.py b/urwid/web_display.py
index 44a505c..2b2de46 100755
--- a/urwid/web_display.py
+++ b/urwid/web_display.py
@@ -19,6 +19,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
"""
Urwid web application display module
"""
@@ -659,7 +661,7 @@ class Screen:
urwid_id = "%09d%09d"%(random.randrange(10**9),
random.randrange(10**9))
self.pipe_name = os.path.join(_prefs.pipe_dir,"urwid"+urwid_id)
- os.mkfifo(self.pipe_name+".in",0600)
+ os.mkfifo(self.pipe_name+".in",0o600)
signal.signal(signal.SIGTERM,self._cleanup_pipe)
self.input_fd = os.open(self.pipe_name+".in",
@@ -743,9 +745,11 @@ class Screen:
rows = MAX_ROWS
self.screen_size = cols, rows
- def draw_screen(self, (cols, rows), r ):
+ def draw_screen(self, size, r ):
"""Send a screen update to the client."""
+ (cols, rows) = size
+
if cols != self.last_screen_width:
self.last_screen = {}
diff --git a/urwid/widget.py b/urwid/widget.py
index 661e7ed..97cc4b9 100644
--- a/urwid/widget.py
+++ b/urwid/widget.py
@@ -19,8 +19,11 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
from operator import attrgetter
+from urwid.compat import text_type, with_metaclass
from urwid.util import (MetaSuper, decompose_tagmarkup, calc_width,
is_wide_char, move_prev_char, move_next_char)
from urwid.text_layout import calc_pos, calc_coords, shift_line
@@ -203,15 +206,10 @@ def cache_widget_rows(cls):
return cached_rows
-class Widget(object):
+class Widget(with_metaclass(WidgetMeta, object)):
"""
Widget base class
- .. attribute:: __metaclass__
- :annotation: = urwid.WidgetMeta
-
- See :class:`urwid.WidgetMeta` definition
-
.. attribute:: _selectable
:annotation: = False
@@ -443,8 +441,6 @@ class Widget(object):
:returns: ``True`` if the position was set successfully anywhere on
*row*, ``False`` otherwise
"""
- __metaclass__ = WidgetMeta
-
_selectable = False
_sizing = frozenset([FLOW, BOX, FIXED])
_command_map = command_map
@@ -507,7 +503,7 @@ class Widget(object):
If ``'box'`` is among the values returned then the other
methods must be able to accept a two-element tuple
- (*maxcol*, *maxrow*) to their size paramter.
+ (*maxcol*, *maxrow*) to their size parameter.
If ``'fixed'`` is among the values returned then the other
methods must be able to accept an empty tuple () to
@@ -538,7 +534,7 @@ class Widget(object):
implemented for container widgets.
:class:`Text` widgets have implemented this method.
- You can use :meth:`Text.pack` to calculate the minumum
+ You can use :meth:`Text.pack` to calculate the minimum
columns and rows required to display a text widget without wrapping,
or call it iteratively to calculate the minimum number of columns
required to display the text wrapped into a target number of rows.
@@ -827,7 +823,7 @@ class Text(Widget):
>>> t = Text(('bold', u"stuff"), 'right', 'any')
>>> t
<Text flow widget 'stuff' align='right' wrap='any'>
- >>> print t.text
+ >>> print(t.text)
stuff
>>> t.attrib
[('bold', 5)]
@@ -868,10 +864,10 @@ class Text(Widget):
:type markup: text markup
>>> t = Text(u"foo")
- >>> print t.text
+ >>> print(t.text)
foo
>>> t.set_text(u"bar")
- >>> print t.text
+ >>> print(t.text)
bar
>>> t.text = u"baz" # not supported because text stores text but set_text() takes markup
Traceback (most recent call last):
@@ -1101,11 +1097,22 @@ class Edit(Text):
deletion. A caption may prefix the editing area. Uses text class
for text layout.
- Users of this class to listen for ``"change"`` events
- sent when the value of edit_text changes. See :func:``connect_signal``.
+ Users of this class may listen for ``"change"`` or ``"postchange"``
+ events. See :func:``connect_signal``.
+
+ * ``"change"`` is sent just before the value of edit_text changes.
+ It receives the new text as an argument. Note that ``"change"`` cannot
+ change the text in question as edit_text changes the text afterwards.
+ * ``"postchange"`` is sent after the value of edit_text changes.
+ It receives the old value of the text as an argument and thus is
+ appropriate for changing the text. It is possible for a ``"postchange"``
+ event handler to get into a loop of changing the text and then being
+ called when the event is re-emitted. It is up to the event
+ 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).
"""
# (this variable is picked up by the MetaSignals metaclass)
- signals = ["change"]
+ signals = ["change", "postchange"]
def valid_char(self, ch):
"""
@@ -1124,7 +1131,7 @@ class Edit(Text):
align=LEFT, wrap=SPACE, allow_tab=False,
edit_pos=None, layout=None, mask=None):
"""
- :param caption: markup for caption preceeding edit_text, see
+ :param caption: markup for caption preceding edit_text, see
:class:`Text` for description of text markup.
:type caption: text markup
:param edit_text: initial text for editing, type (bytes or unicode)
@@ -1160,6 +1167,7 @@ class Edit(Text):
self.allow_tab = allow_tab
self._edit_pos = 0
self.set_caption(caption)
+ self._edit_text = ''
self.set_edit_text(edit_text)
if edit_pos is None:
edit_pos = len(edit_text)
@@ -1270,15 +1278,15 @@ class Edit(Text):
"""
Set the caption markup for this widget.
- :param caption: markup for caption preceeding edit_text, see
+ :param caption: markup for caption preceding edit_text, see
:meth:`Text.__init__` for description of text markup.
>>> e = Edit("")
>>> e.set_caption("cap1")
- >>> print e.caption
+ >>> print(e.caption)
cap1
>>> e.set_caption(('bold', "cap2"))
- >>> print e.caption
+ >>> print(e.caption)
cap2
>>> e.attrib
[('bold', 4)]
@@ -1289,7 +1297,9 @@ class Edit(Text):
self._caption, self._attrib = decompose_tagmarkup(caption)
self._invalidate()
- caption = property(lambda self:self._caption)
+ caption = property(lambda self:self._caption, doc="""
+ Read-only property returning the caption for this widget.
+ """)
def set_edit_pos(self, pos):
"""
@@ -1321,7 +1331,9 @@ class Edit(Text):
self._edit_pos = pos
self._invalidate()
- edit_pos = property(lambda self:self._edit_pos, set_edit_pos)
+ edit_pos = property(lambda self:self._edit_pos, set_edit_pos, doc="""
+ Property controlling the edit position for this widget.
+ """)
def set_mask(self, mask):
"""
@@ -1344,20 +1356,22 @@ class Edit(Text):
>>> e = Edit()
>>> e.set_edit_text(u"yes")
- >>> print e.edit_text
+ >>> print(e.edit_text)
yes
>>> e
<Edit selectable flow widget 'yes' edit_pos=0>
>>> e.edit_text = u"no" # Urwid 0.9.9 or later
- >>> print e.edit_text
+ >>> print(e.edit_text)
no
"""
text = self._normalize_to_caption(text)
self.highlight = None
self._emit("change", text)
+ old_text = self._edit_text
self._edit_text = text
if self.edit_pos > len(text):
self.edit_pos = len(text)
+ self._emit("postchange", old_text)
self._invalidate()
def get_edit_text(self):
@@ -1365,15 +1379,15 @@ class Edit(Text):
Return the edit text for this widget.
>>> e = Edit(u"What? ", u"oh, nothing.")
- >>> print e.get_edit_text()
+ >>> print(e.get_edit_text())
oh, nothing.
- >>> print e.edit_text
+ >>> print(e.edit_text)
oh, nothing.
"""
return self._edit_text
edit_text = property(get_edit_text, set_edit_text, doc="""
- Read-only property returning the edit text for this widget.
+ Property controlling the edit text for this widget.
""")
def insert_text(self, text):
@@ -1392,7 +1406,7 @@ class Edit(Text):
<Edit selectable flow widget '42.5' edit_pos=4>
>>> e.set_edit_pos(2)
>>> e.insert_text(u"a")
- >>> print e.edit_text
+ >>> print(e.edit_text)
42a.5
"""
text = self._normalize_to_caption(text)
@@ -1406,8 +1420,8 @@ class Edit(Text):
Return text converted to the same type as self.caption
(bytes or unicode)
"""
- tu = isinstance(text, unicode)
- cu = isinstance(self._caption, unicode)
+ tu = isinstance(text, text_type)
+ cu = isinstance(self._caption, text_type)
if tu == cu:
return text
if tu:
@@ -1451,12 +1465,12 @@ class Edit(Text):
>>> e.keypress(size, 'x')
>>> e.keypress(size, 'left')
>>> e.keypress(size, '1')
- >>> print e.edit_text
+ >>> print(e.edit_text)
1x
>>> e.keypress(size, 'backspace')
>>> e.keypress(size, 'end')
>>> e.keypress(size, '2')
- >>> print e.edit_text
+ >>> print(e.edit_text)
x2
>>> e.keypress(size, 'shift f1')
'shift f1'
@@ -1465,8 +1479,8 @@ class Edit(Text):
p = self.edit_pos
if self.valid_char(key):
- if (isinstance(key, unicode) and not
- isinstance(self._caption, unicode)):
+ if (isinstance(key, text_type) and not
+ isinstance(self._caption, text_type)):
# screen is sending us unicode input, must be using utf-8
# encoding because that's all we support, so convert it
# to bytes to match our caption's type
@@ -1700,10 +1714,10 @@ class IntEdit(Edit):
>>> e, size = IntEdit(u"", 5002), (10,)
>>> e.keypress(size, 'home')
>>> e.keypress(size, 'delete')
- >>> print e.edit_text
+ >>> print(e.edit_text)
002
>>> e.keypress(size, 'end')
- >>> print e.edit_text
+ >>> print(e.edit_text)
2
"""
(maxcol,) = size
@@ -1728,7 +1742,7 @@ class IntEdit(Edit):
True
"""
if self.edit_text:
- return long(self.edit_text)
+ return int(self.edit_text)
else:
return 0
diff --git a/urwid/wimp.py b/urwid/wimp.py
index 03a3613..62a0819 100755
--- a/urwid/wimp.py
+++ b/urwid/wimp.py
@@ -19,6 +19,8 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
+
from urwid.widget import (Text, WidgetWrap, delegate_to_widget_mixin, BOX,
FLOW)
from urwid.canvas import CompositeCanvas
@@ -109,7 +111,7 @@ class CheckBox(WidgetWrap):
# allow users of this class to listen for change events
# sent when the state of this widget is modified
# (this variable is picked up by the MetaSignals metaclass)
- signals = ["change"]
+ signals = ["change", 'postchange']
def __init__(self, label, state=False, has_mixed=False,
on_state_change=None, user_data=None):
@@ -121,7 +123,7 @@ class CheckBox(WidgetWrap):
function call for a single callback
:param user_data: user_data for on_state_change
- Signals supported: ``'change'``
+ Signals supported: ``'change'``, ``"postchange"``
Register signal handler with::
@@ -184,12 +186,12 @@ class CheckBox(WidgetWrap):
Return label text.
>>> cb = CheckBox(u"Seriously")
- >>> print cb.get_label()
+ >>> print(cb.get_label())
Seriously
- >>> print cb.label
+ >>> print(cb.label)
Seriously
>>> cb.set_label([('bright_attr', u"flashy"), u" normal"])
- >>> print cb.label # only text is returned
+ >>> print(cb.label) # only text is returned
flashy normal
"""
return self._label.text
@@ -200,7 +202,7 @@ class CheckBox(WidgetWrap):
Set the CheckBox state.
state -- True, False or "mixed"
- do_callback -- False to supress signal from this change
+ do_callback -- False to suppress signal from this change
>>> changes = []
>>> def callback_a(cb, state, user_data):
@@ -233,7 +235,8 @@ class CheckBox(WidgetWrap):
# self._state is None is a special case when the CheckBox
# has just been created
- if do_callback and self._state is not None:
+ old_state = self._state
+ if do_callback and old_state is not None:
self._emit('change', state)
self._state = state
# rebuild the display widget with the new state
@@ -241,6 +244,8 @@ class CheckBox(WidgetWrap):
('fixed', self.reserve_columns, self.states[state] ),
self._label ] )
self._w.focus_col = 0
+ if do_callback and old_state is not None:
+ self._emit('postchange', old_state)
def get_state(self):
"""Return the state of the checkbox."""
@@ -335,7 +340,7 @@ class RadioButton(CheckBox):
This function will append the new radio button to group.
"first True" will set to True if group is empty.
- Signals supported: ``'change'``
+ Signals supported: ``'change'``, ``"postchange"``
Register signal handler with::
@@ -374,7 +379,7 @@ class RadioButton(CheckBox):
state -- True, False or "mixed"
- do_callback -- False to supress signal from this change
+ do_callback -- False to suppress signal from this change
If state is True all other radio buttons in the same button
group will be set to False.
@@ -504,9 +509,9 @@ class Button(WidgetWrap):
Return label text.
>>> b = Button(u"Ok")
- >>> print b.get_label()
+ >>> print(b.get_label())
Ok
- >>> print b.label
+ >>> print(b.label)
Ok
"""
return self._label.text