summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.coveragerc5
-rw-r--r--.coveralls.yml3
-rw-r--r--.travis.yml40
-rw-r--r--ISSUE_TEMPLATE.md14
-rw-r--r--PULL_REQUEST_TEMPLATE.md11
-rw-r--r--README.rst24
-rw-r--r--docs/changelog.rst12
-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/tutorial/index.rst6
-rwxr-xr-xexamples/browse.py6
-rwxr-xr-xexamples/calc.py42
-rwxr-xr-xexamples/dialog.py6
-rwxr-xr-xexamples/fib.py2
-rwxr-xr-xexamples/graph.py2
-rwxr-xr-xexamples/subproc.py2
-rw-r--r--examples/subproc2.py11
-rw-r--r--examples/twisted_serve_ssh.py8
-rw-r--r--setup.py4
-rw-r--r--tox.ini24
-rw-r--r--urwid/__init__.py2
-rw-r--r--urwid/canvas.py18
-rw-r--r--urwid/command_map.py2
-rw-r--r--urwid/compat.py26
-rwxr-xr-xurwid/container.py19
-rwxr-xr-xurwid/curses_display.py12
-rwxr-xr-xurwid/decoration.py21
-rwxr-xr-xurwid/display_common.py21
-rw-r--r--urwid/escape.py2
-rwxr-xr-xurwid/font.py38
-rwxr-xr-xurwid/graphics.py144
-rw-r--r--urwid/highlight.css19
-rwxr-xr-xurwid/html_fragment.py16
-rw-r--r--urwid/lcd_display.py5
-rw-r--r--urwid/listbox.py81
-rwxr-xr-xurwid/main_loop.py24
-rwxr-xr-xurwid/monitored_list.py20
-rwxr-xr-xurwid/old_str_util.py11
-rw-r--r--urwid/raw_display.py11
-rw-r--r--urwid/signals.py3
-rwxr-xr-xurwid/split_repr.py8
-rw-r--r--urwid/tests/test_doctests.py1
-rw-r--r--urwid/tests/test_event_loops.py11
-rw-r--r--urwid/tests/test_vterm.py14
-rw-r--r--urwid/tests/test_widget.py8
-rw-r--r--urwid/text_layout.py8
-rw-r--r--urwid/treetools.py6
-rw-r--r--urwid/util.py10
-rw-r--r--urwid/version.py1
-rw-r--r--urwid/vterm.py4
-rwxr-xr-xurwid/web_display.py8
-rw-r--r--urwid/widget.py80
-rwxr-xr-xurwid/wimp.py23
55 files changed, 639 insertions, 288 deletions
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..57420f2
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,5 @@
+[run]
+omit =
+ .tox/*
+ setup.py
+
diff --git a/.coveralls.yml b/.coveralls.yml
new file mode 100644
index 0000000..af0bd27
--- /dev/null
+++ b/.coveralls.yml
@@ -0,0 +1,3 @@
+service_name: travis-pro
+repo_token: lNMvPpeiiKc20j7oGjScYtGKUfVseah6S
+
diff --git a/.travis.yml b/.travis.yml
index d19ac11..3fa964e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,26 +1,26 @@
language: python
-python:
- - "2.6"
- - "2.7_with_system_site_packages"
- - "3.2_with_system_site_packages"
- - "3.3"
- - "3.4"
- - "3.5"
- - "pypy"
+matrix:
+ include:
+ - python: 2.7
+ env: TOXENV=py27
+ - python: 3.3
+ env: TOXENV=py33
+ - python: 3.4
+ env: TOXENV=py34
+ - python: 3.5
+ env: TOXENV=py35
+ - python: 3.6
+ env: TOXENV=py36
+ - python: pypy
+ env: TOXENV=pypy
before_install:
- "sudo apt-get update"
- "sudo apt-get install python-gi python3-gi"
install:
- - "pip install setuptools"
- - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install "twisted<=15.4"; fi
- - "[ $TRAVIS_PYTHON_VERSION == '3.2_with_system_site_packages' ] && pip install 'tornado<=4.3.0' || pip install twisted tornado"
+ - "pip install tox coverage coveralls"
script:
- - "DEPS=\"$TRAVIS_PYTHON_VERSION `python bin/deps.py`\"; echo $DEPS"
- - "[ \"$DEPS\" == '2.6 tornado twisted' -o
- \"$DEPS\" == '2.7_with_system_site_packages pygobject tornado twisted' -o
- \"$DEPS\" == '3.2_with_system_site_packages pygobject tornado' -o
- \"$DEPS\" == '3.3 tornado twisted' -o
- \"$DEPS\" == '3.4 tornado twisted' -o
- \"$DEPS\" == '3.5 tornado twisted' -o
- \"$DEPS\" == 'pypy tornado twisted' ]"
- - "python setup.py test"
+ - "tox"
+ - "coverage report"
+after_success:
+ - "coveralls"
+
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..e10d7ac
--- /dev/null
+++ b/ISSUE_TEMPLATE.md
@@ -0,0 +1,14 @@
+##### Description:
+[...]
+
+##### Affected versions (if applicable)
+- [ ] `master` branch (specify commit)
+- [ ] Latest stable version from `pypi`
+- [ ] Other (specify source)
+
+##### Steps to reproduce (if applicable)
+[...]
+
+##### Expected/actual outcome
+[...]
+
diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..d2b1df3
--- /dev/null
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,11 @@
+##### Checklist
+- [ ] I've ensured that similar functionality has not already been implemented
+- [ ] I've ensured that similar functionality has not earlier been proposed and declined
+- [ ] I've branched off the `master` or `python-dual-support` branch
+- [ ] I've merged fresh upstream into my branch recently
+- [ ] I've ran `tox` successfully in local environment
+- [ ] I've included docstrings and/or documentation and/or examples for my code (if this is a new feature)
+
+##### Description:
+*(P. S. If this pull requests fixes an existing issue, please specify which one.)*
+
diff --git a/README.rst b/README.rst
index 9a185ba..261f68b 100644
--- a/README.rst
+++ b/README.rst
@@ -2,10 +2,19 @@
:alt: build status
:target: https://travis-ci.org/urwid/urwid/
+.. image:: https://coveralls.io/repos/github/urwid/urwid/badge.svg
+ :alt: build coverage
+ :target: https://coveralls.io/github/urwid/urwid
+
`Development version documentation <http://urwid.readthedocs.org/en/latest/>`_
+**Urwid is looking for new maintainers, please open an issue here to volunteer!**
+
.. 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,18 @@ 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
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 676a5d3..b79ee44 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -279,7 +279,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 +307,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 +566,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 +593,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 +611,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 +1048,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/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/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/examples/browse.py b/examples/browse.py
index d5a5f16..ab689c0 100755
--- a/examples/browse.py
+++ b/examples/browse.py
@@ -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..e56be4a 100755
--- a/examples/calc.py
+++ b/examples/calc.py
@@ -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..1328e79 100755
--- a/examples/dialog.py
+++ b/examples/dialog.py
@@ -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/fib.py b/examples/fib.py
index e3262b4..ad6acc5 100755
--- a/examples/fib.py
+++ b/examples/fib.py
@@ -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..536fa00 100755
--- a/examples/graph.py
+++ b/examples/graph.py
@@ -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/subproc.py b/examples/subproc.py
index 64eb072..4c7e918 100755
--- a/examples/subproc.py
+++ b/examples/subproc.py
@@ -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..c40a647 100644
--- a/examples/subproc2.py
+++ b/examples/subproc2.py
@@ -1,8 +1,15 @@
# 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/twisted_serve_ssh.py b/examples/twisted_serve_ssh.py
index aad63f9..c1a0004 100644
--- a/examples/twisted_serve_ssh.py
+++ b/examples/twisted_serve_ssh.py
@@ -31,6 +31,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 +200,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 +208,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 +412,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.py b/setup.py
index 2bdafaf..37a7413 100644
--- a/setup.py
+++ b/setup.py
@@ -70,6 +70,7 @@ setup_d = {
"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",
],
}
@@ -78,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/tox.ini b/tox.ini
new file mode 100644
index 0000000..570e26f
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,24 @@
+[tox]
+envlist =
+ py27
+ py33
+ py34
+ py35
+ py36
+ pypy
+
+[testenv]
+usedevelop = true
+deps =
+ setuptools
+ tornado
+ coverage
+ py27: twisted==16.6.0
+ py33: twisted
+ py34: twisted
+ py35: twisted
+ py36: twisted
+ pypy: twisted
+commands =
+ coverage run ./setup.py test
+
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..de0bf7a 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,34 @@ if PYTHON3:
chr2 = lambda x: bytes([x])
B = lambda x: x.encode('iso8859-1')
bytes3 = bytes
+ text_type = str
+ xrange = range
+ text_types = (str,)
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)
+
+
+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 fdd5da4..c2cc71f 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 = []
@@ -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
@@ -2272,9 +2275,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 867691d..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
@@ -91,8 +93,9 @@ _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 | _ITALICS)
+ _STANDOUT | _UNDERLINE | _BLINK | _BOLD | _ITALICS | _STRIKETHROUGH)
_BG_MASK = _BG_COLOR_MASK | _BG_BASIC_COLOR | _BG_HIGH_COLOR
DEFAULT = 'default'
@@ -138,6 +141,7 @@ _ATTRIBUTES = {
'underline': _UNDERLINE,
'blink': _BLINK,
'standout': _STANDOUT,
+ 'strikethrough': _STRIKETHROUGH,
}
def _value_lookup_table(values, size):
@@ -452,7 +456,8 @@ class AttrSpec(object):
'h8' (color number 8), 'h255' (color number 255)
Setting:
- 'bold', 'italics', '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
@@ -507,6 +512,7 @@ class AttrSpec(object):
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):
"""
@@ -548,7 +554,7 @@ class AttrSpec(object):
return (self._foreground_color() +
',bold' * self.bold + ',italics' * self.italics +
',standout' * self.standout + ',blink' * self.blink +
- ',underline' * self.underline)
+ ',underline' * self.underline + ',strikethrough' * self.strikethrough)
def _set_foreground(self, foreground):
color = None
@@ -715,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):
@@ -814,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/highlight.css b/urwid/highlight.css
new file mode 100644
index 0000000..097663d
--- /dev/null
+++ b/urwid/highlight.css
@@ -0,0 +1,19 @@
+/* Style definition file generated by highlight 3.41, http://www.andre-simon.de/ */
+/* highlight theme: Kwrite Editor */
+body.hl { background-color:#e0eaee; }
+pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New',monospace;}
+.hl.num { color:#b07e00; }
+.hl.esc { color:#ff00ff; }
+.hl.str { color:#bf0303; }
+.hl.pps { color:#818100; }
+.hl.slc { color:#838183; font-style:italic; }
+.hl.com { color:#838183; font-style:italic; }
+.hl.ppc { color:#008200; }
+.hl.opt { color:#000000; }
+.hl.ipl { color:#0057ae; }
+.hl.lin { color:#555555; }
+.hl.kwa { color:#000000; font-weight:bold; }
+.hl.kwb { color:#0057ae; }
+.hl.kwc { color:#000000; font-weight:bold; }
+.hl.kwd { color:#010181; }
+
diff --git a/urwid/html_fragment.py b/urwid/html_fragment.py
index 3db1fd9..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:
@@ -134,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):
@@ -219,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:
@@ -227,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 72ce2d5..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):
"""
@@ -313,7 +315,7 @@ class ListBox(Widget, WidgetContainerMixin):
if getattr(body, 'get_focus', None):
self._body = body
else:
- self._body = PollingListWalker(body)
+ self._body = SimpleListWalker(body)
self._invalidate()
body = property(_get_body, _set_body, doc="""
@@ -480,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))
@@ -498,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))
@@ -512,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),)
+ 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
@@ -602,7 +604,7 @@ class ListBox(Widget, WidgetContainerMixin):
"""
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
@@ -784,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()
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()
@@ -888,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)
@@ -916,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 )
@@ -939,10 +941,10 @@ class ListBox(Widget, WidgetContainerMixin):
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
@@ -975,15 +977,14 @@ 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
@@ -994,12 +995,11 @@ class ListBox(Widget, WidgetContainerMixin):
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:
@@ -1018,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
@@ -1258,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
@@ -1443,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
diff --git a/urwid/main_loop.py b/urwid/main_loop.py
index 28577b2..442c27d 100755
--- a/urwid/main_loop.py
+++ b/urwid/main_loop.py
@@ -21,12 +21,14 @@
#
# Urwid web site: http://excess.org/urwid/
+from __future__ import division, print_function
import time
import heapq
import select
import os
from functools import wraps
+from itertools import count
from weakref import WeakKeyDictionary
try:
@@ -442,7 +444,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:
@@ -589,6 +591,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 +604,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 +695,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 +715,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
@@ -848,7 +852,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]
+ raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
def handle_exit(self,f):
"""
@@ -1183,7 +1187,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]
+ raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
def handle_exit(self, f, enable_idle=True):
"""
@@ -1203,7 +1207,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()
@@ -1325,7 +1329,7 @@ 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]
+ raise self._exc_info[0](self._exc_info[1]).with_traceback(self._exc_info[2])
self._exc_info = None
@@ -1354,7 +1358,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 e48e4b0..b0482b1 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
"""
@@ -663,8 +665,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()
@@ -832,7 +837,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:
@@ -931,7 +936,7 @@ class Screen(BaseScreen, RealTerminal):
fg = "39"
st = ("1;" * a.bold + "3;" * a.italics +
"4;" * a.underline + "5;" * a.blink +
- "7;" * a.standout)
+ "7;" * a.standout + "9;" * a.strikethrough)
if a.background_high:
bg = "48;5;%d" % a.background_number
elif a.background_basic:
diff --git a/urwid/signals.py b/urwid/signals.py
index 9e93597..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
@@ -33,7 +34,7 @@ class MetaSignals(type):
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)
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..b01212d 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
diff --git a/urwid/tests/test_vterm.py b/urwid/tests/test_vterm.py
index 4dadfcc..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)
@@ -183,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 cc8c63e..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)
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..7b70ec5 100644
--- a/urwid/version.py
+++ b/urwid/version.py
@@ -1,3 +1,4 @@
+from __future__ import division, print_function
VERSION = (1, 3, 1)
__version__ = ''.join(['-.'[type(x) == int]+str(x) for x in VERSION])[1:]
diff --git a/urwid/vterm.py b/urwid/vterm.py
index 0f091ea..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)
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 9a732e7..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
@@ -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):
"""
@@ -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)
@@ -1275,10 +1283,10 @@ class Edit(Text):
>>> 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 25e70c1..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
@@ -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::
@@ -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