urwid-1.1.1/0000775000175000017500000000000012051304275012205 5ustar ianian00000000000000urwid-1.1.1/MANIFEST.in0000664000175000017500000000021212051303575013740 0ustar ianian00000000000000recursive-include examples *.py *.tac recursive-include docs *.rst *.py *.py.xdotool *.png *.html *.sh Makefile include COPYING CHANGELOG urwid-1.1.1/PKG-INFO0000664000175000017500000000445512051304275013312 0ustar ianian00000000000000Metadata-Version: 1.1 Name: urwid Version: 1.1.1 Summary: A full-featured console (xterm et al.) user interface library Home-page: http://excess.org/urwid/ Author: Ian Ward Author-email: ian@excess.org License: LGPL Download-URL: http://excess.org/urwid/urwid-1.1.1.tar.gz Description: Urwid is a console user interface library. It includes many features useful for text console application developers including: - Applcations resize quickly and smoothly - Automatic, programmable text alignment and wrapping - Simple markup for setting text attributes within blocks of text - Powerful list box with programmable content for scrolling all widget types - Your choice of event loops: Twisted, Glib or built-in select-based loop - Pre-built widgets include edit boxes, buttons, check boxes and radio buttons - Display modules include raw, curses, and experimental LCD and web displays - Support for UTF-8, simple 8-bit and CJK encodings - 256 and 88 color mode support - Python 3.2 support Home Page: http://excess.org/urwid/ Documentation: http://excess.org/urwid/docs/ Example Program Screenshots: http://excess.org/urwid/examples.html Keywords: curses ui widget scroll listbox user interface text layout console ncurses Platform: unix-like Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: Console :: Curses Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Operating System :: POSIX Classifier: Operating System :: Unix Classifier: Operating System :: MacOS :: MacOS X Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Widget Sets Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 urwid-1.1.1/setup.py0000664000175000017500000000747312051303575013734 0ustar ianian00000000000000#!/usr/bin/python # # Urwid setup.py exports the useful bits # Copyright (C) 2004-2012 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: http://excess.org/urwid/ try: PYTHON3 = not str is bytes except NameError: PYTHON3 = False try: from setuptools import setup, Extension # distribute required for Python 3 have_setuptools = True except ImportError: if PYTHON3: raise from distutils.core import setup, Extension have_setuptools = False import os exec(open(os.path.join("urwid","version.py")).read()) release = __version__ setup_d = { 'name':"urwid", 'version':release, 'author':"Ian Ward", 'author_email':"ian@excess.org", 'ext_modules':[Extension('urwid.str_util', sources=['source/str_util.c'])], 'packages':['urwid'], 'url':"http://excess.org/urwid/", 'download_url':"http://excess.org/urwid/urwid-%s.tar.gz"%release, 'license':"LGPL", 'keywords':"curses ui widget scroll listbox user interface text layout console ncurses", 'platforms':"unix-like", 'description': "A full-featured console (xterm et al.) user interface library", 'long_description':""" Urwid is a console user interface library. It includes many features useful for text console application developers including: - Applcations resize quickly and smoothly - Automatic, programmable text alignment and wrapping - Simple markup for setting text attributes within blocks of text - Powerful list box with programmable content for scrolling all widget types - Your choice of event loops: Twisted, Glib or built-in select-based loop - Pre-built widgets include edit boxes, buttons, check boxes and radio buttons - Display modules include raw, curses, and experimental LCD and web displays - Support for UTF-8, simple 8-bit and CJK encodings - 256 and 88 color mode support - Python 3.2 support Home Page: http://excess.org/urwid/ Documentation: http://excess.org/urwid/docs/ Example Program Screenshots: http://excess.org/urwid/examples.html """[1:], 'classifiers':[ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: Console :: Curses", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Operating System :: POSIX", "Operating System :: Unix", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Widget Sets", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.4", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", ], } if have_setuptools: setup_d['zip_safe'] = False setup_d['test_suite'] = 'urwid.tests.test_all' if PYTHON3: setup_d['use_2to3'] = True if __name__ == "__main__": setup(**setup_d) urwid-1.1.1/COPYING0000664000175000017500000006350212045064707013254 0ustar ianian00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! urwid-1.1.1/CHANGELOG0000664000175000017500000007540112051304175013425 0ustar ianian00000000000000 -*- coding: utf-8 -*- Urwid 1.1.1 * Fix for Pile not changing focus on mouse events * Fix for Overlay.get_cursor_coords() Urwid 1.1.0 * New common container API: focus, focus_position, contents, options(), get_focus_path(), set_focus_path(), __getitem__, __iter__(), __reversed__() implemented across all included container widgets A full description doesn't fit here, see the Container Widgets section in the manual for details * New Sphinx-based documentation now included in source: Tutorial rewritten, manual revised and new reference based on updated docstrings (by Marco Giusti, Patrick Totzke) * New list walker SimpleFocusListWalker like SimpleListWalker but updates focus position as items are inserted or removed * New decoration widget WidgetDisable to disable interaction with the widgets it wraps * SelectableIcon selectable text widget used by button widgets is now documented (available since 0.9.9) * Columns widget now tries to keep column in focus visible, hiding columns on the left when necessary * Padding widget now defaults to ('relative', 100) instead of 'pack' so that left and right parameters are more useful and more child widgets are supported * New list walker "API Version 2" that is simpler for many list walker uses; "API Version 1" will still continue to be supported * List walkers may now allow iteration from the absolute top or bottom of the list if they provide a positions() method * raw_display now erases to the end of the line with EL escape sequence to improve copy+paste behavior for some terminals * Filler now has top and bottom parameters like Padding's left and right parameters and accepts 'pack' instead of None as a height value for widgets that calculate their own number of rows * Pile and Columns now accepts 'pack' instead of 'flow' for widgets that calculate their own number of rows or columns * Pile and Columns now accept 'given' instead of 'fixed' for cases where the number of rows or columns are specified by the container options * Pile and Columns widgets now accept any iterable to their __init__() methods * Widget now has a default focus_position property that raises an IndexError when read to be consistent with new common container API * GridFlow now supports multiple cell widths within the same widget * BoxWidget, FlowWidget and FixedWidget are deprecated, instead use the sizing() function or _sizing attribute to specify the supported sizing modes for your custom widgets * Some new shift+arrow and numpad input sequences from RXVT and xterm are now recognized * Fix for alarms when used with a screen event loop (e.g. curses_display) * Fix for raw_display when terminal width is 1 column * Fixes for a Columns.get_cursor_coords() regression and a SelectableIcon.get_cursor_coords() bug * Fixes for incorrect handling of box columns in a number of Columns methods when that column is selectable * Fix for Terminal widget input handling with Python 3 Urwid 1.0.3 * Fix for alarms when used with a screen event loop (e.g. curses_display) * Fix for Overlay.get_cursor_coords() Urwid 1.0.2 * Fix for bug when entering Unicode text into Edit widget with bytes caption * Fix a regression when not running in UTF-8 mode * Fix for a MainLoop.remove_watch_pipe() bug * Fix for a bug when packing empty Edit widgets * Fix for a ListBox "contents too long" error with very large Edit widgets * Prevent ListBoxes from selecting 0-height selectable widgets when moving up or down * Fix a number of bugs caused by 0-height widgets in a ListBox Urwid 1.0.1 * Fix for Terminal widget in BSD/OSX * Fix for a Filler mouse_event() position bug * Fix support for mouse positions up to x=255, y=255 * Fixes for a number of string encoding issues under Python 3 * Fix for a LineBox border __init__() parameters * Fix input input of UTF-8 in tour.py example by converting captions to unicode * Fix tutorial examples' use of TextCanvas and switch to using unicode literals * Prevent raw_display from calling tcseattr() or tcgetattr() on non-ttys * Disable curses_display external event loop support: screen resizing and gpm events are not properly supported * Mark PollingListWalker as deprecated Urwid 1.0.0 * New support for Python 3.2 from the same 2.x code base, requires distribute instead of setuptools (by Kirk McDonald, Wendell, Marien Zwart) everything except TwistedEventLoop and GLibEventLoop is supported * New experimental Terminal widget with xterm emulation and terminal.py example program (by aszlig) * Edit widget now supports a mask (for passwords), has a insert_text_result() method for full-field validation and normalizes input text to Unicode or bytes based on the caption type used * New TreeWidget, TreeNode, ParentNode, TreeWalker and TreeListBox classes for lazy expanding/collapsing tree views factored out of browse.py example program, with new treesample.py example program (by Rob Lanphier) * MainLoop now calls draw_screen() just before going idle, so extra calls to draw_screen() in user code may now be removed * New MainLoop.watch_pipe() method for subprocess or threaded communication with the process/thread updating the UI, and new subproc.py example demonstrating its use * New PopUpLauncher and PopUpTarget widgets and MainLoop option for creating pop-ups and drop-downs, and new pop_up.py example program * New twisted_serve_ssh.py example (by Ali Afshar) that serves multiple displays over ssh from the same application using Twisted and the TwistedEventLoop * ListBox now includes a get_cursor_coords() method, allowing nested ListBox widgets * Columns widget contents may now be marked to always be treated as flow widgets for mixing flow and box widgets more easily * New lcd_display module with support for CF635 USB LCD panel and lcd_cf635.py example program with menus, slider controls and a custom font * Shared command_map instance is now stored as Widget._command_map class attribute and may be overridden in subclasses or individual widgets for more control over special keystrokes * Overlay widget parameters may now be adjusted after creation with set_overlay_parameters() method * New WidgetPlaceholder widget useful for swapping widgets without having to manipulate a container widget's contents * LineBox widgets may now include title text * ProgressBar text content and alignment may now be overridden * Use reactor.stop() in TwistedEventLoop and document that Twisted's reactor is not designed to be stopped then restarted * curses_display now supports AttrSpec and external event loops (Twisted or GLib) just like raw_display * raw_display and curses_display now support the IBMPC character set (currently only used by Terminal widget) * Fix for a gpm_mev bug preventing user input when on the console * Fix for leaks of None objects in str_util extension * Fix for WidgetWrap and AttrMap not working with fixed widgets * Fix for a lock up when attempting to wrap text containing wide characters into a single character column Urwid 0.9.9.2 * Fix for an Overlay get_cursor_coords(), and Text top-widget bug * Fix for a Padding rows() bug when used with width=PACK * Fix for a bug with large flow widgets used in an Overlay * Fix for a gpm_mev bug * Fix for Pile and GraphVScale when rendered with no contents * Fix for a Python 2.3 incompatibility (0.9.9 is the last release to claim support Python 2.3) Urwid 0.9.9.1 * Fix for ListBox snapping to selectable widgets taller than the ListBox itself * raw_display switching to alternate buffer now works properly with Terminal.app * Fix for BoxAdapter backwards incompatibility introduced in 0.9.9 * Fix for a doctest failure under powerpc * Fix for systems with gpm_mev installed but not running gpm Urwid 0.9.9 * New support for 256 and 88 color terminals with raw_display and html_fragment display modules * New palette_test example program to demonstrate high color modes * New AttrSpec class for specifying specific colors instead of using attributes defined in the screen's palette * New MainLoop class ties together widgets, user input, screen display and one of a number of new event loops, removing the need for tedious, error-prone boilerplate code * New GLibEventLoop allows running Urwid applications with GLib (makes D-Bus integration easier) * New TwistedEventLoop allows running Urwid with a Twisted reactor * Added new docstrings and doctests to many widget classes * New AttrMap widget supports mapping any attribute to any other attribute, replaces AttrWrap widget * New WidgetDecoration base class for AttrMap, BoxAdapter, Padding, Filler and LineBox widgets creates a common method for accessing and updating their contained widgets * New left and right values may be specified in Padding widgets * New command_map for specifying which keys cause actions such as clicking Button widgets and scrolling ListBox widgets * New tty_signal_keys() method of raw_display.Screen and curses_display.Screen allows changing or disabling the keys used to send signals to the application * Added helpful __repr__ for many widget classes * Updated all example programs to use MainLoop class * Updated tutorial with MainLoop usage and improved examples * Renamed WidgetWrap.w to _w, indicating its intended use as a way to implement a widget with other widgets, not necessarily as a container for other widgets * Replaced all tabs with 4 spaces, code is now more aerodynamic (and PEP 8 compliant) * Added saving of stdin and stdout in raw_display module allowing the originals to be redirected * Updated BigText widget's HalfBlock5x4Font * Fixed graph example CPU usage when animation is stopped * Fixed a memory leak related to objects listening for signals * Fixed a Popen3 deprecation warning Urwid 0.9.8.4 * Fixed incompatibilities with Python 2.6 (by Friedrich Weber) * Fixed a SimpleListWalker with emptied list bug (found by Walter Mundt) * Fixed a curses_display stop()/start() bug (found by Christian Scharkus) * Fixed an is_wide_character() segfault on bad input data bug (by Andrew Psaltis) * Fixed a CanvasCache with render() used in both a widget and its superclass bug (found by Andrew Psaltis) * Fixed a ListBox.ends_visible() on empty list bug (found by Marc Hartstein) * Fixed a tutorial example bug (found by Kurtis D. Rader) * Fixed an Overlay.keypress() bug (found by Andreas Klöckner) * Fixed setuptools configuration (by Andreas Klöckner) Urwid 0.9.8.3 * Fixed a canvas cache memory leak affecting 0.9.8, 0.9.8.1 and 0.9.8.2 (found by John Goodfellow) * Fixed a canvas fill_attr() bug (found by Joern Koerner) Urwid 0.9.8.2 * Fixed incompatibilities with Python 2.3 * Fixed Pile cursor pref_col bug, WidgetWrap rows caching bug, Button mouse_event with no callback bug, Filler body bug triggered by the tutorial and a LineBox lline parameter typo. Urwid 0.9.8.1 * Fixed a Filler render() bug, a raw_display start()/stop() bug and a number of problems triggered by very small terminal window sizes. Urwid 0.9.8 * Rendering is now significantly faster. * New Widget base class for all widgets. It includes automatic caching of rows() and render() methods. It also adds a new __super attribute for accessing methods in superclasses. Widgets must now call self._invalidate() to notify the cache when their content has changed. To disable caching in a widget set the class variable no_cache to a list that includes the string "render". * Canvas classes have been reorganized: Canvas has been renamed to TextCanvas and Canvas is now the base class for all canvases. New canvas classes include BlankCanvas, SolidCanvas and CompositeCanvas. * External event loops may now be used with the raw_display module. The new methods get_input_descriptors() and get_input_nonblocking() should be used instead of get_input() to allow input processing without blocking. * The Columns, Pile and ListBox widgets now choose their first selectable child widget as the focus widget by defaut. * New ListWalker base class for list walker classes. * New Signals class that will be used to improve the existing event callbacks. Currently it is used for ListWalker objects to notify their ListBox when their content has changed. * SimpleListWalker now behaves as a list and supports all list operations. This class now detects when changes are made to the list and notifies the ListBox object. New code should use this class to wrap lists of widgets before passing them to the ListBox constructor. * New PollingListWalker class is now the default list walker that is used when passing a simple list to the ListBox constructor. This class is intended for backwards compatibility only. When this class is used the ListBox object is unable to cache its render() method. * The curses_display module can now draw in the lower-right corner of the screen. * All display modules now have start() and stop() methods that may be used instead of calling run_wrapper(). * The raw_display module now uses an alternate buffer so that the original screen can be restored on exit. The old behaviour is available by seting the alternate_buffer parameter of start() or run_wrapper() to False. * Many internal string processing functions have been rewritten in C to improve their performance. * Compatible with Python >= 2.2. Python 2.1 is no longer supported. Urwid 0.9.7.2 * Improved performance in UTF-8 mode when ASCII text is used. * Fixed a UTF-8 input bug. * Added a clear() function to the the display modules to force the screen to be repainted on the next draw_screen() call. Urwid 0.9.7.1 * Fixed bugs in Padding and Overlay widgets introduced in 0.9.7. Urwid 0.9.7 * Added initial support for fixed widgets - widgets that have a fixed size on screen. Fixed widgets expect a size parameter equal to (). Fixed widgets must implement the pack(..) function to return their size. * New BigText class that draws text with fonts made of grids of character cells. BigText is a fixed widget and doesn't do any alignment or wrapping. It is intended for banners and number readouts that need to stand out on the screen. Fonts: Thin3x3Font, Thin4x3Font, Thin6x6Font (full ascii) UTF-8 only fonts: HalfBlock5x4Font, HalfBlock6x5Font, HalfBlockHeavy6x5Font, HalfBlock7x7Font (full ascii) New function get_all_fonts() may be used to get a list of the available fonts. * New example program bigtext.py demonstrates use of BigText. * Padding class now has a clipping mode that pads or clips fixed widgets to make them behave as flow widgets. * Overlay class can now accept a fixed widget as the widget to display "on top". * New Canvas functions: pad_trim() and pad_trim_left_right(). * Fixed a bug in Filler.get_cursor_coords() that causes a crash if the contained widget's get_cursor_coords() function returns None. * Fixed a bug in Text.pack() that caused an infinite loop when the text contained a newline. This function is not currently used by Urwid. * Edit.__init__() now calls set_edit_text() to initialize its text. * Overlay.calculate_padding_filler() and Padding.padding_values() now include focus parameters. Urwid 0.9.6 * Fixed Unicode conversion and locale issues when using Urwid with Python < 2.4. The graph.py example program should now work properly with older versions of Python. * The docgen_tutorial.py script can now write out the tutorial example programs as individual files. * Updated reference documentation table of contents to show which widgets are flow and/or box widgets. * Columns.set_focus(..) will now accept an integer or a widget as its parameter. * Added detection for rxvt's HOME and END escape sequences. * Added support for setuptools (improved distutils). Urwid 0.9.5 * Some Unicode characters are now converted to use the G1 alternate character set with DEC special and line drawing characters. These Unicode characters should now "just work" in almost all terminals and encodings. When Urwid is run with the UTF-8 encoding the characters are left as UTF-8 and not converted. The characters converted are: \u00A3 (£), \u00B0 (°), \u00B1 (±), \u00B7 (·), \u03C0 (π), \u2260 (≠), \u2264 (≤), \u2265 (≥), \u23ba (⎺), \u23bb (⎻), \u23bc (⎼), \u23bd (⎽), \u2500 (─), \u2502 (│), \u250c (┌), \u2510 (┐), \u2514 (└), \u2518 (┘), \u251c (├), \u2524 (┤), \u252c (┬), \u2534 (┴), \u253c (┼), \u2592 (▒), \u25c6 (◆) * New SolidFill class for filling an area with a single character. * New LineBox class for wrapping widgets in a box made of line- drawing characters. May be used as a box widget or a flow widget. * New example program graph.py demonstrates use of BarGraph, LineBox, ProgressBar and SolidFill. * Pile class may now be used as a box widget and contain a mix of box and flow widgets. * Columns class may now contain a mix of box and flow widgets. The box widgets will take their height from the maximum height of the flow widgets. * Improved the smoothness of resizing with raw_display module. The module will now try to stop updating the screen when a resize event occurs during the update. * The Edit and IntEdit classes now use their set_edit_text() and set_edit_pos() functions when handling keypresses, so those functions may be overridden to catch text modification. * The set_state() functions in the CheckBox and RadioButton classes now have a do_callback parameter that determines if the callback function registered will be called. * Fixed a newly introduced incompatibility with python < 2.3. * Fixed a missing symbol in curses_display when python is linked against libcurses. * Fixed mouse handling bugs in the Frame and Overlay classes. * Fixed a Padding bug when the left or right has no padding. Urwid 0.9.4 * Enabled mouse handling across the Urwid library. Added a new mouse_event() method to the Widget interface definition and to the following widgets: Edit, CheckBox, RadioButton, Button, GridFlow, Padding, Filler, Overlay, Frame, Pile, Columns, BoxAdapter and ListBox. Updated example programs browse.py, calc.py, dialog.py, edit.py and tour.py to support mouse input. * Released the files used to generate the reference and tutorial documentation: docgen_reference.py, docgen_tutorial.py and tmpl_tutorial.html. The "docgen" scripts write the documentation to stdout. docgen_tutorial.py requires the Templayer HTML templating library to run: http://excess.org/templayer/ * Improved Widget and List Walker interface documentation. * Fixed a bug in the handling of invalid UTF-8 data. All invalid characters are now replaced with '?' characters when displayed. Urwid 0.9.3 * Improved mouse reporting. The raw_display module now detects gpm mouse events by reading /usr/bin/mev output. The curses_display module already supports gpm directly. Mouse drag events are now reported by raw_display in terminals that provide button event tracking and on the console with gpm. Note that gpm may report coordinates off the screen if the user drags the mouse off the edge. Button release events now report which button was released if that information is available, currently only on the console with gpm. * Added display of raw keycodes to the input_test.py example program. * Fixed a text layout bug affecting clipped text with blank lines, and another related to wrapped text starting with a space character. * Fixed a Frame.keypress() bug that caused it to call keypress on unselectable widgets. Urwid 0.9.2 * Preliminary mouse support was added to the raw_display and curses_display modules. A new Screen.set_mouse_tracking() method was added to enable mouse tracking. Mouse events are returned alongside keystrokes from the Screen.get_input() method. The widget interface does not yet include mouse handling. This will be addressed in the next release. * A new convenience function is_mouse_event() was added to help in separating mouse events from keystrokes. * Added a new example program input_test.py. This program displays the keyboard and mouse input it receives. It may be run as a CGI script or from the command line. On the command line it defaults to using the curses_display module, use input_test.py raw to use the raw_display module instead. * Fixed an Edit.render() bug that caused it to render the cursor in a different location than that reported by Edit.get_cursor_coords() in some circumstances. * Fixed a bug preventing use of UTF-8 characters with Divider widgets. Urwid 0.9.1 * BarGraph and ProgressBar can now display data more accurately by using the UTF-8 vertical and horizontal eighth characters. This behavior will be enabled when the UTF-8 encoding is detected and "smoothed" attributes are passed to the BarGraph or ProgressBar constructors. * New get_encoding_mode() function to determine how Urwid will treat raw string data. * New raw_display.signal_init() and raw_display.signal_restore() methods that may be overridden by threaded applications that need to call signal.signal() from their main thread. * Fixed a bug that prevented the use of UTF-8 strings in text markup. * Removed some forgotten asserts that broke 8-bit and CJK input. Urwid 0.9.0 * New support for UTF-8 encoding including input, display and editing of narrow and wide (CJK) characters. Preliminary combining (zero-width) character support is included, but full support will require terminal behavior detection. Right-to-Left input and display are not implemented. * New raw_display module that handles console display without relying on external libraries. This module was written as a work around for the lack of UTF-8 support in the standard version of ncurses. Eliminates "dead corner" in the bottom right of the screen. Avoids use of bold text in xterm and gnome-terminal for improved text legibility. * Fixed Overlay bug related to UTF-8 handling. * Fixed Edit.move_cursor_to_coords(..) bug related to wide characters in UTF-8 encoding. Urwid 0.9.0-pre3 * Fixed Canvas attribute padding bug related to -pre1 changes. Urwid 0.9.0-pre2 * Replaced the custom align and wrap modes in example program calc.py with a new layout class. * Fixed Overlay class call to Canvas.overlay() broken by -pre1 changes. * Fixed Padding bug related to Canvas -pre1 changes. Urwid 0.9.0-pre1 * New support for UTF-8 encoding. Unicode strings may be used and will be converted to the current encoding when output. Regular strings in the current encoding may still be used. PLEASE NOTE: There are issues related to displaying UTF-8 characters with the curses_display module that have not yet been resolved. * New set_encoding() function replaces util.set_double_byte_encoding(). * New supports_unicode() function to query if unicode strings with characters outside the ascii range may be used with the current encoding. * New TextLayout and StandardTextLayout classes to perform text wrapping and alignment. Text widgets now have a layout parameter to allow use of custom TextLayout objects. * New layout structure replaces line translation structure. Layout structure now allows arbitrary reordering/positioning of text segments, inclusion of UTF-8 characters and insertion of text not found in the original text string. * Removed util.register_align_mode() and util.register_wrap_mode(). Their functionality has been replaced by the new layout classes. Urwid 0.8.10 * Expanded tutorial to cover advanced ListBox usage, custom widget classes and the Pile, BoxAdapter, Columns, GridFlow and Overlay classes. * Added escape sequence for "shift tab" to curses_display. * Added ListBox.set_focus_valign() to allow positioning of the focus widget within the ListBox. * Added WidgetWrap class for extending existing widgets without inheriting their complete namespace. * Fixed web_display/mozilla breakage from 0.8.9. Fixed crash on invalid locale setting. Fixed ListBox slide-back bug. Fixed improper space trimming in calculate_alignment(). Fixed browse.py example program rows bug. Fixed sum definition, use of long ints for python2.1. Fixed warnings with python2.1. Fixed Padding.get_pref_col() bug. Fixed Overlay splitting CJK characters bug. Urwid 0.8.9 * New Overlay class for drawing widgets that obscure parts of other widgets. May be used for drop down menus, combo boxes, overlapping "windows", caption text etc. * New BarGraph, GraphVScale and ProgressBar classes for graphical display of data in Urwid applications. * New method for configuring keyboard input timeouts and delays: curses_display.Screen.set_input_timeouts(). * Fixed a ListBox.set_focus() bug. Urwid 0.8.8 * New web_display module that emulates a console display within a web browser window. Application must be run as a CGI script under Apache. Supports font/window resizing, keepalive for long-lived connections, limiting maximum concurrent connections, polling and connected update methods. Tested with Mozilla Firefox and Internet Explorer. * New BoxAdapter class for using box widgets in places that usually expect flow widgets. * New curses_display input handling with better ESC key detection and broader escape code support. * Shortened resize timeout on gradual resize to improve responsiveness. Urwid 0.8.7 * New widget classes: Button, RadioButton, CheckBox. * New layout widget classes: Padding, GridFlow. * New dialog.py example program that behaves like dialog(1) command. * Pile widgets now support selectable items, focus changing with up and down keys and setting the cursor position. * Frame widgets now support selectable items in the header and footer. * Columns widgets now support fixed width and relative width columns, a minimum width for all columns, selectable items within columns containing flow widgets (already supported for box widgets), focus changing with left and right keys and setting the cursor position. * Filler widgets may now wrap box widgets and have more alignment options. * Updated tour.py example program to show new widget types and features. * Avoid hogging cpu on gradual window resize and fix for slow resize with cygwin's broken curses implementation. * Fixed minor CJK problem and curs_set() crash under MacOSX and Cygwin. * Fixed crash when deleting cells in calc.py example program. Urwid 0.8.6 * Improved support for CJK double-byte encodings: BIG5, UHC, GBK, GB2312, CN-GB, EUC-KR, EUC-CN, EUC-JP (JISX 0208 only) and EUC-TW (CNS 11643 plain 1 only) * Added support for ncurses' use_default_colors() function to curses_display module (Python >= 2.4). register_palette() and register_palette_entry() now accept "default" as foreground and/or background. If the terminal's default attributes cannot be detected black on light gray will be used to accomodate terminals with always-black cursors. "default" is now the default for text with no attributes. This means that areas with no attributes will change from light grey on black (curses default) to black on light gray or the terminal's default. * Modified examples to not use black as background of Edit widgets. * Fixed curses_display curs_set() call so that cursor is hidden when widget in focus has no cursor position. Urwid 0.8.5 * New tutorial covering basic operation of: curses_display.Screen, Canvas, Text, FlowWidget, Filler, BoxWidget, AttrWrap, Edit, ListBox and Frame classes * New widget class: Filler * New ListBox functions: get_focus(), set_focus() * Debian packages for Python 2.4. * Fixed curses_display bug affecting text with no attributes. Urwid 0.8.4 * Improved support for Cyrillic and other simple 8-bit encodings. * Added new functions to simplify taking screenshots: html_fragment.screenshot_init() and html_fragment.screenshot_collect() * Improved urwid/curses_display.py input debugging * Fixed cursor in screenshots of CJK text. Fixed "end" key in Edit boxes with CJK text. Urwid 0.8.3 * Added support for CJK double-byte encodings. Word wrapping mode "space" will wrap on edges of double width characters. Wrapping and clipping will not split double width characters. curses_display.Screen.get_input() may now return double width characters. Text and Edit classes will work with a mix of regular and double width characters. * Use new method Edit.set_edit_text() instead of Edit.update_text(). * Minor improvements to edit.py example program. Urwid 0.8.2 * Re-released under GNU Lesser General Public License. Urwid 0.8.1 * Added support for monochrome terminals. see curses_display.Screen.register_palette_entry() and example programs. set TERM=xterm-mono to test programs in monochrome mode. * Added unit testing code test_urwid.py to the examples. * Can now run urwid/curses_display.py to test your terminal's input and colour rendering. * Fixed an OSX browse.py compatibility issue. Added some OSX keycodes. Urwid 0.8.0 * Initial Release urwid-1.1.1/urwid.egg-info/0000775000175000017500000000000012051304275015031 5ustar ianian00000000000000urwid-1.1.1/urwid.egg-info/top_level.txt0000664000175000017500000000000612051304275017557 0ustar ianian00000000000000urwid urwid-1.1.1/urwid.egg-info/PKG-INFO0000664000175000017500000000445512051304275016136 0ustar ianian00000000000000Metadata-Version: 1.1 Name: urwid Version: 1.1.1 Summary: A full-featured console (xterm et al.) user interface library Home-page: http://excess.org/urwid/ Author: Ian Ward Author-email: ian@excess.org License: LGPL Download-URL: http://excess.org/urwid/urwid-1.1.1.tar.gz Description: Urwid is a console user interface library. It includes many features useful for text console application developers including: - Applcations resize quickly and smoothly - Automatic, programmable text alignment and wrapping - Simple markup for setting text attributes within blocks of text - Powerful list box with programmable content for scrolling all widget types - Your choice of event loops: Twisted, Glib or built-in select-based loop - Pre-built widgets include edit boxes, buttons, check boxes and radio buttons - Display modules include raw, curses, and experimental LCD and web displays - Support for UTF-8, simple 8-bit and CJK encodings - 256 and 88 color mode support - Python 3.2 support Home Page: http://excess.org/urwid/ Documentation: http://excess.org/urwid/docs/ Example Program Screenshots: http://excess.org/urwid/examples.html Keywords: curses ui widget scroll listbox user interface text layout console ncurses Platform: unix-like Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: Console :: Curses Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Classifier: Operating System :: POSIX Classifier: Operating System :: Unix Classifier: Operating System :: MacOS :: MacOS X Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Widget Sets Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 urwid-1.1.1/urwid.egg-info/dependency_links.txt0000664000175000017500000000000112051304275021077 0ustar ianian00000000000000 urwid-1.1.1/urwid.egg-info/not-zip-safe0000664000175000017500000000000112041527275017265 0ustar ianian00000000000000 urwid-1.1.1/urwid.egg-info/SOURCES.txt0000664000175000017500000001025512051304275016720 0ustar ianian00000000000000CHANGELOG COPYING MANIFEST.in setup.py docs/Makefile docs/conf.py docs/index.rst docs/urwid-logo.png docs/manual/canvascache.rst docs/manual/displayattributes.rst docs/manual/displaymodules.rst docs/manual/encodings.rst docs/manual/index.rst docs/manual/mainloop.rst docs/manual/overview.rst docs/manual/textlayout.rst docs/manual/userinput.rst docs/manual/wanat.py docs/manual/wanat_multi.py docs/manual/wcur1.py docs/manual/wcur2.py docs/manual/widgets.rst docs/manual/wmod.py docs/manual/wsel.py docs/manual/images/display_modules.png docs/manual/images/introduction.png docs/manual/images/urwid_widgets_1.png docs/manual/images/urwid_widgets_2.png docs/manual/images/widget_layout.png docs/reference/attrspec.rst docs/reference/canvas.rst docs/reference/command_map.rst docs/reference/constants.rst docs/reference/deprecated.rst docs/reference/display_modules.rst docs/reference/exceptions.rst docs/reference/global_settings.rst docs/reference/index.rst docs/reference/list_walkers.rst docs/reference/main_loop.rst docs/reference/meta.rst docs/reference/signals.rst docs/reference/text_layout.rst docs/reference/widget.rst docs/tools/compile_pngs.sh docs/tools/screenshots.sh docs/tools/templates/indexcontent.html docs/tools/templates/indexsidebar.html docs/tools/templates/layout.html docs/tutorial/adventure.py docs/tutorial/adventure.py.xdotool docs/tutorial/adventure1.png docs/tutorial/adventure2.png docs/tutorial/adventure3.png docs/tutorial/adventure4.png docs/tutorial/attr.py docs/tutorial/attr.py.xdotool docs/tutorial/attr1.png docs/tutorial/attr2.png docs/tutorial/attr3.png docs/tutorial/attr4.png docs/tutorial/cmenu.py docs/tutorial/cmenu.py.xdotool docs/tutorial/cmenu1.png docs/tutorial/cmenu2.png docs/tutorial/cmenu3.png docs/tutorial/cmenu4.png docs/tutorial/highcolors.py docs/tutorial/highcolors.py.xdotool docs/tutorial/highcolors1.png docs/tutorial/hmenu.py docs/tutorial/hmenu.py.xdotool docs/tutorial/hmenu1.png docs/tutorial/hmenu2.png docs/tutorial/hmenu3.png docs/tutorial/hmenu4.png docs/tutorial/index.rst docs/tutorial/input.py docs/tutorial/input.py.xdotool docs/tutorial/input1.png docs/tutorial/input2.png docs/tutorial/input3.png docs/tutorial/input4.png docs/tutorial/input5.png docs/tutorial/lbscr.py docs/tutorial/lbscr.py.xdotool docs/tutorial/lbscr1.png docs/tutorial/lbscr2.png docs/tutorial/lbscr3.png docs/tutorial/lbscr4.png docs/tutorial/lbscr5.png docs/tutorial/lbscr6.png docs/tutorial/lbscr7.png docs/tutorial/lbscr8.png docs/tutorial/lbscr9.png docs/tutorial/menu25.png docs/tutorial/minimal.py docs/tutorial/minimal.py.xdotool docs/tutorial/minimal1.png docs/tutorial/multiple.py docs/tutorial/multiple.py.xdotool docs/tutorial/multiple1.png docs/tutorial/multiple2.png docs/tutorial/multiple3.png docs/tutorial/multiple4.png docs/tutorial/qa.py docs/tutorial/qa.py.xdotool docs/tutorial/qa1.png docs/tutorial/qa2.png docs/tutorial/qa3.png docs/tutorial/sig.py docs/tutorial/sig.py.xdotool docs/tutorial/sig1.png docs/tutorial/sig2.png docs/tutorial/sig3.png docs/tutorial/sig4.png docs/tutorial/smenu.py docs/tutorial/smenu.py.xdotool docs/tutorial/smenu1.png docs/tutorial/smenu2.png docs/tutorial/smenu3.png examples/bigtext.py examples/browse.py examples/calc.py examples/dialog.py examples/edit.py examples/fib.py examples/graph.py examples/input_test.py examples/lcd_cf635.py examples/palette_test.py examples/pop_up.py examples/subproc.py examples/subproc2.py examples/terminal.py examples/tour.py examples/treesample.py examples/twisted_serve_ssh.py examples/twisted_serve_ssh.tac source/str_util.c urwid/__init__.py urwid/canvas.py urwid/command_map.py urwid/compat.py urwid/container.py urwid/curses_display.py urwid/decoration.py urwid/display_common.py urwid/escape.py urwid/font.py urwid/graphics.py urwid/html_fragment.py urwid/lcd_display.py urwid/listbox.py urwid/main_loop.py urwid/monitored_list.py urwid/old_str_util.py urwid/raw_display.py urwid/signals.py urwid/split_repr.py urwid/tests.py urwid/text_layout.py urwid/treetools.py urwid/util.py urwid/version.py urwid/vterm.py urwid/vterm_test.py urwid/web_display.py urwid/widget.py urwid/wimp.py urwid.egg-info/PKG-INFO urwid.egg-info/SOURCES.txt urwid.egg-info/dependency_links.txt urwid.egg-info/not-zip-safe urwid.egg-info/top_level.txturwid-1.1.1/source/0000775000175000017500000000000012051304275013505 5ustar ianian00000000000000urwid-1.1.1/source/str_util.c0000664000175000017500000005345112050733643015532 0ustar ianian00000000000000/* Urwid unicode character processing tables Copyright (C) 2006 Rebecca Breu. This file contains rewritten code of utable.py by 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/ */ #define PY_SSIZE_T_CLEAN #include #define ENC_UTF8 1 #define ENC_WIDE 2 #define ENC_NARROW 3 #if PY_MAJOR_VERSION >= 3 #define PYTHON3 #endif #if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION < 5 #define Py_ssize_t int #define FMT_N "i" #else #define FMT_N "n" #endif static int widths_len = 2*38; static const long int widths[] = { 126, 1, 159, 0, 687, 1, 710, 0, 711, 1, 727, 0, 733, 1, 879, 0, 1154, 1, 1161, 0, 4347, 1, 4447, 2, 7467, 1, 7521, 0, 8369, 1, 8426, 0, 9000, 1, 9002, 2, 11021, 1, 12350, 2, 12351, 1, 12438, 2, 12442, 0, 19893, 2, 19967, 1, 55203, 2, 63743, 1, 64106, 2, 65039, 1, 65059, 0, 65131, 2, 65279, 1, 65376, 2, 65500, 1, 65510, 2, 120831, 1, 262141, 2, 1114109, 1 }; static short byte_encoding = ENC_UTF8; static PyObject * to_bool(int val) { if (val) Py_RETURN_TRUE; else Py_RETURN_FALSE; } //====================================================================== static char get_byte_encoding_doc[] = "get_byte_encoding() -> string encoding\n\n\ Get byte encoding ('utf8', 'wide', or 'narrow')."; static PyObject * get_byte_encoding(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple(args, "")) return NULL; if (byte_encoding == ENC_UTF8) return Py_BuildValue("s", "utf8"); if (byte_encoding == ENC_WIDE) return Py_BuildValue("s", "wide"); if (byte_encoding == ENC_NARROW) return Py_BuildValue("s", "narrow"); Py_RETURN_NONE; // should never happen } //====================================================================== static char set_byte_encoding_doc[] = "set_byte_encoding(string encoding) -> None\n\n\ Set byte encoding. \n\n\ encoding -- one of 'utf8', 'wide', 'narrow'"; static PyObject * set_byte_encoding(PyObject *self, PyObject *args) { char * enc; if (!PyArg_ParseTuple(args, "s", &enc)) return NULL; if (strcmp(enc, "utf8") == 0) byte_encoding = ENC_UTF8; else if (strcmp(enc, "wide") == 0) byte_encoding = ENC_WIDE; else if (strcmp(enc, "narrow") == 0) byte_encoding = ENC_NARROW; else { // got wrong encoding PyErr_SetString(PyExc_ValueError, "Unknown encoding."); return NULL; } Py_RETURN_NONE; } //====================================================================== static char get_width_doc[] = "get_width(int ord) -> int width\n\n\ Return the screen column width for unicode ordinal ord.\n\n\ ord -- ordinal"; static int Py_GetWidth(long int ord) { int i; if ((ord == 0xe) || (ord == 0xf)) return 0; for (i=0; i= 0) { if ((text[pos]&0xc0) != 0x80) { Py_DecodeOne(text, text_len, pos, subret); ret[0] = subret[0]; ret[1] = pos-1; return; } pos-=1; if (pos == pos-4) //error { ret[0] = '?'; ret[1] = pos - 1; return; } } } static PyObject * decode_one_right(PyObject *self, PyObject *args) { PyObject *py_text; Py_ssize_t pos, text_len; char *text; Py_ssize_t ret[2] = {'?',0}; if (!PyArg_ParseTuple(args, "O" FMT_N, &py_text, &pos)) return NULL; #ifndef PYTHON3 PyString_AsStringAndSize(py_text, &text, &text_len); #else PyBytes_AsStringAndSize(py_text, &text, &text_len); #endif Py_DecodeOneRight((const unsigned char *)text, text_len, pos, ret); return Py_BuildValue("(" FMT_N ", " FMT_N ")", ret[0], ret[1]); } //====================================================================== static char within_double_byte_doc[] = "within_double_byte(strint text, int line_start, int pos) -> int withindb\n\n\ Return whether pos is within a double-byte encoded character.\n\n\ str -- string in question\n\ line_start -- offset of beginning of line (< pos)\n\ pos -- offset in question\n\n\ Return values:\n\ 0 -- not within dbe char, or double_byte_encoding == False\n\ 1 -- pos is on the 1st half of a dbe char\n\ 2 -- pos is on the 2nd half of a dbe char"; static int Py_WithinDoubleByte(const unsigned char *str, Py_ssize_t line_start, Py_ssize_t pos) { Py_ssize_t i; if ((str[pos] >= 0x40) && (str[pos] < 0x7f)) { //might be second half of big5, uhc or gbk encoding if (pos == line_start) return 0; if (str[pos-1] >= 0x81) { if ((Py_WithinDoubleByte(str, line_start, pos-1)) == 1) return 2; else return 0; } } if (str[pos] < 0x80) return 0; for (i=pos-1; i>=line_start; i--) if (str[i] < 0x80) break; if ((pos-i) & 1) return 1; else return 2; } static PyObject * within_double_byte(PyObject *self, PyObject *args) { const unsigned char *str; Py_ssize_t str_len, line_start, pos; Py_ssize_t ret; if (!PyArg_ParseTuple(args, "s#" FMT_N FMT_N, &str, &str_len, &line_start, &pos)) return NULL; if (line_start < 0 || line_start >= str_len) { PyErr_SetString(PyExc_IndexError, "is_wide_char: Argument \"line_start\" is outside of string."); return NULL; } if (pos < 0 || pos >= str_len) { PyErr_SetString(PyExc_IndexError, "is_wide_char: Argument \"pos\" is outside of string."); return NULL; } if (pos < line_start) { PyErr_SetString(PyExc_IndexError, "is_wide_char: Argument \"pos\" is before \"line_start\"."); return NULL; } ret = Py_WithinDoubleByte(str, line_start, pos); return Py_BuildValue(FMT_N, ret); } //====================================================================== char is_wide_char_doc[] = "is_wide_char(string/unicode text, int offs) -> bool iswide\n\n\ Test if the character at offs within text is wide.\n\n\ text -- string or unicode text\n\ offs -- offset"; static int Py_IsWideChar(PyObject *text, Py_ssize_t offs) { const unsigned char *str; Py_UNICODE *ustr; Py_ssize_t ret[2], str_len; if (PyUnicode_Check(text)) //text_py is unicode string { ustr = PyUnicode_AS_UNICODE(text); return (Py_GetWidth((long int)ustr[offs]) == 2); } #ifndef PYTHON3 if (!PyString_Check(text)) { #else if (!PyBytes_Check(text)) { #endif PyErr_SetString(PyExc_TypeError, "is_wide_char: Argument \"text\" is not a string."); return -1; } #ifndef PYTHON3 str = (const unsigned char *)PyString_AsString(text); str_len = (int) PyString_Size(text); #else str = (const unsigned char *)PyBytes_AsString(text); str_len = (int) PyBytes_Size(text); #endif if (byte_encoding == ENC_UTF8) { Py_DecodeOne(str, str_len, offs, ret); return (Py_GetWidth(ret[0]) == 2); } if (byte_encoding == ENC_WIDE) return (Py_WithinDoubleByte(str, offs, offs) == 1); return 0; } static PyObject * is_wide_char(PyObject *self, PyObject *args) { PyObject *text; Py_ssize_t offs; int ret; if (!PyArg_ParseTuple(args, "O" FMT_N, &text, &offs)) return NULL; ret = Py_IsWideChar(text, offs); if ( ret == -1) // error return NULL; return Py_BuildValue("O", to_bool(ret)); } //====================================================================== char move_prev_char_doc[] = "move_prev_char(string/unicode text, int start_offs, int end_offs) -> int pos\n\n\ Return the position of the character before end_offs.\n\n\ text -- string or unicode text\n\ start_offs -- start offset\n\ end_offs -- end offset"; static Py_ssize_t Py_MovePrevChar(PyObject *text, Py_ssize_t start_offs, Py_ssize_t end_offs) { Py_ssize_t position; unsigned char *str; if (PyUnicode_Check(text)) //text_py is unicode string return end_offs-1; else #ifndef PYTHON3 str = (unsigned char *)PyString_AsString(text); #else str = (unsigned char *)PyBytes_AsString(text); #endif if (byte_encoding == ENC_UTF8) //encoding is utf8 { position = end_offs - 1; while ((str[position]&0xc0) == 0x80) position -=1; return position; } else if ((byte_encoding == ENC_WIDE) && (Py_WithinDoubleByte(str, start_offs, end_offs-1) == 2)) return end_offs-2; else return end_offs-1; } static PyObject * move_prev_char(PyObject *self, PyObject *args) { PyObject *text; Py_ssize_t start_offs, end_offs; Py_ssize_t ret; if (!PyArg_ParseTuple(args, "O" FMT_N FMT_N, &text, &start_offs, &end_offs)) return NULL; ret = Py_MovePrevChar(text, start_offs, end_offs); return Py_BuildValue(FMT_N, ret); } //====================================================================== char move_next_char_doc[] = "move_next_char(string/unicode text, int start_offs, int end_offs) -> int pos\n\n\ Return the position of the character after start_offs.\n\n\ text -- string or unicode text\n\ start_offs -- start offset\n\ end_offs -- end offset"; static Py_ssize_t Py_MoveNextChar(PyObject *text, Py_ssize_t start_offs, Py_ssize_t end_offs) { Py_ssize_t position; unsigned char * str; if (PyUnicode_Check(text)) //text_py is unicode string return start_offs+1; else #ifndef PYTHON3 str = (unsigned char *)PyString_AsString(text); #else str = (unsigned char *)PyBytes_AsString(text); #endif if (byte_encoding == ENC_UTF8) //encoding is utf8 { position = start_offs + 1; while ((position < end_offs) && ((str[position]&0xc0) == 0x80)) position +=1; return position; } else if ((byte_encoding == ENC_WIDE) && (Py_WithinDoubleByte(str, start_offs, start_offs) == 1)) return start_offs+2; else return start_offs+1; } static PyObject * move_next_char(PyObject *self, PyObject *args) { PyObject *text; Py_ssize_t start_offs, end_offs; Py_ssize_t ret; if (!PyArg_ParseTuple(args, "O" FMT_N FMT_N, &text, &start_offs, &end_offs)) return NULL; ret = Py_MoveNextChar(text, start_offs, end_offs); return Py_BuildValue(FMT_N, ret); } //====================================================================== char calc_width_doc[] = "calc_width(string/unicode text, int start_off, int end_offs) -> int width\n\n\ Return the screen column width of text between start_offs and end_offs.\n\n\ text -- string or unicode text\n\ start_offs -- start offset\n\ end_offs -- end offset"; static Py_ssize_t Py_CalcWidth(PyObject *text, Py_ssize_t start_offs, Py_ssize_t end_offs) { unsigned char * str; Py_ssize_t i, ret[2], str_len; int screencols; Py_UNICODE *ustr; if (PyUnicode_Check(text)) //text_py is unicode string { ustr = PyUnicode_AS_UNICODE(text); screencols = 0; for(i=start_offs; i pref_col) { ret[0] = i; ret[1] = screencols; return 0; } screencols += width; } ret[0] = i; ret[1] = screencols; return 0; } #ifndef PYTHON3 if (!PyString_Check(text)) #else if (!PyBytes_Check(text)) #endif { PyErr_SetString(PyExc_TypeError, "Neither unicode nor string."); return -1; } #ifndef PYTHON3 str = (unsigned char *)PyString_AsString(text); str_len = (int) PyString_Size(text); #else str = (unsigned char *)PyBytes_AsString(text); str_len = PyBytes_Size(text); #endif if (byte_encoding == ENC_UTF8) { i = start_offs; screencols = 0; while (i pref_col) { ret[0] = i; ret[1] = screencols; return 0; } i = dummy[1]; screencols += width; } ret[0] = i; ret[1] = screencols; return 0; } // "wide" and "narrow" i = start_offs + pref_col; if (i>= end_offs) { ret[0] = end_offs; ret[1] = end_offs - start_offs; return 0; } if (byte_encoding == ENC_WIDE) if (Py_WithinDoubleByte(str, start_offs, i)==2) i -= 1; ret[0] = i; ret[1] = i - start_offs; return 0; } static PyObject * calc_text_pos(PyObject *self, PyObject *args) { PyObject *text; Py_ssize_t start_offs, end_offs, ret[2]; int pref_col, err; if (!PyArg_ParseTuple(args, "O" FMT_N FMT_N "i", &text, &start_offs, &end_offs, &pref_col)) return NULL; err = Py_CalcTextPos(text, start_offs, end_offs, pref_col, ret); if (err==-1) //an error occured return NULL; return Py_BuildValue("(" FMT_N FMT_N ")", ret[0], ret[1]); } //====================================================================== static PyMethodDef Str_UtilMethods[] = { {"get_byte_encoding", get_byte_encoding, METH_VARARGS, get_byte_encoding_doc}, {"set_byte_encoding", set_byte_encoding, METH_VARARGS, set_byte_encoding_doc}, {"get_width", get_width, METH_VARARGS, get_width_doc}, {"decode_one", decode_one, METH_VARARGS, decode_one_doc}, {"decode_one_right", decode_one_right, METH_VARARGS, decode_one_right_doc}, {"within_double_byte", within_double_byte, METH_VARARGS, within_double_byte_doc}, {"is_wide_char", is_wide_char, METH_VARARGS, is_wide_char_doc}, {"move_prev_char", move_prev_char, METH_VARARGS, move_prev_char_doc}, {"move_next_char", move_next_char, METH_VARARGS, move_next_char_doc}, {"calc_width", calc_width, METH_VARARGS, calc_width_doc}, {"calc_text_pos", calc_text_pos, METH_VARARGS, calc_text_pos_doc}, {NULL, NULL, 0, NULL} // Sentinel }; #ifndef PYTHON3 PyMODINIT_FUNC initstr_util(void) { Py_InitModule("str_util", Str_UtilMethods); } int main(int argc, char *argv[]) { //Pass argv[0] to the Python interpreter: Py_SetProgramName(argv[0]); //Initialize the Python interpreter. Py_Initialize(); //Add a static module: initstr_util(); return 0; } #else static struct PyModuleDef Str_UtilModule = { PyModuleDef_HEAD_INIT, "str_util", NULL, -1, Str_UtilMethods }; PyMODINIT_FUNC PyInit_str_util(void) { return PyModule_Create(&Str_UtilModule); } #endif urwid-1.1.1/docs/0000775000175000017500000000000012051304275013135 5ustar ianian00000000000000urwid-1.1.1/docs/tutorial/0000775000175000017500000000000012051304275015000 5ustar ianian00000000000000urwid-1.1.1/docs/tutorial/qa.py.xdotool0000664000175000017500000000020512051303575017441 0ustar ianian00000000000000windowsize --usehints $RXVTWINDOWID 21 7 type --window $RXVTWINDOWID 'Arthur, King of the Britons' key --window $RXVTWINDOWID Return urwid-1.1.1/docs/tutorial/smenu2.png0000664000175000017500000000173712051303575016731 0ustar ianian00000000000000PNG  IHDRl.bKGD̿ pHYs  ~IDATxr s* dG9m=w%kEՁ~lYP$Fb$Fb$Fb$Fb$FbngVVڲg4H]X71Y_m\l~-`IO$ռ =+ Lq0+ hE,1dt & Vc`۰d7%s{( '3W/1-s( $Fb$Fb$Fb$Fb$Fbskӣã+$>11^+DMafeLF!&J]bb9L-bVXEX2~brGxI[ZD+Ez%Llbe qpUqsl?+ʣv^;}OXo, kWVӖ9-"-q&&&YotҖܖ,)LҖdoL;yh+.m xL[ t22V[hʼnhDŵGx_рӖG°c0dڒzσԜK>ROo""N[ K-2E0#BBlĴej׊ vE Yq|a;]~bbjK2GTFf5c='U_K%-mazP﨨7aG}L1lZy7-*h.8׊$ ze!.xuW[^_mw?bj˶b-acFjKMvYѯ$a0Ֆ91*nZâ"-\+NHV /؝&@0*}@a&ӂ&:arcN_TS„y7!?jӤ ",>m9Z cµhEZHHHHHlpb?M>Npj,IENDB`urwid-1.1.1/docs/tutorial/qa3.png0000664000175000017500000000133712051303575016200 0ustar ianian00000000000000PNG  IHDRiJ}bKGD̿ pHYs  ~IDATx n `AzUX0'=IOzғ'=Q`TMnGޗ"WwoqYPNd/vJ?&*$Q376}&}<ڴrGVE{اP_M=ڧNI/)2ˈb=+W(qu|[H2EW -- aOI>0H鵕/w"D7;Sn^DŚMOggZVs%_77l ;;nE-zT7iz/13nXQG^9zOgWYk;=&X& O4k~}>~yoCMGɀA_߼lpߵ} t\fCK7EoVb{`q }4H9KAs_9p[9E~1IOzғ'=IOߡKMIENDB`urwid-1.1.1/docs/tutorial/lbscr5.png0000664000175000017500000000112312051303575016677 0ustar ianian00000000000000PNG  IHDRimk PLTE9nbKGD- pHYs  ~IDATH͊0ǧ"B/> }cBz } aB^E5ĺM,N&dfXݖdAU.66v/cQQ.r;3k=pōh4UBL30"QDEa1Un+"nU44޴"-eGvݧe#_3C6mEZ]7FjoݜN-RHGL z6YMRHk,bE8KxuihOHS9 V@<^s]eiKHD!ݠ+rTRYbyАBDD !h z $Nr RYHbehL!,ME] oz^2R 2Őbs# 08b C1pI{z3hIA'^:qܫ=z_"v+;_Uf~,?.PrmsiB_ƃsJאxs3|a˳Wah|$N5lxƞϘeWNgΞeS _z$s~uVskcp}s2Fy_'9{Wi}읡׿MhTdN32_7n/<|A/[N=zѣG}$Ϙ'ѣG=zѣG=zѣG=zѣG=z?FL#H/[8}k,[4/+#Nk:~9}kj㎽yۿ[U5*5!j-:=zѣG=z?5ĬIENDB`urwid-1.1.1/docs/tutorial/sig4.png0000664000175000017500000000164412051303575016363 0ustar ianian00000000000000PNG  IHDRiJ}bKGD̿ pHYs  ~HIDATx D=j6OpRI cxrh@O~&omP[NGSS^2nFW:YŭdY (z6j9{ {άjoy'~*~m]>s=z4Gm9kﺸ `ZGs9Krl,Lܝ+^tpviƙh R v~}pr0DTr27^O_Y`\_`=藢EL:#ϓp}_ h nj<0݆ {h2vKOm BG rWoWNkm G&!UJ o'')B9EOGV&P$#G!_.]=p\12W|=.)(TcJxG7<I֖ia GEN= {0AJDFn`bw"(Uxr}8䡤ފss<E1yOlO-TPbgw`G97Dx3`c,(#1#1#1#1#1QcWV֖XqebR oۅ Y2oDbۉՅ7cwZ1XbALH.Xq>`Y3 +>%|cV 'gQb-k(oר08ǚ' ]cu#w<ͩg1AI) Q߁Xm%U຀i cb9]aQ+YĔ؀0D{V̂_eߍ(֣bwpQ塟7tqe]em2*BMpl)_=6 -Sq4^43>k + [YǖR[. ;bbKO 7-$F+~׊ZlL4/-S6[& -@yLR6[PK t-Яrf[ZӋޱebKe[ qe]e-V,.-JG0!&7g22%%U@ì% |*,r4bh[Ta"8]&MimscA-$F+~׊َʵCa`E"1G0<E-;ac3e1rmQ!L"aa"Ă6)?V{a-$F+Ҋ$Fb$Fb$Fb$Fb$Fb/'P)?Q4 IENDB`urwid-1.1.1/docs/tutorial/lbscr9.png0000664000175000017500000000131212051303575016703 0ustar ianian00000000000000PNG  IHDRcIG PLTEFbKGD- pHYs  ~WIDATX嗽0e/`q+kq' z J).U |#rYΞ<3Bf"f U Ul9M8HsqF) 65D[#S 8xSGL[oO`($HOOp<kR&b@" %S$RĪədG"*'}9 SIzmF82! W$?2gI;a; E $둫!&r׷ʖ -lB"C&.j0c[kͽUu> OPLDLT6VaDz*X3Y e=2$GU` $ L> .6,AUpcQ* $I+}ྷeGATT%OoK>Tԏïϗ衜=z+7|rФʑϖxnit,U?jul5GN\ps\\9:#B*OT+e3s0gwfK; ^8BgB/,{R"k+1͒DU(WC,kqB6_Oϊϊ4 FG ZRIENDB`urwid-1.1.1/docs/tutorial/hmenu2.png0000664000175000017500000000301012051303575016700 0ustar ianian00000000000000PNG  IHDRsPLTEMMM===EEE --'''888"""222(((+++DDڂ滻oo㧧珏>>>  //Q*bKGD L pHYs  ~IDATx݉rFali6ML?_eI^`IFb5Ɠ$I$I$I$I$Isn.@ 4@ 4@ 4hhhZ@ 4@ 4@ 4@ h7 cp ǜ1eFu=%@ t55% p Fgd E3QőG+9&܏朻'@<ۮcuwD}R'hLt`-* i?֏Y躛9;]rtR|ɑ@/4}g tz섖K yt7x @2qЖ ̵ӠFfm'7^23hE˲^ (˜\ōhRMؖ &@'rah@QЫ]hhh4@ 4@ 4@ $I$@ h4h-@ h4Z@-@ hdMZQe($@?v|OS33躾k{}~/~~V(>=3A?0|2@/yh6vG/~D ==sFFn4x=[xm7]sv#;O)>ܽ=^G o n:0* G޽|4o}ֶF:fN닪[+dczWZ YG}4?4ozt>.ˈAK_vתz1ʠ[4dKIQ6_7?=' @Ǧ"F(7U@>F+| ]=5tķNfGzD^ܨUJaL4a/*G=y#]zy/,hI㻀s?z@yx 4k}N^2 D | i?><~#(@_Vzh/63׼{\FPpNnAGwex7 5۳pݢ@@m*YCp4+Yn  4+К)ѫhm*4h h4h @ h4h-@ h4Z@-@ hZ@-@ h Z@-h Z@ 4h Z@ 4h Z@ h4h @ h4h-$I$I$I$I$IVIENDB`urwid-1.1.1/docs/tutorial/input.py0000664000175000017500000000040512051303575016512 0ustar ianian00000000000000import urwid def show_or_exit(key): if key in ('q', 'Q'): raise urwid.ExitMainLoop() txt.set_text(repr(key)) txt = urwid.Text(u"Hello World") fill = urwid.Filler(txt, 'top') loop = urwid.MainLoop(fill, unhandled_input=show_or_exit) loop.run() urwid-1.1.1/docs/tutorial/hmenu1.png0000664000175000017500000000230112051303575016701 0ustar ianian00000000000000PNG  IHDRsPLTE --DDڂ滻oo㧧珏 /////㨨ppp===OOOoooeee...bKGDf |d pHYs  ~IDATxys@aU}_;&,' >96ӝ7LL$I$I$I$I$IS#͙h Z@-4h Z@ h4h @kC[G.Zw@w,Z}(7;-Z޶\F}+ ,vqfN&.C<ڽ8:5znk[x К*n :@k(q5cwU~$^=@5si@G,@ h4h-@ h4Z@-@K$ITG3 4h Z@ h4h @ h4huV$ YK Gu|C[k|:D@fa/FhGx\_B<4'W!n5ϩ-Z}ɍСܮ==rR'ȑ|N/nn)!ۡ[ORЭmt[Ń@k_=>yvwqxZϞŋ@k_fk/GGg1w:eнW'BFјR:46lW9t0 .,-_4Z6wfQyh &]>CH)#3 t7 &v~wq6uIACy3Кgߺt@W޾y7Кt¬-09꛴ZKuZj|hyֺܰ@w3ϙzeu4;?n@k3 4h Z@ h4h @ h4h-@ h4Z@-@ hZ@-@ h Z@-h Z@-4h Z@ h4h @ h4h-@ h4Z$I$I$I$I$I9?dϹIENDB`urwid-1.1.1/docs/tutorial/highcolors1.png0000664000175000017500000000047612051303575017741 0ustar ianian00000000000000PNG  IHDRPLTE____lN pHYs  ~IDATh @ #JyU&kzNBbBDDDDDDv*****auD~wqUX銩[mSnr?UU^S>~2R5,+\ǵݻSM izQ ]~ܔ(#a{V*M X1IENDB`urwid-1.1.1/docs/tutorial/lbscr7.png0000664000175000017500000000131612051303575016705 0ustar ianian00000000000000PNG  IHDRM PLTEFbKGD- pHYs  ~[IDATX혱0G^9F  D.;`OHS],5f^;rpaJlggY+(f/0BӴTE!wйHLE.i 62)HH;Z,SMt*2%wajiV* h HIdwC:2צkP"IDNqE F E M9 E bZi3=Bo` !`CӅu}gȆ,ݯw[#如O ", kEUL;GDW)%ž7Ԥ{vETq: 08ItWǞO;8Y98KɥcoO/ٱoph_=lJc9VǖcjvHEf9,,DzvkWPv=ĥc}q\q[ׯNr~ҿӯmӶW?ߞC)Y"MݶeWlݒjH|99^x u#NG9{G?r}mYNݲ~y5 ,k[ѱ>zkW} cD)IENDB`urwid-1.1.1/docs/tutorial/attr.py0000664000175000017500000000070512051303575016330 0ustar ianian00000000000000import urwid def exit_on_q(key): if key in ('q', 'Q'): raise urwid.ExitMainLoop() palette = [ ('banner', 'black', 'light gray'), ('streak', 'black', 'dark red'), ('bg', 'black', 'dark blue'),] txt = urwid.Text(('banner', u" Hello World "), align='center') map1 = urwid.AttrMap(txt, 'streak') fill = urwid.Filler(map1) map2 = urwid.AttrMap(fill, 'bg') loop = urwid.MainLoop(map2, palette, unhandled_input=exit_on_q) loop.run() urwid-1.1.1/docs/tutorial/cmenu4.png0000664000175000017500000000261212051303575016704 0ustar ianian00000000000000PNG  IHDR)3i0bKGD̿ pHYs  ~.IDATx aܣ3l6/mwg:L8WAlY lM @̍ťBjm)O_m %_C )GǩGE )CJ/#R|7$T'"~)\ŏhZv^Ns}H,}dTZ*_"q{FړPDK>aY8:R!oK|TR?_bﵬTb")z)T9TRPJWpPZ3ԺPܡ@jk!Y.RH!ԤR;t5N|5e 2`.R;tf×Hr ;tqȆR;絲) cC♸B&ȵf)".%F(}#aĞS.A#,)RFC-5CJYq)ipgs%8u)3ʑ^*sYr&yݑu zRIm!+%H[e{(m)uP\P&` o2ÈYq,ao3` W3^F )ZH 񭾻 F/U3gLu͛1l(U/˕s{fY;4 7KͨBvC_gf)b )B=R*GTno]45XY\1[lbw){>#2hJmqŤaBHҲDa?:S")K{;4KRT­ceuSjF% )E nR%c"uY ;-%9Ked̺TIu|#;K. T$e3=n8?%iD=Kʽ\ RH-)e˺l$e˺ T$eʺ$e% iX`jc )rRP\ʹ=)z>JJdU*Ŀ*;R?X WSHIU*ȼRʏDGj9ɲT۽B$ R`o*c} TG@sJg o&竩2g\dBjBu6d]֔K@jk!Y6*' JyE1Ri;4RH!RH/hy|B\B{M}%8w ԈC#RH!YȺ(%<'$e`PbC GP39IENDB`urwid-1.1.1/docs/tutorial/input1.png0000664000175000017500000000035112051303575016727 0ustar ianian00000000000000PNG  IHDRl-n bKGD̿ pHYs  ~IDATXK Eѻ}~( Ll8$(:y_ϥu҉L}Q>cq~Q5XA^֫ۍ{.:< &L0a„ &LXIu!vIENDB`urwid-1.1.1/docs/tutorial/minimal1.png0000664000175000017500000000045312051303575017221 0ustar ianian00000000000000PNG  IHDRiJ}bKGD̿ pHYs  ~IDATxA {z. R44]Äd4#z$d6+='LwjP+YW?fҩT)qZL~3f6kPuqgoũHԚIENDB`urwid-1.1.1/docs/tutorial/multiple.py0000664000175000017500000000172512051303575017214 0ustar ianian00000000000000import urwid def question(): return urwid.Pile([urwid.Edit(('I say', u"What is your name?\n"))]) def answer(name): return urwid.Text(('I say', u"Nice to meet you, " + name + "\n")) class ConversationListBox(urwid.ListBox): def __init__(self): body = urwid.SimpleFocusListWalker([question()]) super(ConversationListBox, self).__init__(body) def keypress(self, size, key): key = super(ConversationListBox, self).keypress(size, key) if key != 'enter': return key name = self.focus[0].edit_text if not name: raise urwid.ExitMainLoop() # replace or add response self.focus.contents[1:] = [(answer(name), self.focus.options())] pos = self.focus_position # add a new question self.body.insert(pos + 1, question()) self.focus_position = pos + 1 palette = [('I say', 'default,bold', 'default'),] urwid.MainLoop(ConversationListBox(), palette).run() urwid-1.1.1/docs/tutorial/multiple3.png0000664000175000017500000000204012051303575017422 0ustar ianian00000000000000PNG  IHDR(bKGD̿ pHYs  ~IDATxr Eu{rf,@76bf  l#X^Iv}+;H:~7K~yD, # |6~1 <\g;3.Ŏ*9*>n,O6%*=O;H>k/ƏjHRr Wy|[V3i|'gš|ݝs.o2s"$uVx-_'< b9}̑=Oy+\?C?Uq+1TIz^ꎟ U/Q97b~\rJ#[|nxxx}MFy$kT+ x/b x<޿0D ݥ '~!,]/T yB _P D/$'`1YMcxmYZjEVK% _xxxApա_@~B1'_@7 7_t^n04xxxygpIENDB`urwid-1.1.1/docs/tutorial/lbscr2.png0000664000175000017500000000140112051303575016673 0ustar ianian00000000000000PNG  IHDRimk PLTEFbKGD- pHYs  ~IDATH͊@+/G>`d= h$/!]OhPGe!,-b $c+TAAL르BƋdd؝Һr24*8#Dͭ;5W5`]-XMHDA @ HOh,MW"<D3DXHI71(KK E0#_q/t!eD^`0H6_ _ɡ ?3g]^hmh;"ƌh!g:eqɥDo1:ՆQ}"E|L aE~>uE5BN|3^\ffP480Ѹ!f@͐g-}nAZ]7w]"LG[]X]ܪխ^1&Ȏv,jk\b`M"q(frzAQrF>/A~߮0WЗIENDB`urwid-1.1.1/docs/tutorial/cmenu2.png0000664000175000017500000000230612051303575016702 0ustar ianian00000000000000PNG  IHDR)3i0bKGD̿ pHYs  ~jIDATxkқ0 QG|i&l yLG gl/N*b[7\,.RPkKyB}Zl Z)-,ܴB{Zy +RH!Ryy&jE$RYڏXάYҕ>IT^*_"i]sG{!L'K6)O[ϓ K]QB|T\p8TR'y(YR.q*`H!]H!eꤧSRH9CV`Rk6.L@R! 7ϊRH!5v]|߅eS9 "5%Hi/PJJ<ERᗓ~,3Hݿa9 kaT]>|\JԥdV}^q跏e:]:ii Lwh6{MUȈgoFRθu(bTC_-|+ϸ)E]>&ZgA HeI,7KŤ. Jk%H9K.F)~)g)溘TH!RH=Q(EQ7Ky쳢c$՛N;K@ .kS"RH=Ezkbe*b^,)TִRKXLjtRuGc&RYRFDB)RH!SH]ԥ E+Eꢤ.eeLjҶ(c] (uI]X^~B e.^_RN ^ K]^_ uF K!  R f RH!,RZYK,Z uF KA`-K`+.H!RHqvkd .1Rh k)4,Y@.nƺ RH"٭U%H.Rgаd )R?,l%s.IENDB`urwid-1.1.1/docs/tutorial/cmenu.py0000664000175000017500000000437212051303575016471 0ustar ianian00000000000000import urwid def menu_button(caption, callback): button = urwid.Button(caption) urwid.connect_signal(button, 'click', callback) return urwid.AttrMap(button, None, focus_map='reversed') def sub_menu(caption, choices): contents = menu(caption, choices) def open_menu(button): return top.open_box(contents) return menu_button([caption, u'...'], open_menu) def menu(title, choices): body = [urwid.Text(title), urwid.Divider()] body.extend(choices) return urwid.ListBox(urwid.SimpleFocusListWalker(body)) def item_chosen(button): response = urwid.Text([u'You chose ', button.label, u'\n']) done = menu_button(u'Ok', exit_program) top.open_box(urwid.Filler(urwid.Pile([response, done]))) def exit_program(button): raise urwid.ExitMainLoop() menu_top = menu(u'Main Menu', [ sub_menu(u'Applications', [ sub_menu(u'Accessories', [ menu_button(u'Text Editor', item_chosen), menu_button(u'Terminal', item_chosen), ]), ]), sub_menu(u'System', [ sub_menu(u'Preferences', [ menu_button(u'Appearance', item_chosen), ]), menu_button(u'Lock Screen', item_chosen), ]), ]) class CascadingBoxes(urwid.WidgetPlaceholder): max_box_levels = 4 def __init__(self, box): super(CascadingBoxes, self).__init__(urwid.SolidFill(u'/')) self.box_level = 0 self.open_box(box) def open_box(self, box): self.original_widget = urwid.Overlay(urwid.LineBox(box), self.original_widget, align='center', width=('relative', 80), valign='middle', height=('relative', 80), min_width=24, min_height=8, left=self.box_level * 3, right=(self.max_box_levels - self.box_level - 1) * 3, top=self.box_level * 2, bottom=(self.max_box_levels - self.box_level - 1) * 2) self.box_level += 1 def keypress(self, size, key): if key == 'esc' and self.box_level > 1: self.original_widget = self.original_widget[0] self.box_level -= 1 else: return super(CascadingBoxes, self).keypress(size, key) top = CascadingBoxes(menu_top) urwid.MainLoop(top, palette=[('reversed', 'standout', '')]).run() urwid-1.1.1/docs/tutorial/hmenu4.png0000664000175000017500000000314512051303575016713 0ustar ianian00000000000000PNG  IHDRs]PLTEMMM===EEE'''888"""222(((+++>>>  --DDڂ //0bKGD pHYs  ~IDATx܉vF@сn6uuK̊@a;9 d'X A$I$I$I$I$Ip h66@ͷm66@ͷm66@ͷmt,%|,@t%{X=7A9:tr@o:~ ]ߒ3}7E,~F8tClah>L6d\@hSt|5a EЧ{ցN5.#`29 Cr1ߡ(;XPAG]:bt)}tQ@wz9jNzX;&8nv7D}c/O/aH>I}~@g?9[/} m 0ͷm hm@o6@ͷm hm@ 46Z@-@ hZ7 z+]k@Ëh@{C/R6WJ:}(EF96e^lh]~]%a/p_̺j\vY֠eC=4A@ٵw&AKS( @+]5@[bkrgAg,-k:kf,@ h4'y}nɈvW =bw~mqa7xt__&c<;Ӹ˱/:d'~1١<׻M,,o*;wOC~ymhrI.:S>_zUr̂g5Y7Vy@9tk.<,v80+nC'|9Q.7L+yk@,GXy$Zt(Η'x!9JhT@ h4ڠߊf:PfO﭅o{~,?/ڧwП?Y ֠;s_ЌSzy<cfry>:^^ӗw?>}E-xht"K=<}LWЗ>t彦ߏ}..}=vq? :>mdcе)>)CfN[xYMnz4h h4h @ h4h-@ h4Z@-@ hZ@-@ h Z@-h Z@ 4h Z@ 4h Z@ h4h @ h4h-$I$I$I$I$I?_!C=IENDB`urwid-1.1.1/docs/tutorial/cmenu3.png0000664000175000017500000000274612051303575016713 0ustar ianian00000000000000PNG  IHDR)3i0bKGD̿ pHYs  ~IDATxa w{=fhP[[WPÍͥ(E)o<>lLڳ+m,ܵ(E)jyj»RR;5@qkI\'uAοD;K*ղvR?O=/ʒ]3RYwHp ) !RAS[yRRwDO%DJJeRL]Ԅw(u?(eͩ JQX) Jj]XaRSR~xJQRzVRps)@L$K[жuƑup3l 4kaILZ!#iJ .>ҙP}%})KHIfDzRb6Rї/hI*v .pShSr9d 𜧂Rf=^J9,Pnov[Fߊe05cw)E)JQjS.RL]0u鐚lNIB!R2A\(E)J=\j ؠT5 ؠT5, ؠ]?XJQRzn.sQo0K6("E KYA)l ~.axIENDB`urwid-1.1.1/docs/tutorial/minimal.py.xdotool0000664000175000017500000000005112051303575020465 0ustar ianian00000000000000windowsize --usehints $RXVTWINDOWID 21 7 urwid-1.1.1/docs/tutorial/sig.py.xdotool0000664000175000017500000000023012051303575017620 0ustar ianian00000000000000windowsize --usehints $RXVTWINDOWID 21 7 type --window $RXVTWINDOWID 'Tim t' type --window $RXVTWINDOWID 'he Enchanter' key --window $RXVTWINDOWID Down urwid-1.1.1/docs/tutorial/qa.py0000664000175000017500000000101112051303575015746 0ustar ianian00000000000000import urwid def exit_on_q(key): if key in ('q', 'Q'): raise urwid.ExitMainLoop() class QuestionBox(urwid.Filler): def keypress(self, size, key): if key != 'enter': return super(QuestionBox, self).keypress(size, key) self.original_widget = urwid.Text( u"Nice to meet you,\n%s.\n\nPress Q to exit." % edit.edit_text) edit = urwid.Edit(u"What is your name?\n") fill = QuestionBox(edit) loop = urwid.MainLoop(fill, unhandled_input=exit_on_q) loop.run() urwid-1.1.1/docs/tutorial/lbscr3.png0000664000175000017500000000123212051303575016676 0ustar ianian00000000000000PNG  IHDRimk PLTEFbKGD- pHYs  ~'IDATH͊@KCC\}ҧ}@!w}i觐>5* Rg,(˟Kf{ G/ #2H<ja'\i1ƂS ADJΆ<-ͩ=RWr"5DyHDͩ4b>u_^ {E/+b^18bpG ŰİP&:"}͠_ bP+b/ݷGƐq|] " y11ߎwM{Ey"mkc#/-r?,z x9Хfb,Ҹ`>m!OpZ/IENDB`urwid-1.1.1/docs/tutorial/minimal.py0000664000175000017500000000016612051303575017005 0ustar ianian00000000000000import urwid txt = urwid.Text(u"Hello World") fill = urwid.Filler(txt, 'top') loop = urwid.MainLoop(fill) loop.run() urwid-1.1.1/docs/tutorial/index.rst0000664000175000017500000003750512051303575016655 0ustar ianian00000000000000.. _urwid-tutorial: ****************** Urwid Tutorial ****************** .. currentmodule:: urwid Minimal Application ------------------- .. image:: minimal1.png This program displays the string ``Hello World`` in the top left corner of the screen and will run until interrupted with *CTRL+C* (*^C*). .. literalinclude:: minimal.py :linenos: * The *txt* :class:`Text` widget handles formatting blocks of text, wrapping to the next line when necessary. Widgets like this are called "flow widgets" because their sizing can have a number of columns given, in this case the full screen width, then they will flow to fill as many rows as necessary. * The *fill* :class:`Filler` widget fills in blank lines above or below flow widgets so that they can be displayed in a fixed number of rows. This Filler will align our Text to the top of the screen, filling all the rows below with blank lines. Widgets which are given both the number of columns and number of rows they must be displayed in are called "box widgets". * The :class:`MainLoop` class handles displaying our widgets as well as accepting input from the user. The widget passed to :class:`MainLoop` is called the "topmost" widget. The topmost widget is used to render the whole screen and so it must be a box widget. In this case our widgets can't handle any user input so we need to interrupt the program to exit with *^C*. Global Input ------------ .. image:: input1.png .. image:: input2.png .. image:: input3.png .. image:: input4.png .. image:: input5.png This program initially displays the string ``Hello World``, then it displays each key pressed, exiting when the user presses *Q*. .. literalinclude:: input.py :linenos: * The :class:`MainLoop` class has an optional function parameter *unhandled_input*. This function will be called once for each keypress that is not handled by the widgets being displayed. Since none of the widgets being displayed here handle input, every key the user presses will be passed to the *show_or_exit* function. * The :exc:`ExitMainLoop` exception is used to exit cleanly from the :meth:`MainLoop.run` function when the user presses *Q*. All other input is displayed by replacing the current Text widget's content. Display Attributes ------------------ .. image:: attr1.png .. image:: attr2.png .. image:: attr3.png .. image:: attr4.png This program displays the string ``Hello World`` in the center of the screen. It uses different attributes for the text, the space on either side of the text and the space above and below the text. It waits for a keypress before exiting. The screenshots above show how these widgets react to being resized. .. literalinclude:: attr.py :linenos: * Display attributes are defined as part of a palette. Valid foreground, background and setting values are documented in :ref:`foreground-background` A palette is a list of tuples containing: 1. Name of the display attribute, typically a string 2. Foreground color and settings for 16-color (normal) mode 3. Background color for normal mode 4. Settings for monochrome mode (optional) 5. Foreground color and settings for 88 and 256-color modes (optional, see next example) 6. Background color for 88 and 256-color modes (optional) * A :class:`Text` widget is created containing the string ``" Hello World "`` with display attribute ``'banner'``. The attributes of text in a Text widget is set by using a (*attribute*, *text*) tuple instead of a simple text string. Display attributes will flow with the text, and multiple display attributes may be specified by combining tuples into a list. This format is called :ref:`text-markup`. * An :class:`AttrMap` widget is created to wrap the text widget with display attribute ``'streak'``. :class:`AttrMap` widgets allow you to map any display attribute to any other display attribute, but by default they will set the display attribute of everything that does not already have a display attribute. In this case the text has an attribute, so only the areas around the text used for alignment will be have the new attribute. * A second :class:`AttrMap` widget is created to wrap the :class:`Filler` widget with attribute ``'bg'``. When this program is run you can now clearly see the separation of the text, the alignment around the text, and the filler above and below the text. .. seealso:: :ref:`using-display-attributes` High Color Modes ---------------- .. image:: highcolors1.png This program displays the string ``Hello World`` in the center of the screen. It uses a number of 256-color-mode colors to decorate the text, and will work in any terminal that supports 256-color mode. It will exit when *Q* is pressed. .. literalinclude:: highcolors.py :linenos: This palette only defines values for the high color foregroundand backgrounds, because only the high colors will be used. A real application should define values for all the modes in their palette. Valid foreground, background and setting values are documented in :ref:`foreground-background`. * Behind the scenes our :class:`MainLoop` class has created a :class:`raw_display.Screen` object for drawing the screen. The program is put into 256-color mode by using the screen object's :meth:`set_terminal_properties() ` method. This example also demonstrates how you can build the widgets to display in a top-down order instead of the usual bottom-up order. In some places we need to use a *placeholder* widget because we must provide a widget before the correct one has been created. * We change the topmost widget used by the :class:`MainLoop` by 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. * :class:`Divider` widgets are used to create blank lines, colored with :class:`AttrMap`. * :ref:`container-widgets` like :class:`Pile` have a ``contents`` property that we can treat like a list of (*widget*, *options*) tuples. :attr:`Pile.contents` supports normal list operations including ``append()`` to add child widgets. :meth:`Pile.options` is used to generate the default options for the new child widgets. Question and Answer ------------------- .. image:: qa1.png .. image:: qa2.png .. image:: qa3.png This program asks for your name then responds ``Nice to meet you, (your name).`` .. literalinclude:: qa.py :linenos: The :class:`Edit` widget is based on the :class:`Text` widget but it accepts keyboard input for entering text, making corrections and moving the cursor around with the *HOME*, *END* and arrow keys. Here we are customizing the :class:`Filler` decoration widget that is holding our :class:`Edit` widget by subclassing it and defining a new ``keypress()`` method. Customizing decoration or container widgets to handle input this way is a common pattern in Urwid applications. This pattern is easier to maintain and extend than handling all special input in an *unhandled_input* function. * In *QuestionBox.keypress()* all keypresses except *ENTER* are passed along to the default :meth:`Filler.keypress` which sends them to the child :meth:`Edit.keypress` method. * Note that names containing *Q* can be entered into the :class:`Edit` widget without causing the program to exit because :meth:`Edit.keypress` indicates that it has handled the key by returning ``None``. See :meth:`Widget.keypress` for more information. * When *ENTER* is pressed the child widget ``original_widget`` is changed to a :class:`Text` widget. * :class:`Text` widgets don't handle any keyboard input so all input ends up in the *unhandled_input* function *exit_on_q*, allowing the user to exit the program. Signal Handlers --------------- .. image:: sig1.png .. image:: sig2.png .. image:: sig3.png .. image:: sig4.png This program asks for your name and responds ``Nice to meet you, (your name)`` *while* you type your name. Press *DOWN* then *SPACE* or *ENTER* to exit. .. literalinclude:: sig.py :linenos: * An :class:`Edit` widget and a :class:`Text` reply widget are created, like in the previous example. * The :func:`connect_signal` function is used to attach our *on_ask_change()* function to our :class:`Edit` widget's ``'change'`` signal. Now any time the content of the :class:`Edit` widget changes *on_ask_change()* will be called and passed the new content. * Finally we attach our *on_exit_clicked()* function to our exit :class:`Button`'s ``'click'`` signal. * *on_ask_change()* updates the reply text as the user enters their name and *on_exit_click()* exits. Multiple Questions ------------------ .. image:: multiple1.png .. image:: multiple2.png .. image:: multiple3.png .. image:: multiple4.png This program asks for your name and responds ``Nice to meet you, (your name).`` It then asks again, and again. Old values may be changed and the responses will be updated when you press *ENTER*. *ENTER* on a blank line exits. .. literalinclude:: multiple.py :linenos: :class:`ListBox` widgets let you scroll through a number of flow widgets vertically. It handles *UP*, *DOWN*, *PAGE UP* and *PAGE DOWN* keystrokes and changing the focus for you. :ref:`listbox-contents` are managed by a "list walker", one of the list walkers that is easiest to use is :class:`SimpleFocusListWalker`. :class:`SimpleFocusListWalker` is like a normal python list of widgets, but any time you insert or remove widgets the focus position is updated automatically. Here we are customizing our :class:`ListBox`'s keypress handling by overriding it in a subclass. * The *question()* function is used to build widgets to communicate with the user. Here we return a :class:`Pile` widget with a single :class:`Edit` widget to start. * We retrieve the name entered with :attr:`ListBox.focus` to get the :class:`Pile` in focus, the standard :ref:`container widget ` method ``[0]`` to get the first child of the pile and :attr:`Edit.edit_text` to get the user-entered text. * For the response we use the fact that we can treat :attr:`Pile.contents` like a list of (*widget*, *options*) tuples to create or replace any existing response by assigning a one-tuple list to *contents[1:]*. We create the default options using :meth:`Pile.options`. * To add another question after the current one we treat our :class:`SimpleFocusListWalker` stored as :attr:`ListBox.body` like a normal list of widgets by calling *insert()*, then update the focus position to the widget we just created. Simple Menu ----------- .. image:: smenu1.png .. image:: smenu2.png .. image:: smenu3.png We can create a very simple menu using a list of :class:`Button` widgets. This program lets you choose an option then repeats what you chose. .. literalinclude:: smenu.py :linenos: * *menu()* builds a :class:`ListBox` with a *title* and a sequence of :class:`Button` widgets. Each button has its ``'click'`` signal attached to *item_chosen*, with item name is passed as data. The buttons are decorated with an :class:`AttrMap` that applies a display attribute when a button is in focus. * *item_chosen()* replaces the menu displayed with text indicating the users' choice. * *exit_program()* causes the program to exit on any keystroke. * The menu is created and decorated with an :class:`Overlay` using a :class:`SolidFill` as the background. The :class:`Overlay` is given a miniumum width and height but is allowed to expand to 60% of the available space if the user's terminal window is large enough. Cascading Menu -------------- .. image:: cmenu1.png .. image:: cmenu2.png .. image:: cmenu3.png .. image:: cmenu4.png A nested menu effect can be created by having some buttons open new menus. This program lets you choose an option from a nested menu that cascades across the screen. You may return to previous menus by pressing *ESC*. .. literalinclude:: cmenu.py :linenos: * *menu_button()* returns an :class:`AttrMap`-decorated :class:`Button` and attaches a *callback* to the the 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 menu when that button is clicked. Notice that :ref:`text markup ` is used to add ``'...'`` to the end of the *caption* passed to *menu_button()*. * *menu()* builds a :class:`ListBox` with a *title* and a sequence of widgets. * *item_chosen()* displays the users' choice similar to the previous example. * *menu_top* is the top level menu with all of its child menus and options built using the functions above. This example introduces :class:`WidgetPlaceholder`. :class:`WidgetPlaceholder` is a :ref:`decoration widget ` that does nothing to the widget it decorates. It is useful if you need a simple way to replace a widget that doesn't involve knowing its position in a :ref:`container `, or in this case as a base class for a widget that will be replacing its own contents regularly. * *CascadingBoxes* is a new widget that extends :class:`WidgetPlaceholder`. It provides an *open_box()* method that displays a box widget *box* "on top of" all the previous content with an :class:`Overlay` and a :class:`LineBox`. The position of each successive box is shifted right and down from the previous one. * *CascadingBoxes.keypress()* intercepts *ESC* keys to cause the current box to be removed and the previous one to be shown. This allows the user to return to a previous menu level. Horizontal Menu --------------- .. image:: hmenu1.png .. image:: hmenu2.png .. image:: hmenu3.png .. image:: hmenu4.png This example is like the previous but new menus appear on the right and push old menus off the left side of the screen. The look of buttons and other menu elements are heavily customized and new widget classes are used instead of factory functions. .. literalinclude:: hmenu.py :linenos: * *MenuButton* is a customized :class:`Button` widget. :class:`Button` uses :class:`WidgetWrap` to create its appearance and this class replaces the display widget created by :class:`Button` by the wrapped widget in *self._w*. * *SubMenu* is implemented with a *MenuButton* but uses :class:`WidgetWrap` to hide the implementation instead of inheriting from *MenuButton*. The constructor builds a widget for the menu that this button will open and stores it in *self.menu*. * *Choice* is like *SubMenu* but displays the item chosen instead of another menu. The *palette* used in this example includes an entry with the special name ``None``. The foreground and background specified in this entry are used as a default when no other display attribute is specified. * *HorizontalBoxes* arranges the menus displayed similar to the previous example. There is no special handling required for going to previous menus here because :class:`Columns` already handles switching focus when *LEFT* or *RIGHT* is pressed. :class:`AttrMap` with the *focus_map* dict is used to change the appearance of a number of the display attributes when a menu is in focus. Adventure Game -------------- .. image:: adventure1.png .. image:: adventure2.png .. image:: adventure3.png .. image:: adventure4.png We can use the same sort of code to build a simple adventure game. Instead of menus we have "places" and instead of submenus and parent menus we just have "exits". This example scrolls previous places off the top of the screen, allowing you to scroll back to view but not interact with previous places. .. literalinclude:: adventure.py :linenos: This example starts to show some separation between the application logic and the widgets that have been created. The *AdventureGame* class is responsible for all the changes that happen through the game and manages the topmost widget, but isn't a widget itself. This is a good pattern to follow as your application grows larger. urwid-1.1.1/docs/tutorial/hmenu3.png0000664000175000017500000000351512051303575016713 0ustar ianian00000000000000PNG  IHDRs{PLTEMMM===EEE///'''888"""222(((+++ppp>>> ... --DDڂ //<bKGD L pHYs  ~kIDATx݋v6aQ.ݺۺu¥qIH''lv$I$I$I$I$ISA{>; 4@ 4@ 4@ 4@ 4@ 4@ 4@ 4@ 4@ 4@ 4@W{^ w:lk[nۧQT9u#kwz3t>cg=g 0;G{7:q)ȠYCGF}dte -s +{z 0f|3tDqyV G9٣՞@U;xA#h>C @ 4@ 4@ 4@ 4@ 4@ 4@ -I$Iϫ+@ h4Z@-@ hZ@-@ h tToǬT;x1: 讛A@7sR;~*U_/fU;~s/w芮ߢ;IdLr?Ѡx~?qatڋWد3ADo֘KEʲ Y/j3tN@gQt2o/ za;5sS~0|K,Хn6<]Cz ǻ޼ =Nuibt_^Zm'5ztt(WkqFcԥ:o +geX^b\te ]fNQ? ,GCafщnr| YTtqcЕ];ʑ/P[m(G39L18 !\m 4@Ak4sӎSȾwCۇ ,o'гSA F̘~̍Y1ASC Y[tԞ.FMRe+ӈ|ʉ*wAn{m;~ chLtAgWb'dgt >r"lۿ=>)䤅hA_DPAϻ+k@GF.g9wѷ]]C.W.N0>u4[CgYYx-CY|tc_b0tIn:Ԓ#/лjy03s:v~z·@ h4h@ h4Z@-@ hZ@-@ h Z@-h Z@-4h Z@ h4h 4@ h4h 4@ h4h-@ h4Z@-@ hZ$I$I$I$I$I?QvIENDB`urwid-1.1.1/docs/tutorial/highcolors.py0000664000175000017500000000170612051303575017521 0ustar ianian00000000000000import urwid def exit_on_q(key): if key in ('q', 'Q'): raise urwid.ExitMainLoop() palette = [ ('banner', '', '', '', '#ffa', '#60d'), ('streak', '', '', '', 'g50', '#60a'), ('inside', '', '', '', 'g38', '#808'), ('outside', '', '', '', 'g27', '#a06'), ('bg', '', '', '', 'g7', '#d06'),] placeholder = urwid.SolidFill() loop = urwid.MainLoop(placeholder, palette, unhandled_input=exit_on_q) loop.screen.set_terminal_properties(colors=256) loop.widget = urwid.AttrMap(placeholder, 'bg') loop.widget.original_widget = urwid.Filler(urwid.Pile([])) div = urwid.Divider() outside = urwid.AttrMap(div, 'outside') inside = urwid.AttrMap(div, 'inside') txt = urwid.Text(('banner', u" Hello World "), align='center') streak = urwid.AttrMap(txt, 'streak') pile = loop.widget.base_widget # .base_widget skips the decorations for item in [outside, inside, streak, inside, outside]: pile.contents.append((item, pile.options())) loop.run() urwid-1.1.1/docs/tutorial/multiple.py.xdotool0000664000175000017500000000026312051303575020677 0ustar ianian00000000000000windowsize --usehints $RXVTWINDOWID 23 13 key --window $RXVTWINDOWID A b e Return B o b key --window $RXVTWINDOWID Return C a r l Return key --window $RXVTWINDOWID D a v e Return urwid-1.1.1/docs/tutorial/hmenu.py0000664000175000017500000000525712051303575016501 0ustar ianian00000000000000import urwid class MenuButton(urwid.Button): def __init__(self, caption, callback): super(MenuButton, self).__init__("") urwid.connect_signal(self, 'click', callback) self._w = urwid.AttrMap(urwid.SelectableIcon( [u' \N{BULLET} ', caption], 2), None, 'selected') class SubMenu(urwid.WidgetWrap): def __init__(self, caption, choices): super(SubMenu, self).__init__(MenuButton( [caption, u"\N{HORIZONTAL ELLIPSIS}"], self.open_menu)) line = urwid.Divider(u'\N{LOWER ONE QUARTER BLOCK}') listbox = urwid.ListBox(urwid.SimpleFocusListWalker([ urwid.AttrMap(urwid.Text([u"\n ", caption]), 'heading'), urwid.AttrMap(line, 'line'), urwid.Divider()] + choices + [urwid.Divider()])) self.menu = urwid.AttrMap(listbox, 'options') def open_menu(self, button): top.open_box(self.menu) class Choice(urwid.WidgetWrap): def __init__(self, caption): super(Choice, self).__init__( MenuButton(caption, self.item_chosen)) self.caption = caption def item_chosen(self, button): response = urwid.Text([u' You chose ', self.caption, u'\n']) done = MenuButton(u'Ok', exit_program) response_box = urwid.Filler(urwid.Pile([response, done])) top.open_box(urwid.AttrMap(response_box, 'options')) def exit_program(key): raise urwid.ExitMainLoop() menu_top = SubMenu(u'Main Menu', [ SubMenu(u'Applications', [ SubMenu(u'Accessories', [ Choice(u'Text Editor'), Choice(u'Terminal'), ]), ]), SubMenu(u'System', [ SubMenu(u'Preferences', [ Choice(u'Appearance'), ]), Choice(u'Lock Screen'), ]), ]) palette = [ (None, 'light gray', 'black'), ('heading', 'black', 'light gray'), ('line', 'black', 'light gray'), ('options', 'dark gray', 'black'), ('focus heading', 'white', 'dark red'), ('focus line', 'black', 'dark red'), ('focus options', 'black', 'light gray'), ('selected', 'white', 'dark blue')] focus_map = { 'heading': 'focus heading', 'options': 'focus options', 'line': 'focus line'} class HorizontalBoxes(urwid.Columns): def __init__(self): super(HorizontalBoxes, self).__init__([], dividechars=1) def open_box(self, box): if self.contents: del self.contents[self.focus_position + 1:] self.contents.append((urwid.AttrMap(box, 'options', focus_map), self.options('given', 24))) self.focus_position = len(self.contents) - 1 top = HorizontalBoxes() top.open_box(menu_top.menu) urwid.MainLoop(urwid.Filler(top, 'middle', 10), palette).run() urwid-1.1.1/docs/tutorial/lbscr.py.xdotool0000664000175000017500000000050312051303575020146 0ustar ianian00000000000000windowsize --usehints $RXVTWINDOWID 15 7 key --window $RXVTWINDOWID Down key --window $RXVTWINDOWID Down key --window $RXVTWINDOWID Down key --window $RXVTWINDOWID Up key --window $RXVTWINDOWID Down windowsize --usehints $RXVTWINDOWID 20 9 windowsize --usehints $RXVTWINDOWID 25 7 windowsize --usehints $RXVTWINDOWID 11 13 urwid-1.1.1/docs/tutorial/smenu.py.xdotool0000664000175000017500000000016612051303575020175 0ustar ianian00000000000000windowsize --usehints $RXVTWINDOWID 24 11 key --window $RXVTWINDOWID Down Down Down key --window $RXVTWINDOWID Return urwid-1.1.1/docs/tutorial/sig3.png0000664000175000017500000000163712051303575016364 0ustar ianian00000000000000PNG  IHDRiJ}bKGD̿ pHYs  ~CIDATx E}L{-8bTz-p\Ao/xSZN=Gi'bhOi:YͲʬ:(YlTsF)N)8άj_yv*ܶ,=ZXĽ69,tz]joq#g^o-,W梸ή=qy8 DJ'r/[6XL;;G?@2S~:EObK$EgL#ϓ=N}&i}UAYV)@MGSLd喞%=ڭg\뵳Iz|Oq8 qv\GnY-?EEW1gڧ苾苾苾苾苾}b[M]m|CM6zYђx3 yz\RP|wQ!]^H2_USr o=yMrV?ܝ| &HU0ND+qNojy}4ފss<E1yw|O-TPzdgn#F1bĈ[,oV;'+G$F1bĈ#Fk1UTEbĈ#F1bSEU$F1bĈ#Fq1U첊1zp]M;lM_#vϱ׎Wsb]v`,~gbg}L]1~`K:wU,`y4bBV#.~I,gtZcg5b-x4*X\B답W@LU1bĈ#FŶƶjUl?*>7?ٳ! ȋEh!Zb`yib`./YU,/f5z0ɪنXֿe s,O/yewl=1bĈ#F,H1bĈ#FXbĈ#F1bĈ5.VH>%F1bĈ#Fbve(=IENDB`urwid-1.1.1/docs/tutorial/sig.py0000664000175000017500000000113712051303575016140 0ustar ianian00000000000000import urwid palette = [('I say', 'default,bold', 'default', 'bold'),] ask = urwid.Edit(('I say', u"What is your name?\n")) reply = urwid.Text(u"") button = urwid.Button(u'Exit') div = urwid.Divider() pile = urwid.Pile([ask, div, reply, div, button]) top = urwid.Filler(pile, valign='top') def on_ask_change(edit, new_edit_text): reply.set_text(('I say', u"Nice to meet you, %s" % new_edit_text)) def on_exit_clicked(button): raise urwid.ExitMainLoop() urwid.connect_signal(ask, 'change', on_ask_change) urwid.connect_signal(button, 'click', on_exit_clicked) urwid.MainLoop(top, palette).run() urwid-1.1.1/docs/tutorial/smenu.py0000664000175000017500000000206012051303575016501 0ustar ianian00000000000000import urwid choices = u'Chapman Cleese Gilliam Idle Jones Palin'.split() def menu(title, choices): body = [urwid.Text(title), urwid.Divider()] for c in choices: button = urwid.Button(c) urwid.connect_signal(button, 'click', item_chosen, c) body.append(urwid.AttrMap(button, None, focus_map='reversed')) return urwid.ListBox(urwid.SimpleFocusListWalker(body)) def item_chosen(button, choice): response = urwid.Text([u'You chose ', choice, u'\n']) done = urwid.Button(u'Ok') urwid.connect_signal(done, 'click', exit_program) main.original_widget = urwid.Filler(urwid.Pile([response, urwid.AttrMap(done, None, focus_map='reversed')])) def exit_program(button): raise urwid.ExitMainLoop() main = urwid.Padding(menu(u'Pythons', choices), left=2, right=2) top = urwid.Overlay(main, urwid.SolidFill(u'\N{MEDIUM SHADE}'), align='center', width=('relative', 60), valign='middle', height=('relative', 60), min_width=20, min_height=9) urwid.MainLoop(top, palette=[('reversed', 'standout', '')]).run() urwid-1.1.1/docs/tutorial/multiple1.png0000664000175000017500000000076712051303575017436 0ustar ianian00000000000000PNG  IHDR(bKGD̿ pHYs  ~IDATx 0й=& bA!KHja:BL:n) pמNyH=gQ3DG'Eh1w:ױ)-$2I.^Zyu:OVR'm6V\Lzyr̷ޱ|#qlozkl3g)XSX[?BIWo{Y?ӑt2}EFUɉt ό+rm ?g_߃so?+9`ݷIENDB`urwid-1.1.1/docs/tutorial/input5.png0000664000175000017500000000024412051303575016734 0ustar ianian00000000000000PNG  IHDRl-n bKGD̿ pHYs  ~HIDATX1  -!$1ю-1f*oرDz9m ` `0 `Ol-XvIENDB`urwid-1.1.1/docs/tutorial/lbscr1.png0000664000175000017500000000116312051303575016677 0ustar ianian00000000000000PNG  IHDRimk PLTEJd pHYs  ~IDATHj0 a%!On>CO%O1+Ό;P mh/e~Z-҃~,hKt$gchg I.m'YZ{B a!!" E 5ꘋ$háFϵp‹/BDL6Az7ӗl@6K 1ʀF?)Z98$@?(YtVDHo H8fAYDӡZHG/p(4(k3z)вskOH{n#(+@4z7 |o]d :Ea!77C6#b,)dJ:$e3n\R[D.p3p: g_>g d"ӴC^2$tG^i4] x&di%U\=* kH+Hvh*DUPֺ /] SW7CW5C^KfP8 GHb7>i|;CIENDB`urwid-1.1.1/docs/tutorial/sig2.png0000664000175000017500000000133012051303575016351 0ustar ianian00000000000000PNG  IHDRiJ}bKGD̿ pHYs  ~|IDATx n =& I(!P\lzUdX;46ʰG { ]WopmYUֶszF=vGlWЕ)]݅GɸYnu^֖1\5,YG>͖ t}musEύ-6߫梸\={U#tw&}pڒ~aWr7=d>KPpv]C:5TzٚЄOғ zd9G7MVHOzғ'=IOz!-b[n}*?oS9F KgEA#G&$C #xf->d,DN QmՈ,`/}hU5J_0 UZ߻S‘I*UIr @8$ 5d 2s"ccI2TPғ~=6n!=IOzғ'=IOzғvx$Fy}G: {xSڙ'KM>E1y}y W.(uUxsHKIENDB`urwid-1.1.1/docs/tutorial/multiple2.png0000664000175000017500000000150012051303575017421 0ustar ianian00000000000000PNG  IHDR(bKGD̿ pHYs  ~IDATxn w{r&mf@VM>]6~$pi}O}Ftz> `\\]3*Һ:OuY'Ǟg'^RGjȣܘAy>efO}"}%sƺ{[ކ̂O~*^ioUpd?{ƓT8s֛=ؖhw[w6 ]eLf W5<_smp?{syW-Vk>vsr5@OxܨAOXJ`9Vי4jV?(xld?VJRj)}OoSV4fN0wSt?jO &y_O٩%׿)] hɈu <鹭l.ܮ]M}ΉX/~YN{c /{T := x:|Li==y|܃Y!}x^YgǏȣh?bD~fOy^0X0}\. މ %CvP,xLAo(lV۩GN9}h SJ[vbxCx,& [Y]{yb.貫{+oN }AkBvxPC3{WO /[`ji9<׾0'ݾ0'D_7*k5g a=Ri^ȇf(5!'оpgg:O}glP. m&J-͉_ZűmR+!Аz>RO!| CCH #04]ÁSxegUܸ.¹v<1]89 /N_!|b~م~gFT%E<'ۘR5<2OБơȲJ?ń9ZuQdD`91UAI㱔N]Ǔ9. ףnGz~ a:ycQ:tΝC~IENDB`urwid-1.1.1/docs/tutorial/lbscr4.png0000664000175000017500000000113012051303575016674 0ustar ianian00000000000000PNG  IHDRimk PLTEFbKGD- pHYs  ~IDATHj@GBB.cT5s]A'T)ž}@_unM)ڌA6? 0KŸ"'A(UTAʼ,-b"K@afG vnPAGZֻ 7DyDۭO{-uu@juAQG0\+H ^s\"J'7}"Zi}'X dDQ2QRledyH ^ ,]H4JQ/Dݟ7x/kfX3P- !ft46rf261[_x39̠ keX3Hsf >J y3ļmۄ᷉N26y~m;.W,yywM{QK iEXYw J!ȫG6.G\#Y:' 6=hAN UBgx2&Dz/IENDB`urwid-1.1.1/docs/tutorial/lbscr6.png0000664000175000017500000000113012051303575016676 0ustar ianian00000000000000PNG  IHDRimk PLTEFbKGD- pHYs  ~IDATHj@GBB.cT5s]A'T)ž}@_unM)ڌA6? 0KŸ"'A(UTAʼ,-b"K@afG vnPAGZֻ 7DyDۭO{-uu@juAQG0\+H ^s\"J'7}"Zi}'X dDQ2QRledyH ^ ,]H4JQ/Dݟ7x/kfX3P- !ft46rf261[_x39̠ keX3Hsf >J y3ļmۄ᷉N26y~m;.W,yywM{QK iEXYw J!ȫG6.G\#Y:' 6=hAN UBgx2&Dz/IENDB`urwid-1.1.1/docs/tutorial/lbscr8.png0000664000175000017500000000130612051303575016705 0ustar ianian00000000000000PNG  IHDRi>\ PLTEFbKGD- pHYs  ~SIDATXAk0Ǔ)EzO {x}=?I*^t:Kv81 cPL>Y⟘v]jRW6'ap89@gJd<8nqbf\)%:V\kCx6:)Mx<(!II- ^C4M6钱ILI+[W͓Fi,)D2y&3jk@[T0jSTq_Uʥ,Ǖ䑝ؖElYY] YT\۪(,K!DZH>VeU}KӦ<,Ȳʢm]xL><2 .Ϭ(ȒԷm@v:V{eR't]VQu\Fqt\Fv2Oetr2ڼZ]&-+]FJsey㲂L.8Q] Q⺌. 1;:xHk]F]o_Mg+6|O.F>wm}k=:j; mnHJ>[rdöJA-MgUV.yڎRϘoGꇂnVIENDB`urwid-1.1.1/docs/tutorial/input4.png0000664000175000017500000000024412051303575016733 0ustar ianian00000000000000PNG  IHDRl-n bKGD̿ pHYs  ~HIDATX1 @QOACԠ?>l,ڱ%Ƭr ;X30َ+`0 `o`x߽¾IENDB`urwid-1.1.1/docs/tutorial/cmenu1.png0000664000175000017500000000221612051303575016701 0ustar ianian00000000000000PNG  IHDR)3i0bKGD̿ pHYs  ~2IDATx[ D>GmtSBDLTx4tMkR\jRH[j$͚Ԟ]iRRHZ|rxWB 51f{Lb+cR?EFg vSz6N5b,Ɵm_,_fgHye F{.an_"ԥ/TS+[P]^{;B=)W*mtK*{K]ǏyfB/R4%u^`d]qs\+|͍cuG%emiW&/βz<%?L0 T38G.Dn}T0tMY0ץvE/!xћ$g D )H!iK3 \.~%_czbe<3}k͖Z!+D) 5XFf, #Qu3H bܧ<)B iR.YRH]D)KRd H "uǻ"RH!5ME"K@j(%l6m PjWK.yRH!p)ڝ 'KCB#t.AAq-Rb.V0QRH!*RZY,A-BC))] `46B U R)4YZ #R%RZhFmH!RHqwkd .>Rh @G KP t)ьڐB )V*H]|hd jTJAHR h!RH"ݭU%H.)4,A-ҥ@ F3jC  ;yIENDB`urwid-1.1.1/docs/tutorial/attr2.png0000664000175000017500000000037012051303575016544 0ustar ianian00000000000000PNG  IHDRZf` PLTE<% pHYs  ~IDATH1 ahXp@@aD4 W 1&˲>ڂsY_p~Rq+Įx'pc%'p>7D9?4w9,q*ޞ_neY_gv.$hhIENDB`urwid-1.1.1/docs/tutorial/adventure.py.xdotool0000664000175000017500000000023712051303575021042 0ustar ianian00000000000000windowsize --usehints $RXVTWINDOWID 23 16 key --window $RXVTWINDOWID Return Down Down key --window $RXVTWINDOWID Return Down key --window $RXVTWINDOWID Return urwid-1.1.1/docs/tutorial/attr.py.xdotool0000664000175000017500000000024412051303575020015 0ustar ianian00000000000000windowsize --usehints $RXVTWINDOWID 21 7 windowsize --usehints $RXVTWINDOWID 10 9 windowsize --usehints $RXVTWINDOWID 30 3 windowsize --usehints $RXVTWINDOWID 15 2 urwid-1.1.1/docs/tutorial/adventure.py0000664000175000017500000000500712051303575017353 0ustar ianian00000000000000import 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() urwid-1.1.1/docs/tutorial/input2.png0000664000175000017500000000032212051303575016726 0ustar ianian00000000000000PNG  IHDRl-n bKGD̿ pHYs  ~vIDATXA @w{{DaS"3 FaFn cj cϱ"Jjy<\ƙ|`J{,yg:8LLo`ޯJQ˭:"eIENDB`urwid-1.1.1/docs/tutorial/adventure2.png0000664000175000017500000000226412051303575017573 0ustar ianian00000000000000PNG  IHDRobKGD̿ pHYs  ~XIDATxm Ԭ-$Yi" knd%WoNQͭGL<ף=b#!DGzrYɹ>s|}UIMC8=y@Fv೺:BM>6کӞGx'fsU&݁ŽLd{C2?R住zz/8#dJαy;N@LT19z,z#ps*'xl~k@F[ƊTdĤ*vZ?%8!5L+uNTJ7EJz\VaG{-'ylTbUuT']U'~sEY摠&>,xޛI:*v  # yWxՎσ_~erv<>Q>9UJ;=GnFLC?sQ,L,mGyCC5P~?lűm<^78 *!yCLvA0bC0^,.{p.op}.϶ 7AgB 'KqB *[~~KvAeO񬸿3BE0U{/=6t pHYs  ~IDAT81 0 P_ z3ZIR4j?a!R`+B( k[Ek'dc*IH27  KHbMːxKsYl%:rt[7|!QV2M_GV6RzPo$IbTٰIENDB`urwid-1.1.1/docs/tutorial/qa1.png0000664000175000017500000000061412051303575016173 0ustar ianian00000000000000PNG  IHDRiJ}bKGD̿ pHYs  ~0IDATx w{(:uO~&[ =zѣG=zѣG=zѣS(rѼ>rjt,#}2e.i)~5lg,ݭA_ n2Uc]J}->jB/;*fQ9#}˕eȽ^[Z:Ѥ^5'%9{1$ގ% R6ѓ_ _{Ҳz;*@_-ؔ{G=zѣ_sE4zѣG=zѣG=zѣG=zѣGv*IENDB`urwid-1.1.1/docs/tutorial/lbscr.py0000664000175000017500000000174412051303575016467 0ustar ianian00000000000000import urwid def show_all_input(keys, raw): """make keys pressed visible to the user""" show_key.set_text(u"Pressed: " + u" ".join([ unicode(k) for k in keys])) return keys def exit_on_cr(key): if key == 'enter': raise urwid.ExitMainLoop() palette = [('header', 'white', 'black'), ('reveal focus', 'black', 'dark cyan', 'standout'),] div = urwid.Divider(u"-") content = urwid.SimpleListWalker([ urwid.AttrMap(w, None, 'reveal focus') for w in [ urwid.Text(u"This is a text string that is fairly long"), urwid.Divider(u"-"),] + [ urwid.Text(u"Numbers %d" % i) for i in range(40)] + [ urwid.Divider(u"-"), urwid.Text(u"The end."),]]) listbox = urwid.ListBox(content) show_key = urwid.Text(u"", wrap='clip') head = urwid.AttrMap(show_key, 'header') top = urwid.Frame(listbox, head) loop = urwid.MainLoop(top, palette, input_filter=show_all_input, unhandled_input=exit_on_cr) loop.run() urwid-1.1.1/docs/tutorial/cmenu.py.xdotool0000664000175000017500000000022512051303575020151 0ustar ianian00000000000000windowsize --usehints $RXVTWINDOWID 33 14 key --window $RXVTWINDOWID Return key --window $RXVTWINDOWID Return Down key --window $RXVTWINDOWID Return urwid-1.1.1/docs/tutorial/multiple4.png0000664000175000017500000000204312051303575017426 0ustar ianian00000000000000PNG  IHDR(bKGD̿ pHYs  ~IDATx r Eu{rNb'634%D5ڎ~m`>o0Q^+1T^Gxf{ķ<x^^*6ѣc w3'))t<'(2>Zb/,p|.7+.y^5)}x<nC>y4eQ+&y:\Y_0&~4/y ] q< d/ȗq(~u˓APEg5ل˳~-ԘMV~<x<xx_Ds/@ "&BBߠ_y8le&JSxg/=agi_I_W[fg?"!n\@=SO=SO=SO=SO=SO=/#"|#;}yA]yOP+궒$ԅ30L۪W)7#뷈q;!mU!wYz>E3)GCj =e kL1Xm5 /o JP^]wስ u fSWTi\?bqcwrvPinݿɂ(Nk/EzN_Ϯ/8]zsc{zge[6Z׉fV S~!fnG動n8*3_3r~,׹j+.VsS9潅zꩧzꩧzꩧzꩧzꩧ+: RIENDB`urwid-1.1.1/docs/tutorial/menu25.png0000664000175000017500000000055612051303575016631 0ustar ianian00000000000000PNG  IHDRxp%NbKGD̿ pHYs  ~IDATxQ0w{&*UDBN- ,)x+~7-{Pu<5G4TPN~N e~6OSrYԥ,OWIHڻO3ZOz9#x,:E[bM!m"dY:`6V]~ jk٣Y S@&c |Mbn$DOCGNo)n-@ɇ QɔIENDB`urwid-1.1.1/docs/reference/0000775000175000017500000000000012051304275015073 5ustar ianian00000000000000urwid-1.1.1/docs/reference/deprecated.rst0000664000175000017500000000031412051303575017725 0ustar ianian00000000000000Deprecated Classes ------------------ .. currentmodule:: urwid .. autoclass:: FlowWidget .. autoclass:: BoxWidget .. autoclass:: FixedWidget .. autoclass:: AttrWrap .. autoclass:: PollingListWalker urwid-1.1.1/docs/reference/attrspec.rst0000664000175000017500000000014112051303575017450 0ustar ianian00000000000000Raw Display Attributes ====================== .. currentmodule:: urwid .. autoclass:: AttrSpec urwid-1.1.1/docs/reference/list_walkers.rst0000664000175000017500000000054412051303575020335 0ustar ianian00000000000000List Walker Classes =================== .. currentmodule:: urwid ListWalker ---------- .. autoclass:: ListWalker List-like List Walkers ---------------------- .. autoclass:: SimpleFocusListWalker .. autoclass:: SimpleListWalker TreeWalker and Nodes -------------------- .. autoclass:: TreeWalker .. autoclass:: TreeNode .. autoclass:: ParentNode urwid-1.1.1/docs/reference/global_settings.rst0000664000175000017500000000024412051303575021007 0ustar ianian00000000000000Global Settings =============== .. currentmodule:: urwid .. autofunction:: set_encoding .. autofunction:: get_encoding_mode .. autofunction:: supports_unicode urwid-1.1.1/docs/reference/signals.rst0000664000175000017500000000113612051303575017270 0ustar ianian00000000000000Signal Functions ================ .. currentmodule:: urwid The :func:`urwid.\*_signal` functions use a shared Signals object instance for tracking registered and connected signals. There is no reason to instantiate your own Signals object. .. function:: connect_signal(obj, name, callback, user_arg=None) .. automethod:: Signals.connect .. function:: disconnect_signal(obj, name, callback, user_arg=None) .. automethod:: Signals.disconnect .. function:: register_signal(sig_cls, signals) .. automethod:: Signals.register .. function:: emit_signal(obj, name, \*args) .. automethod:: Signals.emit urwid-1.1.1/docs/reference/index.rst0000664000175000017500000000042212051303575016734 0ustar ianian00000000000000.. _urwid-reference: ############### Urwid Reference ############### .. toctree:: main_loop widget display_modules list_walkers signals global_settings attrspec canvas text_layout command_map constants exceptions meta deprecated urwid-1.1.1/docs/reference/constants.rst0000664000175000017500000001616012051303575017647 0ustar ianian00000000000000Constants ========= .. currentmodule:: urwid .. note:: These constants may be used, but using the string values themselves in your program is equally supported. These constants are used internally by urwid just to avoid possible misspelling, but the example programs and tutorials tend to use the string values. Widget Sizing Methods --------------------- One or more of these values returned by :meth:`Widget.sizing` to indicate supported sizing methods. .. data:: FLOW :annotation: = 'flow' Widget that is given a number of columns by its parent widget and calculates the number of rows it requires for rendering e.g. :class:`Text` .. data:: BOX :annotation: = 'box' Widget that is given a number of columns and rows by its parent widget and must render that size e.g. :class:`ListBox` .. data:: FIXED :annotation: = 'fixed' Widget that knows the number of columns and rows it requires and will only render at that exact size e.g. :class:`BigText` Horizontal Alignment -------------------- Used to horizontally align text in :class:`Text` widgets and child widgets of :class:`Padding` and :class:`Overlay`. .. data:: LEFT :annotation: = 'left' .. data:: CENTER :annotation: = 'center' .. data:: RIGHT :annotation: = 'right' Veritcal Alignment ------------------ Used to vertically align child widgets of :class:`Filler` and :class:`Overlay`. .. data:: TOP :annotation: = 'top' .. data:: MIDDLE :annotation: = 'middle' .. data:: BOTTOM :annotation: = 'bottom' Width and Height Settings ------------------------- Used to distribute or set widths and heights of child widgets of :class:`Padding`, :class:`Filler`, :class:`Columns`, :class:`Pile` and :class:`Overlay`. .. data:: PACK :annotation: = 'pack' Ask the child widget to calculate the number of columns or rows it needs .. data:: GIVEN :annotation: = 'given' A set number of columns or rows, e.g. ('given', 10) will have exactly 10 columns or rows given to the child widget .. data:: RELATIVE :annotation: = 'relative' A percentage of the total space, e.g. ('relative', 50) will give half of the total columns or rows to the child widget .. data:: RELATIVE_100 :annotation: = ('relative', 100) .. data:: WEIGHT :annotation: = 'weight' A weight value for distributing columns or rows, e.g. ('weight', 3) will give 3 times as many columns or rows as another widget in the same container with ('weight', 1). Text Wrapping Modes ------------------- .. data:: SPACE :annotation: = 'space' wrap text on space characters or at the boundaries of wide characters .. data:: ANY :annotation: = 'any' wrap before any wide or narrow character that would exceed the available screen columns .. data:: CLIP :annotation: = 'clip' clip before any wide or narrow character that would exceed the available screen columns ad don't display the remaining text on the line Foreground and Background Colors -------------------------------- Standard background and foreground colors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. data:: BLACK :annotation: = 'black' .. data:: DARK_RED :annotation: = 'dark red' .. data:: DARK_GREEN :annotation: = 'dark green' .. data:: BROWN :annotation: = 'brown' .. data:: DARK_BLUE :annotation: = 'dark blue' .. data:: DARK_MAGENTA :annotation: = 'dark magenta' .. data:: DARK_CYAN :annotation: = 'dark cyan' .. data:: LIGHT_GRAY :annotation: = 'light gray' Standard foreground colors (not safe to use as background) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. data:: DARK_GRAY :annotation: = 'dark gray' .. data:: LIGHT_RED :annotation: = 'light red' .. data:: LIGHT_GREEN :annotation: = 'light green' .. data:: YELLOW :annotation: = 'yellow' .. data:: LIGHT_BLUE :annotation: = 'light blue' .. data:: LIGHT_MAGENTA :annotation: = 'light magenta' .. data:: LIGHT_CYAN :annotation: = 'light cyan' .. data:: WHITE :annotation: = 'white' User's terminal configuration default foreground or background ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. note:: There is no way to tell if the user's terminal has a light or dark color as their default foreground or background, so it is highly recommended to use this setting for both foreground and background when you do use it. .. data:: DEFAULT :annotation: = 'default' 256 and 88 Color Foregrounds and Backgrounds ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Constants are not defined for these colors. .. seealso:: :ref:`high-colors` Signal Names ------------ .. data:: UPDATE_PALETTE_ENTRY :annotation: = 'update palette entry' sent by :class:`BaseScreen` (and subclasses like :class:`raw_display.Screen`) when a palette entry is changed. :class:`MainLoop` handles this signal by redrawing the whole screen. .. data:: INPUT_DESCRIPTORS_CHANGED :annotation: = 'input descriptors changed' sent by :class:`BaseScreen` (and subclasses like :class:`raw_display.Screen`) when the list of input file descriptors has changed. :class:`MainLoop` handles this signal by updating the file descriptors being watched by its event loop. Command Names ------------- Command names are used as values in :class:`CommandMap` instances. Widgets look up the command associated with keypresses in their :meth:`Widget.keypress` methods. You may define any new command names as you wish and look for them in your own widget code. These are the standard ones expected by code included in Urwid. .. data:: REDRAW_SCREEN :annotation: = 'redraw screen' Default associated keypress: 'ctrl l' :meth:`MainLoop.process_input` looks for this command to force a screen refresh. This is useful in case the screen becomes corrupted. .. data:: ACTIVATE :annotation: = 'activate' Default associated keypresses: ' ' (space), 'enter' Activate a widget such as a :class:`Button`, :class:`CheckBox` or :class:`RadioButton`. .. data:: CURSOR_UP :annotation: = 'cursor up' Default associated keypress: 'up' Move the cursor or selection up one row. .. data:: CURSOR_DOWN :annotation: = 'cursor down' Default associated keypress: 'down' Move the cursor or selection down one row. .. data:: CURSOR_LEFT :annotation: = 'cursor left' Default associated keypress: 'left' Move the cursor or selection left one column. .. data:: CURSOR_RIGHT :annotation: = 'cursor right' Default associated keypress: 'right' Move the cursor or selection right one column. .. data:: CURSOR_PAGE_UP :annotation: = 'cursor page up' Default associated keypress: 'page up' Move the cursor or selection up one page. .. data:: CURSOR_PAGE_DOWN :annotation: = 'cursor page down' Default associated keypress: 'page down' Move the cursor or selection down one page. .. data:: CURSOR_MAX_LEFT :annotation: = 'cursor max left' Default associated keypress: 'home' Move the cursor or selection to the leftmost column. .. data:: CURSOR_MAX_RIGHT :annotation: = 'cursor max right' Default associated keypress: 'end' Move the cursor or selection to the rightmost column. urwid-1.1.1/docs/reference/canvas.rst0000664000175000017500000000070512051303575017104 0ustar ianian00000000000000Canvas Classes and Functions ============================ .. currentmodule:: urwid Canvas Classes -------------- .. autoclass:: Canvas .. autoclass:: TextCanvas .. autoclass:: BlankCanvas .. autoclass:: SolidCanvas .. autoclass:: CompositeCanvas CompositeCanvas Builders ------------------------ .. autofunction:: CanvasCombine .. autofunction:: CanvasJoin .. autofunction:: CanvasOverlay CanvasCache ----------- .. autoclass:: CanvasCache urwid-1.1.1/docs/reference/widget.rst0000664000175000017500000000376712051303575017127 0ustar ianian00000000000000.. _widget-classes: Widget Classes ============== .. currentmodule:: urwid Widget Base Classes ------------------- Widget ~~~~~~ .. autoclass:: Widget :private-members: _invalidate, _emit WidgetWrap ~~~~~~~~~~ .. autoclass:: WidgetWrap WidgetDecoration ~~~~~~~~~~~~~~~~ .. autoclass:: WidgetDecoration WidgetContainerMixin ~~~~~~~~~~~~~~~~~~~~ .. autoclass:: WidgetContainerMixin Basic Widget Classes -------------------- Text ~~~~ .. autoclass:: Text Edit ~~~~ .. autoclass:: Edit IntEdit ~~~~~~~ .. autoclass:: IntEdit Button ~~~~~~ .. autoclass:: Button CheckBox ~~~~~~~~ .. autoclass:: CheckBox RadioButton ~~~~~~~~~~~ .. autoclass:: RadioButton TreeWidget ~~~~~~~~~~ .. autoclass:: TreeWidget SelectableIcon ~~~~~~~~~~~~~~ .. autoclass:: SelectableIcon Decoration Widget Classes ------------------------- AttrMap ~~~~~~~ .. autoclass:: AttrMap Padding ~~~~~~~ .. autoclass:: Padding Filler ~~~~~~ .. autoclass:: Filler Divider ~~~~~~~ .. autoclass:: Divider LineBox ~~~~~~~ .. autoclass:: LineBox SolidFill ~~~~~~~~~ .. autoclass:: SolidFill PopUpLauncher ~~~~~~~~~~~~~ .. autoclass:: PopUpLauncher PopUpTarget ~~~~~~~~~~~ .. autoclass:: PopUpTarget WidgetPlaceholder ~~~~~~~~~~~~~~~~~ .. autoclass:: WidgetPlaceholder WidgetDisable ~~~~~~~~~~~~~ .. autoclass:: WidgetDisable Container Widget Classes ------------------------ Frame ~~~~~ .. autoclass:: Frame ListBox ~~~~~~~ .. autoclass:: ListBox TreeListBox ~~~~~~~~~~~ .. autoclass:: TreeListBox Columns ~~~~~~~ .. autoclass:: Columns Pile ~~~~ .. autoclass:: Pile GridFlow ~~~~~~~~ .. autoclass:: GridFlow BoxAdapter ~~~~~~~~~~ .. autoclass:: BoxAdapter Overlay ~~~~~~~ .. autoclass:: Overlay Graphic Widget Classes ---------------------- BarGraph ~~~~~~~~ .. autoclass:: BarGraph GraphVScale ~~~~~~~~~~~ .. autoclass:: GraphVScale ProgressBar ~~~~~~~~~~~ .. autoclass:: ProgressBar BigText ~~~~~~~ .. autoclass:: BigText .. autofunction:: get_all_fonts Terminal ~~~~~~~~ .. autoclass:: Terminal urwid-1.1.1/docs/reference/text_layout.rst0000664000175000017500000000020012051303575020200 0ustar ianian00000000000000Text Layout Classes =================== .. currentmodule:: urwid .. autoclass:: TextLayout .. autoclass:: StandardTextLayout urwid-1.1.1/docs/reference/meta.rst0000664000175000017500000000020312051303575016550 0ustar ianian00000000000000Metaclasses =========== .. currentmodule:: urwid .. autoclass:: WidgetMeta .. autoclass:: MetaSuper .. autoclass:: MetaSignals urwid-1.1.1/docs/reference/display_modules.rst0000664000175000017500000000123512051303575021025 0ustar ianian00000000000000Display Modules =============== .. currentmodule:: urwid .. autoclass:: BaseScreen raw_display ----------- .. module:: urwid.raw_display .. autoclass:: Screen curses_display -------------- .. module:: urwid.curses_display .. autoclass:: Screen web_display ----------- .. module:: urwid.web_display .. autoclass:: Screen html_fragment ------------- .. module:: urwid.html_fragment .. autoclass:: HtmlGenerator .. autofunction:: screenshot_init .. autofunction:: screenshot_collect lcd_display ----------- .. module:: urwid.lcd_display .. autoclass:: LCDScreen .. autoclass:: CFLCDScreen .. autoclass:: CF635Screen .. autoclass:: KeyRepeatSimulator urwid-1.1.1/docs/reference/command_map.rst0000664000175000017500000000011512051303575020077 0ustar ianian00000000000000Command Map ----------- .. currentmodule:: urwid .. autoclass:: CommandMap urwid-1.1.1/docs/reference/main_loop.rst0000664000175000017500000000047112051303575017606 0ustar ianian00000000000000MainLoop and Event Loops ======================== .. currentmodule:: urwid MainLoop -------- .. autoclass:: MainLoop SelectEventLoop --------------- .. autoclass:: SelectEventLoop GLibEventLoop ------------- .. autoclass:: GLibEventLoop TwistedEventLoop ---------------- .. autoclass:: TwistedEventLoop urwid-1.1.1/docs/reference/exceptions.rst0000664000175000017500000000062412051303575020012 0ustar ianian00000000000000Exceptions ========== .. currentmodule:: urwid .. autoexception:: ExitMainLoop .. autoexception:: WidgetError .. autoexception:: ListBoxError .. autoexception:: ColumnsError .. autoexception:: PileError .. autoexception:: GridFlowError .. autoexception:: BoxAdapterError .. autoexception:: OverlayError .. autoexception:: TextError .. autoexception:: EditError .. autoexception:: CanvasError urwid-1.1.1/docs/manual/0000775000175000017500000000000012051304275014412 5ustar ianian00000000000000urwid-1.1.1/docs/manual/wcur2.py0000664000175000017500000000030712051303575016030 0ustar ianian00000000000000 def get_pref_col(self, (maxcol,)): return self.cursor_x def move_cursor_to_coords(self, (maxcol,), col, row): assert row == 0 self.cursor_x = col return True urwid-1.1.1/docs/manual/displaymodules.rst0000664000175000017500000001252212051303575020206 0ustar ianian00000000000000.. _display-modules: ******************* Display Modules ******************* .. currentmodule:: urwid Urwid's display modules provide a layer of abstraction for drawing to the screen and reading user input. The display module you choose will depend on how you plan to use Urwid. .. image:: images/display_modules.png Typically you will select a display module by passing it to your :class:`MainLoop` constructor, eg: :: loop = MainLoop(widget, ..., screen=urwid.curses_display.Screen()) If you don't specify a display module, the default main loop will use :class:`raw_display.Screen` by default :: # These are the same loop = MainLoop(widget, ...) loop = MainLoop(widget, ..., screen=urwid.raw_display.Screen()) Raw and Curses Display Modules ============================== Urwid has two display modules for displaying to terminals or the console. The :class:`raw_display.Screen` module is a pure-python display module with no external dependencies. It sends and interprets terminal escape sequences directly. This is the default display module used by :class:`MainLoop`. The :class:`curses_display.Screen` module uses the curses or ncurses library provided by the operating system. The library does some optimization of screen updates and uses termcap to adjust to the user's terminal. The (n)curses library will disable colors if it detects a monochrome terminal, so a separate set of attributes should be given for monochrome mode when registering a palette with :class:`curses_display.Screen` High colors will not be used by the :class:`curses_display.Screen` module. See :ref:`setting-a-palette` below. This table summarizes the differences between the two modules: ============================== =========== ============== .. raw_display curses_display ============================== =========== ============== optimized C code no YES compatible with any terminal no YES [1]_ UTF-8 support YES YES [2]_ bright foreground without bold YES [3]_ no 88- or 256-color support YES no mouse dragging support YES no external event loop support YES no ============================== =========== ============== .. [1] if the termcap entry exists and TERM environment variable is set correctly .. [2] if python is linked against the wide version of ncurses .. [3] when using xterm or gnome-terminal Other Display Modules ===================== CGI Web Display Module ``web_display`` -------------------------------------- The :mod:`urwid.web_display` module lets you run your application as a CGI script under Apache instead of running it in a terminal. This module is a proof of concept. There are security and responsiveness issues that need to be resolved before this module is recommended for production use. The tour.py_ and calc.py_ example programs demonstrate use of this module. .. _tour.py: http://excess.org/urwid/browser/examples/tour.py .. _calc.py: http://excess.org/urwid/browser/examples/calc.py Screenshot Display Module ``html_fragment`` ------------------------------------------- Screenshots of Urwid interfaces can be rendered in plain HTML. The :class:`html_fragment.HtmlGenerator` display module lets you do this by simulating user input and capturing the screen as fragments of HTML each time :meth:`html_fragemnt.HtmlGenerator.draw_screen` is called. These fragments may be included in HTML documents. They will be rendered properly by any browser that uses a monospaced font for text that appears in ``
`` tags. HTML screenshots have text that is searchable and selectable in
a web browser, and they will shrink and grow when a user changes their
browser's text size.

The `example screenshots`_ are generated with this display module.

.. _`example screenshots`: http://excess.org/urwid/examples.html


LCD Display Module ``lcd_display``
----------------------------------

Almost any device that displays characters in a grid can be used as a
screen.  The :mod:`lcd_display` module has some base classes for simple
LCD character display devices and a complete implementation of a
:class:`lcd_display.CF635Screen` for Crystal Fontz 635 USB displays with
6 buttons.

The lcd_cf635.py_ example program demonstrates use of this module.

.. _lcd_cf635.py: http://excess.org/urwid/browser/examples/lcd_cf635.py

.. seealso:: `Urwid on a Crystalfontz 635 LCD `_


.. _setting-a-palette:

Setting a Palette
=================

The :class:`MainLoop` constructor takes a *palette* parameter that it passes
to the :meth:`register_palette() ` method of your display module.

A palette is a list of :ref:`display attribute ` names and foreground
and background settings. Display modules may be run in monochrome, normal or
high color modes and you can set different foregrounds and backgrounds for each
mode as part of your palette. eg:

::

    loop = MainLoop(widget, palette=[
        ('headings', 'white,underline', 'black', 'bold,underline'), # bold text in monochrome mode
        ('body_text', 'dark cyan', 'light gray'),
        ('buttons', 'yellow', 'dark green', 'standout'),
        ('section_text', 'body_text'), # alias to body_text
        ])

The :ref:`display-attributes` section of this manual describes all the options
available.
urwid-1.1.1/docs/manual/wmod.py0000664000175000017500000000103212051303575015730 0ustar  ianian00000000000000import urwid

class QuestionnaireItem(urwid.WidgetWrap):
    def __init__(self):
        self.options = []
        unsure = urwid.RadioButton(self.options, u"Unsure")
        yes = urwid.RadioButton(self.options, u"Yes")
        no = urwid.RadioButton(self.options, u"No")
        display_widget = urwid.GridFlow([unsure, yes, no], 15, 3, 1, 'left')
        urwid.WidgetWrap.__init__(self, display_widget)

    def get_state(self):
        for o in self.options:
            if o.get_state() is True:
                return o.get_label()
urwid-1.1.1/docs/manual/displayattributes.rst0000664000175000017500000002414512051303575020730 0ustar  ianian00000000000000.. _display-attributes:

**********************
  Display Attributes
**********************

.. currentmodule:: urwid

Urwid supports a number of common display attributes in monochrome, 16-color,
88-color and 256-color modes.

You are encouraged to provide support for as many of these modes as you like, while
allowing your interface to degrade gracefully by  providing command line arguments
or other interfaces to switch modes.

When setting up a palette with :class:`MainLoop` (or directly
on your screen instance), you may specify attributes for 16-color, monochrome
and high color modes. You can then switch between these modes with
:meth:`screen.set_terminal_properties() `,
where ``screen`` is your screen instance or :attr:`MainLoop.screen`.

.. seealso::
   :meth:`register_palette() reference `,

.. _using-display-attributes:

Using Display Attributes
========================

Once you have defined a palette you may use the its display attribute names
anywhere that expects a display attribute.  When no display attribute is defined
``None`` is used as a default display attribute.

``None`` will typically be rendered with the terminal's default foreground and
background colors.

You can also specify an exact foreground and background using an
:class:`AttrSpec` instance instead of a display attribute name.
Using :class:`AttrSpec` instances in your code may be trickier than using your
screen's palette because you must know which mode (number of colors) the screen is in.

.. _text-markup:

Text Markup
-----------

A :class:`Text` widget can specify which display attributes each part of the
text will use with the format defined in :class:`Text class reference `.
Some examples:

::

    Text(u"a simple string with default attribute")

The string and space around will use the ``None`` default display attribute
which usually appears in the terminal's default foreground and background.

::

    Text(('attr1', u"a string in display attribute attr1"))

The string will appear with foreground and backgrounds specified in the display
module's palette for ``'attr1'``, but the space around (before/after) the text
will appear with the default display attribute.

::

    Text([u"a simple string ", ('attr1', u"ending with attr1")])

The first three words have the default display attribute and the last three words have
display attribute ``'attr1'``.

::

    Text([('attr1', u"start in attr1 "), ('attr2', u"end in attr2")])

The first three words have display attribute ``'attr1'`` and the last three words have
display attribute ``'attr2'``.

::

    Text(('attr1', [u"nesting example ", ('attr2', u"inside"), u" outside"]))

When markup is nested only the innermost attribute applies. Here ``"inside"``
has attribute ``'attr2'`` and all the rest of the text has attribute
``'attr1'``.


Assigning Display Attributes with AttrMap
-----------------------------------------

If you want a whole widget to be assigned a display attribute, or if you want to change
one or more display attributes to other display attributes, you can wrap your widget
in an :class:`AttrMap` widget.  :class:`Text` widgets have no way to specify
a display attribute for the whitespace around the text caused by alignment and wrapping
so :class:`AttrMap` may be used. Some examples:

::

    AttrMap(Text(u"hello"), 'attr1')

The whole :class:`Text` widget will have display attribute ``'attr1'`` including
whitespace around the ``"hello"`` text.

::

    AttrMap(Text(('attr1', u"hello")), 'attr2')

The ``u"hello"`` text will appear with display attribute ``'attr1'`` and all surrounding
whitespace will appear with display attribute ``'attr2'``.

::

    AttrMap(Text([('attr1', u"hello"), u" world"]), {'attr1': 'attr2'})

The :class:`AttrMap` widget will apply display attribute ``'attr2'`` to all parts of
the :class:`Text` widget that are using ``'attr1'``.  The result is the ``"hello"``
text appearing with display attribute ``'attr2'`` and all other text and whitespace
appearing in the default display attribute.


:class:`AttrMap` can also change display attributes differently when they are in focus.
This can be used to "highlight" one or more widgets to make your interface more
user friendly.  To use this feature set the ``focus_map`` parameter when creating the
:class:`AttrMap` widget.


.. _foreground-background:

Foreground and Background Settings
==================================

.. list-table::
   :header-rows: 1
   :widths: 23 15 10 10 15

   * - Supported by Terminal
     - xterm / gnome-term
     - rxvt
     - linux console
     - others
   * - :ref:`16 standard foreground colors <16-standard-foreground>`
     - YES
     - YES
     - YES
     - very widely supported
   * - :ref:`8 standard background colors <8-standard-background>`
     - YES
     - YES
     - YES
     - very widely supported
   * - :ref:`default foreground/background `
     - YES
     - YES
     - YES
     - widely supported
   * - :ref:`bold, underline, standout `
     - YES
     - YES
     - standout
     - widely supported
   * - :ref:`"bright" background colors `
     - YES
     - urxvt
     -
     - some support
   * - :ref:`256-color foreground/background <256-foreground-background>`
     - YES
     -
     -
     - some support
   * - :ref:`88-color foreground/background <88-foreground-background>`
     - w/palette setting
     - urxvt
     -
     - limited support
   * - :ref:`RGB palette setting `
     - YES
     -
     -
     - limited support


.. _16-standard-foreground:

16 Standard Foreground Colors
-----------------------------

* ``'black'``
* ``'dark red'``
* ``'dark green'``
* ``'brown'``
* ``'dark blue'``
* ``'dark magenta'``
* ``'dark cyan'``
* ``'light gray'``
* ``'dark gray'``
* ``'light red'``
* ``'light green'``
* ``'yellow'``
* ``'light blue'``
* ``'light magenta'``
* ``'light cyan'``
* ``'white'``

.. _8-standard-background:

8 Standard Background Colors
----------------------------

* ``'black'``
* ``'dark red'``
* ``'dark green'``
* ``'brown'``
* ``'dark blue'``
* ``'dark magenta'``
* ``'dark cyan'``
* ``'light gray'``

.. _default-foreground-background:

Default Foreground and Background
---------------------------------

* ``'default'`` (or simply ``''``)

``'default'`` may be specified as a foreground or background to use a
terminal's default color. For terminals with transparent backgrounds
``'default'`` is the only way to show the transparent background. There is no
way to tell what the default colors are, so it is best to use default
foregrounds and backgrounds together (not with other colors) to ensure good
contrast.

.. _bold-underline-standout:

Bold, Underline, Standout
-------------------------

* ``'bold'``
* ``'underline'``
* ``'standout'``

These settings may be tagged on to foreground colors using commas, eg: ``'light
gray,underline,bold'``

For monochrome mode combinations of these are the only values that may be used.

Many terminals will turn foreground colors into their bright versions when you
use bold, eg: ``'dark blue,bold'`` might look the same as ``'light blue'``.
Some terminals also will display bright colors in a bold font even if you don't
specify bold. To inhibit this you can try setting ``bright_is_bold=False`` with
:meth:`BaseScreen.set_terminal_properties`, but it is not always supported.

``'standout'`` is usually displayed as the foreground and background colors reversed.

.. _bright-background:

"Bright" Background Colors
--------------------------

.. warning::
   Terminal support for bright background colors is spotty, and they generally
   should be avoided. If you are in a high-color mode you might have better luck
   using the high-color versions ``'h8'``, ``'h9'``, ``'h10'``, ..., ``'h15'``.

* ``'dark gray'``
* ``'light red'``
* ``'light green'``
* ``'yellow'``
* ``'light blue'``
* ``'light magenta'``
* ``'light cyan'``
* ``'white'``


.. _high-colors:

.. _256-foreground-background:

256-Color Foreground and Background Colors
------------------------------------------

In 256-color mode you have the 16 basic colors, a 6 * 6 * 6 color cube and a gray
scale with 24 entries (white and black not included).

The color cube is weighted towards the brighter colors, with RGB points at ``0``,
``0x5f``, ``0x87``, ``0xaf``, ``0xd7`` and ``0xff``.
The hex characters ``'0'``, ``'6'``, ``'8'``, ``'a'``, ``'d'`` and
``'f'`` are used as short-forms for these values.

High colors may be specified by their index ``'h0'``, ..., ``'h255'`` or with the
shortcuts for the color cube ``'#000'``, ``'#006'``, ``'#008'``, ..., ``'#fff'`` or
gray scale entries ``'g0'`` (black from color cube) , ``'g3'``, ``'g7'``, ...
``'g100'`` (white from color cube).

.. seealso::
   The palette_test.py_ example program

.. _palette_test.py: http://excess.org/urwid/browser/examples/palette_test.py

.. _88-foreground-background:

88-Color Foreground and Background Colors
-----------------------------------------

In 88-color mode you have the 16 basic colors, a 4 * 4 * 4 color cube and a gray
scale with 8 entries (white and black not included).

The color cube is weighted towards the brighter colors, with RGB points at ``0``,
``0x8b``, ``0xcd``, and ``0xff``. The hex characters ``'0'``, ``'8'``, ``'c'``
and ``'f'`` are used as short-forms for these values.

High colors may be specified by their index ``'h0'``, ..., ``'h87'`` or with the
shortcuts for the color cube ``'#000'``, ``'#008'``, ``'#00c'``, ..., ``'#fff'`` or
gray scale entries ``'g0'`` (black from color cube), ``'g19'``, ``'g35'``, ...
``'g100'`` (white from color cube).

.. seealso::
   The palette_test.py_ example program

.. _palette_test.py: http://excess.org/urwid/browser/examples/palette_test.py


.. _rgb-palette-setting:

RGB Palette Setting
-------------------

A few terminals have the ability to customize the terminal palette's RGB
values with :meth:`raw_display.Screen.modify_terminal_palette`.
There is no automatic way to tell if this is supported by a user's
terminal, so this feature shouldn't be relied on.

:meth:`raw_display.Screen.reset_default_terminal_palette` is used to
reset the palette in the ``palette_test.py`` example program when switching modes.

urwid-1.1.1/docs/manual/textlayout.rst0000664000175000017500000001107112051303575017370 0ustar  ianian00000000000000.. _text-layout:

***************
  Text Layout
***************

.. currentmodule:: urwid

Mapping a text string to screen coordinates within a widget is called text
layout. The :class:`Text` widget's default layout class supports
aligning text to the left, center or right, and can wrap text on space
characters, at any location, or clip text that is off the edge.

::

    Text("Showing some different alignment modes", align=...)

    align='left' (default)
    +----------------+   +------------------------+
    |Showing some    |   |Showing some different  |
    |different       |   |alignment modes         |
    |alignment modes |   +------------------------+
    +----------------+

    align='center'
    +----------------+   +------------------------+
    |  Showing some  |   | Showing some different |
    |   different    |   |    alignment modes     |
    |alignment modes |   +------------------------+
    +----------------+

    align='right'
    +----------------+   +------------------------+
    |    Showing some|   |  Showing some different|
    |       different|   |         alignment modes|
    | alignment modes|   +------------------------+
    +----------------+

::

    Text("Showing some different wrapping modes\nnewline", wrap=...)

    wrap='space' (default)
    +----------------+   +------------------------+
    |Showing some    |   |Showing some different  |
    |different       |   |wrapping modes          |
    |wrapping modes  |   |newline                 |
    |newline         |   +------------------------+
    +----------------+

    wrap='any'
    +----------------+   +------------------------+
    |Showing some dif|   |Showing some different w|
    |ferent wrapping |   |rapping modes           |
    |modes           |   |newline                 |
    |newline         |   +------------------------+
    +----------------+

    wrap='clip'
    +----------------+   +------------------------+
    |Showing some dif|   |Showing some different w|
    |newline         |   |newline                 |
    +----------------+   +------------------------+

If this is good enough for your application feel free to skip the rest of this
section.

.. seealso::
   :class:`Text widget reference `


Custom Text Layouts
===================

The :class:`StandardTextLayout` is set as the class variable
:attr:`Text.layout`. Individual :class:`Text`
widgets may use a different layout class, or you can change the default by
setting the :attr:`Text.layout` class variable itself.

A custom text layout class should extend the
:class:`TextLayout` base class and return text layout
structures from its ``layout()`` method.

.. seealso::
   :class:`TextLayout reference `


Text Layout Structures
======================

::

    "This is how a string of text might be displayed"
    0----5---10---15---20---25---30---35---40---45--

    0----5---10---15---+   right_aligned_text_layout = [
    |     This is how a|     [(5, 0), (13, 0, 13)],
    |    string of text|     [(4, 13), (14, 14, 28)],
    |might be displayed|     [(18, 29, 47)]
    +------------------+   ]

The mapping from a text string to where that text will be displayed in the
widget is expressed as a text layout structure.

Text layout structures are used both for rendering :class:`Text`
widgets and for mapping ``(x, y)`` positions within a widget back to the
corresponding offsets in the text. The latter is used when moving the cursor in
:class:`Edit` widgets up and down or by clicking with the mouse.

A text layout structure is a list of one or more line layouts. Each line layout
corresponds to a row of text in the widget, starting from its top.

A line layout is a list zero or more of the following tuples, each expressing
text to be displayed from left to right:

A. (*column width*, *starting text offset*, *ending text offset*)
B. (*column width of space characters to insert*, *text offset* or ``None``)
C. (*column width*, *text offset*, *new text to insert*)``

Tuple A displays a segment of text from the :class:`Text` widget.
Column width is explicitly specified because some characters within the text
may be zero width or double width.

Tuple B inserts any number of space characters, and if those characters
correspond to an offset within the text, that may be specified.

Tuple C allows insertion of arbitrary text. This could be used for hyphenating
split words or any other effect not covered by A or B. The
:class:`StandardTextLayout` does not currently use this
tuple in its line layouts.

.. seealso::
   :class:`TextLayout reference `,
   :class:`StandardTextLayout reference `

urwid-1.1.1/docs/manual/overview.rst0000664000175000017500000000600412051303575017014 0ustar  ianian00000000000000Library Overview
================

.. currentmodule:: urwid

Urwid is a console user interface library for `Python`_. Urwid offers an
alternative to using Python's curses module directly and handles many of the
difficult and tedious tasks for you.

.. _Python: http://www.python.org/

.. image:: images/introduction.png

Each Urwid component is loosely coupled and designed to be extended by the
user.

:ref:`Display modules ` are responsible for accepting
:ref:`user input ` and converting escape sequences to lists of
keystrokes and mouse events. They also draw the screen contents and convert
attributes used in the canvases rendered to the actual colors that appear on
screen.

The included widgets are simple building blocks and examples that try not to
impose a particular style of interface.
It may be helpful to think of Urwid as a console widget construction set rather
than a finished UI library like GTK or Qt. The :class:`Widget base class
` describes the widget interface and :ref:`widget layout
` describes how widgets are nested and arranged on the screen.

Text is the bulk of what will be displayed in any console user interface.
Urwid supports a number of :ref:`text encodings ` and Urwid
comes with a configurable :ref:`text layout ` that handles the
most of the common alignment and wrapping modes. If you need more flexibility
you can also write your own text layout classes.

Urwid supports a range of common :ref:`display attributes
`, including 256-color foreground and background settings,
bold, underline and standount settings for displaying text. Not all of these
are supported by all terminals, so Urwid helps you write applications that
support different color modes depending on what the user's terminal supports
and what they choose to enable.

:class:`ListBox` is one of Urwid's most powerful widgets,
and you may control of the :ref:`listbox contents ` by using
a built-in list walker class or by writing one yourself. This is very useful
for scrolling through lists of any significant length, or with nesting, folding
and other similar features.

When a widget renders a canvas to be drawn on screen, a weak reference to it is
stored in the :ref:`canvas cache `. This cache is used any time a
widget needs to be rendered again, reducing the amount of work required to
update the screen. Since only weak references are used, Urwid's display modules
will hold on to a reference to the canvas that they are currently displaying as
a way to keep the cache alive and populated with current data.

Urwid's :ref:`main loop ` simplifies handling of input and updating
the screen.  It also lets you use one of a number of :ref:`the event loops
`, allowing integration with Twisted_'s reactor or Glib_'s event
loop if desired.

.. _Glib: http://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html
.. _Twisted: http://twistedmatrix.com/documents/current/core/howto/reactor-basics.html
urwid-1.1.1/docs/manual/wsel.py0000664000175000017500000000167512051303575015751 0ustar  ianian00000000000000import urwid

class SelectablePudding(urwid.Widget):
    _sizing = frozenset(['flow'])
    _selectable = True

    def __init__(self):
        self.pudding = "pudding"

    def rows(self, size, focus=False):
        return 1

    def render(self, size, focus=False):
        (maxcol,) = size
        num_pudding = maxcol / len(self.pudding)
        pudding = self.pudding
        if focus:
            pudding = pudding.upper()
        return urwid.TextCanvas([pudding * num_pudding],
            maxcol=maxcol)

    def keypress(self, size, key):
        (maxcol,) = size
        if len(key) > 1:
            return key
        if key.lower() in self.pudding:
            # remove letter from pudding
            n = self.pudding.index(key.lower())
            self.pudding = self.pudding[:n] + self.pudding[n+1:]
            if not self.pudding:
                self.pudding = "pudding"
            self._invalidate()
        else:
            return key
urwid-1.1.1/docs/manual/index.rst0000664000175000017500000000034612051303575016260 0ustar  ianian00000000000000.. _urwid-manual:

################
  Urwid Manual
################

.. toctree::
   :maxdepth: 2

   overview
   mainloop
   displaymodules
   widgets
   userinput
   textlayout
   encodings
   displayattributes
   canvascache


urwid-1.1.1/docs/manual/userinput.rst0000664000175000017500000000775612051303575017223 0ustar  ianian00000000000000.. vim: set fileencoding=utf-8:

.. _user-input:

**************
  User Input
**************

.. currentmodule:: urwid

All input from the user is parsed by a display module, and returned from either
the :meth:`get_input() ` or
:meth:`get_input_nonblocking() ` methods as a list.
Window resize events are also included in the user input.

The :class:`MainLoop` class will take this input and pass each
item to the widget methods :meth:`keypress() ` or
:meth:`mouse_event() `. You may
filter input (possibly removing or altering it) before it is passed to the
widgets, or can catch unhandled input by passing functions into the
:class:`MainLoop` constructor. If the window was resized
:class:`MainLoop` will query the new display size and update
the screen.

There may be more than one keystroke or mouse event processed at a time, and
each is sent as a separate item in the list.

.. _keyboard-input:

Keyboard Input
==============

Not all keystrokes are sent by a user's terminal to the program, and which keys
are sent varies from terminal to terminal, but Urwid will report any keys that
are sent.

============= =================
Key pressed   Input returned
============= =================
H             ``'h'``
SHIFT+H       ``'H'``
SPACE         ``' '``
ENTER         ``'enter'``
UP            ``'up'``
PAGE DOWN     ``'page down'``
F5            ``'f5'``
SHIFT+F5      ``'shift f5'``
CTRL+SHIFT+F5 ``'shift ctrl f5'``
ALT+J         ``'meta j'``
============= =================

With Unicode :ref:`text encoding ` you will also receive
Unicode strings for any non-ASCII characters:

=========== ==============
Key pressed Input returned
=========== ==============
é           ``u'é'``
Ж           ``u'Ж'``
カ          ``u'カ'``
=========== ==============

With non-Unicode :ref:`text encoding ` characters will be sent
as-is in the original encoding.

=========== =========================================
Key pressed Input returned (each in its own encoding)
=========== =========================================
é           ``'é'``
Ж           ``'Ж'``
カ          ``'カ'`` (two bytes)
=========== =========================================

Urwid does not try to convert this text to Unicode to avoid losing any
information. If you want the input converted to Unicode in all cases you may
create an input filter to do so.


.. _mouse-input:

Mouse Input
===========

Mouse input is sent as a (*event*, *button*, *x*, *y*) tuple. *event* is a string
describing the event. If the *SHIFT*, *ALT* or *CTRL* keys are held when a mouse
event is sent then *event* may be prefixed by ``'shift '``, ``'meta '`` or
``'ctrl'``. *button* is a number from 1 to 5. *x* and *y* are character
coordinates starting from ``(0, 0)`` at the top-left of the screen.

Support for the right-mouse button and use of modifier keys is poor in many
terminals and some users don't have a middle mouse button, so these shouldn't
be relied on.

``'mouse press'`` Events
------------------------

A mouse button was pressed.

=============== ======================
`button` number Mouse button
=============== ======================
1               Left button
2               Middle button
3               Right button
4               Scroll wheel up [#first]_
5               Scroll wheel down [#first]_
=============== ======================

.. [#first] typically no corresponding release event is sent

``'mouse release'`` Events
--------------------------

Mouse release events will often not have information about which button was
released. In this case *button* will be set to 0.

``'mouse drag'`` Events
-----------------------

In the rare event that your user is using a terminal that can send these events
you can use them to track their mouse dragging from one character cell to the
next across the screen. Be aware that you might see *x* and/or *y* coordinates
one position off the screen if the user drags their mouse to the edge.
urwid-1.1.1/docs/manual/mainloop.rst0000664000175000017500000001013112051303575016760 0ustar  ianian00000000000000.. _main-loop:

*************
  Main Loop
*************

.. currentmodule:: urwid

The :class:`MainLoop` class ties together a :ref:`display
module `, a set of widgets and an :ref:`event loop
`. It handles passing input from the display module to the
widgets, rendering the widgets and passing the rendered canvas to the display
module to be drawn.

You may filter the user's input before it is passed to the widgets with your
own code by using :meth:`MainLoop.input_filter`, or have
special code to handle input not handled by the widgets by using
:func:`MainLoop.unhandled_input`.

You may set alarms to create timed events using
:meth:`MainLoop.set_alarm_at` or
:meth:`MainLoop.set_alarm_in`. These methods automatically add
a call to :func:`MainLoop.draw_screen` after calling your
callback. :meth:`MainLoop.remove_alarm` may be used to remove
alarms.

When the main loop is running, any code that raises an
:exc:`ExitMainLoop` exception will cause the loop to
exit cleanly. If any other exception reaches the main loop code, it will shut
down the screen to avoid leaving the terminal in an unusual state then re-raise
the exception for normal handling.

Using :class:`MainLoop` is highly recommended, but if it does
not fit the needs of your application you may choose to use your own code
instead. There are no dependencies on :class:`MainLoop` in
other parts of Urwid.

Widgets Displayed
=================

The topmost widget displayed by :class:`MainLoop` must be
passed as the first parameter to the constructor. If you want to change the
topmost widget while running, you can assign a new widget to the
:class:`MainLoop` object's
:attr:`MainLoop.widget` attribute. This is useful for
applications that have a number of different modes or views.

The displayed widgets will be handling user input, so it is better to extend
the widgets that are displayed with your application-specific input handling so
that the application's behaviour changes when the widgets change. If all your
custom input handling is done from :meth:`MainLoop.unhandled_input`,
it will be difficult to extend as your application gets more complicated.


.. _event-loops:

Event Loops
===========

Urwid's event loop classes handle waiting for things for the
:class:`MainLoop`. The different event loops allow you to
integrate with Twisted_ or Glib_ libraries, or use a simple ``select``-based
loop. Event loop classes abstract the particulars of waiting for input and
calling functions as a result of timeouts.

You will typically only have a single event loop in your application, even if
you have more than one :class:`MainLoop` running.

You can add your own files to watch to your event loop, with the
:meth:`watch_file() ` method.
Using this interface gives you the special handling
of :exc:`ExitMainLoop` and other exceptions when using Glib_ or Twisted_.

.. _Twisted: http://twistedmatrix.com/trac/
.. _Glib: http://developer.gnome.org/glib/stable/

``SelectEventLoop``
-------------------

This event loop is based on ``select.select()``. This is the default event loop
created if none is passed to :class:`~urwid.main_loop.MainLoop`.

::

    # same as urwid.MainLoop(widget, event_loop=urwid.SelectEventLoop())
    loop = urwid.MainLoop(widget)

.. seealso::

  :class:`SelectEventLoop reference `

``TwistedEventLoop``
--------------------

This event loop uses Twisted's reactor. It has been set up to emulate
:class:`SelectEventLoop`'s behaviour and will start the
reactor and stop it on an error. This is not the standard way of using
Twisted's reactor, so you may need to modify this behaviour for your
application.

::

    loop = urwid.MainLoop(widget, event_loop=urwid.TwistedEventLoop())

.. seealso::

  :class:`TwistedEventLoop reference `

``GLibEventLoop``
-----------------

This event loop uses GLib's event loop. This is useful if you are building an
application that depends on DBus events, but don't want to base your
application on Twisted.

::

    loop = urwid.MainLoop(widget, event_loop=urwid.GLibEventLoop())

.. seealso::

  :class:`GLibEventLoop reference `
urwid-1.1.1/docs/manual/wanat.py0000664000175000017500000000115712051303575016104 0ustar  ianian00000000000000import urwid

class Pudding(urwid.Widget):
    _sizing = frozenset(['flow'])

    def rows(self, size, focus=False):
        return 1

    def render(self, size, focus=False):
        (maxcol,) = size
        num_pudding = maxcol / len("Pudding")
        return urwid.TextCanvas(["Pudding" * num_pudding], maxcol=maxcol)


class BoxPudding(urwid.Widget):
    _sizing = frozenset(['box'])

    def render(self, size, focus=False):
        (maxcol, maxrow) = size
        num_pudding = maxcol / len("Pudding")
        return urwid.TextCanvas(["Pudding" * num_pudding] * maxrow,
                                maxcol=maxcol)
urwid-1.1.1/docs/manual/encodings.rst0000664000175000017500000000632612051303575017126 0ustar  ianian00000000000000.. vim: set fileencoding=utf-8:

.. _text-encodings:

***********************
  Encodings Supported
***********************

.. currentmodule:: urwid

Urwid has a single global setting for text encoding that is set on start-up
based on the configured locale. You may change that setting with the
:meth:`set_encoding` method. eg.

::

    urwid.set_encoding("UTF-8")

There are two distinct modes of handling encodings with Urwid: Unicode or
Pass-through. The mode corresponds to using Unicode strings or normal strings
in your widgets.

::

    txt_a = urwid.Text(u"El Niño")
    txt_b = urwid.Text("El Niño")

``txt_a`` will be automatically encoded when it is displayed (Unicode mode).

``txt_b`` is **assumed** to be in the encoding the user is expecting and passed
through as-is (Pass-through mode). If the encodings are different then the
user will see "mojibake" (garbage) on their screen.

The only time it makes sense to use pass-through mode is if you're handling an
encoding that does not round-trip to Unicode properly, or if you're absolutely
sure you know what you're doing.

Unicode Support
===============

Urwid has a basic understanding of character widths so that the text layout
code can properly wrap and display most text. There is currently no support for
right-to-left text.

You should be able to use any valid Unicode characters that are present in the
global encoding setting in your widgets, with the addition of some common DEC
graphic characters:

::

    \u00A3 (£), \u00B0 (°), \u00B1 (±), \u00B7 (·), \u03C0 (π),
    \u2260 (≠), \u2264 (≤), \u2265 (≥), \u23ba (⎺), \u23bb (⎻),
    \u23bc (⎼), \u23bd (⎽), \u2500 (─), \u2502 (│), \u250c (┌),
    \u2510 (┐), \u2514 (└), \u2518 (┘), \u251c (├), \u2524 (┤),
    \u252c (┬), \u2534 (┴), \u253c (┼), \u2592 (▒), \u25c6 (◆)

If you use these characters with a non-UTF-8 encoding they will be sent using
the alternate character set sequences supported by some terminals.

Pass-through Support
====================

Supported encodings for pass-through mode:

* UTF-8 (narrow and wide characters)
* ISO-8859-*
* EUC-JP (JISX 0208 only)
* EUC-KR
* EUC-CN (aka CN-GB)
* EUC-TW (CNS 11643 plain 1 only)
* GB2312
* GBK
* BIG5
* UHC

In pass-through mode Urwid must still calculate character widths. For UTF-8
mode the widths are specified in the Unicode standard. For ISO-8859-* all
bytes are assumed to be 1 column wide characters. For the remaining supported
encodings any byte with the high-bit set is considered to be half of a 2-column
wide character.

The additional plains in EUC are not currently supported.

Future Work
===========

Text encoding should be a per-screen (display module) setting, not a global
setting. It should be possible to simultaneously support different encodings on
different screens with Urwid. Making this work involves possibly changing the
function signature of many widget methods, because encoding needs to be
specified along with size and focus.

Device-specific encodings should also be possible for Unicode mode. The LCD
display module in development drives a device with a non-standard mapping of
Unicode code points to 8-bit values, but it should still be possible to use a
Unicode text to display the characters it supports.
urwid-1.1.1/docs/manual/widgets.rst0000664000175000017500000007261512051303575016627 0ustar  ianian00000000000000***********
  Widgets
***********

.. currentmodule:: urwid

.. _widget-layout:

Widget Layout
=============

Urwid uses widgets to divide up the available screen space. This makes it easy
to create a fluid interface that moves and changes with the user's terminal and
font size.

.. image:: images/widget_layout.png

The result of rendering a widget is a canvas suitable for displaying on the
screen. When we render the topmost widget:

1. The topmost widget *(a)* is rendered the full size of the screen
2. *(a)* renders *(b)* any size up to the full size of the screen
3. *(b)* renders *(c)*, *(d)* and *(e)* dividing its available screen columns
   between them
4. *(e)* renders *(f)* and *(g)* dividing its available screen rows between
   them
5. *(e)* combines the canvases from *(f)* and *(g)* and returns them
6. *(b)* combines the canvases from *(c)*, *(d)* and *(e)* and returns them
7. *(a)* possibly modifies the canvas from *(b)* and returns it

Widgets *(a)*, *(b)* and *(e)* are called container widgets because they
contain other widgets. Container widgets choose the size and position their
contained widgets.

Container widgets must also keep track of which one of their contained widgets
is in focus. The focus is used when handling keyboard input. If in the above
example *(b)* 's focus widget is *(e)* and *(e)* 's focus widget is
*(f)* then keyboard input will be handled this way:

1. The keypress is passed to the topmost widget *(a)*
2. *(a)* passes the keypress to *(b)*
3. *(b)* passes the keypress to *(e)*, its focus widget
4. *(e)* passes the keypress to *(f)*, its focus widget
5. *(f)* either handles the keypress or returns it
6. *(e)* has an opportunity to handle the keypress if it was returned from
   *(f)*
7. *(b)* has an opportunity to handle the keypress if it was returned from
   *(e)*
8. *(a)* has an opportunity to handle the keypress if it was returned from
   *(b)*

Box, Flow and Fixed Widgets
===========================

The size of a widget is measured in screen columns and rows. Widgets that are
given an exact number of screen columns and rows are called box widgets. The
topmost widget is always a box widget.

Much of the information displayed in a console user interface is text and the
best way to display text is to have it flow from one screen row to the next.
Widgets like this that require a variable number of screen rows are called flow
widgets. Flow widgets are given a number of screen columns and can calculate
how many screen rows they need.

Occasionally it is also useful to have a widget that knows how many screen
columns and rows it requires, regardless of the space available. This is
called a fixed widget.

.. list-table:: How a Widget's Size is Determined
   :widths: 20 40 40
   :header-rows: 1

   * - sizing mode
     - width
     - height
   * - ``'box'``
     - container decides
     - container decides
   * - ``'flow'``
     - container decides
     - widget's :meth:`rows() ` method
   * - ``'fixed'``
     - widget's :meth:`pack() ` method
     - widget's :meth:`pack() ` method

It is an Urwid convention to use the variables :attr:`maxcol` and
:attr:`maxrow` to store a widget's size. Box widgets require both of ``(maxcol,
maxrow)`` to be specified.

Flow widgets expect a single-element tuple ``(maxcol,)`` instead because they
calculate their :attr:`maxrow` based on the :attr:`maxcol` value.

Fixed widgets expect the value ``()`` to be passed in to functions that take
a size because they know their :attr:`maxcol` and :attr:`maxrow` values.

.. _basic-grafic-widgets:

Included Widgets
================

:ref:`Widget class reference `

.. image:: images/urwid_widgets_1.png

Basic and graphic widgets are the content with which users interact. They may
also be used as part of custom widgets you create.

.. image:: images/urwid_widgets_2.png

.. _decoration-widgets:

Decoration Widgets
==================

Decoration widgets alter the appearance or position of a single other widget.
The widget they wrap is available as the
:attr:`original_widget ` property.
If you might be using more than one decoration widget you may use the
:attr:`base_widget ` property to access the
"most" original_widget.
:attr:`Widget.base_widget` points to ``self`` on all non-decoration widgets, so
it is safe to use in any situation.

.. _container-widgets:

Container Widgets
=================

Container widgets divide their available space between their child widgets.
This is how widget layouts are defined. When handling selectable widgets
container widgets also keep track of which of their child widgets is in focus.
Container widgets may be nested, so the actual widget in focus may be many
levels below the topmost widget.

Urwid's container widgets have a common API you can use, regardless of the
container type.  Backwards compatibility is still maintained for the old
container-specific ways of accessing and modifying contents, but this API
is now the preferred way of modifying and traversing containers.

::

  container.focus

is a read-only property that returns the widget in focus for this container.
Empty containers and non-container widgets (that inherit from Widget)
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
any object.
:class:`Columns`, :class:`Pile`, :class:`GridFlow`, :class:`Overlay` and
:class:`ListBox` with a :class:`SimpleListWalker` or :class:`SimpleFocusListWalker`
as its body use integer positions.  :class:`Frame` uses ``'body'``, ``'header'``
and ``'footer'``;  :class:`ListBox` with a custom list walker will use the
positions the list walker returns.

Reading this value on an empty container or on any non-container widgets
(that inherit from Widget) raises an IndexError.  Writing to this property with
an invalid position will also raise an IndexError.  Writing a new value
automatically marks this widget to be redrawn and will be reflected in
``container.focus``.

::

  container.contents

is a read-only property (read/write in some cases) that provides access to a
mapping- or list-like object that contains the child widgets and the options
used for displaying those widgets in this container.  The mapping- or list-like
object always allows reading from positions with the usual ``__getitem__()``
method and may support assignment and deletion with ``__setitem__()`` and
``__delitem__()`` methods.  The values are ``(child widget, option)`` tuples.
When this object or its contents are modified the widget is automatically
flagged to be redrawn.

:class:`Columns`, :class:`Pile` and :class:`GridFlow` allow assigning an
iterable to ``container.contents`` to overwrite the values in
with the ones provided.

:class:`Columns`, :class:`Pile`, :class:`GridFlow`, :class:`Overlay` and
:class:`Frame` support ``container.contents`` item assignment and deletion.

::

  container.options(...)

is a method that returns options objects for use in items added to
``container.contents``.  The arguments are specific to the container type,
and generally match the ``__init__()`` arguments for the container.
The objects returned are currently tuples of strings and integers or ``None``
for containers without child widget options.  This method exists to allow
future versions of Urwid to add new options to existing containers.  Code
that expects the option tuples to remain the same size will fail when new
options are added, so defensive programming with options tuples is strongly
encouraged.

::

  container.__getitem__(x)
  # a.k.a.
  container[x]

is a short-cut method behaving identically to:
``container.contents[x][0].base_widget``.
Which means roughly "give me the child widget at position *x* and skip all
the decoration widgets wrapping it".  Decoration widgets include
:class:`Padding`, :class:`Filler`, :class:`AttrMap` etc.

::

  container.get_focus_path()

is a method that returns the focus position for this container *and* all child
containers along the path defined by their focus settings.  This list of
positions is the closest thing we have to the singular widget-in-focus in
other UI frameworks, because the ultimate widget in focus in Urwid depends
on the focus setting of all its parent container widgets.

::

  container.set_focus_path(p)

is a method that assigns to the focus_position property of each container
along the path given by the list of positions *p*.  It may be used to restore
focus to a widget as returned by a previous call to ``container.get_focus_path()``.

::

  container.__iter__()
  # typically
  for x in container: ...

  container.__reversed__()
  # a.k.a
  reversed(container)

are methods that allow iteration over the *positions* of this container.
Normally the order of the positions generated by __reversed__() will be the
opposite of __iter__().  The exception is the case of :class:`ListBox` with
certain custom list walkers, and the reason goes back to the original way list
walker interface was defined.  Note that a custom list walker might also generate
an unbounded number of positions, so care should be used with this interface and
:class:`ListBox`.


Pile Widgets
------------

:class:`Pile` widgets are used to combine multiple widgets by
stacking them vertically. A Pile can manage selectable widgets by keeping track
of which widget is in focus and it can handle moving the focus between widgets
when the user presses the *UP* and *DOWN* keys. A Pile will also work well when
used within a :class:`ListBox`.

A Pile is selectable only if its focus widget is selectable. If you create a
Pile containing one Text widget and one Edit widget the Pile will choose the
Edit widget as its default focus widget.


Columns Widgets
---------------

:class:`Columns` widgets may be used to arrange either flow
widgets or box widgets horizontally into columns. Columns widgets will manage
selectable widgets by keeping track of which column is in focus and it can
handle moving the focus between columns when the user presses the *LEFT* and
*RIGHT* keys. Columns widgets also work well when used within a
:class:`ListBox`.

Columns widgets are selectable only if the column in focus is selectable. If a
focus column is not specified the first selectable widget will be chosen as the
focus column.


GridFlow Widgets
----------------

The :class:`GridFlow` widget is a flow widget designed for use
with :class:`Button`, :class:`CheckBox` and
:class:`RadioButton` widgets. It renders all the widgets it
contains the same width and it arranges them from left to right and top to
bottom.

The GridFlow widget uses Pile, Columns, Padding and Divider widgets to build a
display widget that will handle the keyboard input and rendering. When the
GridFlow widget is resized it regenerates the display widget to accommodate the
new space.


Overlay Widgets
---------------

The :class:`Overlay` widget is a box widget that contains two
other box widgets. The bottom widget is rendered the full size of the Overlay
widget and the top widget is placed on top, obscuring an area of the bottom
widget. This widget can be used to create effects such as overlapping "windows"
or pop-up menus.

The Overlay widget always treats the top widget as the one in focus. All
keyboard input will be passed to the top widget.

If you want to use a flow flow widget for the top widget, first wrap the flow
widget with a :class:`Filler` widget.


.. _listbox-contents:

ListBox Contents
================

:class:`ListBox` is a box widget that contains flow widgets.
Its contents are displayed stacked vertically, and the
:class:`ListBox` allows the user to scroll through its content.
One of the flow widgets displayed in the :class:`ListBox` is its
focus widget.

ListBox Focus and Scrolling
---------------------------

The :class:`ListBox` is a box widget that contains flow widgets.
Its contents are displayed stacked vertically, and the
:class:`ListBox` allows the user to scroll through its content.
One of the flow widgets displayed in the :class:`ListBox` is the
focus widget. The :class:`ListBox` passes key presses to the
focus widget to allow the user to interact with it. If the focus widget does
not handle a keypress then the :class:`ListBox` may handle the
keypress by scrolling and/or selecting another widget to become the focus
widget.

The :class:`ListBox` tries to do the most sensible thing when
scrolling and changing focus. When the widgets displayed are all
:class:`Text` widgets or other unselectable widgets then the
:class:`ListBox` will behave like a web browser does when the
user presses *UP*, *DOWN*, *PAGE UP* and *PAGE DOWN*: new text is immediately
scrolled in from the top or bottom. The :class:`ListBox` chooses
one of the visible widgets as its focus widget when scrolling. When scrolling
up the :class:`ListBox` chooses the topmost widget as the focus,
and when scrolling down the :class:`ListBox` chooses the
bottommost widget as the focus.

The :class:`ListBox` remembers the location of the widget in
focus as either an "offset" or an "inset". An offset is the number of rows
between the top of the :class:`ListBox` and the beginning of the
focus widget. An offset of zero corresponds to a widget with its top aligned
with the top of the :class:`ListBox`. An inset is the fraction
of rows of the focus widget that are "above" the top of the
:class:`ListBox` and not visible. The
:class:`ListBox` uses this method of remembering the focus
widget location so that when the :class:`ListBox` is resized the
text displayed will stay roughly aligned with the top of the
:class:`ListBox`.

When there are selectable widgets in the :class:`ListBox` the
focus will move between the selectable widgets, skipping the unselectable
widgets. The :class:`ListBox` will try to scroll all the rows of
a selectable widget into view so that the user can see the new focus widget in
its entirety. This behavior can be used to bring more than a single widget into
view by using composite widgets to combine a selectable widget with other
widgets that should be displayed at the same time.


Dynamic ListBox with ListWalker
-------------------------------

While the :class:`ListBox` stores the location of its focus
widget, it does not directly store the actual focus widget or other contents of
the :class:`ListBox`. The storage of a
:class:`ListBox`'s content is delegated to a "List Walker"
object. If a list of widgets is passed to the :class:`ListBox`
constructor then it creates a :class:`SimpleListWalker` object
to manage the list.

When the :class:`ListBox` is `rendering a canvas`_ or `handling
input`_ it will:

.. _rendering a canvas: :meth:`ListBox.render`
.. _handling input: :meth:`ListBox.keypress`

1. Call the :meth:`get_focus` method of its list walker object. This method
   will return the focus widget and a position object.
2. Optionally call the :meth:`get_prev` method of its List Walker object one or
   more times, initially passing the focus position and then passing the new
   position returned on each successive call. This method will return the
   widget and position object "above" the position passed.
3. Optionally call the :meth:`get_next` method of its List Walker object one or
   more times, similarly, to collect widgets and position objects "below" the
   focus position.
4. Optionally call the :meth:`set_focus` method passing one of the position
   objects returned in the previous steps.

This is the only way the :class:`ListBox` accesses its contents,
and it will not store copies of any of the widgets or position objects beyond
the current rendering or input handling operation.

The :class:`SimpleListWalker` stores a list of widgets, and uses
integer indexes into this list as its position objects. It stores the focus
position as an integer, so if you insert a widget into the list above the focus
position then you need to remember to increment the focus position in the
:class:`SimpleListWalker` object or the contents of the
:class:`ListBox` will shift.

A custom List Walker object may be passed to the
:class:`ListBox` constructor instead of a plain list of widgets.
List Walker objects must implement the :ref:`list-walker-interface`.

The fib.py_ example program demonstrates a custom list walker that doesn't
store any widgets. It uses a tuple of two successive Fibonacci numbers as its
position objects and it generates Text widgets to display the numbers on the
fly. The result is a :class:`ListBox` that can scroll through an
unending list of widgets.

The edit.py_ example program demonstrates a custom list walker that loads lines
from a text file only as the user scrolls them into view. This allows even
huge files to be opened almost instantly.

The browse.py_ example program demonstrates a custom list walker that uses a
tuple of strings as position objects, one for the parent directory and one for
the file selected. The widgets are cached in a separate class that is accessed
using a dictionary indexed by parent directory names. This allows the
directories to be read only as required. The custom list walker also allows
directories to be hidden from view when they are "collapsed".

.. _fib.py: http://excess.org/urwid/browser/examples/fib.py
.. _edit.py: http://excess.org/urwid/browser/examples/edit.py
.. _browse.py: http://excess.org/urwid/browser/examples/browse.py


Setting the Focus
-----------------

The easiest way to change the current :class:`ListBox` focus is
to call the :meth:`ListBox.set_focus` method. This method doesn't
require that you know the :class:`ListBox`'s current dimensions
``(maxcol, maxrow)``. It will wait until the next call to either keypress or
render to complete setting the offset and inset values using the dimensions
passed to that method.

The position object passed to :meth:`set_focus` must be compatible with the
List Walker object that the :class:`ListBox` is using. For
:class:`SimpleListWalker` the position is the integer index of
the widget within the list.

The *coming_from* parameter should be set if you know that the old position is
"above" or "below" the previous position. When the
:class:`ListBox` completes setting the offset and inset values
it tries to find the old widget among the visible widgets. If the old widget is
still visible, if will try to avoid causing the :class:`ListBox`
contents to scroll up or down from its previous position. If the widget is not
visible, then the :class:`ListBox` will:

* Display the new focus at the bottom of the :class:`ListBox` if
  *coming_from* is "above".
* Display the new focus at the top of the :class:`ListBox` if
  *coming_from* is "below".
* Display the new focus in the middle of the :class:`ListBox` if
  coming_from is ``None``.

If you know exactly where you want to display the new focus widget within the
:class:`ListBox` you may call
:meth:`ListBox.set_focus_valign`.  This method lets you specify
the *top*, *bottom*, *middle*, a relative position or the exact number of rows
from the top or bottom of the :class:`ListBox`.

List Walkers
------------

:class:`ListBox` does not manage the widgets it displays
directly, instead it passes that task to a class called a "list walker". List
walkers keep track of the widget in focus and provide an opaque position object
that the :class:`ListBox` may use to iterate through widgets
above and below the focus widget.

A :class:`SimpleFocusListWalker`
is a list walker that behaves like a normal Python list. It may be used any
time you will be displaying a moderate number of widgets.

If you need to display a large number of widgets you should implement your own
list walker that manages creating widgets as they are requested and destroying
them later to avoid excessive memory use.

List walkers may also be used to display tree or other structures within a
:class:`ListBox`. A number of the `example programs
`_ demonstrate the use of custom list
walker classes.

.. seealso:: :class:`ListWalker base class reference `


.. _list-walker-interface:

List Walker Interface
---------------------

List Walker API Version 1
~~~~~~~~~~~~~~~~~~~~~~~~~

This API will remain available and is still the least restrictive option for
the programmer.  Your class should subclass :class:`ListWalker`.
Whenever the focus or content changes you are responsible for
calling :meth:`ListWalker._modified`.

.. currentmodule:: MyV1ListWalker

.. method:: get_focus()

   return a ``(widget, position)`` tuple or ``(None, None)`` if empty

.. method:: set_focus(position)

   set the focus and call ``self._modified()`` or raise an :exc:`IndexError`.

.. method:: get_next(position)

   return the ``(widget, position)`` tuple below *position* passed
   or ``(None, None)`` if there is none.

.. method:: get_prev(position)

   return the ``(widget, position)`` tuple above *position* passed
   or ``(None, None)`` if there is none.

List Walker API Version 2
~~~~~~~~~~~~~~~~~~~~~~~~~

This API is an attempt to remove some of the duplicate code that V1 requires for
many users.  List walker API V1 will be implemented automatically by
subclassing :class:`ListWalker` and implementing the V2 methods.
Whenever the focus or content changes you are responsible for
calling :meth:`ListWalker._modified`.

.. currentmodule:: MyV2ListWalker

.. method:: __getitem__(position)

   return widget at *position* or raise an :exc:`IndexError` or :exc:`KeyError`

.. method:: next_position(position)

   return the position below passed *position* or raise an :exc:`IndexError` or :exc:`KeyError`

.. method:: prev_position(position)

   return the position above passed *position* or raise an :exc:`IndexError` or :exc:`KeyError`

.. method:: set_focus(position)

   set the focus and call ``self._modified()`` or raise an :exc:`IndexError`.

.. attribute:: focus

   attribute or property containing the focus position, or define
   :meth:`MyV1ListWalker.get_focus` as above

List Walker Iteration
~~~~~~~~~~~~~~~~~~~~~

There is an optional iteration helper method that may be defined in any list walker.
When this is defined it will be used by :meth:`ListBox.__iter__` and
:meth:`ListBox.__reversed__`:

.. method:: positions(reverse=False)

   return a forward or reverse iterable of positions

.. currentmodule:: urwid



Custom Widgets
==============

Widgets in Urwid are easiest to create by extending other widgets. If you are
making a new type of widget that can use other widgets to display its content,
like a new type of button or control, then you should start by extending
:class:`WidgetWrap` and passing the display widget to its constructor.

The :class:`Widget` interface is described in detail in the
:class:`Widget base class reference ` and is useful if you're looking to modify
the behavior of an existing widget,
build a new widget class from scratch or just want a better understanding of
the library.

One Urwid design choice that stands out is that widgets typically have no
size. Widgets don't store their size on screen, and instead are
passed that information when they need it.

This choice has some advantages:

* widgets may be reused in different locations
* reused widgets only need to be rendered once per size displayed
* widgets don't need to know their parents
* less data to store and update
* no worrying about widgets that haven't received their size yet
* same widgets could be displayed at different sizes to different users
  simultaneously

It also has disadvantages:

* difficult to determine a widget's size on screen
* more parameters to parse
* duplicated size calculations across methods

For determining a widget's size on screen it is possible to look up the size(s)
it was rendered at in the :class:`CanvasCache`. There are plans
to address some of the duplicated size handling code in the container widgets
in a future Urwid release.

The same holds true for a widget's focus state, so that too is passed in to
functions that need it.


Modifying Existing Widgets
--------------------------

The easiest way to create a custom widget is to modify an existing widget.
This can be done by either subclassing the original widget or by wrapping it.
Subclassing is appropriate when you need to interact at a very low level with
the original widget, such as if you are creating a custom edit widget with
different behavior than the usual Edit widgets. If you are creating a custom
widget that doesn't need tight coupling with the original widget then
wrapping is more appropriate.

The :class:`WidgetWrap` class simplifies wrapping existing
widgets. You can create a custom widget simply by creating a subclass of
WidgetWrap and passing a widget into WidgetWrap's constructor.


This is an example of a custom widget that uses WidgetWrap:

.. literalinclude:: wmod.py
   :linenos:

The above code creates a group of RadioButtons and provides a method to
query the state of the buttons.


Widgets from Scratch
--------------------

Widgets must inherit from :class:`Widget`.
Box widgets must implement :meth:`Widget.selectable` and :meth:`Widget.render`
methods, and flow widgets must implement :meth:`Widget.selectable`,
:meth:`Widget.render` and :meth:`Widget.rows` methods.

The default :meth:`Widget.sizing` method returns a set of sizing modes supported
from ``self._sizing``, so we define ``_sizing`` attributes for our flow and
box widgets below.

.. literalinclude:: wanat.py
   :linenos:

The above code implements two widget classes. Pudding is a flow widget and
BoxPudding is a box widget. Pudding will render as much "Pudding" as will fit
in a single row, and BoxPudding will render as much "Pudding" as will fit into
the entire area given.

Note that the rows and render methods' focus parameter must have a default
value of False. Also note that for flow widgets the number of rows returned by
the rows method must match the number of rows rendered by the render method.

To improve the efficiency of your Urwid application you should be careful of
how long your ``rows()`` methods take to execute. The ``rows()`` methods may be called many
times as part of input handling and rendering operations. If you are using a
display widget that is time consuming to create you should consider caching it
to reduce its impact on performance.

It is possible to create a widget that will behave as either a flow widget or
box widget depending on what is required:

.. literalinclude:: wanat_multi.py
   :linenos:

MultiPudding will work in place of either Pudding or BoxPudding above. The
number of elements in the size tuple determines whether the containing widget
is expecting a flow widget or a box widget.


Selectable Widgets
------------------

Selectable widgets such as Edit and Button widgets allow the user to interact
with the application. A widget is selectable if its selectable method returns
True. Selectable widgets must implement the :meth:`Widget.keypress` method to
handle keyboard input.

.. literalinclude:: wsel.py

The SelectablePudding widget will display its contents in uppercase when it is
in focus, and it allows the user to "eat" the pudding by pressing each of the
letters *P*, *U*, *D*, *D*, *I*, *N* and *G* on the keyboard. When the user has
"eaten" all the pudding the widget will reset to its initial state.

Note that keys that are unhandled in the keypress method are returned so that
another widget may be able to handle them. This is a good convention to follow
unless you have a very good reason not to. In this case the *UP* and *DOWN*
keys are returned so that if this widget is in a
:class:`ListBox` the :class:`ListBox` will behave
as the user expects and change the focus or scroll the
:class:`ListBox`.


Widget Displaying the Cursor
----------------------------

Widgets that display the cursor must implement the
:meth:`Widget.get_cursor_coords` method.
Similar to the rows method for flow widgets, this method lets other widgets
make layout decisions without rendering the entire widget. The
:class:`ListBox` widget in particular uses get_cursor_coords to
make sure that the cursor is visible within its focus widget.

.. literalinclude:: wcur1.py
   :linenos:

CursorPudding will let the user move the cursor through the widget by pressing
*LEFT* and *RIGHT*. The cursor must only be added to the canvas when the widget
is in focus. The get_cursor_coords method must always return the same cursor
coordinates that render does.

A widget displaying a cursor may choose to implement :meth:`Widget.get_pref_col`.
This method
returns the preferred column for the cursor, and is called when the focus is
moving up or down off this widget.

Another optional method is :meth:`Widget.move_cursor_to_coords`. This method allows other
widgets to try to position the cursor within this widget. The
:class:`ListBox` widget uses :meth:`Widget.move_cursor_to_coords` when
changing focus and when the user pressed *PAGE UP* or *PAGE DOWN*. This method
must return ``True`` on success and ``False`` on failure. If the cursor may be
placed at any position within the row specified (not only at the exact column
specified) then this method must move the cursor to that position and return
``True``.

.. literalinclude:: wcur2.py
   :linenos:


Widget Metaclass
================

The :class:`Widget` base class has a metaclass defined that
creates a ``__super`` attribute for calling your superclass:
``self.__super`` is the same as the usual ``super(MyClassName, self)``.
This shortcut is of little use with Python 3's new ``super()`` syntax, but
will likely be retained for backwards compatibility in future versions.

This metaclass also uses :class:`MetaSignal`
to allow signals to be defined as a list of signal names
in a ``signals`` class attribute.  This is equivalent to calling
:func:`register_signal` with the class name and list of signals and all those
defined in superclasses after the class definition.

.. seealso::

    :class:`Widget metaclass WidgetMeta `


urwid-1.1.1/docs/manual/wanat_multi.py0000664000175000017500000000073612051303575017320 0ustar  ianian00000000000000import urwid

class MultiPudding(urwid.Widget):
    _sizing = frozenset(['flow', 'box'])

    def rows(self, size, focus=False):
        return 1

    def render(self, size, focus=False):
        if len(size) == 1:
            (maxcol,) = size
            maxrow = 1
        else:
            (maxcol, maxrow) = size
        num_pudding = maxcol / len("Pudding")
        return urwid.TextCanvas(["Pudding" * num_pudding] * maxrow,
                                maxcol=maxcol)
urwid-1.1.1/docs/manual/canvascache.rst0000664000175000017500000000542712051303575017415 0ustar  ianian00000000000000.. _canvas-cache:

****************
  Canvas Cache
****************

.. currentmodule:: urwid

In an Urwid application each time the screen is redrawn typically only part of
the screen actually needs to be updated. A canvas cache is used to store
visible, unchanged canvases so that not all of the visible widgets need to be
rendered for each update.

The :class:`Widget` base class uses some metaclass magic to
capture the canvas objects returned :meth:`Widget.render` is called and return
them the next time :meth:`Widget.render` is called again with the same parameters. The
:meth:`Widget._invalidate` method is provided as a way to remove cached widgets so
that changes to the widget are visible the next time the screen is redrawn.

Similar metaclass magic is used for flow widgets' :meth:`Widget.rows` method. If a
canvas for that widget with the same parameters is cached then the rows of that
canvas are returned instead of calling the widget's actual :meth:`Widget.rows` method.

Composite Canvases
==================

When container and decoration widgets are rendered, they collect the canvases
returned by their children and arrange them into a composite canvas. Composite
canvases may are nested to form a tree with the topmost widget's :meth:`Widget.render`
method returning the root of the tree. That canvas is sent to the display
module to be rendered on the screen.

Composite canvases reference the content and layout from their children,
reducing the number of copies required to build them. When a canvas is removed
from the cache by a call to :meth:`Widget._invalidate` all the direct parents of that
canvas are removed from the cache as well, forcing those widgets to be re-drawn
on the next screen update. This cascade-removal happens only once per update
(the canvas is then no longer in the cache) so batched changes to visible
widgets may be made efficiently. This is important when a user's input gets
ahead of the screen updating -- Urwid handles all the pending input first then
updates the screen with the final result, instead of falling further and
further behind.

Cache Lifetime
==============

The canvases "stored" in the canvas cache are actually weak references to the
canvases. The canvases must have a real reference somewhere for the cache to
function properly. Urwid's display modules store the currently displayed
topmost canvas for this reason. All canvases that are visible on the screen
will remain in the cache, and others will be garbage collected.

Future Work
===========

A 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
optimizations.
urwid-1.1.1/docs/manual/images/0000775000175000017500000000000012051304275015657 5ustar  ianian00000000000000urwid-1.1.1/docs/manual/images/display_modules.png0000664000175000017500000003746412051303575021602 0ustar  ianian00000000000000PNG


IHDRH"n[sBIT|dtEXtSoftwarewww.inkscape.org< IDATxyxLd 	b%%ֈVjZXJMEK-j5DD"dT,r?d9̝3~9gc0픳bn<0
ø|BEos.\|0]>.kC.g"y:ȟyu>x<o	֬~u[w]ϣn\NY*m).l仪WzoYnlYrW{m͛KG?LO>f#Qyy=f}ͻa
|=Yit_um
ޞ'yjXx=޼nǐٿq>""EhާE˕`؏z3%Js$Ӌ3Gs2Zz󻂭ݻUl۳z
&w޼FF8{ynvTk҄8-$>8"E4ۗ=ع|9i)9bso5`O\F_"tDnۊh=p=ճlٲ|4{E˕Ouw~Nݴ͇6mdմqvKmټ2ժUK,KN8STiL.]HaɤoYVL@>}T}]|Spl23X.!=^ޞ+WxA;[]#	N?Br\O?	+8m+.}QiS6KBb^TilS<>Exn333	5Ƕoi.c*ͬ#y>+$N.ܜ8
\gBs~͚'W !//Ptik!M[k?~e\œqc9?֥I1	0u[}MN:Dܙ3rvf˂{;oKܹi3M
,y[	痑bgoOÇQϏrڏh֜?wٲԃSkaP~}u.{*
ŮX,⢢X1u
[HB}(ljݻٷn-;;҆RLw#o
*\4VG9{ŅÇYfo'Gѭ[֤1^>>8}I`ԩ$&]Sv>3>MQf-|>ѨK4ݮ-avҴwoJUʰ&k@thO.ggZ6-!…jтi-r~x-N:D>}hХWHc3{Ё*1I&S/Lp׮4|+בPHayӦ<
j4>aψ`į1nߑm/YbȖ-[/勗޼YYY7c2ꅽ#ںRRK,#kE5yn׊'Nd#>IZ˞ܻLijDtLfsƎû(wfįY3ckF!5
%'vaC	߷/H]}]/<vus\Z889(ʕI,O֭)Z~ԋ)P0JPR5
l#S
oX7҅,q-Pv\E+iJ[KW^au̾kiJNCSEΟ?̱l<nZa狯LŀYMܱ/H:]8o.
^x[0e޼'7//;cZ:ZO2\ɓ??NDŊHzk<n':wP;Xھ1'OP%8
unkK=^&#֮ɱllշ	&WO)Xa*)ӋC&59ĘZnj<=|*UDl;]p~'M>+;;;*ի5k(T4'!]mrtV4}:/w`btJƒIXGМ]'㱳I:ZŗkP588}\꟦x-Jqd˖EGaïQ%JPvmV(dz/Kܙ3x-jS.6AbLȶŋhMzdX&tyr9;⇩u~\wu&:vA.D;ʾuk	ڕ'`_(@VGévu:t*Hr|Eq))_L\
*EfF&Mz$tҷ4҅?@SAA^RUPjUBB<>E)V<
WAץ5qrAΎ*}
SpvuXT+[/
k5SiIpʝb׬-ၻ7aǯ/f[&Qb%?Y/uZٿ1fuQֿ{p1濚)yMywzx\8ń
7ףMHcI:B%}ȸNA1',cܳߓ5@"8fT'Bba2rq)1]s1!׃5ÛL8֮D؞wExܮ,=V%GA܋aS)Z-;CAtò;vn۶E_GɆM[=v՛6&O?no%^QpdVޘ>TuwbbΝс~zv;{{ş9{g[SR%F\~x8yOϺw%9r_F3qc?0YS&˞JfFuRb.~<ɆM7@mΥUy'ҥ>1v/x=WQ$}D1y{Hǵ&Oe_Dž>[,>l1O	>\^+Q@BlM		(MEmA3)9FD`gwk\z? /g	F:Ⱦ57(y[OAc|nYF>00UӧX0°ߊF_">꯻aa[Ⱦؽj%UrHKI@bSl橠|<.=y2AAqs@">ߠ)BUx232Gn!|>ĞnN
/N2O~)1XB\,prq3-	!-x.{}цsprq%*-%+ٟD`14~LGs}h3
\NI^233Y_6JUSV0{X׭	Vxyב܎S
nYwelG.ggsheee~8q%w^WΝGYz)~3?xi?W?۲wu]_ZܦL=u+B^zT=jb|*C|t48'|ڭ+˧L)~uS^gD;mQ;;;Oo)Xz'Dkw;s>~ܓ7U|)PO5hxòO\ܼ
uGG77⢣p-PN ΝR뙖e7k«~Upʝg[X	i=|عl{ĢB:2W}7GELWi5p ӰwIIN&1z~uCײX,$uĊS̤rΞ8N)X(WR/
bLX,GfЏ?Zi0>ʕqp̅_Hc펹rѰKVN >\eQ;{{F74ً-1ղ%{d|}u(תV8乣}u:oox/N|t4Oa?:td˂(]JO߯ƜoY~JøOTBL<WfϞm\v9=;8ψqii$=[B6ûii^+|[&'s!.|ΛIϑE~//{	11d}+b3'		\JJ­P!kوCެ)?GLVVz Y
)ߦD gFcڷc#w>G#Peڵ
hXR[B9^lb7mN<++Ę܋iLϞbgG…oN776gwե$2үX2w_M>OO~8aSFzzvn߽܀^w79Hwv(tEby0v]ń_EV{uNNxyvn7(k焯Y{wpYXxa,-+փ^l	ީ[~7ވ-CMG7
q<ٖmGGܩp
.?$R
<An|J/m~zBWuʗ'k'33˗.@Q˨l랯޴F?
0@Ax}Lr6)fC]>)a	rX,ruOA7AR11S1yx(Pv.?6AA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDDDDLLA@DDD}vo@ll,s̱n_IHHxPMҥKy,"""8{=߷Ƚ  r~Yfp	x
&wi֭3:t(Ǐ ::֭[S@)X M4!,,Z/ۛ5kRvmxWIII`ԨQ<3w&  rjԩSiժ4lGGGΞ=KXX=z1c'0|bbb8uaaaۓ߉_…yꩧ(^͝a|7兟 003gX~W7;vкukKʕ+ǠA8s͛7oooڴic}mXX[xZϟ/.]ɓ'}߿f͚m}DEEѢE/Nb֭100ڵkSxqLzz:Oy׾K\pڈڷoOfРA65j%KB
ZOMMeȐ!+WҥK3h ._lFVVO<+L6
777	VIDATڵ+͚5#55QF1~xj׮mÃ?oo{{@EܵvҥKٲe˗/g̘18piӦ1n8f̘s;w~hŋ	

%66;v2rH-Jxx82>7jԈg}SN1o<̶mʢ{7vI:unRRRӧdˆ
.\`Μ9={2dΌ=SNo>=z={0}t.\ڵkiР|	YYY?ɓ'ӨQ;hZ>'Ora,Æ
n߷o=˖-cϞ=]	&+W.{=N<ɡCHOObȑ#өS'k7oPB;v#Fн{w233x	g8pp>k@v[....9ݻwsҥǏ3sB
k.&O̻K*U(]m2b͋+nkҥ͛
͛7gٱ}vΟ?*U8d
(@̙3ۼ2dRhQ/΂_)T֭oذa)Ry.uڴi4n{lٲ,^3gR`;#;c]P!ʔ)âE5k]M6رcqqq!W\6wKgϞ8::ҩS'$##)SkOLL]tv<$''q5TZUbŊ>"3ֿ]\\qvvuӑ#GRR%Zn7}=|9k/22bggGhh([nL2ԬY7Q{
.lDDEEY*TȦڵkR
k֬!99,oX@>}4i={v>mFŊY|9IIIdeegSFzTT+WaIbŰq^^^6sMJJ
ѤXɓ1660ki?X1bZ# yFHHurm3ob)iԨ<KÃ%K؆ڵkpB.]ȑ#۷/{%22իpI+vöN4[y0uT^ӭ[7ʌ3r
M4m|L2W^y9s믿ڔ"]{{^}U
Yw>>>DDDmggg&LO>yҼysk(m֬۷'**|}})Z(fͲQ(QNNN.FׯGfͤsN"""cҤI$$$|r,XpӺ̙{{{rMƍ`ذa$&&Ȋ+صk̘1s2vX"##dȑte=<#Μ9ӧ5jX{xxzjΝ;ѣGKqqqdzr[ޞ}ҧO?Njj*c޼y@			q,[3gΰm6ܹs믿O>СC={krȃxe_+W.l\Ncǎ=7x
*0x`j	&pQYh|e˖kժeSodd$:u'_L2899fΜ9C@@ko%W\X,/^L:uR
য়~.]'x|oVn]z-zAHH͛7g@HUÇё:u0tPFM͚5s;|IOOG<SP!֭ˠA)ӷo_wNƍiݺ5͍ȑ#K,!%%uU駟±cҬY3:wLŊyWIMM%55
6,X,̙3.]RN^}Ui޼9Zbɒ%lذ-ZȘ1c?~.>>>އ#,r,1seٶϵk㭈b1W%7rby;6lB
)Y~S>ܕt]xbȑ7~A._|O8z'O%K̙3)Sƺm۶,[0Pxő@O|4l$<<$4"##o|tt4)))EoHHH >>ۛ6mʱc$bccsܖB~/9|0[nCsx
xn:.\@͚5/2/&""ʕ+d}իyꩧطo"00J*YݻPbE4h`{n+F-ptṯ]arJ9BŊoz4˗s1ʖ-KƍX,lذN>͎;ѣ...6`ٲexzzRfM-a^ÇS|y5jd}˩Q;vرct֍5kHhh(/^cǎ`iٲ%Ŋ`deepe/_NFXx1.\UV,X+WrAKӦM)TЭ?Xy.\HFȝ;7Νcƍ<3888pY?O?
@bb"˗/'!!
P\9Xd	jՊܹstk׮RB.^ȺuW˖-ٙ-[ry-[FJJ
AAA}oݺOOOJ*VŊ0,X@-ppp`֭lٲVJ:umٵk7oˋgy'''oߎ;lڴN:qocѣ^RJѤIlBjj*W&,,???ϖ-[UdddбcG߉uַ ++ƍGll,
us̜9cǎ֭[Xd	!!!yyxԩuҥƍ#""4HׯςCtԉ
6pY¨R
+W$,,:uX/^?Ⱦ nݚih߾=QQQ={O>ږ?t֍8͛?|'tޝw}ӧOs{r6mbDFFү_?T#GHKKc޽l޼sqq:wL=8t&MClڴuQf[N'k.`>|xڴic(UƼy󈏏矷!
OOOcѢEm={QL#==0È2}a+V4j-?p@caƀ\Wɓ'
gggԩSaFffQBcʔ)aF~aÆa_ѨQ#kSSS…○?0
0dXjaѰaC㥗^ȗ/]a'O6ׯo}f+Vعsac|Ǐ7c޽68::_|L:uYfa|Ѻuk0`_ZB
ƒ%Krl;c<0b
`̞=?0y>x7_0h޼Ѷm[cĈadɹsa!!!W_}e}ݚ5k'x0زeM2j׮m|w93  Ϝ9ӨPaq0nj޽{wo߾3f0|||0uʕ30&Nhm֨^a駟ZӱcGcȑ׵e޽d}e˖ĉkѢō]zXx뭷???#++0XrQLŋ[aƶm;;;#<<0È1c2מSF}sbXXb}|N:_0W^sɓHKK~45p'N@fͮ۶m66lC!+R޽ʕ+,b*U6lM6<:t-[Ҿ}{
.̮]([,%JΎFsNza7gsnր\]]puunݺڵ:P^tl6l@RR8p~~~9䄯
W=FQQQ9f8ڲΝ?6m"==d_pp0C!==]vpBo͛3g}¶mۀ쩠0ȕ_Y,5jիWvqt:d'mƨQlw҅$22UVѷo_^~eXj?<;vk׮lٲ-Zо}{ټy30ZDD>K3	No*UkBZovS6lH"6+Wp)ʖ-{Ou/)\Dl:}a5+W.ZO`` 'N`̙3aÆqWVVMWfMзo_ի7X,TVfe߾}6y.cz6{{{_}Oplݺyvvvʕz|r6mČ3

bٲe|,?<իWgt҅ŋSF
4iBrr2k֬!44_~y7ضmW-w'88oe>37o޽{)W;w+Wٿ0ywZ*۷oɉ_|}-Z3fX%dСҡC<==l[oYڵ+iӦ
*U[Rti|}}ɝ;7mȞxW
rgZj/LZӧ{ xiܸ1!!!T^gϲ}v?dff~zN:O?0ȩ(<;911S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11S11sX,"@@Lð̙c<ƈȃhj@DDDDDLLA@DDTIENDB`urwid-1.1.1/docs/manual/images/urwid_widgets_2.png0000664000175000017500000016615012051303575021501 0ustar  ianian00000000000000PNG


IHDR@h~0sBIT|d	pHYs

B(xtEXtSoftwarewww.inkscape.org< IDATxw\UׇPq\(͆Vfee4JSsefj,M[VjEAQD)sly?{|·{y
B!BJ@4e!B!ݤL]!B!(+	!B!*
	B!@B!BJC !B!D!B!ҐH!BQiH$B!4$B!BT	!B!*
	B!@B!BJC !B!D!B!Ұ0u(U4M;f
J#iZ-4,B!RJi.Cei*IzQL*rp8jv$ǨȔRiil?44R04"&N4-}&uO5MZ
j|iZ;CR24M.c!bPJi6u1*AJ)Jm65M;\JǬHRE07R5MK(7((FTUC:0.nx<0qYB!D)+1@W pH)f)^p7u!@@6K =PQNGӴk#Z@hX!BP hfi1M~4aq@*KtRηzLTuTMF)ku[RRoii
v;@)uygzЃõiZV143Hz!Mq;ɫRK)ը4B!ef4W;CܮrSJ$SJ]PJ=WT~J)"@pY)S`Jo
3z|*RS
3egR*HB5\TSR`/`owRioOp<46
ѻ3FnsM^S:zׁ3zpXޠvB7
h
4i@cC99=zlxo~Ǧ|tIbf(/zxh(L j57wTJ9hhx=v*C@l
e3/pS; 
lAS'5	Fǰ)p~C?'˔>.kaR@V*E$><B!ZY@guR6E04mKJ
	Jy&x=
x*XлYy>=Yiy^HU)MSPwkNdDR|BYw>E<ICcbҥș-R1//آ^SAe*.QlJ{An:MRRW)wr>u5SRʞr0.}Oz(iӹ/Uc܇ٍ<q6,{ޅS
71ݿF7_ڐ#:Nf́i;lHMZbgø|Hzf;^Ođ]dFZw셥խ~^zBly!5Mz>nx9m>ڭ~giM4Mi8M&{D]mcl+ACvDv'n-TvlAy/4RʒAIy/\R1>ɵULE4-0iB}æ(P!\Zj
gNív}k^Ą	?Ěog 5%;{G~Z:?V/̚*6v=/L~g(+M 9)>WAh?V-QOp)~:wޙHg+c"N阧=5.K9fٵegxGطm=N$3#	9qBTʍ2r˳)}ƴ4R*VJhMӒR1RQӴwogΜ=?}>	&*訔4-v¢ۀAwz5Mpfa[h4"=j>C^~5MKV>J)g{ںέtc[R'}B!D%7gr\4M[\ʏLiiپְ_'^xw&/ݗQ݃aqpr)jdftt#al³i+TojJ2Lg댖o|֍kbַ8u_`ݼI$O.>-;|'t9|62߰rL)p4<=zURyGJ7
}fw
c>C@پn"g^s+x<q?Ȼ~ބ-^>;d=W =gM#A1i&ScT:~ǪRɢ24\[y^*\)}=w1,B!*1,dΊjs]R
[zƿ@GҢm"wpvApNvCoB8~hg"ϟA44ׇ+33b@]ϽAT	>ΗGQ۾p23?V ÔĠ0\݅ϛ@ӴlUJ-EoD'i
AntȐl&$Z.
Rp6_[,tǵwqB|iZQṭ
GW4(@odGkVh<5y33݇>OQ;/g,Ͷ"䉾8譌AӴGRߠϰ\;/>2э3并
nETNFúC
aoR{{,o`/BJvձΐ2jh7/bffNvvcr?qd7v;uc4o۝vrޚ8kWw2rB+334lrj"9)w̌|]4MMx6<-zZ);@

⹀i#躄z3//HXCIݏ:7;O=hhγo"zCw\?b}v@M7tлB1ݟ4MۈHj"0pnr@WSw[mlI&H(Msiڇ>6`-8-z@5enA_[^[CMə}݀}f@~̰B!D>AZj۠V׿{ͺ}[n
]qtVa1γ~bmѶwl r,yҢmwHxT'VӿʺΉ#}:sޭu=iפy;}6~RkW΢Sֽ4uArf@FKs(j<[OllqaZl_oC
N}CtNcUAodgA7z?*"{:SHN
ɰyB!Dv/*%tG!B!(PB!<.pB!B]]QܘtB!̓M	!B!*
	B!@B!BJC !B!D!B!ҐH!BQiH$B!4J{cمA]"L];f[#%qp湿;Q?(q{iwIE&VIaPQ+Ri!BSOE7SRr;(933zjdKc'o迗F}BǞ/es̠J}=߶pR];ժ;_?w$MFXZZ0玲hӹqہ@0Դt!w
ZKsѳ}+v+}FPXZCy볯_s&SR9w1
k++뻗J/>>,_xTFΆ@j[
u3⯗;Q`ֲ5dkطLte);D%eegWٲ|6
mk^yy*qQ|J]RiB[0bt>m癕uب7L^!6W033ʪ
yA(P
++k̔J]Eu4/ukeVnQgό[AfgὛyiE܏̌tZ7YURS?8ӦSo,pqs3
/DٺQoUZm\c 1A 9Atd8C;~Fl	jٵGGMRJ{}ӥ޲+	q(ߺDP-)M\,t~Tֿd9Msrq[֖ᝃp/Eml1kH
!3:mȘxƶj'Cg4jPil|5
O`7nX|N$]=Z45ީŦ݇ݩ5{9:31Fz2$\D'РNM=yxկRP:ݕdIN}U4 Rnp]{6^ߛF󥋋}$'%PaS\(?kfrp?̘0~bAXzZ*Go%>"5jҲ]wg}Nc688|5{133mS$@-
:~*ÁpnOmZYYlsLω0?g._J^:M|b~>Nʒraڸ:9̻y:cAq&3:vZj=۷"RjRE3oh׼	ͼ݇9V'?"82\jQA#ĮHOK%9)UKXZZg܂{PRS+\|=mX	u5lo[YXw's:d/F-glVtXe7#'zV[IvΛtvXYW5/q=3o~;Ϳ=w$[lڀY2]혲{B/|{4SGG~@*l]>ǸO3Q~ɦ=HrX~7Sɕ<{(07#zff̝דt^?
?
?K1X㾗v# IDATt5
^LNjŲ7g|֭żP(;ӿfp&nΎsywL5O#hr5ߝUm;=iCxtn5>ˣp)6>n-W=25~I^|?'ϜC4er٧-ʯ~j>
'Y=NV>!G߿e_~DZj
Qmlc%J)N^̇Vs
**K<ԫ뿙m*?It|K'sЊ̳,ݫ<Ow卧|~>Kw؜/0up~ȟ{o
ǓoM7Hm1#!]3fʌESYÉzVfѧ`eiɘi_GIdބQvߞE\ɁA}ѥMs23]/#;[gddf+M*62v%|:Y]sNMWC.mIo~/}w7,͖Q
FMGbR2SǼBU<@f3Y$4WBq6'_٫ػ/gxof##=gB̾|yF<ޖ`Ϩj7G\t$]z?{ȳ\Icޔ0x8O=|<)m[O]`	~*#?x?7+Zj-\)2]}<%'۶`ǡ>p@{=5kLdL<1>5HRr
ǂNcogK:
7Gϲ۲'B<>"+gϹQ/YMMX5{"zSd^J)&Ƅè\ئPfw,3@F0BaTBO%Rii\4(0e{6/gƼN7٘νeܔ;86GUR=ݗ>P|<߰psvGXPw^n}d(okվ<23	=ycwt )7Gv׽7/=P|ٺAֽct(?[7MtFٵkK-+:
rvs~?g1䁁{CUX~Դt/Eۯywֶ6P@r=F5;[o݋i^43'I|׭=uc-db=~wػ;cƭx^Dd}Җqf=9bʒ#!,\ǃp9SoyqشO
5
yYGFO[y#Һ9_yo=gSXBj$_M3\"-5KK+~]O$Gvs߃Cyxѭc\W	xQaN!P'?Vr1xYV/}:&:>־
iiiAǖ>Sͻxxalƿ{roJq~=iԠ.7f	lwc~=alXo7/\2r.l
33EzF&wzoV7ѓ>1_>ن}=+ia>z$&%3iw|eQwIKug"#Ә[Toj!!Go3@bx([C7qDhr\hO0~"VW?TKiIs[+*~m>֝zPӶעegRz>HAHgؠ:ze_Cwm^lj#{H7mv?yܐ",O΄OsZM|Mn,Alܱ?6Uܺffgl}[ә׮qzߵcvJ5p_'=:ٸ
:5qrG4f}|{TТ'w l]f#!,Yؾ>IGάv1S8w_~w^BV>xc*oϕƟkWn;-FzZ*=}xx7-49S|%,l>m>Njdf3cpRSxnsdggY$%]w}wlge߾q=5k8Q+_,nm[Pڊuk?Vĝ9!kބmeӵMtԭZMqq{gf6^|3ĵoф'Bhԛ*VTmUXٕ.ay88R7N4}̧ca߱m8ƺJU{x;#ynT8ƞ<ַX݇HeidJ]}3qo;wʅu>,{=̖VomϡCгD=3Xˡ[>v-ر•{3X[ԛK35iX)V?(zoɃ;r9J2L_`ݱz5yss7jc7?ڤI
y7ߝ8ޜ2+KK~6\L_|im#}ye~۴W?oWac/Yv=K֮k}׿z/tBU=?
@mܸ4϶`>aaku^N:^yޏvvs?u4F'-ȷm]w>3e0cfo\3A|d 3^5Sd.(ߪV7_ӿ{8}5]װ{,y]67>7y7 -#bmeI-g,pCi>}%^4t&gw[5'hؓl7gkxO4d$;!)O/073^]x|Gx{Cu3hY;;@L>U_XHёDbBI	Ԫび>$>&prWVA3?@\L$%;;u.Y4[΄_&
x5簱B&z4=i阛r5|j۰r;}nYi8kJ~}Ehܼ-V^IrO@Yyhܐe+n4^0Ҭ?w@J%[%GŕN$SzԀ^.Fg蕇4^0?R!*{"蕇4^0?R!*
WxtBJE>_TL%N!B!*vOtB!B[!B!ҐH!BQiH$B!4$B!BT	!B!*
	B!FBUJiQQ>t(>mr}C܈\FrVI@BF6(mO]^PnOVi]?^bZ!]{Ӡn|ܛJn.1u*%d.pT=S.)G!(J([	ǝ.G!4u1?B!NU oYYY,Z	~B\U
8+O97uT&Nș3gL]!$B!hYjq7+aȑ.?B!DQdP9א0jebx

`߾}_T2GTf|WL>֭[3m4~>7n䣏>;XZZ2uT1c}1ٰa1`̘1X[[gвeK̙C^i/L޽{xehтsr}L:={obooϧ~
@f͘?>=z0Yn3f`׮]5
GGG>c|}}Y`ݻw7ߘ={6;w7Ņ>MxbvjL믿2w\o믿'NI&,].]̟?[g{Gd۳&4=Fi8;U+c=~boqc>%tlsO(rJHFk4Z|JJZIRJiܸy]]4i49sO?믿N۶my衇L]?~P7!4]|177'&&Ύ
`ܧYfsUԩRiΟ?ORR1ϟ7iذ!Ǐ/6;4xzzKKDD
xyyqر|i.\+Wi,,,/6/^4]64666x{{4Ν3iԨDFFhLSfM<r)d^c/M>Sf{[R+֝>v	'͹1vv^q硜,P9u+(R=`6o̤IL]$QGgg|j*sOO"(MÆ
qrr*i4hP(cw+M}
>(^7ⅾnjs3R2g1lmΑy=v~uy"(qtZcS57h:s6ۏӠ+Ŗak9QN
4M㏍hƋZndd\g<ҿ-mhYۘN4υެPQ	lt';ׂ-Op6k.DƳe	4M_Vtu j*bmmA>~w*8-I#嗦i6+I)TboژDA2S,Y=Ƒt5>{?𳟊=g
;Πa3Y?8ŸI?bPh1#
]lx~=??yt6L0νAl_w	8ytΜ&5
X)O3^ƕSCQH#K>2P{{-gL^ff,	c'gc4}-}}Ka[yy {{ώGBbreӳ&͖=sXwSvlBph$qWٲy!/!%5-Js4,~-h[v;z,/=;}|Gr):_Ϻyo!	7 Ew $L4RL 	~|fWEMڱso0m;MJj:6UƦ5x}
R	$L C/sthEٰ(ѳo4f/xkkK&_MN3opbb2%pθG~_Դ4Mc/wg.+{fuw&&6ޯT±JFш7uQD	-X#GJǎe
 qI#DUw׽FI%_Kg3a4Ò/_ůǻ\gƜ)/ս
rY~#^ICO#:&1pEݣ9M[zwoΈaܛszx/>F#ٶ+G|7ussX
>Ҿ!g.Q3g.p>>ÍEs^)w:@&RZT~LGps	cl;>_¹;N?eF](wqU?{8PEEP̲҆lĭlXfYjeZl82+Ss
RsA |߯}]9<<9>[5+NBA/B&P9~js"nCrq*ZfFz%%^}#}Wiv|
Z/-5}'sWf;3#-+wfVYY	a{SQEdP9G\
²	=R0hHdXZY3YL}y-,>ϟҒF7WwO^}߮:D~V,FRRM=Z3/\&#-7ּ4zrܱq5gO+ش,>fȤClذf]Rf.6Sg4w	YYxp.MӘW>]6dW!0$aЯK{{螊2Q0	ȌZsZ$TvUmH.NE;?R΄
unWk08ҤWn+k[yp?nظ}&-50NUlT߾i\&11v`SZ䱽鈇%\H4t;rڴ_|:mMPH8Ok8Ofn!D^r}h5>:+anaY:qSOj׭g\fF`Kb$33M.N%FHWbchЙq*KD] ++?g3畉	p+3c&LLL2
#T=}Ztmp!Ah6z.$^
1~G\cz^1I`gGg䓃sm+bYZV'ZJ(NEB'A*bh%
~6=_V-mtuY?~i07@Q	-INJ$6[)%olXz؝si2}n͵HM;ٺagsYX!{b&~߰!akѱWqsrklرsa%QiQj]HXp`eIINm\vxZ7ф9Așp읃ӇH9{pĕ~@?Fzz
՛?7ͽ%h؝	_ɍn׳ŀҤ[MVfvz8]|r$
wG/,,|v\Z=bWauϾAk߮<9|<a|ijv)]O 8<9^G{?z:Tt'^4ßN%>1+$!):6(mB/F.""*CE?O$&\3H'?Qz-xw#22 
4M+ϣm@)MӴW,ݶY|47ILǶV]b"3˕=-jۙͽ7p(_|0AOcnaIXp 
g^g;1Q̽toESFl}o+VXYZaf*͛$%%ưG9vBE~e96JV7-1{UHOOc浸5gp9&/(}FIgBO1g*{-aLJtw,,׿1R폖>vuKSţ/\A,Sni(emGC(j9jl4a!|*'SoRyŷh_~tDׯ]eL?,2yhBYm/ʊwݼiſYq[nOUfjjJRB<6ŷSo|;~ӂL{23s-{I4riZh~vkddf2up<]Z]!-Tl<[4qݶV<747Z@'ţoOo?NO@Vָ{W\Q66ݴō+4
Mˣ	nNlݧ_e7?	7gSӈ\QoOf$LLL+l?ֿqy~hi%Ņ/w#'K+xY-8o+stJ޺
ΕUQ\rPKN0}6tƑRzvğ	mAuǒ"95x>^ܨ^i6#O&߿#***@y?cC߶jz'Wf|8uv6zY^GZnGoԜ$-&S2,ۓOƎ
9x"jъ p0`HBrđ=)&&ܴg7lIannQyG!:*l
]7yLMoTBT[0*1ؐHCҨ%)))DFF{"*?yi
Bϐp=8z#ߙA
xi^̜e$O`)e}<1{4tF>5h]ʪy:*/oi)ѵ=vk0ѩa\r-!ظ\
y*/O)IlwUfy۲/]>;7!)1>ߝnRmb%ޞMtKOKeǦtwZfoXsH;V6$%pdlg9DG?/߾#)zܹi-KkW=8NJL`ٰfl&%9U_HOOc?pߢֵԮek<~"<2Ƹl\&)9}s"䂞*6:*sXͦ=eS|0{f/
>T7\AffwwoYGB|\uۿTeABhPc"uRowاJ]VLi}9$[[bc[X>RBTurh߾};#GĖ-[Cܺu+AAA,]3fХKNs>\iո~n{Ɲ8;?=:ҿGrܞSGqpr'%^XZYr:aaLٵɉ	88baiœ#;tI	`ec˅gn+o8n=|x
=]]˸{eeq>8[3xM}0
*z|Dtl=g.bnfFgGNЉ Ѱ^]k@88cjDK887q#/x?Pvl]K|zPV~'Gcɷs!F?kܖwXbc[3ss|z`emKZj2ٻ077c֒_y{<ǎC3oWNfYS"/]aػM;I?խCǙW^{fǃBc.Lx+בЁ}	!-#|fJVf&n\JrN	OXw@?YXYck[3sֳއ_4|'fxW(==Cdeeڷx-JJr"W.EAzz!A':t9KKkc.?Mc@ptMjl:NVtAaaiMKrtC|ڟν)aiU(Cvݡ؄1lȀrfupeFsҪM'>sY0}<\ҪM'[0טx#/2h׃5A\
/~ͽ%}͟2&iب19K6LLyԷwjlx),p`jNٿoF3u,`Ӻ|?Y|.o}年đDZ6/47`uІ?#b$GjZ:/9.dffɬFN|b{Crn۱wȥ)>'%-5+pprR807+k<;k0!++uDӴ|}߁r>HIJ$BN137ܸĄkdf1XVbkcb?;ƳYF
IȤ'AZIIM#!)d,oh&$^g?OG<|qb;Պ[X2icԷoDzZ*ϟea,+oz8ypƻ2ϗy!tNE;{	z
нeL?ȯB.]sN.\_|A~8qbu&MDdd$ƍcQbE
THsNzC_̣ύ 9:8+6ѫ?94ENKKiK}iӑFO&==sDs\xs>ԱГN`;m;ƕp,IkgxύYf>KgGٱۼ>-ygē.S(Ȥ9j@Cg^/G**$UÇs1}|=;v~Ԕd wd?w{y-!1mT?l'K3A֜г@G[иgjoq™1Ql;	q;_l=mjQ׮A137S{?;бy ڴ뾯׍m>-n48ƶ6~
OA"8c׀=)˥L?)xq0]GNZokIVVV2WjG{sH;?Ww=T<Ƭſg5ˬ~ZDi=ED9Do]a0͏L1Ͽ;W׵}AQZDpyX5bV9mr̽pGI=ߘv΍kUG&\[;zjۑeAǍn{cK^8nߍywRޝ>_n̈haiEfF=TG^׮xFSBTerMks{ϯ晑Mkx~%mZGj,-qo]}iDL,W!08G!08^{Sn|Z<ðSx{D7kٖc8Ohf,ĿkʃO«CyXt6'33ޜᅇ2lwp!*uLa sΜ_#z"|4
Mg߱S|r}v|s>Ŀ6,e96Њ5M86*z9M=?w]?.?]q_	@jӉxb/EwwcgOJr"GmɕucjfF}F=I#ydHZBRB`kc>`W\H7ꅇa[Scwbc?'W<}:aFOs?*C;QԴmc$폑C2c&Rv'O_Nff&|G3a*(JI&&&&ST!
Ҫ>3uRSHKMm^	8̱yqؐX΃ٗMw2==Sγd;(xpk7n-[~X	O\Q,T>ﶵb[o3sscC&5%3̓#{=bSN	Msfk#;RܕKն9>~o4~7WG#?1>{+'=41Qf	LLL%gbk_/ dd'i
	:qZuѰ;{|
`~7711ŷsk0n#!>\5-Z߸X߾Q`)o&9G&ew
bc"}88qhf?t2v
=arr甈Joq폜ķs\?vWiR#2UTj?ҪmgZvҺ;{rgdfafz#&/%IIqJMKe37z~>4qqt4nK+kxeb"ù{33ޤK=;`cmIff&Gԭ]
[Onjegx+o<
.wyT>)Ѫm|sԛ|Jty2/Nz>,!njsw[;x._c3SSj0uNįP0㝑diY|GzthkE^<@>Lff
=}>a:+E W?V/UzYJ)_ZCLz=8Tq]V~Cb㮑c>!qv~DRinz~ac[cQ|Քpv:cZES- eK"`FY
KzuILNYг3Y.:$A$)حJ-#=3Cּ2315˽<==̌toL
eete2)=/E֘mݳOBm૱O=eeim{vƽ̤KnєGU.=#s3Ӽ !)Z6E9w<;_Na};f.ƍ2t`fC!
U] ܬ®/C(ATe6	BN
~Ξ})vc`3[T~gp9&eB+Mh٦#}	s>y+deeҢu{>?؁mXXZajfϾ߿'33.b>NoӮs_2IIMҙXĀDZǯr#wdޏkD8jX{>v>κvkaanBdL,[%q΅Gχ=/L`J\<fG(Բ%)%ոA}̅ɝ!J담?X:E9Yw'_zw-~Jbk_][j2	~*_fV&!gN0]ѳ`YX	KK+李ccb"Ù<MlZ#ם̌\.mk3m_\Ȕ\2VІ[2/Vӯk{\M?714ui]r%ݣ#{wb%O__L #3Kv*&&\}Z2q%.'LgOXy;3hN	ack҈sa
K!JKUoqP-&Sg %OѦCO㤏hE3Ocƫ=ЮM<3r|>M9	|~]`a8yOnw3-r>l=;s!Cuqq12J+(bbן[p.<gHIճ>Ϙ)pݖ=85_p:8+ql}Zؠqۖ$iZe!!JO/BTj:9)(8U-"s]88{)ʸrEfc?^@\lO^+x79{3yy+}{0RQn\jFff&oNڵpoowtn~>;_|o<ޛ8;ܚ8;2m{G{"/Ϋ9)i\O0v-!zujK#D!!jI#B.NUOHq6^mm;s>چTe!.l;}Bz$Mf
fff$n4riʸ`bbJ6Yz.߲OƼSlꊕ.:
{w)cʏ6{18
@:cmi~S4o£W~?RR۹Is
ՙXӣ>KξԷqg7?<`k@z|r"DRm`ǐjBY`݊4[<;X%C/F1Z4i*>!9%f-bֻy[	~$
(JMJ]/[Te&
v]ъQ/N7mjU>J-4Vh}ʋe?B\_n.&T4>*\6O_M$!JO/BT]u$aHwʮHh#DeUmG+%
T%Q3&U󏨙""*fKDFFqGqӤqR49ѱrz*;U	~%Gғ*&(iM?:qqqsvv*	sFQ=ǓHɂ*8)Duu%ҹߑyPb%Uޔ)S9w\eWE Gғ*x$Ү^ʲeˈaȑlذ$Iq#j:A@+™3gػw/ׯZlM?)|$4Ms۳rJ^}UV2	~&!5Y#i|T/J)ƌC`` 6m⣏>*hM?B!j"	Bu"!*?EB!-%!*?EBƇGG!A4Mc֬YG}Dǎ4hPeWFhrB!r;@L՟R3o<._̬YPJUvj	~&!"?TQs|>|3gеkWhr5YDDWfڴi1uTn:7'Nd7sssL@>}:wu_ŤIطocǎҒ>___f͚wa,駟gF-'Om۶̞=~ˬ[)S{nx
ԩ'|ͣO>2k׮eܹ_z1i$ݻի9s&;v`ԨQ4lؐ'…ٳ̪U={6۶mW_ё?VZwѣGc~ye@;/22/P
MӴR\*jѻWWȶ+ڭj|C+le96ۨ㣪8M?B!)$XQŔ 򪋨zECEQI *JdB!BہdB!B(	!B!j	B!5@B!BC !B!D!B!ƐH!BQcH$B!1$B!B	!B!j	B!5@B!BC !B!D!B!ƐH!BQcH$B!1$B!B	!B!j	B!5@B!BC !B!D!B!ƐH!BQcHtRfJvJf]ʢr5|7QRMEK!B(@4M2@C kR^*a}L CӴr؞3p[Ӵ{˺
L5M{S4-ú隦SJ-^:ivifWʷR*J)SH4-V[!BT-J)\)=r`,	gR_>K7|q[%hvxK)R*
hh'<XՊ"reWD!BTrR>a#p4^4o6,}V(	x+rZ =)u?pPӴò1B!<4M{^Ӵ4MK4miQm@)er*̕R;t*
[OӴ,MӢ4MZm)pJfJ:7YogTbVjx[k}^.y_Ϲ
4M7|v)4|J)bꕳRiIQU)p
؎ojX	!B\ `4}]SJmRJ5*bs#(.)TJ0#_IT}ԏJk@RJg_/
~obXR7 bRaJB!=I
ϋ5MKɲˀtGߑiZjt]F2
nÁ@ow?U}Zԃ	P[XN6SЃEcOGN)iZVvT`z2ywb_J4M}LCjzeE.-RNE^ex?
3zYRbl2'16O`zqfORM4M{ǰ:Aõ7Ǧϫ?U~|
LB!ՙieyk
x&4;ɱ7Y/7}"Dzx̞fjv&'BQ'O٢Msh8))K7Q4MVJ-
I	:-j
}R])LC1[SJEwmfMDWU~Ab!z׼ؖB!+:mxE]>=Y^
|r4=iZJ6PEis#3]:w%ܞI_`'z=%`ۑw3,[wXp#ʻڍDebn7M)5=ã[:N=	!BbeQJbal_@h#eg7ǐ8]@c>	hE9ex[kṏiZlJ;~y';^4(Z.;=ű`_J筏i4MҀ!*HB!6V.;MӢѓ(`R&JAϲVf~hR&NqEI6_EVQDO^>J<=V:ѻ/OdO.']`6z҂i9ߧR={[.FOLE)BATJY*jXTsިл%I"!B<_
حZޥ*
=ErgqghI)xآZQBeERI~0d[lRj	z{ѻ;AӴDpەRs
uKBO
x=)C:q@M
Zv8.k%iR+ǀMJAS%`'?*CwX)?w7<׹ IDAT6?`x}R4=,RJ-7,DON(z%yB!Qnng:z*GsoX>#OGO'1͞P5}ben89~wc?>
s܃rB C6)ڣceE=FBm%iz@Fx5ʹiJ;OJ
>yʄ*Ѓ'rЃ
+b?es-9Td.qFB!Pbr߰>9}|HaIaj[9fg5M+,+]E=
z?V*]d	~B!Dy;@R)桏khXeMӖWZńB!D#]DC+vxB!(@B!BC),B!BC !B!D!B!ƐH!BQcH$B!1$B!B	!B!j	B!5@B!BC !B!DaV
GE#Wq%Q&pr|Q
 LJ(TY=k"R աq+*B!C
 [Q9>B!*@L(r|!BT.	ʑ4nEQB!|i܊!BQ5HTq+"LJB!D!PIVE!BDjJ2;7&<$h܄w=Scb^f{133g/{oAmKNe6Q&ԳoD=hֲlǴ؛Q"?3?zLre%ռd(GN04<>}5sgR?4>7meeer)zF
9yC{6PXZYcؘN=ʺߒiݼ\}ti](ܪ7?зs;f3u;0f3۱i!qRERhd=ŖRL+7EeȷԌߚF+C5--KQս)r|]acϏ	jG;aaaiJϘaw`痠CZee}
<ˈ	_p>"xxc#551›IQp!)luūηޘï
	I\ĕey[&Kޙ&^,m+*O@I	৹W;~
:tagٳuq=M?ag]־]iبq.өݘH\ޒ=ٻ?㟛!S$"CGjjYUTZEYni6A%$}\n&H%<{=ϸ&tvhOvfO&n|.VBZrmCS<;D\4%'C	R-L{,\=Co[y0
{`&>&2بP@K6X8^!4T*/$t5i~MrZ&l27!&>	pؐ7%3	+{{2yb_w_}Ċ-Lj
읚chb[7H".*D	mdQZNYثJnF_ft89Yhj5c34uG*{R=QQ5]"/'GIN -?g.ީ]il
ȂƵ@v
xo2z]%ϒ"3ݧov׮yCp>yX6%#=L^30b>Yگ{΄j+ڞ+̝2Ү?W9VtR\TH^N6q4<;bni
qxÁ?0hd˧Yx?7<9Xh|U'b12ds7iE*z%DGӫ6PyHJY>w&qccZwz]:yɟShʦ311Đ1*+WC"4ovʇo
3O6l[yo44}7PUAG^{,1,t"c+A,F`'v^;˧G0{(ٸ\bâ"zB~O53MIlT(C	oޟ
+fpiaЌ=
uӾ(/Y[No̭T-6i	
AgG_r-_=ˌ6ұ`
X4m$l+?RNpS62?wܸHG7Vi_SZZUs'gMݎK{'~?lߓ|ġؽz>	gzh#υ'sAJS*]ZsNډgݾ+6hȃgE?ug1LՉ:s6:2qS2ooh1|4~;~gx z?~RvV:ɭ|<śe\>pO,gT
KNv3s/5|C=?dXymu䡭?
JKK5-`ϖDGaԜ50Eqr3qiގ?%3=ogZ	q;kyq!f._n
cpq`ѩJ~??Ldl<-8iDrzf>}9N]
6m}cNofGIi)sLJF]Mp)JJ\D"[w~jfXǑkƆ%Z7QRR©?)/+%3-KhѾ.z~s	ed}Le
LV?[[=̡(,ȣK qJhӹ/
uOx:mRS ?Ry݃u3.4qbצ%.*nBOIS7)lDYYo
Xh*ٙid%ɻ*5ZѼMW/Lun0f4sG{ֱeݲJ#xOEy)2e\U8uI?uנhc<{|-ku͟
HKWڹt%;]kjq6w9V\TȷBQa7"l߳޸Pk\[ҙ7
욺١'_*M*7wO1{ƉC;8yh'.-ӭpƒdt;;glٽy9yy.?`s^D"0}Fuhܛ )5ҲrTUY r
hlcߐ/1?
ݟܠO6x:ͻΩoKtn+_nliV%
aj;mŝ^G{OjǵNMgD8Cpd97H_)>WZYO\R2c`dg?$G#{6ڽyYk}1q=̏9v<<V~X	7ZHRSiա'6tGu	4jlȺ))V;H(Wxee%[17{pK+*"vZbN_74U[)lBII~Z~3b`l3|I$FLm]ld*o`\M
|}l}޹Ғb,ٙ6l\9]c~,AvUHe׆瘎AsLWϨVޟP}F6\>0?vmZƙk2=yO\OI>Q7c\y%J;wMP\T(z-JI[ZB%۶d3wS&<\Vy++Mlgügg;x$~jjy.|`
e!QRBڽK046eyg4oy<tAg#2*wDaAZ:VmUSא_SgAH_tjٷv!Fges'GRۻC'S
Nׇ2n
}z4\Dݯ$޺AvV:ZZ: CT߿FdgҽM]@wPʶS-ԎvVIsQMRًVC2RoSX`^.*t.,#iNn
yvmwxEhj5޽jݲ$'kuؼsc#3yhI沇S՛!8JGW<']/;HvnS~׵ߏJpvݼ|45qoe:}m45柳ՌgǑ?w*6@<;@Д+gs3NnwȾA7u}+N߮R^V]z%#6eczG~BiI1O/<:m()1s&-Uo,'D] Y>{ |0[tHMo#"~@vqh0:yp8~NaQ{`'fy<;b)ڳY(R=:ݷ~^<pmўKUL-IMq{yxng^sLC_-^voPXcp4Gp5hhjУ(+e>cӒ(4qts>n7&@S!]'[0y2<ڽƲI=:$a\>Wټ6>˓:ΫNr=a/S#R3j\Əƾ_u2h|Pg).eNEv-duGf=⍊2[x2eef.w?z{Ю[Z#WYaA3܇o63sBj?DLm܊#NmPФ@FK[n}s&&y*@*Jk܇4Mx;>\LͭiJ;[tDllm]]=KKKDI0mFjQ~8$,1!3=8tu TTU/"Bf6P#2ԗR[zFE~֚"p;5ux8ba߃;CߣΏR"Bdp9"Fhcc|olX8;WNO܍0
s72ɭu~I?2N^]lO%';זH}<4EGϐ[1޽C#{MHOI$5)}Cc,mkk$'q3:prk-*aH{gr~[fder#S,SXGtd0iIhhjꁾf5R\['+#8*abnUe7vC]\<3/.*Qc%sUGڥUq61MוEfvciE3)S3s6z:85u-.)o0thٌd
qjb	ɤgbmsvj:nbbuԠhbnɦnPeLϥpR7@OZ<\뾧RR)q7HEEUTtb`dFS֨kh"J	uuneeYÃׯSWTT*YDSVVssy}&7ĵe[^EOsH$g'r[Y\q~T(u	߳
I_*c]8ARb,KiVϻX@u>[Q! ³$1_6r+<8?AAxϻ/1}=r+<8?AA^"BTnG  DVxq~  \z8AAA@"H_ AAA^"AA! AAA^"AA! AAA^"AA! AAA^*OD"FAF8?^nE£Cxq~S@.F6Ϋ[Rѫi"+w1iZ'ZGxCx:?Қq%I:L;xիOs|jj~*	32?    % AxFD# H "(_w1+	B  /ֲk2Rn|EynD$uH?  (r9stwda]F@PGD# /vR)e]6ErB,Q_N\brMLI'>)6N_|2=vj:|lHb`x/.)J#D\UX;5JOaQ1O3k\A`߳deMF퇦mRb*ZA|9FM`+Ǥdd_s-\RPXLjƝZWħ3	m#m _ރV͚>UĔtڵpaߧ*]bJ:kٹy,}NB@|!c%|XpCry<>H^p%8'ϓVi]Q!Laݳ(w]ʹɾ_VsTT޽_7p|i);Qoq-X$';?wʹ#2ӓ|0u_;Z㑭HG~"xz䏭q;HN{*H4}{:0V{=Q/	Mrb$"u-kW IDAT
_)OODrDNvz?
n}cnRƒ.wYf/yYѯK[
v+KMؖڎ@+s:t}URZʼ՛|~?vN>-3տcySh>oM_̎*s
v^>Ǿ#gNj㣯^k\jo*l}|VR*g/3ݬN,Yۡ;zwf/% <
Dq.k~iX̔qK'g2`_Rm2]ZU^2&/74/߹_%u1UZgoNbajȩm߲{|>[	TCzvBYIR_+6?oN^~읇N5a/VR^Q!T4BZ>]=V +#9chlFҭ}@MѣGG,hd눮!<:bjnMj->`0&VF[Sq4nFae_biCw(dg;.Cy
yys~`Ê٬[>sjhJK;eއeDV>mR-EC,6;7~Ⱥ-dfÊYG⮓LXFk2b"(g\$>6Kއ"';a$Ƭ¶\

s+wiTv^5'.z6,η[+.ZCK~fukݬ){.dW9s|u/R)X9rRZ&̜bo٪PXȚuKTWɄ^q#{_v-YهldWC"ƶes01gw[⭁=ާۖ}#	$ɂmEOG_jh$;>	@88Ϛ?oؼ
55OӭMmUgиעo=!y%&ɒ9sgɟocf|W3a'y>k@1+i_R^V^e>ttG"Ł\n>~XϙAk%Yk͕*Avus8qa^jk1g'@n;޿5f?6wʋTn5s?bU}r:Bn;sST1F6JLKª	8zbbnE&45C;Jim&";3L1|4:{Үk?BC׼U}woN"<"E`h\^ȽUgB|(/+%??|JK	Uaid+|JerΟ8Ȃ{b<+/֧졏8~07qԟTTӦs읚؏uh当
	}c?w1j^c{MvN
551ڒ윚~d-]tuH_6N\
$]o%r=Q^^Οn|$e+g1aX?4F_yN_X_βYrR&F	Ue$
ML1 R)]Z7POzr\e>v4!HYy91fp/550?Gkүk[Dk\_o]Cp+wرs߻Җ#e?L}IYbہcl]:ÛՇmp?2Ŷ|ڒug$%#ASiEi5eCLY3W|b>?<3ӗ}&о+Yİ|[34!#5$=Fwoglf%bji#_P{Z
u~hJ!~gi׵?\:kTwWёADW
ŮBڨ0[ȗd$ɽ']%ZއoLfZX,x(ƌ0'hR1iqmji[P@Aa$gP[ex4֠%%NGUKCBWkh˖s箬eyEJe>E%44ѣ&=fojd@z]̍
)//
Dyye끼[D)Ҋg'wD"U@^:+9wӗ=i:43J/lLΆ4wc34_
ne`n,;-tnk~&3;`ʨOG[Sa\ZrZ&:ZZugVؗWTl^2G}fMUm~tiΤPQVd߻ef(tiq~]CfNxûg8EFֆ\

_Pӳ}O|Cx+
{DԸ|M݈tlqiў=[V1jғOa-J9{)Ib+wjJ-@sug死мuܹ;gy@sKmnՄŨk7N$-g˶1oo5BM]jY7#ߝcEF.@U;kKɦžq-Xajd\Z:;h
edܡcrd$ڴG).)eہczwjh$x:bn,{*_\lKH܄
GοOѷK[y=OӮ*4 6>I!PUQ{{ST\–ߏ(UYY`flȎNP%$تq	_Qqx:bfd@Ii)EGDڽ)JJy^ʙ$	{OJ}ؼf\=ϸs¢b[ڸ;+q,&];S=f/ȩZ^A
4&?HJ@)=rss^OʘЬe{t4㦡]bl--ũwҢMW$%}`Ԥ())?U$JJ95Ǽ-死p3fO$H߷šH{h^ߘqoՙbMH$ͣ7`yd'q+H0|"O%9YZc(~X:بP̈eM8cw돝S*kdUۏ31tl	tjƚ_ɇTnQl^::TJWGKaM+seݒ#vu[@U`gmg`ktp~e,M;|~n`jdPWkcme?q469D\b2#v<
^S=?@\	`ԧ_170HO}0Ⓟށ:
+[Z90z@a`{8yɟ_S#i;sjߘӗپ\Vyy{poFXL[wgN\㧅0vP/nmZp9B^}Mڏ?#+|PyPou<]lkgߕX=mNrWfhov6Dߦe%%B!98~YM_|TTr{3055
SVcכ^)N]Sޚ'4sW@ija
?TNҼyo3s=X]=ꭗG6_&mPS`C[_t+
Y(Ocdj*
u
7l]#?7{o-X/_NF*>Z@C]}8K܍0ͨNۮt~@M2S<ڋ]޶%Pw񼝖j
TZ6""&Ica*{bWPJu3xR-)@ߖ̞4ٓtzx[_!_||;y遀%Nzhٚ9RV^NbJ:̌qdŬM;g'&NC]ߒ/m^ȀϧϧG(,o]*`ldžEӫ776iNKu3v:̚4Z^{ Jpd3&)3z`OkIPD4G1Qښfu7|koyӗpmst|74Ҳrv=ҳ))+#!9
+sN0x5e3%'-w6pqݡdeIk;CW3ϻ(ύJH$_̙:򢖭]6Ugѫ)Osn9?
rq--]Z=%C
5vdL2U+GU$RΏxY3R[GM[$	+(䛍;XɄGy$bn1wWZ8+f뙔(c\K$zQJ$hGDz:4oӵFޟ|q_^NNM; Ԁ7EP_-K<'D  A@|?_AAxy.p/yAxvzE^`Aj^`/CtQԖ~|kb*ΏSm=dD`+ۺ/mT+P+£+ /]IEExR^~AE*O^^~AEO^^~AE*O^1@& ‹E$[yUP}?x£+O3vWrw_x4q|AAxѼzW/<8  hD9@i	܌>G;guJt׮_&/'F'&ϻω 2Rl	$Ep=̏w2{˧83Y
KJKc9x<QO_`Axb`Q\RG	
|'ϙ5. ³?g%ތ""J[>YĔ.߿pj_;ۏ$'=qV$r}	%*pŨ?#kgճw#^|MƥFU;;Dͻh5t26$Hak1
Ax%vo⟪wy.ɗ}B8r*M{	*g,_W 5T\u':"bY/M
wnINi
=GaAB^RK%'GA~. ;x7!q.>MOyҒb
y9٤ަ:Cb/Tm^Q޽CzJ6bEfe7,ovn^&>kzJ3+,#MF7[aPjԤx"] rGTct3s&t	'J'/70ٿpP~]bl;^}o
mbeNA.)-a
-2F"*s7K#c&$s7/7}ݝ8u%{tTSC]!=;T{ZZ+))͜IV}1Ғ/[tyz{Uߗ瓗ͬ}7Wc	۷oC?OtmR2O@
utJVٱk}	  ';ٓra~\:ě7[6,1C{6Z_~2KXp{>fq㖬w3Wu?e
ޜ%eU3xg G?U4.rAnVg䡝L{c͢썛g'tÄ4,+9|Û̉~;z{K3cz'*{΄ҼF$ޒ5!~>L[؝IZΨI)//cl.m͐os6FMMfeGտ[SRVGcx%M
hB'%:
9H$JK	҆^SfaYi%$JJڼNCqT	4}tÍk1'*THxsƁawfpK8AxBw8{9woe8e%zO0sH8֥176d3%{2Ϋ.ꈁs~w OO?ի^@̈qnݳS1A:4ɽr}d0MV.;aB$e272{ztbצelg'nE_#&2;ZwWz?z"/{\t8MVY |,%ىAPOG§*s]?GgOW#񥩫'%%"B *{e;')714@rU_=K @Oߘ̴ĂeC50f䄙8FID$ϒ

TUhl#gۥ-***WGJyE2076<έ|dfL5ٿAZ&ȽUg޻	,m046?{?*imݸAG7*hGy
}m6/bѵ(.)Ii@MӇ9k
\ZqQ[6)N-}MFx:m=[n6tpSKSC̚ Wո)+`ę;w()u9]Z{|9.*anՄPVV!ZgW1`?.֝^'*ުn/Ɛ!
[z1vmAvn>
4b[x:2s'*{Μ{]7⻯>b֒ЋEFR\\H|L$Zڲ.jt1yA[GaEzufСe(
K`ܩkʻsQRV>ݰ~{"܏ IDATY7Vپ+f.ބD"a̱*<:{
6GfzH$2`D>2c3Kr02}=⇥ӈ
ȌP_f.ބ{~~;/}95F\̱9LGX@Vne?|8zH\

g<\;Ai;sjߘӗپ|.oỉ~Ÿ9ЭM._SkT)cĔt;$73ޫ7(-
u\O6զ+.)9̷Czz”WT+$gꈕWҲrS01G끱pϢ“`֒j@^^4iꎺ&-vǥy[t[]b'Pdêo(M}&̤yS;sRNMlh#1g'bӠӹ#3?۽h?g`̊-x^ߢcF9a}GYY)~YMw	/܂Km^N؍>zϿM۠0>{V|Q/U6}=[`amǒuq-2w2RBvYna`\}ʶ]QQ.=btLhAYY驉LEW[YכaҽmK%z;45Pk

4w1ԗ"Bҳ^VQZr944SoҲK&_+_|6עo2q
l,LIJˤoyoQNiI1YZX+OMjugE~N0Idm[9FS[Gg8NBGπvдY+@v]2ƍgV
OI55LC
`fdAEX\Nee++ЦsoH$$޼AF5|_m2hj[[Ǹn>8^K"Z{<DZ&#-`Mul@
GWO
TlRiCcsrytb#63oWf}k*eGpdۊÄ]"!za?^B7eFhQe|ljz(6^COr6Ną0wkddI>,8Z9t/}ãP.%oE43UhjSZVN2vp/,Miܙ|kښ\<}][ǧN;K7xs˹0d>?ZFUI4xě^ðch#ޕnMF<<1.tōk4oӕ3=Ѷ\>QcG"8ǝٯ]s5wynߺAA~^@&+#w2ܗt;THeڲRoݠ}}3:F6k^]Z՛6
{g{^6?PWʕ+L>@


wOضmVOJF}FXɨ=ظpo7vj42=Wofpd;|8A6Pq}-M%++,3I3pܲ2YVk*Ȣ" ">#3纺b㏕b~ʺ?˯5a_mQwj޺C.VLtv5,u###{dWӼM'VL.veˉOcO?r.ǡ+L[aƭ
K066ظ

WMUnɏ6O$@?	Г324Xz"bl*5f2}
vx&Τ牞3?‹O>H8?VcbjF>OUX634
椚y֢o;KOW 21Rf0jpo\Tc0{jXHVd{uO&QXX3Di2g~ !ɏ:KOK5'Fh׆8Sߵ:i@]t7i
`صm#/ZCQu^~eFAFFFIhh]ٷoGDLm|<ܱdŜ)	6ƊRRZG_~džfh=ݴ{X=C89p=yT=>O>Os!w]>͐ke:NxԧzɎ?W&û%zj&>qY[9%lS}PPXĕkL~j}S-	Хg^ڦ,gǡ\~>?~%0[.aoϋSRRBfG*YU);9*rJtede3qP?qI=w9@AȻIC;>zx+CÛ7cJK~Fg#*9TVQܺFةhyYdz{([ݓsi)ɉ꟭mФY]!MSqw[B!?ffff*v!=;Թ	G:ىa{0rްᥧUxWLaQumm8Gʕ9x,1WgZn㹁=4}XOwWbOr)>Pr*iƀ
iSBa{Ƶ6i@׳1bƷ]I~q[xSX\¡xw}ުT{=;7S3fGQ\\«NfVblmX]{Hܾs3SNIgݯj>q͜I/⨚MF/s(:Rv6dRTRBjyܜtw5q7`K]~+@Ռ1wu̂⢛oӚpq{x/ߛ2$_ F*)'h3psa_xفM4j\-]uq=Jm8ʆ3)SݜY8u
Э2pv5oSwv>VKKR/?"lV,,1Y>yٙ:y7
;IB҂Aק[^[vnQ̔.,ݰoH[
Rc!LMzU!hP<Xl2Ow8/?yܳ/OQ=u^ʅ3񤎭9%XʏC))-ethaoW&Ap&FN|tOepODXQ\\R)
:8֞ۺ1Z7Isn_7v
ܠ|	w;C{0*?P!}O>|@*kE`'f}O’Ni؉Ƚ>3su1=Xsn
Ac1[
ãgW֗OhJ.dd_,q[ʗk7s_g{NSRZEwxO֌ҳ#Ѓ$SH=Vֶ!m;v(BǥefrBVlj(3s;H~ɧк:))-|v?70HZzbceAUѧ
x\Vvlk
]?Za8G{;Z4M-M;v%3#x*ؕ~}$JhG(-+Р|>YXT̥+p129yԭƅ]kz?NiޅRe1oyYm"}Zcgm=B}6C>\]noTS:JHE3LML8q,aPVģǨ{A7m3kɬIL	5`@vx-miظ061l>é^]>"tmۊdyfUXc♷rF|fN|1cb\;$Bk+/!tgޮN<_e)zyHwuFVhoǖʕmѬ	MP.,*ٟ3shlhP߁/ߦti6j^cs!Wym,>nƶE7Q\RJ:nVI_c;$Bef?
BtTrgfb"jlғxr?"~^%wc־5f&&}Slݸ\c5|i(*)q4nP02"eC]%(O	!M@%'@{WCYh&׮P\\ĮcWY0 ”={eě`]F|6.DQntBϚ@L7^9~}SVVʙ5 )blbJSԱw˯5EMo<}J>IqGhRXJ8:7;{G-TE\BJR,QEBL	:_ibjБ7g>Obmoٔ$c#HӦGT]9FdkdSczoTSQ9\2X!<}W<]o֤*9w/>100$,t$_K΅Lۏа	ԇy_E?rhv2Φ8֯' 1~Y8.giDfz
Ɍߖزa1{hԏ5]/%b6]]RS(vplhI[kձUlfjk(O#S2XI?kZͻ}_x7">Nӵ;AbemQv0m!=9r/M z_Rdt?RvmHm߿kF4{Gz
[7>z2(/|kcW~#?*}J%o]¸0,B!tY1@~Eq?-;q;/CGQ;m:+{2!Fr7{:q<ƞGPԝJ!9NCX(ں‚G1MIb3Y
]4(?[%G=H1!Bz&?PIot݋WO~n"1mCݚ`Wׁ]626jǙS	{p*1Z7ч}ee?v/VLC#),(..zx֫τi)).fɧIܵv](--~3jA!B!D͢Ϛ@LOtN\ɿ7f1eΊre{Ƣ9	ГĘ.ܲ3r1;{ahdL~2gHp9"vuxi؉_k}ǻfY&&fXXZq61/tǺupA-|p캷~_|6S—֨'qq0gpC:L!j]ojq땯6yoVMX'kmzv]Ўxf,gS0kHMޑw^Cc/??ރG/=|m'O^C8w[f8y#)gݴ#`` R>

΁-X7}cڨ͵G%1}+Oe%.2aHͤP(Zڐ@-Mt6
x=vemiץ͙[#/MN}04Y-,cP;8cL^ne%Yi
Pu{KrGPL5pf7=wzj䆓}
B!D͠ڒ@-Lt3	[a}}ayzEcOre81G@BtYe	W>\=qu|}}IB'"VXmAt
\aQ1O]Ii)J%dde7grK-J[zM*tRm!Sd^KX.>rۋKJ=F3GcO{ǵ~CmL~$@^n#jaaa̘1'Oj;Z->Fq5\+࿣)+9y{}1և,o7Zuۖ>x5jP	W!tjkW@9B;x"}ϟgܸql߾]!:.0yx7űYםe'2dZ懎sLjM佅+Խ07õ	p<3|7I)g#KU+ ]ej;3c%mRid}!*kFRjZuVj;зs[^vsҲ26n+>Аm]LyiX8OtUzrr5ʔ1U,&'ΤUG&mVtB(TRE8EPRZZiʻ|RuCjybOWn#tR$88S^=z-YHv>FpmI)ddO_ZAȿrN`JW\_PSK"RZ&jRo.?66H*d7SIDATL1~bM{9e{"jحvG|v.}
[qsy[G8㋪P(4i&LޞYfѿmUoGDL6w-Y1g
Νws2s¨;ƊDzw᷿˕qwu±nEty9 DcKKv(Z#	2X2\1P?1XrEq諘S?ImnZdG(.|SZVGsP9"6ñD&j<:~<<2H8͌|ƶSg3HNP.+S3QU_&"&QOl"%GcٹKH8~tb2E썌&XKJKMPXT\6ح]!mHN_-Xinֿ#a_|+6n1>_3++7@׶>nǍ`Ġ^x}v6VjDxo}H4p&72B_ʼnq.>8OwWLH+?c_V#ٳvu R=]gЮ/~:nΠԣ#)i簯c˦iRs@#8}9wW3֖mkꟃ{P-};(gnfJn7_ߦԟsScwv6VO&X[xi7QK)J<}Zcmk?7wjCIDƟߩZq_0Z[D$|r8&z7gG3/T=쟃G9+Pǎ[c|؟QqPn#4i.)S|w#qI4iϋQNn.(;051Pp}T*Yp!dgg3k,eP
aea^.zTq4S(*i8vjo#'j$mt!?b%"MIH>dZy%:_.&X"Ɵt۹SvT̟N	Qς--022R/Լ\GT'O9{.P	oF7 .wQOh$b
ExZ&&nʝܥ,
d\p!4HHiFÈ'zVbz}ABǻ2u2'#v-}@`hNg
x}+~bK;DXhXWۡ~jvQO+􇡡!B`
e
Ǜ	rFh;#
*|3%BWg7U/6	}o@ !X#RhYm!B@)lwj;1@B<.9rsܭhB!6=9j"uQߵCњJ:2faX!Fꇸn~y !B!
B)cB!$@B!BZC !B!D!	B!֐H!BQkH$B!5$B!B	!B!j
IB!$@B!BZC !B!D!	B!֐H!BQkH$B!5RA!B!Du y'IENDB`urwid-1.1.1/docs/manual/images/introduction.png0000664000175000017500000006174512051303575021125 0ustar  ianian00000000000000PNG


IHDRsBIT|d	pHYs

B(xtEXtSoftwarewww.inkscape.org< IDATxwTWRػ( b=j%7%51QcI]AA"bEQAB~Jlzs<ǝv߽eH dd2A!HPQt "	 JI$0AA)& (%A$ DAH` R	LAPJ"	 JI$0AA)& (%A$ DAH` R	LAPJ"	 JI$0AA)& (%A$  $DL&StA"	_(CA)uWtQQAPJ"	 JI$0AA)& (%A$ DAH` R	L/.="9zUG_۟UgPn>·N$0A0'&ؾ~a~p^ɽ}}pCYunw=᳞ |DY<'/K2N_qw}OT*ܦICVcTNB\zL3HIMzk!
{tkmy/thM,jWVH|O}qGdT.4ZFR%8qч4jWgYw۟5#E,),sNgOzJl?&[	!ABk;+~c$AP0hik{3Ѧd6vp߶]tNYR223H{xq7	~A#E,#g/gоx]_~b˯#EH`Ŋƒ|
ZZɟca#k1v@RJ*R黷Ԣ1ZhijPJy^&:)U܀17JyS|rz`\U9m3h߬hkikODp7?M{@'v3&TTT޻χacɩiɷh"qtM"	84 v(C/`سf.c8QJ$aV"Awȧ%&t:+SZ%y]{ۏˠQKnȟ
IJv|MMLPWW%idz6ϔ%3c&S'c4yo㚕+i#ttrū؝2WGԨTSVAwiY&$2W<&Eۆ_+DwdyͪhijKbUC\:q&ښؚ&*%2ZU*pA8%
1*aGemzԫQȾ,I$kT|LoAzfjJ[ԡFX׫gH$"	_$+\H`BQ&H |,R%+%-/AP& (%A$ DAH` R	LAPJ"	 JI$0AA)D"QtJM|7ED"+q_7,_/х( (%A$ DAH` R	LAPJ"	EC8BA	L(r=5'O&..Ny1osE%䑁̝;D&;{_ܹIΝE"
H`ˆmLo"	N$oHdB~	L(4"q	oLR"	N$.cD"J$0%|Ȅ%DHdBn&$)"	_L$. D&|H`B%&Ȅ	Ll"q	$H`EFFracSDFF*0BIL&StBԦM?΂X`sIuV.\@-4h+WVpdѣ̟?ggg)UgΜQpdBAH$"	~zHKKB
[N$|9FȾGe񄇇+3fD"]»Yx1)))HR

ԩjhjj䄶6899䕏:uꄡ!R/^Ltt
4,Y³gψ"66'OP|yE9r$By	
4UVPtiPD (666
DAAH` R	LAPJ"	 J高F͊+&0ПĄEM;;[NXDGGr
$!!Q|-t4֎Sy^r>>^/snbeiSyJ?q׮]L0	ۖ]1v%7!eu..kׯBbٵk'McW[̩oQRF
k<&xr͛k\z'OOG[ڶU5L/n7׬SyJ?~׮]847QB|Boz(V?vbL'nL}jZF@(Gf
9?rbX[V-Ժ6;|,._:EGGSN=/&Wz;eThԭWMۦUHn2j2ny_6Gv9O|ӥ
nRDlʕض*WRۖ]Yreչr
:vɫշFǮ\\r}mEG֖Uh˪Boo/>!9^Vm[-m[ǧc~S/Z_EP53>@R,9Y[Uïc~Si		mA	jԶBCSKׅ#4og/:KabZk fѨic_p%jֳȤl{ORGxF9ɗ2,aT1$$$~rall">oҮ|ܹFǮMH$<~ETQa״;e_KT\&1gefvs~խI"33WI{ӽs|/?(eJ\.9c{e.?lHUw>;7-#=-%Oۿ%ֿVbDE<ʱt;7~VyٳN%!gT?j1ѱmVaص

oϣ\8{Ga{cxz{̞-/7ЫtRR#ãu>?TMS{x03njӮX`vzoo^7}	|g:4Dk<
uF?ƉEs~pbk./RuNWʃ{Alc	kǁkؗ[0t|Vo=e[qgI3s]N`Ѭ%
m̸FbB2޷hШH222P9oR2nƍXΜE9幎LƍP.Ix\=2meLK1u[hVP|nq\qS`@XGm;ذlV^L.^z_vi}ǿ
/d)]"=#pg۞}}qR06 aӶ9?N񙑙7鵒e"GO]gv6ҡtlc&,~@̳'X4rPPص#\q?J]O?U7dw9V7nyanARhhjx]n]9O)2\M%0LFxwƕ5g擔T]ǖnoP#++,hILîeWd2dӼ]O.>}4oʇyV]|ލw1*]}R3zԔ$tb.is\{ɒCG]0i"AB!`#lՉ?tFWnQSWʺ^oA-kwE$T*]&HPSS*ziB|zoI	,!έ́=B^U
44ˡ=.gX͘ol{oukkc'lfo}=-:b7\d؀%c:T`adfer%嫩"3Fo^%'a;6y36D̝ޛ&ˆ	iiR~FJ`6[1;;A2[NQʤ,wo^N/iٿ}=TNi"&_6kD432h[uՋ'~~4j,HJut5T'a<{04{RZY]tM76?n'fɻOVZ"iuw5f΃ATMgGhl_O>iKx"&f^Q߲ߌox_ݢ2[s%fu*8<߷v41#?mMTs븛[Zi*@rRjӻ+/ac}oڕp|gcV2:DGyk@UUW߂.vzg_jbQO68
V		oSSS%+W:9vO?TĬfYŸf-8{*E"P1WiGg8q\iJ{B_O43c7:Ȫ±5~7ީ;1(bC^Q|>:PEU%)]r[ԬUU522ҹ4+u1+x0Z3Ⱦt0,iLFF: QQ-`XH~ˢ	շM^e+VEG׀ؗ<{LZ#=
xIg'<}Bju(STJR>/b"\=K9IBoQF=$	jj4*Ý _2G]2AǽmIq
xIY~IuaH$GLU}	GO_28>.'QԮ՜W0eQ:Z*e@(uIO\,
  t*d2_Cll6sL
%11rv# de(_фW	z´uW+ֵPSj`~\7]ޱ\z}},@]U?;zGQTQMbDr# kg*۪t/,{%&r{_wBE2oR&>!fvf)ix\ETl8AxޣTI=-B[wpSծ@?/\
fꜭD@C[_&}c#}YU}QWgݮ+z	lyz}TD8wm/;-QkLB||ԧ+rﯮm2tTYTc϶3{߻59y
ni۬ByL,'>pV]10o_|-7*&֓S+j斄~1Ũt9,mZϣp!M$/Ⱦy)KKsnZ}oux?-hjcݟNMFx՘BF@(9~9EAiCvhbU1)-4lҦZ\ޕu,ci}~z|bkk{|:{aU3
)z6bt7mmqM:kPhu%f5h' IDAT2oz|/(wv!Z=↖qC˯ⲆsGxWiX=w..k
`ll5.LZa]ߒLk\
<^IBߧu^N)kJۅЯ_?&Lmˮ[;P̢@'voľ!vxweu8]mw0E5L=F@(wf.і-bmU훝b=5rv!-::+WM`ޏ[%vv8M_rѬ\oo/~U_3==],-ͱcT"qW\~J}?¤966vL)K)4zAۥc` ·M$0AA)& (%A$ DAH`",.N7
,
1|bccUhwH`鉍R̛7y):V֭TXnZa֬YÚ5k>c	F&{xzzһwoG
P=z{{{ױߖ
*o>z&1/'h?>sUt̝;+:AA$&[Drus
MH`'[Eh	jؾ}+JD+Lo3}	,66ӿ\+jD+Lo/}	͍vM-D>(^0o)}	TD"DAzM„׾$&(z͝;wݻCzM„}ILM(zBQ[_nD+8Ͷ!h	5DOPkb,L5)]KLL޽{sNV^͛9q ?~L&Skod2?VpDBQ$$~zǏs+<{T*qJ*M	ȫ瑼|HJLĄ+ѥSGaffShغu+.\E4ʕ++821b_Nll,...tE!}#G0a

iРCj(
-ZܹsCCCE$Iћ&FhФ=-zND)u?YFFF:GWΰvhjhн#agg5>sL2ܹ{r144$  QaÇ?><|VZ):iѢwF"ƠAVoq777-hад0ԅqhߛr*yøLy5gEwqy
	'))ߕbhjj䄶6899谔	*U
G{666*U



{H$HR\]]R)ۛ)S,Fھ-**Zg{7o.#GM5F)_Ӡmaٶm3gd֬Y3FYx1K,a竣̟Xpuu%**J)߃D"Q\		3P'Zu={".{ZAPCQc444066Vt(_$::4ʗ/P:X
I`2sY~=H>*';;7.T	Mٲe q/`KJ!8g#'ɡį*A"L"$0ْ^{T+;uaIABiyxxЧwt8=h]q=Dzl钯zʽ 2*.;v2q&@=&VO~K{Ŋè`ZC|Aȝd""" 22/^PdILMM155L2hkk+:oJ'˗/ؽ'ljRGA`A4ņQY$*ok?N*Ppp0wiR.A2%(Y\/xg/|F1jՠktHڵ6jidc.[7Krb<3Gu`t'~<#H8L5ݏ:/e)&s㓛_]ƞ=8| tАnh`^u?Ǯzں8vA>ѰaBxߖK`a׶/zײ!fh޼y	,w/O	L1	̘D`ӭCZV2e2p/;\܊VPV|:pEˆ$2	OĄ2(S,ԯ_KKKE\0	L&ѳWoRe:fR~\;aCHOO/?idmY	7q䳒@e6ǝ|rW.RF5Riwk<]'r-:)4v(kД 7J$}'amu3Ǿ >%W/V7'C~m/|̋H=(SEkAPgϞ¡)M2%GF\:Lb}ZlNttt׹kNjլNV}n]YɎhiO_e8Dv5͛VʜKٟoc`wܡi3'?[VsD~d^X4r &)*d۝T233iݹakЄGocXˆ9v-+9K^:Iw-HR*V5ھmoccQ_~Y#rk1Vpc`~~~8v¨͙=,ڽ`Aaף{29s~fm6:
~Q翶yvڋCՓc`~svL&l^=-iչ?`?BFz.&mјc֎~#Ӧd2{p^56aIc0ѽѴuM	?wf\p$	Lڊc`j֦ #:ulݭyZjU/CvmbŊVvRROBc|,N1#VuSЗ9|I`/_&Fk}žq1QOv4;7-%>&,&Sg5`?䯫Z>}nqrW.yT!AUU
EoJ

Z\mdRV3g37[BQcLЩ%86i+u沗O葔DfMkXU~BMpoUeo[:`̛@ɗ6oB7޶`w7U02)KCvL&%9)A[VFL	?e+`9-ilshR=Mdl.\ ZaPd2kS&p>jzA߳w߁/ZqE&144/APLsU6}]œ8ƶ0*5ydOظ|ލj̑PǢ1^oUŽz)JrNm_9OU(]2~DȭjmȫNFz:MZvO_g
97444<]w3c+^(E]4jsֱr4*T*e3|y=ą{Y'C8̿>^TG!./>L&<&RFjMNNE&iҩ$'٭%
PQLJL!99E]XϏMuweffU"Fo{ǠQ)} o03T\1w.VLN¼:w隧.8[9w~S<A]柯w9Y=zYSZv[P&3K'1!I )1-m]TTr4LFRB;?$++?Oǧ75%̉x'f9%}C:

.i<غk3_TNشEmm=dᬾԮYG|;m3:g,՗
[p}N>YKLLF9U\A>/
-R2--gNӰIf}(Feޛ\%/oz]~n;t뗻u,m9yd,<./vE6@SKⲿUcXwLL+c;z .|nM|\o:RС)34kaC?'y?|ƹSiޝ:u/o~LffB#]w>a#NjƮ&2~Ns#aX}^[WZ;))9۹0)_Ξ=K7TF1hФ=:23pðdvZM]SPTTۮȤ_:DZ3X0;
ki_G7=Kp/:Kga€̛؛7-YEOC7Ex!㮗V?ttiIO{I^|zژ[U'1!Yqo[Fvk+YYRb_%à%y
+8w!3#2W8[K?r9Og_`pYTɿs=s64)cV+2R)ʒ9}PU^Gc8/ì)ݱkTSM,ӟ_Vsjj,Ӈӝr"u.,+4}=-fNƌNVaW#Xٵ˯X};N8AVV޿BUU
UrToX"QQ쑝\>.fāݠk{FNgꟉ|LJqf٫sGwR1ɉ̟ԗz7jֳf{5jjj,p=zX2Ix!ʒ2vL;v\@B|7bm[uګ97qx]~~fOۈ9?},[#:`=İ
m̘>i^3ehZ
T0)Sw9S~UGzz]{6ex]yEf挛%qb׮]hARsoe@G4ml>͛z2L.6\?wpbyTW8|Zw==^ޝpV>I?CSZZjϲ2'LZZ:qI\~zk]YTc14:wYnsܾ8j@d%--k>)^B_]&0m;9ɪQQP(޲cXaeLIM !1rہM|ڔv	]PSt`/fD)|	D2*MDDe˖֨GBKcseeer^ZvٓY0Ty#232ެXڛ"++
-:~jճ\jV$3#pՃu([|
ǡ_:07v	E"|tcגƂe}+U)cR &ZA.vDbVfKѦ
gIwȈ稩m	ff"$+3:16)0?իW\֑R~tLjo-V'ԨZ&~>rE\cڵ`?د>}U":N'233QS_x'ޑDl\N]y4
ѽ^G)aT<סY}\cCQW/ƶ
phߛv_f/iL'ihչ\8}V~!lv
۱oK	]>C]&,_NPI\8{G'?Ț_wg?)a-m
RSґIe+oMs>^7^3{mǏm^.Ɍɿ􅿾i9#Sx.i*6vuZ=G٢9[hծqqѣ8CG.ɭnжś
wnĒՇIO.eA:͛ĉ8vTiTbŋ;<32p֏x'p06q}A~?Rr<{URϛ1W(i\*5}p%M(LC":toc
bT^ہW	;y~OO51HI{gK*UQ\:D"N2i$pU,8z(sRM[KӲe/*'	,""#Uix(zkD:&ߞWIKI̼:$&	ySjX*)1`+X
onJm^=R瞆b`XרVH{W?fX'Ʀz.q5jks3<{•G8Nږ,tuHof
zWOKum]}f,K61,aDE`V445hd&o	m{:\璇?[Zһ_+d3DEn8N|X~26v6vuٲsǏxoE5>u;kMG}a{
_ͨT2h3=;u2ѩ/**EO'u4^W%cZoļ%#{RL}r96,]2`֔66%	]F}llZbgܸKLsJFF{oDzT\C9X0/_G62_t3o݇_<}0njfA3huD끆%5{3(=!?
KnY9VKZJ2%J2*,4غ~!gSa3|=Oҥ2̝Л*5롦^O2naj\ IDATjl:pj^E=UϚbӬnȩi쑝$F?|J|ͷq;_Ϡ{4Rz3غ}{W8v]plwΩ}ml‘יCYS0=UغsKUYٶu3߽u,LRƦYRSزvCoaf˨Z303#GP:I[Wu>_Qxќ%߂ʖ;ʤlQDFFP=ﻮYyeO0z&5~7:'>Bh`Qy\MOQ*Nݒld.T6wJ}7c7d'D-;ҭ-G(wޯW{ys'qo mCchnRvwܐw_cXK1tA}U!44:u8
ALu܋:!rA ˲d\wj<IL{'cVor3$tڵ;]K,p>ew%Gtu2,38^$e[HQQY-+Ȥ-wo_FE]uq
aʕ}>luA=-<IFse~nnO
My/2œ\#!.
x!f,1
-|Çede1jbUcHRB	x s{XH q19
GW	&q;\bhFsӮU}q|K"p;\_ɾ8k*cg}_6w?Ob풡=HjWInI"%}r~+W6a7#O0_
{ؚbEJLH&,4چJ+eNdD,jꕋ%`Y&Fm&efr%b[GVP:UU9{5:
VӠJl}-4>EΖ+'Z%byDEbF1դqVI,4z=6a݄C:z^qX4dMb=EDW#mжMD8t)0Lz$ǗT*EO6hޢX)ϖcρt@Rb|u~gL`ΪΩɲmHNϱƭЩF9c"Ë*mKpًq1ԩׄ7Z-?€1n^eA8IWY0Umd(~n='ŢE(*͍qw+zՊHNNa!f1DΧTpN68L,ҾGıigo+)9%/`uttyiaB;ܖШAuV/^Y]R6{_~a45A(T~6$YLW.ttt,xs^Z9#=hBTPP̽|W˧|OYhFgF.~)Hۗ{<<ʕٽN(Ϡ>sG{\,`Cs2=`eaʆ忓BBB´t*\B
`5k ճBukTԵ0KѺMLN'))W}ΉwalrBFiS9hq	əIdD,;eG8-:Ç4~u&6.Ĥ8q]߽נLOaa&=/`ާ|`ւь/CoYn;:3܉n;_G;i4"H>2o-uL *,>&~/06il\Ta
cX`PyC"HObvwsUY8g'6'u3qZbcd3n=[o'>JWb8E6lƍp9r歌0m^~1l²{7R:woE}N_ΰxXHHp6oou##wq°n5ƕ~,Y5!-6q=w|1#׮dnH2_O뇚Fe,]"KGGq݉\񘵛OeNwx)QwKBU"#&m!2*3.Ĥ6:ǫ78c
uj0a.*0Z"9
Թ;0k\y7)ry$)_>]p0ȗ;Q[ǟ:uvPfL|x#ukg.9&_ܹKha=~08fcj\K񽴌[3KMg4*֭[7,Ylz[c>%'%P^N>+{2w!*VR!*"5*DFd<޿N@G-d=3]42iC
C#qȔPNЫ')t\qJHHBAQWoqE[OSahceۖAPY"rVUթYGw1nͥ>f4GnͨY[v	
GAA
TRVBIIUue|n={XjQ}&pYFeBC"R%Lqm;'r
6m'S.],ZЩ).G|6*[lUoX;oɇH?~$9)hke%կuyJ@A"DG!Jq;5sIΤc[i0jTr+bcPRRF_=ի+y>h+4$ɩ^^HW^%l?97vn9M'6a=:y7/LJBhhSF"))IM짨;~XOaQ?rͻgDFzY_uUϬ˒Rzu|T*ئg3&a}Zrcm[q2vz',]E*ZB)---1si99y:Yz֕C[~=~N[{,6Ey{"B12n33vJ*((fVyQZճy]ֿgߦE$&]RY͸9:z5X1{{;ٯxV}L֟ϧπWi錛UJXٶnZuƼyy4ǯú5~Z,,[qdQ|Ψ/WYo"]3b/SYժlHnT"YkͩgTWuݟς9s&(	k5/Ȩ,^<ٓ7^;r6[iٺ.G.SSL5DeSZ={zʗM̅{DdaXK}Z###og\qȨ5ylekFi9y8Ԩ!;tfg1RY2nKќ8Túi>{iڵߓI[а.fx]q|Gp4dML,YQ]q1Fon^vv@B\ޞꝙ)h3׭}6æ56bڢmt5(zѭ^Yr,Q]Y'&ͻyi:'wϤR%E™4WסiMʊ
;#РQM,JEh3tD֗闯71dD[_XWTEh٦g՜Qhظ6¢6'c'{	RY|-"0 {w`'Ν<:Ӯ122`i6	v:s TETqQj%wH$9Xuo\O׾Yg䐎,fY
Ά~/QȍFe'd+|Q}ϳb:$|cuz
3~XZ3bf-p^߫=$:ݕڊ#.J+Eױ;7+Va
RgGdt*C{|*U+{(ߞl9w{N:F_<¨ǏUE!շӨX
XKj6ytLz)_^.4fˌ5ZG>~*݅B6m̚3~Dѻ868oD4iBnLGc_Dd<gc݅^"ZhA&xGZw Ҫcy$%d_QQbuiѩ@n^x]rg #g*V̻{D[.:'A]6gjWV-T).2:ύiݪk7fآO0^I:Ct(T˗CfZum:eװ& #_[xHZPVQM?&(IOOc€VDi׵Adeadܒ;.S&:jq}fޱ	:@ (sg!O~*B9r'9}'qpsC{Ъy"[.gǪL)EFV,xj5mb2Bi	sV@A"MZtVFXT&Br4Ƒ!'WpFO[+Y9gclMWP~f&Y>Ўo'JSLio_n~y}}}9F[kNr닠ծxu˻XI$	;Rq;zXd햶7OX0/{`llQ#D!^bi+l◒ihTDzׁc7ą:_ƈXt0l̊*a.Fp4E_Ȣ$''	\=oe(+cq,%J穴	rN
`_8^2/^8-:\b	#0ZLD^2x}V,Z555qs;U,$3߇F߾e@ 
uNzYY9
{okپc
6|222OP:,qPoRǮcpȱ"7lؐm[7q(_X=ժ`@𝬬5j<!5l&NM{ƌP,}455x
6C{)O?rcu˺(_"0ؿ-wvfX/G`FY&mQ\zs/_3gy_V\E
D'aQ	}zw~x-[¶[`^h'37Q	{[|ɒׯz;849iۼ(eq1tTg=(**vA!HRfq~Na=ɯGOu5,(ыvډ1޼	Gژ:5ui}MDNhxoQ	]|ᅠy{ll1p"+_%6c^+/qcnjd٥@P؏0
ΥudM߿+IDATq'jJ͜[WN*$%+qt._ ^)>~뷞͵vQJMOwM(nY&蔹˗w@QcfSq/6x_;ͬ30a<%@P:=z<|xN5ii:~Kbllʒ˩W^ħM*rfrDoȀQNTQS's&<\v3bgDEE+ʦ7n8XtiUwcuJĤ^lj3w9}5d嘙l6R>H$lڴKPUO4k
+
	ܹy;^}+++/Z^C b1'O1nzݦu4m"muDjPS&2:qhaфw?ʬV/Y쳴4]㮸pE<&bںu7:cRp|oM^>O{MϞ=QWW/O"~txxx8$-bpcPWC$F$!#ңqtҥFy+sؿݽ{WW\\NsЬfUT5QQIG#6:wDE;w֦7]ts	D-::X'44Uʟ~VJEZ-F e7	@L "`@ !	@ ~H&0	$`@ !	@ ~H&0	$`@ !	@ ~H& dDIENDB`urwid-1.1.1/docs/manual/images/widget_layout.png0000664000175000017500000004566712051303575021271 0ustar  ianian00000000000000PNG


IHDRXwyt5sBIT|dtEXtSoftwarewww.inkscape.org< IDATxwXTg{"[4$l6$M{1~n+(އ:c83qw]^<3"H@h8haEDDDdbLL	1""""21&XDDDD&Ę`,""""cEDDDdbLLLwoh4hiiA[[;:::P(P(JdL0Xw	V[[
QS[+$TC7`ffvAKD4JEېj4dg碠PL"R==033ì3~qIR()-9'
?H$|kk+REWW^=bq1pvv>@DDD4Hevܬ\ڱV~~:cZxj523sFDDDDa%Xյ+
,suu1Z_Kk-"""`X	VooѲR^@CwK`98-Iedˆ;\3e|QVVbaa!*o^e,_3likkC]]=
AVV6~p~FGG9Wx{U[Mtww'9;;;QVV3h"VX,FbbBggNE6`ww7jjjT*M!

FYgjی*pBVx9<:Y455W^qZZ[tqgd\uM^hT" 0p2'땵	MMhjnFggI4䩧>UW]eKWϿFEhllı̜1[n9.h9,,,ǎ8p""f">>eظq||t8p2/X*,T*iVs΁;	b^}11ۋDFBEE%d2)BC+**Ƽs`nni:uwYHH{ZZZa&HR,])G+؈ډv,\8Vg~?^Rh\[{j47 =8l݆ 	TTVHr5#5GSӐ~С@vvV	W_}Kb% w7}KvXᇟijϽ$?7	zzz&?C{)HI9c{^{DspqqFyy%^x7|`]BY{{;`-/7cժQ\]cCɇQYU-ǖ۴Kkde)HNN9.^""촚 J%Q\R*$I$HN9b'}މG7=8hhhoGHH04
c?ݷ[,--`,?nxݏs%ЈW^]CBL"[>sN	@b_<'x7p- &&
>mތ#G7i
;*++GVv%,--2:K3D"DyIK/=+yx±0P[$a8O0aiLb1-ZcikK_˗
l@%7ށZԓ\spnk쓕ww7Ν#Gbbp1SxmJG={d"""2lX	V~~sr
#!!v%FX,hte}bqOqEbXSsj]]kn>Wz
11ӃMBV86oEaa
HWџ j"W\y|s{b&""~Cjnn6\F-[	1ѳ:?)!o,1k&*+@e˟M^f̘{~6vҎD]]àuzW(OJJϿ":UxxT*v#MJĆ
V7l#JHLCaAbcѳ!`ii~&![@bffGGGe/>e/FqI)D"6oފM\ r,$'#.~}q,Y^z1RR^{z{{.X
klذ	\s%`v,tvvby퍯`%g^m,KJJko=BLW^OMPj~1
G8883z5r=Ɂ(..EgG'~'L:..Θ>E"""%Q*o[cnre8y*+p4za
֘1u89iFws֞}⦛o=ؽg$	@H0rs`ii	??_֮:̘1
ӃÇS1gN8lki?/I",,,Bjj3ܹ-T7 3+3%rS	@cHL̞|Fжd-lh_T
''G666B}DuU50'1^Hzzzֶ6$I2""D*:kA^Xdw44oJPBCVg/I%"#gA` J=6&
Ehan1h3Ǐg\@Ee%ЈI?uV1uwwc7 1n3dVqq	GG8CVNRX,F{{ADXd񘙉HDDDw
k$TVV~v-z-V..Dkk<43&MbrEDDDƐ-Xv	뭳t*L	
{g7xoI6-lT%%"""2a
r55hkkGoo/j5R)R)$x;UOO+ w@R^5""",""""Tf7JDDDDtz`,""""cEDDDdbLL	1""""21&XDDDD&Ę`,""""cEDDDdbyn$CDDD4$?cD,lGde<"a!1""""21&XDDDD&6bc;R	B	BRR.93lC$""":##`UUբxXj4
鱋FkJ$LRDcc32gtDDDDgՈ`b`ee	__/ڌtDDDDgըwZXvDDDD&5	XN
/sxॗ5ZDfUQY!{쉵k>e6]]gOQQZ[ۅvL
B1;::go2z:e*
+/	g'c>EPA/(>g;³hq|Oqj	[޹vGTUV֠zm&;l$,W(/F9xn1soÏpaxK"༅X3ƿ1E} Nn.5.Gss+8|T"=Ţ[BB|:*,:/~BZQXT`$%7)PQQoBsbRpKw_ιِH$pwJ?7mR꽶̬<AA6-77N(?CRhee()@kk;~߰
Wǟ|+.H'nhh¦;aggzTT`׮h4Xx.<]hg;v+.H̬<466txxhŦ;܊Ep
W?w+ʕO=y/ADD-X..N;:
<՟|ϸwNkMku7܇[wl%Dqq^7hkmO}~ߴA\.a**B;ndb%ztM|mVWW7w~FCGgR[܍W
W]sB+kMؼe^v9Q[[NC~
‚qyyVPr*eb-\q,\t򋑕]9I3ھQv㨩˯(-D[%H9*[ocC_vo\㥓2[aii156C$,B~"sqC&/Y2r

?oagk~p!$|:M,]w~zz!(hK#-=gOǜhCx{ack&2tP̳㵵O/<رc?omɧ{nFlln]0Wo}€T5`ͿE" ;@H}ܤXٛooOL$FD""
̚LKļqĬpZ&4'2#G3 Jc充%04>ryF8w,=ߺu
5u/k@iYpͬB}66ֈLwO|M}|Q]cxp٠tI;Oc:0P*JSDzeRYXjpU?x:7)>^3Gۺ3'1o)|}WPu;Őɤe۫;kn^sixj6{jx7y67_׾>_O\y-rBeopKxdeAsS+jjp1%(@/V[׀Z!%""2F@TՇHevl3XvhΊy{y`֬Ankcd{+ݛpX
0yv=!>x(fƍCR#p^#?VV{CGGr(-DbBEx{zzI1GXhЅՍܼ"L?nwBU(Jb&az1W܊[
;v=b#r8
sJ>LAllR)>lڼ;GjV^8p(-^mmrgꜳ5
x酇%"sm0F"+pn77DGFN3Z>mZS1mZJJC/O?'w%X#E6|*
-յKDDcH%XE8؀5#?9Șњ謬-r8
?\][a&}Ƌϛ
SMRH$OFDDtnuttBP2LWs)ΰR;~cX/[L
>PV-h4FgnfO	8؈1`@aQkJJ+T^*((g!k"[澀v5l$	yyEpgQ5pp+++D̚Dc2QSjM-pD*EkkѮLHGhJ!h2CR%FrvvDhHF$v""""cF9}OP&8>^AScFss3XX:Q_hhB>ȝhaEDDDdbLL	1""""21&XDDDD&Ę`,""""͞M6xC Fb'3ht_MտvDc{ֹ3ht.B""""cEDDDdbLLl\"ARCPj)UP)UP(P)*
*
j&"0"1þ> ,FCDtEHDDDdbl"1vVPP*QZVhؘ`јcmmkkGrG1"".B",haEDcX,N,"Db~TѹZD4,":LA

MMmhllJ(
@$L*96FII
**Vk^.յNv{;_ww/[؊^*V `n.-`;ڡMX.h4(,Baa%4*''[@$,ڐ38Qo==
Tj IF;1hhh5Xӣ\ޅ6TA>"*RHMCcS^3gN_|nj
3
νψ΀RBUUk"BwWG{謇5Qi4TZCLwwGXXC.BmmpM{{8hMK -`r%fMkNWhkԻ^֠3xƳF䖡[a[:TTcJ||F8±C =55Mze֘33pG}(B¬A#3MYyo1XbpB~A9
7ˑ^h4H 7#ؔ_Pa0HĈ\-iBIIYtM,%%F}}?=3h/GQE
򆯏d2)ZZ)CkkJjG8ұ
ss3e
x{r-X%!
 IDAT[Ȍ
l)BúߣVӑ7"=@8V(Xw55u7ZDVvUUՀ-=gعnDD߄3mmq[["oB1?}[xOU,T*\q3h2Ѝ=46O`ڴ
33D"l
{;k77h2[+||)jh$KRl^cce|m+wge~FZN׺ߡ$u||ݶOzW[c DFL1Z_Uuz{ʛskrs˰ek2͝i\Vh}:\8V5'^<*+
3K)YDysg፷~<@F̬NNr;/1}U[ی.vLEwPyHD3[[PF-7@p/z
Yb&y`$	9D}G:0$dG`jv!S0MؘP>ʪ\{g'$Lob?pV]9
Dw7GkggR{m98t('{!,lrs0'qP]Çs``%c`ii:բ6#fGNի/̃xv*mغ-vV	ջ{C
.
w74:7ϋH$BvN)p458KhEQm"ǒjxZ<
{e7bcs>n#_qqMm:; ncc	gg;46ϖ=cj'BffR1wWLcܷ`D"A4S5G x|v{xPU݀?Nkw[n[b,XtJJ==
Q|&u>Ï~sk^Ŧ͇PUՠĄ+8.^s	?w'VۺՃ=_~݋rkPQY
ƛ_cOG(_Xs:7`_Gh;ЊTU5\T|6o=IWn>By:o3W)>6[//ATTxz855S|s3_w
T*x}t᳏ٺ5ZDd8r4K~\--څhW_DjGK]i1ٹSګ뜟EX(jj-F˭,tm`c4B5D^]CI:DgݸODFzT+o4D
1덗JҖ{减䠾wZ#W&$Xq>;1a:8o/L
q9D9C:}3g
6T*de &.6&LHˇT*S~*Wu*y/q[tqM
c+Gmmn99efLDdd!jv8bc|_QS8hǎ;^} <=uZEnsO[{M?ݥ{VW(`m>2?eS5ҤED"q]LّSqe±EB4OvEB6oWaթb1jqnPb1d2)z̲;U7\L眣P^ko3E&u>=WkC}hXY{vkA$kZl!t܏f1Ԥ	KKsD8\ED7?,,o	11c+1~:3z#bvTDF;Q-Xye}~iOBKo/:լYS{OKt%7~^{vTa𾥥cQm3=bvNoy޴ᚴ47k:r83&0e#9Y+z//(5yjPHCD"WC1֨ammq,oܷ`9:B,˻Tӧ!Ԧ׮^;e`Nwbl*\]@ܧoVfVNwX|l~}־rlv.I̘p0fgoeW>]ٍЩ;{\\tA$13
pqUq֢=8ݗxmRx=5ZWWE"~~a8;ۡ^^Xx^Bl.\'Y8;vE&3&s]o@R/6/6fG'7QHpDž!#0h4&Yf\af&8j
$D4Jٝ,١=>6տ,;gt1c2<1Pr&ZZ: l(̙DIi
4Э݋ci`,L++형ҲTjLK=JXY#**'8^csJ٭BYy-&tv$OLRL.Tyv9/?{\xFyyRTӂЊ*Z!4_/悂
\s8}aV9vI
Bq45qByss;RdY??wDG@*˯b)xp`&Z*+QV^OOgx"9%	ӄseSGG[Nᯨk3O0tz8?fK7zBhLB;RĄmw{5Љ"TT?
vvpY٥qÐSFHZc$q߂^&XEEp--U=<8V5mf_rp?"#w{1-zeX8F8VXTÇ6	{ew~«/K~__7\\bo4 ,[{҅umtNMmu=.16T*--8BQg{OO/QV^+|C2a,0I5EU(.RGF2CẇX,Bh?|}\.GAnB'L͔ Tjٰuw"-R֖J$Z/4,@8~Cm]Z[;NJpww” j'fLN\z@
	񃧇3b1ZZڑ]nHbXm/@gg'u
犐~puljY}J5Z[33)B'lIDLL&A@'bj.㰲29SsS<-j؝\i+.M;Vꡳ-XDD\2ZZ\l,WczOWYy#>xp$=ﰴ4$Ibvtw!r,""**P_{ړ{{+,Y4
m 9&ٹՈ@XN#+>NX0/b
̮WlomBL&EXt9OTjlߙ̝+w_.\]mEX0\(/+kH,
GGk47w 'UUغ81'AnUbC ""ٳ/}^>_}s믮XYN6X~oPQՌsCշW'k ?`n.6`>6nN|ۧhw#)1<=*srq`()k_GBgQfce]Xff46uXZ)m`a&lĚW3/^Ta<s~(₰cW6NdU`C*@`ع;
>?>W"#&j2%hBt\ֶꜻuB{CVvx2׬#~BouW'⾻R:;{s݉);[KvX0'*ȃe$XDD4yc|}R7W~ѳk}}qp$J=Pы!א@ޛkVu~.	ěn5:W#`
t"Qͯld	pqא_P^|8`WUU3z{칆	Y}-U}D"ΦRi\-H2H@w^ ^Ox+Ca;،~8U}WEzgk4ffX9"2\*Ag"xaEDDN\dkksZ.]}>d,`n&)][`t_'ݣum[҅Ӆ`Hb([ F,""wbiX~X0J>ZQw
}W^_J8߆kؘ@+AҜH=V5m#\t雰!i2}ruX0˯Ax79p
aj'|*RvV"q:8geG;3bD>Fr\Ҽþ>-
xy:"ZV$&LAB}66q(1HJ%`PR t}
q⟱FZ}p4um_w7;|מD򇟯'`јagkn[qV,s-1IKK3̝3h6X|4e.ζpq"ulw:Xڸ9ݿh9oLhLY}UI걳Ĭ~&xcUl}NDDRX5LL	1""""21&XDDDD&Ę`,""""cEDDDdbLL	1""""21&XDDDD&Ę`,""""cEDDDdbLL	1""""21&XDDDD&Ę`,""""cEDDDdbLLL:h@4a1""&4v,""""cEDDDdbLL	1""""21&XDDDD&Ę`wM\w+i%۲c`)yt:L7khvIh%ؖ_ƒfj}G?Y%1HL`$&X	,@b 1HL`$&X	,@bQ.\xCjQZ헏յV|yAt׉w!(@Ddssg왉Ӹr˥k:~8d;nzɲQϴu׵Z%~7cuzt/e:XZѡzfqa&-X^|}all9/˲1y:V4[q~y'kG`Xud+~zrr,>Z:woߊ>X݃k>X<zssgpЉo}kUqE	,ZilAgJzm౳gƇ^<2E$
c;Hj#Z{/4.8
@Axe0JvڶePPyeGeN%f(@AMM

?kg`SS=.͝ryhl}+Q*e/<68@Ay9}YP$$fݍ/1vwώA,Z\8#{΂"p@AeYo߸ssgX^8܊~y(3XNx:IwA8a`Խze:}_Ӌnii-L`jk`\]t6}gW_ũ;G^scQl{P@+_>WS
KπA,~6}܊v{jo,Φ8.̞f|qX{~llƷǗxpXcl,O>f8I܃P@.L7/ĽG5[hlEEW"nN/8ޙZG-(_g8csvZLƕk/{pb,޽{[[{ߎNtu{Tʢ\.GRZ՘עZ1 ""&ƫ11^0Tp;@b 1HL`$&X	,@bvr8dCXPѧ%B@b 1HL`$&X	,@b 1HL`$&X	,@b 1HL`$&zqǣ‰gٺ=!~VpY"HL`$&X	,@b 1HL`$&X	,@b 1HL`$&X	,@b ʫFS7^շWX6K	,@b 1HL`$&X	,@b 1HL`$&X	,@b 1HL`$EDԃ8MsTIENDB`urwid-1.1.1/docs/manual/images/urwid_widgets_1.png0000664000175000017500000013672712051303575021507 0ustar  ianian00000000000000PNG


IHDR@h~0sBIT|d	pHYs

B(xtEXtSoftwarewww.inkscape.org< IDATxwxUIHBHޤHQ"6,bEDDTTTTlؐbGibAJ5[ 	!~&@ylv̝snJ)RJ)0HnD)RJ).$1>]RJ)X4RJ)RJ)RJ)
RJ)Ry@J)RJDGDJH)'"~^qsz|}=^:ǁ@u)!-/4	N9OD$<ˇ*5+!"6Y甹 NDdXoĉH	/ytNyxsY$WHiY|	{lDD)RJ\wgs&HtF8]D1o1hʐw!Li<yx9-\
>1$lkV+`GDeil@nq=w韡#K&츣L	
r4;aΰM~`)vLov	^ݜر^Yp?w7Y'P{lkf,RJ)R[N@Y\jc$y2O
(oI\("_b[-<݌1;2ls,ivm1=?sᘏ
:g1&QD{,CD`
?Y(nKc~'K܃
~~z9-c3e4)KƘww/y{w?ؑseώͼ,o8۽}IIDFrrRɜsg
NVdlnYr&A	Ү&e@Sdk4ylv[a أx&8T
/l6dl/oD+t.uڭya@DP(RJ]ц|~~9EڍxOH8yƼËþac׳3&^0t,Re+}4!4k'}psYb>={ 3'] :%cʼn_C/xॕ,}9(gq-o8e:K1q"/;3)؀)gF8OnvnbƕJ_`#p6c^籙uMl
@pG)Rۿl,ûN+̾][Ӹ"B㫮ag$TL6s4hk%(]ׯ`E4k݉r3bW-]cUݷq@&WWs(-VczF
˨}Y`Q{HN:y.
9("_{1,ρ:*Tr{>/~@5|eLN3(,wI6D	wm	*)3=`u
v|Q){v/.A`-@D`'H&8eY-"?˰R@J)RW(XoǗd))gzP((;ucE_~~cE^LJTR5f"55 ??ǹONJJוCZ3mDK{7T}9䇽w݄h`%Y{}yg;lZ/g40{ϛ>	n0ƜM:Ƙ>{#߱=>ADgטvؗ	"nMja^.w Ug֏8tco{!8=oEQZԬ۔W?@̡`/ըWi66_IpHqJD?=RR
CG|Oڍ8o'o>wРy;"Ho|Cc,S'Bd%N""TAsc{X)RJs@,5UhR._%:{w}:RIk|qnNJR:ކ3FH+ioszQl%FF޾}Rv#x=5w|{{"D6]FlA+I-	ȟ6?Vlt$f;Ǒ4R$sۢvTcLwRJ)R\lO_RF}=̟7cn-'kĒ:U@7N=;eRJ)RYJNN	BFzrH9N[@tŁ2G)RJeJni	z4QJ)R*r#TRJ)h8RJ)1^K](N)RJH.oN)RJ)ghRJ)34RJ)RJ)RJ)
RJ)Ry@J)RJcG#+}w+$'1lBCxH0%BW2߶_x2%B>sb>*e҇y]煑_0gIvΙ.;}9TnY"}MMK%RRSiq.)Kh%7ٲs/uUV>9@	?)B4O^O|TI89Cw%XIH	|}|=ND=E
0W\V燏pTXV-[f
,{?ѻֳXsR΋-L2% rI98KdMωGطkƏ̴^-^bOWshgIII9׮-э߃#B~+__9=C1?˯.#H[:SXtϤPL.*t,	98@si+']xH0@}{~M!iXˁXʖcʜVjW7Oႁe,!wQ\"hPBu~Z}aع/6Mgڮaͪݓ!$%'3g*wڜ,@unǜߣLbyC-_AIJNfՈ7mEJ(XHH`Pt	v?p9Ŋy}}Vm$jWU
k˲bbѨv5;غ)%C~NL lcQHaUL|}bXv3q'S"`g-(VnJl1ʗ.A
	,?%mgD8[WZc DiY{Ț94h֎EzͳHPzL|,G̡4l.,TW?@`a=rU6~?psޗ)R Oӕ2%1wO
AUO8Eˆu(Zľ"vP*RRݓϔT\㹶eceHLJf5=CbEز`4vQ2(eJc5ԬT&uk\#.]OUι;	۸"Atnݔ9œ%a\צ9>>Bdt,;?rjWK(bӸNuVo%ߏ5l9He(b[Dݼcm֚3p9'OCF;M2%=N'&DyZԫ^p9%sMv ?|4u>ݐשNlޱ6>Edt,>>Bjj2.?k6߸|S>yBu*HZS ߽1nvcWrmQ\sUc>~/OucEuy:w|NLߩ}=z<^!"ڥ~wGf|"k0|}|x^ג0ڷhS1n")i]:j7H>__3%iӤ>K׆jѹM3chv$&%GbR2uw^_ۏ3ˆ/HLJvv5ce9N?36]GTmI<@hآ=e+T}if3݉]0Ͽ5f36 "P}mI_jz-.bYسazQLw7uqխVso<һ>)}Oӕ{#5hJ'l~{Яם<{/
PL,
s(Vh”I`^god3^t	zJeK1ow4uU"hѱqdO
qSf0oe1f8Ay*Br|2gL*K?S"48S?0ƒ+#)ZY%%5͟_$z
?!$8i.ϸv\Q(~
Sf;	N֎m8]}_|TTL7in1GаV+;:#FKߞdF}7}ǧ'{tM8Sf'Ql7]7]C
8p3뵭y;oSfͧ[y.Ȧ>n@i^&zfOp]6p&%|{?9%8`rEL߸Nur$^ҵ?e&κS<$8]>bK]'q=9˘rzMO>Bjj*^3!7M[wi$''1]lZgSf9)`Gy:]&  ={;(W'%53Pbtx<ډA#0鏿ebk|yT\iJ8c;c=k֮P`VLr&%}\ݴ>
yЦq=Z4Mߡ`:Z5_>_oҶYw7q0&Eֳe^zk9 v#ip9yjhո׶l^(U,u&W~ҼTOc&T.g#ظ<9|ݍWF|TX/VcCRT~F}7w,$@Dw0o~ܥ|Y)\&|,Ymhp9N9
rBMm_M鮱rqw *?dݖpC6F`:DFѪںkg͢U;FFuΫmTb"
qꤤc_FR@~N:MdO:U+_riZ?|썌/&kWc<ƇT}D[WL4+ػ"vPnfKDZ}Z~XLEB>>jk
[7s2+&|͒r9<(v~x#Yufq1njwA^mZ/[셄GJNkvח{΋O-XT


~5l{\Ob!/]Jf
Oebtziqq@a4_gլܰf.48KDzcX2gXa{E֧!EZGдn
_Oႁ.X#qdzrPz<0-&?L㞾;x͏8t{ GEwyZ7tPmA~Cx➮z9*pϨ[;zŲV>G&ߏ5pwkyq?T]"֟owMdnʬݼݽ~i}miX*~9NmQXk6m{!эW<<|3o}uy{@̍jWeTHp4
g~o.,9bxq|Tw
:{ϕ(@"ˏ^IZF\^yf(b]1bڧ絏jq}˷zi(H*5xCRZ%YE]
r5v{ж4Vk0BQ>t
w}=u-cv^9Nީum٨{3))
(QV$i{#BˆuwcK)Y8WeJlGV5i%lc{#hZ7EDTP#ql۽B.'q.LIwΩu1BQ\Lw0[sU:8@#T(Sr׉e,@ZU˷ˑ-UTѱsԮZZU*߸}7ǎSry-#vR T'9v[֪Fgom;ۈ=BѠ4]-Әo<|k6o'1ʕ*Ncbْ-Y./,>Krr7pt$))g(YH׍(55W}pECKP~s]ROı{&RV#Oq5n6nkӺe9DI9HBKteέVʘp;n? ?56Lr2[6D66]I5Ԭ?RTZ(΍PC]-=AVҮ9şdc.wҧZϱjTNp1*-ӚjX=ϗ*PzeDأst5y]v?.41QK`ɚ4tZ=%&%ftb;dmѦqt:t{#ݷz9w-;/Bݵڋ ,YN݁̎۲:*D̑tVb8pCۥ3=MIMeM;CH\հAҺ>ziPJ'.+6(|]\uCͅB@w@Wg̥RRNL_b\`Ή(׺)RJ).}0nS#_x]RJ)R*Kt{\1WJ)RJ)@J)RJ	:ܛ!9:+s<zX=4Yl1v}Wc`?۽|1fY=.P(
~6vF`9w$򌷀q
0
p
F߈-s1vlD,"큾5b41Ɯpخh<cHY+L,7l".lOcϓ"sM¶|
yƘiNwN``1|OisAk҄7ZgXOcw6vMαxy1I"5VLr^1wleZv:uDժQuzWg4L_Ҩ^%pt箣S*hnM7q}|EqM,gǮ(,XO%KVl*+1fEj.Y3̜76HP 5qogaܵ_2
3~,v'W8p؄
5{|`+AD5UM{!f"6/"?ȍ*kNROTzGW옚WKEsc'洄L53|㴶L^2Ƭ7<ߝƘJ!d9k`CNpXTdtFb]
<<"v밭=ǀ"SDcN/`'LT|k9-
!Gg1ƊmyjYc}A9z(};m=Gpx+:Ģ`
dæ$JdǮ(;ɲUM-@g"%	xEc	`1fzbiN+2c855ؠ9p1f6c9܋=@څƘ)Nkv ֵ}s?fb[>^p
2t?h<ݜ;3]Df=~\ֻxH;	vEp3GDF51:WPüܣ5~{r#[e%"9ncYvXDZ97 ^'h;v"R
@cvQlvZT?k8{bxl@3Lv.ð-8("2;KvcL96T{pֵ_x<f!"GE0i
u.{Ƙ˹[g}]*HKĶxK3Xrٵn=ː1i?a?`[<.iÜ?Wfw:
o="AKi@5P$l%Kq4mtx$۶m`ٲe̘1.]c+u)hknng#>O?w;Weמh–R8vM>?Aқ?RxY[zs4.k:uhCl`3wnsn`p:uivDG#h!^}NJxԝ>خ?'m %%'>m77~Sst֊).ek=q	^l\̝,<ϼťFe;ekO;ˏb[ZqZ[a[\~ɐB\0l}FlilaW;F`Ji{|ψ)l
{c7.>wpb_wTs#:I_tk
("S1C]#cH{_agc%
pgbξ\+]c[a±-v屭HVKW^2l^ty^(y}: bKluy<-^]n rtЮ}?p%vqYus6i/ʠchѢŊcҤI?\6~8*``SOösCF{+ ?CM{4[aa݌6oKY{k-ke[$cO0wA8/?w;w?	`?56k6qʄ-@HB4i֕o]<|״Mڳ/QqL}9\	#[֊X\_;q6(j\Da/9tٸa9)"Vxga; lLnō1#Ŏyۥ:lwIg@)>؏`/PN"Ԛf5[߅
 8]LF;{1N;yO_㞨M=}Q8[a2
i2<,׿X/6F3ɔ\Rhϳ:|>^xz.ǭw=?N̸_wdb,ܹ}>@r8bzYIfm\+M>TLq
aRt ;
n~ЦR}[!PK3<ږ3]&Y,J*1kQ渍'拦K#SEU~*:"[SyhsOMm!GCB6oh̊#;}U5Fb뭷fԨQ3*
f8@;<83YiӦ?#ܚހoU{qGlۯyǑ'\džt⹗em{novxhӺ~<מT.ßzu6;Aw}w?ensm-mw?In=þ^ʐ[FV=xdثnݒ=vސ)_3}N13scG8:Z}?l.ݙ-ɛ|Bvয়~abхbqѠ7@ISR앲{KYmGkV~$Ւ#lIMT;Rj|LIztkt[e\\xdث\sY;yw,kw_7٘g_zq|m&MKwf:1gؓ|,e=7/{r+o3>xeZu_|cM=6u>+]+_|ڶn/1O91
?2>~w_|/ډXfr9ꭏ&SK*@M;o+̆WəkLR->D*@S1h FW_-rFW^Y'EO5pywu㤣4u@](@3j
P2̴ٗ쇺T~$I;tZ"IkcC)S!9%*P!i;I%'[A7fّtʘPXK:YP$/i{I$+\m+xvx<Ǟ2)ӌV!zuXf#iB6o%֑uRYrԍ`Rږ9S$O=|M8u$nX3(X{1~MXLb]P@NH5FL;KZwQ5D{<? dE9ltB8xFR)f%<щ!;
d}/98l{K<~X!0$M;}*
].`h=2P2iѢ9ߏ;o8W7YK1L.f2^R%f^
9-o73x_IN7ƚ~[<꒺xuTxV.n::%
ICxg`ur'*//72
yoci=P{\	8̮)jfd1>FفfExX;pZ.x1w&l^SP/jIJ(ZO+w|%@үEVlw7{s ][xfo*Pczi8g)u)2I{Ve{$cu.=#i>d3w.Oz}.7kf6!f()?!El)E'PT~Op
M&uGIN$
ӺMM~y,c
UtMzƹ!>>%n4m>?4[zYctFD{N-?OWx7g3g#Q_S5vt]?)Үx$喝".p1nq>*>"{+mY&{q3w86ꮸF8o5]ϧfݭ!x
кL~+Nt(C/~V&?_ls\qxw
F\Ee.!{
8L6/n6{
>xl)ż7EswxKQ&x#G2kR~m[$=)3GG?
Pw|jtn.\9M(Ze^-\P`@רkBTowl}Qp<8xpHpe>bls*^`
ט?=DݼR!oCgHw+^>vc2.)^n>+#0(@V65m,ӭw
Y_vB<n:7X dO
"ݲ!k
;Nϐw	vn|*Rd0 _ϠM`zWz|,5~x)yZTd$s\o\hnvnjm`p9_+f/4,\*V`8Yy߸!ʼVy[Jmk ~o<$3>0.yV63_(q*YKu݅v:@uhCwP~܏WmeeYw#?Q$MXS_+>bMQ&kz55;ɱZ`O3cfW3LÍf݇+ ׆f=ܗf.La|0f,p?IZ8WҎ078^*rr57]o|!~V2&.-}
&}}\=3g_,p#p=ofz$IFCCzmg~
~1Zw:k̏OWv3>5Qٺ +f6,o܃^o\(S+
/5I{:~`fJz*vG[?.K^~ߡ}pԏb__8wm⫫'蜪Z[^V.zteqS$I3'J_[%:d|(3|6Beٵp|d3OF+q
\9; {%_^U}%(ȾMKt(&%-Ȍj)dG$Iii`)IѶ
5`I'M!^V>3q1񱘤Cq3q/@HzA)Y@;%AtIk*-q$0_W^^1*Uu/= {_'1C%nrع
k
((}zip[ܬ9n_ nFW;(9L>#Tb}McfdRtOԷ'AҺ+n18%nWKwp}/v$I$IԂ*P
u1!{TΙDihkIHcV~I جKJjbf̼Hj'
T^s3$U&I$I\3@I3@IMMlc.uտU}G'$k'I$YIlc&7$Le'i$-FOrJ".C]k9Q~~aI~b,I}9DRGHjY$iJ֒F)Yok#i1II#%IkJ LI
9%*iɂlx7>[AW
^I{Iں !׎$*l%!Y
BV~EZFIZD,ܛ$%}1%m*ivI'd%m/c%Qm+/?@$X!{I$Ln
:	VWT|G'G.r$ӄ<@S͠$I{~p:y@џ?7yϏBi8*04nHpP-'y~Fk懸GnQwp+b|wy׉qo1~>r	pRܛ_@CN5.,z}K$iT4	\\W_ַWzٓ*i{;B.of?}%Dg%2fvxp2y̮)Gg]|Y	u)gLޏ􏙨+7'>s$Ih(*`>`.`I@
l0W:m$-U]1*78)nU+iE\9y>V,QCowve5?22b4}`=T`8Gf7ZGI#Ub$iO$,5nwC4$V5c҇=1_W.d{cf㳄q󇸿oM~mf6X-fGaO5s@J?M",l񁖭$-^&ZuZl7|f><0s>x;++_EY\{Wfyqgxԫ(kkMDŽl{Dk9
'nQa@<Ī@A4L$itԻ$i.\AY9P~̾;'xXX7&
e+%drVZgxT΃2Q/2}a7cqP>S^	|fU|gHs!>#g4]rI&mSz,K&I20텿+)	b̢V0S.Goz3^UqedHeHZe.=peC gZ줨֒Lx7XZҾ"އ+5%Z+Ety#V*d}'ouܣ!ې}g\$Ǐiݦ74zxuaM}\a/8رpocg\k8*gGBB}[T
kG+1ܜj8c4	8Pi@[\N5xXd
fz΅B,#Q7?k]v~$h#qsQؖK.>بߢ6b#wkf|q<7>:nntZy%dH}"4/`u͊K{d'O[Dp\A?pv EWuqEȺCĻw,ŕ`!kV2
Yx6}NU!ܔl|V`WZ	]f-mn
AX27ZXIoKd#%݃lK_
BP/'\a|ic2X?IX^36#➅n4pmSbfH=c:%UkI&#
98^]9aIN1;afWx_SIfL5tZ8\LV|W&;87+LC1|誂c|`7L>[ޡ ,1oAfTy3ɭ,0=
+Ɏ"I"‘40ќ%u2kSH?/0.^SJ_A}|frJؤa´y0J9 IDAT]r*oI)Cj)#Sg’RRLsV.s&}R3
>jȿ,xdj{'ܑ6VXfM]3fVeZr”j>s8aj'g&CH/>g'd(?Z$3|mǔhk7Ų~Lc}ju(w=n1*gxNIɫɓϬɤ/fYOF(ܗ3Cw(wMF-w͈Jvח`f/]7>L8)n5$n'Clf`+x
K$IdwNS3ֈ9?$׉Yk
I
~S}f}$h^
yS\q!pgJy.Jy~9wJ5wY^=NYW5yX,BR
Q)kc\	,噀{RIT`foʃ,HUkZI}Ҡke&X3Ðg`XB
k$I}`f%݂b9a+`M3{7I-̽+ނwG|Vfvi{pI{K+Y=%=nf'I!LZPvNS3Sna1[!O:3Y3+^
`F1t&_8Oy
HxxpCs=
	\+II:1R1֔Aҙ	`IHj-TIKd{H.$u+J#AA[Az(i$%!Y
BV4PU\[FIZDrW{s$2/{|]NtA_I:VٶKU3IQa6"0|#L$ml7}qGVG~ S
vX{8(ͅA/n^^}>w?
7w0_ƶMoۨ2rk[ch	]2Zjq9y+oS46T93m
>y3{^@x{k	lm?dAeB694X+d+#1vc͢	Yh}a׉=7R\\NqP
?w2pomCj,`Jx0r![_0UUם#sg[}nTǜB"lYK%xbfYuf>O[WHf;EmGḯTsl{kniOHc(#ƹ5+"[[T#"gjj[Chs
nãR|vm,q\&>4pXlwܼ^2fx`d%Լ |)3oǂ
.,\1[ k][Y}'ĠdAv`M`2G'q73}6snlsύTgwjq-/𑨖W;eǽ(/`o\t|jtJmqO'~%>vns|/WJ#fwZ
1[ܲ}Vvjgp[J!;l~|edqgV5g^$f[#pd7fsW/_kW*6+Ȗdۯ _@ET73}6mls
z_$iM`e3{HiYLcfY܍>Hւd#u&o{Q~%:	66OVe%I$
@MVam,Ю*
ƭxltZl7\qwrQ	x ݢl
x?WuM6(efiã^EY\9 w^5] .*P7E9|\(;.$I
=G&#n|6Ooc[*
0XR/IŋA`=I;K+,:vg$I@酯<$g;?7cPp&;> w0DGiz]BWΔjGR;|YI=C2d'E}'dMцqV3
,//Tb!
xҽ	npU:nu$\
YWkftZ]55!n}>H!U:WSf$܉O|MqxqӷmYypNKggwʱCWb=nU3mL}R3OO8-%,Y7w:D)Kq5d@zlͼ[>slBM7ul}̺dHj"GR>g'jP$iH]$I$I%I2$I@IdZH(I$IOf1G3mV5WHJ]L>5y="WZ,<%I$I$I5	M!7w><]m8:HZX&M1PH$I$Iv4	~W[+I9$|,탻4
8mqeR30gfca`55p@xIj]wzM,I.0Wc%efUsozq8uEVAI$I$IRkjCAb&0	x?Zs7.<ۿ
eyx	e|1wx	_FpKpg<•T%%q]fOF1pC(
'*04nxaq:KZGpIK^a֓&dpPk|nI5.WpCv}e%k~Y3\nP/r	u:4UD^'iaIxj*e$IR&pr+±L7jdã^MjrRt-ǗQJ&MbY*gB?o(mf_ũ҅$/;?ے$I
OK:_o	<
ldfYzlfcCvYBq@D48̞+n>n6[Ca(a}kfvAȾ[u86dCDX-t\V-Hpov֮<+H}ޥ=3`c`738d{s%
0B6L \)lk?>u1Ia13cZ)耛ٔRwB3eekWE@3),ugEolX7Vei.̀kc_~'Ywr_	_yb>`U|v<}
UFns۬5TÝ
BvbOed{+ȚV/}V|>ײ&{|\v+D;֮ %dUmW-+ _;[.>(J{Sd֤.GnU&p+H75{)^SwK4/)2p{jv\qPÒVÕu%/vH_IwK/µo60qntm9C҃iB9''GͣNx@n0q;;I2`fmѸ$.KrdkɺׄT&7Q*0=1!;LMF[D݊|Z\~-~.O$IIK6NsjоVSY\@DzsjxB]o;u4s7N{:-϶ȭqn>rg;5e27dGp.>^2苛Oǝ3	UY[|_((E%>tx(6S	_t6}@wRj%܊y=Y/–Zj)w3:
B
Mʟ4^fuD&}U-;#Cm۶\wul3V$?/$YBM$I$Ijѽ{w:vȐ!Cw}i
J_73yo$1_$kfqfMEN	bf?H̾˺N+9D&}$53@cԨQl֌5jFWA> ^;q[_\Si/A6<]C"R*xJ$I$Ih
~KjUSj^A;婺+&Z?k[$iH:LRs"(euy֕/N隽1 5_#[|Syي+ޕ⽝$I$3+ޑJZZ
LJH/t!YI%]<1ro#.IKʇђ]y]V7"Z2IPڒnrSf9IDY9=(b\B#$={^&-ϓ$X~-e%YMKm+/Kڦ^T|wcxMAWqd$I$F	B3fƘޒ6m
|ff%-M8X&I:wz+f)3?0{.݀[IJZF';ǒvvc(
x$
݌ǀ܂MxIB{^.;XwK$Ix\JafoJsK,p}&{.|i偫Bx	>fv~_Xx
_(vlfIZ;sel+
̾&I13
ѣ?~<{,:[n匮Z4S;T&ߎqntkoVxy[($+GfC8gXx̾ՙw*77l"`3`~$jd/k3_ǩ뀎xOͯ
_x^n0;}\B?F}><x!'JLvQƎ5݃II:Mq,IHZSRIgJTHM$%=$m'$u+J#b/I[d=$Q.YWI'Ij%isIdJ:CR0gA6dIZQP2\N"N;)ݛ}1&K:9K+il[I6*6tHjRD#Gcܸq4|7Ii!fƒvXxp<)U
<61[x̶2LE}&KSnF3:?_v~8Wd^Yܢ;G4Iki7gfIڙ܆WY^a^kfĵ+/L\xpɭ3nfWrIJJ\oy`L|Vtc=^/4o$+C7EWPp}8{?+g3pnqcډ9U`UqBLGCBAj+pt-\Y
of&ipu(pM48WX\Znf}l[3PGLiQqEppJXPLWW3%}W_%fP4XIHwݛJ܎?gOEYoϸt3ǧ?Jz8Sfv(TȺ}/ٟ㣥pŧ+;pJ3={MbKn>U^ITL\0܃݇;$y	iKK,PE3U2h<_1	ͪ{<[~
ّyF%3;KR/b|g3%\`foQIk9S쉐"wqp%FPtvؐMYPrυ2WJJ
fg@R[2qo.nfqJ+0^0Ip0hzڷoO~_/Q@)>Ϗf6\nxJ:Ο#ŲOC
iҁwF+끯K*E\Y .))8~V;~-N*Ij|$QiKhf?V^*cRXtnz]*ia|=
K|_qڂ
3UlSf?eߎeI]kV'kfd? Ջ@3{,>f@l	]S&7N[|֥X$퉿*#i$>cԹL|~V{gfH:if
&rW}k.q%ntAqSٲ7\-|!_m[MJk$_HBޛ|^4-S3[̞.vg4@)"i(&$Wmzxx>fݭ|$jdN՜h5mgt{Qq773,VBW(!;Yx<[M(O5EEeCWwql3S|VO܋[9s[kmJl 69npyA/nw~O-&u?YOܺb1p4.d]7[k|Z_d.GnUc6[l
̹5{_lVvn:US}>Jd,V[zq@	_u0/n^U:W7۫ ;)Lqd̎;(*gs\9[7E#\9+)ڋقB	~x؅C*Lڤq`	hzl,H~;V`iTKc1qJ'>D%5&pI}"C$I$I$I!$I$I$I%I$I$IdH(I$I$I&CB-NJdHj"GR>r|$g%I$I$I$I$I$I%I$I$IdH(I$I$I&C*@I$I$I4RJ$I$Iɐ
P$I$I$MT$I$I$i2$I$I$I!$I$I$I%I$I$IdH(I$I$I&C*@I$I$I4RJ$I$Iɐ
P$I$I$MT$I$I$i2$I$I$I!$I$I$I%I$I$IdH(I$I$I&C*@I$I$I4RJ$I$Iɐ
P$I$I$MT$I$I$i2H$iIs"g"iިgZY|fmI$I$IR\LNt$)\I}$5k{kӱ.+"IHjS,IJZyvznQj~۔3{*^I$I$I}P
'Áـw$m\םY	X\ i`/_5Uzd]]$I$Id֣ I̬݁uuNgu\#WYǕaR>MuZ$I$I$ɨH!f_󮙝
tOEKZ:VgYIsֲsI\<H̾4fk
e%M~$_A>
p!13焨5sLI?[UДSNQ箵I$I$I>ZogהƘsU,?CXಐ/"|VSu[IoIC5+IIZ]W>(iwI,K%i
nt Jҫ(It`$0XSe^08^L^:~PW_%}|-iI+VW1IsHxJIm$}!
yZs~c$})it;Ͳa/2sl'KN	4	`;`i?owse%cao9~i
2׭32L~^4Yqo\Nu\1ZY9ẁ{%)k4̤x]^ϰr`ޝ̆[a:Y;x_ԟrIf| sI@]3+>/`[!xw2-%?mιCxw=w'w&,uM^G&7q3IDC2.bjfV
o(V@g?GK4@3+v`a6Ny':Fiz;9w$C^fd>su$9o(l 
٤̬u6Hʩg>Z? 1fw'bi&O4ޗ44LLĚY>ʚ$c&V6CB[yf;V~DDDD$H?_P3kŪ'?y}@3!xϙd^T~w8:LN\f1$'윉7P[\`Z&A<sB6w\ef%2=74yF)s1\G~VS;@"""""1HP$"""""CD@"""""1HP$"""""#y΢c/^f;PbهsSƲrnZOnD.h9|{>_
OP%%ןfݪe{	%JSq+ZD\Aw^1oDgE<_zGxu2~PcXTgLd_سo/Eu9}g/p=]זI5`4%K`22uEQt	lل1رkGN}C	tjۂ
e}ߤtx.ؔp`[?Sۓ5&d_lqS[ٹ+JJRtS.GDDhC?|;C&QZ߾/^yv/ks޼yϻP'g[7gϮAO~b<^{.6o\?7W{Pk6nZ}{33.?qntjޏ_WMbQQ|9->1NjiM8đ#G.+w }81q77_v_D]3}q?KAWbA=QQioPN%
vnʹMW('7^}++vADDNMP^xVc|y=:+gٽ"ŨӠ9UkOvqNJIx֮ݗd9T\?u qnϛM8?3'C88Xp&׮b,Cfm)]"`EѼMGLwвy/U}F͘@ͩVA.Ѽl޸Mp^RZض9Bm>b	%T6LU)Kd5ԨGgbQQ,_o޼z={>R-YGu)R _K
%hѴ~|qKC)Y(>@ض},M;]<ی(cGAtt.L"<`4UkcyzVJHttl]*5k.I?~ŀȑXT|u.h|qoȢSiҲ=7PH.
jUcǮ=l7u#˗eR(Z}EgARat{ًPT	
,
Ŀs1'ڵh̬8D*Yi=ދ;we$';ԭɗ}'h{mQQFtt4IIxỘ4s>t	xwv%%qN{gm.bc8x(	X>w>e~w-їxFsOKe}=ŰZ3bxUQnQi3sťj#ikmu)M\9>ˎM:E1	=g]W7i^;X'2qs'}O(rq\Kfi.W~,5?/~Ȱ:a-Ͻ=%'+cӆxtؿh=+O|B	^~;FLZ񫹽9r8/n#TՀ>I޹n.宇_`OraŪRoH=_}v7]I-x?rnC&3|zO.+mmF.vz0Ukҭyjxe0W;.9_8`<ax8YwpzS>-1}~sl`SH^|V,GGXr
hΰ7{rAﻃY:ڵh̔ۯev#}u1vcctA._EjD1͞81$';|<2ںz>Hwp|f%TT>o}kdRʜHrncg"^'{
kY^{Rb)^|+
kmWӡ}#ud_GɁw/""'+]~gѬX:>=>`
}X2{v^oIUNNj`֯?panZOJYr	S˞];·^JW{}JUk^`pFzU>yc~mQtyX8{
JߨY[|5Iͦ
ٻ{ga
%37s"vf\.JueٳkcGf{/]7fF/O9?
Z=>751v ۻ?_
I|;lO`ӆ|ދߟMw=΂Yh}__d\2=KܓNe8?XF!غɻӱh{
'u?:n*KHafL׷\~uWۍ]xܽk70˿snmֈZ1>uSOnپ3u1ӯؾ;_^@f
Y|mFFuV\L6kpmgiRiy>9n
KV+Lw̌kt{KCI;o³ZΓ{	
ә5w%7}1͚ÇPL]yik7u3Zz3/#yy5~J8~IDATb{J(ʐz2~b9!O=r5xb&"BuԨK&T.g!JBn<]A߫joFGiR1/Thjـ^T݈ݞb
|6ə}m:qC/3D}1Mrp̲<2ônTZ
kW^ݳr3R9r0Gz0Ӿ_fNMw=N-ǼK1(U"0`WLǔQ@*5~~^^^pYDםMzi׺\,ce}]R,qH2Qٛy(ܛT%6kC:۔e5[RAYn+{'Xߣ\dtOè!W
i8Xgׅ
kWdY)_WB1{O,;-m3<xT5nIvvՒ,;Xx6?~uipZdW5Z=MЧ$fMfeÖmϥiYLۻ{'_~o?sbnOJ׸s.&v`pۄ̞6&ݢϜ2?Hb>֨Ǚ.cÚ?[uSh1<@՚'ԨӘr+ծNuX1x]j4˜yspeJVՊthqSgӑafe˻2~0ƎQoTT4[6;/Dr\sCٮd͢JfL2z@cbӰq;$MZەLJ3!{]"nD.T]|
CJ}.co]s#?nYt7FpwJu>f>//7P48?6}_|~ޏg=_=}oW\ÌKG(Y}N\l,jMϐo~szԫQe%&CߧsH2*ا<ӑ,]6p\#Ndz$-ֿwgHly»#z%;d$?NInN
"Tx}oO(B7GCST111?56o+_dќ$EgP-c_(U<`
N;?-{HkÚ?ذv%{wST9j;-8޹Q$54N[X4wj5 >W?#7S~SK͙S'X]j)wMj(Q'b|b׸U}-_alu(ToپkS"!r1k2VoD	l\G? ;ve߁.@Kn%7-eڍl7U+;fCII\QPԪF^s^-]m;(]"
P"{+q/Y-(R -MCMTW*Ol/%1~\)L&Yf
b7:z\m%ó>(]*ZQ:91iwg
b>̪՛iX2^"'/ɇL7xmYtqq1k}k6c9ko#>MUvl{_K=n2
KI.91ojvGZT!w*qM`ׇ)03(/K>	
!mG?Z0`jT@qL9m;vѾe\noryWSz9~/[OE?5T*ӻQ$"!`f.cqq4jގ*>$RZK;eNG9uSnYO>,_止l:wjʃ.U#"AtH+/w$039HS$"""""CDD@"""""1HP$"""""CD@"""""1HĈ	3s9Q	O#bLj?7"*@F,ʩ/>'}9ܠ#R!".p"""""1HP$"""""CD@"""""1HP$"""""CD@"""""1HP$"""""CD@"""""1Ibf,$֏6
$̬tu/}U2f6n;zH
H+,
9w(8 92,IΤH$=
$,[@} Vw	vt`fQ3@[ιitm6ƀ;pHιf!
%:ژY%`lfL6CQq	_
$\]ff7Euh=sn1*@c3S8XpHŽZ9{@ڑ*lVsod(AZDDDD@i^
̬"p>q8ƛ&`^1iwp%{i_;mH0j=S?Mnϭ$ρyJzfL_
l: /)=s
HgxCXfrȿrȩḪ;_7n8VD.3s
$K
xt}Hl*80tsM
i@^?"w3@"""A;bf#8?""HDD$HsKV#""ADD$h̬4ހ.[!HDD$7̬H#"]	;CC[+Q$"aJYZY3bfM3u4"fVjYcfY;afwiمiR4y`Sr]Y4C^6YID$):3̞O󺹙
23ˍ|x0ӁD`s%ێ6b9璃X׫^6!iěu~o2^)A]
Tb=#J;Ҽn4P""rsSMn	8`.C>@4y
JJӀ%@ K{(d	up?P
+G6i{>BMfր8$9PDru}fy0tuM'̪}ofÁ
`(sΙkd@#ι_r.GpmCM
"*5	qvH[
T~"<0!B\n47;\J7@J]u<
t>Uf΋h>9Enվ=
'!"/}q3k܈D|
3^ϥFsCS;;8fo7;w|^]B}	?
D8;_$R^v΍CY^{s_{@6g#3;?L|I^]YιxmS`9U."f``D*3w
t4vx?;Uuέpo},>սAR?f(-=>	b}#B_""I qέ_o;;?wmzsιl
sȩ:'pP;!0u[,+pݚp>Bsj/"̜؉qv~f/&'~]K_3[bfYȩp%;g~}iYb^~…sJ>|(?	:n~s`	*@'
:Y?}$])NffV?&e!kf3xzjUh?½~""D]4P0osdXPo"9gf_;z=s?A3eKؾ딡"\@_$.Nh?Up8"Le<@s0}fyι6fV	::w&{	oN,>χpȩPl0
p3/I{{U(Fͬ`3{(_ra=N8U!"!y){H^-#~""HeC	<""*	#
GI8ҹ`ˉϗCXϳ;~~HHhl<=BD$\E<;^?pyN,zp{DDRtyh@~HY93kvkfObf{134N"f"}pHr}'99t}\
?Z
_;o?u!tK?	rn+)?dfW9ɰ=p/@Z/h,F8xF suuP~<(99zY-'>_ra=N8U!"!aye G<@Uwfvsn8^_s.fV	x(X.*ۂݾZ~OD$y
Z4y8
Wu
`ZuۑI!n-?'"<@Ͽ~Ws/]w}7(ᯟ;wVmsyJ]X$r%{_"R~dfEι1@t+pέct99
,Pnڡ].D$؂ݾZ~OD$\ylfKSPgfW4)x	3hf#i@=bfr=sU$yv½~""a)R~979W9W?M㜻﹞ιs/_w_sr][9w,"99߿xTable of Contents

urwid-1.1.1/docs/tools/templates/indexcontent.html0000664000175000017500000000465312051303575021675 0ustar  ianian00000000000000{% extends "!defindex.html" %}
{% block body %}

Urwid {{ release }} Documentation

Tutorial

_images/highcolors1.png

The Urwid Tutorial covers example Urwid applications from basic to moderate complexity. Each new concept is explained along the way.

Manual

_images/introduction.png

The Urwid Manual discusses each part of the library and how everything fits together.

Reference

_images/urwid_widgets_1.png

The Urwid Reference documents all the classes and functions in the library individually.

Indices and tables

{% endblock %} urwid-1.1.1/docs/tools/templates/layout.html0000664000175000017500000000024112051303575020475 0ustar ianian00000000000000{% extends "!layout.html" %} {% block rootrellink %}
  • Urwid Homepage »
  • {{ super() }} {% endblock %} urwid-1.1.1/docs/tools/compile_pngs.sh0000775000175000017500000000060512051303575017316 0ustar ianian00000000000000#!/bin/bash -e # args: scripts to capture DISPLAYNUM=1 SCREENSHOTS=`dirname $0`/screenshots.sh XVFB=$(which Xvfb) if [ -n $XVFB ]; then Xvfb :$DISPLAYNUM -screen 0 1024x768x24 & XVFBPID=$! DISPLAY=:$DISPLAYNUM # this still doesn't work trap "kill $XVFBPID" EXIT fi for script in $@; do if [ -f "${script}.xdotool" ]; then "$SCREENSHOTS" "$script" < "${script}.xdotool" fi done urwid-1.1.1/docs/tools/screenshots.sh0000775000175000017500000000123312051303575017175 0ustar ianian00000000000000#!/bin/bash # $1: python script to run # urxvt, xdotool and import are required to run this script CLASSNAME=$(head -c 6 /dev/urandom | base64 | tr -cd [:alnum:]) PYTHON=python urxvt -bg gray90 -b 0 +sb -fn '-misc-fixed-medium-*-*-*-*-140-*-*-*-*-*-*' \ -fb '-misc-fixed-bold-*-*-*-*-140-*-*-*-*-*-*' \ -name "$CLASSNAME" -e "$PYTHON" "$1" & RXVTPID=$! until RXVTWINDOWID=$(xdotool search --classname "$CLASSNAME"); do sleep 0.1 done export RXVTWINDOWID image=${1%.py} c=1 while read -r line; do # the echo trick is needed to expand RXVTWINDOWID variable echo $line | xdotool - import -window "$RXVTWINDOWID" "${image}$c.png" (( c++ )) done kill $RXVTPID urwid-1.1.1/docs/conf.py0000664000175000017500000002317012051303575014441 0ustar ianian00000000000000# -*- coding: utf-8 -*- # # Urwid documentation build configuration file, created by # sphinx-quickstart on Wed Nov 30 20:10:17 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage'] # Add any paths that contain templates here, relative to this directory. templates_path = ['tools/templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Urwid' copyright = u'2012, Ian Ward' # 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') VERSION_MODULE = os.path.abspath(os.path.join(FILE_PATH, '../urwid/version.py')) VERSION_VARS = {} execfile(VERSION_MODULE, VERSION_VARS) # The short X.Y version. version = '.'.join([str(x) for x in VERSION_VARS['VERSION'][:2]]) # The full version, including alpha/beta/rc tags. release = VERSION_VARS['__version__'] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' html_style = None # make readthedocs really use the default theme # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { 'sidebarbgcolor':'#263193', 'sidebarbtncolor':'#263193', 'footerbgcolor':'#181035', 'relbarbgcolor':'#181035', 'sidebarlinkcolor':'#aabee8', 'linkcolor':'#263193', 'visitedlinkcolor':'#263193', 'headtextcolor':'#181035', 'headlinkcolor':'#181035', 'collapsiblesidebar': True, } # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = "Urwid %s Documentation" % (release,) # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = 'urwid-logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['tools/static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. html_sidebars = { 'index': 'indexsidebar.html', } # Additional templates that should be rendered to pages, maps page names to # template names. html_additional_pages = { 'index': 'indexcontent.html', } # If false, no module index is generated. html_domain_indices = False # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Urwiddoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'Urwid.tex', u'Urwid Documentation', u'Ian Ward', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'urwid', u'Urwid Documentation', [u'Ian Ward'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Urwid', u'Urwid Documentation', u'Ian Ward', 'Urwid', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = u'Urwid' epub_author = u'Ian Ward' epub_publisher = u'Ian Ward' epub_copyright = u'2012, Ian Ward' # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. #epub_identifier = '' # A unique identification for the text. #epub_uid = '' # A tuple containing the cover image and cover page html template filenames. #epub_cover = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. #epub_exclude_files = [] # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 # Allow duplicate toc entries. #epub_tocdup = True autoclass_content = "both" autodoc_member_order = "alphabetical" autodoc_default_flags = ["members"] urwid-1.1.1/docs/urwid-logo.png0000664000175000017500000000502712051303575015741 0ustar ianian00000000000000PNG  IHDR2F pHYsNN3 tEXtComment* IDATx R90ml0Ynd K arp$ت}>cѭ#ǒZ[=o[Ow'w>pH߲$ ڊ>WG`r8w+~s寴P3lcM}@iP-<TӳD!*7|ҧqЉ5пkb|Vf~ƆΒL =q%oR~BG)ӫ%?Ԏ2pΪ(Kb$@Ÿ$MQ(k/|qZQ:T(3"m!ox|z4s^(qyi' VppĨ|6c~[^)JyiB=@v%̠2EU)8*zF|'}sB"sD|g79N9+(!sS8yE愰@NDFޕ:! ^Њ}K~Q=< +.. E &LN+u.Ne+E/1MnKlbc>OpZpP@N< ;3X?3߰pf྅I3>|DSGU[#0r?pEa2oIW@f EJ(dB}@F=@6Fۖ<D(aBIR)ax0:l}Al t"px_Aa)D&=fbпM'_[=t Kϑ2Pds ICM`Tȝ GRBOJ(@.@c[)8MA{M9GL!ǎ){iRZBP6k\ RM%Cas4: S6!\'H+w94SޖWXEMkA߆}\ AK1Rџ`ғ66eMPϒ- 3Cʽ@Y=MN52 @4(c%*&Kv`$\JGU<Ș-f'uBRXw5v kzo9Au {Ǽ9ۚ@^@R ~/嬵I0\ Dz":qt t11c- z6y&fs<: BE'>zxb %@| &O׃^c,CLXxFtKl$ts!6#֐o0\B&f^6N0P0ޢXO׻r\]c>b~Y.;#l:os1` iV5lIB8>H^7k~ @e 2*;F]+z*ҐvEChq g{xA>3 "FS;2G_;6ɢ2It{^$7;DbH$k ě;mc ;? #uL[ZHo ,#d9z(׈}e`i!EAIC4^^o|fa,L/E9:"|iy(,,rRa(lleԞW=c|= Fmc,4H,$zk_+}#Rn&h}_r0u49`)relI+{g-Ջ<_WMXQ=YXba1\zqѫeb I$Mظka! 9ńؐƆ;h8qd0T]dj 6b9V_#fsDe %ҌfoBxuA+[m"0TFDjXư"|2~w^hMIF{@F%ȥ+. g#vu"RZ?F ;=>`jVl-=6h `stYk#QNc].r#vY}>CÈzv3e oC#R{|k\a q_nlčEfhy.2Ii*AпE.9ቅZvቅ荵 IENDB`urwid-1.1.1/urwid/0000775000175000017500000000000012051304275013337 5ustar ianian00000000000000urwid-1.1.1/urwid/treetools.py0000664000175000017500000003667312051303575015752 0ustar ianian00000000000000#!/usr/bin/python # # Generic TreeWidget/TreeWalker class # Copyright (c) 2010 Rob Lanphier # Copyright (C) 2004-2010 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 tree view Features: - custom selectable widgets for trees - custom list walker for displaying widgets in a tree fashion """ import urwid from urwid.wimp import SelectableIcon class TreeWidgetError(RuntimeError): pass class TreeWidget(urwid.WidgetWrap): """A widget representing something in a nested tree display.""" indent_cols = 3 unexpanded_icon = SelectableIcon('+', 0) expanded_icon = SelectableIcon('-', 0) def __init__(self, node): self._node = node self._innerwidget = None self.is_leaf = not hasattr(node, 'get_first_child') self.expanded = True widget = self.get_indented_widget() self.__super.__init__(widget) def selectable(self): """ Allow selection of non-leaf nodes so children may be (un)expanded """ return not self.is_leaf def get_indented_widget(self): widget = self.get_inner_widget() if not self.is_leaf: widget = urwid.Columns([('fixed', 1, [self.unexpanded_icon, self.expanded_icon][self.expanded]), widget], dividechars=1) indent_cols = self.get_indent_cols() return urwid.Padding(widget, width=('relative', 100), left=indent_cols) def update_expanded_icon(self): """Update display widget text for parent widgets""" # icon is first element in colums indented widget self._w.base_widget.widget_list[0] = [ self.unexpanded_icon, self.expanded_icon][self.expanded] def get_indent_cols(self): return self.indent_cols * self.get_node().get_depth() def get_inner_widget(self): if self._innerwidget is None: self._innerwidget = self.load_inner_widget() return self._innerwidget def load_inner_widget(self): return urwid.Text(self.get_display_text()) def get_node(self): return self._node def get_display_text(self): return (self.get_node().get_key() + ": " + str(self.get_node().get_value())) def next_inorder(self): """Return the next TreeWidget depth first from this one.""" # first check if there's a child widget firstchild = self.first_child() if firstchild is not None: return firstchild # now we need to hunt for the next sibling thisnode = self.get_node() nextnode = thisnode.next_sibling() depth = thisnode.get_depth() while nextnode is None and depth > 0: # keep going up the tree until we find an ancestor next sibling thisnode = thisnode.get_parent() nextnode = thisnode.next_sibling() depth -= 1 assert depth == thisnode.get_depth() if nextnode is None: # we're at the end of the tree return None else: return nextnode.get_widget() def prev_inorder(self): """Return the previous TreeWidget depth first from this one.""" thisnode = self._node prevnode = thisnode.prev_sibling() if prevnode is not None: # we need to find the last child of the previous widget if its # expanded prevwidget = prevnode.get_widget() lastchild = prevwidget.last_child() if lastchild is None: return prevwidget else: return lastchild else: # need to hunt for the parent depth = thisnode.get_depth() if prevnode is None and depth == 0: return None elif prevnode is None: prevnode = thisnode.get_parent() return prevnode.get_widget() def keypress(self, size, key): """Handle expand & collapse requests (non-leaf nodes)""" if self.is_leaf: return key if key in ("+", "right"): self.expanded = True self.update_expanded_icon() elif key == "-": self.expanded = False self.update_expanded_icon() elif self._w.selectable(): return self.__super.keypress(size, key) else: return key def mouse_event(self, size, event, button, col, row, focus): if self.is_leaf or event != 'mouse press' or button!=1: return False if row == 0 and col == self.get_indent_cols(): self.expanded = not self.expanded self.update_expanded_icon() return True return False def first_child(self): """Return first child if expanded.""" if self.is_leaf or not self.expanded: return None else: if self._node.has_children(): firstnode = self._node.get_first_child() return firstnode.get_widget() else: return None def last_child(self): """Return last child if expanded.""" if self.is_leaf or not self.expanded: return None else: if self._node.has_children(): lastchild = self._node.get_last_child().get_widget() else: return None # recursively search down for the last descendant lastdescendant = lastchild.last_child() if lastdescendant is None: return lastchild else: return lastdescendant class TreeNode(object): """ Store tree contents and cache TreeWidget objects. A TreeNode consists of the following elements: * key: accessor token for parent nodes * value: subclass-specific data * parent: a TreeNode which contains a pointer back to this object * widget: The widget used to render the object """ def __init__(self, value, parent=None, key=None, depth=None): self._key = key self._parent = parent self._value = value self._depth = depth self._widget = None def get_widget(self, reload=False): """ Return the widget for this node.""" if self._widget is None or reload == True: self._widget = self.load_widget() return self._widget def load_widget(self): return TreeWidget(self) def get_depth(self): if self._depth is None and self._parent is None: self._depth = 0 elif self._depth is None: self._depth = self._parent.get_depth() + 1 return self._depth def get_index(self): if self.get_depth() == 0: return None else: key = self.get_key() parent = self.get_parent() return parent.get_child_index(key) def get_key(self): return self._key def set_key(self, key): self._key = key def change_key(self, key): self.get_parent().change_child_key(self._key, key) def get_parent(self): if self._parent == None and self.get_depth() > 0: self._parent = self.load_parent() return self._parent def load_parent(self): """Provide TreeNode with a parent for the current node. This function is only required if the tree was instantiated from a child node (virtual function)""" raise TreeWidgetError("virtual function. Implement in subclass") def get_value(self): return self._value def is_root(self): return self.get_depth() == 0 def next_sibling(self): if self.get_depth() > 0: return self.get_parent().next_child(self.get_key()) else: return None def prev_sibling(self): if self.get_depth() > 0: return self.get_parent().prev_child(self.get_key()) else: return None def get_root(self): root = self while root.get_parent() is not None: root = root.get_parent() return root class ParentNode(TreeNode): """Maintain sort order for TreeNodes.""" def __init__(self, value, parent=None, key=None, depth=None): TreeNode.__init__(self, value, parent=parent, key=key, depth=depth) self._child_keys = None self._children = {} def get_child_keys(self, reload=False): """Return a possibly ordered list of child keys""" if self._child_keys is None or reload == True: self._child_keys = self.load_child_keys() return self._child_keys def load_child_keys(self): """Provide ParentNode with an ordered list of child keys (virtual function)""" raise TreeWidgetError("virtual function. Implement in subclass") def get_child_widget(self, key): """Return the widget for a given key. Create if necessary.""" child = self.get_child_node(key) return child.get_widget() def get_child_node(self, key, reload=False): """Return the child node for a given key. Create if necessary.""" if key not in self._children or reload == True: self._children[key] = self.load_child_node(key) return self._children[key] def load_child_node(self, key): """Load the child node for a given key (virtual function)""" raise TreeWidgetError("virtual function. Implement in subclass") 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 def change_child_key(self, oldkey, newkey): if newkey in self._children: raise TreeWidgetError("%s is already in use" % newkey) self._children[newkey] = self._children.pop(oldkey) self._children[newkey].set_key(newkey) def get_child_index(self, key): try: return self.get_child_keys().index(key) except ValueError: errorstring = ("Can't find key %s in ParentNode %s\n" + "ParentNode items: %s") raise TreeWidgetError(errorstring % (key, self.get_key(), str(self.get_child_keys()))) def next_child(self, key): """Return the next child node in index order from the given key.""" index = self.get_child_index(key) # the given node may have just been deleted if index is None: return None index += 1 child_keys = self.get_child_keys() if index < len(child_keys): # get the next item at same level return self.get_child_node(child_keys[index]) else: return None def prev_child(self, key): """Return the previous child node in index order from the given key.""" index = self.get_child_index(key) if index is None: return None child_keys = self.get_child_keys() index -= 1 if index >= 0: # get the previous item at same level return self.get_child_node(child_keys[index]) else: return None def get_first_child(self): """Return the first TreeNode in the directory.""" child_keys = self.get_child_keys() return self.get_child_node(child_keys[0]) def get_last_child(self): """Return the last TreeNode in the directory.""" child_keys = self.get_child_keys() return self.get_child_node(child_keys[-1]) def has_children(self): """Does this node have any children?""" return len(self.get_child_keys())>0 class TreeWalker(urwid.ListWalker): """ListWalker-compatible class for displaying TreeWidgets positions are TreeNodes.""" def __init__(self, start_from): """start_from: TreeNode with the initial focus.""" self.focus = start_from def get_focus(self): widget = self.focus.get_widget() return widget, self.focus def set_focus(self, focus): self.focus = focus self._modified() def get_next(self, start_from): widget = start_from.get_widget() target = widget.next_inorder() if target is None: return None, None else: return target, target.get_node() def get_prev(self, start_from): widget = start_from.get_widget() target = widget.prev_inorder() if target is None: return None, None else: return target, target.get_node() class TreeListBox(urwid.ListBox): """A ListBox with special handling for navigation and collapsing of TreeWidgets""" def keypress(self, size, key): key = self.__super.keypress(size, key) return self.unhandled_input(size, key) def unhandled_input(self, size, input): """Handle macro-navigation keys""" if input == 'left': self.move_focus_to_parent(size) elif input == '-': self.collapse_focus_parent(size) elif input == 'home': self.focus_home(size) elif input == 'end': self.focus_end(size) else: return input def collapse_focus_parent(self, size): """Collapse parent directory.""" widget, pos = self.body.get_focus() self.move_focus_to_parent(size) pwidget, ppos = self.body.get_focus() if pos != ppos: self.keypress(size, "-") def move_focus_to_parent(self, size): """Move focus to parent of widget in focus.""" widget, pos = self.body.get_focus() parentpos = pos.get_parent() if parentpos is None: return middle, top, bottom = self.calculate_visible( size ) row_offset, focus_widget, focus_pos, focus_rows, cursor = middle trim_top, fill_above = top for widget, pos, rows in fill_above: row_offset -= rows if pos == parentpos: self.change_focus(size, pos, row_offset) return self.change_focus(size, pos.get_parent()) def focus_home(self, size): """Move focus to very top.""" widget, pos = self.body.get_focus() rootnode = pos.get_root() self.change_focus(size, rootnode) def focus_end( self, size ): """Move focus to far bottom.""" maxrow, maxcol = size widget, pos = self.body.get_focus() rootnode = pos.get_root() rootwidget = rootnode.get_widget() lastwidget = rootwidget.last_child() lastnode = lastwidget.get_node() self.change_focus(size, lastnode, maxrow-1) urwid-1.1.1/urwid/listbox.py0000664000175000017500000016426012051303575015410 0ustar ianian00000000000000#!/usr/bin/python # # Urwid listbox class # Copyright (C) 2004-2012 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: http://excess.org/urwid/ from urwid.util import is_mouse_press from urwid.canvas import SolidCanvas, CanvasCombine from urwid.widget import Widget, nocache_widget_render_instance, BOX, GIVEN from urwid.decoration import calculate_top_bottom_filler, normalize_valign from urwid import signals 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) class ListWalkerError(Exception): pass class ListWalker(object): __metaclass__ = signals.MetaSignals signals = ["modified"] def _modified(self): signals.emit_signal(self, "modified") def get_focus(self): """ This default implementation relies on a focus attribute and a __getitem__() method defined in a subclass. Override and don't call this method if these are not defined. """ try: focus = self.focus return self[focus], focus except (IndexError, KeyError, TypeError): return None, None def get_next(self, position): """ This default implementation relies on a next_position() method and a __getitem__() method defined in a subclass. Override and don't call this method if these are not defined. """ try: position = self.next_position(position) return self[position], position except (IndexError, KeyError): return None, None def get_prev(self, position): """ This default implementation relies on a prev_position() method and a __getitem__() method defined in a subclass. Override and don't call this method if these are not defined. """ try: position = self.prev_position(position) return self[position], position except (IndexError, KeyError): return None, None class PollingListWalker(object): # NOT ListWalker subclass def __init__(self, contents): """ contents -- list to poll for changes This class is deprecated. Use SimpleFocusListWalker instead. """ import warnings warnings.warn("PollingListWalker is deprecated, " "use SimpleFocusListWalker instead.", DeprecationWarning) self.contents = contents if not getattr(contents, '__getitem__', None): raise ListWalkerError("PollingListWalker expecting list like " "object, got: %r" % (contents,)) self.focus = 0 def _clamp_focus(self): if self.focus >= len(self.contents): self.focus = len(self.contents)-1 def get_focus(self): """Return (focus widget, focus position).""" if len(self.contents) == 0: return None, None self._clamp_focus() return self.contents[self.focus], self.focus def set_focus(self, position): """Set focus position.""" # this class is deprecated, otherwise I might have fixed this: assert type(position) == int self.focus = position def get_next(self, start_from): """ Return (widget after start_from, position after start_from). """ pos = start_from + 1 if len(self.contents) <= pos: return None, None return self.contents[pos],pos def get_prev(self, start_from): """ Return (widget before start_from, position before start_from). """ pos = start_from - 1 if pos < 0: return None, None return self.contents[pos],pos class SimpleListWalker(MonitoredList, ListWalker): def __init__(self, contents): """ contents -- list to copy into this object Changes made to this object (when it is treated as a list) are detected automatically and will cause ListBox objects using this list walker to be updated. """ if not getattr(contents, '__getitem__', None): raise ListWalkerError, "SimpleListWalker expecting list like object, got: %r"%(contents,) MonitoredList.__init__(self, contents) self.focus = 0 def _get_contents(self): """ Return self. Provides compatibility with old SimpleListWalker class. """ return self contents = property(_get_contents) def _modified(self): if self.focus >= len(self): self.focus = max(0, len(self)-1) ListWalker._modified(self) def set_modified_callback(self, callback): """ This function inherited from MonitoredList is not implemented in SimpleListWalker. Use connect_signal(list_walker, "modified", ...) instead. """ raise NotImplementedError('Use connect_signal(' 'list_walker, "modified", ...) instead.') def set_focus(self, position): """Set focus position.""" try: if position < 0 or position >= len(self): raise ValueError except (TypeError, ValueError): raise IndexError, "No widget at position %s" % (position,) self.focus = position self._modified() def next_position(self, position): """ Return position after start_from. """ if len(self) - 1 <= position: raise IndexError return position + 1 def prev_position(self, position): """ Return position before start_from. """ if position <= 0: raise IndexError return position - 1 def positions(self, reverse=False): """ Optional method for returning an iterable of positions. """ if reverse: return xrange(len(self) - 1, -1, -1) return xrange(len(self)) class SimpleFocusListWalker(ListWalker, MonitoredFocusList): def __init__(self, contents): """ contents -- list to copy into this object Changes made to this object (when it is treated as a list) are detected automatically and will cause ListBox objects using this list walker to be updated. Also, items added or removed before the widget in focus with normal list methods will cause the focus to be updated intelligently. """ if not getattr(contents, '__getitem__', None): raise ListWalkerError("SimpleFocusListWalker expecting list like " "object, got: %r"%(contents,)) MonitoredFocusList.__init__(self, contents) def set_modified_callback(self, callback): """ This function inherited from MonitoredList is not implemented in SimpleFocusListWalker. Use connect_signal(list_walker, "modified", ...) instead. """ raise NotImplementedError('Use connect_signal(' 'list_walker, "modified", ...) instead.') def set_focus(self, position): """Set focus position.""" self.focus = position def next_position(self, position): """ Return position after start_from. """ if len(self) - 1 <= position: raise IndexError return position + 1 def prev_position(self, position): """ Return position before start_from. """ if position <= 0: raise IndexError return position - 1 def positions(self, reverse=False): """ Optional method for returning an iterable of positions. """ if reverse: return xrange(len(self) - 1, -1, -1) return xrange(len(self)) class ListBoxError(Exception): pass class ListBox(Widget, WidgetContainerMixin): """ a horizontally stacked list of widgets """ _selectable = True _sizing = frozenset([BOX]) def __init__(self, body): """ :param body: a ListWalker subclass such as :class:`SimpleFocusListWalker` that contains 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) try: 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 # content has changed self.render = nocache_widget_render_instance(self) # offset_rows is the number of rows between the top of the view # and the top of the focused item self.offset_rows = 0 # inset_fraction is used when the focused widget is off the # top of the view. it is the fraction of the widget cut off # at the top. (numerator, denominator) self.inset_fraction = (0,1) # pref_col is the preferred column for the cursor when moving # between widgets that use the cursor (edit boxes etc.) self.pref_col = 'left' # variable for delayed focus change used by set_focus self.set_focus_pending = 'first selectable' # variable for delayed valign change used by set_focus_valign self.set_focus_valign_pending = None def calculate_visible(self, size, focus=False ): """ Returns the widgets that would be displayed in the ListBox given the current *size* and *focus*. see :meth:`Widget.render` for parameter details :returns: (*middle*, *top*, *bottom*) or (``None``, ``None``, ``None``) *middle* (*row offset*(when +ve) or *inset*(when -ve), *focus widget*, *focus position*, *focus rows*, *cursor coords* or ``None``) *top* (*# lines to trim off top*, list of (*widget*, *position*, *rows*) tuples above focus in order from bottom to top) *bottom* (*# lines to trim off bottom*, list of (*widget*, *position*, *rows*) tuples below focus in order from top to bottom) """ (maxcol, maxrow) = size # 0. set the focus if a change is pending if self.set_focus_pending or self.set_focus_valign_pending: self._set_focus_complete( (maxcol, maxrow), focus ) # 1. start with the focus widget 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 offset_rows, inset_rows = self.get_focus_offset_inset( (maxcol,maxrow)) # force at least one line of focus to be visible if maxrow and offset_rows >= maxrow: offset_rows = maxrow -1 # adjust position so cursor remains visible cursor = None if maxrow and focus_widget.selectable() and focus: if hasattr(focus_widget,'get_cursor_coords'): cursor=focus_widget.get_cursor_coords((maxcol,)) if cursor is not None: cx, cy = cursor effective_cy = cy + offset_rows - inset_rows if effective_cy < 0: # cursor above top? inset_rows = cy elif effective_cy >= maxrow: # cursor below bottom? offset_rows = maxrow - cy -1 if offset_rows < 0: # need to trim the top inset_rows, offset_rows = -offset_rows, 0 # set trim_top by focus trimmimg trim_top = inset_rows focus_rows = focus_widget.rows((maxcol,),True) # 2. collect the widgets above the focus pos = focus_pos fill_lines = offset_rows fill_above = [] top_pos = pos while fill_lines > 0: prev, pos = self.body.get_prev( pos ) if prev is None: # run out of widgets above? offset_rows -= fill_lines break top_pos = pos p_rows = prev.rows( (maxcol,) ) if p_rows: # filter out 0-height widgets fill_above.append( (prev, pos, p_rows) ) if p_rows > fill_lines: # crosses top edge? trim_top = p_rows-fill_lines break fill_lines -= p_rows trim_bottom = focus_rows + offset_rows - inset_rows - maxrow if trim_bottom < 0: trim_bottom = 0 # 3. collect the widgets below the focus pos = focus_pos fill_lines = maxrow - focus_rows - offset_rows + inset_rows fill_below = [] while fill_lines > 0: next, pos = self.body.get_next( pos ) if next is None: # run out of widgets below? break n_rows = next.rows( (maxcol,) ) if n_rows: # filter out 0-height widgets fill_below.append( (next, pos, n_rows) ) if n_rows > fill_lines: # crosses bottom edge? trim_bottom = n_rows-fill_lines fill_lines -= n_rows break fill_lines -= n_rows # 4. fill from top again if necessary & possible fill_lines = max(0, fill_lines) if fill_lines >0 and trim_top >0: if fill_lines <= trim_top: trim_top -= fill_lines offset_rows += fill_lines fill_lines = 0 else: fill_lines -= trim_top offset_rows += trim_top trim_top = 0 pos = top_pos while fill_lines > 0: prev, pos = self.body.get_prev( pos ) if prev is None: break p_rows = prev.rows( (maxcol,) ) fill_above.append( (prev, pos, p_rows) ) if p_rows > fill_lines: # more than required trim_top = p_rows-fill_lines offset_rows += fill_lines break fill_lines -= p_rows offset_rows += p_rows # 5. return the interesting bits return ((offset_rows - inset_rows, focus_widget, focus_pos, focus_rows, cursor ), (trim_top, fill_above), (trim_bottom, fill_below)) def render(self, size, focus=False ): """ Render ListBox and return canvas. see :meth:`Widget.render` for details """ (maxcol, maxrow) = size middle, top, bottom = self.calculate_visible( (maxcol, maxrow), focus=focus) if middle is None: return SolidCanvas(" ", maxcol, maxrow) _ignore, focus_widget, focus_pos, focus_rows, cursor = middle trim_top, fill_above = top trim_bottom, fill_below = bottom combinelist = [] rows = 0 fill_above.reverse() # fill_above is in bottom-up order 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()) 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()) 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) rows += focus_rows combinelist.append((focus_canvas, focus_pos, True)) 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()) rows += w_rows combinelist.append((canvas, w_pos, False)) final_canvas = CanvasCombine(combinelist) if trim_top: final_canvas.trim(trim_top) rows -= trim_top if trim_bottom: final_canvas.trim_end(trim_bottom) rows -= trim_bottom if rows > maxrow: 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),) final_canvas.pad_trim_top_bottom(0, maxrow - rows) return final_canvas def get_cursor_coords(self, size): """ See :meth:`Widget.get_cursor_coords` for details """ (maxcol, maxrow) = size middle, top, bottom = self.calculate_visible( (maxcol, maxrow), True) if middle is None: return None offset_inset, _ignore1, _ignore2, _ignore3, cursor = middle if not cursor: return None x, y = cursor y += offset_inset if y < 0 or y >= maxrow: return None return (x, y) def set_focus_valign(self, valign): """Set the focus widget's display offset and inset. :param valign: one of: 'top', 'middle', 'bottom' ('fixed top', rows) ('fixed bottom', rows) ('relative', percentage 0=top 100=bottom) """ vt, va = normalize_valign(valign,ListBoxError) self.set_focus_valign_pending = vt, va def set_focus(self, position, coming_from=None): """ 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 coming_from: set to 'above' or 'below' if you know that old position is above or below the new position. :type coming_from: str """ 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() 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) def get_focus(self): """ Return a `(focus widget, focus position)` tuple, for backwards 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() def _get_focus(self): """ Return the widget in focus according to our :obj:`list walker `. """ return self.body.get_focus()[0] focus = property(_get_focus, doc="the child widget in focus or None when ListBox is empty") def _get_focus_position(self): """ Return the list walker position of the widget in focus. The type of value returned depends on the :obj:`list walker `. """ w, pos = self.body.get_focus() if w is None: 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 position depend on the list walker in use. :exc:`IndexError` will be raised by reading this property when the ListBox is empty or setting this property to an invalid position. """) def _contents(self): class ListBoxContents(object): __getitem__ = self._contents__getitem__ return ListBoxContents() def _contents__getitem__(self, key): # try list walker protocol v2 first 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() try: try: 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) 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 value for options. .. warning:: This object may not be used to set or iterate over contents. You must use the list walker stored as :attr:`.body` to perform manipulation and iteration, if supported. """) def options(self): """ There are currently no options for ListBox contents. Return None as a placeholder for future options. """ return None def _set_focus_valign_complete(self, size, focus): """ Finish setting the offset and inset now that we have have a maxcol & maxrow. """ (maxcol, maxrow) = size vt,va = self.set_focus_valign_pending self.set_focus_valign_pending = None self.set_focus_pending = None focus_widget, focus_pos = self.body.get_focus() if focus_widget is None: return rows = focus_widget.rows((maxcol,), focus) rtop, rbot = calculate_top_bottom_filler(maxrow, vt, va, GIVEN, rows, None, 0, 0) self.shift_focus((maxcol, maxrow), rtop) def _set_focus_first_selectable(self, size, focus): """ Choose the first visible, selectable widget below the current focus as the focus widget. """ (maxcol, maxrow) = size self.set_focus_valign_pending = None self.set_focus_pending = None middle, top, bottom = self.calculate_visible( (maxcol, maxrow), focus=focus) if middle is None: return row_offset, focus_widget, focus_pos, focus_rows, cursor = middle trim_top, fill_above = top trim_bottom, fill_below = bottom if focus_widget.selectable(): return if trim_bottom: fill_below = fill_below[:-1] new_row_offset = row_offset + focus_rows for widget, pos, rows in fill_below: if widget.selectable(): self.body.set_focus(pos) self.shift_focus((maxcol, maxrow), new_row_offset) return new_row_offset += rows def _set_focus_complete(self, size, focus): """ Finish setting the position now that we have maxcol & maxrow. """ (maxcol, maxrow) = size self._invalidate() if self.set_focus_pending == "first selectable": return self._set_focus_first_selectable( (maxcol,maxrow), focus) if self.set_focus_valign_pending is not None: return self._set_focus_valign_complete( (maxcol,maxrow), focus) coming_from, focus_widget, focus_pos = self.set_focus_pending self.set_focus_pending = None # new position 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) middle,top,bottom=self.calculate_visible((maxcol,maxrow),focus) focus_offset, focus_widget, focus_pos, focus_rows, cursor=middle trim_top, fill_above = top trim_bottom, fill_below = bottom offset = focus_offset for widget, pos, rows in fill_above: offset -= rows if pos == position: self.change_focus((maxcol, maxrow), pos, offset, 'below' ) return offset = focus_offset + focus_rows for widget, pos, rows in fill_below: if pos == position: self.change_focus((maxcol, maxrow), pos, offset, 'above' ) return offset += rows # failed to find widget among visible widgets self.body.set_focus( position ) widget, position = self.body.get_focus() rows = widget.rows((maxcol,), focus) if coming_from=='below': offset = 0 elif coming_from=='above': offset = maxrow-rows else: offset = (maxrow-rows) // 2 self.shift_focus((maxcol, maxrow), offset) def shift_focus(self, size, offset_inset): """ Move the location of the current focus relative to the top. This is used internally by methods that know the widget's *size*. See also :meth:`.set_focus_valign`. :param size: see :meth:`Widget.render` for details :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 the top edge of the listbox (-ve value) or ``0`` if the top edge of the focus widget is aligned with the top edge of the listbox. :type offset_inset: int """ (maxcol, maxrow) = size if offset_inset >= 0: if 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) self.offset_rows = 0 self.inset_fraction = (-offset_inset,tgt_rows) self._invalidate() def update_pref_col_from_focus(self, size): """Update self.pref_col from the focus widget.""" # TODO: should this not be private? (maxcol, maxrow) = size widget, old_pos = self.body.get_focus() if widget is None: return pref_col = None if hasattr(widget,'get_pref_col'): pref_col = widget.get_pref_col((maxcol,)) if pref_col is None and hasattr(widget,'get_cursor_coords'): coords = widget.get_cursor_coords((maxcol,)) if type(coords) == tuple: pref_col,y = coords if pref_col is not None: self.pref_col = pref_col def change_focus(self, size, position, offset_inset = 0, coming_from = None, cursor_coords = None, snap_rows = None): """ Change the current focus widget. This is used internally by methods that know the widget's *size*. 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 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 the top edge of the listbox (-ve value) or 0 if the top edge of the focus widget is aligned with the top edge of the listbox (default if unspecified) :type offset_inset: int :param coming_from: eiter 'above', 'below' or unspecified `None` :type coming_from: str :param cursor_coords: (x, y) tuple indicating the desired column and row for the cursor, a (x,) tuple indicating only the column for the cursor, or unspecified :type cursor_coords: (int, int) :param snap_rows: the maximum number of extra rows to scroll when trying to "snap" a selectable focus into the view :type snap_rows: int """ (maxcol, maxrow) = size # update pref_col before change if cursor_coords: self.pref_col = cursor_coords[0] else: self.update_pref_col_from_focus((maxcol,maxrow)) self._invalidate() 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 # "snap" to selectable widgets align_top = 0 align_bottom = maxrow - tgt_rows if ( coming_from == 'above' and target.selectable() and offset_inset > align_bottom ): if snap_rows >= offset_inset - align_bottom: offset_inset = align_bottom elif snap_rows >= offset_inset - align_top: offset_inset = align_top else: offset_inset -= snap_rows if ( coming_from == 'below' and target.selectable() and offset_inset < align_top ): if snap_rows >= align_top - offset_inset: offset_inset = align_top elif snap_rows >= align_bottom - offset_inset: offset_inset = align_bottom else: offset_inset += snap_rows # convert offset_inset to offset_rows or inset_fraction if offset_inset >= 0: self.offset_rows = offset_inset 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) self.offset_rows = 0 self.inset_fraction = (-offset_inset,tgt_rows) if cursor_coords is None: if coming_from is None: return # must either know row or coming_from cursor_coords = (self.pref_col,) if not hasattr(target,'move_cursor_to_coords'): return attempt_rows = [] if len(cursor_coords) == 1: # only column (not row) specified # start from closest edge and move inwards (pref_col,) = cursor_coords if coming_from=='above': attempt_rows = range( 0, tgt_rows ) else: assert coming_from == 'below', "must specify coming_from ('above' or 'below') if cursor row is not specified" attempt_rows = range( tgt_rows, -1, -1) else: # both column and row specified # 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) if coming_from=='above': attempt_rows = range( pref_row, -1, -1 ) elif coming_from=='below': attempt_rows = range( pref_row, tgt_rows ) else: attempt_rows = [pref_row] for row in attempt_rows: if target.move_cursor_to_coords((maxcol,),pref_col,row): break 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_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,) inset_rows = focus_rows * inum // iden if inset_rows and inset_rows >= focus_rows: raise ListBoxError, "urwid inset_fraction error (please report)" return offset_rows, inset_rows def make_cursor_visible(self, size): """Shift the focus widget so that its cursor is visible.""" (maxcol, maxrow) = size focus_widget, pos = self.body.get_focus() if focus_widget is None: return if not focus_widget.selectable(): return if not hasattr(focus_widget,'get_cursor_coords'): return cursor = focus_widget.get_cursor_coords((maxcol,)) if cursor is None: return cx, cy = cursor offset_rows, inset_rows = self.get_focus_offset_inset( (maxcol, maxrow)) if cy < inset_rows: self.shift_focus( (maxcol,maxrow), - (cy) ) return if offset_rows - inset_rows + cy >= maxrow: self.shift_focus( (maxcol,maxrow), maxrow-cy-1 ) return 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. 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 """ (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() 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 key is None: self.make_cursor_visible((maxcol,maxrow)) return def actual_key(unhandled): if unhandled: return key # pass off the heavy lifting if self._command_map[key] == CURSOR_UP: return actual_key(self._keypress_up((maxcol, maxrow))) if self._command_map[key] == CURSOR_DOWN: return actual_key(self._keypress_down((maxcol, maxrow))) if self._command_map[key] == CURSOR_PAGE_UP: return actual_key(self._keypress_page_up((maxcol, maxrow))) if self._command_map[key] == CURSOR_PAGE_DOWN: return actual_key(self._keypress_page_down((maxcol, maxrow))) return key def _keypress_up(self, size): (maxcol, maxrow) = size middle, top, bottom = self.calculate_visible( (maxcol,maxrow), True) if middle is None: return True focus_row_offset,focus_widget,focus_pos,_ignore,cursor = middle trim_top, fill_above = top row_offset = focus_row_offset # look for selectable widget above pos = focus_pos widget = None for widget, pos, rows in fill_above: row_offset -= rows if rows and widget.selectable(): # this one will do self.change_focus((maxcol,maxrow), pos, row_offset, 'below') return # at this point we must scroll row_offset += 1 self._invalidate() while row_offset > 0: # need to scroll in another candidate widget widget, pos = self.body.get_prev(pos) if widget is None: # cannot scroll any further return True # keypress not handled rows = widget.rows((maxcol,), True) row_offset -= rows if rows and widget.selectable(): # this one will do self.change_focus((maxcol,maxrow), pos, row_offset, 'below') return if not focus_widget.selectable() or focus_row_offset+1>=maxrow: # just take top one if focus is not selectable # or if focus has moved out of view if widget is None: self.shift_focus((maxcol,maxrow), row_offset) return self.change_focus((maxcol,maxrow), pos, row_offset, 'below') return # check if cursor will stop scroll from taking effect if cursor is not None: x,y = cursor if y+focus_row_offset+1 >= maxrow: # cursor position is a problem, # choose another focus if widget is None: # try harder to get prev widget widget, pos = self.body.get_prev(pos) if widget is None: return # can't do anything rows = widget.rows((maxcol,), True) row_offset -= rows if -row_offset >= rows: # must scroll further than 1 line row_offset = - (rows-1) self.change_focus((maxcol,maxrow),pos, row_offset, 'below') return # if all else fails, just shift the current focus. self.shift_focus((maxcol,maxrow), focus_row_offset+1) def _keypress_down(self, size): (maxcol, maxrow) = size middle, top, bottom = self.calculate_visible( (maxcol,maxrow), True) if middle is None: return True focus_row_offset,focus_widget,focus_pos,focus_rows,cursor=middle trim_bottom, fill_below = bottom row_offset = focus_row_offset + focus_rows rows = focus_rows # look for selectable widget below pos = focus_pos widget = None for widget, pos, rows in fill_below: if rows and widget.selectable(): # this one will do self.change_focus((maxcol,maxrow), pos, row_offset, 'above') return row_offset += rows # at this point we must scroll row_offset -= 1 self._invalidate() while row_offset < maxrow: # need to scroll in another candidate widget widget, pos = self.body.get_next(pos) if widget is None: # cannot scroll any further return True # keypress not handled rows = widget.rows((maxcol,)) if rows and widget.selectable(): # this one will do self.change_focus((maxcol,maxrow), pos, row_offset, 'above') return row_offset += rows if not focus_widget.selectable() or focus_row_offset+focus_rows-1 <= 0: # just take bottom one if current is not selectable # or if focus has moved out of view if widget is None: self.shift_focus((maxcol,maxrow), row_offset-rows) return # FIXME: catch this bug in testcase #self.change_focus((maxcol,maxrow), pos, # row_offset+rows, 'above') self.change_focus((maxcol,maxrow), pos, row_offset-rows, 'above') return # check if cursor will stop scroll from taking effect if cursor is not None: x,y = cursor if y+focus_row_offset-1 < 0: # cursor position is a problem, # choose another focus if widget is None: # try harder to get next widget widget, pos = self.body.get_next(pos) if widget is None: return # can't do anything else: row_offset -= rows if row_offset >= maxrow: # must scroll further than 1 line row_offset = maxrow-1 self.change_focus((maxcol,maxrow),pos, row_offset, 'above', ) return # if all else fails, keep the current focus. self.shift_focus((maxcol,maxrow), focus_row_offset-1) def _keypress_page_up(self, size): (maxcol, maxrow) = size middle, top, bottom = self.calculate_visible( (maxcol,maxrow), True) if middle is None: return True row_offset, focus_widget, focus_pos, focus_rows, cursor = middle trim_top, fill_above = top # topmost_visible is row_offset rows above top row of # focus (+ve) or -row_offset rows below top row of focus (-ve) topmost_visible = row_offset # scroll_from_row is (first match) # 1. topmost visible row if focus is not selectable # 2. row containing cursor if focus has a cursor # 3. top row of focus widget if it is visible # 4. topmost visible row otherwise if not focus_widget.selectable(): scroll_from_row = topmost_visible elif cursor is not None: x,y = cursor scroll_from_row = -y elif row_offset >= 0: scroll_from_row = 0 else: scroll_from_row = topmost_visible # snap_rows is maximum extra rows to scroll when # snapping to new a focus snap_rows = topmost_visible - scroll_from_row # move row_offset to the new desired value (1 "page" up) row_offset = scroll_from_row + maxrow # not used below: scroll_from_row = topmost_visible = None # gather potential target widgets t = [] # add current focus t.append((row_offset,focus_widget,focus_pos,focus_rows)) pos = focus_pos # include widgets from calculate_visible(..) for widget, pos, rows in fill_above: row_offset -= rows t.append( (row_offset, widget, pos, rows) ) # 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) if widget is None: break rows = widget.rows((maxcol,)) row_offset -= rows # determine if one below puts current one into snap rgn if row_offset > 0: snap_region_start += 1 t.append( (row_offset, widget, pos, rows) ) # if we can't fill the top we need to adjust the row offsets row_offset, w, p, r = t[-1] if row_offset > 0: adjust = - row_offset t = [(ro+adjust, w, p, r) for (ro,w,p,r) in t] # if focus_widget (first in t) is off edge, remove it row_offset, w, p, r = t[0] if row_offset >= maxrow: del t[0] snap_region_start -= 1 # we'll need this soon self.update_pref_col_from_focus((maxcol,maxrow)) # 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 ) ) #assert 0, repr((t, search_order)) bad_choices = [] cut_off_selectable_chosen = 0 for i in search_order: row_offset, widget, pos, rows = t[i] if not widget.selectable(): continue if not rows: continue # try selecting this widget pref_row = max(0, -row_offset) # if completely within snap region, adjust row_offset if rows + row_offset <= 0: self.change_focus( (maxcol,maxrow), pos, -(rows-1), 'below', (self.pref_col, rows-1), snap_rows-((-row_offset)-(rows-1))) else: self.change_focus( (maxcol,maxrow), pos, row_offset, 'below', (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]) == (None,None) ): pass #return # find out where that actually puts us middle, top, bottom = self.calculate_visible( (maxcol,maxrow), True) act_row_offset, _ign1, _ign2, _ign3, _ign4 = middle # discard chosen widget if it will reduce scroll amount # because of a fixed cursor (absolute last resort) if act_row_offset > row_offset+snap_rows: bad_choices.append(i) continue if act_row_offset < row_offset: bad_choices.append(i) continue # also discard if off top edge (second last resort) if act_row_offset < 0: bad_choices.append(i) cut_off_selectable_chosen = 1 continue return # anything selectable is better than what follows: if cut_off_selectable_chosen: return 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): pass #return # if still none found choose the topmost widget good_choices = [j for j in search_order if j not in bad_choices] for i in good_choices + search_order: row_offset, widget, pos, rows = t[i] if pos == focus_pos: continue if not rows: # never focus a 0-height widget continue # if completely within snap region, adjust row_offset if rows + row_offset <= 0: snap_rows -= (-row_offset) - (rows-1) row_offset = -(rows-1) self.change_focus( (maxcol,maxrow), pos, row_offset, 'below', None, snap_rows ) return # no choices available, just shift current one self.shift_focus((maxcol, maxrow), min(maxrow-1,row_offset)) # final check for pathological case where we may fall short middle, top, bottom = self.calculate_visible( (maxcol,maxrow), True) act_row_offset, _ign1, pos, _ign2, _ign3 = middle if act_row_offset >= row_offset: # no problem return # fell short, try to select anything else above if not t: return _ign1, _ign2, pos, _ign3 = t[-1] widget, pos = self.body.get_prev(pos) if widget is None: # no dice, we're stuck here return # bring in only one row if possible rows = widget.rows((maxcol,), True) self.change_focus((maxcol,maxrow), pos, -(rows-1), 'below', (self.pref_col, rows-1), 0 ) def _keypress_page_down(self, size): (maxcol, maxrow) = size middle, top, bottom = self.calculate_visible( (maxcol,maxrow), True) if middle is None: return True row_offset, focus_widget, focus_pos, focus_rows, cursor = middle trim_bottom, fill_below = bottom # bottom_edge is maxrow-focus_pos rows below top row of focus bottom_edge = maxrow - row_offset # scroll_from_row is (first match) # 1. bottom edge if focus is not selectable # 2. row containing cursor + 1 if focus has a cursor # 3. bottom edge of focus widget if it is visible # 4. bottom edge otherwise if not focus_widget.selectable(): scroll_from_row = bottom_edge elif cursor is not None: x,y = cursor scroll_from_row = y + 1 elif bottom_edge >= focus_rows: scroll_from_row = focus_rows else: scroll_from_row = bottom_edge # snap_rows is maximum extra rows to scroll when # snapping to new a focus snap_rows = bottom_edge - scroll_from_row # move row_offset to the new desired value (1 "page" down) row_offset = -scroll_from_row # not used below: scroll_from_row = bottom_edge = None # gather potential target widgets t = [] # add current focus t.append((row_offset,focus_widget,focus_pos,focus_rows)) pos = focus_pos row_offset += focus_rows # include widgets from calculate_visible(..) for widget, pos, rows in fill_below: t.append( (row_offset, widget, pos, rows) ) row_offset += rows # 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) if widget is None: break rows = widget.rows((maxcol,)) t.append( (row_offset, widget, pos, rows) ) row_offset += rows # determine if one above puts current one into snap rgn if row_offset < maxrow: snap_region_start += 1 # if we can't fill the bottom we need to adjust the row offsets row_offset, w, p, rows = t[-1] if row_offset + rows < maxrow: adjust = maxrow - (row_offset + rows) t = [(ro+adjust, w, p, r) for (ro,w,p,r) in t] # if focus_widget (first in t) is off edge, remove it row_offset, w, p, rows = t[0] if row_offset+rows <= 0: del t[0] snap_region_start -= 1 # we'll need this soon self.update_pref_col_from_focus((maxcol,maxrow)) # 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 ) ) #assert 0, repr((t, search_order)) bad_choices = [] cut_off_selectable_chosen = 0 for i in search_order: row_offset, widget, pos, rows = t[i] if not widget.selectable(): continue if not rows: continue # try selecting this widget pref_row = min(maxrow-row_offset-1, rows-1) # if completely within snap region, adjust row_offset if row_offset >= maxrow: self.change_focus( (maxcol,maxrow), pos, maxrow-1, 'above', (self.pref_col, 0), snap_rows+maxrow-row_offset-1 ) else: self.change_focus( (maxcol,maxrow), pos, row_offset, 'above', (self.pref_col, pref_row), snap_rows ) # find out where that actually puts us middle, top, bottom = self.calculate_visible( (maxcol,maxrow), True) act_row_offset, _ign1, _ign2, _ign3, _ign4 = middle # discard chosen widget if it will reduce scroll amount # because of a fixed cursor (absolute last resort) if act_row_offset < row_offset-snap_rows: bad_choices.append(i) continue if act_row_offset > row_offset: bad_choices.append(i) continue # also discard if off top edge (second last resort) if act_row_offset+rows > maxrow: bad_choices.append(i) cut_off_selectable_chosen = 1 continue return # anything selectable is better than what follows: if cut_off_selectable_chosen: return # if still none found choose the bottommost widget good_choices = [j for j in search_order if j not in bad_choices] for i in good_choices + search_order: row_offset, widget, pos, rows = t[i] if pos == focus_pos: continue if not rows: # never focus a 0-height widget continue # if completely within snap region, adjust row_offset if row_offset >= maxrow: snap_rows -= snap_rows+maxrow-row_offset-1 row_offset = maxrow-1 self.change_focus( (maxcol,maxrow), pos, row_offset, 'above', None, snap_rows ) return # no choices available, just shift current one self.shift_focus((maxcol, maxrow), max(1-focus_rows,row_offset)) # final check for pathological case where we may fall short middle, top, bottom = self.calculate_visible( (maxcol,maxrow), True) act_row_offset, _ign1, pos, _ign2, _ign3 = middle if act_row_offset <= row_offset: # no problem return # fell short, try to select anything else below if not t: return _ign1, _ign2, pos, _ign3 = t[-1] widget, pos = self.body.get_next(pos) if widget is None: # no dice, we're stuck here return # bring in only one row if possible rows = widget.rows((maxcol,), True) self.change_focus((maxcol,maxrow), pos, maxrow-1, 'above', (self.pref_col, 0), 0 ) def mouse_event(self, size, event, button, col, row, focus): """ Pass the event to the contained widgets. May change focus on button 1 press. """ (maxcol, maxrow) = size middle, top, bottom = self.calculate_visible((maxcol, maxrow), focus=True) if middle is None: return False _ignore, focus_widget, focus_pos, focus_rows, cursor = middle trim_top, fill_above = top _ignore, fill_below = bottom fill_above.reverse() # fill_above is in bottom-up order w_list = ( fill_above + [ (focus_widget, focus_pos, focus_rows) ] + fill_below ) wrow = -trim_top for w, w_pos, w_rows in w_list: if wrow + w_rows > row: break wrow += w_rows else: return False focus = focus and w == focus_widget if is_mouse_press(event) and button==1: if w.selectable(): self.change_focus((maxcol,maxrow), w_pos, wrow) if not hasattr(w,'mouse_event'): return False return w.mouse_event((maxcol,), event, button, col, row-wrow, focus) def ends_visible(self, size, focus=False): """ Return a list that may contain ``'top'`` and/or ``'bottom'``. i.e. this function will return one of: [], [``'top'``], [``'bottom'``] or [``'top'``, ``'bottom'``]. convenience function for checking whether the top and bottom of the list are visible """ (maxcol, maxrow) = size l = [] middle,top,bottom = self.calculate_visible( (maxcol,maxrow), focus=focus ) if middle is None: # empty listbox return ['top','bottom'] trim_top, above = top trim_bottom, below = bottom if trim_bottom == 0: row_offset, w, pos, rows, c = middle row_offset += rows for w, pos, rows in below: row_offset += rows if row_offset < maxrow: l.append('bottom') 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): l.insert(0, 'top') return l def __iter__(self): """ Return an iterator over the positions in this ListBox. 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) if positions_fn: for pos in positions_fn(): yield pos return 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) if not w: break pos = focus_pos while True: w, pos = self.body.get_prev(pos) if not w: break yield pos def __reversed__(self): """ Return a reversed iterator over the positions in this ListBox. If :attr:`body` does not implement :meth:`positions` then iterate from above the focus widget up to the top, then from the focus widget down to the bottom. Note that this is not actually the 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) if positions_fn: for pos in positions_fn(reverse=True): yield pos return 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) if not w: break yield pos pos = focus_pos while True: yield pos w, pos = self.body.get_next(pos) if not w: break urwid-1.1.1/urwid/monitored_list.py0000775000175000017500000004170412051303575016757 0ustar ianian00000000000000#!/usr/bin/python # # Urwid MonitoredList class # 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/ from urwid.compat import PYTHON3 def _call_modified(fn): def call_modified_wrapper(self, *args, **kwargs): rval = fn(self, *args, **kwargs) self._modified() return rval return call_modified_wrapper class MonitoredList(list): """ This class can trigger a callback any time its contents are changed with the usual list operations append, extend, etc. """ def _modified(self): pass def set_modified_callback(self, callback): """ Assign a callback function with no parameters that is called any time the list is modified. Callback's return value is ignored. >>> import sys >>> ml = MonitoredList([1,2,3]) >>> ml.set_modified_callback(lambda: sys.stdout.write("modified\\n")) >>> ml MonitoredList([1, 2, 3]) >>> ml.append(10) modified >>> len(ml) 4 >>> ml += [11, 12, 13] modified >>> ml[:] = ml[:2] + ml[-2:] modified >>> ml MonitoredList([1, 2, 12, 13]) """ self._modified = callback def __repr__(self): return "%s(%r)" % (self.__class__.__name__, list(self)) __add__ = _call_modified(list.__add__) __delitem__ = _call_modified(list.__delitem__) if not PYTHON3: __delslice__ = _call_modified(list.__delslice__) __iadd__ = _call_modified(list.__iadd__) __imul__ = _call_modified(list.__imul__) __rmul__ = _call_modified(list.__rmul__) __setitem__ = _call_modified(list.__setitem__) if not PYTHON3: __setslice__ = _call_modified(list.__setslice__) append = _call_modified(list.append) extend = _call_modified(list.extend) insert = _call_modified(list.insert) pop = _call_modified(list.pop) remove = _call_modified(list.remove) reverse = _call_modified(list.reverse) sort = _call_modified(list.sort) class MonitoredFocusList(MonitoredList): """ This class can trigger a callback any time its contents are modified, before and/or after modification, and any time the focus index is changed. """ def __init__(self, *argl, **argd): """ This is a list that tracks one item as the focus item. If items are inserted or removed it will update the focus. >>> ml = MonitoredFocusList([10, 11, 12, 13, 14], focus=3) >>> ml MonitoredFocusList([10, 11, 12, 13, 14], focus=3) >>> del(ml[1]) >>> ml MonitoredFocusList([10, 12, 13, 14], focus=2) >>> ml[:2] = [50, 51, 52, 53] >>> ml MonitoredFocusList([50, 51, 52, 53, 13, 14], focus=4) >>> ml[4] = 99 >>> ml MonitoredFocusList([50, 51, 52, 53, 99, 14], focus=4) >>> ml[:] = [] >>> ml MonitoredFocusList([], focus=None) """ focus = argd.pop('focus', 0) super(MonitoredFocusList, self).__init__(*argl, **argd) self._focus = focus self._focus_modified = lambda ml, indices, new_items: None def __repr__(self): return "%s(%r, focus=%r)" % ( self.__class__.__name__, list(self), self.focus) def _get_focus(self): """ Return the index of the item "in focus" or None if the list is empty. >>> MonitoredFocusList([1,2,3], focus=2)._get_focus() 2 >>> MonitoredFocusList()._get_focus() """ if not self: return None return self._focus def _set_focus(self, index): """ index -- index into this list, any index out of range will raise an IndexError, except when the list is empty and the index passed is ignored. This function may call self._focus_changed when the focus is actually modified. That method may be overridden on the instance with set_focus_changed_callback(). >>> ml = MonitoredFocusList([9, 10, 11]) >>> ml._set_focus(2); ml._get_focus() 2 >>> ml._set_focus(0); ml._get_focus() 0 >>> ml._set_focus(-2) Traceback (most recent call last): ... IndexError: focus index is out of range: -2 """ if not self: self._focus = 0 return if index < 0 or index >= len(self): raise IndexError, 'focus index is out of range: %s' % (index,) if index != int(index): raise IndexError, 'invalid focus index: %s' % (index,) index = int(index) if index != self._focus: self._focus_changed(index) self._focus = index focus = property(_get_focus, _set_focus, doc=""" Get/set the focus index. This value is read as None when the list is empty, and may only be set to a value between 0 and len(self)-1 or an IndexError will be raised. """) def _focus_changed(self, new_focus): pass def set_focus_changed_callback(self, callback): """ Assign a callback to be called when the focus index changes for any reason. The callback is in the form: callback(new_focus) new_focus -- new focus index >>> import sys >>> ml = MonitoredFocusList([1,2,3], focus=1) >>> ml.set_focus_changed_callback(lambda f: sys.stdout.write("focus: %d\\n" % (f,))) >>> ml MonitoredFocusList([1, 2, 3], focus=1) >>> ml.append(10) >>> ml.insert(1, 11) focus: 2 >>> ml MonitoredFocusList([1, 11, 2, 3, 10], focus=2) >>> del ml[:2] focus: 0 >>> ml[:0] = [12, 13, 14] focus: 3 >>> ml.focus = 5 focus: 5 >>> ml MonitoredFocusList([12, 13, 14, 2, 3, 10], focus=5) """ self._focus_changed = callback def _validate_contents_modified(self, indices, new_items): return None def set_validate_contents_modified(self, callback): """ Assign a callback function to handle validating changes to the list. This may raise an exception if the change should not be performed. It may also return an integer position to be the new focus after the list is modified, or None to use the default behaviour. The callback is in the form: callback(indices, new_items) indices -- a (start, stop, step) tuple whose range covers the items being modified new_items -- an iterable of items replacing those at range(*indices), empty if items are being removed, if step==1 this list may contain any number of items """ self._validate_contents_modified = callback def _adjust_focus_on_contents_modified(self, slc, new_items=()): """ Default behaviour is to move the focus to the item following any removed items, unless that item was simply replaced. Failing that choose the last item in the list. returns focus position for after change is applied """ num_new_items = len(new_items) start, stop, step = indices = slc.indices(len(self)) num_removed = len(range(*indices)) focus = self._validate_contents_modified(indices, new_items) if focus is not None: return focus focus = self._focus if step == 1: if start + num_new_items <= focus < stop: focus = stop # adjust for added/removed items if stop <= focus: focus += num_new_items - (stop - start) else: if not num_new_items: # extended slice being removed if focus in range(start, stop, step): focus += 1 # adjust for removed items focus -= len(range(start, min(focus, stop), step)) return min(focus, len(self) + num_new_items - num_removed -1) # override all the list methods that modify the list def __delitem__(self, y): """ >>> ml = MonitoredFocusList([0,1,2,3], focus=2) >>> del ml[3]; ml MonitoredFocusList([0, 1, 2], focus=2) >>> del ml[0]; ml MonitoredFocusList([1, 2], focus=1) >>> del ml[1]; ml MonitoredFocusList([1], focus=0) >>> del ml[0]; ml MonitoredFocusList([], focus=None) >>> ml = MonitoredFocusList([5,4,6,4,5,4,6,4,5], focus=4) >>> del ml[1::2]; ml MonitoredFocusList([5, 6, 5, 6, 5], focus=2) >>> del ml[::2]; ml MonitoredFocusList([6, 6], focus=1) """ if isinstance(y, slice): focus = self._adjust_focus_on_contents_modified(y) else: focus = self._adjust_focus_on_contents_modified(slice(y, y+1)) rval = super(MonitoredFocusList, self).__delitem__(y) self._set_focus(focus) return rval def __setitem__(self, i, y): """ >>> def modified(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 range(0, 1, 1) <- [9] >>> ml[2] = 6 range(2, 3, 1) <- [6] >>> ml.focus 2 >>> ml[-1] = 8 range(3, 4, 1) <- [8] >>> ml MonitoredFocusList([9, 1, 6, 8], focus=2) >>> ml[1::2] = [12, 13] range(1, 4, 2) <- [12, 13] >>> ml[::2] = [10, 11] range(0, 4, 2) <- [10, 11] """ if isinstance(i, slice): focus = self._adjust_focus_on_contents_modified(i, y) else: focus = self._adjust_focus_on_contents_modified(slice(i, i+1 or None), [y]) rval = super(MonitoredFocusList, self).__setitem__(i, y) self._set_focus(focus) return rval if not PYTHON3: def __delslice__(self, i, j): """ >>> def modified(indices, new_items): ... print "range%r <- %r" % (indices, list(new_items)) >>> ml = MonitoredFocusList([0,1,2,3,4], focus=2) >>> ml.set_validate_contents_modified(modified) >>> del ml[3:5] range(3, 5, 1) <- [] >>> ml MonitoredFocusList([0, 1, 2], focus=2) >>> del ml[:1] range(0, 1, 1) <- [] >>> ml MonitoredFocusList([1, 2], focus=1) >>> del ml[1:]; ml range(1, 2, 1) <- [] MonitoredFocusList([1], focus=0) >>> del ml[:]; ml range(0, 1, 1) <- [] MonitoredFocusList([], focus=None) """ focus = self._adjust_focus_on_contents_modified(slice(i, j)) rval = super(MonitoredFocusList, self).__delslice__(i, j) self._set_focus(focus) return rval def __setslice__(self, i, j, y): """ >>> ml = MonitoredFocusList([0,1,2,3,4], focus=2) >>> ml[3:5] = [-1]; ml MonitoredFocusList([0, 1, 2, -1], focus=2) >>> ml[0:1] = []; ml MonitoredFocusList([1, 2, -1], focus=1) >>> ml[1:] = [3, 4]; ml MonitoredFocusList([1, 3, 4], focus=1) >>> ml[1:] = [2]; ml MonitoredFocusList([1, 2], focus=1) >>> ml[0:1] = [9,9,9]; ml MonitoredFocusList([9, 9, 9, 2], focus=3) >>> ml[:] = []; ml MonitoredFocusList([], focus=None) """ focus = self._adjust_focus_on_contents_modified(slice(i, j), y) rval = super(MonitoredFocusList, self).__setslice__(i, j, y) self._set_focus(focus) return rval def __imul__(self, n): """ >>> def modified(indices, 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 range(3, 3, 1) <- [0, 1, 2, 0, 1, 2] >>> ml MonitoredFocusList([0, 1, 2, 0, 1, 2, 0, 1, 2], focus=2) >>> ml *= 0 range(0, 9, 1) <- [] >>> print ml.focus None """ if n > 0: focus = self._adjust_focus_on_contents_modified( slice(len(self), len(self)), list(self)*(n-1)) else: # all contents are being removed focus = self._adjust_focus_on_contents_modified(slice(0, len(self))) rval = super(MonitoredFocusList, self).__imul__(n) self._set_focus(focus) return rval def append(self, item): """ >>> def modified(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) range(3, 3, 1) <- [6] """ focus = self._adjust_focus_on_contents_modified( slice(len(self), len(self)), [item]) rval = super(MonitoredFocusList, self).append(item) self._set_focus(focus) return rval def extend(self, items): """ >>> def modified(indices, 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)) range(3, 3, 1) <- [6, 7, 8] """ focus = self._adjust_focus_on_contents_modified( slice(len(self), len(self)), items) rval = super(MonitoredFocusList, self).extend(items) self._set_focus(focus) return rval def insert(self, index, item): """ >>> ml = MonitoredFocusList([0,1,2,3], focus=2) >>> ml.insert(-1, -1); ml MonitoredFocusList([0, 1, 2, -1, 3], focus=2) >>> ml.insert(0, -2); ml MonitoredFocusList([-2, 0, 1, 2, -1, 3], focus=3) >>> ml.insert(3, -3); ml MonitoredFocusList([-2, 0, 1, -3, 2, -1, 3], focus=4) """ focus = self._adjust_focus_on_contents_modified(slice(index, index), [item]) rval = super(MonitoredFocusList, self).insert(index, item) self._set_focus(focus) return rval def pop(self, index=-1): """ >>> ml = MonitoredFocusList([-2,0,1,-3,2,3], focus=4) >>> ml.pop(3); ml -3 MonitoredFocusList([-2, 0, 1, 2, 3], focus=3) >>> ml.pop(0); ml -2 MonitoredFocusList([0, 1, 2, 3], focus=2) >>> ml.pop(-1); ml 3 MonitoredFocusList([0, 1, 2], focus=2) >>> ml.pop(2); ml 2 MonitoredFocusList([0, 1], focus=1) """ focus = self._adjust_focus_on_contents_modified(slice(index, index+1 or None)) rval = super(MonitoredFocusList, self).pop(index) self._set_focus(focus) return rval def remove(self, value): """ >>> ml = MonitoredFocusList([-2,0,1,-3,2,-1,3], focus=4) >>> ml.remove(-3); ml MonitoredFocusList([-2, 0, 1, 2, -1, 3], focus=3) >>> ml.remove(-2); ml MonitoredFocusList([0, 1, 2, -1, 3], focus=2) >>> ml.remove(3); ml MonitoredFocusList([0, 1, 2, -1], focus=2) """ index = self.index(value) focus = self._adjust_focus_on_contents_modified(slice(index, index+1 or None)) rval = super(MonitoredFocusList, self).remove(value) self._set_focus(focus) return rval def reverse(self): """ >>> ml = MonitoredFocusList([0,1,2,3,4], focus=1) >>> ml.reverse(); ml MonitoredFocusList([4, 3, 2, 1, 0], focus=3) """ rval = super(MonitoredFocusList, self).reverse() self._set_focus(max(0, len(self) - self._focus - 1)) return rval def sort(self): """ >>> ml = MonitoredFocusList([-2,0,1,-3,2,-1,3], focus=4) >>> ml.sort(); ml MonitoredFocusList([-3, -2, -1, 0, 1, 2, 3], focus=5) """ if not self: return value = self[self._focus] rval = super(MonitoredFocusList, self).sort() self._set_focus(self.index(value)) return rval def _test(): import doctest doctest.testmod() if __name__=='__main__': _test() urwid-1.1.1/urwid/old_str_util.py0000775000175000017500000002343512051303575016430 0ustar ianian00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Urwid unicode character processing tables # 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/ import re from urwid.compat import bytes, B, ord2 SAFE_ASCII_RE = re.compile(u"^[ -~]*$") SAFE_ASCII_BYTES_RE = re.compile(B("^[ -~]*$")) _byte_encoding = None # GENERATED DATA # generated from # http://www.unicode.org/Public/4.0-Update/EastAsianWidth-4.0.0.txt widths = [ (126, 1), (159, 0), (687, 1), (710, 0), (711, 1), (727, 0), (733, 1), (879, 0), (1154, 1), (1161, 0), (4347, 1), (4447, 2), (7467, 1), (7521, 0), (8369, 1), (8426, 0), (9000, 1), (9002, 2), (11021, 1), (12350, 2), (12351, 1), (12438, 2), (12442, 0), (19893, 2), (19967, 1), (55203, 2), (63743, 1), (64106, 2), (65039, 1), (65059, 0), (65131, 2), (65279, 1), (65376, 2), (65500, 1), (65510, 2), (120831, 1), (262141, 2), (1114109, 1), ] # ACCESSOR FUNCTIONS def get_width( o ): """Return the screen column width for unicode ordinal o.""" global widths if o == 0xe or o == 0xf: return 0 for num, wid in widths: if o <= num: return wid return 1 def decode_one( text, pos ): """ Return (ordinal at pos, next position) for UTF-8 encoded text. """ assert isinstance(text, bytes), text b1 = ord2(text[pos]) if not b1 & 0x80: return b1, pos+1 error = ord("?"), pos+1 lt = len(text) lt = lt-pos if lt < 2: return error if b1 & 0xe0 == 0xc0: b2 = ord2(text[pos+1]) if b2 & 0xc0 != 0x80: return error o = ((b1&0x1f)<<6)|(b2&0x3f) if o < 0x80: return error return o, pos+2 if lt < 3: return error if b1 & 0xf0 == 0xe0: b2 = ord2(text[pos+1]) if b2 & 0xc0 != 0x80: return error b3 = ord2(text[pos+2]) if b3 & 0xc0 != 0x80: return error o = ((b1&0x0f)<<12)|((b2&0x3f)<<6)|(b3&0x3f) if o < 0x800: return error return o, pos+3 if lt < 4: return error if b1 & 0xf8 == 0xf0: b2 = ord2(text[pos+1]) if b2 & 0xc0 != 0x80: return error b3 = ord2(text[pos+2]) if b3 & 0xc0 != 0x80: return error b4 = ord2(text[pos+2]) if b4 & 0xc0 != 0x80: return error o = ((b1&0x07)<<18)|((b2&0x3f)<<12)|((b3&0x3f)<<6)|(b4&0x3f) if o < 0x10000: return error return o, pos+4 return error def decode_one_uni(text, i): """ decode_one implementation for unicode strings """ return ord(text[i]), i+1 def decode_one_right(text, pos): """ Return (ordinal at pos, next position) for UTF-8 encoded text. pos is assumed to be on the trailing byte of a utf-8 sequence. """ assert isinstance(text, bytes), text error = ord("?"), pos-1 p = pos while p >= 0: if ord2(text[p])&0xc0 != 0x80: o, next = decode_one( text, p ) return o, p-1 p -=1 if p == p-4: return error def set_byte_encoding(enc): assert enc in ('utf8', 'narrow', 'wide') global _byte_encoding _byte_encoding = enc def get_byte_encoding(): return _byte_encoding def calc_text_pos(text, start_offs, end_offs, pref_col): """ Calculate the closest position to the screen column pref_col in text where start_offs is the offset into text assumed to be screen column 0 and end_offs is the end of the range to search. text may be unicode or a byte string in the target _byte_encoding Returns (position, actual_col). """ assert start_offs <= end_offs, repr((start_offs, end_offs)) utfs = isinstance(text, bytes) and _byte_encoding == "utf8" unis = not isinstance(text, bytes) if unis or utfs: decode = [decode_one, decode_one_uni][unis] i = start_offs sc = 0 n = 1 # number to advance by while i < end_offs: o, n = decode(text, i) w = get_width(o) if w+sc > pref_col: return i, sc i = n sc += w return i, sc assert type(text) == bytes, repr(text) # "wide" and "narrow" i = start_offs+pref_col if i >= end_offs: return end_offs, end_offs-start_offs if _byte_encoding == "wide": if within_double_byte(text, start_offs, i) == 2: i -= 1 return i, i-start_offs def calc_width(text, start_offs, end_offs): """ Return the screen column width of text between start_offs and end_offs. text may be unicode or a byte string in the target _byte_encoding Some characters are wide (take two columns) and others affect the previous character (take zero columns). Use the widths table above to calculate the screen column width of text[start_offs:end_offs] """ assert start_offs <= end_offs, repr((start_offs, end_offs)) utfs = isinstance(text, bytes) and _byte_encoding == "utf8" unis = not isinstance(text, bytes) if (unis and not SAFE_ASCII_RE.match(text) ) or (utfs and not SAFE_ASCII_BYTES_RE.match(text)): decode = [decode_one, decode_one_uni][unis] i = start_offs sc = 0 n = 1 # number to advance by while i < end_offs: o, n = decode(text, i) w = get_width(o) i = n sc += w return sc # "wide", "narrow" or all printable ASCII, just return the character count return end_offs - start_offs def is_wide_char(text, offs): """ Test if the character at offs within text is wide. text may be unicode or a byte string in the target _byte_encoding """ if isinstance(text, unicode): o = ord(text[offs]) return get_width(o) == 2 assert isinstance(text, bytes) if _byte_encoding == "utf8": o, n = decode_one(text, offs) return get_width(o) == 2 if _byte_encoding == "wide": return within_double_byte(text, offs, offs) == 1 return False 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): return end_offs-1 assert isinstance(text, bytes) if _byte_encoding == "utf8": o = end_offs-1 while ord2(text[o])&0xc0 == 0x80: o -= 1 return o if _byte_encoding == "wide" and within_double_byte(text, start_offs, end_offs-1) == 2: return end_offs-2 return end_offs-1 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): return start_offs+1 assert isinstance(text, bytes) if _byte_encoding == "utf8": o = start_offs+1 while o= 0x40 and v < 0x7f: # might be second half of big5, uhc or gbk encoding if pos == line_start: return 0 if ord2(text[pos-1]) >= 0x81: if within_double_byte(text, line_start, pos-1) == 1: return 2 return 0 if v < 0x80: return 0 i = pos -1 while i >= line_start: if ord2(text[i]) < 0x80: break i -= 1 if (pos - i) & 1: return 1 return 2 # TABLE GENERATION CODE def process_east_asian_width(): import sys out = [] last = None for line in sys.stdin.readlines(): if line[:1] == "#": continue line = line.strip() hex,rest = line.split(";",1) wid,rest = rest.split(" # ",1) word1 = rest.split(" ",1)[0] if "." in hex: hex = hex.split("..")[1] num = int(hex, 16) if word1 in ("COMBINING","MODIFIER",""): l = 0 elif wid in ("W", "F"): l = 2 else: l = 1 if last is None: out.append((0, l)) last = l if last == l: out[-1] = (num, l) else: out.append( (num, l) ) last = l print "widths = [" for o in out[1:]: # treat control characters same as ascii print "\t%r," % (o,) print "]" if __name__ == "__main__": process_east_asian_width() urwid-1.1.1/urwid/curses_display.py0000775000175000017500000004577512051303575016771 0ustar ianian00000000000000#!/usr/bin/python # # Urwid curses output wrapper.. the horror.. # 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/ """ Curses-based UI implementation """ import curses import _curses from urwid import escape from urwid.display_common import BaseScreen, RealTerminal, AttrSpec, \ UNPRINTABLE_TRANS_TABLE from urwid.compat import bytes, PYTHON3 KEY_RESIZE = 410 # curses.KEY_RESIZE (sometimes not defined) KEY_MOUSE = 409 # curses.KEY_MOUSE _curses_colours = { 'default': (-1, 0), 'black': (curses.COLOR_BLACK, 0), 'dark red': (curses.COLOR_RED, 0), 'dark green': (curses.COLOR_GREEN, 0), 'brown': (curses.COLOR_YELLOW, 0), 'dark blue': (curses.COLOR_BLUE, 0), 'dark magenta': (curses.COLOR_MAGENTA, 0), 'dark cyan': (curses.COLOR_CYAN, 0), 'light gray': (curses.COLOR_WHITE, 0), 'dark gray': (curses.COLOR_BLACK, 1), 'light red': (curses.COLOR_RED, 1), 'light green': (curses.COLOR_GREEN, 1), 'yellow': (curses.COLOR_YELLOW, 1), 'light blue': (curses.COLOR_BLUE, 1), 'light magenta': (curses.COLOR_MAGENTA, 1), 'light cyan': (curses.COLOR_CYAN, 1), 'white': (curses.COLOR_WHITE, 1), } class Screen(BaseScreen, RealTerminal): def __init__(self): super(Screen,self).__init__() self.curses_pairs = [ (None,None), # Can't be sure what pair 0 will default to ] self.palette = {} self.has_color = False self.s = None self.cursor_state = None self._keyqueue = [] self.prev_input_resize = 0 self.set_input_timeouts() self.last_bstate = 0 self.register_palette_entry(None, 'default','default') def set_mouse_tracking(self): """ Enable mouse tracking. After calling this function get_input will include mouse click events along with keystrokes. """ curses.mousemask(0 | curses.BUTTON1_PRESSED | curses.BUTTON1_RELEASED | curses.BUTTON2_PRESSED | curses.BUTTON2_RELEASED | curses.BUTTON3_PRESSED | curses.BUTTON3_RELEASED | curses.BUTTON4_PRESSED | curses.BUTTON4_RELEASED | curses.BUTTON_SHIFT | curses.BUTTON_ALT | curses.BUTTON_CTRL) def start(self): """ Initialize the screen and input mode. """ assert self._started == False self.s = curses.initscr() self.has_color = curses.has_colors() if self.has_color: curses.start_color() if curses.COLORS < 8: # not colourful enough self.has_color = False if self.has_color: try: curses.use_default_colors() self.has_default_colors=True except _curses.error: self.has_default_colors=False self._setup_colour_pairs() curses.noecho() curses.meta(1) curses.halfdelay(10) # use set_input_timeouts to adjust self.s.keypad(0) if not self._signal_keys_set: self._old_signal_keys = self.tty_signal_keys() super(Screen, self).start() def stop(self): """ Restore the screen. """ if self._started == False: return curses.echo() self._curs_set(1) try: curses.endwin() except _curses.error: pass # don't block original error with curses error if self._old_signal_keys: self.tty_signal_keys(*self._old_signal_keys) super(Screen, self).stop() def run_wrapper(self,fn): """Call fn in fullscreen mode. Return to normal on exit. This function should be called to wrap your main program loop. Exception tracebacks will be displayed in normal mode. """ try: self.start() return fn() finally: self.stop() def _setup_colour_pairs(self): """ Initialize all 63 color pairs based on the term: bg * 8 + 7 - fg So to get a color, we just need to use that term and get the right color pair number. """ if not self.has_color: return for fg in xrange(8): for bg in xrange(8): # leave out white on black if fg == curses.COLOR_WHITE and \ bg == curses.COLOR_BLACK: continue curses.init_pair(bg * 8 + 7 - fg, fg, bg) def _curs_set(self,x): if self.cursor_state== "fixed" or x == self.cursor_state: return try: curses.curs_set(x) self.cursor_state = x except _curses.error: self.cursor_state = "fixed" def _clear(self): self.s.clear() self.s.refresh() def _getch(self, wait_tenths): if wait_tenths==0: return self._getch_nodelay() if wait_tenths is None: curses.cbreak() else: curses.halfdelay(wait_tenths) self.s.nodelay(0) return self.s.getch() def _getch_nodelay(self): self.s.nodelay(1) while 1: # this call fails sometimes, but seems to work when I try again try: curses.cbreak() break except _curses.error: pass return self.s.getch() def set_input_timeouts(self, max_wait=None, complete_wait=0.1, resize_wait=0.1): """ Set the get_input timeout values. All values have a granularity of 0.1s, ie. any value between 0.15 and 0.05 will be treated as 0.1 and any value less than 0.05 will be treated as 0. The maximum timeout value for this module is 25.5 seconds. max_wait -- amount of time in seconds to wait for input when there is no input pending, wait forever if None complete_wait -- amount of time in seconds to wait when get_input detects an incomplete escape sequence at the end of the available input resize_wait -- amount of time in seconds to wait for more input after receiving two screen resize requests in a row to stop urwid from consuming 100% cpu during a gradual window resize operation """ def convert_to_tenths( s ): if s is None: return None return int( (s+0.05)*10 ) self.max_tenths = convert_to_tenths(max_wait) self.complete_tenths = convert_to_tenths(complete_wait) self.resize_tenths = convert_to_tenths(resize_wait) def get_input(self, raw_keys=False): """Return pending input as a list. raw_keys -- return raw keycodes as well as translated versions This function will immediately return all the input since the last time it was called. If there is no input pending it will wait before returning an empty list. The wait time may be configured with the set_input_timeouts function. If raw_keys is False (default) this function will return a list of keys pressed. If raw_keys is True this function will return a ( keys pressed, raw keycodes ) tuple instead. Examples of keys returned: * ASCII printable characters: " ", "a", "0", "A", "-", "/" * ASCII control characters: "tab", "enter" * Escape sequences: "up", "page up", "home", "insert", "f1" * Key combinations: "shift f1", "meta a", "ctrl b" * Window events: "window resize" When a narrow encoding is not enabled: * "Extended ASCII" characters: "\\xa1", "\\xb2", "\\xfe" When a wide encoding is enabled: * Double-byte characters: "\\xa1\\xea", "\\xb2\\xd4" When utf8 encoding is enabled: * Unicode characters: u"\\u00a5", u'\\u253c" Examples of mouse events returned: * Mouse button press: ('mouse press', 1, 15, 13), ('meta mouse press', 2, 17, 23) * Mouse button release: ('mouse release', 0, 18, 13), ('ctrl mouse release', 0, 17, 23) """ assert self._started keys, raw = self._get_input( self.max_tenths ) # Avoid pegging CPU at 100% when slowly resizing, and work # around a bug with some braindead curses implementations that # return "no key" between "window resize" commands if keys==['window resize'] and self.prev_input_resize: while True: keys, raw2 = self._get_input(self.resize_tenths) raw += raw2 if not keys: keys, raw2 = self._get_input( self.resize_tenths) raw += raw2 if keys!=['window resize']: break if keys[-1:]!=['window resize']: keys.append('window resize') if keys==['window resize']: self.prev_input_resize = 2 elif self.prev_input_resize == 2 and not keys: self.prev_input_resize = 1 else: self.prev_input_resize = 0 if raw_keys: return keys, raw return keys def _get_input(self, wait_tenths): # this works around a strange curses bug with window resizing # not being reported correctly with repeated calls to this # function without a doupdate call in between curses.doupdate() key = self._getch(wait_tenths) resize = False raw = [] keys = [] while key >= 0: raw.append(key) if key==KEY_RESIZE: resize = True elif key==KEY_MOUSE: keys += self._encode_mouse_event() else: keys.append(key) key = self._getch_nodelay() processed = [] try: while keys: run, keys = escape.process_keyqueue(keys, True) processed += run except escape.MoreInputRequired: key = self._getch(self.complete_tenths) while key >= 0: raw.append(key) if key==KEY_RESIZE: resize = True elif key==KEY_MOUSE: keys += self._encode_mouse_event() else: keys.append(key) key = self._getch_nodelay() while keys: run, keys = escape.process_keyqueue(keys, False) processed += run if resize: processed.append('window resize') return processed, raw def _encode_mouse_event(self): # convert to escape sequence last = next = self.last_bstate (id,x,y,z,bstate) = curses.getmouse() mod = 0 if bstate & curses.BUTTON_SHIFT: mod |= 4 if bstate & curses.BUTTON_ALT: mod |= 8 if bstate & curses.BUTTON_CTRL: mod |= 16 l = [] def append_button( b ): b |= mod l.extend([ 27, ord('['), ord('M'), b+32, x+33, y+33 ]) if bstate & curses.BUTTON1_PRESSED and last & 1 == 0: append_button( 0 ) next |= 1 if bstate & curses.BUTTON2_PRESSED and last & 2 == 0: append_button( 1 ) next |= 2 if bstate & curses.BUTTON3_PRESSED and last & 4 == 0: append_button( 2 ) next |= 4 if bstate & curses.BUTTON4_PRESSED and last & 8 == 0: append_button( 64 ) next |= 8 if bstate & curses.BUTTON1_RELEASED and last & 1: append_button( 0 + escape.MOUSE_RELEASE_FLAG ) next &= ~ 1 if bstate & curses.BUTTON2_RELEASED and last & 2: append_button( 1 + escape.MOUSE_RELEASE_FLAG ) next &= ~ 2 if bstate & curses.BUTTON3_RELEASED and last & 4: append_button( 2 + escape.MOUSE_RELEASE_FLAG ) next &= ~ 4 if bstate & curses.BUTTON4_RELEASED and last & 8: append_button( 64 + escape.MOUSE_RELEASE_FLAG ) next &= ~ 8 self.last_bstate = next return l def _dbg_instr(self): # messy input string (intended for debugging) curses.echo() self.s.nodelay(0) curses.halfdelay(100) str = self.s.getstr() curses.noecho() return str def _dbg_out(self,str): # messy output function (intended for debugging) self.s.clrtoeol() self.s.addstr(str) self.s.refresh() self._curs_set(1) def _dbg_query(self,question): # messy query (intended for debugging) self._dbg_out(question) return self._dbg_instr() def _dbg_refresh(self): self.s.refresh() def get_cols_rows(self): """Return the terminal dimensions (num columns, num rows).""" rows,cols = self.s.getmaxyx() return cols,rows def _setattr(self, a): if a is None: self.s.attrset(0) return elif not isinstance(a, AttrSpec): p = self._palette.get(a, (AttrSpec('default', 'default'),)) a = p[0] if self.has_color: if a.foreground_basic: if a.foreground_number >= 8: fg = a.foreground_number - 8 else: fg = a.foreground_number else: fg = 7 if a.background_basic: bg = a.background_number else: bg = 0 attr = curses.color_pair(bg * 8 + 7 - fg) else: attr = 0 if a.bold: attr |= curses.A_BOLD if a.standout: attr |= curses.A_STANDOUT if a.underline: attr |= curses.A_UNDERLINE if a.blink: attr |= curses.A_BLINK self.s.attrset(attr) def draw_screen(self, (cols, rows), r ): """Paint screen with rendered canvas.""" assert self._started assert r.rows() == rows, "canvas size and passed size don't match" y = -1 for row in r.content(): y += 1 try: self.s.move( y, 0 ) except _curses.error: # terminal shrunk? # move failed so stop rendering. return first = True lasta = None nr = 0 for a, cs, seg in row: if cs != 'U': seg = seg.translate(UNPRINTABLE_TRANS_TABLE) assert isinstance(seg, bytes) if first or lasta != a: self._setattr(a) lasta = a try: if cs in ("0", "U"): for i in range(len(seg)): self.s.addch( 0x400000 + ord(seg[i]) ) else: assert cs is None if PYTHON3: assert isinstance(seg, bytes) self.s.addstr(seg.decode('utf-8')) else: self.s.addstr(seg) except _curses.error: # it's ok to get out of the # screen on the lower right if (y == rows-1 and nr == len(row)-1): pass else: # perhaps screen size changed # quietly abort. return nr += 1 if r.cursor is not None: x,y = r.cursor self._curs_set(1) try: self.s.move(y,x) except _curses.error: pass else: self._curs_set(0) self.s.move(0,0) self.s.refresh() self.keep_cache_alive_link = r def clear(self): """ Force the screen to be completely repainted on the next call to draw_screen(). """ self.s.clear() class _test: def __init__(self): self.ui = Screen() self.l = _curses_colours.keys() self.l.sort() for c in self.l: self.ui.register_palette( [ (c+" on black", c, 'black', 'underline'), (c+" on dark blue",c, 'dark blue', 'bold'), (c+" on light gray",c,'light gray', 'standout'), ]) self.ui.run_wrapper(self.run) def run(self): class FakeRender: pass r = FakeRender() text = [" has_color = "+repr(self.ui.has_color),""] attr = [[],[]] r.coords = {} r.cursor = None for c in self.l: t = "" a = [] for p in c+" on black",c+" on dark blue",c+" on light gray": a.append((p,27)) t=t+ (p+27*" ")[:27] text.append( t ) attr.append( a ) text += ["","return values from get_input(): (q exits)", ""] attr += [[],[],[]] cols,rows = self.ui.get_cols_rows() keys = None while keys!=['q']: r.text=([t.ljust(cols) for t in text]+[""]*rows)[:rows] r.attr=(attr+[[]]*rows) [:rows] self.ui.draw_screen((cols,rows),r) keys, raw = self.ui.get_input( raw_keys = True ) if 'window resize' in keys: cols, rows = self.ui.get_cols_rows() if not keys: continue t = "" a = [] for k in keys: if type(k) == unicode: k = k.encode("utf-8") t += "'"+k + "' " a += [(None,1), ('yellow on dark blue',len(k)), (None,2)] text.append(t + ": "+ repr(raw)) attr.append(a) text = text[-rows:] attr = attr[-rows:] if '__main__'==__name__: _test() urwid-1.1.1/urwid/escape.py0000664000175000017500000003153512051303575015162 0ustar ianian00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Urwid escape sequences common to curses_display and raw_display # 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/ """ Terminal Escape Sequences for input and display """ import re try: from urwid import str_util except ImportError: from urwid import old_str_util as str_util from urwid.compat import bytes, bytes3 within_double_byte = str_util.within_double_byte SO = "\x0e" SI = "\x0f" IBMPC_ON = "\x1b[11m" IBMPC_OFF = "\x1b[10m" DEC_TAG = "0" DEC_SPECIAL_CHARS = u'▮◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·' ALT_DEC_SPECIAL_CHARS = u"_`abcdefghijklmnopqrstuvwxyz{|}~" DEC_SPECIAL_CHARMAP = {} assert len(DEC_SPECIAL_CHARS) == len(ALT_DEC_SPECIAL_CHARS), repr((DEC_SPECIAL_CHARS, ALT_DEC_SPECIAL_CHARS)) for c, alt in zip(DEC_SPECIAL_CHARS, ALT_DEC_SPECIAL_CHARS): DEC_SPECIAL_CHARMAP[ord(c)] = SO + alt + SI SAFE_ASCII_DEC_SPECIAL_RE = re.compile(u"^[ -~%s]*$" % DEC_SPECIAL_CHARS) DEC_SPECIAL_RE = re.compile(u"[%s]" % DEC_SPECIAL_CHARS) ################### ## Input sequences ################### class MoreInputRequired(Exception): pass def escape_modifier( digit ): mode = ord(digit) - ord("1") return "shift "*(mode&1) + "meta "*((mode&2)//2) + "ctrl "*((mode&4)//4) input_sequences = [ ('[A','up'),('[B','down'),('[C','right'),('[D','left'), ('[E','5'),('[F','end'),('[G','5'),('[H','home'), ('[1~','home'),('[2~','insert'),('[3~','delete'),('[4~','end'), ('[5~','page up'),('[6~','page down'), ('[7~','home'),('[8~','end'), ('[[A','f1'),('[[B','f2'),('[[C','f3'),('[[D','f4'),('[[E','f5'), ('[11~','f1'),('[12~','f2'),('[13~','f3'),('[14~','f4'), ('[15~','f5'),('[17~','f6'),('[18~','f7'),('[19~','f8'), ('[20~','f9'),('[21~','f10'),('[23~','f11'),('[24~','f12'), ('[25~','f13'),('[26~','f14'),('[28~','f15'),('[29~','f16'), ('[31~','f17'),('[32~','f18'),('[33~','f19'),('[34~','f20'), ('OA','up'),('OB','down'),('OC','right'),('OD','left'), ('OH','home'),('OF','end'), ('OP','f1'),('OQ','f2'),('OR','f3'),('OS','f4'), ('Oo','/'),('Oj','*'),('Om','-'),('Ok','+'), ('[Z','shift tab'), ('On', '.'), ] + [ (prefix + letter, modifier + key) for prefix, modifier in zip('O[', ('meta ', 'shift ')) for letter, key in zip('abcd', ('up', 'down', 'right', 'left')) ] + [ ("[" + digit + symbol, modifier + key) for modifier, symbol in zip(('shift ', 'meta '), '$^') for digit, key in zip('235678', ('insert', 'delete', 'page up', 'page down', 'home', 'end')) ] + [ ('O' + chr(ord('p')+n), str(n)) for n in range(10) ] + [ # modified cursor keys + home, end, 5 -- [#X and [1;#X forms (prefix+digit+letter, escape_modifier(digit) + key) for prefix in "[","[1;" for digit in "12345678" for letter,key in zip("ABCDEFGH", ('up','down','right','left','5','end','5','home')) ] + [ # modified F1-F4 keys -- O#X form ("O"+digit+letter, escape_modifier(digit) + key) for digit in "12345678" for letter,key in zip("PQRS",('f1','f2','f3','f4')) ] + [ # modified F1-F13 keys -- [XX;#~ form ("["+str(num)+";"+digit+"~", escape_modifier(digit) + key) for digit in "12345678" for num,key in zip( (3,5,6,11,12,13,14,15,17,18,19,20,21,23,24,25,26,28,29,31,32,33,34), ('delete', 'page up', 'page down', 'f1','f2','f3','f4','f5','f6','f7','f8','f9','f10','f11', 'f12','f13','f14','f15','f16','f17','f18','f19','f20')) ] + [ # mouse reporting (special handling done in KeyqueueTrie) ('[M', 'mouse'), # report status response ('[0n', 'status ok') ] class KeyqueueTrie(object): def __init__( self, sequences ): self.data = {} for s, result in sequences: assert type(result) != dict self.add(self.data, s, result) def add(self, root, s, result): assert type(root) == dict, "trie conflict detected" assert len(s) > 0, "trie conflict detected" if root.has_key(ord(s[0])): return self.add(root[ord(s[0])], s[1:], result) if len(s)>1: d = {} root[ord(s[0])] = d return self.add(d, s[1:], result) root[ord(s)] = result def get(self, keys, more_available): result = self.get_recurse(self.data, keys, more_available) if not result: result = self.read_cursor_position(keys, more_available) return result def get_recurse(self, root, keys, more_available): if type(root) != dict: if root == "mouse": return self.read_mouse_info(keys, more_available) return (root, keys) if not keys: # get more keys if more_available: raise MoreInputRequired() return None if not root.has_key(keys[0]): return None return self.get_recurse(root[keys[0]], keys[1:], more_available) def read_mouse_info(self, keys, more_available): if len(keys) < 3: if more_available: raise MoreInputRequired() return None b = keys[0] - 32 x, y = (keys[1] - 33)%256, (keys[2] - 33)%256 # supports 0-255 prefix = "" if b & 4: prefix = prefix + "shift " if b & 8: prefix = prefix + "meta " if b & 16: prefix = prefix + "ctrl " # 0->1, 1->2, 2->3, 64->4, 65->5 button = ((b&64)/64*3) + (b & 3) + 1 if b & 3 == 3: action = "release" button = 0 elif b & MOUSE_RELEASE_FLAG: action = "release" elif b & MOUSE_DRAG_FLAG: action = "drag" else: action = "press" return ( (prefix + "mouse " + action, button, x, y), keys[3:] ) def read_cursor_position(self, keys, more_available): """ Interpret cursor position information being sent by the user's terminal. Returned as ('cursor position', x, y) where (x, y) == (0, 0) is the top left of the screen. """ if not keys: if more_available: raise MoreInputRequired() return None if keys[0] != ord('['): return None # read y value y = 0 i = 1 for k in keys[i:]: i += 1 if k == ord(';'): if not y: return None break if k < ord('0') or k > ord('9'): return None if not y and k == ord('0'): return None y = y * 10 + k - ord('0') if not keys[i:]: if more_available: raise MoreInputRequired() return None # read x value x = 0 for k in keys[i:]: i += 1 if k == ord('R'): if not x: return None return (("cursor position", x-1, y-1), keys[i:]) if k < ord('0') or k > ord('9'): return None if not x and k == ord('0'): return None x = x * 10 + k - ord('0') if not keys[i:]: if more_available: raise MoreInputRequired() return None # This is added to button value to signal mouse release by curses_display # and raw_display when we know which button was released. NON-STANDARD MOUSE_RELEASE_FLAG = 2048 # xterm adds this to the button value to signal a mouse drag event MOUSE_DRAG_FLAG = 32 ################################################# # Build the input trie from input_sequences list input_trie = KeyqueueTrie(input_sequences) ################################################# _keyconv = { -1:None, 8:'backspace', 9:'tab', 10:'enter', 13:'enter', 127:'backspace', # curses-only keycodes follow.. (XXX: are these used anymore?) 258:'down', 259:'up', 260:'left', 261:'right', 262:'home', 263:'backspace', 265:'f1', 266:'f2', 267:'f3', 268:'f4', 269:'f5', 270:'f6', 271:'f7', 272:'f8', 273:'f9', 274:'f10', 275:'f11', 276:'f12', 277:'shift f1', 278:'shift f2', 279:'shift f3', 280:'shift f4', 281:'shift f5', 282:'shift f6', 283:'shift f7', 284:'shift f8', 285:'shift f9', 286:'shift f10', 287:'shift f11', 288:'shift f12', 330:'delete', 331:'insert', 338:'page down', 339:'page up', 343:'enter', # on numpad 350:'5', # on numpad 360:'end', } def process_keyqueue(codes, more_available): """ codes -- list of key codes more_available -- if True then raise MoreInputRequired when in the middle of a character sequence (escape/utf8/wide) and caller will attempt to send more key codes on the next call. returns (list of input, list of remaining key codes). """ code = codes[0] if code >= 32 and code <= 126: key = chr(code) return [key], codes[1:] if _keyconv.has_key(code): return [_keyconv[code]], codes[1:] if code >0 and code <27: return ["ctrl %s" % chr(ord('a')+code-1)], codes[1:] if code >27 and code <32: return ["ctrl %s" % chr(ord('A')+code-1)], codes[1:] em = str_util.get_byte_encoding() if (em == 'wide' and code < 256 and within_double_byte(chr(code),0,0)): if not codes[1:]: if more_available: raise MoreInputRequired() if codes[1:] and codes[1] < 256: db = chr(code)+chr(codes[1]) if within_double_byte(db, 0, 1): return [db], codes[2:] if em == 'utf8' and code>127 and code<256: if code & 0xe0 == 0xc0: # 2-byte form need_more = 1 elif code & 0xf0 == 0xe0: # 3-byte form need_more = 2 elif code & 0xf8 == 0xf0: # 4-byte form need_more = 3 else: return ["<%d>"%code], codes[1:] for i in range(need_more): if len(codes)-1 <= i: if more_available: raise MoreInputRequired() else: return ["<%d>"%code], codes[1:] k = codes[i+1] if k>256 or k&0xc0 != 0x80: return ["<%d>"%code], codes[1:] s = bytes3(codes[:need_more+1]) assert isinstance(s, bytes) try: return [s.decode("utf-8")], codes[need_more+1:] except UnicodeDecodeError: return ["<%d>"%code], codes[1:] if code >127 and code <256: key = chr(code) return [key], codes[1:] if code != 27: return ["<%d>"%code], codes[1:] result = input_trie.get(codes[1:], more_available) if result is not None: result, remaining_codes = result return [result], remaining_codes if codes[1:]: # Meta keys -- ESC+Key form run, remaining_codes = process_keyqueue(codes[1:], more_available) if run[0] == "esc" or run[0].find("meta ") >= 0: return ['esc']+run, remaining_codes return ['meta '+run[0]]+run[1:], remaining_codes return ['esc'], codes[1:] #################### ## Output sequences #################### ESC = "\x1b" CURSOR_HOME = ESC+"[H" CURSOR_HOME_COL = "\r" APP_KEYPAD_MODE = ESC+"=" NUM_KEYPAD_MODE = ESC+">" SWITCH_TO_ALTERNATE_BUFFER = ESC+"7"+ESC+"[?47h" RESTORE_NORMAL_BUFFER = ESC+"[?47l"+ESC+"8" #RESET_SCROLL_REGION = ESC+"[;r" #RESET = ESC+"c" REPORT_STATUS = ESC + "[5n" REPORT_CURSOR_POSITION = ESC+"[6n" INSERT_ON = ESC + "[4h" INSERT_OFF = ESC + "[4l" def set_cursor_position( x, y ): assert type(x) == int assert type(y) == int return ESC+"[%d;%dH" %(y+1, x+1) def move_cursor_right(x): if x < 1: return "" return ESC+"[%dC" % x def move_cursor_up(x): if x < 1: return "" return ESC+"[%dA" % x def move_cursor_down(x): if x < 1: return "" return ESC+"[%dB" % x HIDE_CURSOR = ESC+"[?25l" SHOW_CURSOR = ESC+"[?25h" MOUSE_TRACKING_ON = ESC+"[?1000h"+ESC+"[?1002h" MOUSE_TRACKING_OFF = ESC+"[?1002l"+ESC+"[?1000l" DESIGNATE_G1_SPECIAL = ESC+")0" ERASE_IN_LINE_RIGHT = ESC+"[K" urwid-1.1.1/urwid/widget.py0000664000175000017500000016650212051303575015210 0ustar ianian00000000000000#!/usr/bin/python # # Urwid basic widget classes # Copyright (C) 2004-2012 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: http://excess.org/urwid/ from operator import attrgetter 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 from urwid import signals from urwid import text_layout from urwid.canvas import (CanvasCache, CompositeCanvas, SolidCanvas, apply_text_layout) from urwid.command_map import (command_map, CURSOR_LEFT, CURSOR_RIGHT, CURSOR_UP, CURSOR_DOWN, CURSOR_MAX_LEFT, CURSOR_MAX_RIGHT) from urwid.split_repr import split_repr, remove_defaults, python3_repr # define some names for these constants to avoid misspellings in the source # and to document the constant strings we are using # Widget sizing methods FLOW = 'flow' BOX = 'box' FIXED = 'fixed' # Text alignment modes LEFT = 'left' RIGHT = 'right' CENTER = 'center' # Filler alignment TOP = 'top' MIDDLE = 'middle' BOTTOM = 'bottom' # Text wrapping modes SPACE = 'space' ANY = 'any' CLIP = 'clip' # Width and Height settings PACK = 'pack' GIVEN = 'given' RELATIVE = 'relative' RELATIVE_100 = (RELATIVE, 100) WEIGHT = 'weight' class WidgetMeta(MetaSuper, signals.MetaSignals): """ Bases: :class:`MetaSuper`, :class:`MetaSignals` Automatic caching of render and rows methods. Class variable *no_cache* is a list of names of methods to not cache automatically. Valid method names for *no_cache* are ``'render'`` and ``'rows'``. Class variable *ignore_focus* if defined and set to ``True`` indicates that the canvas this widget renders is not affected by the focus parameter, so it may be ignored when caching. """ def __init__(cls, name, bases, d): no_cache = d.get("no_cache", []) super(WidgetMeta, cls).__init__(name, bases, d) if "render" in d: if "render" not in no_cache: render_fn = cache_widget_render(cls) else: render_fn = nocache_widget_render(cls) cls.render = render_fn if "rows" in d and "rows" not in no_cache: cls.rows = cache_widget_rows(cls) if "no_cache" in d: del cls.no_cache if "ignore_focus" in d: del cls.ignore_focus class WidgetError(Exception): pass def validate_size(widget, size, canv): """ Raise a WidgetError if a canv does not match size size. """ if (size and size[1:] != (0,) and size[0] != canv.cols()) or \ (len(size)>1 and size[1] != canv.rows()): raise WidgetError("Widget %r rendered (%d x %d) canvas" " when passed size %r!" % (widget, canv.cols(), canv.rows(), size)) def update_wrapper(new_fn, fn): """ Copy as much of the function detail from fn to new_fn as we can. """ try: new_fn.__name__ = fn.__name__ new_fn.__dict__.update(fn.__dict__) new_fn.__doc__ = fn.__doc__ new_fn.__module__ = fn.__module__ except TypeError: pass # python2.3 ignore read-only attributes def cache_widget_render(cls): """ Return a function that wraps the cls.render() method and fetches and stores canvases with CanvasCache. """ ignore_focus = bool(getattr(cls, "ignore_focus", False)) fn = cls.render def cached_render(self, size, focus=False): focus = focus and not ignore_focus canv = CanvasCache.fetch(self, cls, size, focus) if canv: return canv canv = fn(self, size, focus=focus) validate_size(self, size, canv) if canv.widget_info: canv = CompositeCanvas(canv) canv.finalize(self, size, focus) CanvasCache.store(cls, canv) return canv cached_render.original_fn = fn update_wrapper(cached_render, fn) return cached_render def nocache_widget_render(cls): """ Return a function that wraps the cls.render() method and finalizes the canvas that it returns. """ fn = cls.render if hasattr(fn, "original_fn"): fn = fn.original_fn def finalize_render(self, size, focus=False): canv = fn(self, size, focus=focus) if canv.widget_info: canv = CompositeCanvas(canv) validate_size(self, size, canv) canv.finalize(self, size, focus) return canv finalize_render.original_fn = fn update_wrapper(finalize_render, fn) return finalize_render def nocache_widget_render_instance(self): """ Return a function that wraps the cls.render() method and finalizes the canvas that it returns, but does not cache the canvas. """ fn = self.render.original_fn def finalize_render(size, focus=False): canv = fn(self, size, focus=focus) if canv.widget_info: canv = CompositeCanvas(canv) canv.finalize(self, size, focus) return canv finalize_render.original_fn = fn update_wrapper(finalize_render, fn) return finalize_render def cache_widget_rows(cls): """ Return a function that wraps the cls.rows() method and returns rows from the CanvasCache if available. """ ignore_focus = bool(getattr(cls, "ignore_focus", False)) fn = cls.rows def cached_rows(self, size, focus=False): focus = focus and not ignore_focus canv = CanvasCache.fetch(self, cls, size, focus) if canv: return canv.rows() return fn(self, size, focus) update_wrapper(cached_rows, fn) return cached_rows class Widget(object): """ Widget base class .. attribute:: __metaclass__ :annotation: = urwid.WidgetMeta See :class:`urwid.WidgetMeta` definition .. attribute:: _selectable :annotation: = False The default :meth:`.selectable` method returns this value. .. attribute:: _sizing :annotation: = frozenset(['flow', 'box', 'fixed']) The default :meth:`.sizing` method returns this value. .. attribute:: _command_map :annotation: = urwid.command_map A shared :class:`CommandMap` instance. May be redefined in subclasses or widget instances. .. method:: render(size, focus=False) .. note:: This method is not implemented in :class:`.Widget` but must be implemented by any concrete subclass :param size: One of the following, *maxcol* and *maxrow* are integers > 0: (*maxcol*, *maxrow*) for box sizing -- the parent chooses the exact size of this widget (*maxcol*,) for flow sizing -- the parent chooses only the number of columns for this widget () for fixed sizing -- this widget is a fixed size which can't be adjusted by the parent :type size: widget size :param focus: set to ``True`` if this widget or one of its children is in focus :type focus: bool :returns: A :class:`Canvas` subclass instance containing the rendered content of this widget :class:`Text` widgets return a :class:`TextCanvas` (arbitrary text and display attributes), :class:`SolidFill` widgets return a :class:`SolidCanvas` (a single character repeated across the whole surface) and container widgets return a :class:`CompositeCanvas` (one or more other canvases arranged arbitrarily). If *focus* is ``False``, the returned canvas may not have a cursor position set. There is some metaclass magic defined in the :class:`Widget` metaclass :class:`WidgetMeta` that causes the result of this method to be cached by :class:`CanvasCache`. Later calls will automatically look up the value in the cache first. As a small optimization the class variable :attr:`ignore_focus` may be defined and set to ``True`` if this widget renders the same canvas regardless of the value of the *focus* parameter. Any time the content of a widget changes it should call :meth:`_invalidate` to remove any cached canvases, or the widget may render the cached canvas instead of creating a new one. .. method:: rows(size, focus=False) .. note:: This method is not implemented in :class:`.Widget` but must be implemented by any flow widget. See :meth:`.sizing`. See :meth:`Widget.render` for parameter details. :returns: The number of rows required for this widget given a number of columns in *size* This is the method flow widgets use to communicate their size to other widgets without having to render a canvas. This should be a quick calculation as this function may be called a number of times in normal operation. If your implementation may take a long time you should add your own caching here. There is some metaclass magic defined in the :class:`Widget` metaclass :class:`WidgetMeta` that causes the result of this function to be retrieved from any canvas cached by :class:`CanvasCache`, so if your widget has been rendered you may not receive calls to this function. The class variable :attr:`ignore_focus` may be defined and set to ``True`` if this widget renders the same size regardless of the value of the *focus* parameter. .. method:: keypress(size, key) .. note:: This method is not implemented in :class:`.Widget` but must be implemented by any selectable widget. See :meth:`.selectable`. :param size: See :meth:`Widget.render` for details :type size: widget size :param key: a single keystroke value; see :ref:`keyboard-input` :type key: bytes or unicode :returns: ``None`` if *key* was handled by this widget or *key* (the same value passed) if *key* was not handled by this widget Container widgets will typically call the :meth:`keypress` method on whichever of their children is set as the focus. The standard widgets use :attr:`_command_map` to determine what action should be performed for a given *key*. You may modify these values to your liking globally, at some level in the widget hierarchy or on individual widgets. See :class:`CommandMap` for the defaults. In your own widgets you may use whatever logic you like: filtering or translating keys, selectively passing along events etc. .. method:: mouse_event(size, event, button, col, row, focus) .. note:: This method is not implemented in :class:`.Widget` but may be implemented by a subclass. Not implementing this method is equivalent to having a method that always returns ``False``. :param size: See :meth:`Widget.render` for details. :type size: widget size :param event: Values such as ``'mouse press'``, ``'ctrl mouse press'``, ``'mouse relase'``, ``'meta mouse release'``, ``'mouse drag'``; see :ref:`mouse-input` :type event: mouse event :param button: 1 through 5 for press events, often 0 for release events (which button was released is often not known) :type button: int :param col: Column of the event, 0 is the left edge of this widget :type col: int :param row: Row of the event, 0 it the top row of this widget :type row: int :param focus: Set to ``True`` if this widget or one of its children is in focus :type focus: bool :returns: ``True`` if the event was handled by this widget, ``False`` otherwise Container widgets will typically call the :meth:`mouse_event` method on whichever of their children is at the position (*col*, *row*). .. method:: get_cursor_coords(size) .. note:: This method is not implemented in :class:`.Widget` but must be implemented by any widget that may return cursor coordinates as part of the canvas that :meth:`render` returns. :param size: See :meth:`Widget.render` for details. :type size: widget size :returns: (*col*, *row*) if this widget has a cursor, ``None`` otherwise Return the cursor coordinates (*col*, *row*) of a cursor that will appear as part of the canvas rendered by this widget when in focus, or ``None`` if no cursor is displayed. The :class:`ListBox` widget uses this method to make sure a cursor in the focus widget is not scrolled out of view. It is a separate method to avoid having to render the whole widget while calculating layout. Container widgets will typically call the :meth:`.get_cursor_coords` method on their focus widget. .. method:: get_pref_col(size) .. note:: This method is not implemented in :class:`.Widget` but may be implemented by a subclass. :param size: See :meth:`Widget.render` for details. :type size: widget size :returns: a column number or ``'left'`` for the leftmost available column or ``'right'`` for the rightmost available column Return the preferred column for the cursor to be displayed in this widget. This value might not be the same as the column returned from :meth:`get_cursor_coords`. The :class:`ListBox` and :class:`Pile` widgets call this method on a widget losing focus and use the value returned to call :meth:`.move_cursor_to_coords` on the widget becoming the focus. This allows the focus to move up and down through widgets while keeping the cursor in approximately the same column on screen. .. method:: move_cursor_to_coords(size, col, row) .. note:: This method is not implemented in :class:`.Widget` but may be implemented by a subclass. Not implementing this method is equivalent to having a method that always returns ``False``. :param size: See :meth:`Widget.render` for details. :type size: widget size :param col: new column for the cursor, 0 is the left edge of this widget :type col: int :param row: new row for the cursor, 0 it the top row of this widget :type row: int :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 def _invalidate(self): """ Mark cached canvases rendered by this widget as dirty so that they will not be used again. """ CanvasCache.invalidate(self) def _emit(self, name, *args): """ Convenience function to emit signals with self as first argument. """ signals.emit_signal(self, name, self, *args) def selectable(self): """ :returns: ``True`` if this is a widget that is designed to take the focus, i.e. it contains something the user might want to interact with, ``False`` otherwise, This default implementation returns :attr:`._selectable`. Subclasses may leave these is if the are not selectable, or if they are always selectable they may set the :attr:`_selectable` class variable to ``True``. If this method returns ``True`` then the :meth:`.keypress` method must be implemented. Returning ``False`` does not guarantee that this widget will never be in focus, only that this widget will usually be skipped over when changing focus. It is still possible for non selectable widgets to have the focus (typically when there are no other selectable widgets visible). """ return self._selectable def sizing(self): """ :returns: A frozenset including one or more of ``'box'``, ``'flow'`` and ``'fixed'``. Default implementation returns the value of :attr:`._sizing`, which for this class includes all three. The sizing modes returned indicate the modes that may be supported by this widget, but is not sufficient to know that using that sizing mode will work. Subclasses should make an effort to remove sizing modes they know will not work given the state of the widget, but many do not yet do this. If a sizing mode is missing from the set then the widget should fail when used in that mode. If ``'flow'`` is among the values returned then the other methods in this widget must be able to accept a single-element tuple (*maxcol*,) to their ``size`` parameter, and the :meth:`rows` method must be defined. 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. If ``'fixed'`` is among the values returned then the other methods must be able to accept an empty tuple () to their size parameter, and the :meth:`pack` method must be defined. """ return self._sizing def pack(self, size, focus=False): """ See :meth:`Widget.render` for parameter details. :returns: A "packed" size (*maxcol*, *maxrow*) for this widget Calculate and return a minimum size where all content could still be displayed. Fixed widgets must implement this method and return their size when ``()`` is passed as the *size* parameter. This default implementation returns the *size* passed, or the *maxcol* passed and the value of :meth:`rows` as the *maxrow* when (*maxcol*,) is passed as the *size* parameter. .. note:: This is a new method that hasn't been fully implemented across the standard widget types. In particular it has not yet been implemented for container widgets. :class:`Text` widgets have implemented this method. You can use :meth:`Text.pack` to calculate the minumum 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. """ if not size: if FIXED in self.sizing(): raise NotImplementedError('Fixed widgets must override' ' Widget.pack()') raise WidgetError('Cannot pack () size, this is not a fixed' ' widget: %s' % repr(self)) elif len(size) == 1: if FLOW in self.sizing(): return size + (self.rows(size, focus),) raise WidgetError('Cannot pack (maxcol,) size, this is not a' ' flow widget: %s' % repr(self)) return size base_widget = property(lambda self:self, doc=""" Read-only property that steps through decoration widgets and returns the one at the base. This default implementation returns self. """) focus = property(lambda self:None, doc=""" Read-only property returning the child widget in focus for container widgets. This default implementation always returns ``None``, indicating that this widget has no children. """) def _not_a_container(self, val=None): raise IndexError( "No focus_position, %r is not a container widget" % self) focus_position = property(_not_a_container, _not_a_container, doc=""" Property for reading and setting the focus position for container widgets. This default implementation raises :exc:`IndexError`, making normal widgets fail the same way accessing :attr:`.focus_position` on an empty container widget would. """) def __repr__(self): """ A friendly __repr__ for widgets, designed to be extended by subclasses with _repr_words and _repr_attr methods. """ return split_repr(self) def _repr_words(self): words = [] if self.selectable(): words = ["selectable"] + words if self.sizing() and self.sizing() != frozenset([FLOW, BOX, FIXED]): sizing_modes = list(self.sizing()) sizing_modes.sort() words.append("/".join(sizing_modes)) return words + ["widget"] def _repr_attrs(self): return {} class FlowWidget(Widget): """ Deprecated. Inherit from Widget and add: _sizing = frozenset(['flow']) at the top of your class definition instead. Base class of widgets that determine their rows from the number of columns available. """ _sizing = frozenset([FLOW]) def rows(self, size, focus=False): """ All flow widgets must implement this function. """ raise NotImplementedError() def render(self, size, focus=False): """ All widgets must implement this function. """ raise NotImplementedError() class BoxWidget(Widget): """ Deprecated. Inherit from Widget and add: _sizing = frozenset(['box']) _selectable = True at the top of your class definition instead. Base class of width and height constrained widgets such as the top level widget attached to the display object """ _selectable = True _sizing = frozenset([BOX]) def render(self, size, focus=False): """ All widgets must implement this function. """ raise NotImplementedError() def fixed_size(size): """ raise ValueError if size != (). Used by FixedWidgets to test size parameter. """ if size != (): raise ValueError("FixedWidget takes only () for size." \ "passed: %r" % (size,)) class FixedWidget(Widget): """ Deprecated. Inherit from Widget and add: _sizing = frozenset(['fixed']) at the top of your class definition instead. Base class of widgets that know their width and height and cannot be resized """ _sizing = frozenset([FIXED]) def render(self, size, focus=False): """ All widgets must implement this function. """ raise NotImplementedError() def pack(self, size=None, focus=False): """ All fixed widgets must implement this function. """ raise NotImplementedError() class Divider(Widget): """ Horizontal divider widget """ _sizing = frozenset([FLOW]) ignore_focus = True def __init__(self,div_char=u" ",top=0,bottom=0): """ :param div_char: character to repeat across line :type div_char: bytes or unicode :param top: number of blank lines above :type top: int :param bottom: number of blank lines below :type bottom: int >>> Divider() >>> Divider(u'-') >>> Divider(u'x', 1, 2) """ self.__super.__init__() self.div_char = div_char self.top = top self.bottom = bottom def _repr_words(self): return self.__super._repr_words() + [ python3_repr(self.div_char)] * (self.div_char != u" ") def _repr_attrs(self): attrs = dict(self.__super._repr_attrs()) if self.top: attrs['top'] = self.top if self.bottom: attrs['bottom'] = self.bottom return attrs def rows(self, size, focus=False): """ Return the number of lines that will be rendered. >>> Divider().rows((10,)) 1 >>> Divider(u'x', 1, 2).rows((10,)) 4 """ (maxcol,) = size return self.top + 1 + self.bottom def render(self, size, focus=False): """ Render the divider as a canvas and return it. >>> Divider().render((10,)).text # ... = b in Python 3 [...' '] >>> Divider(u'-', top=1).render((10,)).text [...' ', ...'----------'] >>> Divider(u'x', bottom=2).render((5,)).text [...'xxxxx', ...' ', ...' '] """ (maxcol,) = size canv = SolidCanvas(self.div_char, maxcol, 1) canv = CompositeCanvas(canv) if self.top or self.bottom: canv.pad_trim_top_bottom(self.top, self.bottom) return canv class SolidFill(BoxWidget): """ A box widget that fills an area with a single character """ _selectable = False ignore_focus = True def __init__(self, fill_char=" "): """ :param fill_char: character to fill area with :type fill_char: bytes or unicode >>> SolidFill(u'8') """ self.__super.__init__() self.fill_char = fill_char def _repr_words(self): return self.__super._repr_words() + [python3_repr(self.fill_char)] def render(self, size, focus=False ): """ Render the Fill as a canvas and return it. >>> SolidFill().render((4,2)).text # ... = b in Python 3 [...' ', ...' '] >>> SolidFill('#').render((5,3)).text [...'#####', ...'#####', ...'#####'] """ maxcol, maxrow = size return SolidCanvas(self.fill_char, maxcol, maxrow) class TextError(Exception): pass class Text(Widget): """ a horizontally resizeable text widget """ _sizing = frozenset([FLOW]) ignore_focus = True _repr_content_length_max = 140 def __init__(self, markup, align=LEFT, wrap=SPACE, layout=None): """ :param markup: content of text widget, one of: bytes or unicode text to be displayed (*display attribute*, *text markup*) *text markup* with *display attribute* applied to all parts of *text markup* with no display attribute already applied [*text markup*, *text markup*, ... ] all *text markup* in the list joined together :type markup: :ref:`text-markup` :param align: typically ``'left'``, ``'center'`` or ``'right'`` :type align: text alignment mode :param wrap: typically ``'space'``, ``'any'`` or ``'clip'`` :type wrap: text wrapping mode :param layout: defaults to a shared :class:`StandardTextLayout` instance :type layout: text layout instance >>> Text(u"Hello") >>> t = Text(('bold', u"stuff"), 'right', 'any') >>> t >>> print t.text stuff >>> t.attrib [('bold', 5)] """ self.__super.__init__() self._cache_maxcol = None self.set_text(markup) self.set_layout(align, wrap, layout) def _repr_words(self): """ Show the text in the repr in python3 format (b prefix for byte strings) and truncate if it's too long """ first = self.__super._repr_words() text = self.get_text()[0] rest = python3_repr(text) if len(rest) > self._repr_content_length_max: rest = (rest[:self._repr_content_length_max * 2 // 3 - 3] + '...' + rest[-self._repr_content_length_max // 3:]) return first + [rest] def _repr_attrs(self): attrs = dict(self.__super._repr_attrs(), align=self._align_mode, wrap=self._wrap_mode) return remove_defaults(attrs, Text.__init__) def _invalidate(self): self._cache_maxcol = None self.__super._invalidate() def set_text(self,markup): """ Set content of text widget. :param markup: see :class:`Text` for description. :type markup: text markup >>> t = Text(u"foo") >>> print t.text foo >>> t.set_text(u"bar") >>> print t.text bar >>> t.text = u"baz" # not supported because text stores text but set_text() takes markup Traceback (most recent call last): AttributeError: can't set attribute """ self._text, self._attrib = decompose_tagmarkup(markup) self._invalidate() def get_text(self): """ :returns: (*text*, *display attributes*) *text* complete bytes/unicode content of text widget *display attributes* run length encoded display attributes for *text*, eg. ``[('attr1', 10), ('attr2', 5)]`` >>> Text(u"Hello").get_text() # ... = u in Python 2 (...'Hello', []) >>> Text(('bright', u"Headline")).get_text() (...'Headline', [('bright', 8)]) >>> Text([('a', u"one"), u"two", ('b', u"three")]).get_text() (...'onetwothree', [('a', 3), (None, 3), ('b', 5)]) """ return self._text, self._attrib text = property(lambda self:self.get_text()[0], doc=""" Read-only property returning the complete bytes/unicode content of this widget """) attrib = property(lambda self:self.get_text()[1], doc=""" Read-only property returning the run-length encoded display attributes of this widget """) def set_align_mode(self, mode): """ Set text alignment mode. Supported modes depend on text layout object in use but defaults to a :class:`StandardTextLayout` instance :param mode: typically ``'left'``, ``'center'`` or ``'right'`` :type mode: text alignment mode >>> t = Text(u"word") >>> t.set_align_mode('right') >>> t.align 'right' >>> t.render((10,)).text # ... = b in Python 3 [...' word'] >>> t.align = 'center' >>> t.render((10,)).text [...' word '] >>> t.align = 'somewhere' Traceback (most recent call last): TextError: Alignment mode 'somewhere' not supported. """ if not self.layout.supports_align_mode(mode): raise TextError("Alignment mode %r not supported."% (mode,)) self._align_mode = mode self._invalidate() def set_wrap_mode(self, mode): """ Set text wrapping mode. Supported modes depend on text layout object in use but defaults to a :class:`StandardTextLayout` instance :param mode: typically ``'space'``, ``'any'`` or ``'clip'`` :type mode: text wrapping mode >>> t = Text(u"some words") >>> t.render((6,)).text # ... = b in Python 3 [...'some ', ...'words '] >>> t.set_wrap_mode('clip') >>> t.wrap 'clip' >>> t.render((6,)).text [...'some w'] >>> t.wrap = 'any' # Urwid 0.9.9 or later >>> t.render((6,)).text [...'some w', ...'ords '] >>> t.wrap = 'somehow' Traceback (most recent call last): TextError: Wrap mode 'somehow' not supported. """ if not self.layout.supports_wrap_mode(mode): raise TextError("Wrap mode %r not supported."%(mode,)) self._wrap_mode = mode self._invalidate() def set_layout(self, align, wrap, layout=None): """ Set the text layout object, alignment and wrapping modes at the same time. :type align: text alignment mode :param wrap: typically 'space', 'any' or 'clip' :type wrap: text wrapping mode :param layout: defaults to a shared :class:`StandardTextLayout` instance :type layout: text layout instance >>> t = Text(u"hi") >>> t.set_layout('right', 'clip') >>> t """ if layout is None: layout = text_layout.default_layout self._layout = layout self.set_align_mode(align) self.set_wrap_mode(wrap) align = property(lambda self:self._align_mode, set_align_mode) wrap = property(lambda self:self._wrap_mode, set_wrap_mode) layout = property(lambda self:self._layout) def render(self, size, focus=False): """ Render contents with wrapping and alignment. Return canvas. See :meth:`Widget.render` for parameter details. >>> Text(u"important things").render((18,)).text # ... = b in Python 3 [...'important things '] >>> Text(u"important things").render((11,)).text [...'important ', ...'things '] """ (maxcol,) = size text, attr = self.get_text() #assert isinstance(text, unicode) trans = self.get_line_translation( maxcol, (text,attr) ) return apply_text_layout(text, attr, trans, maxcol) def rows(self, size, focus=False): """ Return the number of rows the rendered text requires. See :meth:`Widget.rows` for parameter details. >>> Text(u"important things").rows((18,)) 1 >>> Text(u"important things").rows((11,)) 2 """ (maxcol,) = size return len(self.get_line_translation(maxcol)) def get_line_translation(self, maxcol, ta=None): """ Return layout structure used to map self.text to a canvas. This method is used internally, but may be useful for debugging custom layout classes. :param maxcol: columns available for display :type maxcol: int :param ta: ``None`` or the (*text*, *display attributes*) tuple returned from :meth:`.get_text` :type ta: text and display attributes """ if not self._cache_maxcol or self._cache_maxcol != maxcol: self._update_cache_translation(maxcol, ta) return self._cache_translation def _update_cache_translation(self,maxcol, ta): if ta: text, attr = ta else: text, attr = self.get_text() self._cache_maxcol = maxcol self._cache_translation = self._calc_line_translation( text, maxcol ) def _calc_line_translation(self, text, maxcol ): return self.layout.layout( text, self._cache_maxcol, self._align_mode, self._wrap_mode ) def pack(self, size=None, focus=False): """ Return the number of screen columns and rows required for this Text widget to be displayed without wrapping or clipping, as a single element tuple. :param size: ``None`` for unlimited screen columns or (*maxcol*,) to specify a maximum column size :type size: widget size >>> Text(u"important things").pack() (16, 1) >>> Text(u"important things").pack((15,)) (9, 2) >>> Text(u"important things").pack((8,)) (8, 2) """ text, attr = self.get_text() if size is not None: (maxcol,) = size if not hasattr(self.layout, "pack"): return size trans = self.get_line_translation( maxcol, (text,attr)) cols = self.layout.pack( maxcol, trans ) return (cols, len(trans)) i = 0 cols = 0 while i < len(text): j = text.find('\n', i) if j == -1: j = len(text) c = calc_width(text, i, j) if c>cols: cols = c i = j+1 return (cols, text.count('\n') + 1) class EditError(TextError): pass class Edit(Text): """ Text editing widget implements cursor movement, text insertion and 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``. """ # (this variable is picked up by the MetaSignals metaclass) signals = ["change"] def valid_char(self, ch): """ Filter for text that may be entered into this widget by the user :param ch: character to be inserted :type ch: bytes or unicode This implementation returns True for all printable characters. """ return is_wide_char(ch,0) or (len(ch)==1 and ord(ch) >= 32) def selectable(self): return True def __init__(self, caption=u"", edit_text=u"", multiline=False, align=LEFT, wrap=SPACE, allow_tab=False, edit_pos=None, layout=None, mask=None): """ :param caption: markup for caption preceeding 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) must match the text in the caption :type edit_text: bytes or unicode :param multiline: True: 'enter' inserts newline False: return it :type multiline: bool :param align: typically 'left', 'center' or 'right' :type align: text alignment mode :param wrap: typically 'space', 'any' or 'clip' :type wrap: text wrapping mode :param allow_tab: True: 'tab' inserts 1-8 spaces False: return it :type allow_tab: bool :param edit_pos: initial position for cursor, None:end of edit_text :type edit_pos: int :param layout: defaults to a shared :class:`StandardTextLayout` instance :type layout: text layout instance :param mask: hide text entered with this character, None:disable mask :type mask: bytes or unicode >>> Edit() >>> Edit(u"Y/n? ", u"yes") >>> Edit(u"Name ", u"Smith", edit_pos=1) >>> Edit(u"", u"3.14", align='right') """ self.__super.__init__("", align, wrap, layout) self.multiline = multiline self.allow_tab = allow_tab self._edit_pos = 0 self.set_caption(caption) self.set_edit_text(edit_text) if edit_pos is None: edit_pos = len(edit_text) self.set_edit_pos(edit_pos) self.set_mask(mask) self._shift_view_to_cursor = False def _repr_words(self): return self.__super._repr_words()[:-1] + [ python3_repr(self._edit_text)] + [ 'caption=' + python3_repr(self._caption)] * bool(self._caption) + [ 'multiline'] * (self.multiline is True) def _repr_attrs(self): attrs = dict(self.__super._repr_attrs(), edit_pos=self._edit_pos) return remove_defaults(attrs, Edit.__init__) def get_text(self): """ Returns ``(text, display attributes)``. See :meth:`Text.get_text` for details. Text returned includes the caption and edit_text, possibly masked. >>> Edit(u"What? ","oh, nothing.").get_text() # ... = u in Python 2 (...'What? oh, nothing.', []) >>> Edit(('bright',u"user@host:~$ "),"ls").get_text() (...'user@host:~$ ls', [('bright', 13)]) >>> Edit(u"password:", u"seekrit", mask=u"*").get_text() (...'password:*******', []) """ if self._mask is None: return self._caption + self._edit_text, self._attrib else: return self._caption + (self._mask * len(self._edit_text)), self._attrib def set_text(self, markup): """ Not supported by Edit widget. >>> Edit().set_text("test") Traceback (most recent call last): EditError: set_text() not supported. Use set_caption() or set_edit_text() instead. """ # FIXME: this smells. reimplement Edit as a WidgetWrap subclass to # clean this up # hack to let Text.__init__() work if not hasattr(self, '_text') and markup == "": self._text = None return raise EditError("set_text() not supported. Use set_caption()" " or set_edit_text() instead.") def get_pref_col(self, size): """ Return the preferred column for the cursor, or the current cursor x value. May also return ``'left'`` or ``'right'`` to indicate the leftmost or rightmost column available. This method is used internally and by other widgets when moving the cursor up or down between widgets so that the column selected is one that the user would expect. >>> size = (10,) >>> Edit().get_pref_col(size) 0 >>> e = Edit(u"", u"word") >>> e.get_pref_col(size) 4 >>> e.keypress(size, 'left') >>> e.get_pref_col(size) 3 >>> e.keypress(size, 'end') >>> e.get_pref_col(size) 'right' >>> e = Edit(u"", u"2\\nwords") >>> e.keypress(size, 'left') >>> e.keypress(size, 'up') >>> e.get_pref_col(size) 4 >>> e.keypress(size, 'left') >>> e.get_pref_col(size) 0 """ (maxcol,) = size pref_col, then_maxcol = self.pref_col_maxcol if then_maxcol != maxcol: return self.get_cursor_coords((maxcol,))[0] else: return pref_col def update_text(self): """ No longer supported. >>> Edit().update_text() Traceback (most recent call last): EditError: update_text() has been removed. Use set_caption() or set_edit_text() instead. """ raise EditError("update_text() has been removed. Use " "set_caption() or set_edit_text() instead.") def set_caption(self, caption): """ Set the caption markup for this widget. :param caption: markup for caption preceeding edit_text, see :meth:`Text.__init__` for description of text markup. >>> e = Edit("") >>> e.set_caption("cap1") >>> print e.caption cap1 >>> e.set_caption(('bold', "cap2")) >>> print e.caption cap2 >>> e.attrib [('bold', 4)] >>> e.caption = "cap3" # not supported because caption stores text but set_caption() takes markup Traceback (most recent call last): AttributeError: can't set attribute """ self._caption, self._attrib = decompose_tagmarkup(caption) self._invalidate() caption = property(lambda self:self._caption) def set_edit_pos(self, pos): """ Set the cursor position with a self.edit_text offset. Clips pos to [0, len(edit_text)]. :param pos: cursor position :type pos: int >>> e = Edit(u"", u"word") >>> e.edit_pos 4 >>> e.set_edit_pos(2) >>> e.edit_pos 2 >>> e.edit_pos = -1 # Urwid 0.9.9 or later >>> e.edit_pos 0 >>> e.edit_pos = 20 >>> e.edit_pos 4 """ if pos < 0: pos = 0 if pos > len(self._edit_text): pos = len(self._edit_text) self.highlight = None self.pref_col_maxcol = None, None self._edit_pos = pos self._invalidate() edit_pos = property(lambda self:self._edit_pos, set_edit_pos) def set_mask(self, mask): """ Set the character for masking text away. :param mask: hide text entered with this character, None:disable mask :type mask: bytes or unicode """ self._mask = mask self._invalidate() def set_edit_text(self, text): """ Set the edit text for this widget. :param text: text for editing, type (bytes or unicode) must match the text in the caption :type text: bytes or unicode >>> e = Edit() >>> e.set_edit_text(u"yes") >>> print e.edit_text yes >>> e >>> e.edit_text = u"no" # Urwid 0.9.9 or later >>> print e.edit_text no """ text = self._normalize_to_caption(text) self.highlight = None self._emit("change", text) self._edit_text = text if self.edit_pos > len(text): self.edit_pos = len(text) self._invalidate() def get_edit_text(self): """ Return the edit text for this widget. >>> e = Edit(u"What? ", u"oh, nothing.") >>> print e.get_edit_text() oh, nothing. >>> 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. """) def insert_text(self, text): """ Insert text at the cursor position and update cursor. This method is used by the keypress() method when inserting one or more characters into edit_text. :param text: text for inserting, type (bytes or unicode) must match the text in the caption :type text: bytes or unicode >>> e = Edit(u"", u"42") >>> e.insert_text(u".5") >>> e >>> e.set_edit_pos(2) >>> e.insert_text(u"a") >>> print e.edit_text 42a.5 """ text = self._normalize_to_caption(text) result_text, result_pos = self.insert_text_result(text) self.set_edit_text(result_text) self.set_edit_pos(result_pos) self.highlight = None def _normalize_to_caption(self, text): """ Return text converted to the same type as self.caption (bytes or unicode) """ tu = isinstance(text, unicode) cu = isinstance(self._caption, unicode) if tu == cu: return text if tu: return text.encode('ascii') # follow python2's implicit conversion return text.decode('ascii') def insert_text_result(self, text): """ Return result of insert_text(text) without actually performing the insertion. Handy for pre-validation. :param text: text for inserting, type (bytes or unicode) must match the text in the caption :type text: bytes or unicode """ # if there's highlighted text, it'll get replaced by the new text text = self._normalize_to_caption(text) if self.highlight: start, stop = self.highlight btext, etext = self.edit_text[:start], self.edit_text[stop:] result_text = btext + etext result_pos = start else: result_text = self.edit_text result_pos = self.edit_pos try: result_text = (result_text[:result_pos] + text + result_text[result_pos:]) except: assert 0, repr((self.edit_text, result_text, text)) result_pos += len(text) return (result_text, result_pos) def keypress(self, size, key): """ Handle editing keystrokes, return others. >>> e, size = Edit(), (20,) >>> e.keypress(size, 'x') >>> e.keypress(size, 'left') >>> e.keypress(size, '1') >>> print e.edit_text 1x >>> e.keypress(size, 'backspace') >>> e.keypress(size, 'end') >>> e.keypress(size, '2') >>> print e.edit_text x2 >>> e.keypress(size, 'shift f1') 'shift f1' """ (maxcol,) = size p = self.edit_pos if self.valid_char(key): if (isinstance(key, unicode) and not isinstance(self._caption, unicode)): # 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 key = key.encode('utf-8') self.insert_text(key) elif key=="tab" and self.allow_tab: key = " "*(8-(self.edit_pos%8)) self.insert_text(key) elif key=="enter" and self.multiline: key = "\n" self.insert_text(key) elif self._command_map[key] == CURSOR_LEFT: if p==0: return key p = move_prev_char(self.edit_text,0,p) self.set_edit_pos(p) elif self._command_map[key] == CURSOR_RIGHT: if p >= len(self.edit_text): return key p = move_next_char(self.edit_text,p,len(self.edit_text)) self.set_edit_pos(p) elif self._command_map[key] in (CURSOR_UP, CURSOR_DOWN): self.highlight = None x,y = self.get_cursor_coords((maxcol,)) pref_col = self.get_pref_col((maxcol,)) assert pref_col is not None #if pref_col is None: # pref_col = x if self._command_map[key] == CURSOR_UP: y -= 1 else: y += 1 if not self.move_cursor_to_coords((maxcol,),pref_col,y): return key elif key=="backspace": self.pref_col_maxcol = None, None if not self._delete_highlighted(): if p == 0: return key p = move_prev_char(self.edit_text,0,p) self.set_edit_text( self.edit_text[:p] + self.edit_text[self.edit_pos:] ) self.set_edit_pos( p ) elif key=="delete": self.pref_col_maxcol = None, None if not self._delete_highlighted(): if p >= len(self.edit_text): return key p = move_next_char(self.edit_text,p,len(self.edit_text)) self.set_edit_text( self.edit_text[:self.edit_pos] + self.edit_text[p:] ) elif self._command_map[key] in (CURSOR_MAX_LEFT, CURSOR_MAX_RIGHT): self.highlight = None self.pref_col_maxcol = None, None x,y = self.get_cursor_coords((maxcol,)) if self._command_map[key] == CURSOR_MAX_LEFT: self.move_cursor_to_coords((maxcol,), LEFT, y) else: self.move_cursor_to_coords((maxcol,), RIGHT, y) return else: # key wasn't handled return key def move_cursor_to_coords(self, size, x, y): """ Set the cursor position with (x,y) coordinates. Returns True if move succeeded, False otherwise. >>> size = (10,) >>> e = Edit("","edit\\ntext") >>> e.move_cursor_to_coords(size, 5, 0) True >>> e.edit_pos 4 >>> e.move_cursor_to_coords(size, 5, 3) False >>> e.move_cursor_to_coords(size, 0, 1) True >>> e.edit_pos 5 """ (maxcol,) = size trans = self.get_line_translation(maxcol) top_x, top_y = self.position_coords(maxcol, 0) if y < top_y or y >= len(trans): return False pos = calc_pos( self.get_text()[0], trans, x, y ) e_pos = pos - len(self.caption) if e_pos < 0: e_pos = 0 if e_pos > len(self.edit_text): e_pos = len(self.edit_text) self.edit_pos = e_pos self.pref_col_maxcol = x, maxcol self._invalidate() return True def mouse_event(self, size, event, button, x, y, focus): """ Move the cursor to the location clicked for button 1. >>> size = (20,) >>> e = Edit("","words here") >>> e.mouse_event(size, 'mouse press', 1, 2, 0, True) True >>> e.edit_pos 2 """ (maxcol,) = size if button==1: return self.move_cursor_to_coords( (maxcol,), x, y ) def _delete_highlighted(self): """ Delete all highlighted text and update cursor position, if any text is highlighted. """ if not self.highlight: return start, stop = self.highlight btext, etext = self.edit_text[:start], self.edit_text[stop:] self.set_edit_text( btext + etext ) self.edit_pos = start self.highlight = None return True def render(self, size, focus=False): """ Render edit widget and return canvas. Include cursor when in focus. >>> c = Edit("? ","yes").render((10,), focus=True) >>> c.text # ... = b in Python 3 [...'? yes '] >>> c.cursor (5, 0) """ (maxcol,) = size self._shift_view_to_cursor = bool(focus) canv = Text.render(self,(maxcol,)) if focus: canv = CompositeCanvas(canv) canv.cursor = self.get_cursor_coords((maxcol,)) # .. will need to FIXME if I want highlight to work again #if self.highlight: # hstart, hstop = self.highlight_coords() # d.coords['highlight'] = [ hstart, hstop ] return canv def get_line_translation(self, maxcol, ta=None ): trans = Text.get_line_translation(self, maxcol, ta) if not self._shift_view_to_cursor: return trans text, ignore = self.get_text() x,y = calc_coords( text, trans, self.edit_pos + len(self.caption) ) if x < 0: return ( trans[:y] + [shift_line(trans[y],-x)] + trans[y+1:] ) elif x >= maxcol: return ( trans[:y] + [shift_line(trans[y],-(x-maxcol+1))] + trans[y+1:] ) return trans def get_cursor_coords(self, size): """ Return the (*x*, *y*) coordinates of cursor within widget. >>> Edit("? ","yes").get_cursor_coords((10,)) (5, 0) """ (maxcol,) = size self._shift_view_to_cursor = True return self.position_coords(maxcol,self.edit_pos) def position_coords(self,maxcol,pos): """ Return (*x*, *y*) coordinates for an offset into self.edit_text. """ p = pos + len(self.caption) trans = self.get_line_translation(maxcol) x,y = calc_coords(self.get_text()[0], trans,p) return x,y class IntEdit(Edit): """Edit widget for integer values""" def valid_char(self, ch): """ Return true for decimal digits. """ return len(ch)==1 and ch in "0123456789" def __init__(self,caption="",default=None): """ caption -- caption markup default -- default edit value >>> IntEdit(u"", 42) """ if default is not None: val = str(default) else: val = "" self.__super.__init__(caption,val) def keypress(self, size, key): """ Handle editing keystrokes. Remove leading zeros. >>> e, size = IntEdit(u"", 5002), (10,) >>> e.keypress(size, 'home') >>> e.keypress(size, 'delete') >>> print e.edit_text 002 >>> e.keypress(size, 'end') >>> print e.edit_text 2 """ (maxcol,) = size unhandled = Edit.keypress(self,(maxcol,),key) if not unhandled: # trim leading zeros while self.edit_pos > 0 and self.edit_text[:1] == "0": self.set_edit_pos( self.edit_pos - 1) self.set_edit_text(self.edit_text[1:]) return unhandled def value(self): """ Return the numeric value of self.edit_text. >>> e, size = IntEdit(), (10,) >>> e.keypress(size, '5') >>> e.keypress(size, '1') >>> e.value() == 51 True """ if self.edit_text: return long(self.edit_text) else: return 0 def delegate_to_widget_mixin(attribute_name): """ Return a mixin class that delegates all standard widget methods to an attribute given by attribute_name. This mixin is designed to be used as a superclass of another widget. """ # FIXME: this is so common, let's add proper support for it # when layout and rendering are separated get_delegate = attrgetter(attribute_name) class DelegateToWidgetMixin(Widget): no_cache = ["rows"] # crufty metaclass work-around def render(self, size, focus=False): canv = get_delegate(self).render(size, focus=focus) return CompositeCanvas(canv) selectable = property(lambda self:get_delegate(self).selectable) get_cursor_coords = property( lambda self:get_delegate(self).get_cursor_coords) get_pref_col = property(lambda self:get_delegate(self).get_pref_col) keypress = property(lambda self:get_delegate(self).keypress) move_cursor_to_coords = property( lambda self:get_delegate(self).move_cursor_to_coords) rows = property(lambda self:get_delegate(self).rows) mouse_event = property(lambda self:get_delegate(self).mouse_event) sizing = property(lambda self:get_delegate(self).sizing) pack = property(lambda self:get_delegate(self).pack) return DelegateToWidgetMixin class WidgetWrapError(Exception): pass class WidgetWrap(delegate_to_widget_mixin('_wrapped_widget'), Widget): def __init__(self, w): """ w -- widget to wrap, stored as self._w This object will pass the functions defined in Widget interface definition to self._w. The purpose of this widget is to provide a base class for widgets that compose other widgets for their display and behaviour. The details of that composition should not affect users of the subclass. The subclass may decide to expose some of the wrapped widgets by behaving like a ContainerWidget or WidgetDecoration, or it may hide them from outside access. """ self._wrapped_widget = w def _set_w(self, w): """ Change the wrapped widget. This is meant to be called only by subclasses. >>> size = (10,) >>> ww = WidgetWrap(Edit("hello? ","hi")) >>> ww.render(size).text # ... = b in Python 3 [...'hello? hi '] >>> ww.selectable() True >>> ww._w = Text("goodbye") # calls _set_w() >>> ww.render(size).text [...'goodbye '] >>> ww.selectable() False """ self._wrapped_widget = w self._invalidate() _w = property(lambda self:self._wrapped_widget, _set_w) def _raise_old_name_error(self, val=None): raise WidgetWrapError("The WidgetWrap.w member variable has " "been renamed to WidgetWrap._w (not intended for use " "outside the class and its subclasses). " "Please update your code to use self._w " "instead of self.w.") w = property(_raise_old_name_error, _raise_old_name_error) def _test(): import doctest doctest.testmod() if __name__=='__main__': _test() urwid-1.1.1/urwid/raw_display.py0000664000175000017500000007335612051303575016247 0ustar ianian00000000000000#!/usr/bin/python # # Urwid raw display module # 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/ """ Direct terminal UI implementation """ import fcntl import termios import os import select import struct import sys import tty import signal from urwid import util from urwid import escape from urwid.display_common import BaseScreen, RealTerminal, \ UPDATE_PALETTE_ENTRY, AttrSpec, UNPRINTABLE_TRANS_TABLE, \ INPUT_DESCRIPTORS_CHANGED from urwid import signals from urwid.compat import PYTHON3, bytes, B from subprocess import Popen, PIPE class Screen(BaseScreen, RealTerminal): def __init__(self): """Initialize a screen that directly prints escape codes to an output terminal. """ super(Screen, self).__init__() self._pal_escape = {} signals.connect_signal(self, UPDATE_PALETTE_ENTRY, 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() self.screen_buf = None self._screen_buf_canvas = None self._resized = False self.maxrow = None self.gpm_mev = None self.gpm_event_pending = False self.last_bstate = 0 self._setup_G1_done = False self._rows_used = None self._cy = 0 self.bright_is_bold = os.environ.get('TERM',None) != "xterm" self._next_timeout = None self._term_output_file = sys.stdout self._term_input_file = sys.stdin # pipe for signalling external event loops about resize events self._resize_pipe_rd, self._resize_pipe_wr = os.pipe() fcntl.fcntl(self._resize_pipe_rd, fcntl.F_SETFL, os.O_NONBLOCK) def _on_update_palette_entry(self, name, *attrspecs): # copy the attribute to a dictionary containing the escape seqences self._pal_escape[name] = self._attrspec_to_escape( attrspecs[{16:0,1:1,88:2,256:3}[self.colors]]) def set_input_timeouts(self, max_wait=None, complete_wait=0.125, resize_wait=0.125): """ Set the get_input timeout values. All values are in floating point numbers of seconds. max_wait -- amount of time in seconds to wait for input when there is no input pending, wait forever if None complete_wait -- amount of time in seconds to wait when get_input detects an incomplete escape sequence at the end of the available input resize_wait -- amount of time in seconds to wait for more input after receiving two screen resize requests in a row to stop Urwid from consuming 100% cpu during a gradual window resize operation """ self.max_wait = max_wait if max_wait is not None: if self._next_timeout is None: self._next_timeout = max_wait else: self._next_timeout = min(self._next_timeout, self.max_wait) self.complete_wait = complete_wait self.resize_wait = resize_wait def _sigwinch_handler(self, signum, frame): if not self._resized: os.write(self._resize_pipe_wr, B('R')) self._resized = True self.screen_buf = None def signal_init(self): """ Called in the startup of run wrapper to set the SIGWINCH signal handler to self._sigwinch_handler. Override this function to call from main thread in threaded applications. """ signal.signal(signal.SIGWINCH, self._sigwinch_handler) def signal_restore(self): """ Called in the finally block of run wrapper to restore the SIGWINCH handler to the default handler. Override this function to call from main thread in threaded applications. """ signal.signal(signal.SIGWINCH, signal.SIG_DFL) def set_mouse_tracking(self): """ Enable mouse tracking. After calling this function get_input will include mouse click events along with keystrokes. """ self._term_output_file.write(escape.MOUSE_TRACKING_ON) self._start_gpm_tracking() def _start_gpm_tracking(self): if not os.path.isfile("/usr/bin/mev"): return if not os.environ.get('TERM',"").lower().startswith("linux"): return if not Popen: return m = Popen(["/usr/bin/mev","-e","158"], stdin=PIPE, stdout=PIPE, close_fds=True) fcntl.fcntl(m.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) self.gpm_mev = m def _stop_gpm_tracking(self): os.kill(self.gpm_mev.pid, signal.SIGINT) os.waitpid(self.gpm_mev.pid, 0) self.gpm_mev = None def start(self, alternate_buffer=True): """ Initialize the screen and input mode. alternate_buffer -- use alternate screen buffer """ assert not self._started if alternate_buffer: self._term_output_file.write(escape.SWITCH_TO_ALTERNATE_BUFFER) self._rows_used = None else: self._rows_used = 0 fd = self._term_input_file.fileno() if os.isatty(fd): self._old_termios_settings = termios.tcgetattr(fd) tty.setcbreak(fd) self.signal_init() self._alternate_buffer = alternate_buffer self._input_iter = self._run_input_iter() self._next_timeout = self.max_wait if not self._signal_keys_set: self._old_signal_keys = self.tty_signal_keys(fileno=fd) super(Screen, self).start() signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED) def stop(self): """ Restore the screen. """ self.clear() if not self._started: return signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED) self.signal_restore() fd = self._term_input_file.fileno() if os.isatty(fd): termios.tcsetattr(fd, termios.TCSADRAIN, self._old_termios_settings) move_cursor = "" if self.gpm_mev: self._stop_gpm_tracking() if self._alternate_buffer: move_cursor = escape.RESTORE_NORMAL_BUFFER elif self.maxrow is not None: move_cursor = escape.set_cursor_position( 0, self.maxrow) self._term_output_file.write(self._attrspec_to_escape(AttrSpec('','')) + escape.SI + escape.MOUSE_TRACKING_OFF + escape.SHOW_CURSOR + move_cursor + "\n" + escape.SHOW_CURSOR ) self._input_iter = self._fake_input_iter() if self._old_signal_keys: self.tty_signal_keys(*(self._old_signal_keys + (fd,))) super(Screen, self).stop() def run_wrapper(self, fn, alternate_buffer=True): """ Call start to initialize screen, then call fn. When fn exits call stop to restore the screen to normal. alternate_buffer -- use alternate screen buffer and restore normal screen buffer on exit """ try: self.start(alternate_buffer) return fn() finally: self.stop() def get_input(self, raw_keys=False): """Return pending input as a list. raw_keys -- return raw keycodes as well as translated versions This function will immediately return all the input since the last time it was called. If there is no input pending it will wait before returning an empty list. The wait time may be configured with the set_input_timeouts function. If raw_keys is False (default) this function will return a list of keys pressed. If raw_keys is True this function will return a ( keys pressed, raw keycodes ) tuple instead. Examples of keys returned: * ASCII printable characters: " ", "a", "0", "A", "-", "/" * ASCII control characters: "tab", "enter" * Escape sequences: "up", "page up", "home", "insert", "f1" * Key combinations: "shift f1", "meta a", "ctrl b" * Window events: "window resize" When a narrow encoding is not enabled: * "Extended ASCII" characters: "\\xa1", "\\xb2", "\\xfe" When a wide encoding is enabled: * Double-byte characters: "\\xa1\\xea", "\\xb2\\xd4" When utf8 encoding is enabled: * Unicode characters: u"\\u00a5", u'\\u253c" Examples of mouse events returned: * Mouse button press: ('mouse press', 1, 15, 13), ('meta mouse press', 2, 17, 23) * Mouse drag: ('mouse drag', 1, 16, 13), ('mouse drag', 1, 17, 13), ('ctrl mouse drag', 1, 18, 13) * Mouse button release: ('mouse release', 0, 18, 13), ('ctrl mouse release', 0, 17, 23) """ assert self._started self._wait_for_input_ready(self._next_timeout) self._next_timeout, keys, raw = self._input_iter.next() # Avoid pegging CPU at 100% when slowly resizing if keys==['window resize'] and self.prev_input_resize: while True: self._wait_for_input_ready(self.resize_wait) self._next_timeout, keys, raw2 = \ self._input_iter.next() raw += raw2 #if not keys: # keys, raw2 = self._get_input( # self.resize_wait) # raw += raw2 if keys!=['window resize']: break if keys[-1:]!=['window resize']: keys.append('window resize') if keys==['window resize']: self.prev_input_resize = 2 elif self.prev_input_resize == 2 and not keys: self.prev_input_resize = 1 else: self.prev_input_resize = 0 if raw_keys: return keys, raw return keys def get_input_descriptors(self): """ Return a list of integer file descriptors that should be polled in external event loops to check for user input. Use this method if you are implementing yout own event loop. """ if not self._started: return [] fd_list = [self._term_input_file.fileno(), self._resize_pipe_rd] if self.gpm_mev is not None: fd_list.append(self.gpm_mev.stdout.fileno()) return fd_list def get_input_nonblocking(self): """ Return a (next_input_timeout, keys_pressed, raw_keycodes) tuple. Use this method if you are implementing your own event loop. When there is input waiting on one of the descriptors returned by get_input_descriptors() this method should be called to read and process the input. This method expects to be called in next_input_timeout seconds (a floating point number) if there is no input waiting. """ return self._input_iter.next() def _run_input_iter(self): def empty_resize_pipe(): # clean out the pipe used to signal external event loops # that a resize has occured try: while True: os.read(self._resize_pipe_rd, 1) except OSError: pass while True: processed = [] codes = self._get_gpm_codes() + \ self._get_keyboard_codes() original_codes = codes try: while codes: run, codes = escape.process_keyqueue( codes, True) processed.extend(run) except escape.MoreInputRequired: k = len(original_codes) - len(codes) yield (self.complete_wait, processed, original_codes[:k]) empty_resize_pipe() original_codes = codes processed = [] codes += self._get_keyboard_codes() + \ self._get_gpm_codes() while codes: run, codes = escape.process_keyqueue( codes, False) processed.extend(run) if self._resized: processed.append('window resize') self._resized = False yield (self.max_wait, processed, original_codes) empty_resize_pipe() def _fake_input_iter(self): """ This generator is a placeholder for when the screen is stopped to always return that no input is available. """ while True: yield (self.max_wait, [], []) def _get_keyboard_codes(self): codes = [] while True: code = self._getch_nodelay() if code < 0: break codes.append(code) return codes def _get_gpm_codes(self): codes = [] try: while self.gpm_mev is not None and self.gpm_event_pending: codes.extend(self._encode_gpm_event()) except IOError, e: if e.args[0] != 11: raise return codes def _wait_for_input_ready(self, timeout): ready = None fd_list = [self._term_input_file.fileno()] if self.gpm_mev is not None: fd_list.append(self.gpm_mev.stdout.fileno()) while True: try: if timeout is None: ready,w,err = select.select( fd_list, [], fd_list) else: ready,w,err = select.select( fd_list,[],fd_list, timeout) break except select.error, e: if e.args[0] != 4: raise if self._resized: ready = [] break return ready def _getch(self, timeout): ready = self._wait_for_input_ready(timeout) if self.gpm_mev is not None: if self.gpm_mev.stdout.fileno() in ready: self.gpm_event_pending = True if self._term_input_file.fileno() in ready: return ord(os.read(self._term_input_file.fileno(), 1)) return -1 def _encode_gpm_event( self ): self.gpm_event_pending = False s = self.gpm_mev.stdout.readline().decode('ascii') l = s.split(",") if len(l) != 6: # unexpected output, stop tracking self._stop_gpm_tracking() signals.emit_signal(self, INPUT_DESCRIPTORS_CHANGED) return [] ev, x, y, ign, b, m = s.split(",") ev = int( ev.split("x")[-1], 16) x = int( x.split(" ")[-1] ) y = int( y.lstrip().split(" ")[0] ) b = int( b.split(" ")[-1] ) m = int( m.split("x")[-1].rstrip(), 16 ) # convert to xterm-like escape sequence last = next = self.last_bstate l = [] mod = 0 if m & 1: mod |= 4 # shift if m & 10: mod |= 8 # alt if m & 4: mod |= 16 # ctrl def append_button( b ): b |= mod l.extend([ 27, ord('['), ord('M'), b+32, x+32, y+32 ]) if ev == 20: # press if b & 4 and last & 1 == 0: append_button( 0 ) next |= 1 if b & 2 and last & 2 == 0: append_button( 1 ) next |= 2 if b & 1 and last & 4 == 0: append_button( 2 ) next |= 4 elif ev == 146: # drag if b & 4: append_button( 0 + escape.MOUSE_DRAG_FLAG ) elif b & 2: append_button( 1 + escape.MOUSE_DRAG_FLAG ) elif b & 1: append_button( 2 + escape.MOUSE_DRAG_FLAG ) else: # release if b & 4 and last & 1: append_button( 0 + escape.MOUSE_RELEASE_FLAG ) next &= ~ 1 if b & 2 and last & 2: append_button( 1 + escape.MOUSE_RELEASE_FLAG ) next &= ~ 2 if b & 1 and last & 4: append_button( 2 + escape.MOUSE_RELEASE_FLAG ) next &= ~ 4 self.last_bstate = next return l def _getch_nodelay(self): return self._getch(0) def get_cols_rows(self): """Return the terminal dimensions (num columns, num rows).""" buf = fcntl.ioctl(0, termios.TIOCGWINSZ, ' '*4) y, x = struct.unpack('hh', buf) self.maxrow = y return x, y def _setup_G1(self): """ Initialize the G1 character set to graphics mode if required. """ if self._setup_G1_done: return while True: try: self._term_output_file.write(escape.DESIGNATE_G1_SPECIAL) self._term_output_file.flush() break except IOError: pass self._setup_G1_done = True def draw_screen(self, (maxcol, maxrow), r ): """Paint screen with rendered canvas.""" assert self._started assert maxrow == r.rows() # quick return if nothing has changed if self.screen_buf and r is self._screen_buf_canvas: return self._setup_G1() if self._resized: # handle resize before trying to draw screen return o = [escape.HIDE_CURSOR, self._attrspec_to_escape(AttrSpec('',''))] def partial_display(): # returns True if the screen is in partial display mode # ie. only some rows belong to the display return self._rows_used is not None if not partial_display(): o.append(escape.CURSOR_HOME) if self.screen_buf: osb = self.screen_buf else: osb = [] sb = [] cy = self._cy y = -1 def set_cursor_home(): if not partial_display(): return escape.set_cursor_position(0, 0) return (escape.CURSOR_HOME_COL + escape.move_cursor_up(cy)) def set_cursor_row(y): if not partial_display(): return escape.set_cursor_position(0, y) return escape.move_cursor_down(y - cy) def set_cursor_position(x, y): if not partial_display(): return escape.set_cursor_position(x, y) if cy > y: return ('\b' + escape.CURSOR_HOME_COL + escape.move_cursor_up(cy - y) + escape.move_cursor_right(x)) return ('\b' + escape.CURSOR_HOME_COL + escape.move_cursor_down(y - cy) + escape.move_cursor_right(x)) def is_blank_row(row): if len(row) > 1: return False if row[0][2].strip(): return False return True def attr_to_escape(a): if a in self._pal_escape: return self._pal_escape[a] elif isinstance(a, AttrSpec): return self._attrspec_to_escape(a) # undefined attributes use default/default # TODO: track and report these return self._attrspec_to_escape( AttrSpec('default','default')) ins = None o.append(set_cursor_home()) cy = 0 for row in r.content(): y += 1 if False and osb and osb[y] == row: # this row of the screen buffer matches what is # currently displayed, so we can skip this line sb.append( osb[y] ) continue sb.append(row) # leave blank lines off display when we are using # the default screen buffer (allows partial screen) if partial_display() and y > self._rows_used: if is_blank_row(row): continue self._rows_used = y if y or partial_display(): o.append(set_cursor_position(0, y)) # after updating the line we will be just over the # edge, but terminals still treat this as being # on the same line cy = y whitespace_at_end = False if row and row[-1][2][-1:] == B(' '): whitespace_at_end = True a, cs, run = row[-1] row = row[:-1] + [(a, cs, run.rstrip(B(' ')))] elif y == maxrow-1 and maxcol>1: row, back, ins = self._last_row(row) first = True lasta = lastcs = None for (a,cs, run) in row: assert isinstance(run, bytes) # canvases should render with bytes if cs != 'U': run = run.translate(UNPRINTABLE_TRANS_TABLE) if first or lasta != a: o.append(attr_to_escape(a)) lasta = a if first or lastcs != cs: assert cs in [None, "0", "U"], repr(cs) if lastcs == "U": o.append( escape.IBMPC_OFF ) if cs is None: o.append( escape.SI ) elif cs == "U": o.append( escape.IBMPC_ON ) else: o.append( escape.SO ) lastcs = cs o.append( run ) first = False if ins: (inserta, insertcs, inserttext) = ins ias = attr_to_escape(inserta) assert insertcs in [None, "0", "U"], repr(insertcs) if cs is None: icss = escape.SI elif cs == "U": icss = escape.IBMPC_ON else: icss = escape.SO o += [ "\x08"*back, ias, icss, escape.INSERT_ON, inserttext, escape.INSERT_OFF ] if cs == "U": o.append(escape.IBMPC_OFF) if whitespace_at_end: o.append(escape.ERASE_IN_LINE_RIGHT) if r.cursor is not None: x,y = r.cursor o += [set_cursor_position(x, y), escape.SHOW_CURSOR ] self._cy = y if self._resized: # handle resize before trying to draw screen return try: for l in o: if isinstance(l, bytes) and PYTHON3: l = l.decode('utf-8') self._term_output_file.write(l) self._term_output_file.flush() except IOError, e: # ignore interrupted syscall if e.args[0] != 4: raise self.screen_buf = sb self._screen_buf_canvas = r def _last_row(self, row): """On the last row we need to slide the bottom right character into place. Calculate the new line, attr and an insert sequence to do that. eg. last row: XXXXXXXXXXXXXXXXXXXXYZ Y will be drawn after Z, shifting Z into position. """ new_row = row[:-1] z_attr, z_cs, last_text = row[-1] last_cols = util.calc_width(last_text, 0, len(last_text)) last_offs, z_col = util.calc_text_pos(last_text, 0, len(last_text), last_cols-1) if last_offs == 0: z_text = last_text del new_row[-1] # we need another segment y_attr, y_cs, nlast_text = row[-2] nlast_cols = util.calc_width(nlast_text, 0, len(nlast_text)) z_col += nlast_cols nlast_offs, y_col = util.calc_text_pos(nlast_text, 0, len(nlast_text), nlast_cols-1) y_text = nlast_text[nlast_offs:] if nlast_offs: new_row.append((y_attr, y_cs, nlast_text[:nlast_offs])) else: z_text = last_text[last_offs:] y_attr, y_cs = z_attr, z_cs nlast_cols = util.calc_width(last_text, 0, last_offs) nlast_offs, y_col = util.calc_text_pos(last_text, 0, last_offs, nlast_cols-1) y_text = last_text[nlast_offs:last_offs] if nlast_offs: new_row.append((y_attr, y_cs, last_text[:nlast_offs])) new_row.append((z_attr, z_cs, z_text)) return new_row, z_col-y_col, (y_attr, y_cs, y_text) def clear(self): """ Force the screen to be completely repainted on the next call to draw_screen(). """ self.screen_buf = None self.setup_G1 = True def _attrspec_to_escape(self, a): """ Convert AttrSpec instance a to an escape sequence for the terminal >>> s = Screen() >>> s.set_terminal_properties(colors=256) >>> a2e = s._attrspec_to_escape >>> a2e(s.AttrSpec('brown', 'dark green')) '\\x1b[0;33;42m' >>> a2e(s.AttrSpec('#fea,underline', '#d0d')) '\\x1b[0;38;5;229;4;48;5;164m' """ if a.foreground_high: fg = "38;5;%d" % a.foreground_number elif a.foreground_basic: if a.foreground_number > 7: if self.bright_is_bold: fg = "1;%d" % (a.foreground_number - 8 + 30) else: fg = "%d" % (a.foreground_number - 8 + 90) else: fg = "%d" % (a.foreground_number + 30) else: fg = "39" st = ("1;" * a.bold + "4;" * a.underline + "5;" * a.blink + "7;" * a.standout) if a.background_high: bg = "48;5;%d" % a.background_number elif a.background_basic: if a.background_number > 7: # this doesn't work on most terminals bg = "%d" % (a.background_number - 8 + 100) else: bg = "%d" % (a.background_number + 40) else: bg = "49" return escape.ESC + "[0;%s;%s%sm" % (fg, st, bg) def set_terminal_properties(self, colors=None, bright_is_bold=None, has_underline=None): """ colors -- number of colors terminal supports (1, 16, 88 or 256) or None to leave unchanged bright_is_bold -- set to True if this terminal uses the bold setting to create bright colors (numbers 8-15), set to False if this Terminal can create bright colors without bold or None to leave unchanged has_underline -- set to True if this terminal can use the underline setting, False if it cannot or None to leave unchanged """ if colors is None: colors = self.colors if bright_is_bold is None: bright_is_bold = self.bright_is_bold if has_underline is None: has_underline = self.has_underline if colors == self.colors and bright_is_bold == self.bright_is_bold \ and has_underline == self.has_underline: return self.colors = colors self.bright_is_bold = bright_is_bold self.has_underline = has_underline self.clear() self._pal_escape = {} for p,v in self._palette.items(): self._on_update_palette_entry(p, *v) def reset_default_terminal_palette(self): """ Attempt to set the terminal palette to default values as taken from xterm. Uses number of colors from current set_terminal_properties() screen setting. """ if self.colors == 1: return def rgb_values(n): if self.colors == 16: aspec = AttrSpec("h%d"%n, "", 256) else: aspec = AttrSpec("h%d"%n, "", self.colors) return aspec.get_rgb_values()[:3] entries = [(n,) + rgb_values(n) for n in range(self.colors)] self.modify_terminal_palette(entries) def modify_terminal_palette(self, entries): """ entries - list of (index, red, green, blue) tuples. Attempt to set part of the terminal pallette (this does not work on all terminals.) The changes are sent as a single escape sequence so they should all take effect at the same time. 0 <= index < 256 (some terminals will only have 16 or 88 colors) 0 <= red, green, blue < 256 """ modify = ["%d;rgb:%02x/%02x/%02x" % (index, red, green, blue) for index, red, green, blue in entries] self._term_output_file.write("\x1b]4;"+";".join(modify)+"\x1b\\") self._term_output_file.flush() # shortcut for creating an AttrSpec with this screen object's # number of colors AttrSpec = lambda self, fg, bg: AttrSpec(fg, bg, self.colors) def _test(): import doctest doctest.testmod() if __name__=='__main__': _test() urwid-1.1.1/urwid/decoration.py0000775000175000017500000012105612051303575016052 0ustar ianian00000000000000#!/usr/bin/python # # Urwid widget decoration classes # Copyright (C) 2004-2012 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: http://excess.org/urwid/ from urwid.util import int_scale from urwid.widget import (Widget, WidgetError, BOX, FLOW, LEFT, CENTER, RIGHT, PACK, CLIP, GIVEN, RELATIVE, RELATIVE_100, TOP, MIDDLE, BOTTOM, delegate_to_widget_mixin) from urwid.split_repr import remove_defaults from urwid.canvas import CompositeCanvas, SolidCanvas from urwid.widget import Divider, Edit, Text, SolidFill # doctests class WidgetDecoration(Widget): # "decorator" was already taken """ original_widget -- the widget being decorated This is a base class for decoration widgets, widgets that contain one or more widgets and only ever have a single focus. This type of widget will affect the display or behaviour of the original_widget but it is not part of determining a chain of focus. Don't actually do this -- use a WidgetDecoration subclass instead, these are not real widgets: >>> WidgetDecoration(Text(u"hi")) > """ def __init__(self, original_widget): self._original_widget = original_widget def _repr_words(self): return self.__super._repr_words() + [repr(self._original_widget)] def _get_original_widget(self): return self._original_widget def _set_original_widget(self, original_widget): self._original_widget = original_widget self._invalidate() original_widget = property(_get_original_widget, _set_original_widget) def _get_base_widget(self): """ Return the widget without decorations. If there is only one Decoration then this is the same as original_widget. >>> t = Text('hello') >>> wd1 = WidgetDecoration(t) >>> wd2 = WidgetDecoration(wd1) >>> wd3 = WidgetDecoration(wd2) >>> wd3.original_widget is wd2 True >>> wd3.base_widget is t True """ w = self while hasattr(w, '_original_widget'): w = w._original_widget return w base_widget = property(_get_base_widget) def selectable(self): return self._original_widget.selectable() def sizing(self): return self._original_widget.sizing() class WidgetPlaceholder(delegate_to_widget_mixin('_original_widget'), WidgetDecoration): """ This is a do-nothing decoration widget that can be used for swapping between widgets without modifying the container of this widget. This can be useful for making an interface with a number of distinct pages or for showing and hiding menu or status bars. The widget displayed is stored as the self.original_widget property and can be changed by assigning a new widget to it. """ pass class AttrMapError(WidgetError): pass class AttrMap(delegate_to_widget_mixin('_original_widget'), WidgetDecoration): """ AttrMap is a decoration that maps one set of attributes to another. This object will pass all function calls and variable references to the wrapped widget. """ def __init__(self, w, attr_map, focus_map=None): """ :param w: widget to wrap (stored as self.original_widget) :type w: widget :param attr_map: attribute to apply to *w*, or dict of old display attribute: new display attribute mappings :type attr_map: display attribute or dict :param focus_map: attribute to apply when in focus or dict of old display attribute: new display attribute mappings; if ``None`` use *attr* :type focus_map: display attribute or dict >>> AttrMap(Divider(u"!"), 'bright') attr_map={None: 'bright'}> >>> AttrMap(Edit(), 'notfocus', 'focus') 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 [('greeting', None, ...'hi ')] >>> am.render(size, focus=True).content().next() [('fgreet', None, ...'hi ')] >>> am2 = AttrMap(Text(('word', u"hi")), {'word':'greeting', None:'bg'}) >>> am2 attr_map={'word': 'greeting', None: 'bg'}> >>> am2.render(size).content().next() [('greeting', None, ...'hi'), ('bg', None, ...' ')] """ self.__super.__init__(w) if type(attr_map) != dict: self.set_attr_map({None: attr_map}) else: self.set_attr_map(attr_map) if focus_map is not None and type(focus_map) != dict: self.set_focus_map({None: focus_map}) else: self.set_focus_map(focus_map) def _repr_attrs(self): # only include the focus_attr when it takes effect (not None) d = dict(self.__super._repr_attrs(), attr_map=self._attr_map) if self._focus_map is not None: d['focus_map'] = self._focus_map return d def get_attr_map(self): # make a copy so ours is not accidentally modified # FIXME: a dictionary that detects modifications would be better return dict(self._attr_map) def set_attr_map(self, attr_map): """ Set the attribute mapping dictionary {from_attr: to_attr, ...} Note this function does not accept a single attribute the way the constructor does. You must specify {None: attribute} instead. >>> w = AttrMap(Text(u"hi"), None) >>> w.set_attr_map({'a':'b'}) >>> w attr_map={'a': 'b'}> """ for from_attr, to_attr in attr_map.items(): if not from_attr.__hash__ or not to_attr.__hash__: raise AttrMapError("%r:%r attribute mapping is invalid. " "Attributes must be hashable" % (from_attr, to_attr)) self._attr_map = attr_map self._invalidate() attr_map = property(get_attr_map, set_attr_map) def get_focus_map(self): # make a copy so ours is not accidentally modified # FIXME: a dictionary that detects modifications would be better if self._focus_map: return dict(self._focus_map) def set_focus_map(self, focus_map): """ Set the focus attribute mapping dictionary {from_attr: to_attr, ...} If None this widget will use the attr mapping instead (no change when in focus). Note this function does not accept a single attribute the way the constructor does. You must specify {None: attribute} instead. >>> w = AttrMap(Text(u"hi"), {}) >>> w.set_focus_map({'a':'b'}) >>> w attr_map={} focus_map={'a': 'b'}> >>> w.set_focus_map(None) >>> w attr_map={}> """ if focus_map is not None: for from_attr, to_attr in focus_map.items(): if not from_attr.__hash__ or not to_attr.__hash__: raise AttrMapError("%r:%r attribute mapping is invalid. " "Attributes must be hashable" % (from_attr, to_attr)) self._focus_map = focus_map self._invalidate() focus_map = property(get_focus_map, set_focus_map) def render(self, size, focus=False): """ Render wrapped widget and apply attribute. Return canvas. """ attr_map = self._attr_map if focus and self._focus_map is not None: attr_map = self._focus_map canv = self._original_widget.render(size, focus=focus) canv = CompositeCanvas(canv) canv.fill_attr_apply(attr_map) return canv class AttrWrap(AttrMap): def __init__(self, w, attr, focus_attr=None): """ w -- widget to wrap (stored as self.original_widget) attr -- attribute to apply to w focus_attr -- attribute to apply when in focus, if None use attr This widget is a special case of the new AttrMap widget, and it will pass all function calls and variable references to the wrapped widget. This class is maintained for backwards compatibility only, new code should use AttrMap instead. >>> AttrWrap(Divider(u"!"), 'bright') attr='bright'> >>> AttrWrap(Edit(), 'notfocus', 'focus') attr='notfocus' focus_attr='focus'> >>> size = (5,) >>> aw = AttrWrap(Text(u"hi"), 'greeting', 'fgreet') >>> aw.render(size, focus=False).content().next() [('greeting', None, ...'hi ')] >>> aw.render(size, focus=True).content().next() [('fgreet', None, ...'hi ')] """ self.__super.__init__(w, attr, focus_attr) def _repr_attrs(self): # only include the focus_attr when it takes effect (not None) d = dict(self.__super._repr_attrs(), attr=self.attr) del d['attr_map'] if 'focus_map' in d: del d['focus_map'] if self.focus_attr is not None: d['focus_attr'] = self.focus_attr return d # backwards compatibility, widget used to be stored as w get_w = WidgetDecoration._get_original_widget set_w = WidgetDecoration._set_original_widget w = property(get_w, set_w) def get_attr(self): return self.attr_map[None] def set_attr(self, attr): """ Set the attribute to apply to the wrapped widget >> w = AttrWrap(Divider("-"), None) >> w.set_attr('new_attr') >> w attr='new_attr'> """ self.set_attr_map({None: attr}) attr = property(get_attr, set_attr) def get_focus_attr(self): focus_map = self.focus_map if focus_map: return focus_map[None] def set_focus_attr(self, focus_attr): """ Set the attribute to apply to the wapped widget when it is in focus If None this widget will use the attr instead (no change when in focus). >> w = AttrWrap(Divider("-"), 'old') >> w.set_focus_attr('new_attr') >> w attr='old' focus_attr='new_attr'> >> w.set_focus_attr(None) >> w attr='old'> """ self.set_focus_map({None: focus_attr}) focus_attr = property(get_focus_attr, set_focus_attr) def __getattr__(self,name): """ Call getattr on wrapped widget. This has been the longstanding behaviour of AttrWrap, but is discouraged. New code should be using AttrMap and .base_widget or .original_widget instad. """ return getattr(self._original_widget, name) def sizing(self): return self._original_widget.sizing() class BoxAdapterError(Exception): pass class BoxAdapter(WidgetDecoration): """ Adapter for using a box widget where a flow widget would usually go """ no_cache = ["rows"] def __init__(self, box_widget, height): """ Create a flow widget that contains a box widget :param box_widget: box widget to wrap :type box_widget: Widget :param height: number of rows for box widget :type height: int >>> BoxAdapter(SolidFill(u"x"), 5) # 5-rows of x's height=5> """ if hasattr(box_widget, 'sizing') and BOX not in box_widget.sizing(): raise BoxAdapterError("%r is not a box widget" % box_widget) WidgetDecoration.__init__(self,box_widget) self.height = height def _repr_attrs(self): return dict(self.__super._repr_attrs(), height=self.height) # originally stored as box_widget, keep for compatibility box_widget = property(WidgetDecoration._get_original_widget, WidgetDecoration._set_original_widget) def sizing(self): return set([FLOW]) def rows(self, size, focus=False): """ Return the predetermined height (behave like a flow widget) >>> BoxAdapter(SolidFill(u"x"), 5).rows((20,)) 5 """ return self.height # The next few functions simply tack-on our height and pass through # to self._original_widget def get_cursor_coords(self, size): (maxcol,) = size if not hasattr(self._original_widget,'get_cursor_coords'): return None return self._original_widget.get_cursor_coords((maxcol, self.height)) def get_pref_col(self, size): (maxcol,) = size if not hasattr(self._original_widget,'get_pref_col'): return None return self._original_widget.get_pref_col((maxcol, self.height)) def keypress(self, size, key): (maxcol,) = size return self._original_widget.keypress((maxcol, self.height), key) def move_cursor_to_coords(self, size, col, row): (maxcol,) = size if not hasattr(self._original_widget,'move_cursor_to_coords'): return True return self._original_widget.move_cursor_to_coords((maxcol, self.height), col, row ) def mouse_event(self, size, event, button, col, row, focus): (maxcol,) = size if not hasattr(self._original_widget,'mouse_event'): return False return self._original_widget.mouse_event((maxcol, self.height), event, button, col, row, focus) def render(self, size, focus=False): (maxcol,) = size canv = self._original_widget.render((maxcol, self.height), focus) canv = CompositeCanvas(canv) return canv def __getattr__(self, name): """ Pass calls to box widget. """ return getattr(self.box_widget, name) class PaddingError(Exception): pass class Padding(WidgetDecoration): def __init__(self, w, align=LEFT, width=RELATIVE_100, min_width=None, left=0, right=0): """ :param w: a box, flow or fixed widget to pad on the left and/or right this widget is stored as self.original_widget :type w: Widget :param align: one of: ``'left'``, ``'center'``, ``'right'`` (``'relative'``, *percentage* 0=left 100=right) :param width: one of: *given width* integer number of columns for self.original_widget ``'pack'`` try to pack self.original_widget to its ideal size (``'relative'``, *percentage of total width*) make width depend on the container's width ``'clip'`` to enable clipping mode for a fixed widget :param min_width: the minimum number of columns for self.original_widget or ``None`` :type min_width: int :param left: a fixed number of columns to pad on the left :type left: int :param right: a fixed number of columns to pad on thr right :type right: int Clipping Mode: (width= ``'clip'``) In clipping mode this padding widget will behave as a flow widget and self.original_widget will be treated as a fixed widget. self.original_widget will will be clipped to fit the available number of columns. For example if align is ``'left'`` then self.original_widget may be clipped on the right. >>> size = (7,) >>> def pr(w): ... for t in w.render(size).text: ... print "|%s|" % (t.decode('ascii'),) >>> pr(Padding(Text(u"Head"), ('relative', 20), 'pack')) | Head | >>> pr(Padding(Divider(u"-"), left=2, right=1)) | ---- | >>> pr(Padding(Divider(u"*"), 'center', 3)) | *** | >>> p=Padding(Text(u"1234"), 'left', 2, None, 1, 1) >>> p left=1 right=1 width=2> >>> pr(p) # align against left | 12 | | 34 | >>> p.align = 'right' >>> pr(p) # align against right | 12 | | 34 | >>> pr(Padding(Text(u"hi\\nthere"), 'right', 'pack')) # pack text first | hi | | there| """ self.__super.__init__(w) # convert obsolete parameters 'fixed left' and 'fixed right': if type(align) == tuple and align[0] in ('fixed left', 'fixed right'): if align[0]=='fixed left': left = align[1] align = LEFT else: right = align[1] align = RIGHT if type(width) == tuple and width[0] in ('fixed left', 'fixed right'): if width[0]=='fixed left': left = width[1] else: right = width[1] width = RELATIVE_100 # convert old clipping mode width=None to width='clip' if width is None: width = CLIP self.left = left self.right = right self._align_type, self._align_amount = normalize_align(align, PaddingError) self._width_type, self._width_amount = normalize_width(width, PaddingError) self.min_width = min_width def sizing(self): if self._width_type == CLIP: return set([FLOW]) return self.original_widget.sizing() def _repr_attrs(self): attrs = dict(self.__super._repr_attrs(), align=self.align, width=self.width, left=self.left, right=self.right, min_width=self.min_width) return remove_defaults(attrs, Padding.__init__) def _get_align(self): """ Return the padding alignment setting. """ return simplify_align(self._align_type, self._align_amount) def _set_align(self, align): """ Set the padding alignment. """ self._align_type, self._align_amount = normalize_align(align, PaddingError) align = property(_get_align, _set_align) def _get_width(self): """ Return the padding widthment setting. """ return simplify_width(self._width_type, self._width_amount) def _set_width(self, width): """ Set the padding width. """ self._width_type, self._width_amount = normalize_width(width, PaddingError) width = property(_get_width, _set_width) def render(self, size, focus=False): left, right = self.padding_values(size, focus) maxcol = size[0] maxcol -= left+right if self._width_type == CLIP: canv = self._original_widget.render((), focus) else: canv = self._original_widget.render((maxcol,)+size[1:], focus) if canv.cols() == 0: canv = SolidCanvas(' ', size[0], canv.rows()) canv = CompositeCanvas(canv) canv.set_depends([self._original_widget]) return canv canv = CompositeCanvas(canv) canv.set_depends([self._original_widget]) if left != 0 or right != 0: canv.pad_trim_left_right(left, right) return canv def padding_values(self, size, focus): """Return the number of columns to pad on the left and right. Override this method to define custom padding behaviour.""" maxcol = size[0] if self._width_type == CLIP: width, ignore = self._original_widget.pack((), focus=focus) return calculate_left_right_padding(maxcol, self._align_type, self._align_amount, CLIP, width, None, self.left, self.right) if self._width_type == PACK: maxwidth = max(maxcol - self.left - self.right, self.min_width or 0) (width, ignore) = self._original_widget.pack((maxwidth,), focus=focus) return calculate_left_right_padding(maxcol, self._align_type, self._align_amount, GIVEN, width, self.min_width, self.left, self.right) return calculate_left_right_padding(maxcol, self._align_type, self._align_amount, self._width_type, self._width_amount, self.min_width, self.left, self.right) def rows(self, size, focus=False): """Return the rows needed for self.original_widget.""" (maxcol,) = size left, right = self.padding_values(size, focus) if self._width_type == PACK: pcols, prows = self._original_widget.pack((maxcol-left-right,), focus) return prows if self._width_type == CLIP: fcols, frows = self._original_widget.pack((), focus) return frows return self._original_widget.rows((maxcol-left-right,), focus=focus) def keypress(self, size, key): """Pass keypress to self._original_widget.""" maxcol = size[0] left, right = self.padding_values(size, True) maxvals = (maxcol-left-right,)+size[1:] return self._original_widget.keypress(maxvals, key) def get_cursor_coords(self,size): """Return the (x,y) coordinates of cursor within self._original_widget.""" if not hasattr(self._original_widget,'get_cursor_coords'): return None left, right = self.padding_values(size, True) maxcol = size[0] maxvals = (maxcol-left-right,)+size[1:] if maxvals[0] == 0: return None coords = self._original_widget.get_cursor_coords(maxvals) if coords is None: return None x, y = coords return x+left, y def move_cursor_to_coords(self, size, x, y): """Set the cursor position with (x,y) coordinates of self._original_widget. Returns True if move succeeded, False otherwise. """ if not hasattr(self._original_widget,'move_cursor_to_coords'): return True left, right = self.padding_values(size, True) maxcol = size[0] maxvals = (maxcol-left-right,)+size[1:] if type(x)==int: if x < left: x = left elif x >= maxcol-right: x = maxcol-right-1 x -= left return self._original_widget.move_cursor_to_coords(maxvals, x, y) def mouse_event(self, size, event, button, x, y, focus): """Send mouse event if position is within self._original_widget.""" if not hasattr(self._original_widget,'mouse_event'): return False left, right = self.padding_values(size, focus) maxcol = size[0] if x < left or x >= maxcol-right: return False maxvals = (maxcol-left-right,)+size[1:] return self._original_widget.mouse_event(maxvals, event, button, x-left, y, focus) def get_pref_col(self, size): """Return the preferred column from self._original_widget, or None.""" if not hasattr(self._original_widget,'get_pref_col'): return None left, right = self.padding_values(size, True) maxcol = size[0] maxvals = (maxcol-left-right,)+size[1:] x = self._original_widget.get_pref_col(maxvals) if type(x) == int: return x+left return x class FillerError(Exception): pass class Filler(WidgetDecoration): def __init__(self, body, valign=MIDDLE, height=PACK, min_height=None, top=0, bottom=0): """ :param body: a flow widget or box widget to be filled around (stored as self.original_widget) :type body: Widget :param valign: one of: ``'top'``, ``'middle'``, ``'bottom'``, (``'relative'``, *percentage* 0=top 100=bottom) :param height: one of: ``'pack'`` if body is a flow widget *given height* integer number of rows for self.original_widget (``'relative'``, *percentage of total height*) make height depend on container's height :param min_height: one of: ``None`` if no minimum or if body is a flow widget *minimum height* integer number of rows for the widget when height not fixed :param top: a fixed number of rows to fill at the top :type top: int :param bottom: a fixed number of rows to fill at the bottom :type bottom: int If body is a flow widget then height must be ``'flow'`` and *min_height* will be ignored. Filler widgets will try to satisfy height argument first by reducing the valign amount when necessary. If height still cannot be satisfied it will also be reduced. """ self.__super.__init__(body) # convert old parameters to the new top/bottom values 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") 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") bottom = height[1] height = RELATIVE_100 if isinstance(valign, tuple): if valign[0] == 'fixed top': top = valign[1] valign = TOP elif valign[0] == 'fixed bottom': bottom = valign[1] valign = BOTTOM # convert old flow mode parameter height=None to height='flow' if height is None or height == FLOW: height = PACK self.top = top self.bottom = bottom self.valign_type, self.valign_amount = normalize_valign(valign, FillerError) self.height_type, self.height_amount = normalize_height(height, FillerError) if self.height_type not in (GIVEN, PACK): self.min_height = min_height else: self.min_height = None def sizing(self): return set([BOX]) # always a box widget def _repr_attrs(self): attrs = dict(self.__super._repr_attrs(), valign=simplify_valign(self.valign_type, self.valign_amount), height=simplify_height(self.height_type, self.height_amount), top=self.top, bottom=self.bottom, min_height=self.min_height) return remove_defaults(attrs, Filler.__init__) # backwards compatibility, widget used to be stored as body get_body = WidgetDecoration._get_original_widget set_body = WidgetDecoration._set_original_widget body = property(get_body, set_body) def selectable(self): """Return selectable from body.""" return self._original_widget.selectable() def filler_values(self, size, focus): """ Return the number of rows to pad on the top and bottom. Override this method to define custom padding behaviour. """ (maxcol, maxrow) = size if self.height_type == PACK: height = self._original_widget.rows((maxcol,),focus=focus) return calculate_top_bottom_filler(maxrow, self.valign_type, self.valign_amount, GIVEN, height, None, self.top, self.bottom) return calculate_top_bottom_filler(maxrow, self.valign_type, self.valign_amount, self.height_type, self.height_amount, self.min_height, self.top, self.bottom) def render(self, size, focus=False): """Render self.original_widget with space above and/or below.""" (maxcol, maxrow) = size top, bottom = self.filler_values(size, focus) if self.height_type == PACK: canv = self._original_widget.render((maxcol,), focus) else: canv = self._original_widget.render((maxcol,maxrow-top-bottom),focus) canv = CompositeCanvas(canv) if maxrow and canv.rows() > maxrow and canv.cursor is not None: cx, cy = canv.cursor if cy >= maxrow: canv.trim(cy-maxrow+1,maxrow-top-bottom) if canv.rows() > maxrow: canv.trim(0, maxrow) return canv canv.pad_trim_top_bottom(top, bottom) return canv def keypress(self, size, key): """Pass keypress to self.original_widget.""" (maxcol, maxrow) = size if self.height_type == PACK: return self._original_widget.keypress((maxcol,), key) top, bottom = self.filler_values((maxcol,maxrow), True) return self._original_widget.keypress((maxcol,maxrow-top-bottom), key) def get_cursor_coords(self, size): """Return cursor coords from self.original_widget if any.""" (maxcol, maxrow) = size if not hasattr(self._original_widget, 'get_cursor_coords'): return None top, bottom = self.filler_values(size, True) if self.height_type == PACK: coords = self._original_widget.get_cursor_coords((maxcol,)) else: coords = self._original_widget.get_cursor_coords( (maxcol,maxrow-top-bottom)) if not coords: return None x, y = coords if y >= maxrow: y = maxrow-1 return x, y+top def get_pref_col(self, size): """Return pref_col from self.original_widget if any.""" (maxcol, maxrow) = size if not hasattr(self._original_widget, 'get_pref_col'): return None if self.height_type == PACK: x = self._original_widget.get_pref_col((maxcol,)) else: top, bottom = self.filler_values(size, True) x = self._original_widget.get_pref_col( (maxcol, maxrow-top-bottom)) return x def move_cursor_to_coords(self, size, col, row): """Pass to self.original_widget.""" (maxcol, maxrow) = size if not hasattr(self._original_widget, 'move_cursor_to_coords'): return True top, bottom = self.filler_values(size, True) if row < top or row >= maxcol-bottom: return False if self.height_type == PACK: return self._original_widget.move_cursor_to_coords((maxcol,), col, row-top) return self._original_widget.move_cursor_to_coords( (maxcol, maxrow-top-bottom), col, row-top) def mouse_event(self, size, event, button, col, row, focus): """Pass to self.original_widget.""" (maxcol, maxrow) = size if not hasattr(self._original_widget, 'mouse_event'): return False top, bottom = self.filler_values(size, True) if row < top or row >= maxrow-bottom: return False if self.height_type == PACK: return self._original_widget.mouse_event((maxcol,), event, button, col, row-top, focus) return self._original_widget.mouse_event((maxcol, maxrow-top-bottom), event, button,col, row-top, focus) class WidgetDisable(WidgetDecoration): """ A decoration widget that disables interaction with the widget it wraps. This widget always passes focus=False to the wrapped widget, even if it somehow does become the focus. """ no_cache = ["rows"] ignore_focus = True def selectable(self): return False def rows(self, size, focus=False): return self._original_widget.rows(size, False) def sizing(self): return self._original_widget.sizing() def pack(self, size, focus=False): return self._original_widget.pack(size, False) def render(self, size, focus=False): canv = self._original_widget.render(size, False) return CompositeCanvas(canv) def normalize_align(align, err): """ Split align into (align_type, align_amount). Raise exception err if align doesn't match a valid alignment. """ if align in (LEFT, CENTER, RIGHT): return (align, None) elif type(align) == tuple and len(align) == 2 and align[0] == RELATIVE: return align raise err("align value %r is not one of 'left', 'center', " "'right', ('relative', percentage 0=left 100=right)" % (align,)) def simplify_align(align_type, align_amount): """ Recombine (align_type, align_amount) into an align value. Inverse of normalize_align. """ if align_type == RELATIVE: return (align_type, align_amount) return align_type def normalize_width(width, err): """ Split width into (width_type, width_amount). Raise exception err if width doesn't match a valid alignment. """ if width in (CLIP, PACK): return (width, None) elif type(width) == int: return (GIVEN, width) elif type(width) == tuple and len(width) == 2 and width[0] == RELATIVE: return width raise err("width value %r is not one of fixed number of columns, " "'pack', ('relative', percentage of total width), 'clip'" % (width,)) def simplify_width(width_type, width_amount): """ Recombine (width_type, width_amount) into an width value. Inverse of normalize_width. """ if width_type in (CLIP, PACK): return width_type elif width_type == GIVEN: return width_amount return (width_type, width_amount) def normalize_valign(valign, err): """ Split align into (valign_type, valign_amount). Raise exception err if align doesn't match a valid alignment. """ if valign in (TOP, MIDDLE, BOTTOM): return (valign, None) elif (isinstance(valign, tuple) and len(valign) == 2 and valign[0] == RELATIVE): return valign raise err("valign value %r is not one of 'top', 'middle', " "'bottom', ('relative', percentage 0=left 100=right)" % (valign,)) def simplify_valign(valign_type, valign_amount): """ Recombine (valign_type, valign_amount) into an valign value. Inverse of normalize_valign. """ if valign_type == RELATIVE: return (valign_type, valign_amount) return valign_type def normalize_height(height, err): """ Split height into (height_type, height_amount). Raise exception err if height isn't valid. """ if height in (FLOW, PACK): return (height, None) elif (isinstance(height, tuple) and len(height) == 2 and height[0] == RELATIVE): return height elif isinstance(height, int): return (GIVEN, height) raise err("height value %r is not one of fixed number of columns, " "'pack', ('relative', percentage of total height)" % (height,)) def simplify_height(height_type, height_amount): """ Recombine (height_type, height_amount) into an height value. Inverse of normalize_height. """ if height_type in (FLOW, PACK): return height_type elif height_type == GIVEN: return height_amount return (height_type, height_amount) def calculate_top_bottom_filler(maxrow, valign_type, valign_amount, height_type, height_amount, min_height, top, bottom): """ Return the amount of filler (or clipping) on the top and bottom part of maxrow rows to satisfy the following: valign_type -- 'top', 'middle', 'bottom', 'relative' valign_amount -- a percentage when align_type=='relative' height_type -- 'given', 'relative', 'clip' height_amount -- a percentage when width_type=='relative' otherwise equal to the height of the widget min_height -- a desired minimum width for the widget or None top -- a fixed number of rows to fill on the top bottom -- a fixed number of rows to fill on the bottom >>> ctbf = calculate_top_bottom_filler >>> ctbf(15, 'top', 0, 'given', 10, None, 2, 0) (2, 3) >>> ctbf(15, 'relative', 0, 'given', 10, None, 2, 0) (2, 3) >>> ctbf(15, 'relative', 100, 'given', 10, None, 2, 0) (5, 0) >>> ctbf(15, 'middle', 0, 'given', 4, None, 2, 0) (6, 5) >>> ctbf(15, 'middle', 0, 'given', 18, None, 2, 0) (0, 0) >>> ctbf(20, 'top', 0, 'relative', 60, None, 0, 0) (0, 8) >>> ctbf(20, 'relative', 30, 'relative', 60, None, 0, 0) (2, 6) >>> ctbf(20, 'relative', 30, 'relative', 60, 14, 0, 0) (2, 4) """ if height_type == RELATIVE: maxheight = max(maxrow - top - bottom, 0) height = int_scale(height_amount, 101, maxheight + 1) if min_height is not None: height = max(height, min_height) else: height = height_amount standard_alignments = {TOP:0, MIDDLE:50, BOTTOM:100} valign = standard_alignments.get(valign_type, valign_amount) # add the remainder of top/bottom to the filler filler = maxrow - height - top - bottom bottom += int_scale(100 - valign, 101, filler + 1) top = maxrow - height - bottom # reduce filler if we are clipping an edge if bottom < 0 < top: shift = min(top, -bottom) top -= shift bottom += shift elif top < 0 < bottom: shift = min(bottom, -top) bottom -= shift top += shift # no negative values for filler at the moment top = max(top, 0) bottom = max(bottom, 0) return top, bottom def calculate_left_right_padding(maxcol, align_type, align_amount, width_type, width_amount, min_width, left, right): """ Return the amount of padding (or clipping) on the left and right part of maxcol columns to satisfy the following: align_type -- 'left', 'center', 'right', 'relative' align_amount -- a percentage when align_type=='relative' width_type -- 'fixed', 'relative', 'clip' width_amount -- a percentage when width_type=='relative' otherwise equal to the width of the widget min_width -- a desired minimum width for the widget or None left -- a fixed number of columns to pad on the left right -- a fixed number of columns to pad on the right >>> clrp = calculate_left_right_padding >>> clrp(15, 'left', 0, 'given', 10, None, 2, 0) (2, 3) >>> clrp(15, 'relative', 0, 'given', 10, None, 2, 0) (2, 3) >>> clrp(15, 'relative', 100, 'given', 10, None, 2, 0) (5, 0) >>> clrp(15, 'center', 0, 'given', 4, None, 2, 0) (6, 5) >>> clrp(15, 'left', 0, 'clip', 18, None, 0, 0) (0, -3) >>> clrp(15, 'right', 0, 'clip', 18, None, 0, -1) (-2, -1) >>> clrp(15, 'center', 0, 'given', 18, None, 2, 0) (0, 0) >>> clrp(20, 'left', 0, 'relative', 60, None, 0, 0) (0, 8) >>> clrp(20, 'relative', 30, 'relative', 60, None, 0, 0) (2, 6) >>> clrp(20, 'relative', 30, 'relative', 60, 14, 0, 0) (2, 4) """ if width_type == RELATIVE: maxwidth = max(maxcol - left - right, 0) width = int_scale(width_amount, 101, maxwidth + 1) if min_width is not None: width = max(width, min_width) else: width = width_amount standard_alignments = {LEFT:0, CENTER:50, RIGHT:100} align = standard_alignments.get(align_type, align_amount) # add the remainder of left/right the padding padding = maxcol - width - left - right right += int_scale(100 - align, 101, padding + 1) left = maxcol - width - right # reduce padding if we are clipping an edge if right < 0 and left > 0: shift = min(left, -right) left -= shift right += shift elif left < 0 and right > 0: shift = min(right, -left) right -= shift left += shift # only clip if width_type == 'clip' if width_type != CLIP and (left < 0 or right < 0): left = max(left, 0) right = max(right, 0) return left, right def _test(): import doctest doctest.testmod() if __name__=='__main__': _test() urwid-1.1.1/urwid/tests.py0000775000175000017500000032245512051303575015073 0ustar ianian00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Urwid unit testing .. ok, ok, ok # Copyright (C) 2004-2012 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: http://excess.org/urwid/ import unittest from doctest import DocTestSuite, ELLIPSIS, IGNORE_EXCEPTION_DETAIL import urwid from urwid.compat import bytes, B from urwid.vterm_test import TermTest from urwid.text_layout import calc_pos, calc_coords, CanNotDisplayText from urwid.canvas import (shard_body, shard_body_tail, shards_trim_top, shards_trim_sides, shards_join, shards_trim_rows, shard_body_row) from urwid.graphics import calculate_bargraph_display class DecodeOneTest(unittest.TestCase): def gwt(self, ch, exp_ord, exp_pos): ch = B(ch) o, pos = urwid.str_util.decode_one(ch,0) assert o==exp_ord, " got:%r expected:%r" % (o, exp_ord) assert pos==exp_pos, " got:%r expected:%r" % (pos, exp_pos) def test1byte(self): self.gwt("ab", ord("a"), 1) self.gwt("\xc0a", ord("?"), 1) # error def test2byte(self): self.gwt("\xc2", ord("?"), 1) # error self.gwt("\xc0\x80", ord("?"), 1) # error self.gwt("\xc2\x80", 0x80, 2) self.gwt("\xdf\xbf", 0x7ff, 2) def test3byte(self): self.gwt("\xe0", ord("?"), 1) # error self.gwt("\xe0\xa0", ord("?"), 1) # error self.gwt("\xe0\x90\x80", ord("?"), 1) # error self.gwt("\xe0\xa0\x80", 0x800, 3) self.gwt("\xef\xbf\xbf", 0xffff, 3) def test4byte(self): self.gwt("\xf0", ord("?"), 1) # error self.gwt("\xf0\x90", ord("?"), 1) # error self.gwt("\xf0\x90\x80", ord("?"), 1) # error self.gwt("\xf0\x80\x80\x80", ord("?"), 1) # error self.gwt("\xf0\x90\x80\x80", 0x10000, 4) self.gwt("\xf3\xbf\xbf\xbf", 0xfffff, 4) class CalcWidthTest(unittest.TestCase): def wtest(self, desc, s, exp): s = B(s) result = urwid.calc_width( s, 0, len(s)) assert result==exp, "%s got:%r expected:%r" % (desc, result, exp) def test1(self): urwid.set_encoding("utf-8") self.wtest("narrow", "hello", 5) self.wtest("wide char", '\xe6\x9b\xbf', 2) self.wtest("invalid", '\xe6', 1) self.wtest("zero width", '\xcc\x80', 0) self.wtest("mixed", 'hello\xe6\x9b\xbf\xe6\x9b\xbf', 9) def test2(self): urwid.set_encoding("euc-jp") self.wtest("narrow", "hello", 5) self.wtest("wide", "\xA1\xA1\xA1\xA1", 4) self.wtest("invalid", "\xA1", 1) class ConvertDecSpecialTest(unittest.TestCase): def ctest(self, desc, s, exp, expcs): exp = B(exp) urwid.set_encoding('ascii') c = urwid.Text(s).render((5,)) result = c._text[0] assert result==exp, "%s got:%r expected:%r" % (desc, result, exp) resultcs = c._cs[0] assert resultcs==expcs, "%s got:%r expected:%r" % (desc, resultcs, expcs) def test1(self): self.ctest("no conversion", u"hello", "hello", [(None,5)]) self.ctest("only special", u"£££££", "}}}}}", [("0",5)]) self.ctest("mix left", u"££abc", "}}abc", [("0",2),(None,3)]) self.ctest("mix right", u"abc££", "abc}}", [(None,3),("0",2)]) self.ctest("mix inner", u"a££bc", "a}}bc", [(None,1),("0",2),(None,2)] ) self.ctest("mix well", u"£a£b£", "}a}b}", [("0",1),(None,1),("0",1),(None,1),("0",1)] ) class WithinDoubleByteTest(unittest.TestCase): def setUp(self): urwid.set_encoding("euc-jp") def wtest(self, s, ls, pos, expected, desc): result = urwid.within_double_byte(B(s), ls, pos) assert result==expected, "%s got:%r expected: %r" % (desc, result, expected) def test1(self): self.wtest("mnopqr",0,2,0,'simple no high bytes') self.wtest("mn\xA1\xA1qr",0,2,1,'simple 1st half') self.wtest("mn\xA1\xA1qr",0,3,2,'simple 2nd half') self.wtest("m\xA1\xA1\xA1\xA1r",0,3,1,'subsequent 1st half') self.wtest("m\xA1\xA1\xA1\xA1r",0,4,2,'subsequent 2nd half') self.wtest("mn\xA1@qr",0,3,2,'simple 2nd half lo') self.wtest("mn\xA1\xA1@r",0,4,0,'subsequent not 2nd half lo') self.wtest("m\xA1\xA1\xA1@r",0,4,2,'subsequent 2nd half lo') def test2(self): self.wtest("\xA1\xA1qr",0,0,1,'begin 1st half') self.wtest("\xA1\xA1qr",0,1,2,'begin 2nd half') self.wtest("\xA1@qr",0,1,2,'begin 2nd half lo') self.wtest("\xA1\xA1\xA1\xA1r",0,2,1,'begin subs. 1st half') self.wtest("\xA1\xA1\xA1\xA1r",0,3,2,'begin subs. 2nd half') self.wtest("\xA1\xA1\xA1@r",0,3,2,'begin subs. 2nd half lo') self.wtest("\xA1@\xA1@r",0,3,2,'begin subs. 2nd half lo lo') self.wtest("@\xA1\xA1@r",0,3,0,'begin subs. not 2nd half lo') def test3(self): self.wtest("abc \xA1\xA1qr",4,4,1,'newline 1st half') self.wtest("abc \xA1\xA1qr",4,5,2,'newline 2nd half') self.wtest("abc \xA1@qr",4,5,2,'newline 2nd half lo') self.wtest("abc \xA1\xA1\xA1\xA1r",4,6,1,'newl subs. 1st half') self.wtest("abc \xA1\xA1\xA1\xA1r",4,7,2,'newl subs. 2nd half') self.wtest("abc \xA1\xA1\xA1@r",4,7,2,'newl subs. 2nd half lo') self.wtest("abc \xA1@\xA1@r",4,7,2,'newl subs. 2nd half lo lo') self.wtest("abc @\xA1\xA1@r",4,7,0,'newl subs. not 2nd half lo') class CalcTextPosTest(unittest.TestCase): def ctptest(self, text, tests): text = B(text) for s,e,p, expected in tests: got = urwid.calc_text_pos( text, s, e, p ) assert got == expected, "%r got:%r expected:%r" % ((s,e,p), got, expected) def test1(self): text = "hello world out there" tests = [ (0,21,0, (0,0)), (0,21,5, (5,5)), (0,21,21, (21,21)), (0,21,50, (21,21)), (2,15,50, (15,13)), (6,21,0, (6,0)), (6,21,3, (9,3)), ] self.ctptest(text, tests) def test2_wide(self): urwid.set_encoding("euc-jp") text = "hel\xA1\xA1 world out there" tests = [ (0,21,0, (0,0)), (0,21,4, (3,3)), (2,21,2, (3,1)), (2,21,3, (5,3)), (6,21,0, (6,0)), ] self.ctptest(text, tests) def test3_utf8(self): urwid.set_encoding("utf-8") text = "hel\xc4\x83 world \xe2\x81\x81 there" tests = [ (0,21,0, (0,0)), (0,21,4, (5,4)), (2,21,1, (3,1)), (2,21,2, (5,2)), (2,21,3, (6,3)), (6,21,7, (15,7)), (6,21,8, (16,8)), ] self.ctptest(text, tests) def test4_utf8(self): urwid.set_encoding("utf-8") text = "he\xcc\x80llo \xe6\x9b\xbf world" tests = [ (0,15,0, (0,0)), (0,15,1, (1,1)), (0,15,2, (4,2)), (0,15,4, (6,4)), (8,15,0, (8,0)), (8,15,1, (8,0)), (8,15,2, (11,2)), (8,15,5, (14,5)), ] self.ctptest(text, tests) class CalcBreaksTest(unittest.TestCase): def cbtest(self, width, exp): result = urwid.default_layout.calculate_text_segments( B(self.text), width, self.mode ) assert len(result) == len(exp), repr((result, exp)) for l,e in zip(result, exp): end = l[-1][-1] assert end == e, repr((result,exp)) def test(self): for width, exp in self.do: self.cbtest( width, exp ) class CalcBreaksCharTest(CalcBreaksTest): mode = 'any' text = "abfghsdjf askhtrvs\naltjhgsdf ljahtshgf" # tests do = [ ( 100, [18,38] ), ( 6, [6, 12, 18, 25, 31, 37, 38] ), ( 10, [10, 18, 29, 38] ), ] class CalcBreaksDBCharTest(CalcBreaksTest): def setUp(self): urwid.set_encoding("euc-jp") mode = 'any' text = "abfgh\xA1\xA1j\xA1\xA1xskhtrvs\naltjhgsdf\xA1\xA1jahtshgf" # tests do = [ ( 10, [10, 18, 28, 38] ), ( 6, [5, 11, 17, 18, 25, 31, 37, 38] ), ( 100, [18, 38]), ] class CalcBreaksWordTest(CalcBreaksTest): mode = 'space' text = "hello world\nout there. blah" # tests do = [ ( 10, [5, 11, 22, 27] ), ( 5, [5, 11, 17, 22, 27] ), ( 100, [11, 27] ), ] class CalcBreaksWordTest2(CalcBreaksTest): mode = 'space' text = "A simple set of words, really...." do = [ ( 10, [8, 15, 22, 33]), ( 17, [15, 33]), ( 13, [12, 22, 33]), ] class CalcBreaksDBWordTest(CalcBreaksTest): def setUp(self): urwid.set_encoding("euc-jp") mode = 'space' text = "hel\xA1\xA1 world\nout-\xA1\xA1tre blah" # tests do = [ ( 10, [5, 11, 21, 26] ), ( 5, [5, 11, 16, 21, 26] ), ( 100, [11, 26] ), ] class CalcBreaksUTF8Test(CalcBreaksTest): def setUp(self): urwid.set_encoding("utf-8") mode = 'space' text = '\xe6\x9b\xbf\xe6\xb4\xbc\xe6\xb8\x8e\xe6\xba\x8f\xe6\xbd\xba' do = [ (4, [6, 12, 15] ), (10, [15] ), (5, [6, 12, 15] ), ] class CalcBreaksCantDisplayTest(unittest.TestCase): def test(self): urwid.set_encoding("euc-jp") self.assertRaises(CanNotDisplayText, urwid.default_layout.calculate_text_segments, B('\xA1\xA1'), 1, 'space' ) urwid.set_encoding("utf-8") self.assertRaises(CanNotDisplayText, urwid.default_layout.calculate_text_segments, B('\xe9\xa2\x96'), 1, 'space' ) class SubsegTest(unittest.TestCase): def setUp(self): urwid.set_encoding("euc-jp") def st(self, seg, text, start, end, exp): text = B(text) s = urwid.LayoutSegment(seg) result = s.subseg( text, start, end ) assert result == exp, "Expected %r, got %r"%(exp,result) def test1_padding(self): self.st( (10, None), "", 0, 8, [(8, None)] ) self.st( (10, None), "", 2, 10, [(8, None)] ) self.st( (10, 0), "", 3, 7, [(4, 0)] ) self.st( (10, 0), "", 0, 20, [(10, 0)] ) def test2_text(self): self.st( (10, 0, B("1234567890")), "", 0, 8, [(8,0,B("12345678"))] ) self.st( (10, 0, B("1234567890")), "", 2, 10, [(8,0,B("34567890"))] ) self.st( (10, 0, B("12\xA1\xA156\xA1\xA190")), "", 2, 8, [(6, 0, B("\xA1\xA156\xA1\xA1"))] ) self.st( (10, 0, B("12\xA1\xA156\xA1\xA190")), "", 3, 8, [(5, 0, B(" 56\xA1\xA1"))] ) self.st( (10, 0, B("12\xA1\xA156\xA1\xA190")), "", 2, 7, [(5, 0, B("\xA1\xA156 "))] ) self.st( (10, 0, B("12\xA1\xA156\xA1\xA190")), "", 3, 7, [(4, 0, B(" 56 "))] ) self.st( (10, 0, B("12\xA1\xA156\xA1\xA190")), "", 0, 20, [(10, 0, B("12\xA1\xA156\xA1\xA190"))] ) def test3_range(self): t = "1234567890" self.st( (10, 0, 10), t, 0, 8, [(8, 0, 8)] ) self.st( (10, 0, 10), t, 2, 10, [(8, 2, 10)] ) self.st( (6, 2, 8), t, 1, 6, [(5, 3, 8)] ) self.st( (6, 2, 8), t, 0, 5, [(5, 2, 7)] ) self.st( (6, 2, 8), t, 1, 5, [(4, 3, 7)] ) t = "12\xA1\xA156\xA1\xA190" self.st( (10, 0, 10), t, 0, 8, [(8, 0, 8)] ) self.st( (10, 0, 10), t, 2, 10, [(8, 2, 10)] ) self.st( (6, 2, 8), t, 1, 6, [(1, 3), (4, 4, 8)] ) self.st( (6, 2, 8), t, 0, 5, [(4, 2, 6), (1, 6)] ) self.st( (6, 2, 8), t, 1, 5, [(1, 3), (2, 4, 6), (1, 6)] ) class CalcTranslateTest(unittest.TestCase): def setUp(self): urwid.set_encoding("utf-8") def test1_left(self): result = urwid.default_layout.layout( self.text, self.width, 'left', self.mode) assert result == self.result_left, result def test2_right(self): result = urwid.default_layout.layout( self.text, self.width, 'right', self.mode) assert result == self.result_right, result def test3_center(self): result = urwid.default_layout.layout( self.text, self.width, 'center', self.mode) assert result == self.result_center, result class CalcTranslateCharTest(CalcTranslateTest): text = "It's out of control!\nYou've got to" mode = 'any' width = 15 result_left = [ [(15, 0, 15)], [(5, 15, 20), (0, 20)], [(13, 21, 34), (0, 34)]] result_right = [ [(15, 0, 15)], [(10, None), (5, 15, 20), (0,20)], [(2, None), (13, 21, 34), (0,34)]] result_center = [ [(15, 0, 15)], [(5, None), (5, 15, 20), (0,20)], [(1, None), (13, 21, 34), (0,34)]] class CalcTranslateWordTest(CalcTranslateTest): text = "It's out of control!\nYou've got to" mode = 'space' width = 14 result_left = [ [(11, 0, 11), (0, 11)], [(8, 12, 20), (0, 20)], [(13, 21, 34), (0, 34)]] result_right = [ [(3, None), (11, 0, 11), (0, 11)], [(6, None), (8, 12, 20), (0, 20)], [(1, None), (13, 21, 34), (0, 34)]] result_center = [ [(2, None), (11, 0, 11), (0, 11)], [(3, None), (8, 12, 20), (0, 20)], [(1, None), (13, 21, 34), (0, 34)]] class CalcTranslateWordTest2(CalcTranslateTest): text = "It's out of control!\nYou've got to " mode = 'space' width = 14 result_left = [ [(11, 0, 11), (0, 11)], [(8, 12, 20), (0, 20)], [(14, 21, 35), (0, 35)]] result_right = [ [(3, None), (11, 0, 11), (0, 11)], [(6, None), (8, 12, 20), (0, 20)], [(14, 21, 35), (0, 35)]] result_center = [ [(2, None), (11, 0, 11), (0, 11)], [(3, None), (8, 12, 20), (0, 20)], [(14, 21, 35), (0, 35)]] class CalcTranslateWordTest3(CalcTranslateTest): def setUp(self): urwid.set_encoding('utf-8') text = B('\xe6\x9b\xbf\xe6\xb4\xbc\n\xe6\xb8\x8e\xe6\xba\x8f\xe6\xbd\xba') width = 10 mode = 'space' result_left = [ [(4, 0, 6), (0, 6)], [(6, 7, 16), (0, 16)]] result_right = [ [(6, None), (4, 0, 6), (0, 6)], [(4, None), (6, 7, 16), (0, 16)]] result_center = [ [(3, None), (4, 0, 6), (0, 6)], [(2, None), (6, 7, 16), (0, 16)]] class CalcTranslateWordTest4(CalcTranslateTest): text = ' Die Gedank' width = 3 mode = 'space' result_left = [ [(0, 0)], [(3, 1, 4), (0, 4)], [(3, 5, 8)], [(3, 8, 11), (0, 11)]] result_right = [ [(3, None), (0, 0)], [(3, 1, 4), (0, 4)], [(3, 5, 8)], [(3, 8, 11), (0, 11)]] result_center = [ [(2, None), (0, 0)], [(3, 1, 4), (0, 4)], [(3, 5, 8)], [(3, 8, 11), (0, 11)]] class CalcTranslateWordTest5(CalcTranslateTest): text = ' Word.' width = 3 mode = 'space' result_left = [[(3, 0, 3)], [(3, 3, 6), (0, 6)]] result_right = [[(3, 0, 3)], [(3, 3, 6), (0, 6)]] result_center = [[(3, 0, 3)], [(3, 3, 6), (0, 6)]] class CalcTranslateClipTest(CalcTranslateTest): text = "It's out of control!\nYou've got to\n\nturn it off!!!" mode = 'clip' width = 14 result_left = [ [(20, 0, 20), (0, 20)], [(13, 21, 34), (0, 34)], [(0, 35)], [(14, 36, 50), (0, 50)]] result_right = [ [(-6, None), (20, 0, 20), (0, 20)], [(1, None), (13, 21, 34), (0, 34)], [(14, None), (0, 35)], [(14, 36, 50), (0, 50)]] result_center = [ [(-3, None), (20, 0, 20), (0, 20)], [(1, None), (13, 21, 34), (0, 34)], [(7, None), (0, 35)], [(14, 36, 50), (0, 50)]] class CalcTranslateCantDisplayTest(CalcTranslateTest): text = B('Hello\xe9\xa2\x96') mode = 'space' width = 1 result_left = [[]] result_right = [[]] result_center = [[]] class CalcPosTest(unittest.TestCase): def setUp(self): self.text = "A" * 27 self.trans = [ [(2,None),(7,0,7),(0,7)], [(13,8,21),(0,21)], [(3,None),(5,22,27),(0,27)]] self.mytests = [(1,0, 0), (2,0, 0), (11,0, 7), (-3,1, 8), (-2,1, 8), (1,1, 9), (31,1, 21), (1,2, 22), (11,2, 27) ] def tests(self): for x,y, expected in self.mytests: got = calc_pos( self.text, self.trans, x, y ) assert got == expected, "%r got:%r expected:%r" % ((x, y), got, expected) class Pos2CoordsTest(unittest.TestCase): pos_list = [5, 9, 20, 26] text = "1234567890" * 3 mytests = [ ( [[(15,0,15)], [(15,15,30),(0,30)]], [(5,0),(9,0),(5,1),(11,1)] ), ( [[(9,0,9)], [(12,9,21)], [(9,21,30),(0,30)]], [(5,0),(0,1),(11,1),(5,2)] ), ( [[(2,None), (15,0,15)], [(2,None), (15,15,30),(0,30)]], [(7,0),(11,0),(7,1),(13,1)] ), ( [[(3, 6, 9),(0,9)], [(5, 20, 25),(0,25)]], [(0,0),(3,0),(0,1),(5,1)] ), ( [[(10, 0, 10),(0,10)]], [(5,0),(9,0),(10,0),(10,0)] ), ] def test(self): for t, answer in self.mytests: for pos,a in zip(self.pos_list,answer) : r = calc_coords( self.text, t, pos) assert r==a, "%r got: %r expected: %r"%(t,r,a) class CanvasCacheTest(unittest.TestCase): def setUp(self): # purge the cache urwid.CanvasCache._widgets.clear() def cct(self, widget, size, focus, expected): got = urwid.CanvasCache.fetch(widget, urwid.Widget, size, focus) assert expected==got, "got: %s expected: %s"%(got, expected) def test1(self): a = urwid.Text("") b = urwid.Text("") blah = urwid.TextCanvas() blah.finalize(a, (10,1), False) blah2 = urwid.TextCanvas() blah2.finalize(a, (15,1), False) bloo = urwid.TextCanvas() bloo.finalize(b, (20,2), True) urwid.CanvasCache.store(urwid.Widget, blah) urwid.CanvasCache.store(urwid.Widget, blah2) urwid.CanvasCache.store(urwid.Widget, bloo) self.cct(a, (10,1), False, blah) self.cct(a, (15,1), False, blah2) self.cct(a, (15,1), True, None) self.cct(a, (10,2), False, None) self.cct(b, (20,2), True, bloo) self.cct(b, (21,2), True, None) urwid.CanvasCache.invalidate(a) self.cct(a, (10,1), False, None) self.cct(a, (15,1), False, None) self.cct(b, (20,2), True, bloo) class CanvasTest(unittest.TestCase): def ct(self, text, attr, exp_content): c = urwid.TextCanvas([B(t) for t in text], attr) content = list(c.content()) assert content == exp_content, "got: %r expected: %r" % (content, exp_content) def ct2(self, text, attr, left, top, cols, rows, def_attr, exp_content): c = urwid.TextCanvas([B(t) for t in text], attr) content = list(c.content(left, top, cols, rows, def_attr)) assert content == exp_content, "got: %r expected: %r" % (content, exp_content) def test1(self): self.ct(["Hello world"], None, [[(None, None, B("Hello world"))]]) self.ct(["Hello world"], [[("a",5)]], [[("a", None, B("Hello")), (None, None, B(" world"))]]) self.ct(["Hi","There"], None, [[(None, None, B("Hi "))], [(None, None, B("There"))]]) def test2(self): self.ct2(["Hello"], None, 0, 0, 5, 1, None, [[(None, None, B("Hello"))]]) self.ct2(["Hello"], None, 1, 0, 4, 1, None, [[(None, None, B("ello"))]]) self.ct2(["Hello"], None, 0, 0, 4, 1, None, [[(None, None, B("Hell"))]]) self.ct2(["Hi","There"], None, 1, 0, 3, 2, None, [[(None, None, B("i "))], [(None, None, B("her"))]]) self.ct2(["Hi","There"], None, 0, 0, 5, 1, None, [[(None, None, B("Hi "))]]) self.ct2(["Hi","There"], None, 0, 1, 5, 1, None, [[(None, None, B("There"))]]) class ShardBodyTest(unittest.TestCase): def sbt(self, shards, shard_tail, expected): result = shard_body(shards, shard_tail, False) assert result == expected, "got: %r expected: %r" % (result, expected) def sbttail(self, num_rows, sbody, expected): result = shard_body_tail(num_rows, sbody) assert result == expected, "got: %r expected: %r" % (result, expected) def sbtrow(self, sbody, expected): result = list(shard_body_row(sbody)) assert result == expected, "got: %r expected: %r" % (result, expected) def test1(self): cviews = [(0,0,10,5,None,"foo"),(0,0,5,5,None,"bar")] self.sbt(cviews, [], [(0, None, (0,0,10,5,None,"foo")), (0, None, (0,0,5,5,None,"bar"))]) self.sbt(cviews, [(0, 3, None, (0,0,5,8,None,"baz"))], [(3, None, (0,0,5,8,None,"baz")), (0, None, (0,0,10,5,None,"foo")), (0, None, (0,0,5,5,None,"bar"))]) self.sbt(cviews, [(10, 3, None, (0,0,5,8,None,"baz"))], [(0, None, (0,0,10,5,None,"foo")), (3, None, (0,0,5,8,None,"baz")), (0, None, (0,0,5,5,None,"bar"))]) self.sbt(cviews, [(15, 3, None, (0,0,5,8,None,"baz"))], [(0, None, (0,0,10,5,None,"foo")), (0, None, (0,0,5,5,None,"bar")), (3, None, (0,0,5,8,None,"baz"))]) def test2(self): sbody = [(0, None, (0,0,10,5,None,"foo")), (0, None, (0,0,5,5,None,"bar")), (3, None, (0,0,5,8,None,"baz"))] self.sbttail(5, sbody, []) self.sbttail(3, sbody, [(0, 3, None, (0,0,10,5,None,"foo")), (0, 3, None, (0,0,5,5,None,"bar")), (0, 6, None, (0,0,5,8,None,"baz"))]) sbody = [(0, None, (0,0,10,3,None,"foo")), (0, None, (0,0,5,5,None,"bar")), (3, None, (0,0,5,9,None,"baz"))] self.sbttail(3, sbody, [(10, 3, None, (0,0,5,5,None,"bar")), (0, 6, None, (0,0,5,9,None,"baz"))]) def test3(self): self.sbtrow([(0, None, (0,0,10,5,None,"foo")), (0, None, (0,0,5,5,None,"bar")), (3, None, (0,0,5,8,None,"baz"))], [20]) self.sbtrow([(0, iter("foo"), (0,0,10,5,None,"foo")), (0, iter("bar"), (0,0,5,5,None,"bar")), (3, iter("zzz"), (0,0,5,8,None,"baz"))], ["f","b","z"]) class ShardsTrimTest(unittest.TestCase): def sttop(self, shards, top, expected): result = shards_trim_top(shards, top) assert result == expected, "got: %r expected: %r" (result, expected) def strows(self, shards, rows, expected): result = shards_trim_rows(shards, rows) assert result == expected, "got: %r expected: %r" (result, expected) def stsides(self, shards, left, cols, expected): result = shards_trim_sides(shards, left, cols) assert result == expected, "got: %r expected: %r" (result, expected) def test1(self): shards = [(5, [(0,0,10,5,None,"foo"),(0,0,5,5,None,"bar")])] self.sttop(shards, 2, [(3, [(0,2,10,3,None,"foo"),(0,2,5,3,None,"bar")])]) self.strows(shards, 2, [(2, [(0,0,10,2,None,"foo"),(0,0,5,2,None,"bar")])]) shards = [(5, [(0,0,10,5,None,"foo")]),(3,[(0,0,10,3,None,"bar")])] self.sttop(shards, 2, [(3, [(0,2,10,3,None,"foo")]),(3,[(0,0,10,3,None,"bar")])]) self.sttop(shards, 5, [(3, [(0,0,10,3,None,"bar")])]) self.sttop(shards, 7, [(1, [(0,2,10,1,None,"bar")])]) self.strows(shards, 7, [(5, [(0,0,10,5,None,"foo")]),(2, [(0,0,10,2,None,"bar")])]) self.strows(shards, 5, [(5, [(0,0,10,5,None,"foo")])]) self.strows(shards, 4, [(4, [(0,0,10,4,None,"foo")])]) shards = [(5, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz")]), (3,[(0,0,10,3,None,"bar")])] self.sttop(shards, 2, [(3, [(0,2,10,3,None,"foo"), (0,2,5,6,None,"baz")]), (3,[(0,0,10,3,None,"bar")])]) self.sttop(shards, 5, [(3, [(0,0,10,3,None,"bar"), (0,5,5,3,None,"baz")])]) self.sttop(shards, 7, [(1, [(0,2,10,1,None,"bar"), (0,7,5,1,None,"baz")])]) self.strows(shards, 7, [(5, [(0,0,10,5,None,"foo"), (0,0,5,7,None,"baz")]), (2, [(0,0,10,2,None,"bar")])]) self.strows(shards, 5, [(5, [(0,0,10,5,None,"foo"), (0,0,5,5,None,"baz")])]) self.strows(shards, 4, [(4, [(0,0,10,4,None,"foo"), (0,0,5,4,None,"baz")])]) def test2(self): shards = [(5, [(0,0,10,5,None,"foo"),(0,0,5,5,None,"bar")])] self.stsides(shards, 0, 15, [(5, [(0,0,10,5,None,"foo"),(0,0,5,5,None,"bar")])]) self.stsides(shards, 6, 9, [(5, [(6,0,4,5,None,"foo"),(0,0,5,5,None,"bar")])]) self.stsides(shards, 6, 6, [(5, [(6,0,4,5,None,"foo"),(0,0,2,5,None,"bar")])]) self.stsides(shards, 0, 10, [(5, [(0,0,10,5,None,"foo")])]) self.stsides(shards, 10, 5, [(5, [(0,0,5,5,None,"bar")])]) self.stsides(shards, 1, 7, [(5, [(1,0,7,5,None,"foo")])]) shards = [(5, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz")]), (3,[(0,0,10,3,None,"bar")])] self.stsides(shards, 0, 15, [(5, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz")]), (3,[(0,0,10,3,None,"bar")])]) self.stsides(shards, 2, 13, [(5, [(2,0,8,5,None,"foo"), (0,0,5,8,None,"baz")]), (3,[(2,0,8,3,None,"bar")])]) self.stsides(shards, 2, 10, [(5, [(2,0,8,5,None,"foo"), (0,0,2,8,None,"baz")]), (3,[(2,0,8,3,None,"bar")])]) self.stsides(shards, 2, 8, [(5, [(2,0,8,5,None,"foo")]), (3,[(2,0,8,3,None,"bar")])]) self.stsides(shards, 2, 6, [(5, [(2,0,6,5,None,"foo")]), (3,[(2,0,6,3,None,"bar")])]) self.stsides(shards, 10, 5, [(8, [(0,0,5,8,None,"baz")])]) self.stsides(shards, 11, 3, [(8, [(1,0,3,8,None,"baz")])]) class ShardsJoinTest(unittest.TestCase): def sjt(self, shard_lists, expected): result = shards_join(shard_lists) assert result == expected, "got: %r expected: %r" (result, expected) def test(self): shards1 = [(5, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz")]), (3,[(0,0,10,3,None,"bar")])] shards2 = [(3, [(0,0,10,3,None,"aaa")]), (5,[(0,0,10,5,None,"bbb")])] shards3 = [(3, [(0,0,10,3,None,"111")]), (2,[(0,0,10,3,None,"222")]), (3,[(0,0,10,3,None,"333")])] self.sjt([shards1], shards1) self.sjt([shards1, shards2], [(3, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz"), (0,0,10,3,None,"aaa")]), (2, [(0,0,10,5,None,"bbb")]), (3, [(0,0,10,3,None,"bar")])]) self.sjt([shards1, shards3], [(3, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz"), (0,0,10,3,None,"111")]), (2, [(0,0,10,3,None,"222")]), (3, [(0,0,10,3,None,"bar"), (0,0,10,3,None,"333")])]) self.sjt([shards1, shards2, shards3], [(3, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz"), (0,0,10,3,None,"aaa"), (0,0,10,3,None,"111")]), (2, [(0,0,10,5,None,"bbb"), (0,0,10,3,None,"222")]), (3, [(0,0,10,3,None,"bar"), (0,0,10,3,None,"333")])]) class TagMarkupTest(unittest.TestCase): mytests = [ ("simple one", "simple one", []), (('blue',"john"), "john", [('blue',4)]), (["a ","litt","le list"], "a little list", []), (["mix",('high',[" it ",('ital',"up a")])," little"], "mix it up a little", [(None,3),('high',4),('ital',4)]), ([u"££", u"x££"], u"££x££", []), ([B("\xc2\x80"), B("\xc2\x80")], B("\xc2\x80\xc2\x80"), []), ] def test(self): for input, text, attr in self.mytests: restext,resattr = urwid.decompose_tagmarkup( input ) assert restext == text, "got: %r expected: %r" % (restext, text) assert resattr == attr, "got: %r expected: %r" % (resattr, attr) def test_bad_tuple(self): self.assertRaises(urwid.TagMarkupException, lambda: urwid.decompose_tagmarkup((1,2,3))) def test_bad_type(self): self.assertRaises(urwid.TagMarkupException, lambda: urwid.decompose_tagmarkup(5)) class TextTest(unittest.TestCase): def setUp(self): 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 "] 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 "] 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"] 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"] got = self.t.render((18,))._text assert got == expected, "got: %r expected: %r" % (got, expected) class EditTest(unittest.TestCase): def setUp(self): self.t1 = urwid.Edit(B(""),"blah blah") self.t2 = urwid.Edit(B("stuff:"), "blah blah") self.t3 = urwid.Edit(B("junk:\n"),"blah blah\n\nbloo",1) self.t4 = urwid.Edit(u"better:") def ktest(self, e, key, expected, pos, desc): 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: " % ( desc, e.edit_pos, pos) def test1_left(self): self.t1.set_edit_pos(0) self.ktest(self.t1,'left','left',0,"left at left edge") self.ktest(self.t2,'left',None,8,"left within text") self.t3.set_edit_pos(10) self.ktest(self.t3,'left',None,9,"left after newline") def test2_right(self): self.ktest(self.t1,'right','right',9,"right at right edge") self.t2.set_edit_pos(8) self.ktest(self.t2,'right',None,9,"right at right edge-1") self.t3.set_edit_pos(0) self.t3.keypress((12,),'right') assert self.t3.get_pref_col((12,)) == 1 def test3_up(self): self.ktest(self.t1,'up','up',9,"up at top") self.t2.set_edit_pos(2) self.t2.keypress((12,),"left") assert self.t2.get_pref_col((12,)) == 7 self.ktest(self.t2,'up','up',1,"up at top again") assert self.t2.get_pref_col((12,)) == 7 self.t3.set_edit_pos(10) self.ktest(self.t3,'up',None,0,"up at top+1") def test4_down(self): self.ktest(self.t1,'down','down',9,"down single line") self.t3.set_edit_pos(5) self.ktest(self.t3,'down',None,10,"down line 1 to 2") self.ktest(self.t3,'down',None,15,"down line 2 to 3") self.ktest(self.t3,'down','down',15,"down at bottom") def test_utf8_input(self): urwid.set_encoding("utf-8") self.t1.set_edit_text('') self.t1.keypress((12,), u'û') self.assertEquals(self.t1.edit_text, u'û'.encode('utf-8')) self.t4.keypress((12,), u'û') self.assertEquals(self.t4.edit_text, u'û') class EditRenderTest(unittest.TestCase): def rtest(self, w, expected_text, expected_cursor): expected_text = [B(t) for t in expected_text] get_cursor = w.get_cursor_coords((4,)) assert get_cursor == expected_cursor, "got: %r expected: %r" % ( get_cursor, expected_cursor) r = w.render((4,), focus = 1) text = [t for a, cs, t in [ln[0] for ln in r.content()]] assert text == expected_text, "got: %r expected: %r" % (text, expected_text) assert r.cursor == expected_cursor, "got: %r expected: %r" % ( r.cursor, expected_cursor) def test1_SpaceWrap(self): w = urwid.Edit("","blah blah") w.set_edit_pos(0) self.rtest(w,["blah","blah"],(0,0)) w.set_edit_pos(4) self.rtest(w,["lah ","blah"],(3,0)) w.set_edit_pos(5) self.rtest(w,["blah","blah"],(0,1)) w.set_edit_pos(9) self.rtest(w,["blah","lah "],(3,1)) def test2_ClipWrap(self): w = urwid.Edit("","blah\nblargh",1) w.set_wrap_mode('clip') w.set_edit_pos(0) self.rtest(w,["blah","blar"],(0,0)) w.set_edit_pos(10) self.rtest(w,["blah","argh"],(3,1)) w.set_align_mode('right') w.set_edit_pos(6) self.rtest(w,["blah","larg"],(0,1)) def test3_AnyWrap(self): w = urwid.Edit("","blah blah") w.set_wrap_mode('any') self.rtest(w,["blah"," bla","h "],(1,2)) def test4_CursorNudge(self): w = urwid.Edit("","hi",align='right') w.keypress((4,),'end') self.rtest(w,[" hi "],(3,0)) w.keypress((4,),'left') self.rtest(w,[" hi"],(3,0)) class SelectableText(urwid.Text): def selectable(self): return 1 def keypress(self, size, key): return key class ListBoxCalculateVisibleTest(unittest.TestCase): def cvtest(self, desc, body, focus, offset_rows, inset_fraction, exp_offset_inset, exp_cur ): lbox = urwid.ListBox(body) lbox.body.set_focus( focus ) lbox.offset_rows = offset_rows lbox.inset_fraction = inset_fraction middle, top, bottom = lbox.calculate_visible((4,5),focus=1) offset_inset, focus_widget, focus_pos, _ign, cursor = middle if cursor is not None: x, y = cursor y += offset_inset cursor = x, y assert offset_inset == exp_offset_inset, "%s got: %r expected: %r" %(desc,offset_inset,exp_offset_inset) assert cursor == exp_cur, "%s (cursor) got: %r expected: %r" %(desc,cursor,exp_cur) def test1_simple(self): T = urwid.Text l = [T(""),T(""),T("\n"),T("\n\n"),T("\n"),T(""),T("")] self.cvtest( "simple top position", l, 3, 0, (0,1), 0, None ) self.cvtest( "simple middle position", l, 3, 1, (0,1), 1, None ) self.cvtest( "simple bottom postion", l, 3, 2, (0,1), 2, None ) self.cvtest( "straddle top edge", l, 3, 0, (1,2), -1, None ) self.cvtest( "straddle bottom edge", l, 3, 4, (0,1), 4, None ) self.cvtest( "off bottom edge", l, 3, 5, (0,1), 4, None ) self.cvtest( "way off bottom edge", l, 3, 100, (0,1), 4, None ) self.cvtest( "gap at top", l, 0, 2, (0,1), 0, None ) self.cvtest( "gap at top and off bottom edge", l, 2, 5, (0,1), 2, None ) self.cvtest( "gap at bottom", l, 6, 1, (0,1), 4, None ) self.cvtest( "gap at bottom and straddling top edge", l, 4, 0, (1,2), 1, None ) self.cvtest( "gap at bottom cannot completely fill", [T(""),T(""),T("")], 1, 0, (0,1), 1, None ) self.cvtest( "gap at top and bottom", [T(""),T(""),T("")], 1, 2, (0,1), 1, None ) def test2_cursor(self): T, E = urwid.Text, urwid.Edit l1 = [T(""),T(""),T("\n"),E("","\n\nX"),T("\n"),T(""),T("")] l2 = [T(""),T(""),T("\n"),E("","YY\n\n"),T("\n"),T(""),T("")] l2[3].set_edit_pos(2) self.cvtest( "plain cursor in view", l1, 3, 1, (0,1), 1, (1,3) ) self.cvtest( "cursor off top", l2, 3, 0, (1,3), 0, (2, 0) ) self.cvtest( "cursor further off top", l2, 3, 0, (2,3), 0, (2, 0) ) self.cvtest( "cursor off bottom", l1, 3, 3, (0,1), 2, (1, 4) ) self.cvtest( "cursor way off bottom", l1, 3, 100, (0,1), 2, (1, 4) ) class ListBoxChangeFocusTest(unittest.TestCase): def cftest(self, desc, body, pos, offset_inset, coming_from, cursor, snap_rows, exp_offset_rows, exp_inset_fraction, exp_cur ): lbox = urwid.ListBox(body) lbox.change_focus( (4,5), pos, offset_inset, coming_from, cursor, snap_rows ) exp = exp_offset_rows, exp_inset_fraction act = lbox.offset_rows, lbox.inset_fraction cursor = None focus_widget, focus_pos = lbox.body.get_focus() if focus_widget.selectable(): if hasattr(focus_widget,'get_cursor_coords'): cursor=focus_widget.get_cursor_coords((4,)) assert act == exp, "%s got: %s expected: %s" %(desc, act, exp) assert cursor == exp_cur, "%s (cursor) got: %r expected: %r" %(desc,cursor,exp_cur) def test1unselectable(self): T = urwid.Text l = [T("\n"),T("\n\n"),T("\n\n"),T("\n\n"),T("\n")] self.cftest( "simple unselectable", l, 2, 0, None, None, None, 0, (0,1), None ) self.cftest( "unselectable", l, 2, 1, None, None, None, 1, (0,1), None ) self.cftest( "unselectable off top", l, 2, -2, None, None, None, 0, (2,3), None ) self.cftest( "unselectable off bottom", l, 3, 2, None, None, None, 2, (0,1), None ) def test2selectable(self): T, S = urwid.Text, SelectableText l = [T("\n"),T("\n\n"),S("\n\n"),T("\n\n"),T("\n")] self.cftest( "simple selectable", l, 2, 0, None, None, None, 0, (0,1), None ) self.cftest( "selectable", l, 2, 1, None, None, None, 1, (0,1), None ) self.cftest( "selectable at top", l, 2, 0, 'below', None, None, 0, (0,1), None ) self.cftest( "selectable at bottom", l, 2, 2, 'above', None, None, 2, (0,1), None ) self.cftest( "selectable off top snap", l, 2, -1, 'below', None, None, 0, (0,1), None ) self.cftest( "selectable off bottom snap", l, 2, 3, 'above', None, None, 2, (0,1), None ) self.cftest( "selectable off top no snap", l, 2, -1, 'above', None, None, 0, (1,3), None ) self.cftest( "selectable off bottom no snap", l, 2, 3, 'below', None, None, 3, (0,1), None ) def test3large_selectable(self): T, S = urwid.Text, SelectableText l = [T("\n"),S("\n\n\n\n\n\n"),T("\n")] self.cftest( "large selectable no snap", l, 1, -1, None, None, None, 0, (1,7), None ) self.cftest( "large selectable snap up", l, 1, -2, 'below', None, None, 0, (0,1), None ) self.cftest( "large selectable snap up2", l, 1, -2, 'below', None, 2, 0, (0,1), None ) self.cftest( "large selectable almost snap up", l, 1, -2, 'below', None, 1, 0, (2,7), None ) self.cftest( "large selectable snap down", l, 1, 0, 'above', None, None, 0, (2,7), None ) self.cftest( "large selectable snap down2", l, 1, 0, 'above', None, 2, 0, (2,7), None ) self.cftest( "large selectable almost snap down", l, 1, 0, 'above', None, 1, 0, (0,1), None ) m = [T("\n\n\n\n"), S("\n\n\n\n\n"), T("\n\n\n\n")] self.cftest( "large selectable outside view down", m, 1, 4, 'above', None, None, 0, (0,1), None ) self.cftest( "large selectable outside view up", m, 1, -5, 'below', None, None, 0, (1,6), None ) def test4cursor(self): T,E = urwid.Text, urwid.Edit #... def test5set_focus_valign(self): T,E = urwid.Text, urwid.Edit lbox = urwid.ListBox(urwid.SimpleFocusListWalker([ T(''), T('')])) lbox.set_focus_valign('middle') # TODO: actually test the result class ListBoxRenderTest(unittest.TestCase): def ltest(self,desc,body,focus,offset_inset_rows,exp_text,exp_cur): exp_text = [B(t) for t in exp_text] lbox = urwid.ListBox(body) lbox.body.set_focus( focus ) lbox.shift_focus((4,10), offset_inset_rows ) canvas = lbox.render( (4,5), focus=1 ) text = [bytes().join([t for at, cs, t in ln]) for ln in canvas.content()] cursor = canvas.cursor assert text == exp_text, "%s (text) got: %r expected: %r" %(desc,text,exp_text) assert cursor == exp_cur, "%s (cursor) got: %r expected: %r" %(desc,cursor,exp_cur) def test1_simple(self): T = urwid.Text self.ltest( "simple one text item render", [T("1\n2")], 0, 0, ["1 ","2 "," "," "," "],None) self.ltest( "simple multi text item render off bottom", [T("1"),T("2"),T("3\n4"),T("5"),T("6")], 2, 2, ["1 ","2 ","3 ","4 ","5 "],None) self.ltest( "simple multi text item render off top", [T("1"),T("2"),T("3\n4"),T("5"),T("6")], 2, 1, ["2 ","3 ","4 ","5 ","6 "],None) def test2_trim(self): T = urwid.Text self.ltest( "trim unfocused bottom", [T("1\n2"),T("3\n4"),T("5\n6")], 1, 2, ["1 ","2 ","3 ","4 ","5 "],None) self.ltest( "trim unfocused top", [T("1\n2"),T("3\n4"),T("5\n6")], 1, 1, ["2 ","3 ","4 ","5 ","6 "],None) self.ltest( "trim none full focus", [T("1\n2\n3\n4\n5")], 0, 0, ["1 ","2 ","3 ","4 ","5 "],None) self.ltest( "trim focus bottom", [T("1\n2\n3\n4\n5\n6")], 0, 0, ["1 ","2 ","3 ","4 ","5 "],None) self.ltest( "trim focus top", [T("1\n2\n3\n4\n5\n6")], 0, -1, ["2 ","3 ","4 ","5 ","6 "],None) self.ltest( "trim focus top and bottom", [T("1\n2\n3\n4\n5\n6\n7")], 0, -1, ["2 ","3 ","4 ","5 ","6 "],None) def test3_shift(self): T,E = urwid.Text, urwid.Edit self.ltest( "shift up one fit", [T("1\n2"),T("3"),T("4"),T("5"),T("6")], 4, 5, ["2 ","3 ","4 ","5 ","6 "],None) e = E("","ab\nc",1) e.set_edit_pos( 2 ) self.ltest( "shift down one cursor over edge", [e,T("3"),T("4"),T("5\n6")], 0, -1, ["ab ","c ","3 ","4 ","5 "], (2,0)) self.ltest( "shift up one cursor over edge", [T("1\n2"),T("3"),T("4"),E("","d\ne")], 3, 4, ["2 ","3 ","4 ","d ","e "], (1,4)) self.ltest( "shift none cursor top focus over edge", [E("","ab\n"),T("3"),T("4"),T("5\n6")], 0, -1, [" ","3 ","4 ","5 ","6 "], (0,0)) e = E("","abc\nd") e.set_edit_pos( 3 ) self.ltest( "shift none cursor bottom focus over edge", [T("1\n2"),T("3"),T("4"),e], 3, 4, ["1 ","2 ","3 ","4 ","abc "], (3,4)) def test4_really_large_contents(self): T,E = urwid.Text, urwid.Edit self.ltest("really large edit", [T(u"hello"*100)], 0, 0, ["hell","ohel","lohe","lloh","ello"], None) self.ltest("really large edit", [E(u"", u"hello"*100)], 0, 0, ["hell","ohel","lohe","lloh","llo "], (3,4)) class ListBoxKeypressTest(unittest.TestCase): def ktest(self, desc, key, body, focus, offset_inset, exp_focus, exp_offset_inset, exp_cur, lbox = None): if lbox is None: lbox = urwid.ListBox(body) lbox.body.set_focus( focus ) lbox.shift_focus((4,10), offset_inset ) ret_key = lbox.keypress((4,5),key) middle, top, bottom = lbox.calculate_visible((4,5),focus=1) offset_inset, focus_widget, focus_pos, _ign, cursor = middle if cursor is not None: x, y = cursor y += offset_inset cursor = x, y exp = exp_focus, exp_offset_inset act = focus_pos, offset_inset assert act == exp, "%s got: %r expected: %r" %(desc,act,exp) assert cursor == exp_cur, "%s (cursor) got: %r expected: %r" %(desc,cursor,exp_cur) return ret_key,lbox def test1_up(self): T,S,E = urwid.Text, SelectableText, urwid.Edit self.ktest( "direct selectable both visible", 'up', [S(""),S("")], 1, 1, 0, 0, None ) self.ktest( "selectable skip one all visible", 'up', [S(""),T(""),S("")], 2, 2, 0, 0, None ) key,lbox = self.ktest( "nothing above no scroll", 'up', [S("")], 0, 0, 0, 0, None ) assert key == 'up' key, lbox = self.ktest( "unselectable above no scroll", 'up', [T(""),T(""),S("")], 2, 2, 2, 2, None ) assert key == 'up' self.ktest( "unselectable above scroll 1", 'up', [T(""),S(""),T("\n\n\n")], 1, 0, 1, 1, None ) self.ktest( "selectable above scroll 1", 'up', [S(""),S(""),T("\n\n\n")], 1, 0, 0, 0, None ) self.ktest( "selectable above too far", 'up', [S(""),T(""),S(""),T("\n\n\n")], 2, 0, 2, 1, None ) self.ktest( "selectable above skip 1 scroll 1", 'up', [S(""),T(""),S(""),T("\n\n\n")], 2, 1, 0, 0, None ) self.ktest( "tall selectable above scroll 2", 'up', [S(""),S("\n"),S(""),T("\n\n\n")], 2, 0, 1, 0, None ) self.ktest( "very tall selectable above scroll 5", 'up', [S(""),S("\n\n\n\n"),S(""),T("\n\n\n\n")], 2, 0, 1, 0, None ) self.ktest( "very tall selected scroll within 1", 'up', [S(""),S("\n\n\n\n\n")], 1, -1, 1, 0, None ) self.ktest( "edit above pass cursor", 'up', [E("","abc"),E("","de")], 1, 1, 0, 0, (2, 0) ) key,lbox = self.ktest( "edit too far above pass cursor A", 'up', [E("","abc"),T("\n\n\n\n"),E("","de")], 2, 4, 1, 0, None ) self.ktest( "edit too far above pass cursor B", 'up', None, None, None, 0, 0, (2,0), lbox ) self.ktest( "within focus cursor made not visible", 'up', [T("\n\n\n"),E("hi\n","ab")], 1, 3, 0, 0, None ) self.ktest( "within focus cursor made not visible (2)", 'up', [T("\n\n\n\n"),E("hi\n","ab")], 1, 3, 0, -1, None ) self.ktest( "force focus unselectable" , 'up', [T("\n\n\n\n"),S("")], 1, 4, 0, 0, None ) self.ktest( "pathological cursor widget", 'up', [T("\n"),E("\n\n\n\n\n","a")], 1, 4, 0, -1, None ) self.ktest( "unselectable to unselectable", 'up', [T(""),T(""),T(""),T(""),T(""),T(""),T("")], 2, 0, 1, 0, None ) self.ktest( "unselectable over edge to same", 'up', [T(""),T("12\n34"),T(""),T(""),T(""),T("")],1,-1, 1, 0, None ) key,lbox = self.ktest( "edit short between pass cursor A", 'up', [E("","abcd"),E("","a"),E("","def")], 2, 2, 1, 1, (1,1) ) self.ktest( "edit short between pass cursor B", 'up', None, None, None, 0, 0, (3,0), lbox ) e = E("","\n\n\n\n\n") e.set_edit_pos(1) key,lbox = self.ktest( "edit cursor force scroll", 'up', [e], 0, -1, 0, 0, (0,0) ) assert lbox.inset_fraction[0] == 0 def test2_down(self): T,S,E = urwid.Text, SelectableText, urwid.Edit self.ktest( "direct selectable both visible", 'down', [S(""),S("")], 0, 0, 1, 1, None ) self.ktest( "selectable skip one all visible", 'down', [S(""),T(""),S("")], 0, 0, 2, 2, None ) key,lbox = self.ktest( "nothing below no scroll", 'down', [S("")], 0, 0, 0, 0, None ) assert key == 'down' key, lbox = self.ktest( "unselectable below no scroll", 'down', [S(""),T(""),T("")], 0, 0, 0, 0, None ) assert key == 'down' self.ktest( "unselectable below scroll 1", 'down', [T("\n\n\n"),S(""),T("")], 1, 4, 1, 3, None ) self.ktest( "selectable below scroll 1", 'down', [T("\n\n\n"),S(""),S("")], 1, 4, 2, 4, None ) self.ktest( "selectable below too far", 'down', [T("\n\n\n"),S(""),T(""),S("")], 1, 4, 1, 3, None ) self.ktest( "selectable below skip 1 scroll 1", 'down', [T("\n\n\n"),S(""),T(""),S("")], 1, 3, 3, 4, None ) self.ktest( "tall selectable below scroll 2", 'down', [T("\n\n\n"),S(""),S("\n"),S("")], 1, 4, 2, 3, None ) self.ktest( "very tall selectable below scroll 5", 'down', [T("\n\n\n\n"),S(""),S("\n\n\n\n"),S("")], 1, 4, 2, 0, None ) self.ktest( "very tall selected scroll within 1", 'down', [S("\n\n\n\n\n"),S("")], 0, 0, 0, -1, None ) self.ktest( "edit below pass cursor", 'down', [E("","de"),E("","abc")], 0, 0, 1, 1, (2, 1) ) key,lbox=self.ktest( "edit too far below pass cursor A", 'down', [E("","de"),T("\n\n\n\n"),E("","abc")], 0, 0, 1, 0, None ) self.ktest( "edit too far below pass cursor B", 'down', None, None, None, 2, 4, (2,4), lbox ) odd_e = E("","hi\nab") odd_e.set_edit_pos( 2 ) # disble cursor movement in odd_e object odd_e.move_cursor_to_coords = lambda s,c,xy: 0 self.ktest( "within focus cursor made not visible", 'down', [odd_e,T("\n\n\n\n")], 0, 0, 1, 1, None ) self.ktest( "within focus cursor made not visible (2)", 'down', [odd_e,T("\n\n\n\n"),], 0, 0, 1, 1, None ) self.ktest( "force focus unselectable" , 'down', [S(""),T("\n\n\n\n")], 0, 0, 1, 0, None ) odd_e.set_edit_text( "hi\n\n\n\n\n" ) self.ktest( "pathological cursor widget", 'down', [odd_e,T("\n")], 0, 0, 1, 4, None ) self.ktest( "unselectable to unselectable", 'down', [T(""),T(""),T(""),T(""),T(""),T(""),T("")], 4, 4, 5, 4, None ) self.ktest( "unselectable over edge to same", 'down', [T(""),T(""),T(""),T(""),T("12\n34"),T("")],4,4, 4, 3, None ) key,lbox=self.ktest( "edit short between pass cursor A", 'down', [E("","abc"),E("","a"),E("","defg")], 0, 0, 1, 1, (1,1) ) self.ktest( "edit short between pass cursor B", 'down', None, None, None, 2, 2, (3,2), lbox ) e = E("","\n\n\n\n\n") e.set_edit_pos(4) key,lbox = self.ktest( "edit cursor force scroll", 'down', [e], 0, 0, 0, -1, (0,4) ) assert lbox.inset_fraction[0] == 1 def test3_page_up(self): T,S,E = urwid.Text, SelectableText, urwid.Edit self.ktest( "unselectable aligned to aligned", 'page up', [T(""),T("\n"),T("\n\n"),T(""),T("\n"),T("\n\n")], 3, 0, 1, 0, None ) self.ktest( "unselectable unaligned to aligned", 'page up', [T(""),T("\n"),T("\n"),T("\n"),T("\n"),T("\n\n")], 3,-1, 1, 0, None ) self.ktest( "selectable to unselectable", 'page up', [T(""),T("\n"),T("\n"),T("\n"),S("\n"),T("\n\n")], 4, 1, 1, -1, None ) self.ktest( "selectable to cut off selectable", 'page up', [S("\n\n"),T("\n"),T("\n"),S("\n"),T("\n\n")], 3, 1, 0, -1, None ) self.ktest( "seletable to selectable", 'page up', [T("\n\n"),S("\n"),T("\n"),S("\n"),T("\n\n")], 3, 1, 1, 1, None ) self.ktest( "within very long selectable", 'page up', [S(""),S("\n\n\n\n\n\n\n\n"),T("\n")], 1, -6, 1, -1, None ) e = E("","\n\nab\n\n\n\n\ncd\n") e.set_edit_pos(11) self.ktest( "within very long cursor widget", 'page up', [S(""),e,T("\n")], 1, -6, 1, -2, (2, 0) ) self.ktest( "pathological cursor widget", 'page up', [T(""),E("\n\n\n\n\n\n\n\n","ab"),T("")], 1, -5, 0, 0, None ) e = E("","\nab\n\n\n\n\ncd\n") e.set_edit_pos(10) self.ktest( "very long cursor widget snap", 'page up', [T(""),e,T("\n")], 1, -5, 1, 0, (2, 1) ) self.ktest( "slight scroll selectable", 'page up', [T("\n"),S("\n"),T(""),S(""),T("\n\n\n"),S("")], 5, 4, 3, 0, None ) self.ktest( "scroll into snap region", 'page up', [T("\n"),S("\n"),T(""),T(""),T("\n\n\n"),S("")], 5, 4, 1, 0, None ) self.ktest( "mid scroll short", 'page up', [T("\n"),T(""),T(""),S(""),T(""),T("\n"),S(""),T("\n")], 6, 2, 3, 1, None ) self.ktest( "mid scroll long", 'page up', [T("\n"),S(""),T(""),S(""),T(""),T("\n"),S(""),T("\n")], 6, 2, 1, 0, None ) self.ktest( "mid scroll perfect", 'page up', [T("\n"),S(""),S(""),S(""),T(""),T("\n"),S(""),T("\n")], 6, 2, 2, 0, None ) self.ktest( "cursor move up fail short", 'page up', [T("\n"),T("\n"),E("","\nab"),T(""),T("")], 2, 1, 2, 4, (0, 4) ) self.ktest( "cursor force fail short", 'page up', [T("\n"),T("\n"),E("\n","ab"),T(""),T("")], 2, 1, 0, 0, None ) odd_e = E("","hi\nab") odd_e.set_edit_pos( 2 ) # disble cursor movement in odd_e object odd_e.move_cursor_to_coords = lambda s,c,xy: 0 self.ktest( "cursor force fail long", 'page up', [odd_e,T("\n"),T("\n"),T("\n"),S(""),T("\n")], 4, 2, 1, -1, None ) self.ktest( "prefer not cut off", 'page up', [S("\n"),T("\n"),S(""),T("\n\n"),S(""),T("\n")], 4, 2, 2, 1, None ) self.ktest( "allow cut off", 'page up', [S("\n"),T("\n"),T(""),T("\n\n"),S(""),T("\n")], 4, 2, 0, -1, None ) self.ktest( "at top fail", 'page up', [T("\n\n"),T("\n"),T("\n\n\n")], 0, 0, 0, 0, None ) self.ktest( "all visible fail", 'page up', [T("a"),T("\n")], 0, 0, 0, 0, None ) self.ktest( "current ok fail", 'page up', [T("\n\n"),S("hi")], 1, 3, 1, 3, None ) self.ktest( "all visible choose top selectable", 'page up', [T(""),S("a"),S("b"),S("c")], 3, 3, 1, 1, None ) self.ktest( "bring in edge choose top", 'page up', [S("b"),T("-"),S("-"),T("c"),S("d"),T("-")],4,3, 0, 0, None ) self.ktest( "bring in edge choose top selectable", 'page up', [T("b"),S("-"),S("-"),T("c"),S("d"),T("-")],4,3, 1, 1, None ) def test4_page_down(self): T,S,E = urwid.Text, SelectableText, urwid.Edit self.ktest( "unselectable aligned to aligned", 'page down', [T("\n\n"),T("\n"),T(""),T("\n\n"),T("\n"),T("")], 2, 4, 4, 3, None ) self.ktest( "unselectable unaligned to aligned", 'page down', [T("\n\n"),T("\n"),T("\n"),T("\n"),T("\n"),T("")], 2, 4, 4, 3, None ) self.ktest( "selectable to unselectable", 'page down', [T("\n\n"),S("\n"),T("\n"),T("\n"),T("\n"),T("")], 1, 2, 4, 4, None ) self.ktest( "selectable to cut off selectable", 'page down', [T("\n\n"),S("\n"),T("\n"),T("\n"),S("\n\n")], 1, 2, 4, 3, None ) self.ktest( "seletable to selectable", 'page down', [T("\n\n"),S("\n"),T("\n"),S("\n"),T("\n\n")], 1, 1, 3, 2, None ) self.ktest( "within very long selectable", 'page down', [T("\n"),S("\n\n\n\n\n\n\n\n"),S("")], 1, 2, 1, -3, None ) e = E("","\nab\n\n\n\n\ncd\n\n") e.set_edit_pos(2) self.ktest( "within very long cursor widget", 'page down', [T("\n"),e,S("")], 1, 2, 1, -2, (1, 4) ) odd_e = E("","ab\n\n\n\n\n\n\n\n\n") odd_e.set_edit_pos( 1 ) # disble cursor movement in odd_e object odd_e.move_cursor_to_coords = lambda s,c,xy: 0 self.ktest( "pathological cursor widget", 'page down', [T(""),odd_e,T("")], 1, 1, 2, 4, None ) e = E("","\nab\n\n\n\n\ncd\n") e.set_edit_pos(2) self.ktest( "very long cursor widget snap", 'page down', [T("\n"),e,T("")], 1, 2, 1, -3, (1, 3) ) self.ktest( "slight scroll selectable", 'page down', [S(""),T("\n\n\n"),S(""),T(""),S("\n"),T("\n")], 0, 0, 2, 4, None ) self.ktest( "scroll into snap region", 'page down', [S(""),T("\n\n\n"),T(""),T(""),S("\n"),T("\n")], 0, 0, 4, 3, None ) self.ktest( "mid scroll short", 'page down', [T("\n"),S(""),T("\n"),T(""),S(""),T(""),T(""),T("\n")], 1, 2, 4, 3, None ) self.ktest( "mid scroll long", 'page down', [T("\n"),S(""),T("\n"),T(""),S(""),T(""),S(""),T("\n")], 1, 2, 6, 4, None ) self.ktest( "mid scroll perfect", 'page down', [T("\n"),S(""),T("\n"),T(""),S(""),S(""),S(""),T("\n")], 1, 2, 5, 4, None ) e = E("","hi\nab") e.set_edit_pos( 1 ) self.ktest( "cursor move up fail short", 'page down', [T(""),T(""),e,T("\n"),T("\n")], 2, 1, 2, -1, (1, 0) ) odd_e = E("","hi\nab") odd_e.set_edit_pos( 1 ) # disble cursor movement in odd_e object odd_e.move_cursor_to_coords = lambda s,c,xy: 0 self.ktest( "cursor force fail short", 'page down', [T(""),T(""),odd_e,T("\n"),T("\n")], 2, 2, 4, 3, None ) self.ktest( "cursor force fail long", 'page down', [T("\n"),S(""),T("\n"),T("\n"),T("\n"),E("hi\n","ab")], 1, 2, 4, 4, None ) self.ktest( "prefer not cut off", 'page down', [T("\n"),S(""),T("\n\n"),S(""),T("\n"),S("\n")], 1, 2, 3, 3, None ) self.ktest( "allow cut off", 'page down', [T("\n"),S(""),T("\n\n"),T(""),T("\n"),S("\n")], 1, 2, 5, 4, None ) self.ktest( "at bottom fail", 'page down', [T("\n\n"),T("\n"),T("\n\n\n")], 2, 1, 2, 1, None ) self.ktest( "all visible fail", 'page down', [T("a"),T("\n")], 1, 1, 1, 1, None ) self.ktest( "current ok fail", 'page down', [S("hi"),T("\n\n")], 0, 0, 0, 0, None ) self.ktest( "all visible choose last selectable", 'page down', [S("a"),S("b"),S("c"),T("")], 0, 0, 2, 2, None ) self.ktest( "bring in edge choose last", 'page down', [T("-"),S("d"),T("c"),S("-"),T("-"),S("b")],1,1, 5,4, None ) self.ktest( "bring in edge choose last selectable", 'page down', [T("-"),S("d"),T("c"),S("-"),S("-"),T("b")],1,1, 4,3, None ) class ZeroHeightContentsTest(unittest.TestCase): def test_listbox_pile(self): lb = urwid.ListBox(urwid.SimpleListWalker( [urwid.Pile([])])) lb.render((40,10), focus=True) def test_listbox_text_pile_page_down(self): lb = urwid.ListBox(urwid.SimpleListWalker( [urwid.Text(u'above'), urwid.Pile([])])) lb.keypress((40,10), 'page down') self.assertEquals(lb.get_focus()[1], 0) lb.keypress((40,10), 'page down') # second one caused ListBox failure self.assertEquals(lb.get_focus()[1], 0) def test_listbox_text_pile_page_up(self): lb = urwid.ListBox(urwid.SimpleListWalker( [urwid.Pile([]), urwid.Text(u'below')])) lb.set_focus(1) lb.keypress((40,10), 'page up') self.assertEquals(lb.get_focus()[1], 1) lb.keypress((40,10), 'page up') # second one caused pile failure self.assertEquals(lb.get_focus()[1], 1) def test_listbox_text_pile_down(self): sp = urwid.Pile([]) sp.selectable = lambda: True # abuse our Pile lb = urwid.ListBox(urwid.SimpleListWalker([urwid.Text(u'above'), sp])) lb.keypress((40,10), 'down') self.assertEquals(lb.get_focus()[1], 0) lb.keypress((40,10), 'down') self.assertEquals(lb.get_focus()[1], 0) def test_listbox_text_pile_up(self): sp = urwid.Pile([]) sp.selectable = lambda: True # abuse our Pile lb = urwid.ListBox(urwid.SimpleListWalker([sp, urwid.Text(u'below')])) lb.set_focus(1) lb.keypress((40,10), 'up') self.assertEquals(lb.get_focus()[1], 1) lb.keypress((40,10), 'up') self.assertEquals(lb.get_focus()[1], 1) class PaddingTest(unittest.TestCase): def ptest(self, desc, align, width, maxcol, left, right,min_width=None): p = urwid.Padding(None, align, width, min_width) l, r = p.padding_values((maxcol,),False) assert (l,r)==(left,right), "%s expected %s but got %s"%( desc, (left,right), (l,r)) def petest(self, desc, align, width): self.assertRaises(urwid.PaddingError, lambda: urwid.Padding(None, align, width)) def test_create(self): self.petest("invalid pad",6,5) self.petest("invalid pad type",('bad',2),5) self.petest("invalid width",'center','42') self.petest("invalid width type",'center',('gouranga',4)) def test_values(self): self.ptest("left align 5 7",'left',5,7,0,2) self.ptest("left align 7 7",'left',7,7,0,0) self.ptest("left align 9 7",'left',9,7,0,0) self.ptest("right align 5 7",'right',5,7,2,0) self.ptest("center align 5 7",'center',5,7,1,1) self.ptest("fixed left",('fixed left',3),5,10,3,2) self.ptest("fixed left reduce",('fixed left',3),8,10,2,0) self.ptest("fixed left shrink",('fixed left',3),18,10,0,0) self.ptest("fixed left, right", ('fixed left',3),('fixed right',4),17,3,4) self.ptest("fixed left, right, min_width", ('fixed left',3),('fixed right',4),10,3,2,5) self.ptest("fixed left, right, min_width 2", ('fixed left',3),('fixed right',4),10,2,0,8) self.ptest("fixed right",('fixed right',3),5,10,2,3) self.ptest("fixed right reduce",('fixed right',3),8,10,0,2) self.ptest("fixed right shrink",('fixed right',3),18,10,0,0) self.ptest("fixed right, left", ('fixed right',3),('fixed left',4),17,4,3) self.ptest("fixed right, left, min_width", ('fixed right',3),('fixed left',4),10,2,3,5) self.ptest("fixed right, left, min_width 2", ('fixed right',3),('fixed left',4),10,0,2,8) self.ptest("relative 30",('relative',30),5,10,1,4) self.ptest("relative 50",('relative',50),5,10,2,3) self.ptest("relative 130 edge",('relative',130),5,10,5,0) self.ptest("relative -10 edge",('relative',-10),4,10,0,6) self.ptest("center relative 70",'center',('relative',70), 10,1,2) self.ptest("center relative 70 grow 8",'center',('relative',70), 10,1,1,8) def mctest(self, desc, left, right, size, cx, innercx): class Inner: def __init__(self, desc, innercx): self.desc = desc self.innercx = innercx def move_cursor_to_coords(self,size,cx,cy): assert cx==self.innercx, desc i = Inner(desc,innercx) p = urwid.Padding(i, ('fixed left',left), ('fixed right',right)) p.move_cursor_to_coords(size, cx, 0) def test_cursor(self): self.mctest("cursor left edge",2,2,(10,2),2,0) self.mctest("cursor left edge-1",2,2,(10,2),1,0) self.mctest("cursor right edge",2,2,(10,2),7,5) self.mctest("cursor right edge+1",2,2,(10,2),8,5) def test_reduced_padding_cursor(self): # FIXME: This is at least consistent now, but I don't like it. # pack() on an Edit should leave room for the cursor # fixing this gets deep into things like Edit._shift_view_to_cursor # though, so this might not get fixed for a while p = urwid.Padding(urwid.Edit(u'',u''), width='pack', left=4) self.assertEquals(p.render((10,), True).cursor, None) self.assertEquals(p.get_cursor_coords((10,)), None) self.assertEquals(p.render((4,), True).cursor, None) self.assertEquals(p.get_cursor_coords((4,)), None) p = urwid.Padding(urwid.Edit(u'',u''), width=('relative', 100), left=4) self.assertEquals(p.render((10,), True).cursor, (4, 0)) self.assertEquals(p.get_cursor_coords((10,)), (4, 0)) self.assertEquals(p.render((4,), True).cursor, None) self.assertEquals(p.get_cursor_coords((4,)), None) class FillerTest(unittest.TestCase): def ftest(self, desc, valign, height, maxrow, top, bottom, min_height=None): f = urwid.Filler(None, valign, height, min_height) t, b = f.filler_values((20,maxrow), False) assert (t,b)==(top,bottom), "%s expected %s but got %s"%( desc, (top,bottom), (t,b)) def fetest(self, desc, valign, height): self.assertRaises(urwid.FillerError, lambda: urwid.Filler(None, valign, height)) def test_create(self): self.fetest("invalid pad",6,5) self.fetest("invalid pad type",('bad',2),5) self.fetest("invalid width",'middle','42') self.fetest("invalid width type",'middle',('gouranga',4)) self.fetest("invalid combination",('relative',20), ('fixed bottom',4)) self.fetest("invalid combination 2",('relative',20), ('fixed top',4)) def test_values(self): self.ftest("top align 5 7",'top',5,7,0,2) self.ftest("top align 7 7",'top',7,7,0,0) self.ftest("top align 9 7",'top',9,7,0,0) self.ftest("bottom align 5 7",'bottom',5,7,2,0) self.ftest("middle align 5 7",'middle',5,7,1,1) self.ftest("fixed top",('fixed top',3),5,10,3,2) self.ftest("fixed top reduce",('fixed top',3),8,10,2,0) self.ftest("fixed top shrink",('fixed top',3),18,10,0,0) self.ftest("fixed top, bottom", ('fixed top',3),('fixed bottom',4),17,3,4) self.ftest("fixed top, bottom, min_width", ('fixed top',3),('fixed bottom',4),10,3,2,5) self.ftest("fixed top, bottom, min_width 2", ('fixed top',3),('fixed bottom',4),10,2,0,8) self.ftest("fixed bottom",('fixed bottom',3),5,10,2,3) self.ftest("fixed bottom reduce",('fixed bottom',3),8,10,0,2) self.ftest("fixed bottom shrink",('fixed bottom',3),18,10,0,0) self.ftest("fixed bottom, top", ('fixed bottom',3),('fixed top',4),17,4,3) self.ftest("fixed bottom, top, min_height", ('fixed bottom',3),('fixed top',4),10,2,3,5) self.ftest("fixed bottom, top, min_height 2", ('fixed bottom',3),('fixed top',4),10,0,2,8) self.ftest("relative 30",('relative',30),5,10,1,4) self.ftest("relative 50",('relative',50),5,10,2,3) self.ftest("relative 130 edge",('relative',130),5,10,5,0) self.ftest("relative -10 edge",('relative',-10),4,10,0,6) self.ftest("middle relative 70",'middle',('relative',70), 10,1,2) self.ftest("middle relative 70 grow 8",'middle',('relative',70), 10,1,1,8) def test_repr(self): repr(urwid.Filler(urwid.Text(u'hai'))) class FrameTest(unittest.TestCase): def ftbtest(self, desc, focus_part, header_rows, footer_rows, size, focus, top, bottom): class FakeWidget: def __init__(self, rows, want_focus): self.ret_rows = rows self.want_focus = want_focus def rows(self, size, focus=False): assert self.want_focus == focus return self.ret_rows header = footer = None if header_rows: header = FakeWidget(header_rows, focus and focus_part == 'header') if footer_rows: footer = FakeWidget(footer_rows, focus and focus_part == 'footer') f = urwid.Frame(None, header, footer, focus_part) rval = f.frame_top_bottom(size, focus) exp = (top, bottom), (header_rows, footer_rows) assert exp == rval, "%s expected %r but got %r"%( desc,exp,rval) def test(self): self.ftbtest("simple", 'body', 0, 0, (9, 10), True, 0, 0) self.ftbtest("simple h", 'body', 3, 0, (9, 10), True, 3, 0) self.ftbtest("simple f", 'body', 0, 3, (9, 10), True, 0, 3) self.ftbtest("simple hf", 'body', 3, 3, (9, 10), True, 3, 3) self.ftbtest("almost full hf", 'body', 4, 5, (9, 10), True, 4, 5) self.ftbtest("full hf", 'body', 5, 5, (9, 10), True, 4, 5) self.ftbtest("x full h+1f", 'body', 6, 5, (9, 10), False, 4, 5) self.ftbtest("full h+1f", 'body', 6, 5, (9, 10), True, 4, 5) self.ftbtest("full hf+1", 'body', 5, 6, (9, 10), True, 3, 6) self.ftbtest("F full h+1f", 'footer', 6, 5, (9, 10), True, 5, 5) self.ftbtest("F full hf+1", 'footer', 5, 6, (9, 10), True, 4, 6) self.ftbtest("F full hf+5", 'footer', 5, 11, (9, 10), True, 0, 10) self.ftbtest("full hf+5", 'body', 5, 11, (9, 10), True, 0, 9) self.ftbtest("H full hf+1", 'header', 5, 6, (9, 10), True, 5, 5) self.ftbtest("H full h+1f", 'header', 6, 5, (9, 10), True, 6, 4) self.ftbtest("H full h+5f", 'header', 11, 5, (9, 10), True, 10, 0) class PileTest(unittest.TestCase): def ktest(self, desc, l, focus_item, key, rkey, rfocus, rpref_col): p = urwid.Pile( l, focus_item ) rval = p.keypress( (20,), key ) assert rkey == rval, "%s key expected %r but got %r" %( desc, rkey, rval) new_focus = l.index(p.get_focus()) assert new_focus == rfocus, "%s focus expected %r but got %r" %( desc, rfocus, new_focus) new_pref = p.get_pref_col((20,)) assert new_pref == rpref_col, ( "%s pref_col expected %r but got %r" % ( desc, rpref_col, new_pref)) def test_select_change(self): T,S,E = urwid.Text, SelectableText, urwid.Edit self.ktest("simple up", [S("")], 0, "up", "up", 0, 0) self.ktest("simple down", [S("")], 0, "down", "down", 0, 0) self.ktest("ignore up", [T(""),S("")], 1, "up", "up", 1, 0) self.ktest("ignore down", [S(""),T("")], 0, "down", "down", 0, 0) self.ktest("step up", [S(""),S("")], 1, "up", None, 0, 0) self.ktest("step down", [S(""),S("")], 0, "down", None, 1, 0) self.ktest("skip step up", [S(""),T(""),S("")], 2, "up", None, 0, 0) self.ktest("skip step down", [S(""),T(""),S("")], 0, "down", None, 2, 0) self.ktest("pad skip step up", [T(""),S(""),T(""),S("")], 3, "up", None, 1, 0) self.ktest("pad skip step down", [S(""),T(""),S(""),T("")], 0, "down", None, 2, 0) self.ktest("padi skip step up", [S(""),T(""),S(""),T(""),S("")], 4, "up", None, 2, 0) self.ktest("padi skip step down", [S(""),T(""),S(""),T(""), S("")], 0, "down", None, 2, 0) e = E("","abcd", edit_pos=1) e.keypress((20,),"right") # set a pref_col self.ktest("pref step up", [S(""),T(""),e], 2, "up", None, 0, 2) self.ktest("pref step down", [e,T(""),S("")], 0, "down", None, 2, 2) z = E("","1234") self.ktest("prefx step up", [z,T(""),e], 2, "up", None, 0, 2) assert z.get_pref_col((20,)) == 2 z = E("","1234") self.ktest("prefx step down", [e,T(""),z], 0, "down", None, 2, 2) assert z.get_pref_col((20,)) == 2 def test_init_with_a_generator(self): urwid.Pile(urwid.Text(c) for c in "ABC") def test_change_focus_with_mouse(self): p = urwid.Pile([urwid.Edit(), urwid.Edit()]) self.assertEquals(p.focus_position, 0) p.mouse_event((10,), 'button press', 1, 1, 1, True) self.assertEquals(p.focus_position, 1) class ColumnsTest(unittest.TestCase): def cwtest(self, desc, l, divide, size, exp, focus_column=0): c = urwid.Columns(l, divide, focus_column) rval = c.column_widths( size ) assert rval == exp, "%s expected %s, got %s"%(desc,exp,rval) def test_widths(self): x = urwid.Text("") # sample "column" self.cwtest( "simple 1", [x], 0, (20,), [20] ) self.cwtest( "simple 2", [x,x], 0, (20,), [10,10] ) self.cwtest( "simple 2+1", [x,x], 1, (20,), [10,9] ) self.cwtest( "simple 3+1", [x,x,x], 1, (20,), [6,6,6] ) self.cwtest( "simple 3+2", [x,x,x], 2, (20,), [5,6,5] ) self.cwtest( "simple 3+2", [x,x,x], 2, (21,), [6,6,5] ) self.cwtest( "simple 4+1", [x,x,x,x], 1, (25,), [6,5,6,5] ) self.cwtest( "squish 4+1", [x,x,x,x], 1, (7,), [1,1,1,1] ) self.cwtest( "squish 4+1", [x,x,x,x], 1, (6,), [1,2,1] ) self.cwtest( "squish 4+1", [x,x,x,x], 1, (4,), [2,1] ) self.cwtest( "fixed 3", [('fixed',4,x),('fixed',6,x), ('fixed',2,x)], 1, (25,), [4,6,2] ) self.cwtest( "fixed 3 cut", [('fixed',4,x),('fixed',6,x), ('fixed',2,x)], 1, (13,), [4,6] ) self.cwtest( "fixed 3 cut2", [('fixed',4,x),('fixed',6,x), ('fixed',2,x)], 1, (10,), [4] ) self.cwtest( "mixed 4", [('weight',2,x),('fixed',5,x), x, ('weight',3,x)], 1, (14,), [2,5,1,3] ) self.cwtest( "mixed 4 a", [('weight',2,x),('fixed',5,x), x, ('weight',3,x)], 1, (12,), [1,5,1,2] ) self.cwtest( "mixed 4 b", [('weight',2,x),('fixed',5,x), x, ('weight',3,x)], 1, (10,), [2,5,1] ) self.cwtest( "mixed 4 c", [('weight',2,x),('fixed',5,x), x, ('weight',3,x)], 1, (20,), [4,5,2,6] ) def test_widths_focus_end(self): x = urwid.Text("") # sample "column" self.cwtest("end simple 2", [x,x], 0, (20,), [10,10], 1) self.cwtest("end simple 2+1", [x,x], 1, (20,), [10,9], 1) self.cwtest("end simple 3+1", [x,x,x], 1, (20,), [6,6,6], 2) self.cwtest("end simple 3+2", [x,x,x], 2, (20,), [5,6,5], 2) self.cwtest("end simple 3+2", [x,x,x], 2, (21,), [6,6,5], 2) self.cwtest("end simple 4+1", [x,x,x,x], 1, (25,), [6,5,6,5], 3) self.cwtest("end squish 4+1", [x,x,x,x], 1, (7,), [1,1,1,1], 3) self.cwtest("end squish 4+1", [x,x,x,x], 1, (6,), [0,1,2,1], 3) self.cwtest("end squish 4+1", [x,x,x,x], 1, (4,), [0,0,2,1], 3) self.cwtest("end fixed 3", [('fixed',4,x),('fixed',6,x), ('fixed',2,x)], 1, (25,), [4,6,2], 2) self.cwtest("end fixed 3 cut", [('fixed',4,x),('fixed',6,x), ('fixed',2,x)], 1, (13,), [0,6,2], 2) self.cwtest("end fixed 3 cut2", [('fixed',4,x),('fixed',6,x), ('fixed',2,x)], 1, (8,), [0,0,2], 2) self.cwtest("end mixed 4", [('weight',2,x),('fixed',5,x), x, ('weight',3,x)], 1, (14,), [2,5,1,3], 3) self.cwtest("end mixed 4 a", [('weight',2,x),('fixed',5,x), x, ('weight',3,x)], 1, (12,), [1,5,1,2], 3) self.cwtest("end mixed 4 b", [('weight',2,x),('fixed',5,x), x, ('weight',3,x)], 1, (10,), [0,5,1,2], 3) self.cwtest("end mixed 4 c", [('weight',2,x),('fixed',5,x), x, ('weight',3,x)], 1, (20,), [4,5,2,6], 3) def mctest(self, desc, l, divide, size, col, row, exp, f_col, pref_col): c = urwid.Columns( l, divide ) rval = c.move_cursor_to_coords( size, col, row ) assert rval == exp, "%s expected %r, got %r"%(desc,exp,rval) assert c.focus_col == f_col, "%s expected focus_col %s got %s"%( desc, f_col, c.focus_col) pc = c.get_pref_col( size ) assert pc == pref_col, "%s expected pref_col %s, got %s"%( desc, pref_col, pc) def test_move_cursor(self): e, s, x = urwid.Edit("",""),SelectableText(""), urwid.Text("") self.mctest("nothing selectbl",[x,x,x],1,(20,),9,0,False,0,None) self.mctest("dead on",[x,s,x],1,(20,),9,0,True,1,9) self.mctest("l edge",[x,s,x],1,(20,),6,0,True,1,6) self.mctest("r edge",[x,s,x],1,(20,),13,0,True,1,13) self.mctest("l off",[x,s,x],1,(20,),2,0,True,1,2) self.mctest("r off",[x,s,x],1,(20,),17,0,True,1,17) self.mctest("l off 2",[x,x,s],1,(20,),2,0,True,2,2) self.mctest("r off 2",[s,x,x],1,(20,),17,0,True,0,17) self.mctest("l between",[s,s,x],1,(20,),6,0,True,0,6) self.mctest("r between",[x,s,s],1,(20,),13,0,True,1,13) self.mctest("l between 2l",[s,s,x],2,(22,),6,0,True,0,6) self.mctest("r between 2l",[x,s,s],2,(22,),14,0,True,1,14) self.mctest("l between 2r",[s,s,x],2,(22,),7,0,True,1,7) self.mctest("r between 2r",[x,s,s],2,(22,),15,0,True,2,15) # unfortunate pref_col shifting self.mctest("l e edge",[x,e,x],1,(20,),6,0,True,1,7) self.mctest("r e edge",[x,e,x],1,(20,),13,0,True,1,12) def test_init_with_a_generator(self): urwid.Columns(urwid.Text(c) for c in "ABC") def test_old_attributes(self): c = urwid.Columns([urwid.Text(u'a'), urwid.SolidFill(u'x')], box_columns=[1]) self.assertEquals(c.box_columns, [1]) c.box_columns=[] self.assertEquals(c.box_columns, []) def test_box_column(self): c = urwid.Columns([urwid.Filler(urwid.Edit()),urwid.Text('')], box_columns=[0]) c.keypress((10,), 'x') c.get_cursor_coords((10,)) c.move_cursor_to_coords((10,), 0, 0) c.mouse_event((10,), 'foo', 1, 0, 0, True) c.get_pref_col((10,)) class LineBoxTest(unittest.TestCase): def border(self, tl, t, tr, l, r, bl, b, br): return [bytes().join([tl, t, tr]), bytes().join([l, B(" "), r]), bytes().join([bl, b, br]),] def test_linebox_border(self): urwid.set_encoding("utf-8") t = urwid.Text("") l = urwid.LineBox(t).render((3,)).text # default self.assertEqual(l, self.border(B("\xe2\x94\x8c"), B("\xe2\x94\x80"), B("\xe2\x94\x90"), B("\xe2\x94\x82"), B("\xe2\x94\x82"), B("\xe2\x94\x94"), B("\xe2\x94\x80"), B("\xe2\x94\x98"))) nums = [B(str(n)) for n in range(8)] b = dict(zip(["tlcorner", "tline", "trcorner", "lline", "rline", "blcorner", "bline", "brcorner"], nums)) l = urwid.LineBox(t, **b).render((3,)).text self.assertEqual(l, self.border(*nums)) class BarGraphTest(unittest.TestCase): def bgtest(self, desc, data, top, widths, maxrow, exp ): rval = calculate_bargraph_display(data,top,widths,maxrow) assert rval == exp, "%s expected %r, got %r"%(desc,exp,rval) def test1(self): self.bgtest('simplest',[[0]],5,[1],1, [(1,[(0,1)])] ) self.bgtest('simpler',[[0],[0]],5,[1,2],5, [(5,[(0,3)])] ) self.bgtest('simple',[[5]],5,[1],1, [(1,[(1,1)])] ) self.bgtest('2col-1',[[2],[0]],5,[1,2],5, [(3,[(0,3)]), (2,[(1,1),(0,2)]) ] ) self.bgtest('2col-2',[[0],[2]],5,[1,2],5, [(3,[(0,3)]), (2,[(0,1),(1,2)]) ] ) self.bgtest('2col-3',[[2],[3]],5,[1,2],5, [(2,[(0,3)]), (1,[(0,1),(1,2)]), (2,[(1,3)]) ] ) self.bgtest('3col-1',[[5],[3],[0]],5,[2,1,1],5, [(2,[(1,2),(0,2)]), (3,[(1,3),(0,1)]) ] ) self.bgtest('3col-2',[[4],[4],[4]],5,[2,1,1],5, [(1,[(0,4)]), (4,[(1,4)]) ] ) self.bgtest('3col-3',[[1],[2],[3]],5,[2,1,1],5, [(2,[(0,4)]), (1,[(0,3),(1,1)]), (1,[(0,2),(1,2)]), (1,[(1,4)]) ] ) self.bgtest('3col-4',[[4],[2],[4]],5,[1,2,1],5, [(1,[(0,4)]), (2,[(1,1),(0,2),(1,1)]), (2,[(1,4)]) ] ) def test2(self): self.bgtest('simple1a',[[2,0],[2,1]],2,[1,1],2, [(1,[(1,2)]),(1,[(1,1),(2,1)]) ] ) self.bgtest('simple1b',[[2,1],[2,0]],2,[1,1],2, [(1,[(1,2)]),(1,[(2,1),(1,1)]) ] ) self.bgtest('cross1a',[[2,2],[1,2]],2,[1,1],2, [(2,[(2,2)]) ] ) self.bgtest('cross1b',[[1,2],[2,2]],2,[1,1],2, [(2,[(2,2)]) ] ) self.bgtest('mix1a',[[3,2,1],[2,2,2],[1,2,3]],3,[1,1,1],3, [(1,[(1,1),(0,1),(3,1)]),(1,[(2,1),(3,2)]), (1,[(3,3)]) ] ) self.bgtest('mix1b',[[1,2,3],[2,2,2],[3,2,1]],3,[1,1,1],3, [(1,[(3,1),(0,1),(1,1)]),(1,[(3,2),(2,1)]), (1,[(3,3)]) ] ) class SmoothBarGraphTest(unittest.TestCase): def sbgtest(self, desc, data, top, exp ): urwid.set_encoding('utf-8') g = urwid.BarGraph( ['black','red','blue'], None, {(1,0):'red/black', (2,1):'blue/red'}) g.set_data( data, top ) rval = g.calculate_display((5,3)) assert rval == exp, "%s expected %r, got %r"%(desc,exp,rval) def test1(self): self.sbgtest('simple', [[3]], 5, [(1, [(0, 5)]), (1, [((1, 0, 6), 5)]), (1, [(1, 5)])] ) self.sbgtest('boring', [[4,2]], 6, [(1, [(0, 5)]), (1, [(1, 5)]), (1, [(2,5)]) ] ) self.sbgtest('two', [[4],[2]], 6, [(1, [(0, 5)]), (1, [(1, 3), (0, 2)]), (1, [(1, 5)]) ] ) self.sbgtest('twos', [[3],[4]], 6, [(1, [(0, 5)]), (1, [((1,0,4), 3), (1, 2)]), (1, [(1,5)]) ] ) self.sbgtest('twof', [[4],[3]], 6, [(1, [(0, 5)]), (1, [(1,3), ((1,0,4), 2)]), (1, [(1,5)]) ] ) class OverlayTest(unittest.TestCase): def test_old_params(self): o1 = urwid.Overlay(urwid.SolidFill(u'X'), urwid.SolidFill(u'O'), ('fixed left', 5), ('fixed right', 4), ('fixed top', 3), ('fixed bottom', 2),) self.assertEquals(o1.contents[1][1], ( 'left', None, 'relative', 100, None, 5, 4, 'top', None, 'relative', 100, None, 3, 2)) o2 = urwid.Overlay(urwid.SolidFill(u'X'), urwid.SolidFill(u'O'), ('fixed right', 5), ('fixed left', 4), ('fixed bottom', 3), ('fixed top', 2),) self.assertEquals(o2.contents[1][1], ( 'right', None, 'relative', 100, None, 4, 5, 'bottom', None, 'relative', 100, None, 2, 3)) def test_get_cursor_coords(self): self.assertEquals(urwid.Overlay(urwid.Filler(urwid.Edit()), urwid.SolidFill(u'B'), 'right', 1, 'bottom', 1).get_cursor_coords((2,2)), (1,1)) class GridFlowTest(unittest.TestCase): def test_cell_width(self): gf = urwid.GridFlow([], 5, 0, 0, 'left') self.assertEquals(gf.cell_width, 5) def test_basics(self): repr(urwid.GridFlow([], 5, 0, 0, 'left')) # should not fail class CanvasJoinTest(unittest.TestCase): def cjtest(self, desc, l, expected): l = [(c, None, False, n) for c, n in l] result = list(urwid.CanvasJoin(l).content()) assert result == expected, "%s expected %r, got %r"%( desc, expected, result) def test(self): C = urwid.TextCanvas hello = C([B("hello")]) there = C([B("there")], [[("a",5)]]) a = C([B("a")]) hi = C([B("hi")]) how = C([B("how")], [[("a",1)]]) dy = C([B("dy")]) how_you = C([B("how"), B("you")]) self.cjtest("one", [(hello, 5)], [[(None, None, B("hello"))]]) self.cjtest("two", [(hello, 5), (there, 5)], [[(None, None, B("hello")), ("a", None, B("there"))]]) self.cjtest("two space", [(hello, 7), (there, 5)], [[(None, None, B("hello")),(None,None,B(" ")), ("a", None, B("there"))]]) self.cjtest("three space", [(hi, 4), (how, 3), (dy, 2)], [[(None, None, B("hi")),(None,None,B(" ")),("a",None, B("h")), (None,None,B("ow")),(None,None,B("dy"))]]) self.cjtest("four space", [(a, 2), (hi, 3), (dy, 3), (a, 1)], [[(None, None, B("a")),(None,None,B(" ")), (None, None, B("hi")),(None,None,B(" ")), (None, None, B("dy")),(None,None,B(" ")), (None, None, B("a"))]]) self.cjtest("pile 2", [(how_you, 4), (hi, 2)], [[(None, None, B('how')), (None, None, B(' ')), (None, None, B('hi'))], [(None, None, B('you')), (None, None, B(' ')), (None, None, B(' '))]]) self.cjtest("pile 2r", [(hi, 4), (how_you, 3)], [[(None, None, B('hi')), (None, None, B(' ')), (None, None, B('how'))], [(None, None, B(' ')), (None, None, B('you'))]]) class CanvasOverlayTest(unittest.TestCase): def cotest(self, desc, bgt, bga, fgt, fga, l, r, et): bgt = B(bgt) fgt = B(fgt) bg = urwid.CompositeCanvas( urwid.TextCanvas([bgt],[bga])) fg = urwid.CompositeCanvas( urwid.TextCanvas([fgt],[fga])) bg.overlay(fg, l, 0) result = list(bg.content()) assert result == et, "%s expected %r, got %r"%( desc, et, result) def test1(self): self.cotest("left", "qxqxqxqx", [], "HI", [], 0, 6, [[(None, None, B("HI")),(None,None,B("qxqxqx"))]]) self.cotest("right", "qxqxqxqx", [], "HI", [], 6, 0, [[(None, None, B("qxqxqx")),(None,None,B("HI"))]]) self.cotest("center", "qxqxqxqx", [], "HI", [], 3, 3, [[(None, None, B("qxq")),(None,None,B("HI")), (None,None,B("xqx"))]]) self.cotest("center2", "qxqxqxqx", [], "HI ", [], 2, 2, [[(None, None, B("qx")),(None,None,B("HI ")), (None,None,B("qx"))]]) self.cotest("full", "rz", [], "HI", [], 0, 0, [[(None, None, B("HI"))]]) def test2(self): self.cotest("same","asdfghjkl",[('a',9)],"HI",[('a',2)],4,3, [[('a',None,B("asdf")),('a',None,B("HI")),('a',None,B("jkl"))]]) self.cotest("diff","asdfghjkl",[('a',9)],"HI",[('b',2)],4,3, [[('a',None,B("asdf")),('b',None,B("HI")),('a',None,B("jkl"))]]) self.cotest("None end","asdfghjkl",[('a',9)],"HI ",[('a',2)], 2,3, [[('a',None,B("as")),('a',None,B("HI")), (None,None,B(" ")),('a',None,B("jkl"))]]) self.cotest("float end","asdfghjkl",[('a',3)],"HI",[('a',2)], 4,3, [[('a',None,B("asd")),(None,None,B("f")), ('a',None,B("HI")),(None,None,B("jkl"))]]) self.cotest("cover 2","asdfghjkl",[('a',5),('c',4)],"HI", [('b',2)],4,3, [[('a',None,B("asdf")),('b',None,B("HI")),('c',None,B("jkl"))]]) self.cotest("cover 2-2","asdfghjkl", [('a',4),('d',1),('e',1),('c',3)], "HI",[('b',2)], 4, 3, [[('a',None,B("asdf")),('b',None,B("HI")),('c',None,B("jkl"))]]) def test3(self): urwid.set_encoding("euc-jp") self.cotest("db0","\xA1\xA1\xA1\xA1\xA1\xA1",[],"HI",[],2,2, [[(None,None,B("\xA1\xA1")),(None,None,B("HI")), (None,None,B("\xA1\xA1"))]]) self.cotest("db1","\xA1\xA1\xA1\xA1\xA1\xA1",[],"OHI",[],1,2, [[(None,None,B(" ")),(None,None,B("OHI")), (None,None,B("\xA1\xA1"))]]) self.cotest("db2","\xA1\xA1\xA1\xA1\xA1\xA1",[],"OHI",[],2,1, [[(None,None,B("\xA1\xA1")),(None,None,B("OHI")), (None,None,B(" "))]]) self.cotest("db3","\xA1\xA1\xA1\xA1\xA1\xA1",[],"OHIO",[],1,1, [[(None,None,B(" ")),(None,None,B("OHIO")),(None,None,B(" "))]]) class CanvasPadTrimTest(unittest.TestCase): def cptest(self, desc, ct, ca, l, r, et): ct = B(ct) c = urwid.CompositeCanvas( urwid.TextCanvas([ct], [ca])) c.pad_trim_left_right(l, r) result = list(c.content()) assert result == et, "%s expected %r, got %r"%( desc, et, result) def test1(self): self.cptest("none", "asdf", [], 0, 0, [[(None,None,B("asdf"))]]) self.cptest("left pad", "asdf", [], 2, 0, [[(None,None,B(" ")),(None,None,B("asdf"))]]) self.cptest("right pad", "asdf", [], 0, 2, [[(None,None,B("asdf")),(None,None,B(" "))]]) def test2(self): self.cptest("left trim", "asdf", [], -2, 0, [[(None,None,B("df"))]]) self.cptest("right trim", "asdf", [], 0, -2, [[(None,None,B("as"))]]) class WidgetSquishTest(unittest.TestCase): def wstest(self, w): c = w.render((80,0), focus=False) assert c.rows() == 0 c = w.render((80,0), focus=True) assert c.rows() == 0 c = w.render((80,1), focus=False) assert c.rows() == 1 c = w.render((0, 25), focus=False) c = w.render((1, 25), focus=False) def fwstest(self, w): def t(cols, focus): wrows = w.rows((cols,), focus) c = w.render((cols,), focus) assert c.rows() == wrows, (c.rows(), wrows) if focus and hasattr(w, 'get_cursor_coords'): gcc = w.get_cursor_coords((cols,)) assert c.cursor == gcc, (c.cursor, gcc) t(0, False) t(1, False) t(0, True) t(1, True) def test_listbox(self): self.wstest(urwid.ListBox([])) self.wstest(urwid.ListBox([urwid.Text("hello")])) def test_bargraph(self): self.wstest(urwid.BarGraph(['foo','bar'])) def test_graphvscale(self): self.wstest(urwid.GraphVScale([(0,"hello")], 1)) self.wstest(urwid.GraphVScale([(5,"hello")], 1)) def test_solidfill(self): self.wstest(urwid.SolidFill()) def test_filler(self): self.wstest(urwid.Filler(urwid.Text("hello"))) def test_overlay(self): self.wstest(urwid.Overlay( urwid.BigText("hello",urwid.Thin6x6Font()), urwid.SolidFill(), 'center', None, 'middle', None)) self.wstest(urwid.Overlay( urwid.Text("hello"), urwid.SolidFill(), 'center', ('relative', 100), 'middle', None)) def test_frame(self): self.wstest(urwid.Frame(urwid.SolidFill())) self.wstest(urwid.Frame(urwid.SolidFill(), header=urwid.Text("hello"))) self.wstest(urwid.Frame(urwid.SolidFill(), header=urwid.Text("hello"), footer=urwid.Text("hello"))) def test_pile(self): self.wstest(urwid.Pile([urwid.SolidFill()])) self.wstest(urwid.Pile([('flow', urwid.Text("hello"))])) self.wstest(urwid.Pile([])) def test_columns(self): self.wstest(urwid.Columns([urwid.SolidFill()])) self.wstest(urwid.Columns([(4, urwid.SolidFill())])) def test_buttons(self): self.fwstest(urwid.Button(u"hello")) self.fwstest(urwid.RadioButton([], u"hello")) class CommonContainerTest(unittest.TestCase): def test_pile(self): t1 = urwid.Text(u'one') t2 = urwid.Text(u'two') t3 = urwid.Text(u'three') sf = urwid.SolidFill('x') p = urwid.Pile([]) self.assertEquals(p.focus, None) self.assertRaises(IndexError, lambda: getattr(p, 'focus_position')) self.assertRaises(IndexError, lambda: setattr(p, 'focus_position', None)) self.assertRaises(IndexError, lambda: setattr(p, 'focus_position', 0)) p.contents = [(t1, ('pack', None)), (t2, ('pack', None)), (sf, ('given', 3)), (t3, ('pack', None))] p.focus_position = 1 del p.contents[0] self.assertEquals(p.focus_position, 0) p.contents[0:0] = [(t3, ('pack', None)), (t2, ('pack', None))] p.contents.insert(3, (t1, ('pack', None))) self.assertEquals(p.focus_position, 2) self.assertRaises(urwid.PileError, lambda: p.contents.append(t1)) self.assertRaises(urwid.PileError, lambda: p.contents.append((t1, None))) self.assertRaises(urwid.PileError, lambda: p.contents.append((t1, 'given'))) p = urwid.Pile([t1, t2]) self.assertEquals(p.focus, t1) self.assertEquals(p.focus_position, 0) p.focus_position = 1 self.assertEquals(p.focus, t2) self.assertEquals(p.focus_position, 1) p.focus_position = 0 self.assertRaises(IndexError, lambda: setattr(p, 'focus_position', -1)) self.assertRaises(IndexError, lambda: setattr(p, 'focus_position', 2)) # old methods: p.set_focus(0) self.assertRaises(IndexError, lambda: p.set_focus(-1)) self.assertRaises(IndexError, lambda: p.set_focus(2)) p.set_focus(t2) self.assertEquals(p.focus_position, 1) self.assertRaises(ValueError, lambda: p.set_focus('nonexistant')) self.assertEquals(p.widget_list, [t1, t2]) self.assertEquals(p.item_types, [('weight', 1), ('weight', 1)]) p.widget_list = [t2, t1] self.assertEquals(p.widget_list, [t2, t1]) self.assertEquals(p.contents, [(t2, ('weight', 1)), (t1, ('weight', 1))]) self.assertEquals(p.focus_position, 1) # focus unchanged p.item_types = [('flow', None), ('weight', 2)] self.assertEquals(p.item_types, [('flow', None), ('weight', 2)]) self.assertEquals(p.contents, [(t2, ('pack', None)), (t1, ('weight', 2))]) self.assertEquals(p.focus_position, 1) # focus unchanged p.widget_list = [t1] self.assertEquals(len(p.contents), 1) self.assertEquals(p.focus_position, 0) p.widget_list.extend([t2, t1]) self.assertEquals(len(p.contents), 3) self.assertEquals(p.item_types, [ ('flow', None), ('weight', 1), ('weight', 1)]) p.item_types[:] = [('weight', 2)] self.assertEquals(len(p.contents), 1) def test_columns(self): t1 = urwid.Text(u'one') t2 = urwid.Text(u'two') t3 = urwid.Text(u'three') sf = urwid.SolidFill('x') c = urwid.Columns([]) self.assertEquals(c.focus, None) self.assertRaises(IndexError, lambda: getattr(c, 'focus_position')) self.assertRaises(IndexError, lambda: setattr(c, 'focus_position', None)) self.assertRaises(IndexError, lambda: setattr(c, 'focus_position', 0)) c.contents = [ (t1, ('pack', None, False)), (t2, ('weight', 1, False)), (sf, ('weight', 2, True)), (t3, ('given', 10, False))] c.focus_position = 1 del c.contents[0] self.assertEquals(c.focus_position, 0) c.contents[0:0] = [ (t3, ('given', 10, False)), (t2, ('weight', 1, False))] c.contents.insert(3, (t1, ('pack', None, False))) self.assertEquals(c.focus_position, 2) self.assertRaises(urwid.ColumnsError, lambda: c.contents.append(t1)) self.assertRaises(urwid.ColumnsError, lambda: c.contents.append((t1, None))) self.assertRaises(urwid.ColumnsError, lambda: c.contents.append((t1, 'given'))) c = urwid.Columns([t1, t2]) self.assertEquals(c.focus, t1) self.assertEquals(c.focus_position, 0) c.focus_position = 1 self.assertEquals(c.focus, t2) self.assertEquals(c.focus_position, 1) c.focus_position = 0 self.assertRaises(IndexError, lambda: setattr(c, 'focus_position', -1)) self.assertRaises(IndexError, lambda: setattr(c, 'focus_position', 2)) # old methods: c = urwid.Columns([t1, ('weight', 3, t2), sf], box_columns=[2]) c.set_focus(0) self.assertRaises(IndexError, lambda: c.set_focus(-1)) self.assertRaises(IndexError, lambda: c.set_focus(3)) c.set_focus(t2) self.assertEquals(c.focus_position, 1) self.assertRaises(ValueError, lambda: c.set_focus('nonexistant')) self.assertEquals(c.widget_list, [t1, t2, sf]) self.assertEquals(c.column_types, [ ('weight', 1), ('weight', 3), ('weight', 1)]) self.assertEquals(c.box_columns, [2]) c.widget_list = [t2, t1, sf] self.assertEquals(c.widget_list, [t2, t1, sf]) self.assertEquals(c.box_columns, [2]) self.assertEquals(c.contents, [ (t2, ('weight', 1, False)), (t1, ('weight', 3, False)), (sf, ('weight', 1, True))]) self.assertEquals(c.focus_position, 1) # focus unchanged c.column_types = [ ('flow', None), # use the old name ('weight', 2), ('fixed', 5)] self.assertEquals(c.column_types, [ ('flow', None), ('weight', 2), ('fixed', 5)]) self.assertEquals(c.contents, [ (t2, ('pack', None, False)), (t1, ('weight', 2, False)), (sf, ('given', 5, True))]) self.assertEquals(c.focus_position, 1) # focus unchanged c.widget_list = [t1] self.assertEquals(len(c.contents), 1) self.assertEquals(c.focus_position, 0) c.widget_list.extend([t2, t1]) self.assertEquals(len(c.contents), 3) self.assertEquals(c.column_types, [ ('flow', None), ('weight', 1), ('weight', 1)]) c.column_types[:] = [('weight', 2)] self.assertEquals(len(c.contents), 1) def test_list_box(self): lb = urwid.ListBox(urwid.SimpleFocusListWalker([])) self.assertEquals(lb.focus, None) self.assertRaises(IndexError, lambda: getattr(lb, 'focus_position')) self.assertRaises(IndexError, lambda: setattr(lb, 'focus_position', None)) self.assertRaises(IndexError, lambda: setattr(lb, 'focus_position', 0)) t1 = urwid.Text(u'one') t2 = urwid.Text(u'two') lb = urwid.ListBox(urwid.SimpleListWalker([t1, t2])) self.assertEquals(lb.focus, t1) self.assertEquals(lb.focus_position, 0) lb.focus_position = 1 self.assertEquals(lb.focus, t2) self.assertEquals(lb.focus_position, 1) lb.focus_position = 0 self.assertRaises(IndexError, lambda: setattr(lb, 'focus_position', -1)) self.assertRaises(IndexError, lambda: setattr(lb, 'focus_position', 2)) def test_grid_flow(self): gf = urwid.GridFlow([], 5, 1, 0, 'left') self.assertEquals(gf.focus, None) self.assertEquals(gf.contents, []) self.assertRaises(IndexError, lambda: getattr(gf, 'focus_position')) self.assertRaises(IndexError, lambda: setattr(gf, 'focus_position', None)) self.assertRaises(IndexError, lambda: setattr(gf, 'focus_position', 0)) self.assertEquals(gf.options(), ('given', 5)) self.assertEquals(gf.options(width_amount=9), ('given', 9)) self.assertRaises(urwid.GridFlowError, lambda: gf.options( 'pack', None)) t1 = urwid.Text(u'one') t2 = urwid.Text(u'two') gf = urwid.GridFlow([t1, t2], 5, 1, 0, 'left') self.assertEquals(gf.focus, t1) self.assertEquals(gf.focus_position, 0) self.assertEquals(gf.contents, [(t1, ('given', 5)), (t2, ('given', 5))]) gf.focus_position = 1 self.assertEquals(gf.focus, t2) self.assertEquals(gf.focus_position, 1) gf.contents.insert(0, (t2, ('given', 5))) self.assertEquals(gf.focus_position, 2) self.assertRaises(urwid.GridFlowError, lambda: gf.contents.append(())) self.assertRaises(urwid.GridFlowError, lambda: gf.contents.insert(1, (t1, ('pack', None)))) gf.focus_position = 0 self.assertRaises(IndexError, lambda: setattr(gf, 'focus_position', -1)) self.assertRaises(IndexError, lambda: setattr(gf, 'focus_position', 3)) # old methods: gf.set_focus(0) self.assertRaises(IndexError, lambda: gf.set_focus(-1)) self.assertRaises(IndexError, lambda: gf.set_focus(3)) gf.set_focus(t1) self.assertEquals(gf.focus_position, 1) self.assertRaises(ValueError, lambda: gf.set_focus('nonexistant')) def test_overlay(self): s1 = urwid.SolidFill(u'1') s2 = urwid.SolidFill(u'2') o = urwid.Overlay(s1, s2, 'center', ('relative', 50), 'middle', ('relative', 50)) self.assertEquals(o.focus, s1) self.assertEquals(o.focus_position, 1) self.assertRaises(IndexError, lambda: setattr(o, 'focus_position', None)) self.assertRaises(IndexError, lambda: setattr(o, 'focus_position', 2)) self.assertEquals(o.contents[0], (s2, urwid.Overlay._DEFAULT_BOTTOM_OPTIONS)) self.assertEquals(o.contents[1], (s1, ( 'center', None, 'relative', 50, None, 0, 0, 'middle', None, 'relative', 50, None, 0, 0))) def test_frame(self): s1 = urwid.SolidFill(u'1') f = urwid.Frame(s1) self.assertEquals(f.focus, s1) self.assertEquals(f.focus_position, 'body') self.assertRaises(IndexError, lambda: setattr(f, 'focus_position', None)) self.assertRaises(IndexError, lambda: setattr(f, 'focus_position', 'header')) t1 = urwid.Text(u'one') t2 = urwid.Text(u'two') t3 = urwid.Text(u'three') f = urwid.Frame(s1, t1, t2, 'header') self.assertEquals(f.focus, t1) self.assertEquals(f.focus_position, 'header') f.focus_position = 'footer' self.assertEquals(f.focus, t2) self.assertEquals(f.focus_position, 'footer') self.assertRaises(IndexError, lambda: setattr(f, 'focus_position', -1)) self.assertRaises(IndexError, lambda: setattr(f, 'focus_position', 2)) del f.contents['footer'] self.assertEquals(f.footer, None) self.assertEquals(f.focus_position, 'body') f.contents.update(footer=(t3, None), header=(t2, None)) self.assertEquals(f.header, t2) self.assertEquals(f.footer, t3) def set1(): f.contents['body'] = t1 self.assertRaises(urwid.FrameError, set1) def set2(): f.contents['body'] = (t1, 'given') self.assertRaises(urwid.FrameError, set2) def test_focus_path(self): # big tree of containers t = urwid.Text(u'x') e = urwid.Edit(u'?') c = urwid.Columns([t, e, t, t]) p = urwid.Pile([t, t, c, t]) a = urwid.AttrMap(p, 'gets ignored') s = urwid.SolidFill(u'/') o = urwid.Overlay(e, s, 'center', 'pack', 'middle', 'pack') lb = urwid.ListBox(urwid.SimpleFocusListWalker([t, a, o, t])) lb.focus_position = 1 g = urwid.GridFlow([t, t, t, t, e, t], 10, 0, 0, 'left') g.focus_position = 4 f = urwid.Frame(lb, header=t, footer=g) self.assertEquals(f.get_focus_path(), ['body', 1, 2, 1]) f.set_focus_path(['footer']) # same as f.focus_position = 'footer' self.assertEquals(f.get_focus_path(), ['footer', 4]) f.set_focus_path(['body', 1, 2, 2]) self.assertEquals(f.get_focus_path(), ['body', 1, 2, 2]) self.assertRaises(IndexError, lambda: f.set_focus_path([0, 1, 2])) self.assertRaises(IndexError, lambda: f.set_focus_path(['body', 2, 2])) f.set_focus_path(['body', 2]) # focus the overlay self.assertEquals(f.get_focus_path(), ['body', 2, 1]) def test_all(): """ Return a TestSuite with all tests available """ unittests = [ DecodeOneTest, CalcWidthTest, ConvertDecSpecialTest, WithinDoubleByteTest, CalcTextPosTest, CalcBreaksCharTest, CalcBreaksDBCharTest, CalcBreaksWordTest, CalcBreaksWordTest2, CalcBreaksDBWordTest, CalcBreaksUTF8Test, CalcBreaksCantDisplayTest, SubsegTest, CalcTranslateCharTest, CalcTranslateWordTest, CalcTranslateWordTest2, CalcTranslateWordTest3, CalcTranslateWordTest4, CalcTranslateWordTest5, CalcTranslateClipTest, CalcTranslateCantDisplayTest, CalcPosTest, Pos2CoordsTest, CanvasCacheTest, CanvasTest, ShardBodyTest, ShardsTrimTest, ShardsJoinTest, TagMarkupTest, TextTest, EditTest, EditRenderTest, ListBoxCalculateVisibleTest, ListBoxChangeFocusTest, ListBoxRenderTest, ListBoxKeypressTest, ZeroHeightContentsTest, PaddingTest, FillerTest, FrameTest, PileTest, ColumnsTest, LineBoxTest, BarGraphTest, OverlayTest, GridFlowTest, SmoothBarGraphTest, CanvasJoinTest, CanvasOverlayTest, CanvasPadTrimTest, WidgetSquishTest, TermTest, CommonContainerTest, ] module_doctests = [ urwid.widget, urwid.wimp, urwid.decoration, urwid.display_common, urwid.main_loop, urwid.monitored_list, urwid.raw_display, 'urwid.split_repr', # override function with same name urwid.util, ] tests = unittest.TestSuite() for t in unittests: tests.addTest(unittest.TestLoader().loadTestsFromTestCase(t)) for m in module_doctests: tests.addTest(DocTestSuite( m, optionflags=ELLIPSIS | IGNORE_EXCEPTION_DETAIL)) return tests if __name__ == '__main__': unittest.TextTestRunner(verbosity=2).run(test_all()) urwid-1.1.1/urwid/util.py0000664000175000017500000003100712051303575014671 0ustar ianian00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Urwid utility functions # 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/ from urwid import escape from urwid.compat import bytes str_util = escape.str_util # bring str_util functions into our namespace calc_text_pos = str_util.calc_text_pos calc_width = str_util.calc_width is_wide_char = str_util.is_wide_char move_next_char = str_util.move_next_char move_prev_char = str_util.move_prev_char within_double_byte = str_util.within_double_byte def detect_encoding(): # Try to determine if using a supported double-byte encoding import locale try: try: locale.setlocale(locale.LC_ALL, "") except locale.Error: pass return locale.getlocale()[1] or "" except ValueError, e: # with invalid LANG value python will throw ValueError if e.args and e.args[0].startswith("unknown locale"): return "" else: raise if 'detected_encoding' not in locals(): detected_encoding = detect_encoding() else: assert 0, "It worked!" _target_encoding = None _use_dec_special = True def set_encoding( encoding ): """ Set the byte encoding to assume when processing strings and the encoding to use when converting unicode strings. """ encoding = encoding.lower() global _target_encoding, _use_dec_special if encoding in ( 'utf-8', 'utf8', 'utf' ): str_util.set_byte_encoding("utf8") _use_dec_special = False elif encoding in ( 'euc-jp' # JISX 0208 only , 'euc-kr', 'euc-cn', 'euc-tw' # CNS 11643 plain 1 only , 'gb2312', 'gbk', 'big5', 'cn-gb', 'uhc' # these shouldn't happen, should they? , 'eucjp', 'euckr', 'euccn', 'euctw', 'cncb' ): str_util.set_byte_encoding("wide") _use_dec_special = True else: str_util.set_byte_encoding("narrow") _use_dec_special = True # if encoding is valid for conversion from unicode, remember it _target_encoding = 'ascii' try: if encoding: u"".encode(encoding) _target_encoding = encoding except LookupError: pass def get_encoding_mode(): """ Get the mode Urwid is using when processing text strings. Returns 'narrow' for 8-bit encodings, 'wide' for CJK encodings or 'utf8' for UTF-8 encodings. """ return str_util.get_byte_encoding() def apply_target_encoding( s ): """ Return (encoded byte string, character set rle). """ if _use_dec_special and type(s) == unicode: # first convert drawing characters try: s = s.translate( escape.DEC_SPECIAL_CHARMAP ) except NotImplementedError: # python < 2.4 needs to do this the hard way.. for c, alt in zip(escape.DEC_SPECIAL_CHARS, escape.ALT_DEC_SPECIAL_CHARS): s = s.replace( c, escape.SO+alt+escape.SI ) if type(s) == unicode: s = s.replace( escape.SI+escape.SO, u"" ) # remove redundant shifts s = s.encode( _target_encoding ) assert isinstance(s, bytes) SO = escape.SO.encode('ascii') SI = escape.SI.encode('ascii') sis = s.split(SO) assert isinstance(sis[0], bytes) sis0 = sis[0].replace(SI, bytes()) sout = [] cout = [] if sis0: sout.append( sis0 ) cout.append( (None,len(sis0)) ) if len(sis)==1: return sis0, cout for sn in sis[1:]: assert isinstance(sn, bytes) assert isinstance(SI, bytes) sl = sn.split(SI, 1) if len(sl) == 1: sin = sl[0] assert isinstance(sin, bytes) sout.append(sin) rle_append_modify(cout, (escape.DEC_TAG.encode('ascii'), len(sin))) continue sin, son = sl son = son.replace(SI, bytes()) if sin: sout.append(sin) rle_append_modify(cout, (escape.DEC_TAG, len(sin))) if son: sout.append(son) rle_append_modify(cout, (None, len(son))) outstr = bytes().join(sout) return outstr, cout ###################################################################### # Try to set the encoding using the one detected by the locale module set_encoding( detected_encoding ) ###################################################################### def supports_unicode(): """ Return True if python is able to convert non-ascii unicode strings to the current encoding. """ return _target_encoding and _target_encoding != 'ascii' def calc_trim_text( text, start_offs, end_offs, start_col, end_col ): """ Calculate the result of trimming text. start_offs -- offset into text to treat as screen column 0 end_offs -- offset into text to treat as the end of the line start_col -- screen column to trim at the left end_col -- screen column to trim at the right Returns (start, end, pad_left, pad_right), where: start -- resulting start offset end -- resulting end offset pad_left -- 0 for no pad or 1 for one space to be added pad_right -- 0 for no pad or 1 for one space to be added """ spos = start_offs pad_left = pad_right = 0 if start_col > 0: spos, sc = calc_text_pos( text, spos, end_offs, start_col ) if sc < start_col: pad_left = 1 spos, sc = calc_text_pos( text, start_offs, end_offs, start_col+1 ) run = end_col - start_col - pad_left pos, sc = calc_text_pos( text, spos, end_offs, run ) if sc < run: pad_right = 1 return ( spos, pos, pad_left, pad_right ) def trim_text_attr_cs( text, attr, cs, start_col, end_col ): """ Return ( trimmed text, trimmed attr, trimmed cs ). """ spos, epos, pad_left, pad_right = calc_trim_text( text, 0, len(text), start_col, end_col ) attrtr = rle_subseg( attr, spos, epos ) cstr = rle_subseg( cs, spos, epos ) if pad_left: al = rle_get_at( attr, spos-1 ) rle_append_beginning_modify( attrtr, (al, 1) ) rle_append_beginning_modify( cstr, (None, 1) ) if pad_right: al = rle_get_at( attr, epos ) rle_append_modify( attrtr, (al, 1) ) rle_append_modify( cstr, (None, 1) ) return (bytes().rjust(pad_left) + text[spos:epos] + bytes().rjust(pad_right), attrtr, cstr) def rle_get_at( rle, pos ): """ Return the attribute at offset pos. """ x = 0 if pos < 0: return None for a, run in rle: if x+run > pos: return a x += run return None def rle_subseg( rle, start, end ): """Return a sub segment of an rle list.""" l = [] x = 0 for a, run in rle: if start: if start >= run: start -= run x += run continue x += start run -= start start = 0 if x >= end: break if x+run > end: run = end-x x += run l.append( (a, run) ) return l def rle_len( rle ): """ Return the number of characters covered by a run length encoded attribute list. """ run = 0 for v in rle: assert type(v) == tuple, repr(rle) a, r = v run += r return run def rle_append_beginning_modify( rle, (a, r) ): """ Append (a, r) to BEGINNING of rle. Merge with first run when possible MODIFIES rle parameter contents. Returns None. """ if not rle: rle[:] = [(a, r)] else: al, run = rle[0] if a == al: rle[0] = (a,run+r) else: rle[0:0] = [(al, r)] def rle_append_modify( rle, (a, r) ): """ Append (a,r) to the rle list rle. Merge with last run when possible. MODIFIES rle parameter contents. Returns None. """ if not rle or rle[-1][0] != a: rle.append( (a,r) ) return la,lr = rle[-1] rle[-1] = (a, lr+r) def rle_join_modify( rle, rle2 ): """ Append attribute list rle2 to rle. Merge last run of rle with first run of rle2 when possible. MODIFIES attr parameter contents. Returns None. """ if not rle2: return rle_append_modify(rle, rle2[0]) rle += rle2[1:] def rle_product( rle1, rle2 ): """ Merge the runs of rle1 and rle2 like this: eg. rle1 = [ ("a", 10), ("b", 5) ] rle2 = [ ("Q", 5), ("P", 10) ] rle_product: [ (("a","Q"), 5), (("a","P"), 5), (("b","P"), 5) ] rle1 and rle2 are assumed to cover the same total run. """ i1 = i2 = 1 # rle1, rle2 indexes if not rle1 or not rle2: return [] a1, r1 = rle1[0] a2, r2 = rle2[0] l = [] while r1 and r2: r = min(r1, r2) rle_append_modify( l, ((a1,a2),r) ) r1 -= r if r1 == 0 and i1< len(rle1): a1, r1 = rle1[i1] i1 += 1 r2 -= r if r2 == 0 and i2< len(rle2): a2, r2 = rle2[i2] i2 += 1 return l def rle_factor( rle ): """ Inverse of rle_product. """ rle1 = [] rle2 = [] for (a1, a2), r in rle: rle_append_modify( rle1, (a1, r) ) rle_append_modify( rle2, (a2, r) ) return rle1, rle2 class TagMarkupException(Exception): pass def decompose_tagmarkup(tm): """Return (text string, attribute list) for tagmarkup passed.""" tl, al = _tagmarkup_recurse(tm, None) # join as unicode or bytes based on type of first element text = tl[0][:0].join(tl) if al and al[-1][0] is None: del al[-1] return text, al def _tagmarkup_recurse( tm, attr ): """Return (text list, attribute list) for tagmarkup passed. tm -- tagmarkup attr -- current attribute or None""" if type(tm) == list: # for lists recurse to process each subelement rtl = [] ral = [] for element in tm: tl, al = _tagmarkup_recurse( element, attr ) if ral: # merge attributes when possible last_attr, last_run = ral[-1] top_attr, top_run = al[0] if last_attr == top_attr: ral[-1] = (top_attr, last_run + top_run) del al[-1] rtl += tl ral += al return rtl, ral if type(tm) == tuple: # tuples mark a new attribute boundary if len(tm) != 2: raise TagMarkupException, "Tuples must be in the form (attribute, tagmarkup): %r" % (tm,) attr, element = tm return _tagmarkup_recurse( element, attr ) if not isinstance(tm,(basestring, bytes)): raise TagMarkupException, "Invalid markup element: %r" % tm # text return [tm], [(attr, len(tm))] def is_mouse_event( ev ): return type(ev) == tuple and len(ev)==4 and ev[0].find("mouse")>=0 def is_mouse_press( ev ): return ev.find("press")>=0 class MetaSuper(type): """adding .__super""" def __init__(cls, name, bases, d): super(MetaSuper, cls).__init__(name, bases, d) if hasattr(cls, "_%s__super" % name): raise AttributeError, "Class has same name as one of its super classes" setattr(cls, "_%s__super" % name, super(cls)) def int_scale(val, val_range, out_range): """ Scale val in the range [0, val_range-1] to an integer in the range [0, out_range-1]. This implementaton uses the "round-half-up" rounding method. >>> "%x" % int_scale(0x7, 0x10, 0x10000) '7777' >>> "%x" % int_scale(0x5f, 0x100, 0x10) '6' >>> int_scale(2, 6, 101) 40 >>> int_scale(1, 3, 4) 2 """ num = int(val * (out_range-1) * 2 + (val_range-1)) dem = ((val_range-1) * 2) # if num % dem == 0 then we are exactly half-way and have rounded up. return num // dem urwid-1.1.1/urwid/compat.py0000664000175000017500000000301212050733643015174 0ustar ianian00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Urwid python compatibility definitions # Copyright (C) 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/ import sys try: # python 2.4 and 2.5 compat bytes = bytes except NameError: bytes = str PYTHON3 = sys.version_info > (3, 0) # for iterating over byte strings: # ord2 calls ord in python2 only # chr2 converts an ordinal value to a length-1 byte string # B returns a byte string in all supported python versions # bytes3 creates a byte string from a list of ordinal values if PYTHON3: ord2 = lambda x: x chr2 = lambda x: bytes([x]) B = lambda x: x.encode('iso8859-1') bytes3 = bytes else: ord2 = ord chr2 = chr B = lambda x: x bytes3 = lambda x: bytes().join([chr(c) for c in x]) urwid-1.1.1/urwid/version.py0000664000175000017500000000014112051304213015362 0ustar ianian00000000000000 VERSION = (1, 1, 1) __version__ = ''.join(['-.'[type(x) == int]+str(x) for x in VERSION])[1:] urwid-1.1.1/urwid/font.py0000775000175000017500000006057512050733643014703 0ustar ianian00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # # Urwid BigText fonts # Copyright (C) 2004-2006 Ian Ward # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Urwid web site: http://excess.org/urwid/ from urwid.escape import SAFE_ASCII_DEC_SPECIAL_RE from urwid.util import apply_target_encoding, str_util from urwid.canvas import TextCanvas def separate_glyphs(gdata, height): """return (dictionary of glyphs, utf8 required)""" gl = gdata.split("\n") del gl[0] del gl[-1] for g in gl: assert "\t" not in g assert len(gl) == height+1, repr(gdata) key_line = gl[0] del gl[0] c = None # current character key_index = 0 # index into character key line end_col = 0 # column position at end of glyph start_col = 0 # column position at start of glyph jl = [0]*height # indexes into lines of gdata (gl) dout = {} utf8_required = False while True: if c is None: if key_index >= len(key_line): break c = key_line[key_index] if key_index < len(key_line) and key_line[key_index] == c: end_col += str_util.get_width(ord(c)) key_index += 1 continue out = [] for k in range(height): l = gl[k] j = jl[k] y = 0 fill = 0 while y < end_col - start_col: if j >= len(l): fill = end_col - start_col - y break y += str_util.get_width(ord(l[j])) j += 1 assert y + fill == end_col - start_col, \ repr((y, fill, end_col)) segment = l[jl[k]:j] if not SAFE_ASCII_DEC_SPECIAL_RE.match(segment): utf8_required = True out.append(segment + " " * fill) jl[k] = j start_col = end_col dout[c] = (y + fill, out) c = None return dout, utf8_required _all_fonts = [] def get_all_fonts(): """ Return a list of (font name, font class) tuples. """ return _all_fonts[:] def add_font(name, cls): _all_fonts.append((name, cls)) class Font(object): def __init__(self): assert self.height assert self.data self.char = {} self.canvas = {} self.utf8_required = False for gdata in self.data: self.add_glyphs(gdata) def add_glyphs(self, gdata): d, utf8_required = separate_glyphs(gdata, self.height) self.char.update(d) self.utf8_required |= utf8_required def characters(self): l = self.char.keys() l.sort() return "".join(l) def char_width(self, c): if self.char.has_key(c): return self.char[c][0] return 0 def char_data(self, c): return self.char[c][1] def render(self, c): if c in self.canvas: return self.canvas[c] width, l = self.char[c] tl = [] csl = [] for d in l: t, cs = apply_target_encoding(d) tl.append(t) csl.append(cs) canv = TextCanvas(tl, None, csl, maxcol=width, check_width=False) self.canvas[c] = canv return canv #safe_palette = u"┘┐┌└┼─├┤┴┬│" #more_palette = u"═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡╢╣╤╥╦╧╨╩╪╫╬○" #block_palette = u"▄#█#▀#▌#▐#▖#▗#▘#▙#▚#▛#▜#▝#▞#▟" class Thin3x3Font(Font): height = 3 data = [u""" 000111222333444555666777888999 ! ┌─┐ ┐ ┌─┐┌─┐ ┐┌─ ┌─ ┌─┐┌─┐┌─┐ │ │ │ │ ┌─┘ ─┤└─┼└─┐├─┐ ┼├─┤└─┤ │ └─┘ ┴ └─ └─┘ ┴ ─┘└─┘ ┴└─┘ ─┘ . """, ur""" "###$$$%%%'*++,--.///:;==???[[\\\]]^__` " ┼┼┌┼┐O /' /.. _┌─┐┌ \ ┐^ ` ┼┼└┼┐ / * ┼ ─ / ., _ ┌┘│ \ │ └┼┘/ O , ./ . └ \ ┘ ── """] add_font("Thin 3x3",Thin3x3Font) class Thin4x3Font(Font): height = 3 data = Thin3x3Font.data + [u""" 0000111122223333444455556666777788889999 ####$$$$ ┌──┐ ┐ ┌──┐┌──┐ ┐┌── ┌── ┌──┐┌──┐┌──┐ ┼─┼┌┼┼┐ │ │ │ ┌──┘ ─┤└──┼└──┐├──┐ ┼├──┤└──┤ ┼─┼└┼┼┐ └──┘ ┴ └── └──┘ ┴ ──┘└──┘ ┴└──┘ ──┘ └┼┼┘ """] add_font("Thin 4x3",Thin4x3Font) class HalfBlock5x4Font(Font): height = 4 data = [u""" 00000111112222233333444445555566666777778888899999 !! ▄▀▀▄ ▄█ ▄▀▀▄ ▄▀▀▄ ▄ █ █▀▀▀ ▄▀▀ ▀▀▀█ ▄▀▀▄ ▄▀▀▄ █ █ █ █ ▄▀ ▄▀ █▄▄█ █▄▄ █▄▄ ▐▌ ▀▄▄▀ ▀▄▄█ █ █ █ █ ▄▀ ▄ █ █ █ █ █ █ █ █ █ ▀ ▀▀ ▀▀▀ ▀▀▀▀ ▀▀ ▀ ▀▀▀ ▀▀ ▀ ▀▀ ▀▀ ▀ """, u''' """######$$$$$$%%%%%&&&&&((()))******++++++,,,-----..////:::;; █▐▌ █ █ ▄▀█▀▄ ▐▌▐▌ ▄▀▄ █ █ ▄ ▄ ▄ ▐▌ ▀█▀█▀ ▀▄█▄ █ ▀▄▀ ▐▌ ▐▌ ▄▄█▄▄ ▄▄█▄▄ ▄▄▄▄ █ ▀ ▀ ▀█▀█▀ ▄ █ █ ▐▌▄ █ ▀▄▌▐▌ ▐▌ ▄▀▄ █ ▐▌ ▀ ▄▀ ▀ ▀ ▀▀▀ ▀ ▀ ▀▀ ▀ ▀ ▄▀ ▀ ▀ ''', ur""" <<<<<=====>>>>>?????@@@@@@[[[[\\\\]]]]^^^^____```{{{{||}}}}~~~~''´´´ ▄▀ ▀▄ ▄▀▀▄ ▄▀▀▀▄ █▀▀ ▐▌ ▀▀█ ▄▀▄ ▀▄ ▄▀ █ ▀▄ ▄ █ ▄▀ ▄▀ ▀▀▀▀ ▀▄ ▄▀ █ █▀█ █ █ █ ▄▀ █ ▀▄ ▐▐▌▌ ▀▄ ▀▀▀▀ ▄▀ ▀ █ ▀▀▀ █ ▐▌ █ █ █ █ ▀ ▀ ▀ ▀ ▀▀▀ ▀▀▀ ▀ ▀▀▀ ▀▀▀▀ ▀ ▀ ▀ """, u''' AAAAABBBBBCCCCCDDDDDEEEEEFFFFFGGGGGHHHHHIIJJJJJKKKKK ▄▀▀▄ █▀▀▄ ▄▀▀▄ █▀▀▄ █▀▀▀ █▀▀▀ ▄▀▀▄ █ █ █ █ █ █ █▄▄█ █▄▄▀ █ █ █ █▄▄ █▄▄ █ █▄▄█ █ █ █▄▀ █ █ █ █ █ ▄ █ █ █ █ █ ▀█ █ █ █ ▄ █ █ ▀▄ ▀ ▀ ▀▀▀ ▀▀ ▀▀▀ ▀▀▀▀ ▀ ▀▀ ▀ ▀ ▀ ▀▀ ▀ ▀ ''', u''' LLLLLMMMMMMNNNNNOOOOOPPPPPQQQQQRRRRRSSSSSTTTTT █ █▄ ▄█ ██ █ ▄▀▀▄ █▀▀▄ ▄▀▀▄ █▀▀▄ ▄▀▀▄ ▀▀█▀▀ █ █ ▀ █ █▐▌█ █ █ █▄▄▀ █ █ █▄▄▀ ▀▄▄ █ █ █ █ █ ██ █ █ █ █ ▌█ █ █ ▄ █ █ ▀▀▀▀ ▀ ▀ ▀ ▀ ▀▀ ▀ ▀▀▌ ▀ ▀ ▀▀ ▀ ''', u''' UUUUUVVVVVVWWWWWWXXXXXXYYYYYYZZZZZ █ █ █ █ █ █ █ █ █ █ ▀▀▀█ █ █ ▐▌ ▐▌ █ ▄ █ ▀▄▀ ▀▄▀ ▄▀ █ █ █ █ ▐▌█▐▌ ▄▀ ▀▄ █ █ ▀▀ ▀ ▀ ▀ ▀ ▀ ▀ ▀▀▀▀ ''', u''' aaaaabbbbbcccccdddddeeeeeffffggggghhhhhiijjjjkkkkk █ █ ▄▀▀ █ ▄ ▄ █ ▀▀▄ █▀▀▄ ▄▀▀▄ ▄▀▀█ ▄▀▀▄ ▀█▀ ▄▀▀▄ █▀▀▄ ▄ ▄ █ ▄▀ ▄▀▀█ █ █ █ ▄ █ █ █▀▀ █ ▀▄▄█ █ █ █ █ █▀▄ ▀▀▀ ▀▀▀ ▀▀ ▀▀▀ ▀▀ ▀ ▄▄▀ ▀ ▀ ▀ ▄▄▀ ▀ ▀ ''', u''' llmmmmmmnnnnnooooopppppqqqqqrrrrssssstttt █ █ █ █▀▄▀▄ █▀▀▄ ▄▀▀▄ █▀▀▄ ▄▀▀█ █▀▀ ▄▀▀▀ ▀█▀ █ █ █ █ █ █ █ █ █ █ █ █ █ ▀▀▄ █ ▀ ▀ ▀ ▀ ▀ ▀▀ █▀▀ ▀▀█ ▀ ▀▀▀ ▀ ''', u''' uuuuuvvvvvwwwwwwxxxxxxyyyyyzzzzz █ █ █ █ █ ▄ █ ▀▄ ▄▀ █ █ ▀▀█▀ █ █ ▐▌▐▌ ▐▌█▐▌ ▄▀▄ ▀▄▄█ ▄▀ ▀▀ ▀▀ ▀ ▀ ▀ ▀ ▄▄▀ ▀▀▀▀ '''] add_font("Half Block 5x4",HalfBlock5x4Font) class HalfBlock6x5Font(Font): height = 5 data = [u""" 000000111111222222333333444444555555666666777777888888999999 ..:://// ▄▀▀▀▄ ▄█ ▄▀▀▀▄ ▄▀▀▀▄ ▄ █ █▀▀▀▀ ▄▀▀▀ ▀▀▀▀█ ▄▀▀▀▄ ▄▀▀▀▄ █ █ █ █ █ █ █ █ █ █ ▐▌ █ █ █ █ ▀ ▐▌ █ █ █ ▄▀ ▀▀▄ ▀▀▀█▀ ▀▀▀▀▄ █▀▀▀▄ █ ▄▀▀▀▄ ▀▀▀█ ▄ █ █ █ █ ▄▀ ▄ █ █ █ █ █ ▐▌ █ █ █ ▐▌ ▀▀▀ ▀▀▀ ▀▀▀▀▀ ▀▀▀ ▀ ▀▀▀▀ ▀▀▀ ▀ ▀▀▀ ▀▀▀ ▀ ▀ """] add_font("Half Block 6x5",HalfBlock6x5Font) class HalfBlockHeavy6x5Font(Font): height = 5 data = [u""" 000000111111222222333333444444555555666666777777888888999999 ..:://// ▄███▄ ▐█▌ ▄███▄ ▄███▄ █▌ █████ ▄███▄ █████ ▄███▄ ▄███▄ █▌ █▌ ▐█ ▀█▌ ▀ ▐█ ▀ ▐█ █▌ █▌ █▌ █▌ █▌ █▌ ▐█ █▌ ▐█ █▌ ▐█ █▌ ▐█ █▌ ▄█▀ ██▌ █████ ████▄ ████▄ ▐█ ▐███▌ ▀████ █▌ █▌ ▐█ █▌ ▄█▀ ▄ ▐█ █▌ ▐█ █▌ ▐█ █▌ █▌ ▐█ ▐█ █▌▐█ ▀███▀ ███▌ █████ ▀███▀ █▌ ████▀ ▀███▀ ▐█ ▀███▀ ▀███▀ █▌ █▌ """] add_font("Half Block Heavy 6x5",HalfBlockHeavy6x5Font) class Thin6x6Font(Font): height = 6 data = [u""" 000000111111222222333333444444555555666666777777888888999999'' ┌───┐ ┐ ┌───┐ ┌───┐ ┐ ┌─── ┌─── ┌───┐ ┌───┐ ┌───┐ │ │ │ │ │ │ ┌ │ │ │ │ │ │ │ │ │ / │ │ ┌───┘ ─┤ └──┼─ └───┐ ├───┐ ┼ ├───┤ └───┤ │ │ │ │ │ │ │ │ │ │ │ │ │ └───┘ ┴ └─── └───┘ ┴ ───┘ └───┘ ┴ └───┘ ───┘ """, ur''' !! """######$$$$$$%%%%%%&&&&&&((()))******++++++ │ ││ ┌ ┌ ┌─┼─┐ ┌┐ / ┌─┐ / \ │ ─┼─┼─ │ │ └┘ / │ │ │ │ \ / │ │ │ │ └─┼─┐ / ┌─\┘ │ │ ──X── ──┼── │ ─┼─┼─ │ │ / ┌┐ │ \, │ │ / \ │ . ┘ ┘ └─┼─┘ / └┘ └───\ \ / ''', ur""" ,,-----..//////::;;<<<<=====>>>>??????@@@@@@ / ┌───┐ ┌───┐ / . . / ──── \ │ │┌──┤ ──── / / \ ┌─┘ ││ │ / . , \ ──── / │ │└──┘ , . / \ / . └───┘ """, ur""" [[\\\\\\]]^^^____``{{||}}~~~~~~ ┌ \ ┐ /\ \ ┌ │ ┐ │ \ │ │ │ │ ┌─┐ │ \ │ ┤ │ ├ └─┘ │ \ │ │ │ │ └ \ ┘ ──── └ │ ┘ """, u""" AAAAAABBBBBBCCCCCCDDDDDDEEEEEEFFFFFFGGGGGGHHHHHHIIJJJJJJ ┌───┐ ┬───┐ ┌───┐ ┬───┐ ┬───┐ ┬───┐ ┌───┐ ┬ ┬ ┬ ┬ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├───┤ ├───┤ │ │ │ ├── ├── │ ──┬ ├───┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┬ │ ┴ ┴ ┴───┘ └───┘ ┴───┘ ┴───┘ ┴ └───┘ ┴ ┴ ┴ └───┘ """, u""" KKKKKKLLLLLLMMMMMMNNNNNNOOOOOOPPPPPPQQQQQQRRRRRRSSSSSS ┬ ┬ ┬ ┌─┬─┐ ┬─┐ ┬ ┌───┐ ┬───┐ ┌───┐ ┬───┐ ┌───┐ │ ┌─┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─┴┐ │ │ │ │ │ │ │ │ │ ├───┘ │ │ ├─┬─┘ └───┐ │ └┐ │ │ │ │ │ │ │ │ │ │ ┐│ │ └─┐ │ ┴ ┴ ┴───┘ ┴ ┴ ┴ └─┴ └───┘ ┴ └──┼┘ ┴ ┴ └───┘ └ """, u""" TTTTTTUUUUUUVVVVVVWWWWWWXXXXXXYYYYYYZZZZZZ ┌─┬─┐ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┌───┐ │ │ │ │ │ │ │ └┐ ┌┘ │ │ ┌─┘ │ │ │ │ │ │ │ │ ├─┤ └─┬─┘ ┌┘ │ │ │ └┐ ┌┘ │ │ │ ┌┘ └┐ │ ┌┘ ┴ └───┘ └─┘ └─┴─┘ ┴ ┴ ┴ └───┘ """, u""" aaaaaabbbbbbccccccddddddeeeeeefffgggggghhhhhhiijjj ┌─┐ │ │ │ │ . . ┌───┐ ├───┐ ┌───┐ ┌───┤ ┌───┐ ┼ ┌───┐ ├───┐ ┐ ┐ ┌───┤ │ │ │ │ │ ├───┘ │ │ │ │ │ │ │ └───┴ └───┘ └───┘ └───┘ └───┘ ┴ └───┤ ┴ ┴ ┴ │ └───┘ ─┘ """, u""" kkkkkkllmmmmmmnnnnnnooooooppppppqqqqqqrrrrrssssss │ │ │ ┌─ │ ┬─┬─┐ ┬───┐ ┌───┐ ┌───┐ ┌───┐ ┬──┐ ┌───┐ ├─┴┐ │ │ │ │ │ │ │ │ │ │ │ │ │ └───┐ ┴ └─ └ ┴ ┴ ┴ ┴ └───┘ ├───┘ └───┤ ┴ └───┘ │ │ """, u""" ttttuuuuuuvvvvvvwwwwwwxxxxxxyyyyyyzzzzzz │ ─┼─ ┬ ┬ ┬ ┬ ┬ ┬ ─┐ ┌─ ┬ ┬ ────┬ │ │ │ └┐ ┌┘ │ │ │ ├─┤ │ │ ┌───┘ └─ └───┴ └─┘ └─┴─┘ ─┘ └─ └───┤ ┴──── └───┘ """] add_font("Thin 6x6",Thin6x6Font) class HalfBlock7x7Font(Font): height = 7 data = [u""" 0000000111111122222223333333444444455555556666666777777788888889999999''' ▄███▄ ▐█▌ ▄███▄ ▄███▄ █▌ ▐█████▌ ▄███▄ ▐█████▌ ▄███▄ ▄███▄ ▐█ ▐█ █▌ ▀█▌ ▐█ █▌▐█ █▌▐█ █▌ ▐█ ▐█ ▐█ ▐█ █▌▐█ █▌▐█ ▐█ ▐ █▌ █▌ █▌ ▐██ ▐█████▌▐████▄ ▐████▄ █▌ █████ ▀████▌ ▐█ ▌ █▌ █▌ ▄█▀ █▌ █▌ █▌▐█ █▌ ▐█ ▐█ █▌ █▌ ▐█ █▌ █▌ ▄█▀ ▐█ █▌ █▌ █▌▐█ █▌ █▌ ▐█ █▌ █▌ ▀███▀ ███▌ ▐█████▌ ▀███▀ █▌ ▐████▀ ▀███▀ ▐█ ▀███▀ ▀███▀ """, u''' !!! """""#######$$$$$$$%%%%%%%&&&&&&&(((())))*******++++++ ▐█ ▐█ █▌ ▐█ █▌ █ ▄ █▌ ▄█▄ █▌▐█ ▄▄ ▄▄ ▐█ ▐█ █▌▐█████▌ ▄███▄ ▐█▌▐█ ▐█ █▌ ▐█ █▌ ▀█▄█▀ ▐█ ▐█ ▐█ █▌ ▐█▄█▄▄ ▀ █▌ ███ █▌ ▐█ ▐█████▌ ████▌ ▐█ ▐█████▌ ▀▀█▀█▌ ▐█ ▄ ███▌▄ █▌ ▐█ ▄█▀█▄ ▐█ ▐█ █▌ ▀███▀ █▌▐█▌▐█ █▌ ▐█ █▌ ▀▀ ▀▀ ▐█ █ ▐█ ▀ ▀██▀█▌ █▌▐█ ''', u""" ,,,------.../////:::;;;<<<<<<<======>>>>>>>???????@@@@@@@ █▌ ▄█▌ ▐█▄ ▄███▄ ▄███▄ ▐█ ▐█ ▐█ ▄█▀ ▐████▌ ▀█▄ ▐█ █▌▐█ ▄▄█▌ ▐████▌ █▌ ▐██ ██▌ █▌ ▐█▐█▀█▌ ▐█ ▐█ ▐█ ▀█▄ ▐████▌ ▄█▀ █▌ ▐█▐█▄█▌ █▌ ▀ ▀█▌ ▐█▀ ▐█ ▀▀▀ ▐█ ▐█ ▐█ █▌ ▀███▀ ▀ """, ur""" [[[[\\\\\]]]]^^^^^^^_____```{{{{{|||}}}}}~~~~~~~´´´ ▐██▌▐█ ▐██▌ ▐█▌ ▐█ █▌▐█ ▐█ █▌ ▐█ █▌ █▌ ▐█ █▌ █▌ █▌ ▐█ ▐█ ▄▄ ▐█ ▐█ ▐█ █▌▐█ █▌ ▄█▌ ▐█ ▐█▄ ▐▀▀█▄▄▌ ▐█ █▌ █▌ ▀█▌ ▐█ ▐█▀ ▀▀ ▐█ ▐█ █▌ █▌ ▐█ ▐█ ▐██▌ █▌▐██▌ █████ █▌▐█ ▐█ """, u""" AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIJJJJJJJ ▄███▄ ▐████▄ ▄███▄ ▐████▄ ▐█████▌▐█████▌ ▄███▄ ▐█ █▌ ██▌ █▌ ▐█ █▌▐█ █▌▐█ ▐█ █▌▐█ ▐█ ▐█ ▐█ █▌ ▐█ █▌ ▐█████▌▐█████ ▐█ ▐█ █▌▐████ ▐████ ▐█ ▐█████▌ ▐█ █▌ ▐█ █▌▐█ █▌▐█ ▐█ █▌▐█ ▐█ ▐█ ██▌▐█ █▌ ▐█ █▌ ▐█ █▌▐█ █▌▐█ ▐█ █▌▐█ ▐█ ▐█ █▌▐█ █▌ ▐█ ▐█ █▌ ▐█ █▌▐████▀ ▀███▀ ▐████▀ ▐█████▌▐█ ▀███▀ ▐█ █▌ ██▌ ▀███▀ """, u""" KKKKKKKLLLLLLLMMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSS ▐█ █▌▐█ ▄█▌▐█▄ ▐██ █▌ ▄███▄ ▐████▄ ▄███▄ ▐████▄ ▄███▄ ▐█ █▌ ▐█ ▐█ ▐▌ █▌▐██▌ █▌▐█ █▌▐█ █▌▐█ █▌▐█ █▌▐█ ▐█▄█▌ ▐█ ▐█ ▐▌ █▌▐█▐█ █▌▐█ █▌▐████▀ ▐█ █▌▐█████ ▀███▄ ▐█▀█▌ ▐█ ▐█ █▌▐█ █▌█▌▐█ █▌▐█ ▐█ █▌▐█ █▌ █▌ ▐█ █▌ ▐█ ▐█ █▌▐█ ▐██▌▐█ █▌▐█ ▐█ █▌█▌▐█ █▌ █▌ ▐█ █▌▐█████▌▐█ █▌▐█ ██▌ ▀███▀ ▐█ ▀███▀ ▐█ █▌ ▀███▀ ▀▀ """, u""" TTTTTTTUUUUUUUVVVVVVVWWWWWWWWXXXXXXXYYYYYYYZZZZZZZ █████▌▐█ █▌▐█ █▌▐█ █▌▐█ █▌ █▌ █▌▐█████▌ █▌ ▐█ █▌ █▌ ▐█ ▐█ █▌ ▐█ █▌ ▐█ ▐█ █▌ █▌ ▐█ █▌ ▐█ █▌ ▐█ █▌ ▐█▌ ▐██ █▌ █▌ ▐█ █▌ ███ ▐█ ▐▌ █▌ ███ █▌ █▌ █▌ ▐█ █▌ ▐█▌ ▐█ ▐▌ █▌ █▌ ▐█ █▌ █▌ █▌ ▀███▀ █ ▀█▌▐█▀ ▐█ █▌ █▌ ▐█████▌ """, u""" aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffggggggghhhhhhhiiijjjj ▐█ █▌ ▄█▌ ▐█ █▌ █▌ ▐█ █▌ ▐█ ▐█ ▄███▄ ▐████▄ ▄███▄ ▄████▌ ▄███▄ ▐███ ▄███▄ ▐████▄ ▐█▌ ▐█▌ ▄▄▄█▌▐█ █▌▐█ ▐█ █▌▐█▄▄▄█▌ ▐█ ▐█ █▌▐█ █▌ █▌ █▌ ▐█▀▀▀█▌▐█ █▌▐█ ▐█ █▌▐█▀▀▀ ▐█ ▐█▄▄▄█▌▐█ █▌ █▌ █▌ ▀████▌▐████▀ ▀███▀ ▀████▌ ▀███▀ ▐█ ▀▀▀█▌▐█ █▌ █▌ █▌ ▀███▀ ▐██ """, u""" kkkkkkkllllmmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrsssssss ▐█ ██ ▐█ ▐█ ▐█ ▄█▌ ▐█ ▄█▌▐█▄ ▐████▄ ▄███▄ ▐████▄ ▄████▌ ▄███▌ ▄███▄ ▐█▄█▀ ▐█ ▐█ ▐▌ █▌▐█ █▌▐█ █▌▐█ █▌▐█ █▌▐█ ▐█▄▄▄ ▐█▀▀█▄ ▐█ ▐█ ▐▌ █▌▐█ █▌▐█ █▌▐█ █▌▐█ █▌▐█ ▀▀▀█▌ ▐█ █▌ ▐█▌▐█ █▌▐█ █▌ ▀███▀ ▐████▀ ▀████▌▐█ ▀███▀ ▐█ █▌ """, u""" tttttuuuuuuuvvvvvvvwwwwwwwwxxxxxxxyyyyyyyzzzzzzz █▌ █▌ ███▌▐█ █▌▐█ █▌▐█ █▌▐█ █▌▐█ █▌▐█████▌ █▌ ▐█ █▌ █▌ ▐█ ▐█ █▌ ▀█▄█▀ ▐█ █▌ ▄█▀ █▌ ▐█ █▌ ███ ▐█ ▐▌ █▌ ▄█▀█▄ ▐█▄▄▄█▌ ▄█▀ █▌ ▀███▀ ▐█▌ ▀█▌▐█▀ ▐█ █▌ ▀▀▀█▌▐█████▌ ▀███▀ """] 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 "----------------" for n,cls in l: f = cls() u = "" if f.utf8_required: u = "(U)" print ("%-20s %3s " % (n,u)), c = f.characters() if c == all_ascii: print "Full ASCII" elif c.startswith(all_ascii): print "Full ASCII + " + c[len(all_ascii):] else: print "Characters: " + c urwid-1.1.1/urwid/main_loop.py0000775000175000017500000012602612051303575015702 0ustar ianian00000000000000#!/usr/bin/python # # Urwid main loop code # Copyright (C) 2004-2012 Ian Ward # Copyright (C) 2008 Walter Mundt # Copyright (C) 2009 Andrew Psaltis # # 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/ import time import heapq import select import fcntl import os from urwid.util import is_mouse_event from urwid.compat import PYTHON3 from urwid.command_map import command_map, REDRAW_SCREEN from urwid.wimp import PopUpTarget from urwid import signals from urwid.display_common import INPUT_DESCRIPTORS_CHANGED PIPE_BUFFER_READ_SIZE = 4096 # can expect this much on Linux, so try for that class ExitMainLoop(Exception): """ When this exception is raised within a main loop the main loop will exit cleanly. """ pass class MainLoop(object): """ This is the standard main loop implementation for a single interactive session. :param widget: the topmost widget used for painting the screen, stored as :attr:`widget` and may be modified. Must be a box widget. :type widget: widget instance :param palette: initial palette for screen :type palette: iterable of palette entries :param screen: screen to use, default is a new :class:`raw_display.Screen` instance; stored as :attr:`screen` :type screen: display module screen instance :param handle_mouse: ``True`` to ask :attr:`.screen` to process mouse events :type handle_mouse: bool :param input_filter: a function to filter input before sending it to :attr:`.widget`, called from :meth:`.input_filter` :type input_filter: callable :param unhandled_input: a function called when input is not handled by :attr:`.widget`, called from :meth:`.unhandled_input` :type unhandled_input: callable :param event_loop: if :attr:`.screen` supports external an event loop it may be given here, default is a new :class:`SelectEventLoop` instance; stored as :attr:`.event_loop` :type event_loop: event loop instance :param pop_ups: `True` to wrap :attr:`.widget` with a :class:`PopUpTarget` instance to allow any widget to open a pop-up anywhere on the screen :type pop_ups: boolean .. attribute:: screen The screen object this main loop uses for screen updates and reading input .. attribute:: event_loop The event loop object this main loop uses for waiting on alarms and IO """ def __init__(self, widget, palette=(), screen=None, handle_mouse=True, input_filter=None, unhandled_input=None, event_loop=None, pop_ups=False): self._widget = widget self.handle_mouse = handle_mouse self.pop_ups = pop_ups # triggers property setting side-effect if not screen: from urwid import raw_display screen = raw_display.Screen() if palette: screen.register_palette(palette) self.screen = screen self.screen_size = None self._unhandled_input = unhandled_input self._input_filter = input_filter if not hasattr(screen, 'get_input_descriptors' ) and event_loop is not None: raise NotImplementedError("screen object passed " "%r does not support external event loops" % (screen,)) if event_loop is None: event_loop = SelectEventLoop() self.event_loop = event_loop self._input_timeout = None self._watch_pipes = {} def _set_widget(self, widget): self._widget = widget if self.pop_ups: self._topmost_widget.original_widget = self._widget else: self._topmost_widget = self._widget widget = property(lambda self:self._widget, _set_widget, doc= """ Property for the topmost widget used to draw the screen. This must be a box widget. """) def _set_pop_ups(self, pop_ups): self._pop_ups = pop_ups if pop_ups: self._topmost_widget = PopUpTarget(self._widget) else: self._topmost_widget = self._widget pop_ups = property(lambda self:self._pop_ups, _set_pop_ups) def set_alarm_in(self, sec, callback, user_data=None): """ Schedule an alarm in *sec* seconds that will call *callback* from the within the :meth:`run` method. :param sec: seconds until alarm :type sec: float :param callback: function to call with two parameters: this main loop object and *user_data* :type callback: callable """ def cb(): callback(self, user_data) return self.event_loop.alarm(sec, cb) def set_alarm_at(self, tm, callback, user_data=None): """ Schedule an alarm at *tm* time that will call *callback* from the within the :meth`run` function. Returns a handle that may be passed to :meth:`remove_alarm`. :param tm: time to call callback e.g. ``time.time() + 5`` :type tm: float :param callback: function to call with two parameters: this main loop object and *user_data* :type callback: callable """ def cb(): callback(self, user_data) return self.event_loop.alarm(tm - time.time(), cb) def remove_alarm(self, handle): """ Remove an alarm. Return ``True`` if *handle* was found, ``False`` otherwise. """ return self.event_loop.remove_alarm(handle) def watch_pipe(self, callback): """ Create a pipe for use by a subprocess or thread to trigger a callback in the process/thread running the main loop. :param callback: function taking one parameter to call from within the process/thread running the main loop :type callback: callable This method returns a file descriptor attached to the write end of a pipe. The read end of the pipe is added to the list of files :attr:`event_loop` is watching. When data is written to the pipe the callback function will be called and passed a single value containing data read from the pipe. This method may be used any time you want to update widgets from another thread or subprocess. Data may be written to the returned file descriptor with ``os.write(fd, data)``. Ensure that data is less than 512 bytes (or 4K on Linux) so that the callback will be triggered just once with the complete value of data passed in. If the callback returns ``False`` then the watch will be removed from :attr:`event_loop` and the read end of the pipe will be closed. You are responsible for closing the write end of the pipe with ``os.close(fd)``. """ pipe_rd, pipe_wr = os.pipe() fcntl.fcntl(pipe_rd, fcntl.F_SETFL, os.O_NONBLOCK) watch_handle = None def cb(): data = os.read(pipe_rd, PIPE_BUFFER_READ_SIZE) rval = callback(data) if rval is False: self.event_loop.remove_watch_file(watch_handle) os.close(pipe_rd) watch_handle = self.event_loop.watch_file(pipe_rd, cb) self._watch_pipes[pipe_wr] = (watch_handle, pipe_rd) return pipe_wr def remove_watch_pipe(self, write_fd): """ Close the read end of the pipe and remove the watch created by :meth:`watch_pipe`. You are responsible for closing the write end of the pipe. Returns ``True`` if the watch pipe exists, ``False`` otherwise """ try: watch_handle, pipe_rd = self._watch_pipes.pop(write_fd) except KeyError: return False if not self.event_loop.remove_watch_file(watch_handle): return False os.close(pipe_rd) return True def watch_file(self, fd, callback): """ Call *callback* when *fd* has some data to read. No parameters are passed to callback. Returns a handle that may be passed to :meth:`remove_watch_file`. """ return self.event_loop.watch_file(fd, callback) def remove_watch_file(self, handle): """ Remove a watch file. Returns ``True`` if the watch file exists, ``False`` otherwise. """ return self.event_loop.remove_watch_file(handle) def run(self): """ Start the main loop handling input events and updating the screen. The loop will continue until an :exc:`ExitMainLoop` exception is raised. This method will use :attr:`screen`'s run_wrapper() method if :attr:`screen`'s start() method has not already been called. """ try: if self.screen.started: self._run() else: self.screen.run_wrapper(self._run) except ExitMainLoop: pass def _test_run(self): """ >>> w = _refl("widget") # _refl prints out function calls >>> w.render_rval = "fake canvas" # *_rval is used for return values >>> scr = _refl("screen") >>> scr.get_input_descriptors_rval = [42] >>> scr.get_cols_rows_rval = (20, 10) >>> scr.started = True >>> scr._urwid_signals = {} >>> evl = _refl("event_loop") >>> evl.enter_idle_rval = 1 >>> evl.watch_file_rval = 2 >>> ml = MainLoop(w, [], scr, event_loop=evl) >>> ml.run() # doctest:+ELLIPSIS screen.set_mouse_tracking() screen.get_cols_rows() widget.render((20, 10), focus=True) screen.draw_screen((20, 10), 'fake canvas') screen.get_input_descriptors() event_loop.watch_file(42, ) event_loop.enter_idle() event_loop.run() event_loop.remove_enter_idle(1) event_loop.remove_watch_file(2) >>> scr.started = False >>> ml.run() # doctest:+ELLIPSIS screen.run_wrapper() """ def _run(self): if self.handle_mouse: self.screen.set_mouse_tracking() if not hasattr(self.screen, 'get_input_descriptors'): return self._run_screen_event_loop() self.draw_screen() fd_handles = [] def reset_input_descriptors(only_remove=False): for handle in fd_handles: self.event_loop.remove_watch_file(handle) if only_remove: del fd_handles[:] else: fd_handles[:] = [ self.event_loop.watch_file(fd, self._update) for fd in self.screen.get_input_descriptors()] if not fd_handles and self._input_timeout is not None: self.event_loop.remove_alarm(self._input_timeout) try: signals.connect_signal(self.screen, INPUT_DESCRIPTORS_CHANGED, reset_input_descriptors) except NameError: pass # watch our input descriptors reset_input_descriptors() idle_handle = self.event_loop.enter_idle(self.entering_idle) # Go.. self.event_loop.run() # tidy up self.event_loop.remove_enter_idle(idle_handle) reset_input_descriptors(True) signals.disconnect_signal(self.screen, INPUT_DESCRIPTORS_CHANGED, reset_input_descriptors) def _update(self, timeout=False): """ >>> w = _refl("widget") >>> w.selectable_rval = True >>> w.mouse_event_rval = True >>> scr = _refl("screen") >>> scr.get_cols_rows_rval = (15, 5) >>> scr.get_input_nonblocking_rval = 1, ['y'], [121] >>> evl = _refl("event_loop") >>> ml = MainLoop(w, [], scr, event_loop=evl) >>> ml._input_timeout = "old timeout" >>> ml._update() # doctest:+ELLIPSIS event_loop.remove_alarm('old timeout') screen.get_input_nonblocking() event_loop.alarm(1, ) screen.get_cols_rows() widget.selectable() widget.keypress((15, 5), 'y') >>> scr.get_input_nonblocking_rval = None, [("mouse press", 1, 5, 4) ... ], [] >>> ml._update() screen.get_input_nonblocking() widget.mouse_event((15, 5), 'mouse press', 1, 5, 4, focus=True) >>> scr.get_input_nonblocking_rval = None, [], [] >>> ml._update() screen.get_input_nonblocking() """ if self._input_timeout is not None and not timeout: # cancel the timeout, something else triggered the update self.event_loop.remove_alarm(self._input_timeout) self._input_timeout = None max_wait, keys, raw = self.screen.get_input_nonblocking() if max_wait is not None: # if get_input_nonblocking wants to be called back # make sure it happens with an alarm self._input_timeout = self.event_loop.alarm(max_wait, lambda: self._update(timeout=True)) keys = self.input_filter(keys, raw) if keys: self.process_input(keys) if 'window resize' in keys: self.screen_size = None def _run_screen_event_loop(self): """ This method is used when the screen does not support using external event loops. The alarms stored in the SelectEventLoop in :attr:`event_loop` are modified by this method. """ next_alarm = None while True: self.draw_screen() if not next_alarm and self.event_loop._alarms: next_alarm = heapq.heappop(self.event_loop._alarms) keys = None while not keys: if next_alarm: sec = max(0, next_alarm[0] - time.time()) self.screen.set_input_timeouts(sec) else: self.screen.set_input_timeouts(None) keys, raw = self.screen.get_input(True) if not keys and next_alarm: sec = next_alarm[0] - time.time() if sec <= 0: break keys = self.input_filter(keys, raw) if keys: self.process_input(keys) while next_alarm: sec = next_alarm[0] - time.time() if sec > 0: break tm, callback = next_alarm callback() if self.event_loop._alarms: next_alarm = heapq.heappop(self.event_loop._alarms) else: next_alarm = None if 'window resize' in keys: self.screen_size = None def _test_run_screen_event_loop(self): """ >>> w = _refl("widget") >>> scr = _refl("screen") >>> scr.get_cols_rows_rval = (10, 5) >>> scr.get_input_rval = [], [] >>> ml = MainLoop(w, screen=scr) >>> def stop_now(loop, data): ... raise ExitMainLoop() >>> handle = ml.set_alarm_in(0, stop_now) >>> try: ... ml._run_screen_event_loop() ... except ExitMainLoop: ... pass screen.get_cols_rows() widget.render((10, 5), focus=True) screen.draw_screen((10, 5), None) screen.set_input_timeouts(0) screen.get_input(True) """ def process_input(self, keys): """ This method will pass keyboard input and mouse events to :attr:`widget`. This method is called automatically from the :meth:`run` method when there is input, but may also be called to simulate input from the user. *keys* is a list of input returned from :attr:`screen`'s get_input() or get_input_nonblocking() methods. Returns ``True`` if any key was handled by a widget or the :meth:`unhandled_input` method. """ if not self.screen_size: self.screen_size = self.screen.get_cols_rows() something_handled = False for k in keys: if k == 'window resize': continue if is_mouse_event(k): event, button, col, row = k if self._topmost_widget.mouse_event(self.screen_size, event, button, col, row, focus=True ): k = None elif self._topmost_widget.selectable(): k = self._topmost_widget.keypress(self.screen_size, k) if k: if command_map[k] == REDRAW_SCREEN: self.screen.clear() something_handled = True else: something_handled |= bool(self.unhandled_input(k)) else: something_handled = True return something_handled def _test_process_input(self): """ >>> w = _refl("widget") >>> w.selectable_rval = True >>> scr = _refl("screen") >>> scr.get_cols_rows_rval = (10, 5) >>> ml = MainLoop(w, [], scr) >>> ml.process_input(['enter', ('mouse drag', 1, 14, 20)]) screen.get_cols_rows() widget.selectable() widget.keypress((10, 5), 'enter') widget.mouse_event((10, 5), 'mouse drag', 1, 14, 20, focus=True) True """ def input_filter(self, keys, raw): """ This function is passed each all the input events and raw keystroke values. These values are passed to the *input_filter* function passed to the constructor. That function must return a list of keys to be passed to the widgets to handle. If no *input_filter* was defined this implementation will return all the input events. """ if self._input_filter: return self._input_filter(keys, raw) return keys def unhandled_input(self, input): """ This function is called with any input that was not handled by the widgets, and calls the *unhandled_input* function passed to the constructor. If no *unhandled_input* was defined then the input will be ignored. *input* is the keyboard or mouse input. The *unhandled_input* function should return ``True`` if it handled the input. """ if self._unhandled_input: return self._unhandled_input(input) def entering_idle(self): """ This method is called whenever the event loop is about to enter the idle state. :meth:`draw_screen` is called here to update the screen when anything has changed. """ if self.screen.started: self.draw_screen() def draw_screen(self): """ Render the widgets and paint the screen. This method is called automatically from :meth:`entering_idle`. If you modify the widgets displayed outside of handling input or responding to an alarm you will need to call this method yourself to repaint the screen. """ if not self.screen_size: self.screen_size = self.screen.get_cols_rows() canvas = self._topmost_widget.render(self.screen_size, focus=True) self.screen.draw_screen(self.screen_size, canvas) class SelectEventLoop(object): """ Event loop based on :func:`select.select` """ def __init__(self): self._alarms = [] self._watch_files = {} self._idle_handle = 0 self._idle_callbacks = {} def _test_event_loop(self): """ >>> import os >>> rd, wr = os.pipe() >>> evl = SelectEventLoop() >>> def step1(): ... print "writing" ... os.write(wr, "hi".encode('ascii')) >>> def step2(): ... print os.read(rd, 2).decode('ascii') ... raise ExitMainLoop >>> handle = evl.alarm(0, step1) >>> handle = evl.watch_file(rd, step2) >>> evl.run() writing hi """ def alarm(self, seconds, callback): """ Call callback() given time from from now. No parameters are passed to callback. Returns a handle that may be passed to remove_alarm() seconds -- floating point time to wait before calling callback callback -- function to call from event loop """ tm = time.time() + seconds heapq.heappush(self._alarms, (tm, callback)) return (tm, callback) def remove_alarm(self, handle): """ Remove an alarm. Returns True if the alarm exists, False otherwise """ try: self._alarms.remove(handle) heapq.heapify(self._alarms) return True except ValueError: return False def _test_remove_alarm(self): """ >>> evl = SelectEventLoop() >>> handle = evl.alarm(50, lambda: None) >>> evl.remove_alarm(handle) True >>> evl.remove_alarm(handle) False """ def watch_file(self, fd, callback): """ Call callback() when fd has some data to read. No parameters are passed to callback. Returns a handle that may be passed to remove_watch_file() fd -- file descriptor to watch for input callback -- function to call when input is available """ self._watch_files[fd] = callback return fd def remove_watch_file(self, handle): """ Remove an input file. Returns True if the input file exists, False otherwise """ if handle in self._watch_files: del self._watch_files[handle] return True return False def _test_remove_watch_file(self): """ >>> evl = SelectEventLoop() >>> handle = evl.watch_file(5, lambda: None) >>> evl.remove_watch_file(handle) True >>> evl.remove_watch_file(handle) False """ def enter_idle(self, callback): """ Add a callback for entering idle. Returns a handle that may be passed to remove_idle() """ self._idle_handle += 1 self._idle_callbacks[self._idle_handle] = callback return self._idle_handle def remove_enter_idle(self, handle): """ Remove an idle callback. Returns True if the handle was removed. """ try: del self._idle_callbacks[handle] except KeyError: return False return True def _entering_idle(self): """ Call all the registered idle callbacks. """ for callback in self._idle_callbacks.values(): callback() def run(self): """ Start the event loop. Exit the loop when any callback raises an exception. If ExitMainLoop is raised, exit cleanly. """ try: self._did_something = True while True: try: self._loop() except select.error, e: if e.args[0] != 4: # not just something we need to retry raise except ExitMainLoop: pass def _test_run(self): """ >>> import os >>> rd, wr = os.pipe() >>> os.write(wr, "data".encode('ascii')) # something to read from rd 4 >>> evl = SelectEventLoop() >>> def say_hello(): ... print "hello" >>> def say_waiting(): ... print "waiting" >>> def exit_clean(): ... print "clean exit" ... raise ExitMainLoop >>> def exit_error(): ... 1/0 >>> handle = evl.alarm(0.01, exit_clean) >>> handle = evl.alarm(0.005, say_hello) >>> evl.enter_idle(say_waiting) 1 >>> evl.run() waiting hello waiting clean exit >>> handle = evl.watch_file(rd, exit_clean) >>> evl.run() clean exit >>> evl.remove_watch_file(handle) True >>> handle = evl.alarm(0, exit_error) >>> evl.run() Traceback (most recent call last): ... ZeroDivisionError: integer division or modulo by zero >>> handle = evl.watch_file(rd, exit_error) >>> evl.run() Traceback (most recent call last): ... ZeroDivisionError: integer division or modulo by zero """ def _loop(self): """ A single iteration of the event loop """ fds = self._watch_files.keys() if self._alarms or self._did_something: if self._alarms: tm = self._alarms[0][0] timeout = max(0, tm - time.time()) if self._did_something and (not self._alarms or (self._alarms and timeout > 0)): timeout = 0 tm = 'idle' ready, w, err = select.select(fds, [], fds, timeout) else: tm = None ready, w, err = select.select(fds, [], fds) if not ready: if tm == 'idle': self._entering_idle() self._did_something = False elif tm is not None: # must have been a timeout tm, alarm_callback = self._alarms.pop(0) alarm_callback() self._did_something = True for fd in ready: self._watch_files[fd]() self._did_something = True if not PYTHON3: class GLibEventLoop(object): """ Event loop based on gobject.MainLoop """ def __init__(self): import gobject self.gobject = gobject self._alarms = [] self._watch_files = {} self._idle_handle = 0 self._glib_idle_enabled = False # have we called glib.idle_add? self._idle_callbacks = {} self._loop = self.gobject.MainLoop() self._exc_info = None self._enable_glib_idle() def _test_event_loop(self): """ >>> import os >>> rd, wr = os.pipe() >>> evl = GLibEventLoop() >>> def step1(): ... print "writing" ... os.write(wr, "hi") >>> def step2(): ... print os.read(rd, 2) ... raise ExitMainLoop >>> handle = evl.alarm(0, step1) >>> handle = evl.watch_file(rd, step2) >>> evl.run() writing hi """ def alarm(self, seconds, callback): """ Call callback() given time from from now. No parameters are passed to callback. Returns a handle that may be passed to remove_alarm() seconds -- floating point time to wait before calling callback callback -- function to call from event loop """ @self.handle_exit def ret_false(): callback() self._enable_glib_idle() return False fd = self.gobject.timeout_add(int(seconds*1000), ret_false) self._alarms.append(fd) return (fd, callback) def remove_alarm(self, handle): """ Remove an alarm. Returns True if the alarm exists, False otherwise """ try: self._alarms.remove(handle[0]) self.gobject.source_remove(handle[0]) return True except ValueError: return False def _test_remove_alarm(self): """ >>> evl = GLibEventLoop() >>> handle = evl.alarm(50, lambda: None) >>> evl.remove_alarm(handle) True >>> evl.remove_alarm(handle) False """ def watch_file(self, fd, callback): """ Call callback() when fd has some data to read. No parameters are passed to callback. Returns a handle that may be passed to remove_watch_file() fd -- file descriptor to watch for input callback -- function to call when input is available """ @self.handle_exit def io_callback(source, cb_condition): callback() self._enable_glib_idle() return True self._watch_files[fd] = \ self.gobject.io_add_watch(fd,self.gobject.IO_IN,io_callback) return fd def remove_watch_file(self, handle): """ Remove an input file. Returns True if the input file exists, False otherwise """ if handle in self._watch_files: self.gobject.source_remove(self._watch_files[handle]) del self._watch_files[handle] return True return False def _test_remove_watch_file(self): """ >>> evl = GLibEventLoop() >>> handle = evl.watch_file(1, lambda: None) >>> evl.remove_watch_file(handle) True >>> evl.remove_watch_file(handle) False """ def enter_idle(self, callback): """ Add a callback for entering idle. Returns a handle that may be passed to remove_enter_idle() """ self._idle_handle += 1 self._idle_callbacks[self._idle_handle] = callback return self._idle_handle def _enable_glib_idle(self): if self._glib_idle_enabled: return self.gobject.idle_add(self._glib_idle_callback) self._glib_idle_enabled = True def _glib_idle_callback(self): for callback in self._idle_callbacks.values(): callback() self._glib_idle_enabled = False return False # ask glib not to call again (or we would be called def remove_enter_idle(self, handle): """ Remove an idle callback. Returns True if the handle was removed. """ try: del self._idle_callbacks[handle] except KeyError: return False return True def run(self): """ Start the event loop. Exit the loop when any callback raises an exception. If ExitMainLoop is raised, exit cleanly. """ try: self._loop.run() finally: if self._loop.is_running(): self._loop.quit() if self._exc_info: # An exception caused us to exit, raise it now exc_info = self._exc_info self._exc_info = None raise exc_info[0], exc_info[1], exc_info[2] def _test_run(self): """ >>> import os >>> rd, wr = os.pipe() >>> os.write(wr, "data") # something to read from rd 4 >>> evl = GLibEventLoop() >>> def say_hello(): ... print "hello" >>> def say_waiting(): ... print "waiting" >>> def exit_clean(): ... print "clean exit" ... raise ExitMainLoop >>> def exit_error(): ... 1/0 >>> handle = evl.alarm(0.01, exit_clean) >>> handle = evl.alarm(0.005, say_hello) >>> evl.enter_idle(say_waiting) 1 >>> evl.run() waiting hello waiting clean exit >>> handle = evl.watch_file(rd, exit_clean) >>> evl.run() clean exit >>> evl.remove_watch_file(handle) True >>> handle = evl.alarm(0, exit_error) >>> evl.run() Traceback (most recent call last): ... ZeroDivisionError: integer division or modulo by zero >>> handle = evl.watch_file(rd, exit_error) >>> evl.run() Traceback (most recent call last): ... ZeroDivisionError: integer division or modulo by zero """ def handle_exit(self,f): """ Decorator that cleanly exits the :class:`GLibEventLoop` if :exc:`ExitMainLoop` is thrown inside of the wrapped function. Store the exception info if some other exception occurs, it will be reraised after the loop quits. *f* -- function to be wrapped """ def wrapper(*args,**kargs): try: return f(*args,**kargs) except ExitMainLoop: self._loop.quit() except: import sys self._exc_info = sys.exc_info() if self._loop.is_running(): self._loop.quit() return False return wrapper try: from twisted.internet.abstract import FileDescriptor except ImportError: FileDescriptor = object class TwistedInputDescriptor(FileDescriptor): def __init__(self, reactor, fd, cb): self._fileno = fd self.cb = cb FileDescriptor.__init__(self, reactor) def fileno(self): return self._fileno def doRead(self): return self.cb() class TwistedEventLoop(object): """ Event loop based on Twisted_ """ _idle_emulation_delay = 1.0/256 # a short time (in seconds) def __init__(self, reactor=None, manage_reactor=True): """ :param reactor: reactor to use :type reactor: :class:`twisted.internet.reactor`. :param: manage_reactor: `True` if you want this event loop to run and stop the reactor. :type manage_reactor: boolean .. WARNING:: Twisted's reactor doesn't like to be stopped and run again. If you need to stop and run your :class:`MainLoop`, consider setting ``manage_reactor=False`` and take care of running/stopping the reactor at the beginning/ending of your program yourself. .. _Twisted: http://twistedmatrix.com/trac/ """ if reactor is None: import twisted.internet.reactor reactor = twisted.internet.reactor self.reactor = reactor self._alarms = [] self._watch_files = {} self._idle_handle = 0 self._twisted_idle_enabled = False self._idle_callbacks = {} self._exc_info = None self.manage_reactor = manage_reactor self._enable_twisted_idle() def alarm(self, seconds, callback): """ Call callback() given time from from now. No parameters are passed to callback. Returns a handle that may be passed to remove_alarm() seconds -- floating point time to wait before calling callback callback -- function to call from event loop """ handle = self.reactor.callLater(seconds, self.handle_exit(callback)) return handle def remove_alarm(self, handle): """ Remove an alarm. Returns True if the alarm exists, False otherwise """ from twisted.internet.error import AlreadyCancelled, AlreadyCalled try: handle.cancel() return True except AlreadyCancelled: return False except AlreadyCalled: return False def _test_remove_alarm(self): """ >>> evl = TwistedEventLoop() >>> handle = evl.alarm(50, lambda: None) >>> evl.remove_alarm(handle) True >>> evl.remove_alarm(handle) False """ def watch_file(self, fd, callback): """ Call callback() when fd has some data to read. No parameters are passed to callback. Returns a handle that may be passed to remove_watch_file() fd -- file descriptor to watch for input callback -- function to call when input is available """ ind = TwistedInputDescriptor(self.reactor, fd, self.handle_exit(callback)) self._watch_files[fd] = ind self.reactor.addReader(ind) return fd def remove_watch_file(self, handle): """ Remove an input file. Returns True if the input file exists, False otherwise """ if handle in self._watch_files: self.reactor.removeReader(self._watch_files[handle]) del self._watch_files[handle] return True return False def _test_remove_watch_file(self): """ >>> evl = TwistedEventLoop() >>> handle = evl.watch_file(1, lambda: None) >>> evl.remove_watch_file(handle) True >>> evl.remove_watch_file(handle) False """ def enter_idle(self, callback): """ Add a callback for entering idle. Returns a handle that may be passed to remove_enter_idle() """ self._idle_handle += 1 self._idle_callbacks[self._idle_handle] = callback return self._idle_handle def _enable_twisted_idle(self): """ Twisted's reactors don't have an idle or enter-idle callback so the best we can do for now is to set a timer event in a very short time to approximate an enter-idle callback. .. WARNING:: This will perform worse than the other event loops until we can find a fix or workaround """ if self._twisted_idle_enabled: return self.reactor.callLater(self._idle_emulation_delay, self.handle_exit(self._twisted_idle_callback, enable_idle=False)) self._twisted_idle_enabled = True def _twisted_idle_callback(self): for callback in self._idle_callbacks.values(): callback() self._twisted_idle_enabled = False def remove_enter_idle(self, handle): """ Remove an idle callback. Returns True if the handle was removed. """ try: del self._idle_callbacks[handle] except KeyError: return False return True def run(self): """ Start the event loop. Exit the loop when any callback raises an exception. If ExitMainLoop is raised, exit cleanly. """ if not self.manage_reactor: return self.reactor.run() if self._exc_info: # An exception caused us to exit, raise it now exc_info = self._exc_info self._exc_info = None raise exc_info[0], exc_info[1], exc_info[2] def _test_run(self): """ >>> import os >>> rd, wr = os.pipe() >>> os.write(wr, "data") # something to read from rd 4 >>> evl = TwistedEventLoop() >>> def say_hello_data(): ... print "hello data" ... os.read(rd, 4) >>> def say_waiting(): ... print "waiting" >>> def say_hello(): ... print "hello" >>> handle = evl.watch_file(rd, say_hello_data) >>> def say_being_twisted(): ... print "oh I'm messed up" ... raise ExitMainLoop >>> handle = evl.alarm(0.0625, say_being_twisted) >>> handle = evl.alarm(0.03125, say_hello) >>> evl.enter_idle(say_waiting) 1 >>> evl.run() hello data waiting hello waiting oh I'm messed up """ def handle_exit(self, f, enable_idle=True): """ Decorator that cleanly exits the :class:`TwistedEventLoop` if :class:`ExitMainLoop` is thrown inside of the wrapped function. Store the exception info if some other exception occurs, it will be reraised after the loop quits. *f* -- function to be wrapped """ def wrapper(*args,**kargs): rval = None try: rval = f(*args,**kargs) except ExitMainLoop: if self.manage_reactor: self.reactor.stop() except: import sys print sys.exc_info() self._exc_info = sys.exc_info() if self.manage_reactor: self.reactor.crash() if enable_idle: self._enable_twisted_idle() return rval return wrapper def _refl(name, rval=None, exit=False): """ This function is used to test the main loop classes. >>> scr = _refl("screen") >>> scr.function("argument") screen.function('argument') >>> scr.callme(when="now") screen.callme(when='now') >>> scr.want_something_rval = 42 >>> x = scr.want_something() screen.want_something() >>> x 42 """ class Reflect(object): def __init__(self, name, rval=None): self._name = name self._rval = rval def __call__(self, *argl, **argd): args = ", ".join([repr(a) for a in argl]) if args and argd: args = args + ", " args = args + ", ".join([k+"="+repr(v) for k,v in argd.items()]) print self._name+"("+args+")" if exit: raise ExitMainLoop() return self._rval def __getattr__(self, attr): if attr.endswith("_rval"): raise AttributeError() #print self._name+"."+attr if hasattr(self, attr+"_rval"): return Reflect(self._name+"."+attr, getattr(self, attr+"_rval")) return Reflect(self._name+"."+attr) return Reflect(name) def _test(): import doctest doctest.testmod() if __name__=='__main__': _test() urwid-1.1.1/urwid/text_layout.py0000664000175000017500000004177212051303575016307 0ustar ianian00000000000000#!/usr/bin/python # # Urwid Text Layout classes # 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/ 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 class TextLayout: def supports_align_mode(self, align): """Return True if align is a supported align mode.""" return True def supports_wrap_mode(self, wrap): """Return True if wrap is a supported wrap mode.""" return True def layout(self, text, width, align, wrap ): """ Return a layout structure for text. :param text: string in current encoding or unicode string :param width: number of screen columns available :param align: align mode for text :param wrap: wrap mode for text Layout structure is a list of line layouts, one per output line. Line layouts are lists than may contain the following tuples: * (column width of text segment, start offset, end offset) * (number of space characters to insert, offset or None) * (column width of insert text, offset, "insert text") The offset in the last two tuples is used to determine the attribute used for the inserted spaces or text respectively. The attribute used will be the same as the attribute at that text offset. If the offset is None when inserting spaces then no attribute will be used. """ raise NotImplementedError("This function must be overridden by a real" " text layout class. (see StandardTextLayout)") class CanNotDisplayText(Exception): pass class StandardTextLayout(TextLayout): def __init__(self):#, tab_stops=(), tab_stop_every=8): pass #""" #tab_stops -- list of screen column indexes for tab stops #tab_stop_every -- repeated interval for following tab stops #""" #assert tab_stop_every is None or type(tab_stop_every)==int #if not tab_stops and tab_stop_every: # self.tab_stops = (tab_stop_every,) #self.tab_stops = tab_stops #self.tab_stop_every = tab_stop_every def supports_align_mode(self, align): """Return True if align is 'left', 'center' or 'right'.""" return align in ('left', 'center', 'right') def supports_wrap_mode(self, wrap): """Return True if wrap is 'any', 'space' or 'clip'.""" return wrap in ('any', 'space', 'clip') def layout(self, text, width, align, wrap ): """Return a layout structure for text.""" try: segs = self.calculate_text_segments( text, width, wrap ) return self.align_layout( text, width, segs, wrap, align ) except CanNotDisplayText: return [[]] def pack(self, maxcol, layout): """ Return a minimal maxcol value that would result in the same number of lines for layout. layout must be a layout structure returned by self.layout(). """ maxwidth = 0 assert layout, "huh? empty layout?: "+repr(layout) for l in layout: lw = line_width(l) if lw >= maxcol: return maxcol maxwidth = max(maxwidth, lw) return maxwidth def align_layout( self, text, width, segs, wrap, align ): """Convert the layout segs to an aligned layout.""" out = [] for l in segs: sc = line_width(l) if sc == width or align=='left': out.append(l) continue if align == 'right': out.append([(width-sc, None)] + l) continue assert align == 'center' out.append([((width-sc+1) // 2, None)] + l) return out def calculate_text_segments(self, text, width, wrap): """ Calculate the segments of text to display given width screen columns to display them. text - unicode text or byte string to display width - number of available screen columns wrap - wrapping mode used Returns a layout structure without aligmnent applied. """ nl, nl_o, sp_o = "\n", "\n", " " if PYTHON3 and isinstance(text, bytes): nl = B(nl) # can only find bytes in python3 bytestrings nl_o = ord(nl_o) # + an item of a bytestring is the ordinal value sp_o = ord(sp_o) b = [] p = 0 if wrap == 'clip': # no wrapping to calculate, so it's easy. while p<=len(text): n_cr = text.find(nl, p) if n_cr == -1: n_cr = len(text) sc = calc_width(text, p, n_cr) l = [(0,n_cr)] if p!=n_cr: l = [(sc, p, n_cr)] + l b.append(l) p = n_cr+1 return b while p<=len(text): # look for next eligible line break n_cr = text.find(nl, p) if n_cr == -1: n_cr = len(text) sc = calc_width(text, p, n_cr) if sc == 0: # removed character hint b.append([(0,n_cr)]) p = n_cr+1 continue if sc <= width: # this segment fits b.append([(sc,p,n_cr), # removed character hint (0,n_cr)]) p = n_cr+1 continue pos, sc = calc_text_pos( text, p, n_cr, width ) if pos == p: # pathological width=1 double-byte case raise CanNotDisplayText( "Wide character will not fit in 1-column width") if wrap == 'any': b.append([(sc,p,pos)]) p = pos continue assert wrap == 'space' if text[pos] == sp_o: # perfect space wrap b.append([(sc,p,pos), # removed character hint (0,pos)]) p = pos+1 continue if is_wide_char(text, pos): # perfect next wide b.append([(sc,p,pos)]) p = pos continue prev = pos while prev > p: prev = move_prev_char(text, p, prev) if text[prev] == sp_o: sc = calc_width(text,p,prev) l = [(0,prev)] if p!=prev: l = [(sc,p,prev)] + l b.append(l) p = prev+1 break if is_wide_char(text,prev): # wrap after wide char next = move_next_char(text, prev, pos) sc = calc_width(text,p,next) b.append([(sc,p,next)]) p = next break else: # unwrap previous line space if possible to # fit more text (we're breaking a word anyway) if b and (len(b[-1]) == 2 or ( len(b[-1])==1 and len(b[-1][0])==2 )): # look for removed space above if len(b[-1]) == 1: [(h_sc, h_off)] = b[-1] p_sc = 0 p_off = p_end = h_off else: [(p_sc, p_off, p_end), (h_sc, h_off)] = b[-1] if (p_sc < width and h_sc==0 and text[h_off] == sp_o): # combine with previous line del b[-1] p = p_off pos, sc = calc_text_pos( text, p, n_cr, width ) b.append([(sc,p,pos)]) # check for trailing " " or "\n" p = pos if p < len(text) and ( text[p] in (sp_o, nl_o)): # removed character hint b[-1].append((0,p)) p += 1 continue # force any char wrap b.append([(sc,p,pos)]) p = pos return b ###################################### # default layout object to use default_layout = StandardTextLayout() ###################################### class LayoutSegment: def __init__(self, seg): """Create object from line layout segment structure""" assert type(seg) == tuple, repr(seg) assert len(seg) in (2,3), repr(seg) self.sc, self.offs = seg[:2] assert type(self.sc) == int, repr(self.sc) if len(seg)==3: assert type(self.offs) == int, repr(self.offs) assert self.sc > 0, repr(seg) t = seg[2] if type(t) == bytes: self.text = t self.end = None else: assert type(t) == int, repr(t) self.text = None self.end = t else: assert len(seg) == 2, repr(seg) if self.offs is not None: assert self.sc >= 0, repr(seg) assert type(self.offs)==int self.text = self.end = None def subseg(self, text, start, end): """ Return a "sub-segment" list containing segment structures that make up a portion of this segment. A list is returned to handle cases where wide characters need to be replaced with a space character at either edge so two or three segments will be returned. """ if start < 0: start = 0 if end > self.sc: end = self.sc if start >= end: return [] # completely gone if self.text: # use text stored in segment (self.text) spos, epos, pad_left, pad_right = calc_trim_text( self.text, 0, len(self.text), start, end ) return [ (end-start, self.offs, bytes().ljust(pad_left) + self.text[spos:epos] + bytes().ljust(pad_right)) ] elif self.end: # use text passed as parameter (text) spos, epos, pad_left, pad_right = calc_trim_text( text, self.offs, self.end, start, end ) l = [] if pad_left: l.append((1,spos-1)) l.append((end-start-pad_left-pad_right, spos, epos)) if pad_right: l.append((1,epos)) return l else: # simple padding adjustment return [(end-start,self.offs)] def line_width( segs ): """ Return the screen column width of one line of a text layout structure. This function ignores any existing shift applied to the line, represended by an (amount, None) tuple at the start of the line. """ sc = 0 seglist = segs if segs and len(segs[0])==2 and segs[0][1]==None: seglist = segs[1:] for s in seglist: sc += s[0] return sc def shift_line( segs, amount ): """ Return a shifted line from a layout structure to the left or right. segs -- line of a layout structure amount -- screen columns to shift right (+ve) or left (-ve) """ assert type(amount)==int, repr(amount) if segs and len(segs[0])==2 and segs[0][1]==None: # existing shift amount += segs[0][0] if amount: return [(amount,None)]+segs[1:] return segs[1:] if amount: return [(amount,None)]+segs return segs def trim_line( segs, text, start, end ): """ Return a trimmed line of a text layout structure. text -- text to which this layout structre applies start -- starting screen column end -- ending screen column """ l = [] x = 0 for seg in segs: sc = seg[0] if start or sc < 0: if start >= sc: start -= sc x += sc continue s = LayoutSegment(seg) if x+sc >= end: # can all be done at once return s.subseg( text, start, end-x ) l += s.subseg( text, start, sc ) start = 0 x += sc continue if x >= end: break if x+sc > end: s = LayoutSegment(seg) l += s.subseg( text, 0, end-x ) break l.append( seg ) return l def calc_line_pos( text, line_layout, pref_col ): """ Calculate the closest linear position to pref_col given a line layout structure. Returns None if no position found. """ closest_sc = None closest_pos = None current_sc = 0 if pref_col == 'left': for seg in line_layout: s = LayoutSegment(seg) if s.offs is not None: return s.offs return elif pref_col == 'right': for seg in line_layout: s = LayoutSegment(seg) if s.offs is not None: closest_pos = s s = closest_pos if s is None: return if s.end is None: return s.offs return calc_text_pos( text, s.offs, s.end, s.sc-1)[0] for seg in line_layout: s = LayoutSegment(seg) if s.offs is not None: if s.end is not None: if (current_sc <= pref_col and pref_col < current_sc + s.sc): # exact match within this segment return calc_text_pos( text, s.offs, s.end, pref_col - current_sc )[0] elif current_sc <= pref_col: closest_sc = current_sc + s.sc - 1 closest_pos = s if closest_sc is None or ( abs(pref_col-current_sc) < abs(pref_col-closest_sc) ): # this screen column is closer closest_sc = current_sc closest_pos = s.offs if current_sc > closest_sc: # we're moving past break current_sc += s.sc if closest_pos is None or type(closest_pos) == int: return closest_pos # return the last positions in the segment "closest_pos" s = closest_pos return calc_text_pos( text, s.offs, s.end, s.sc-1)[0] def calc_pos( text, layout, pref_col, row ): """ Calculate the closest linear position to pref_col and row given a layout structure. """ if row < 0 or row >= len(layout): raise Exception("calculate_pos: out of layout row range") pos = calc_line_pos( text, layout[row], pref_col ) if pos is not None: return pos rows_above = range(row-1,-1,-1) rows_below = range(row+1,len(layout)) while rows_above and rows_below: if rows_above: r = rows_above.pop(0) pos = calc_line_pos(text, layout[r], pref_col) if pos is not None: return pos if rows_below: r = rows_below.pop(0) pos = calc_line_pos(text, layout[r], pref_col) if pos is not None: return pos return 0 def calc_coords( text, layout, pos, clamp=1 ): """ Calculate the coordinates closest to position pos in text with layout. text -- raw string or unicode string layout -- layout structure applied to text pos -- integer position into text clamp -- ignored right now """ closest = None y = 0 for line_layout in layout: x = 0 for seg in line_layout: s = LayoutSegment(seg) if s.offs is None: x += s.sc continue if s.offs == pos: return x,y if s.end is not None and s.offs<=pos and s.end>pos: x += calc_width( text, s.offs, pos ) return x,y distance = abs(s.offs - pos) if s.end is not None and s.end>> si = SelectableIcon(u"[!]") >>> si >>> si.render((4,), focus=True).cursor (1, 0) >>> si = SelectableIcon("((*))", 2) >>> si.render((8,), focus=True).cursor (2, 0) >>> si.render((2,), focus=True).cursor (0, 1) """ c = self.__super.render(size, focus) if focus: # create a new canvas so we can add a cursor c = CompositeCanvas(c) c.cursor = self.get_cursor_coords(size) return c def get_cursor_coords(self, size): """ Return the position of the cursor if visible. This method is required for widgets that display a cursor. """ if self._cursor_position > len(self.text): return None # find out where the cursor will be displayed based on # the text layout (maxcol,) = size trans = self.get_line_translation(maxcol) x, y = calc_coords(self.text, trans, self._cursor_position) if maxcol <= x: return None return x, y def keypress(self, size, key): """ No keys are handled by this widget. This method is required for selectable widgets. """ return key class CheckBoxError(Exception): pass class CheckBox(WidgetWrap): def sizing(self): return frozenset([FLOW]) states = { True: SelectableIcon("[X]"), False: SelectableIcon("[ ]"), 'mixed': SelectableIcon("[#]") } reserve_columns = 4 # 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"] def __init__(self, label, state=False, has_mixed=False, on_state_change=None, user_data=None): """ :param label: markup for check box label :param state: False, True or "mixed" :param has_mixed: True if "mixed" is a state to cycle through :param on_state_change: shorthand for connect_signal() function call for a single callback :param user_data: user_data for on_state_change Signals supported: ``'change'`` Register signal handler with:: urwid.connect_signal(check_box, 'change', callback, user_data) where callback is callback(check_box, new_state [,user_data]) Unregister signal handlers with:: urwid.disconnect_signal(check_box, 'change', callback, user_data) >>> CheckBox(u"Confirm") >>> CheckBox(u"Yogourt", "mixed", True) >>> cb = CheckBox(u"Extra onions", True) >>> cb >>> cb.render((20,), focus=True).text # ... = b in Python 3 [...'[X] Extra onions '] """ self.__super.__init__(None) # self.w set by set_state below self._label = Text("") self.has_mixed = has_mixed self._state = None # The old way of listening for a change was to pass the callback # in to the constructor. Just convert it to the new way: if on_state_change: connect_signal(self, 'change', on_state_change, user_data) self.set_label(label) self.set_state(state) def _repr_words(self): return self.__super._repr_words() + [ python3_repr(self.label)] def _repr_attrs(self): return dict(self.__super._repr_attrs(), state=self.state) def set_label(self, label): """ Change the check box label. label -- markup for label. See Text widget for description of text markup. >>> cb = CheckBox(u"foo") >>> cb >>> cb.set_label(('bright_attr', u"bar")) >>> cb """ self._label.set_text(label) # no need to call self._invalidate(). WidgetWrap takes care of # that when self.w changes def get_label(self): """ Return label text. >>> cb = CheckBox(u"Seriously") >>> print cb.get_label() Seriously >>> print cb.label Seriously >>> cb.set_label([('bright_attr', u"flashy"), u" normal"]) >>> print cb.label # only text is returned flashy normal """ return self._label.text label = property(get_label) def set_state(self, state, do_callback=True): """ Set the CheckBox state. state -- True, False or "mixed" do_callback -- False to supress signal from this change >>> changes = [] >>> def callback_a(cb, state, user_data): ... changes.append("A %r %r" % (state, user_data)) >>> def callback_b(cb, state): ... changes.append("B %r" % state) >>> cb = CheckBox('test', False, False) >>> connect_signal(cb, 'change', callback_a, "user_a") >>> connect_signal(cb, 'change', callback_b) >>> cb.set_state(True) # both callbacks will be triggered >>> cb.state True >>> disconnect_signal(cb, 'change', callback_a, "user_a") >>> cb.state = False >>> cb.state False >>> cb.set_state(True) >>> cb.state True >>> cb.set_state(False, False) # don't send signal >>> changes ["A True 'user_a'", 'B True', 'B False', 'B True'] """ if self._state == state: return if state not in self.states: raise CheckBoxError("%s Invalid state: %s" % ( repr(self), repr(state))) # self._state is None is a special case when the CheckBox # has just been created if do_callback and self._state is not None: self._emit('change', state) self._state = state # rebuild the display widget with the new state self._w = Columns( [ ('fixed', self.reserve_columns, self.states[state] ), self._label ] ) self._w.focus_col = 0 def get_state(self): """Return the state of the checkbox.""" return self._state state = property(get_state, set_state) def keypress(self, size, key): """ Toggle state on 'activate' command. >>> assert CheckBox._command_map[' '] == 'activate' >>> assert CheckBox._command_map['enter'] == 'activate' >>> size = (10,) >>> cb = CheckBox('press me') >>> cb.state False >>> cb.keypress(size, ' ') >>> cb.state True >>> cb.keypress(size, ' ') >>> cb.state False """ if self._command_map[key] != ACTIVATE: return key self.toggle_state() def toggle_state(self): """ Cycle to the next valid state. >>> cb = CheckBox("3-state", has_mixed=True) >>> cb.state False >>> cb.toggle_state() >>> cb.state True >>> cb.toggle_state() >>> cb.state 'mixed' >>> cb.toggle_state() >>> cb.state False """ if self.state == False: self.set_state(True) elif self.state == True: if self.has_mixed: self.set_state('mixed') else: self.set_state(False) elif self.state == 'mixed': self.set_state(False) def mouse_event(self, size, event, button, x, y, focus): """ Toggle state on button 1 press. >>> size = (20,) >>> cb = CheckBox("clickme") >>> cb.state False >>> cb.mouse_event(size, 'mouse press', 1, 2, 0, True) True >>> cb.state True """ if button != 1 or not is_mouse_press(event): return False self.toggle_state() return True class RadioButton(CheckBox): states = { True: SelectableIcon("(X)"), False: SelectableIcon("( )"), 'mixed': SelectableIcon("(#)") } reserve_columns = 4 def __init__(self, group, label, state="first True", on_state_change=None, user_data=None): """ :param group: list for radio buttons in same group :param label: markup for radio button label :param state: False, True, "mixed" or "first True" :param on_state_change: shorthand for connect_signal() function call for a single 'change' callback :param user_data: user_data for on_state_change This function will append the new radio button to group. "first True" will set to True if group is empty. Signals supported: ``'change'`` Register signal handler with:: urwid.connect_signal(radio_button, 'change', callback, user_data) where callback is callback(radio_button, new_state [,user_data]) Unregister signal handlers with:: urwid.disconnect_signal(radio_button, 'change', callback, user_data) >>> bgroup = [] # button group >>> b1 = RadioButton(bgroup, u"Agree") >>> b2 = RadioButton(bgroup, u"Disagree") >>> len(bgroup) 2 >>> b1 >>> b2 >>> b2.render((15,), focus=True).text # ... = b in Python 3 [...'( ) Disagree '] """ if state=="first True": state = not group self.group = group self.__super.__init__(label, state, False, on_state_change, user_data) group.append(self) def set_state(self, state, do_callback=True): """ Set the RadioButton state. state -- True, False or "mixed" do_callback -- False to supress signal from this change If state is True all other radio buttons in the same button group will be set to False. >>> bgroup = [] # button group >>> b1 = RadioButton(bgroup, u"Agree") >>> b2 = RadioButton(bgroup, u"Disagree") >>> b3 = RadioButton(bgroup, u"Unsure") >>> b1.state, b2.state, b3.state (True, False, False) >>> b2.set_state(True) >>> b1.state, b2.state, b3.state (False, True, False) >>> def relabel_button(radio_button, new_state): ... radio_button.set_label(u"Think Harder!") >>> connect_signal(b3, 'change', relabel_button) >>> b3 >>> b3.set_state(True) # this will trigger the callback >>> b3 """ if self._state == state: return self.__super.set_state(state, do_callback) # if we're clearing the state we don't have to worry about # other buttons in the button group if state is not True: return # clear the state of each other radio button for cb in self.group: if cb is self: continue if cb._state: cb.set_state(False) def toggle_state(self): """ Set state to True. >>> bgroup = [] # button group >>> b1 = RadioButton(bgroup, "Agree") >>> b2 = RadioButton(bgroup, "Disagree") >>> b1.state, b2.state (True, False) >>> b2.toggle_state() >>> b1.state, b2.state (False, True) >>> b2.toggle_state() >>> b1.state, b2.state (False, True) """ self.set_state(True) class Button(WidgetWrap): def sizing(self): return frozenset([FLOW]) button_left = Text("<") button_right = Text(">") signals = ["click"] def __init__(self, label, on_press=None, user_data=None): """ :param label: markup for button label :param on_press: shorthand for connect_signal() function call for a single callback :param user_data: user_data for on_press Signals supported: ``'click'`` Register signal handler with:: urwid.connect_signal(button, 'click', callback, user_data) where callback is callback(button [,user_data]) Unregister signal handlers with:: urwid.disconnect_signal(button, 'click', callback, user_data) >>> Button(u"Ok")