From 89be250c38f3ef21a7baa5bcae8d4bfb67120058 Mon Sep 17 00:00:00 2001 From: Aki Korhonen Date: Mon, 6 Jun 2022 20:22:25 +0800 Subject: [PATCH 1/3] Provided access to underlying curses screen. This makes hybrid apps that don't follow the entire npyscreen flow easier. YMMW. --- npyscreen/apNPSApplication.py | 52 ++++++++++++++++++++----- npyscreen/npyssafewrapper.py | 71 +++++++---------------------------- 2 files changed, 56 insertions(+), 67 deletions(-) diff --git a/npyscreen/apNPSApplication.py b/npyscreen/apNPSApplication.py index cc68b2c..9dcac86 100644 --- a/npyscreen/apNPSApplication.py +++ b/npyscreen/apNPSApplication.py @@ -9,24 +9,58 @@ class AlreadyOver(Exception): pass + + + class NPSApp(object): - _run_called = 0 + _initscr_not_done = True + def main(self): """Overload this method to create your application""" - + + def __init__(self, enable_mouse=True, enable_colors=True): + self._enable_mouse = enable_mouse + self._enable_colors = enable_colors + self._screen = None + self._fork = False + def resize(self): pass - def __remove_argument_call_main(self, screen, enable_mouse=True): - # screen disgarded. - if enable_mouse: + def _wrapped_startup(self): + if self._fork or NPSApp._initscr_not_done: + NPSApp._initscr_not_done = False + locale.setlocale(locale.LC_ALL, '') + self._screen = curses.initscr() + try: + curses.start_color() + except: + pass + + curses.noecho() + curses.cbreak() + self._screen.keypad(True) + if self._fork: + curses.def_prog_mode() + curses.reset_prog_mode() + + if self._enable_mouse: curses.mousemask(curses.ALL_MOUSE_EVENTS) - del screen - return self.main() + + try: + return_code = self.main() + finally: + self._screen.keypad(False) + curses.echo() + curses.nocbreak() + curses.endwin() + + return return_code def run(self, fork=None): """Run application. Calls Mainloop wrapped properly.""" + self._fork = fork if fork is None: - return npyssafewrapper.wrapper(self.__remove_argument_call_main) + return npyssafewrapper.wrapper(self._wrapped_startup) else: - return npyssafewrapper.wrapper(self.__remove_argument_call_main, fork=fork) + return npyssafewrapper.wrapper(self._wrapped_startup, fork=fork) diff --git a/npyscreen/npyssafewrapper.py b/npyscreen/npyssafewrapper.py index 1ff1f3e..3e70e85 100644 --- a/npyscreen/npyssafewrapper.py +++ b/npyscreen/npyssafewrapper.py @@ -10,8 +10,6 @@ import sys import warnings -_NEVER_RUN_INITSCR = True -_SCREEN = None def wrapper_basic(call_function): #set the locale properly @@ -30,17 +28,12 @@ def wrapper_basic(call_function): # curses.echo() # curses.endwin() -def wrapper(call_function, fork=None, reset=True): - global _NEVER_RUN_INITSCR +def wrapper(call_function, fork=None): + if fork: - wrapper_fork(call_function, reset=reset) - elif fork == False: - wrapper_no_fork(call_function) + wrapper_fork(call_function) else: - if _NEVER_RUN_INITSCR: - wrapper_no_fork(call_function) - else: - wrapper_fork(call_function, reset=reset) + wrapper_no_fork(call_function) def wrapper_fork(call_function, reset=True): pid = os.fork() @@ -50,57 +43,19 @@ def wrapper_fork(call_function, reset=True): if reset: external_reset() else: - locale.setlocale(locale.LC_ALL, '') - _SCREEN = curses.initscr() - try: - curses.start_color() - except: - pass - _SCREEN.keypad(1) - curses.noecho() - curses.cbreak() - curses.def_prog_mode() - curses.reset_prog_mode() - return_code = call_function(_SCREEN) - _SCREEN.keypad(0) - curses.echo() - curses.nocbreak() - curses.endwin() + + return_code = call_function() + sys.exit(0) def external_reset(): subprocess.call(['reset', '-Q']) def wrapper_no_fork(call_function, reset=False): - global _NEVER_RUN_INITSCR - if not _NEVER_RUN_INITSCR: - warnings.warn("""Repeated calls of endwin may cause a memory leak. Use wrapper_fork to avoid.""") - global _SCREEN - return_code = None - if _NEVER_RUN_INITSCR: - _NEVER_RUN_INITSCR = False - locale.setlocale(locale.LC_ALL, '') - _SCREEN = curses.initscr() - try: - curses.start_color() - except: - pass - curses.noecho() - curses.cbreak() - _SCREEN.keypad(1) - curses.noecho() - curses.cbreak() - _SCREEN.keypad(1) - - try: - return_code = call_function(_SCREEN) - finally: - _SCREEN.keypad(0) - curses.echo() - curses.nocbreak() - # Calling endwin() and then refreshing seems to cause a memory leak. - curses.endwin() - if reset: - external_reset() - return return_code + return_code = call_function() + + if reset: + external_reset() + + return return_code From c92a68d729430968076215cf7f8964f96d2eaf1f Mon Sep 17 00:00:00 2001 From: Aki Korhonen Date: Mon, 6 Jun 2022 20:49:49 +0800 Subject: [PATCH 2/3] Changed "is" and "is not" to == and !=. --- npyscreen/npyspmfuncs.py | 2 +- npyscreen/proto_fm_screen_area.py | 4 ++-- npyscreen/wgautocomplete.py | 4 ++-- npyscreen/wgcombobox.py | 4 ++-- npyscreen/wgmonthbox.py | 2 +- npyscreen/wgmultiline.py | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) mode change 100755 => 100644 npyscreen/wgmultiline.py diff --git a/npyscreen/npyspmfuncs.py b/npyscreen/npyspmfuncs.py index 3cb523a..61e0300 100644 --- a/npyscreen/npyspmfuncs.py +++ b/npyscreen/npyspmfuncs.py @@ -25,7 +25,7 @@ def CallSubShell(subshell): rtn = os.system("%s" % (subshell)) curses.reset_prog_mode() - if rtn is not 0: return False + if rtn != 0: return False else: return True curses.reset_prog_mode() diff --git a/npyscreen/proto_fm_screen_area.py b/npyscreen/proto_fm_screen_area.py index 6e134b6..8d2cf9b 100644 --- a/npyscreen/proto_fm_screen_area.py +++ b/npyscreen/proto_fm_screen_area.py @@ -147,8 +147,8 @@ def refresh(self): self.curses_pad.refresh(self.show_from_y,self.show_from_x,self.show_aty,self.show_atx,_my,_mx) except curses.error: pass - if self.show_from_y is 0 and \ - self.show_from_x is 0 and \ + if self.show_from_y == 0 and \ + self.show_from_x == 0 and \ (_my >= self.lines) and \ (_mx >= self.columns): self.ALL_SHOWN = True diff --git a/npyscreen/wgautocomplete.py b/npyscreen/wgautocomplete.py index b804fcf..ec81873 100644 --- a/npyscreen/wgautocomplete.py +++ b/npyscreen/wgautocomplete.py @@ -58,12 +58,12 @@ def auto_complete(self, input): (lambda x: os.path.split(x)[1].startswith(fname)), flist )) - if len(possibilities) is 0: + if len(possibilities) == 0: # can't complete curses.beep() break - if len(possibilities) is 1: + if len(possibilities) == 1: if self.value != possibilities[0]: self.value = possibilities[0] if os.path.isdir(self.value) \ diff --git a/npyscreen/wgcombobox.py b/npyscreen/wgcombobox.py index 7793fce..80f28c1 100644 --- a/npyscreen/wgcombobox.py +++ b/npyscreen/wgcombobox.py @@ -12,7 +12,7 @@ class ComboBox(textbox.Textfield): def __init__(self, screen, value = None, values=None,**keywords): self.values = values or [] self.value = value or None - if value is 0: + if value == 0: self.value = 0 super(ComboBox, self).__init__(screen, **keywords) @@ -26,7 +26,7 @@ def update(self, **keywords): super(ComboBox, self).update(**keywords) def _print(self): - if self.value == None or self.value is '': + if self.value == None or self.value == '': printme = '-unset-' else: try: diff --git a/npyscreen/wgmonthbox.py b/npyscreen/wgmonthbox.py index 30af554..c98ff22 100644 --- a/npyscreen/wgmonthbox.py +++ b/npyscreen/wgmonthbox.py @@ -221,7 +221,7 @@ def update(self, clear=True): print_column = self.relx for thisday in calrow: - if thisday is 0: + if thisday == 0: pass elif day == thisday: if self.do_colors(): diff --git a/npyscreen/wgmultiline.py b/npyscreen/wgmultiline.py old mode 100755 new mode 100644 index d8309f2..4c20ec3 --- a/npyscreen/wgmultiline.py +++ b/npyscreen/wgmultiline.py @@ -473,7 +473,7 @@ def h_find_char(self, input): searchingfor = chr(input).upper() for counter in range(len(self.values)): try: - if self.values[counter].find(searchingfor) is not -1: + if self.values[counter].find(searchingfor) != -1: self.cursor_line = counter break except AttributeError: From 3c003beb747f016fde8826a6da1b21217d0afb3f Mon Sep 17 00:00:00 2001 From: Aki Korhonen Date: Mon, 6 Jun 2022 22:37:47 +0800 Subject: [PATCH 3/3] Incorporate npcole/npyscreen pull 120 --- PKG-INFO | 205 +++--- TESTING-mouse-multi-select.py | 26 + build/lib/npyscreen/__init__.py | 115 --- build/lib/npyscreen/fmForm.py | 488 ------------- build/lib/npyscreen/fm_form_edit_loop.py | 119 ---- build/lib/npyscreen/wgmultiline.py | 862 ----------------------- build/lib/npyscreen/wgtitlefield.py | 174 ----- build/lib/npyscreen/wgwidget.py | 803 --------------------- npyscreen/__init__.py | 4 +- npyscreen/apNPSApplicationEvents.py | 24 +- npyscreen/apOptions.py | 218 +++--- npyscreen/fmActionFormV2.py | 118 ++-- npyscreen/fmForm.py | 284 ++++---- npyscreen/fmFormMultiPage.py | 2 +- npyscreen/fm_form_edit_loop.py | 25 +- npyscreen/npyspmfuncs.py | 5 +- npyscreen/utilNotify.py | 24 +- npyscreen/wgNMenuDisplay.py | 8 +- npyscreen/wgboxwidget.py | 133 ++-- npyscreen/wgcheckbox.py | 177 ++--- npyscreen/wgcombobox.py | 73 +- npyscreen/wgfilenamecombo.py | 4 +- npyscreen/wggrid.py | 28 +- npyscreen/wgmultiline.py | 603 ++++++++-------- npyscreen/wgmultiselect.py | 2 +- npyscreen/wgtextbox.py | 372 +++++----- npyscreen/wgtitlefield.py | 16 +- npyscreen/wgwidget.py | 486 +++++++------ setup.py | 55 +- 29 files changed, 1551 insertions(+), 3902 deletions(-) create mode 100644 TESTING-mouse-multi-select.py delete mode 100644 build/lib/npyscreen/__init__.py delete mode 100644 build/lib/npyscreen/fmForm.py delete mode 100644 build/lib/npyscreen/fm_form_edit_loop.py delete mode 100644 build/lib/npyscreen/wgmultiline.py delete mode 100644 build/lib/npyscreen/wgtitlefield.py delete mode 100644 build/lib/npyscreen/wgwidget.py mode change 100644 => 100755 npyscreen/wgmultiline.py diff --git a/PKG-INFO b/PKG-INFO index a4b7863..375507a 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,194 +1,205 @@ Metadata-Version: 1.1 Name: npyscreen -Version: 4.9.1 +Version: 4.11.1 Summary: Writing user interfaces without all that ugly mucking about in hyperspace Home-page: http://www.npcole.com/npyscreen/ Author: Nicholas Cole Author-email: n@npcole.com License: New BSD License Description: This library provides a framework for developing console applications using Python and curses. - + This framework should be powerful enough to create everything from quick, simple programs to complex, multi-screen applications. It is designed to make doing the simple tasks very quick and to take much of the pain out of writing larger applications. - + There is a very wide variety of default widgets - everything from simple text fields to more complex tree and grid views. - - I have used versions of this library for private scripts and small applications for around ten years. As a result, it is fairly mature. - + + I have used versions of this library for private scripts and small applications for around ten years. As a result, it is fairly mature. + Documentation is online at http://npyscreen.readthedocs.org - + Please report bugs or make feature requests using the bug-tracker at http://code.google.com/p/npyscreen. - + There is a mailing list available at https://groups.google.com/forum/?fromgroups#!forum/npyscreen/ - - + + *Latest Changes*: - + + Version 4.11.1: Merged pull request #120 with code to access curses screen directly + + Version 4.11.0: Cleaned up a couple of syntax errors, added Numericfield (in addition to Textfield), and corrected behavior of safe_to_exit() so that it works with TitleText and TitleNumeric classes. + + Version 4.10.5: Merged in bug-fixes and enhancements suggested by Nathan Lewis. + + Version 4.10.0: All widgets have a safe_to_exit() method that will be called + (at least by the default handlers) before exiting a widget. Users can + perform input verification functions here. Return True if the widget should + allow exiting, or False if it should not. Various minor bug-fixes. + Version 4.9.1: minor change to Multiline widgets to make custom versions easier (final widget value is never set to MORE_LABEL). - + Version 4.9: new function blank_terminal() added. (User request) Improvements to facilities for writing unit tests. (user request) Bugs related to hidden widgets fixed. - - Version 4.8.7 New methods added to the Multiline class to assist widget authors. - - + + Version 4.8.7 New methods added to the Multiline class to assist widget authors. + + Version 4.8.6 MultiLineAction widgets no longer raise an exception if empty and selected unless set to do so explicitly. - + Version 4.8.5 improves the writing of tests. - + Version 4.8.4 adds support for custom-defined colours (not recommended. Added at user request for a custom application). - + Version 4.8.3 updates the documentation. - + Version 4.8.2 makes the standard grid widget easier to customize by adding the custom_print_cell method. - + Version 4.8.1 fixes a bug that causes npyscreen to crash. - + Version 4.8.0 adds a mechanism for finer control of BoxTitle contained widgets. - + Version 4.7.2 includes documentation updates. - + Version 4.7.1 is a bugfix release. - + Version 4.7.0 adds scripting support for writing automated tests. - + Version 4.6.4 adds keybindings relevant to Windows. - + Version 4.6.3 is a minor bug-fix release. - + Version 4.6.1 updates the documentation to note that there is a bug in Python's curses library in 3.4.0. This is fixed in 3.4.1. I do not propose to put a workaround into npyscreen, which would complicate the code a great deal for a bug that very few people face. For more details, see: http://bugs.python.org/issue21088 - - - - - Version 4.6.0 introduces a way to define a callback for when widget values change. The help system has been improved by minor interface changes. + + + + + Version 4.6.0 introduces a way to define a callback for when widget values change. The help system has been improved by minor interface changes. Both of these were user suggestions. Thank you to those who suggested them. - - Version 4.5.0 introduces a greater control of colour for certain widgets. - + + Version 4.5.0 introduces a greater control of colour for certain widgets. + Version 4.4.0 introduces the new tree class TreeData. This is a new version of NPSTreeData that follows PEP 8 conventions for method names. NPSTreeData is now deprecated. The form class ActionFormMinimal has been added at a user request. This is a special version of ActionFrom that only features an OK button by default. - + Version 4.3.5 introduces the new classes SliderNoLabel, TitleSliderNoLabel, SliderPercent, TitleSliderPercent. - - - Version 4.3.4 Minor bugfixes. The notify functions and ActionPopups derived from them now use the ActionFormV2 widgets. + + + Version 4.3.4 Minor bugfixes. The notify functions and ActionPopups derived from them now use the ActionFormV2 widgets. This change should not affect existing code, but let me know if there are problems. - + Version 4.3.0 allows you to specify a negative value for rely or relx when creating a widget. This will cause the widget to be aligned - with the bottom or right of the screen. The new method *set_relyx(y, x)* can be used to set the position of the widget on the Form if you never need to do that manually. - + with the bottom or right of the screen. The new method *set_relyx(y, x)* can be used to set the position of the widget on the Form if you never need to do that manually. + The classes *ActionFormV2*, *ActionFormExpandedV2* and *ActionFormV2WithMenus* have been introduced. - These feature cleaner code that should be easier to subclass. - + These feature cleaner code that should be easier to subclass. + The *ButtonPress* class can now be created with the argument *when_pressed_function=None*, which can be used in place of overriding the *whenPressed* method. Note that this might create a reference cycle - within your application, so use with care. - - + within your application, so use with care. + + Version 4.2.0 introduces the ability of Grid widgets to highlight the whole line that the cursor is on (user request). - - Version 4.1.0 introduces support for hvc consoles (thanks to wu.fuheng@********* for the bug report). Title widgets can now define a when_cursor_moved() method directly - on themselves that will be called as expected by the contained entry_widget during its edit loop (user request). - - + + Version 4.1.0 introduces support for hvc consoles (thanks to wu.fuheng@********* for the bug report). Title widgets can now define a when_cursor_moved() method directly + on themselves that will be called as expected by the contained entry_widget during its edit loop (user request). + + Version 4.0.0 introduces a new version scheme. Due to a packaging error in the 3.0 release series some users were having problems obtaining the latest version. This is most easily fixed with a new major version release. - - Version 3.10 MultiLineEditable, MultiLineEditableTitle, MultiLineEditableBoxed classes added, allowing the user to edit lists of items. - See EXAMPLE-MultilineEditable for an example. - + + Version 3.10 MultiLineEditable, MultiLineEditableTitle, MultiLineEditableBoxed classes added, allowing the user to edit lists of items. + See EXAMPLE-MultilineEditable for an example. + Version 3.6 Title.. widgets should now resize properly. Menu items can now be specified with arguments and keywords. - + Version 3.5 when_value_edited defined on Title.. widgets now work as users expect. - + Version 3.4 Fixed bugs in Title.. widgets and in the App classes. - + Version 3.3 and the subsequent minor releases fix some bugs, mainly related to changes caused by allowing resized forms. - - Version 3.2 adds CheckboxBare - a checkbox without a label. Added at user request. - - Version 3.0 *IMPORTANT* The version number has changed to version 3.0. - This is because newer versions of pip distinguish between pre-release and released versions, - and this will allow more flexibility in future releases. A version '2.0' might have caused confusion at this stage. - - Version 3.0 fixes the specification of max_width values for titled widgets (Thanks to Phil Rich for the bug report). + + Version 3.2 adds CheckboxBare - a checkbox without a label. Added at user request. + + Version 3.0 *IMPORTANT* The version number has changed to version 3.0. + This is because newer versions of pip distinguish between pre-release and released versions, + and this will allow more flexibility in future releases. A version '2.0' might have caused confusion at this stage. + + Version 3.0 fixes the specification of max_width values for titled widgets (Thanks to Phil Rich for the bug report). Please report any further problems. - + Version 2.0pre90 introduces a new BufferPager and TitleBufferPager class. (User request, suggested by dennis@wsec.be) - + Version 2.0pre88 *IMPORTANT* This version supports resizing the terminal. Read the documentation for more detail about how to disable this feature if you need to. It has been implemented in a way that should be compatible with existing code. New code can make the resizing even more flexible. - + Version 2.0pre87 Updates the documentation and contains various bug fixes. - - Version 2.0pre85 and 2.0pre86 are both bugfix releases. - + + Version 2.0pre85 and 2.0pre86 are both bugfix releases. + Version 2.0pre84 introduces an experimental system for editing lists of options. See documentation for details. - + Version 2.0pre83 multi-line checkbox widgets are now possible. These can also be used as contained widgets within the multiselect class. See documentation for details. - + Version 2.0pre82 changes the menu system and allows menu items to be given keyboard shortcuts. - + Version 2.0pre81 introduces FilenameCombo, TitleFilenameCombo. - + Version 2.0pre79 is a bugfix release. - + Version 2.0pre76 further improves the handling of mouse events on compatible terminals. - - + + Version 2.0pre75 improves the handling of the mouse on compatible terminals. - + Version 2.0pre74 corrects one minor bug and introduces makes box widgets behave slightly more predictably (.editable attribute now linked to that of the contained widget. - + Version 2.0pre73 corrects two bugs - thanks to Lasse for his help in finding them and offering patches. - + Version 2.0pre71 new tree classes introduced. Bug fixes. - + Version 2.0pre70 introduces the MLTreeMultiSelect class. - - Version 2.0pre69 fixes and tidies up some of the new tree classes. There is an API change assocatied with this, noted in the documentation, though backward compatibility should have been maintained. - + + Version 2.0pre69 fixes and tidies up some of the new tree classes. There is an API change assocatied with this, noted in the documentation, though backward compatibility should have been maintained. + Version 2.0pre68 setting a form's .editing attribute to False now causes it to exit immediately, even if a widget is still being edited. - + Version 2.0pre67 fixes minor bugs. - + Version 2.0pre65 fixes several bugs. All textboxes now honour the .hidden attribute. The major side effect of this is that tree classes are now easier to write. - + Version 2.0pre64 extends multi-page support and includes revision to the documentation. - - Version 2.0pre63 adds initial support for multi-page forms. See documentation on the + + Version 2.0pre63 adds initial support for multi-page forms. See documentation on the FormMultiPage class for details. - + Version 2.0pre57 fixes color support - it should now be possible to display a terminal with a different color background. Text widgets have some additional color options. - + Version 2.0pre52 fixes compatibility with python2.6, 3.0 and 3.1. All other versions should be unaffected. - + Version 2.0pre50 enables basic mouse support. Note that the Apple terminal does not handle mouse events correctly. - + Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python :: 3 diff --git a/TESTING-mouse-multi-select.py b/TESTING-mouse-multi-select.py new file mode 100644 index 0000000..b408c47 --- /dev/null +++ b/TESTING-mouse-multi-select.py @@ -0,0 +1,26 @@ +import npyscreen + + +class TestForm(npyscreen.ActionFormV2): + def create(self): + self.testfield = self.add_widget(npyscreen.TitleMultiSelect, + name="test label", + max_height=15, + rely=2, + relx=2, + values=["value1", "value2", "value3"]) + + +class testapp(npyscreen.NPSAppManaged): + # def __init__(self): + # super().__init__() + + def onStart(self): + self.addForm("MAIN", + TestForm, "Test Form", + use_max_space=True) + + +# create the app +app = testapp() +app.run() diff --git a/build/lib/npyscreen/__init__.py b/build/lib/npyscreen/__init__.py deleted file mode 100644 index e9cbde2..0000000 --- a/build/lib/npyscreen/__init__.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/python - -from .globals import DEBUG, DISABLE_RESIZE_SYSTEM - -from .wgwidget import TEST_SETTINGS, ExhaustedTestInput, add_test_input_from_iterable, add_test_input_ch - -from .npyssafewrapper import wrapper, wrapper_basic - -from .npysThemeManagers import ThemeManager, disableColor, enableColor -from . import npysThemes as Themes -from .apNPSApplication import NPSApp -from .apNPSApplicationManaged import NPSAppManaged -from .proto_fm_screen_area import setTheme -from .fmForm import FormBaseNew, Form, TitleForm, TitleFooterForm, SplitForm, FormExpanded, FormBaseNewExpanded, blank_terminal -from .fmActionForm import ActionForm, ActionFormExpanded -from .fmActionFormV2 import ActionFormV2, ActionFormExpandedV2, ActionFormMinimal -from .fmFormWithMenus import FormWithMenus, ActionFormWithMenus, \ - FormBaseNewWithMenus, SplitFormWithMenus, \ - ActionFormV2WithMenus -from .fmPopup import Popup, MessagePopup, ActionPopup, PopupWide, ActionPopupWide -from .fmFormMutt import FormMutt, FormMuttWithMenus -from .fmFileSelector import FileSelector, selectFile - -from .fmFormMuttActive import ActionControllerSimple, TextCommandBox, \ - FormMuttActive, FormMuttActiveWithMenus -from .fmFormMuttActive import FormMuttActiveTraditional, FormMuttActiveTraditionalWithMenus - - -from .fmFormMultiPage import FormMultiPage, FormMultiPageAction,\ - FormMultiPageActionWithMenus, FormMultiPageWithMenus - -from .npysNPSFilteredData import NPSFilteredDataBase, NPSFilteredDataList - -from .wgbutton import MiniButton -from .wgbutton import MiniButtonPress -from .wgbutton import MiniButton as Button -from .wgbutton import MiniButtonPress as ButtonPress - -from .wgtextbox import Textfield, FixedText -from .wgtitlefield import TitleText, TitleFixedText -from .wgpassword import PasswordEntry, TitlePassword -from .wgannotatetextbox import AnnotateTextboxBase -from .wgannotatetextbox import AnnotateTextboxBaseRight - -from .wgslider import Slider, TitleSlider -from .wgslider import SliderNoLabel, TitleSliderNoLabel -from .wgslider import SliderPercent, TitleSliderPercent - -from .wgwidget import DummyWidget, NotEnoughSpaceForWidget -from . import wgwidget as widget - -from .wgmultiline import MultiLine, Pager, TitleMultiLine, TitlePager, MultiLineAction, BufferPager, TitleBufferPager -from .wgmultiselect import MultiSelect, TitleMultiSelect, MultiSelectFixed, \ - TitleMultiSelectFixed, MultiSelectAction -from .wgeditmultiline import MultiLineEdit -from .wgcombobox import ComboBox, TitleCombo -from .wgcheckbox import Checkbox, RoundCheckBox, CheckBoxMultiline, RoundCheckBoxMultiline, CheckBox, CheckboxBare -from .wgFormControlCheckbox import FormControlCheckbox -from .wgautocomplete import TitleFilename, Filename, Autocomplete -from .muMenu import Menu -from .wgselectone import SelectOne, TitleSelectOne -from .wgdatecombo import DateCombo, TitleDateCombo - -from .npysTree import TreeData -from .wgmultilinetree import MLTree, MLTreeAnnotated, MLTreeAction, MLTreeAnnotatedAction -from .wgmultilinetreeselectable import MLTreeMultiSelect, TreeLineSelectable -from .wgmultilinetreeselectable import MLTreeMultiSelectAnnotated, TreeLineSelectableAnnotated - - -# The following are maintained for compatibility with old code only. ########################################## - -from .compatibility_code.oldtreeclasses import MultiLineTree, SelectOneTree -from .compatibility_code.oldtreeclasses import MultiLineTreeNew, MultiLineTreeNewAction, TreeLine, TreeLineAnnotated # Experimental -from .compatibility_code.oldtreeclasses import MultiLineTreeNewAnnotatedAction, MultiLineTreeNewAnnotated # Experimental -from .compatibility_code.npysNPSTree import NPSTreeData - -# End compatibility. ########################################################################################### - -from .wgfilenamecombo import FilenameCombo, TitleFilenameCombo -from .wgboxwidget import BoxBasic, BoxTitle -from .wgmultiline import MultiLineActionWithShortcuts -from .wgmultilineeditable import MultiLineEditable, MultiLineEditableTitle, MultiLineEditableBoxed - -from .wgmonthbox import MonthBox -from .wggrid import SimpleGrid -from .wggridcoltitles import GridColTitles - -from .muNewMenu import NewMenu, MenuItem -from .wgNMenuDisplay import MenuDisplay, MenuDisplayScreen - -from .npyspmfuncs import CallSubShell - -from .utilNotify import notify, notify_confirm, notify_wait, notify_ok_cancel, notify_yes_no - -# Base classes for overriding: - -# Standard Forms: -from . import stdfmemail - -# Experimental Only -from .wgtextboxunicode import TextfieldUnicode -from .wgtexttokens import TextTokens, TitleTextTokens - -# Very experimental. Don't use for anything serious -from .apOptions import SimpleOptionForm -from .apOptions import OptionListDisplay, OptionChanger, OptionList, OptionLimitedChoices, OptionListDisplayLine -from .apOptions import OptionFreeText, OptionSingleChoice, OptionMultiChoice, OptionMultiFreeList, \ - OptionBoolean, OptionFilename, OptionDate, OptionMultiFreeText - - -# This really is about as experimental as it gets -from .apNPSApplicationEvents import StandardApp -from .eveventhandler import Event - - diff --git a/build/lib/npyscreen/fmForm.py b/build/lib/npyscreen/fmForm.py deleted file mode 100644 index 9145c77..0000000 --- a/build/lib/npyscreen/fmForm.py +++ /dev/null @@ -1,488 +0,0 @@ -#!/usr/bin/python -from . import proto_fm_screen_area -from . import wgwidget as widget -from . import wgbutton as button -import weakref -from . import npyspmfuncs as pmfuncs -#import Menu -import curses -import _curses -from . import npysGlobalOptions -from . import wgwidget_proto -from . import fm_form_edit_loop as form_edit_loop -from . import util_viewhelp -from . import npysGlobalOptions as GlobalOptions -from .eveventhandler import EventHandler -from .globals import DISABLE_RESIZE_SYSTEM - -class _FormBase(proto_fm_screen_area.ScreenArea, - widget.InputHandler, - wgwidget_proto._LinePrinter, - EventHandler): - BLANK_COLUMNS_RIGHT= 2 - BLANK_LINES_BASE = 2 - OK_BUTTON_TEXT = 'OK' - OK_BUTTON_BR_OFFSET = (2,6) - OKBUTTON_TYPE = button.MiniButton - DEFAULT_X_OFFSET = 2 - PRESERVE_SELECTED_WIDGET_DEFAULT = False # Preserve cursor location between displays? - FRAMED = True - ALLOW_RESIZE = True - FIX_MINIMUM_SIZE_WHEN_CREATED = True - WRAP_HELP = True - - - def __init__(self, name=None, parentApp=None, framed=None, help=None, color='FORMDEFAULT', - widget_list=None, cycle_widgets=False, *args, **keywords): - super(_FormBase, self).__init__(*args, **keywords) - self.initialize_event_handling() - self.preserve_selected_widget = self.__class__.PRESERVE_SELECTED_WIDGET_DEFAULT - if parentApp: - try: - self.parentApp = weakref.proxy(parentApp) - except: - self.parentApp = parentApp - try: - self.keypress_timeout = self.parentApp.keypress_timeout_default - except AttributeError: - pass - if framed is None: - self.framed = self.__class__.FRAMED - else: - self.framed = framed - self.name=name - self.editing = False - ## OLD MENU CODE REMOVED self.__menus = [] - self._clear_all_widgets() - - self.help = help - - self.color = color - - self.cycle_widgets = cycle_widgets - - self.set_up_handlers() - self.set_up_exit_condition_handlers() - if hasattr(self, 'initialWidgets'): - self.create_widgets_from_list(self.__class__.initialWidgets) - if widget_list: - self.create_widgets_from_list(widget_list) - self.create() - - if self.FIX_MINIMUM_SIZE_WHEN_CREATED: - self.min_l = self.lines - self.min_c = self.columns - - - def resize(self): - pass - - def _clear_all_widgets(self, ): - self._widgets__ = [] - self._widgets_by_id = {} - self._next_w_id = 0 - self.nextrely = self.DEFAULT_NEXTRELY - self.nextrelx = self.DEFAULT_X_OFFSET - self.editw = 0 # Index of widget to edit. - - def create_widgets_from_list(self, widget_list): - # This code is currently experimental, and the API may change in future releases - # (npyscreen.TextBox, {'rely': 2, 'relx': 7, 'editable': False}) - for line in widget_list: - w_type = line[0] - keywords = line[1] - self.add_widget(w_type, **keywords) - - def set_value(self, value): - self.value = value - for _w in self._widgets__: - if hasattr(_w, 'when_parent_changes_value'): - _w.when_parent_changes_value() - - def _resize(self, *args): - global DISABLE_RESIZE_SYSTEM - if DISABLE_RESIZE_SYSTEM: - return False - - if not self.ALLOW_RESIZE: - return False - - if hasattr(self, 'parentApp'): - self.parentApp.resize() - - self._create_screen() - self.resize() - for w in self._widgets__: - w._resize() - self.DISPLAY() - - - - def create(self): - """Programmers should over-ride this in derived classes, creating widgets here""" - pass - - def set_up_handlers(self): - self.complex_handlers = [] - self.handlers = { - curses.KEY_F1: self.h_display_help, - "KEY_F(1)": self.h_display_help, - "^O": self.h_display_help, - "^L": self.h_display, - curses.KEY_RESIZE: self._resize, - } - - def set_up_exit_condition_handlers(self): - # What happens when widgets exit? - # each widget will set it's how_exited value: this should - # be used to look up the following table. - - self.how_exited_handers = { - widget.EXITED_DOWN: self.find_next_editable, - widget.EXITED_RIGHT: self.find_next_editable, - widget.EXITED_UP: self.find_previous_editable, - widget.EXITED_LEFT: self.find_previous_editable, - widget.EXITED_ESCAPE: self.do_nothing, - True: self.find_next_editable, # A default value - widget.EXITED_MOUSE: self.get_and_use_mouse_event, - False: self.do_nothing, - None: self.do_nothing, - } - - def handle_exiting_widgets(self, condition): - self.how_exited_handers[condition]() - - def do_nothing(self, *args, **keywords): - pass - - def exit_editing(self, *args, **keywords): - self.editing = False - try: - self._widgets__[self.editw].entry_widget.editing = False - except: - pass - try: - self._widgets__[self.editw].editing = False - except: - pass - - def adjust_widgets(self): - """This method can be overloaded by derived classes. It is called when editing any widget, as opposed to - the while_editing() method, which may only be called when moving between widgets. Since it is called for - every keypress, and perhaps more, be careful when selecting what should be done here.""" - - - def while_editing(self, *args, **keywords): - """This function gets called during the edit loop, on each iteration - of the loop. It does nothing: it is here to make customising the loop - as easy as overriding this function. A proxy to the currently selected widget is - passed to the function.""" - - def on_screen(self): - # is the widget in editw on sreen at the moment? - # if not, alter screen so that it is. - - w = weakref.proxy(self._widgets__[self.editw]) - - max_y, max_x = self._max_physical() - - w_my, w_mx = w.calculate_area_needed() - - # always try to show the top of the screen. - self.show_from_y = 0 - self.show_from_x = 0 - - while w.rely + w_my -1 > self.show_from_y + max_y: - self.show_from_y += 1 - - while w.rely < self.show_from_y: - self.show_from_y -= 1 - - - while w.relx + w_mx -1 > self.show_from_x + max_x: - self.show_from_x += 1 - - while w.relx < self.show_from_x: - self.show_from_x -= 1 - - def h_display_help(self, input): - if self.help == None: return - if self.name: - help_name="%s Help" %(self.name) - else: help_name=None - curses.flushinp() - util_viewhelp.view_help(self.help, title=help_name, autowrap=self.WRAP_HELP) - #select.ViewText(self.help, name=help_name) - self.display() - return True - - def DISPLAY(self): - self.curses_pad.redrawwin() - self.erase() - self.display() - self.display(clear=False) - if self.editing and self.editw is not None: - self._widgets__[self.editw].display() - - - def h_display(self, input): - self._resize() - self.DISPLAY() - - def safe_get_mouse_event(self): - try: - mouse_event = curses.getmouse() - return mouse_event - except _curses.error: - return None - - def get_and_use_mouse_event(self): - mouse_event = self.safe_get_mouse_event() - if mouse_event: - self.use_mouse_event(mouse_event) - - def use_mouse_event(self, mouse_event): - wg = self.find_mouse_handler(mouse_event) - if wg: - self.set_editing(wg) - if hasattr(wg, 'handle_mouse_event'): - wg.handle_mouse_event(mouse_event) - else: - curses.beep() - - def find_mouse_handler(self, mouse_event): - #mouse_id, x, y, z, bstate = mouse_event - for wd in self._widgets__: - try: - if wd.intersted_in_mouse_event(mouse_event) == True: - return wd - except AttributeError: - pass - return None - - def set_editing(self, wdg): - try: - self.editw = self._widgets__.index(wdg) - except ValueError: - pass - - - def find_next_editable(self, *args): - if not self.editw == len(self._widgets__): - if not self.cycle_widgets: - r = list(range(self.editw+1, len(self._widgets__))) - else: - r = list(range(self.editw+1, len(self._widgets__))) + list(range(0, self.editw)) - for n in r: - if self._widgets__[n].editable and not self._widgets__[n].hidden: - self.editw = n - break - self.display() - - - def find_previous_editable(self, *args): - if not self.editw == 0: - # remember that xrange does not return the 'last' value, - # so go to -1, not 0! (fence post error in reverse) - for n in range(self.editw-1, -1, -1 ): - if self._widgets__[n].editable and not self._widgets__[n].hidden: - self.editw = n - break - - #def widget_useable_space(self, rely=0, relx=0): - # #Slightly misreports space available. - # mxy, mxx = self.lines-1, self.columns-1 - # return (mxy-1-rely, mxx-1-relx) - - def center_on_display(self): - my, mx = self._max_physical() - if self.lines < my: - self.show_aty = (my - self.lines) // 2 - else: - self.show_aty = 0 - - if self.columns < mx: - self.show_atx = (mx - self.columns) // 2 - else: - self.show_atx = 0 - - - def display(self, clear=False): - #APPLICATION_THEME_MANAGER.setTheme(self) - if curses.has_colors() and not npysGlobalOptions.DISABLE_ALL_COLORS: - self.curses_pad.attrset(0) - color_attribute = self.theme_manager.findPair(self, self.color) - self.curses_pad.bkgdset(' ', color_attribute) - self.curses_pad.attron(color_attribute) - self.curses_pad.erase() - self.draw_form() - for w in [wg for wg in self._widgets__ if wg.hidden]: - w.clear() - for w in [wg for wg in self._widgets__ if not wg.hidden]: - w.update(clear=clear) - - self.refresh() - - def draw_title_and_help(self): - try: - if self.name: - _title = self.name[:(self.columns-4)] - _title = ' ' + str(_title) + ' ' - #self.curses_pad.addstr(0,1, ' '+str(_title)+' ') - if isinstance(_title, bytes): - _title = _title.decode('utf-8', 'replace') - self.add_line(0,1, - _title, - self.make_attributes_list(_title, curses.A_NORMAL), - self.columns-4 - ) - except: - pass - - if self.help and self.editing: - try: - help_advert = " Help: F1 or ^O " - if isinstance(help_advert, bytes): - help_advert = help_advert.decode('utf-8', 'replace') - self.add_line( - 0, self.curses_pad.getmaxyx()[1]-len(help_advert)-2, - help_advert, - self.make_attributes_list(help_advert, curses.A_NORMAL), - len(help_advert) - ) - except: - pass - - def draw_form(self): - if self.framed: - if curses.has_colors() and not GlobalOptions.DISABLE_ALL_COLORS: - self.curses_pad.attrset(0) - self.curses_pad.bkgdset(' ', curses.A_NORMAL | self.theme_manager.findPair(self, self.color)) - self.curses_pad.border() - self.draw_title_and_help() - - - def add_widget(self, widgetClass, w_id=None, max_height=None, rely=None, relx=None, *args, **keywords): - """Add a widget to the form. The form will do its best to decide on placing, unless you override it. - The form of this function is add_widget(WidgetClass, ....) with any arguments or keywords supplied to - the widget. The wigdet will be added to self._widgets__ - - It is safe to use the return value of this function to keep hold of the widget, since that is a weak - reference proxy, but it is not safe to keep hold of self._widgets__""" - - if rely is None: - rely = self.nextrely - if relx is None: - relx = self.nextrelx - - if max_height is False: - max_height = self.curses_pad.getmaxyx()[0] - rely - 1 - - _w = widgetClass(self, - rely=rely, - relx=relx, - max_height=max_height, - *args, **keywords) - - self.nextrely = _w.height + _w.rely - self._widgets__.append(_w) - w_proxy = weakref.proxy(_w) - if not w_id: - w_id = self._next_w_id - self._next_w_id += 1 - self._widgets_by_id[w_id] = w_proxy - - return w_proxy - - def get_widget(self, w_id): - return self._widgets_by_id[w_id] - - add = add_widget - -class FormBaseNew(form_edit_loop.FormNewEditLoop, _FormBase): - # use the new-style edit loop. - pass - -class Form(form_edit_loop.FormDefaultEditLoop, _FormBase, ): - #use the old-style edit loop - pass - - def resize(self): - super(Form, self).resize() - self.move_ok_button() - - - -class FormBaseNewExpanded(form_edit_loop.FormNewEditLoop, _FormBase): - BLANK_LINES_BASE = 1 - OK_BUTTON_BR_OFFSET = (1,6) - # use the new-style edit loop. - pass - -class FormExpanded(form_edit_loop.FormDefaultEditLoop, _FormBase, ): - BLANK_LINES_BASE = 1 - OK_BUTTON_BR_OFFSET = (1,6) - #use the old-style edit loop - pass - - - - -class TitleForm(Form): - """A form without a box, just a title line""" - BLANK_LINES_BASE = 1 - DEFAULT_X_OFFSET = 1 - DEFAULT_NEXTRELY = 1 - BLANK_COLUMNS_RIGHT = 0 - OK_BUTTON_BR_OFFSET = (1,6) - #OKBUTTON_TYPE = button.MiniButton - #DEFAULT_X_OFFSET = 1 - def draw_form(self): - MAXY, MAXX = self.curses_pad.getmaxyx() - self.curses_pad.hline(0, 0, curses.ACS_HLINE, MAXX) - self.draw_title_and_help() - -class TitleFooterForm(TitleForm): - BLANK_LINES_BASE=1 - def draw_form(self): - MAXY, MAXX = self.curses_pad.getmaxyx() - - if self.editing: - self.curses_pad.hline(MAXY-1, 0, curses.ACS_HLINE, - MAXX - self.__class__.OK_BUTTON_BR_OFFSET[1] - 1) - else: - self.curses_pad.hline(MAXY-1, 0, curses.ACS_HLINE, MAXX-1) - - super(TitleFooterForm, self).draw_form() - -class SplitForm(Form): - MOVE_LINE_ON_RESIZE = False - """Just the same as the Title Form, but with a horizontal line""" - def __init__(self, draw_line_at=None, *args, **keywords): - super(SplitForm, self).__init__(*args, **keywords) - if not hasattr(self, 'draw_line_at'): - if draw_line_at != None: - self.draw_line_at = draw_line_at - else: - self.draw_line_at = self.get_half_way() - - def draw_form(self,): - MAXY, MAXX = self.curses_pad.getmaxyx() - super(SplitForm, self).draw_form() - self.curses_pad.hline(self.draw_line_at, 1, curses.ACS_HLINE, MAXX-2) - - def get_half_way(self): - return self.curses_pad.getmaxyx()[0] // 2 - - def resize(self): - super(SplitForm, self).resize() - if self.MOVE_LINE_ON_RESIZE: - self.draw_line_at = self.get_half_way() - - -def blank_terminal(): - F = _FormBase(framed=False) - F.erase() - F.display() - - diff --git a/build/lib/npyscreen/fm_form_edit_loop.py b/build/lib/npyscreen/fm_form_edit_loop.py deleted file mode 100644 index 5072c5e..0000000 --- a/build/lib/npyscreen/fm_form_edit_loop.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -""" -form_edit_loop.py - -Created by Nicholas Cole on 2008-03-31. -Copyright (c) 2008 __MyCompanyName__. All rights reserved. -""" - -import sys -import os -import weakref - -class FormNewEditLoop(object): - "Edit Fields .editing = False" - def pre_edit_loop(self): - pass - def post_edit_loop(self): - pass - def _during_edit_loop(self): - pass - - def edit_loop(self): - self.editing = True - self.display() - while not (self._widgets__[self.editw].editable and not self._widgets__[self.editw].hidden): - self.editw += 1 - if self.editw > len(self._widgets__)-1: - self.editing = False - return False - - while self.editing: - if not self.ALL_SHOWN: self.on_screen() - self.while_editing(weakref.proxy(self._widgets__[self.editw])) - self._during_edit_loop() - if not self.editing: - break - self._widgets__[self.editw].edit() - self._widgets__[self.editw].display() - - self.handle_exiting_widgets(self._widgets__[self.editw].how_exited) - - if self.editw > len(self._widgets__)-1: self.editw = len(self._widgets__)-1 - - def edit(self): - self.pre_edit_loop() - self.edit_loop() - self.post_edit_loop() - -class FormDefaultEditLoop(object): - def edit(self): - """Edit the fields until the user selects the ok button added in the lower right corner. Button will - be removed when editing finishes""" - # Add ok button. Will remove later - tmp_rely, tmp_relx = self.nextrely, self.nextrelx - my, mx = self.curses_pad.getmaxyx() - ok_button_text = self.__class__.OK_BUTTON_TEXT - my -= self.__class__.OK_BUTTON_BR_OFFSET[0] - mx -= len(ok_button_text)+self.__class__.OK_BUTTON_BR_OFFSET[1] - self.ok_button = self.add_widget(self.__class__.OKBUTTON_TYPE, name=ok_button_text, rely=my, relx=mx, use_max_space=True) - ok_button_postion = len(self._widgets__)-1 - self.ok_button.update() - # End add buttons - self.editing=True - if self.editw < 0: self.editw=0 - if self.editw > len(self._widgets__)-1: - self.editw = len(self._widgets__)-1 - if not self.preserve_selected_widget: - self.editw = 0 - if not self._widgets__[self.editw].editable: self.find_next_editable() - - - self.display() - - while not (self._widgets__[self.editw].editable and not self._widgets__[self.editw].hidden): - self.editw += 1 - if self.editw > len(self._widgets__)-1: - self.editing = False - return False - - while self.editing: - if not self.ALL_SHOWN: self.on_screen() - self.while_editing(weakref.proxy(self._widgets__[self.editw])) - if not self.editing: - break - self._widgets__[self.editw].edit() - self._widgets__[self.editw].display() - - self.handle_exiting_widgets(self._widgets__[self.editw].how_exited) - - if self.editw > len(self._widgets__)-1: self.editw = len(self._widgets__)-1 - if self.ok_button.value: - self.editing = False - - self.ok_button.destroy() - del self._widgets__[ok_button_postion] - del self.ok_button - self.nextrely, self.nextrelx = tmp_rely, tmp_relx - self.display() - - #try: - # self.parentApp._FORM_VISIT_LIST.pop() - #except: - # pass - - - self.editing = False - self.erase() - - def move_ok_button(self): - if hasattr(self, 'ok_button'): - my, mx = self.curses_pad.getmaxyx() - my -= self.__class__.OK_BUTTON_BR_OFFSET[0] - mx -= len(self.__class__.OK_BUTTON_TEXT)+self.__class__.OK_BUTTON_BR_OFFSET[1] - self.ok_button.relx = mx - self.ok_button.rely = my - - - diff --git a/build/lib/npyscreen/wgmultiline.py b/build/lib/npyscreen/wgmultiline.py deleted file mode 100644 index cd5990c..0000000 --- a/build/lib/npyscreen/wgmultiline.py +++ /dev/null @@ -1,862 +0,0 @@ -#!/usr/bin/python -import copy -from . import wgwidget as widget -from . import wgtextbox as textbox -import textwrap -import curses -from . import wgtitlefield as titlefield -from . import fmPopup as Popup -import weakref -import collections -import copy - -MORE_LABEL = "- more -" # string to tell user there are more options - -class FilterPopupHelper(Popup.Popup): - def create(self): - super(FilterPopupHelper, self).create() - self.filterbox = self.add(titlefield.TitleText, name='Find:', ) - self.nextrely += 1 - self.statusline = self.add(textbox.Textfield, color = 'LABEL', editable = False) - - def updatestatusline(self): - self.owner_widget._filter = self.filterbox.value - filtered_lines = self.owner_widget.get_filtered_indexes() - len_f = len(filtered_lines) - if self.filterbox.value == None or self.filterbox.value == '': - self.statusline.value = '' - elif len_f == 0: - self.statusline.value = '(No Matches)' - elif len_f == 1: - self.statusline.value = '(1 Match)' - else: - self.statusline.value = '(%s Matches)' % len_f - - def adjust_widgets(self): - self.updatestatusline() - self.statusline.display() - - - - -class MultiLine(widget.Widget): - _safe_to_display_cache = True - """Display a list of items to the user. By overloading the display_value method, this widget can be made to -display different kinds of objects. Given the standard definition, -the same effect can be achieved by altering the __str__() method of displayed objects""" - _MINIMUM_HEIGHT = 2 # Raise an error if not given this. - _contained_widgets = textbox.Textfield - _contained_widget_height = 1 - def __init__(self, screen, values = None, value = None, - slow_scroll=False, scroll_exit=False, - return_exit=False, select_exit=False, - exit_left = False, - exit_right = False, - widgets_inherit_color = False, - always_show_cursor = False, - allow_filtering = True, - **keywords): - - self.never_cache = False - self.exit_left = exit_left - self.exit_right = exit_right - self.allow_filtering = allow_filtering - self.widgets_inherit_color = widgets_inherit_color - super(MultiLine, self).__init__(screen, **keywords) - if self.height < self.__class__._MINIMUM_HEIGHT: - raise widget.NotEnoughSpaceForWidget("Height of %s allocated. Not enough space allowed for %s" % (self.height, str(self))) - self.make_contained_widgets() - - self.value = value - - # does pushing return select and then leave the widget? - self.return_exit = return_exit - - # does any selection leave the widget? - self.select_exit = select_exit - - # Show cursor even when not editing? - self.always_show_cursor = always_show_cursor - - - self.slow_scroll = slow_scroll - self.scroll_exit = scroll_exit - - self.start_display_at = 0 - self.cursor_line = 0 - self.values = values or [] - self._filter = None - - #These are just to do some optimisation tricks - self._last_start_display_at = None - self._last_cursor_line = None - self._last_values = copy.copy(values) - self._last_value = copy.copy(value) - self._last_filter = None - self._filtered_values_cache = [] - - #override - it looks nicer. - if self.scroll_exit: self.slow_scroll=True - - def resize(self): - super(MultiLine, self).resize() - self.make_contained_widgets() - self.reset_display_cache() - self.display() - - def make_contained_widgets(self, ): - self._my_widgets = [] - for h in range(self.height // self.__class__._contained_widget_height): - self._my_widgets.append( - self._contained_widgets(self.parent, - rely=(h*self._contained_widget_height)+self.rely, - relx = self.relx, - max_width=self.width, - max_height=self.__class__._contained_widget_height - ) - ) - - - def display_value(self, vl): - """Overload this function to change how values are displayed. -Should accept one argument (the object to be represented), and return a string or the -object to be passed to the contained widget.""" - try: - return self.safe_string(str(vl)) - except ReferenceError: - return "**REFERENCE ERROR**" - - try: - return "Error displaying " + self.safe_string(repr(vl)) - except: - return "**** Error ****" - - def calculate_area_needed(self): - return 0,0 - - - def reset_cursor(self): - self.start_display_at = 0 - self.cursor_line = 0 - - def reset_display_cache(self): - self._last_values = False - self._last_value = False - - def update(self, clear=True): - if self.hidden and clear: - self.clear() - return False - elif self.hidden: - return False - - if self.values == None: - self.values = [] - - # clear = None is a good value for this widget - display_length = len(self._my_widgets) - #self._remake_filter_cache() - self._filtered_values_cache = self.get_filtered_indexes() - - if self.editing or self.always_show_cursor: - if self.cursor_line < 0: self.cursor_line = 0 - if self.cursor_line > len(self.values)-1: self.cursor_line = len(self.values)-1 - - if self.slow_scroll: - if self.cursor_line > self.start_display_at+display_length-1: - self.start_display_at = self.cursor_line - (display_length-1) - - if self.cursor_line < self.start_display_at: - self.start_display_at = self.cursor_line - - else: - if self.cursor_line > self.start_display_at+(display_length-2): - self.start_display_at = self.cursor_line - - if self.cursor_line < self.start_display_at: - self.start_display_at = self.cursor_line - (display_length-2) - if self.start_display_at < 0: self.start_display_at=0 - - # What this next bit does is to not bother updating the screen if nothing has changed. - no_change = False - try: - if (self._safe_to_display_cache and \ - self._last_value is self.value) and \ - (self.values == self._last_values) and \ - (self.start_display_at == self._last_start_display_at) and \ - (clear != True) and \ - (self._last_cursor_line == self.cursor_line) and \ - (self._last_filter == self._filter) and \ - self.editing: - no_change = True - - else: - no_change = False - except: - no_change = False - if clear: - no_change = False - if not no_change or clear or self.never_cache: - if clear is True: - self.clear() - - if (self._last_start_display_at != self.start_display_at) \ - and clear is None: - self.clear() - else: - pass - - self._last_start_display_at = self.start_display_at - - self._before_print_lines() - indexer = 0 + self.start_display_at - for line in self._my_widgets[:-1]: - self._print_line(line, indexer) - line.task = "PRINTLINE" - line.update(clear=True) - indexer += 1 - - # Now do the final line - line = self._my_widgets[-1] - - if (len(self.values) <= indexer+1):# or (len(self._my_widgets)*self._contained_widget_height) self.cursor_line: - self.cursor_line = possible - self.update() - break - try: - if self.cursor_line-self.start_display_at > len(self._my_widgets) or \ - self._my_widgets[self.cursor_line-self.start_display_at].task == MORE_LABEL: - if self.slow_scroll: - self.start_display_at += 1 - else: - self.start_display_at = self.cursor_line - except IndexError: - self.cursor_line = 0 - self.start_display_at = 0 - - def move_previous_filtered(self, *args): - if self._filter == None: - return False - nextline = self.cursor_line - _filtered_values_cache_reversed = copy.copy(self._filtered_values_cache) - _filtered_values_cache_reversed.reverse() - for possible in _filtered_values_cache_reversed: - if possible < self.cursor_line: - self.cursor_line = possible - return True - break - - def get_selected_objects(self): - if self.value == None: - return None - else: - return [self.values[x] for x in self.value] - - def handle_mouse_event(self, mouse_event): - # unfinished - #mouse_id, x, y, z, bstate = mouse_event - #self.cursor_line = y - self.rely - self.parent.show_aty + self.start_display_at - - mouse_id, rel_x, rel_y, z, bstate = self.interpret_mouse_event(mouse_event) - self.cursor_line = rel_y // self._contained_widget_height + self.start_display_at - - - ##if self.cursor_line > len(self.values): - ## self.cursor_line = len(self.values) - self.display() - - def set_up_handlers(self): - super(MultiLine, self).set_up_handlers() - self.handlers.update ( { - curses.KEY_UP: self.h_cursor_line_up, - ord('k'): self.h_cursor_line_up, - curses.KEY_LEFT: self.h_cursor_line_up, - curses.KEY_DOWN: self.h_cursor_line_down, - ord('j'): self.h_cursor_line_down, - curses.KEY_RIGHT: self.h_cursor_line_down, - curses.KEY_NPAGE: self.h_cursor_page_down, - curses.KEY_PPAGE: self.h_cursor_page_up, - curses.ascii.TAB: self.h_exit_down, - curses.ascii.NL: self.h_select_exit, - curses.KEY_HOME: self.h_cursor_beginning, - curses.KEY_END: self.h_cursor_end, - ord('g'): self.h_cursor_beginning, - ord('G'): self.h_cursor_end, - ord('x'): self.h_select, - # "^L": self.h_set_filtered_to_selected, - curses.ascii.SP: self.h_select, - curses.ascii.ESC: self.h_exit_escape, - curses.ascii.CR: self.h_select_exit, - } ) - - if self.allow_filtering: - self.handlers.update ( { - ord('l'): self.h_set_filter, - ord('L'): self.h_clear_filter, - ord('n'): self.move_next_filtered, - ord('N'): self.move_previous_filtered, - ord('p'): self.move_previous_filtered, - # "^L": self.h_set_filtered_to_selected, - - } ) - - - if self.exit_left: - self.handlers.update({ - curses.KEY_LEFT: self.h_exit_left - }) - - if self.exit_right: - self.handlers.update({ - curses.KEY_RIGHT: self.h_exit_right - }) - - self.complex_handlers = [ - #(self.t_input_isprint, self.h_find_char) - ] - - def h_find_char(self, input): - # The following ought to work, but there is a curses keyname bug - # searchingfor = curses.keyname(input).upper() - # do this instead: - searchingfor = chr(input).upper() - for counter in range(len(self.values)): - try: - if self.values[counter].find(searchingfor) is not -1: - self.cursor_line = counter - break - except AttributeError: - break - - def t_input_isprint(self, input): - if curses.ascii.isprint(input): return True - else: return False - - def h_set_filter(self, ch): - if not self.allow_filtering: - return None - P = FilterPopupHelper() - P.owner_widget = weakref.proxy(self) - P.display() - P.filterbox.edit() - self._remake_filter_cache() - self.jump_to_first_filtered() - - def h_clear_filter(self, ch): - self.clear_filter() - self.update() - - def h_cursor_beginning(self, ch): - self.cursor_line = 0 - - def h_cursor_end(self, ch): - self.cursor_line= len(self.values)-1 - if self.cursor_line < 0: - self.cursor_line = 0 - - def h_cursor_page_down(self, ch): - self.cursor_line += (len(self._my_widgets)-1) # -1 because of the -more- - if self.cursor_line >= len(self.values)-1: - self.cursor_line = len(self.values) -1 - if not (self.start_display_at + len(self._my_widgets) -1 ) > len(self.values): - self.start_display_at += (len(self._my_widgets)-1) - if self.start_display_at > len(self.values) - (len(self._my_widgets)-1): - self.start_display_at = len(self.values) - (len(self._my_widgets)-1) - - def h_cursor_page_up(self, ch): - self.cursor_line -= (len(self._my_widgets)-1) - if self.cursor_line < 0: - self.cursor_line = 0 - self.start_display_at -= (len(self._my_widgets)-1) - if self.start_display_at < 0: self.start_display_at = 0 - - def h_cursor_line_up(self, ch): - self.cursor_line -= 1 - if self.cursor_line < 0: - if self.scroll_exit: - self.cursor_line = 0 - self.h_exit_up(ch) - else: - self.cursor_line = 0 - - def h_cursor_line_down(self, ch): - self.cursor_line += 1 - if self.cursor_line >= len(self.values): - if self.scroll_exit: - self.cursor_line = len(self.values)-1 - self.h_exit_down(ch) - return True - else: - self.cursor_line -=1 - return True - - if self._my_widgets[self.cursor_line-self.start_display_at].task == MORE_LABEL: - if self.slow_scroll: - self.start_display_at += 1 - else: - self.start_display_at = self.cursor_line - - def h_exit(self, ch): - self.editing = False - self.how_exited = True - - def h_set_filtered_to_selected(self, ch): - # This is broken on multiline - if len(self._filtered_values_cache) < 2: - self.value = self._filtered_values_cache - else: - # There is an error - trying to select too many things. - curses.beep() - - def h_select(self, ch): - self.value = self.cursor_line - if self.select_exit: - self.editing = False - self.how_exited = True - - def h_select_exit(self, ch): - self.h_select(ch) - if self.return_exit or self.select_exit: - self.editing = False - self.how_exited=True - - - def edit(self): - self.editing = True - self.how_exited = None - #if self.value: self.cursor_line = self.value - self.display() - while self.editing: - self.get_and_use_key_press() - self.update(clear=None) -## self.clear() -## self.update(clear=False) - self.parent.refresh() -## curses.napms(10) -## curses.flushinp() - -class MultiLineAction(MultiLine): - RAISE_ERROR_IF_EMPTY_ACTION = False - def __init__(self, *args, **keywords): - self.allow_multi_action = False - super(MultiLineAction, self).__init__(*args, **keywords) - - def actionHighlighted(self, act_on_this, key_press): - "Override this Method" - pass - - def h_act_on_highlighted(self, ch): - try: - return self.actionHighlighted(self.values[self.cursor_line], ch) - except IndexError: - if self.RAISE_ERROR_IF_EMPTY_ACTION: - raise - else: - pass - - def set_up_handlers(self): - super(MultiLineAction, self).set_up_handlers() - self.handlers.update ( { - curses.ascii.NL: self.h_act_on_highlighted, - curses.ascii.CR: self.h_act_on_highlighted, - ord('x'): self.h_act_on_highlighted, - curses.ascii.SP: self.h_act_on_highlighted, - } ) - - -class MultiLineActionWithShortcuts(MultiLineAction): - shortcut_attribute_name = 'shortcut' - def set_up_handlers(self): - super(MultiLineActionWithShortcuts, self).set_up_handlers() - self.add_complex_handlers( ((self.h_find_shortcut_action, self.h_execute_shortcut_action),) ) - - - def h_find_shortcut_action(self, _input): - _input_decoded = curses.ascii.unctrl(_input) - for r in range(len(self.values)): - if hasattr(self.values[r], self.shortcut_attribute_name): - from . import utilNotify - if getattr(self.values[r], self.shortcut_attribute_name) == _input \ - or getattr(self.values[r], self.shortcut_attribute_name) == _input_decoded: - return r - return False - - def h_execute_shortcut_action(self, _input): - l = self.h_find_shortcut_action(_input) - if l is False: - return None - self.cursor_line = l - self.display() - self.h_act_on_highlighted(_input) - - - - -class Pager(MultiLine): - def __init__(self, screen, autowrap=False, center=False, **keywords): - super(Pager, self).__init__(screen, **keywords) - self.autowrap = autowrap - self.center = center - self._values_cache_for_wrapping = [] - - def reset_display_cache(self): - super(Pager, self).reset_display_cache() - self._values_cache_for_wrapping = False - - def _wrap_message_lines(self, message_lines, line_length): - lines = [] - for line in message_lines: - if line.rstrip() == '': - lines.append('') - else: - this_line_set = textwrap.wrap(line.rstrip(), line_length) - if this_line_set: - lines.extend(this_line_set) - else: - lines.append('') - return lines - - def resize(self): - super(Pager, self).resize() - #self.values = [str(self.width), str(self._my_widgets[0].width),] - if self.autowrap: - self.setValuesWrap(list(self.values)) - if self.center: - self.centerValues() - - def setValuesWrap(self, lines): - if self.autowrap and (lines == self._values_cache_for_wrapping): - return False - try: - lines = lines.split('\n') - except AttributeError: - pass - self.values = self._wrap_message_lines(lines, self.width-1) - self._values_cache_for_wrapping = self.values - - def centerValues(self): - self.values = [ l.strip().center(self.width-1) for l in self.values ] - - def update(self, clear=True): - #we look this up a lot. Let's have it here. - if self.autowrap: - self.setValuesWrap(list(self.values)) - - if self.center: - self.centerValues() - - display_length = len(self._my_widgets) - values_len = len(self.values) - - if self.start_display_at > values_len - display_length: - self.start_display_at = values_len - display_length - if self.start_display_at < 0: self.start_display_at = 0 - - indexer = 0 + self.start_display_at - for line in self._my_widgets[:-1]: - self._print_line(line, indexer) - indexer += 1 - - # Now do the final line - line = self._my_widgets[-1] - - if values_len <= indexer+1: - self._print_line(line, indexer) - else: - line.value = MORE_LABEL - line.highlight = False - line.show_bold = False - - for w in self._my_widgets: - # call update to avoid needless refreshes - w.update(clear=True) - # There is a bug somewhere that affects the first line. This cures it. - # Without this line, the first line inherits the color of the form when not editing. Not clear why. - self._my_widgets[0].update() - - - - def edit(self): - # Make sure a value never gets set. - value = self.value - super(Pager, self).edit() - self.value = value - - def h_scroll_line_up(self, input): - self.start_display_at -= 1 - if self.scroll_exit and self.start_display_at < 0: - self.editing = False - self.how_exited = widget.EXITED_UP - - def h_scroll_line_down(self, input): - self.start_display_at += 1 - if self.scroll_exit and self.start_display_at >= len(self.values)-self.start_display_at+1: - self.editing = False - self.how_exited = widget.EXITED_DOWN - - def h_scroll_page_down(self, input): - self.start_display_at += len(self._my_widgets) - - def h_scroll_page_up(self, input): - self.start_display_at -= len(self._my_widgets) - - def h_show_beginning(self, input): - self.start_display_at = 0 - - def h_show_end(self, input): - self.start_display_at = len(self.values) - len(self._my_widgets) - - def h_select_exit(self, input): - self.exit(self, input) - - def set_up_handlers(self): - super(Pager, self).set_up_handlers() - self.handlers = { - curses.KEY_UP: self.h_scroll_line_up, - curses.KEY_LEFT: self.h_scroll_line_up, - curses.KEY_DOWN: self.h_scroll_line_down, - curses.KEY_RIGHT: self.h_scroll_line_down, - curses.KEY_NPAGE: self.h_scroll_page_down, - curses.KEY_PPAGE: self.h_scroll_page_up, - curses.KEY_HOME: self.h_show_beginning, - curses.KEY_END: self.h_show_end, - curses.ascii.NL: self.h_exit, - curses.ascii.CR: self.h_exit, - curses.ascii.SP: self.h_scroll_page_down, - curses.ascii.TAB: self.h_exit, - ord('j'): self.h_scroll_line_down, - ord('k'): self.h_scroll_line_up, - ord('x'): self.h_exit, - ord('q'): self.h_exit, - ord('g'): self.h_show_beginning, - ord('G'): self.h_show_end, - curses.ascii.ESC: self.h_exit_escape, - } - - self.complex_handlers = [ - ] - -class TitleMultiLine(titlefield.TitleText): - _entry_type = MultiLine - - def get_selected_objects(self): - return self.entry_widget.get_selected_objects() - - def get_values(self): - if hasattr(self, 'entry_widget'): - return self.entry_widget.values - elif hasattr(self, '__tmp_value'): - return self.__tmp_values - else: - return None - def set_values(self, value): - if hasattr(self, 'entry_widget'): - self.entry_widget.values = value - elif hasattr(self, '__tmp_value'): - # probably trying to set the value before the textarea is initialised - self.__tmp_values = value - def del_values(self): - del self.entry_widget.value - values = property(get_values, set_values, del_values) - - -class TitlePager(TitleMultiLine): - _entry_type = Pager - -class BufferPager(Pager): - DEFAULT_MAXLEN = None - - def __init__(self, screen, maxlen=False, *args, **keywords): - super(BufferPager, self).__init__(screen, *args, **keywords) - if maxlen is False: - maxlen = self.DEFAULT_MAXLEN - self.values = collections.deque(maxlen=maxlen) - - def clearBuffer(self): - self.values.clear() - - def setValuesWrap(self, lines): - if self.autowrap and (lines == self._values_cache_for_wrapping): - return False - try: - lines = lines.split('\n') - except AttributeError: - pass - - self.clearBuffer() - self.buffer(self._wrap_message_lines(lines, self.width-1)) - self._values_cache_for_wrapping = copy.deepcopy(self.values) - - def buffer(self, lines, scroll_end=True, scroll_if_editing=False): - "Add data to be displayed in the buffer." - self.values.extend(lines) - if scroll_end: - if not self.editing: - self.start_display_at = len(self.values) - len(self._my_widgets) - elif scroll_if_editing: - self.start_display_at = len(self.values) - len(self._my_widgets) - -class TitleBufferPager(TitleMultiLine): - _entry_type = BufferPager - - def clearBuffer(self): - return self.entry_widget.clearBuffer() - - def buffer(self, *args, **values): - return self.entry_widget.buffer(*args, **values) - - - - - diff --git a/build/lib/npyscreen/wgtitlefield.py b/build/lib/npyscreen/wgtitlefield.py deleted file mode 100644 index 0d212ec..0000000 --- a/build/lib/npyscreen/wgtitlefield.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/python -import curses -import weakref -from . import wgtextbox as textbox -from . import wgwidget as widget - -class TitleText(widget.Widget): - _entry_type = textbox.Textfield - - def __init__(self, screen, - begin_entry_at = 16, - field_width = None, - value = None, - use_two_lines = None, - hidden=False, - labelColor='LABEL', - allow_override_begin_entry_at=True, - **keywords): - - self.text_field_begin_at = begin_entry_at - self.field_width_request = field_width - self.labelColor = labelColor - self.allow_override_begin_entry_at = allow_override_begin_entry_at - super(TitleText, self).__init__(screen, **keywords) - - if self.name is None: self.name = 'NoName' - - if use_two_lines is None: - if len(self.name)+2 >= begin_entry_at: - self.use_two_lines = True - else: - self.use_two_lines = False - else: - self.use_two_lines = use_two_lines - - self._passon = keywords.copy() - for dangerous in ('relx', 'rely','value',):# 'width','max_width'): - try: - self._passon.pop(dangerous) - except: - pass - - if self.field_width_request: - self._passon['width'] = self.field_width_request - else: - if 'max_width' in self._passon.keys(): - if self._passon['max_width'] > 0: - if self._passon['max_width'] < self.text_field_begin_at: - raise ValueError("The maximum width specified is less than the text_field_begin_at value.") - else: - self._passon['max_width'] -= self.text_field_begin_at+1 - - if 'width' in self._passon: - #if 0 < self._passon['width'] < self.text_field_begin_at: - # raise ValueError("The maximum width specified %s is less than the text_field_begin_at value %s." % (self._passon['width'], self.text_field_begin_at)) - if self._passon['width'] > 0: - self._passon['width'] -= self.text_field_begin_at+1 - - if self.use_two_lines: - if 'max_height' in self._passon and self._passon['max_height']: - if self._passon['max_height'] == 1: - raise ValueError("I don't know how to resolve this: max_height == 1 but widget using 2 lines.") - self._passon['max_height'] -= 1 - if 'height' in self._passon and self._passon['height']: - raise ValueError("I don't know how to resolve this: height == 1 but widget using 2 lines.") - self._passon['height'] -= 1 - - - self.make_contained_widgets() - self.set_value(value) - self.hidden = hidden - - - - def resize(self): - super(TitleText, self).resize() - self.label_widget.relx = self.relx - self.label_widget.rely = self.rely - self.entry_widget.relx = self.relx + self.text_field_begin_at - self.entry_widget.rely = self.rely + self._contained_rely_offset - self.label_widget._resize() - self.entry_widget._resize() - self.recalculate_size() - - def make_contained_widgets(self): - self.label_widget = textbox.Textfield(self.parent, relx=self.relx, rely=self.rely, width=len(self.name)+1, value=self.name, color=self.labelColor) - if self.label_widget.on_last_line and self.use_two_lines: - # we're in trouble here. - if len(self.name) > 12: - ab_label = 12 - else: - ab_label = len(self.name) - self.use_two_lines = False - self.label_widget = textbox.Textfield(self.parent, relx=self.relx, rely=self.rely, width=ab_label+1, value=self.name) - if self.allow_override_begin_entry_at: - self.text_field_begin_at = ab_label + 1 - if self.use_two_lines: - self._contained_rely_offset = 1 - else: - self._contained_rely_offset = 0 - - self.entry_widget = self.__class__._entry_type(self.parent, - relx=(self.relx + self.text_field_begin_at), - rely=(self.rely+self._contained_rely_offset), value = self.value, - **self._passon) - self.entry_widget.parent_widget = weakref.proxy(self) - self.recalculate_size() - - - def recalculate_size(self): - self.height = self.entry_widget.height - if self.use_two_lines: self.height += 1 - else: pass - self.width = self.entry_widget.width + self.text_field_begin_at - - def edit(self): - self.editing=True - self.display() - self.entry_widget.edit() - #self.value = self.textarea.value - self.how_exited = self.entry_widget.how_exited - self.editing=False - self.display() - - def update(self, clear = True): - if clear: self.clear() - if self.hidden: return False - if self.editing: - self.label_widget.show_bold = True - self.label_widget.color = 'LABELBOLD' - else: - self.label_widget.show_bold = False - self.label_widget.color = self.labelColor - self.label_widget.update() - self.entry_widget.update() - - def handle_mouse_event(self, mouse_event): - if self.entry_widget.intersted_in_mouse_event(mouse_event): - self.entry_widget.handle_mouse_event(mouse_event) - - def get_value(self): - if hasattr(self, 'entry_widget'): - return self.entry_widget.value - elif hasattr(self, '__tmp_value'): - return self.__tmp_value - else: - return None - def set_value(self, value): - if hasattr(self, 'entry_widget'): - self.entry_widget.value = value - else: - # probably trying to set the value before the textarea is initialised - self.__tmp_value = value - def del_value(self): - del self.entry_widget.value - value = property(get_value, set_value, del_value) - - @property - def editable(self): - try: - return self.entry_widget.editable - except AttributeError: - return self._editable - - @editable.setter - def editable(self, value): - self._editable = value - try: - self.entry_widget.editable = value - except AttributeError: - self._editable = value - -class TitleFixedText(TitleText): - _entry_type = textbox.FixedText diff --git a/build/lib/npyscreen/wgwidget.py b/build/lib/npyscreen/wgwidget.py deleted file mode 100644 index 35cba6f..0000000 --- a/build/lib/npyscreen/wgwidget.py +++ /dev/null @@ -1,803 +0,0 @@ -#!/usr/bin/python -import codecs -import copy -import sys -import curses -import curses.ascii -#import curses.wrapper -import weakref -from . import npysGlobalOptions as GlobalOptions -from . import wgwidget_proto -import locale -import warnings - -from .globals import DEBUG - -# experimental -from .eveventhandler import EventHandler - - - -EXITED_DOWN = 1 -EXITED_UP = -1 -EXITED_LEFT = -2 -EXITED_RIGHT = 2 -EXITED_ESCAPE= 127 -EXITED_MOUSE = 130 - -SETMAX = 'SETMAX' -RAISEERROR = 'RAISEERROR' - -ALLOW_NEW_INPUT = True - -TEST_SETTINGS = { - 'TEST_INPUT': None, - 'TEST_INPUT_LOG': [], - 'CONTINUE_AFTER_TEST_INPUT': False, - 'INPUT_GENERATOR': None, - } - - -def add_test_input_from_iterable(test_input): - global TEST_SETTINGS - if not TEST_SETTINGS['TEST_INPUT']: - TEST_SETTINGS['TEST_INPUT'] = [] - TEST_SETTINGS['TEST_INPUT'].extend([ch for ch in test_input]) - -def add_test_input_ch(test_input): - global TEST_SETTINGS - if not TEST_SETTINGS['TEST_INPUT']: - TEST_SETTINGS['TEST_INPUT'] = [] - TEST_SETTINGS['TEST_INPUT'].append(test_input) - - -class ExhaustedTestInput(Exception): - pass - -class NotEnoughSpaceForWidget(Exception): - pass - -class InputHandler(object): - "An object that can handle user input" - - def handle_input(self, _input): - """Returns True if input has been dealt with, and no further action needs taking. - First attempts to look up a method in self.input_handers (which is a dictionary), then - runs the methods in self.complex_handlers (if any), which is an array of form (test_func, dispatch_func). - If test_func(input) returns true, then dispatch_func(input) is called. Check to see if parent can handle. - No further action taken after that point.""" - - if _input in self.handlers: - self.handlers[_input](_input) - return True - - try: - _unctrl_input = curses.ascii.unctrl(_input) - except TypeError: - _unctrl_input = None - - if _unctrl_input and (_unctrl_input in self.handlers): - self.handlers[_unctrl_input](_input) - return True - - - if not hasattr(self, 'complex_handlers'): - return False - else: - for test, handler in self.complex_handlers: - if test(_input) is not False: - handler(_input) - return True - if hasattr(self, 'parent_widget') and hasattr(self.parent_widget, 'handle_input'): - if self.parent_widget.handle_input(_input): - return True - elif hasattr(self, 'parent') and hasattr(self.parent, 'handle_input'): - if self.parent.handle_input(_input): - return True - - else: - pass - # If we've got here, all else has failed, so: - return False - - def set_up_handlers(self): - """This function should be called somewhere during object initialisation (which all library-defined widgets do). You might like to override this in your own definition, -but in most cases the add_handers or add_complex_handlers methods are what you want.""" - #called in __init__ - self.handlers = { - curses.ascii.NL: self.h_exit_down, - curses.ascii.CR: self.h_exit_down, - curses.ascii.TAB: self.h_exit_down, - curses.KEY_BTAB: self.h_exit_up, - curses.KEY_DOWN: self.h_exit_down, - curses.KEY_UP: self.h_exit_up, - curses.KEY_LEFT: self.h_exit_left, - curses.KEY_RIGHT: self.h_exit_right, - "^P": self.h_exit_up, - "^N": self.h_exit_down, - curses.ascii.ESC: self.h_exit_escape, - curses.KEY_MOUSE: self.h_exit_mouse, - } - - self.complex_handlers = [] - - def add_handlers(self, handler_dictionary): - """Update the dictionary of simple handlers. Pass in a dictionary with keyname (eg "^P" or curses.KEY_DOWN) as the key, and the function that key should call as the values """ - self.handlers.update(handler_dictionary) - - def add_complex_handlers(self, handlers_list): - """add complex handlers: format of the list is pairs of - (test_function, callback) sets""" - - for pair in handlers_list: - assert len(pair) == 2 - self.complex_handlers.extend(handlers_list) - - def remove_complex_handler(self, test_function): - _new_list = [] - for pair in self.complex_handlers: - if not pair[0] == test_function: - _new_list.append(pair) - self.complex_handlers = _new_list - -########################################################################################### -# Handler Methods here - npc convention - prefix with h_ - - def h_exit_down(self, _input): - """Called when user leaves the widget to the next widget""" - self.editing = False - self.how_exited = EXITED_DOWN - - def h_exit_right(self, _input): - self.editing = False - self.how_exited = EXITED_RIGHT - - def h_exit_up(self, _input): - """Called when the user leaves the widget to the previous widget""" - self.editing = False - self.how_exited = EXITED_UP - - def h_exit_left(self, _input): - self.editing = False - self.how_exited = EXITED_LEFT - - def h_exit_escape(self, _input): - self.editing = False - self.how_exited = EXITED_ESCAPE - - def h_exit_mouse(self, _input): - mouse_event = self.parent.safe_get_mouse_event() - if mouse_event and self.intersted_in_mouse_event(mouse_event): - self.handle_mouse_event(mouse_event) - else: - if mouse_event: - curses.ungetmouse(*mouse_event) - ch = self.parent.curses_pad.getch() - assert ch == curses.KEY_MOUSE - self.editing = False - self.how_exited = EXITED_MOUSE - - - - -class Widget(InputHandler, wgwidget_proto._LinePrinter, EventHandler): - "A base class for widgets. Do not use directly" - - _SAFE_STRING_STRIPS_NL = True - - def destroy(self): - """Destroy the widget: methods should provide a mechanism to destroy any references that might - case a memory leak. See select. module for an example""" - pass - - def __init__(self, screen, - relx=0, rely=0, name=None, value=None, - width = False, height = False, - max_height = False, max_width=False, - editable=True, - hidden=False, - color='DEFAULT', - use_max_space=False, - check_value_change=True, - check_cursor_move=True, - value_changed_callback=None, - **keywords): - """The following named arguments may be supplied: - name= set the name of the widget. - width= set the width of the widget. - height= set the height. - max_height= let the widget choose a height up to this maximum. - max_width= let the widget choose a width up to this maximum. - editable=True/False the user may change the value of the widget. - hidden=True/False The widget is hidden. - check_value_change=True - perform a check on every keypress and run when_value_edit if the value is different. - check_cursor_move=True - perform a check every keypress and run when_cursor_moved if the cursor has moved. - value_changed_callback - should be None or a Function. If it is a function, it will have be called when the value changes - and passed the keyword argument widget=self. - """ - self.check_value_change=check_value_change - self.check_cursor_move =check_cursor_move - self.hidden = hidden - self.interested_in_mouse_even_when_not_editable = False# used only for rare widgets to allow user to click - # even if can't actually select the widget. See mutt-style forms - - try: - self.parent = weakref.proxy(screen) - except TypeError: - self.parent = screen - self.use_max_space = use_max_space - self.set_relyx(rely, relx) - #self.relx = relx - #self.rely = rely - self.color = color - self.encoding = 'utf-8'#locale.getpreferredencoding() - if GlobalOptions.ASCII_ONLY or locale.getpreferredencoding() == 'US-ASCII': - self._force_ascii = True - else: - self._force_ascii = False - - - self.set_up_handlers() - - # To allow derived classes to set this and then call this method safely... - try: - self.value - except AttributeError: - self.value = value - - # same again - try: - self.name - except: - self.name=name - - self.request_width = width # widgets should honour if possible - self.request_height = height # widgets should honour if possible - - self.max_height = max_height - self.max_width = max_width - - self.set_size() - - self.editing = False # Change to true during an edit - - self.editable = editable - if self.parent.curses_pad.getmaxyx()[0]-1 == self.rely: self.on_last_line = True - else: self.on_last_line = False - - if value_changed_callback: - self.value_changed_callback = value_changed_callback - else: - self.value_changed_callback = None - - self.initialize_event_handling() - - def set_relyx(self, y, x): - """ - Set the position of the widget on the Form. If y or x is a negative value, - npyscreen will try to position it relative to the bottom or right edge of the - Form. Note that this ignores any margins that the Form may have defined. - This is currently an experimental feature. A future version of the API may - take account of the margins set by the parent Form. - """ - self._requested_rely = y - self._requested_relx = x - if y >= 0: - self.rely = y - else: - self._requested_rely = y - self.rely = self.parent.curses_pad.getmaxyx()[0] + y - # I don't think there is any real value in using these margins - #if self.parent.BLANK_LINES_BASE and not self.use_max_space: - # self.rely -= self.parent.BLANK_LINES_BASE - if self.rely < 0: - self.rely = 0 - if x >= 0: - self.relx = x - else: - self.relx = self.parent.curses_pad.getmaxyx()[1] + x - # I don't think there is any real value in using these margins - #if self.parent.BLANK_COLUMNS_RIGHT and not self.use_max_space: - # self.relx -= self.parent.BLANK_COLUMNS_RIGHT - if self.relx < 0: - self.relx = 0 - - def _move_widget_on_terminal_resize(self): - if self._requested_rely < 0 or self._requested_relx < 0: - self.set_relyx(self._requested_rely, self._requested_relx) - - def _resize(self): - "Internal Method. This will be the method called when the terminal resizes." - self._move_widget_on_terminal_resize() - self._recalculate_size() - if self.parent.curses_pad.getmaxyx()[0]-1 == self.rely: self.on_last_line = True - else: self.on_last_line = False - self.resize() - self.when_resized() - - def resize(self): - "Widgets should override this to control what should happen when they are resized." - pass - - def _recalculate_size(self): - return self.set_size() - - def when_resized(self): - # this method is called when the widget has been resized. - pass - - - def do_colors(self): - "Returns True if the widget should try to paint in coloour." - if curses.has_colors() and not GlobalOptions.DISABLE_ALL_COLORS: - return True - else: - return False - - def space_available(self): - """The space available left on the screen, returned as rows, columns""" - if self.use_max_space: - y, x = self.parent.useable_space(self.rely, self.relx) - else: - y, x = self.parent.widget_useable_space(self.rely, self.relx) - return y,x - - def calculate_area_needed(self): - """Classes should provide a function to -calculate the screen area they need, returning either y,x, or 0,0 if -they want all the screen they can. However, do not use this to say how -big a given widget is ... use .height and .width instead""" - return 0,0 - - def set_size(self): - """Set the size of the object, reconciling the user's request with the space available""" - my, mx = self.space_available() - #my = my+1 # Probably want to remove this. - ny, nx = self.calculate_area_needed() - - max_height = self.max_height - max_width = self.max_width - # What to do if max_height or max_width is negative - if max_height not in (None, False) and max_height < 0: - max_height = my + max_height - if max_width not in (None, False) and max_width < 0: - max_width = mx + max_width - - if max_height not in (None, False) and max_height <= 0: - raise NotEnoughSpaceForWidget("Not enough space for requested size") - if max_width not in (None, False) and max_width <= 0: - raise NotEnoughSpaceForWidget("Not enough space for requested size") - - if ny > 0: - if my >= ny: - self.height = ny - else: - self.height = RAISEERROR - elif max_height: - if max_height <= my: - self.height = max_height - else: - self.height = self.request_height - else: - self.height = (self.request_height or my) - - #if mx <= 0 or my <= 0: - # raise Exception("Not enough space for widget") - - - if nx > 0: # if a minimum space is specified.... - if mx >= nx: # if max width is greater than needed space - self.width = nx # width is needed space - else: - self.width = RAISEERROR # else raise an error - elif self.max_width: # otherwise if a max width is speciied - if max_width <= mx: - self.width = max_width - else: - self.width = RAISEERROR - else: - self.width = self.request_width or mx - - if self.height == RAISEERROR or self.width == RAISEERROR: - # Not enough space for widget - raise NotEnoughSpaceForWidget("Not enough space: max y and x = %s , %s. Height and Width = %s , %s " % (my, mx, self.height, self.width) ) # unsafe. Need to add error here. - - def update(self, clear=True): - """How should object display itself on the screen. Define here, but do not actually refresh the curses - display, since this should be done as little as possible. This base widget puts nothing on screen.""" - if self.hidden: - self.clear() - return True - - def display(self): - """Do an update of the object AND refresh the screen""" - if self.hidden: - self.clear() - self.parent.refresh() - else: - self.update() - self.parent.refresh() - - def set_editable(self, value): - if value: self._is_editable = True - else: self._is_editable = False - - def get_editable(self): - return(self._is_editable) - - def clear(self, usechar=' '): - """Blank the screen area used by this widget, ready for redrawing""" - for y in range(self.height): -#This method is too slow -# for x in range(self.width+1): -# try: -# # We are in a try loop in case the cursor is moved off the bottom right corner of the screen -# self.parent.curses_pad.addch(self.rely+y, self.relx + x, usechar) -# except: pass -#Use this instead - if self.do_colors(): - self.parent.curses_pad.addstr(self.rely+y, self.relx, usechar * (self.width), self.parent.theme_manager.findPair(self, self.parent.color)) # used to be width + 1 - else: - self.parent.curses_pad.addstr(self.rely+y, self.relx, usechar * (self.width)) # used to be width + 1 - - def edit(self): - """Allow the user to edit the widget: ie. start handling keypresses.""" - self.editing = 1 - self._pre_edit() - self._edit_loop() - return self._post_edit() - - def _pre_edit(self): - self.highlight = 1 - old_value = self.value - self.how_exited = False - - def _edit_loop(self): - if not self.parent.editing: - _i_set_parent_editing = True - self.parent.editing = True - else: - _i_set_parent_editing = False - while self.editing and self.parent.editing: - self.display() - self.get_and_use_key_press() - if _i_set_parent_editing: - self.parent.editing = False - - if self.editing: - self.editing = False - self.how_exited = True - - def _post_edit(self): - self.highlight = 0 - self.update() - - def _get_ch(self): - #try: - # # Python3.3 and above - returns unicode - # ch = self.parent.curses_pad.get_wch() - # self._last_get_ch_was_unicode = True - #except AttributeError: - - # For now, disable all attempt to use get_wch() - # but everything that follows could be in the except clause above. - - # Try to read utf-8 if possible. - _stored_bytes =[] - self._last_get_ch_was_unicode = True - global ALLOW_NEW_INPUT - if ALLOW_NEW_INPUT == True and locale.getpreferredencoding() == 'UTF-8': - ch = self.parent.curses_pad.getch() - if ch <= 127: - rtn_ch = ch - self._last_get_ch_was_unicode = False - return rtn_ch - elif ch <= 193: - rtn_ch = ch - self._last_get_ch_was_unicode = False - return rtn_ch - # if we are here, we need to read 1, 2 or 3 more bytes. - # all of the subsequent bytes should be in the range 128 - 191, - # but we'll risk not checking... - elif 194 <= ch <= 223: - # 2 bytes - _stored_bytes.append(ch) - _stored_bytes.append(self.parent.curses_pad.getch()) - elif 224 <= ch <= 239: - # 3 bytes - _stored_bytes.append(ch) - _stored_bytes.append(self.parent.curses_pad.getch()) - _stored_bytes.append(self.parent.curses_pad.getch()) - elif 240 <= ch <= 244: - # 4 bytes - _stored_bytes.append(ch) - _stored_bytes.append(self.parent.curses_pad.getch()) - _stored_bytes.append(self.parent.curses_pad.getch()) - _stored_bytes.append(self.parent.curses_pad.getch()) - elif ch >= 245: - # probably a control character - self._last_get_ch_was_unicode = False - return ch - - if sys.version_info[0] >= 3: - ch = bytes(_stored_bytes).decode('utf-8', errors='strict') - else: - ch = ''.join([chr(b) for b in _stored_bytes]) - ch = ch.decode('utf-8') - else: - ch = self.parent.curses_pad.getch() - self._last_get_ch_was_unicode = False - - # This line should not be in the except clause. - return ch - - def try_adjust_widgets(self): - if hasattr(self.parent, "adjust_widgets"): - self.parent.adjust_widgets() - if hasattr(self.parent, "parentApp"): - if hasattr(self.parent.parentApp, "_internal_adjust_widgets"): - self.parent.parentApp._internal_adjust_widgets() - if hasattr(self.parent.parentApp, "adjust_widgets"): - self.parent.parentApp.adjust_widgets() - - - def try_while_waiting(self): - if hasattr(self.parent, "while_waiting"): - self.parent.while_waiting() - if hasattr(self.parent, "parentApp"): - if hasattr(self.parent.parentApp, "_internal_while_waiting"): - self.parent.parentApp._internal_while_waiting() - if hasattr(self.parent.parentApp, "while_waiting"): - self.parent.parentApp.while_waiting() - - def get_and_use_key_press(self): - global TEST_SETTINGS - if (TEST_SETTINGS['TEST_INPUT'] is None) and (TEST_SETTINGS['INPUT_GENERATOR'] is None): - curses.raw() - curses.cbreak() - curses.meta(1) - self.parent.curses_pad.keypad(1) - if self.parent.keypress_timeout: - curses.halfdelay(self.parent.keypress_timeout) - ch = self._get_ch() - if ch == -1: - return self.try_while_waiting() - else: - self.parent.curses_pad.timeout(-1) - ch = self._get_ch() - # handle escape-prefixed rubbish. - if ch == curses.ascii.ESC: - #self.parent.curses_pad.timeout(1) - self.parent.curses_pad.nodelay(1) - ch2 = self.parent.curses_pad.getch() - if ch2 != -1: - ch = curses.ascii.alt(ch2) - self.parent.curses_pad.timeout(-1) # back to blocking mode - #curses.flushinp() - elif (TEST_SETTINGS['INPUT_GENERATOR']): - self._last_get_ch_was_unicode = True - try: - ch = next(TEST_SETTINGS['INPUT_GENERATOR']) - except StopIteration: - if TEST_SETTINGS['CONTINUE_AFTER_TEST_INPUT']: - TEST_SETTINGS['INPUT_GENERATOR'] = None - return - else: - raise ExhaustedTestInput - else: - self._last_get_ch_was_unicode = True - try: - ch = TEST_SETTINGS['TEST_INPUT'].pop(0) - TEST_SETTINGS['TEST_INPUT_LOG'].append(ch) - except IndexError: - if TEST_SETTINGS['CONTINUE_AFTER_TEST_INPUT']: - TEST_SETTINGS['TEST_INPUT'] = None - return - else: - raise ExhaustedTestInput - - self.handle_input(ch) - if self.check_value_change: - self.when_check_value_changed() - if self.check_cursor_move: - self.when_check_cursor_moved() - - - self.try_adjust_widgets() - - def intersted_in_mouse_event(self, mouse_event): - if not self.editable and not self.interested_in_mouse_even_when_not_editable: - return False - mouse_id, x, y, z, bstate = mouse_event - x += self.parent.show_from_x - y += self.parent.show_from_y - if self.relx <= x <= self.relx + self.width-1 + self.parent.show_atx: - if self.rely <= y <= self.rely + self.height-1 + self.parent.show_aty: - return True - return False - - def handle_mouse_event(self, mouse_event): - # mouse_id, x, y, z, bstate = mouse_event - pass - - def interpret_mouse_event(self, mouse_event): - mouse_id, x, y, z, bstate = mouse_event - x += self.parent.show_from_x - y += self.parent.show_from_y - rel_y = y - self.rely - self.parent.show_aty - rel_x = x - self.relx - self.parent.show_atx - return (mouse_id, rel_x, rel_y, z, bstate) - - #def when_parent_changes_value(self): - # Can be called by forms when they chage their value. - #pass - - def when_check_value_changed(self): - "Check whether the widget's value has changed and call when_valued_edited if so." - try: - if self.value == self._old_value: - return False - except AttributeError: - self._old_value = copy.deepcopy(self.value) - self.when_value_edited() - # Value must have changed: - self._old_value = copy.deepcopy(self.value) - self._internal_when_value_edited() - self.when_value_edited() - if hasattr(self, 'parent_widget'): - self.parent_widget.when_value_edited() - self.parent_widget._internal_when_value_edited() - return True - - def _internal_when_value_edited(self): - if self.value_changed_callback: - return self.value_changed_callback(widget=self) - - def when_value_edited(self): - """Called when the user edits the value of the widget. Will usually also be called the first time - that the user edits the widget.""" - pass - - def when_check_cursor_moved(self): - if hasattr(self, 'cursor_line'): - cursor = self.cursor_line - elif hasattr(self, 'cursor_position'): - cursor = self.cursor_position - elif hasattr(self, 'edit_cell'): - cursor = copy.copy(self.edit_cell) - else: - return None - try: - if self._old_cursor == cursor: - return False - except AttributeError: - pass - # Value must have changed: - self._old_cursor = cursor - self.when_cursor_moved() - if hasattr(self, 'parent_widget'): - self.parent_widget.when_cursor_moved() - - def when_cursor_moved(self): - "Called when the cursor moves" - pass - - def safe_string(self, this_string): - """Check that what you are trying to display contains only - printable chars. (Try to catch dodgy input). Give it a string, - and it will return a string safe to print - without touching - the original. In Python 3 this function is not needed - - N.B. This will return a unicode string on python 3 and a utf-8 string - on python2 - """ - try: - if not this_string: - return "" - #this_string = str(this_string) - # In python 3 - #if sys.version_info[0] >= 3: - # return this_string.replace('\n', ' ') - if self.__class__._SAFE_STRING_STRIPS_NL == True: - rtn_value = this_string.replace('\n', ' ') - else: - rtn_value = this_string - - # Does the terminal want ascii? - if self._force_ascii: - if isinstance(rtn_value, bytes): - # no it isn't. - try: - rtn_value = rtn_value.decode(self.encoding, 'replace') - except TypeError: - # Python2.6 - rtn_value = rtn_value.decode(self.encoding, 'replace') - else: - if sys.version_info[0] >= 3: - # even on python3, in this case, we want a string that - # contains only ascii chars - but in unicode, so: - rtn_value = rtn_value.encode('ascii', 'replace').decode() - return rtn_value - else: - return rtn_value.encode('ascii', 'replace') - return rtn_value - # If not.... - if not GlobalOptions.ASCII_ONLY: - # is the string already unicode? - if isinstance(rtn_value, bytes): - # no it isn't. - try: - rtn_value = rtn_value.decode(self.encoding, 'replace') - except: - # Python2.6 - rtn_value = rtn_value.decode(self.encoding, 'replace') - if sys.version_info[0] >= 3: - return rtn_value - else: - return rtn_value.encode('utf-8', 'replace') - else: - rtn = self.safe_filter(this_string) - return rtn - except: - if DEBUG: - raise - else: - return "*ERROR DISPLAYING STRING*" - - def safe_filter(self, this_string): - try: - this_string = this_string.decode(self.encoding, 'replace') - return this_string.encode('ascii', 'replace').decode() - except: - # Things have gone badly wrong if we get here, but let's try to salvage it. - try: - if self._safe_filter_value_cache[0] == this_string: - return self._safe_filter_value_cache[1] - except AttributeError: - pass - s = [] - for cha in this_string.replace('\n', ' '): - #if curses.ascii.isprint(cha): - # s.append(cha) - #else: - # s.append('?') - try: - s.append(str(cha)) - except: - s.append('?') - s = ''.join(s) - - self._safe_filter_value_cache = (this_string, s) - - return s - - #s = '' - #for cha in this_string.replace('\n', ''): - # try: - # s += cha.encode('ascii') - # except: - # s += '?' - #return s - -class DummyWidget(Widget): - "This widget is invisible and does nothing. Which is sometimes important." - def __init__(self, screen, *args, **keywords): - super(DummyWidget, self).__init__(screen, *args, **keywords) - self.height = 0 - self.widget = 0 - self.parent = screen - def display(self): - pass - def update(self, clear=False): - pass - def set_editable(self, value): - if value: self._is_editable = True - else: self._is_editable = False - def get_editable(self): - return(self._is_editable) - def clear(self, usechar=' '): - pass - def calculate_area_needed(self): - return 0,0 - - diff --git a/npyscreen/__init__.py b/npyscreen/__init__.py index e9cbde2..37bd9c8 100755 --- a/npyscreen/__init__.py +++ b/npyscreen/__init__.py @@ -36,8 +36,8 @@ from .wgbutton import MiniButton as Button from .wgbutton import MiniButtonPress as ButtonPress -from .wgtextbox import Textfield, FixedText -from .wgtitlefield import TitleText, TitleFixedText +from .wgtextbox import Textfield, FixedText, Numericfield +from .wgtitlefield import TitleText, TitleFixedText, TitleNumeric from .wgpassword import PasswordEntry, TitlePassword from .wgannotatetextbox import AnnotateTextboxBase from .wgannotatetextbox import AnnotateTextboxBaseRight diff --git a/npyscreen/apNPSApplicationEvents.py b/npyscreen/apNPSApplicationEvents.py index 16ab8f0..4273d1e 100644 --- a/npyscreen/apNPSApplicationEvents.py +++ b/npyscreen/apNPSApplicationEvents.py @@ -6,7 +6,7 @@ class NPSEventQueue(object): def __init__(self): self.interal_queue = collections.deque() - + def get(self, maximum=None): if maximum is None: maximum = -1 @@ -15,12 +15,12 @@ def get(self, maximum=None): try: yield self.interal_queue.pop() except IndexError: - return + raise StopIteration counter += 1 - + def put(self, event): self.interal_queue.append(event) - + class StandardApp(NPSAppManaged, EventHandler): MAINQUEUE_TYPE = NPSEventQueue keypress_timeout_default = 2 @@ -35,30 +35,30 @@ def __init__(self): self.event_queues = {} self.initalize_application_event_queues() self.initialize_event_handling() - + def _internal_while_waiting(self): # Parent NPSAppManaged does not define this, so no need to call. self.process_event_queues(max_events_per_queue=self.max_events_per_queue) - - + + def initalize_application_event_queues(self): # in the standard application the event queue is not threaded so... main_queue = self.MAINQUEUE_TYPE() self.event_queues['MAINQUEUE'] = main_queue - + def process_event_queues(self, max_events_per_queue=None): for queue in self.event_queues.values(): for event in queue.get(maximum=max_events_per_queue): self.process_event(event) - + def register_for_event(self, registering_object, event_name): if event_name not in self.event_directory: self.event_directory[event_name] = weakref.WeakSet() self.event_directory[event_name].add(registering_object) - + def queue_event(self, event, queue='MAINQUEUE'): self.event_queues[queue].put(event) - + def process_event(self, event): discard_list = [] if event.name not in self.event_directory: @@ -70,6 +70,6 @@ def process_event(self, event): result = registered_object.handle_event(event) if result is False: discard_list.append(registered_object) - + for registered_object in discard_list: self.event_directory[event.name].discard(registered_object) diff --git a/npyscreen/apOptions.py b/npyscreen/apOptions.py index 769125a..8fd23f5 100644 --- a/npyscreen/apOptions.py +++ b/npyscreen/apOptions.py @@ -1,92 +1,101 @@ -import weakref -import textwrap import datetime +import weakref from . import fmForm from . import fmPopup -from . import wgtitlefield from . import wgannotatetextbox -from . import wgmultiline -from . import wgselectone -from . import wgmultiselect -from . import wgeditmultiline from . import wgcheckbox -from . import wgfilenamecombo from . import wgdatecombo +from . import wgeditmultiline +from . import wgfilenamecombo +from . import wgmultiline +from . import wgmultiselect +from . import wgselectone +from . import wgtitlefield + class SimpleOptionForm(fmForm.Form): - def create(self,): + + def create(self, ): self.wOptionList = self.add(OptionListDisplay, ) - + def beforeEditing(self, ): try: self.wOptionList.values = self.value.options except AttributeError: pass - + def afterEditing(self): if self.value.filename: self.value.write_to_file() self.parentApp.switchFormPrevious() + class OptionListDisplayLine(wgannotatetextbox.AnnotateTextboxBase): - ANNOTATE_WIDTH = 25 + + ANNOTATE_WIDTH = 25 + def getAnnotationAndColor(self): return (self.value.get_name_user(), 'LABEL') - + def display_value(self, vl): return vl.get_for_single_line_display() - + + class OptionListDisplay(wgmultiline.MultiLineAction): + _contained_widgets = OptionListDisplayLine + def actionHighlighted(self, act_on_this, key_press): rtn = act_on_this.change_option() self.display() return rtn - + def display_value(self, vl): return vl + class OptionChanger(fmPopup.ActionPopupWide): - def on_ok(self,): + + def on_ok(self, ): self.OPTION_TO_CHANGE.set_from_widget_value(self.OPTION_WIDGET.value) + class OptionList(object): + def __init__(self, filename=None): - self.options = [] + self.options = [] self.filename = filename self.define_serialize_functions() - + def define_serialize_functions(self): self.SERIALIZE_FUNCTIONS = { - OptionFreeText: self.save_text, - OptionSingleChoice: self.save_text, - OptionMultiChoice: self.save_multi_text, - OptionMultiFreeText: self.save_text, - OptionBoolean: self.save_bool, - OptionFilename: self.save_text, - OptionDate: self.save_date, - OptionMultiFreeList: self.save_list, + OptionFreeText: self.save_text, + OptionSingleChoice: self.save_text, + OptionMultiChoice: self.save_multi_text, + OptionMultiFreeText: self.save_text, + OptionBoolean: self.save_bool, + OptionFilename: self.save_text, + OptionDate: self.save_date, + OptionMultiFreeList: self.save_list, } - + self.UNSERIALIZE_FUNCTIONS = { - OptionFreeText: self.reload_text, - OptionSingleChoice: self.reload_text, - OptionMultiChoice: self.load_multi_text, - OptionMultiFreeText: self.reload_text, - OptionBoolean: self.load_bool, - OptionFilename: self.reload_text, - OptionDate: self.load_date, - OptionMultiFreeList: self.load_list, + OptionFreeText: self.reload_text, + OptionSingleChoice: self.reload_text, + OptionMultiChoice: self.load_multi_text, + OptionMultiFreeText: self.reload_text, + OptionBoolean: self.load_bool, + OptionFilename: self.reload_text, + OptionDate: self.load_date, + OptionMultiFreeList: self.load_list, } - + def get(self, name): for o in self.options: if o.get_real_name() == name: return o - - - + def write_to_file(self, fn=None, exclude_defaults=True): fn = fn or self.filename if not fn: @@ -95,53 +104,53 @@ def write_to_file(self, fn=None, exclude_defaults=True): for opt in self.options: if opt.default != opt.get(): f.write('%s=%s\n' % (opt.get_real_name(), self.serialize_option_value(opt))) - + def reload_from_file(self, fn=None): fn = fn or self.filename with open(fn, 'r', encoding="utf-8") as f: for line in f.readlines(): - line = line.strip() - name, value = line.split("=", maxsplit=1) - for option in self.options: - if option.get_real_name() == name: - option.set(self.deserialize_option_value(option, value.encode('ascii'))) - + line = line.strip() + name, value = line.split("=", maxsplit=1) + for option in self.options: + if option.get_real_name() == name: + option.set(self.deserialize_option_value(option, value.encode('ascii'))) + def serialize_option_value(self, option): return self.SERIALIZE_FUNCTIONS[option.__class__](option) - + def deserialize_option_value(self, option, serialized): return self.UNSERIALIZE_FUNCTIONS[option.__class__](serialized) - + def _encode_text_for_saving(self, txt): return txt.encode('unicode-escape').decode('ascii') - + def _decode_text_from_saved(self, txt): return txt.decode('unicode-escape') - + def save_text(self, option): s = option.get() if not s: s = '' return self._encode_text_for_saving(s) - + def reload_text(self, txt): return self._decode_text_from_saved(txt) - + def save_bool(self, option): if option.get(): return 'True' else: return 'False' - + def load_bool(self, txt): txt = txt.decode() - if txt in ('True', ): + if txt in ('True',): return True - elif txt in ('False', ): + elif txt in ('False',): return False else: raise ValueError("Could not decode %s" % txt) - + def save_multi_text(self, option): line = [] opt = option.get() @@ -150,50 +159,50 @@ def save_multi_text(self, option): for text_part in opt: line.append(text_part.encode('unicode-escape').decode('ascii')) return "\t".join(line) - + def load_multi_text(self, text): parts = text.decode('ascii').split("\t") rtn = [] for p in parts: rtn.append(p.encode('ascii').decode('unicode-escape')) return rtn - + def save_list(self, lst): pts_to_save = [] for p in lst.get(): pts_to_save.append(self._encode_text_for_saving(p)) return "\t".join(pts_to_save) - + def load_list(self, text): parts = text.decode('ascii').split("\t") parts_to_return = [] for p in parts: parts_to_return.append(self._decode_text_from_saved(p.encode('ascii'))) return parts_to_return - + def save_date(self, option): if option.get(): return option.get().ctime() else: return None - + def load_date(self, txt): if txt: return datetime.datetime.strptime(txt.decode(), "%a %b %d %H:%M:%S %Y") else: return None - - - + + class Option(object): DEFAULT = '' - def __init__(self, name, - value=None, - documentation=None, - short_explanation=None, - option_widget_keywords = None, - default = None, - ): + + def __init__(self, name, + value=None, + documentation=None, + short_explanation=None, + option_widget_keywords=None, + default=None, + ): self.name = name self.default = default or self.DEFAULT self.set(value or self.default) @@ -201,81 +210,81 @@ def __init__(self, name, self.short_explanation = short_explanation self.option_widget_keywords = option_widget_keywords or {} self.default = default or self.DEFAULT - + def when_set(self): pass - - def get(self,): + + def get(self, ): return self.value - + def get_for_single_line_display(self): return repr(self.value) - + def set_from_widget_value(self, vl): self.set(vl) - + def set(self, value): self.value = value self.when_set() - + def get_real_name(self): # This might be for internal use return self.name - + def get_name_user(self): # You could do translation here. return self.name - + def _set_up_widget_values(self, option_form, main_option_widget): main_option_widget.value = self.value - + def change_option(self): option_changing_form = OptionChanger() option_changing_form.OPTION_TO_CHANGE = weakref.proxy(self) if self.documentation: - explanation_widget = option_changing_form.add(wgmultiline.Pager, - editable=False, value=None, - max_height=(option_changing_form.lines - 3) // 2, - autowrap=True, - ) + explanation_widget = option_changing_form.add(wgmultiline.Pager, + editable=False, value=None, + max_height=(option_changing_form.lines - 3) // 2, + autowrap=True, + ) option_changing_form.nextrely += 1 explanation_widget.values = self.documentation - - - option_widget = option_changing_form.add(self.WIDGET_TO_USE, - name=self.get_name_user(), - **self.option_widget_keywords - ) + + option_widget = option_changing_form.add(self.WIDGET_TO_USE, + name=self.get_name_user(), + **self.option_widget_keywords + ) option_changing_form.OPTION_WIDGET = option_widget - self._set_up_widget_values(option_changing_form, option_widget) + self._set_up_widget_values(option_changing_form, option_widget) option_changing_form.edit() - + class OptionLimitedChoices(Option): def __init__(self, name, choices=None, *args, **keywords): super(OptionLimitedChoices, self).__init__(name, *args, **keywords) choices = choices or [] self.setChoices(choices) - + def setChoices(self, choices): self.choices = choices - - def getChoices(self,): + + def getChoices(self, ): return self.choices - + def _set_up_widget_values(self, option_form, main_option_widget): - main_option_widget.value = [] + main_option_widget.value = [] main_option_widget.values = self.getChoices() for x in range(len(main_option_widget.values)): if self.value and main_option_widget.values[x] in self.value: main_option_widget.value.append(x) - + def set_from_widget_value(self, vl): value = [] for v in vl: value.append(self.choices[v]) self.set(value) - + + class OptionFreeText(Option): WIDGET_TO_USE = wgtitlefield.TitleText @@ -294,7 +303,7 @@ class OptionMultiFreeList(Option): DEFAULT = [] def _set_up_widget_values(self, option_form, main_option_widget): main_option_widget.value = "\n".join(self.get()) - + def set_from_widget_value(self, vl): self.set(vl.split("\n")) @@ -304,7 +313,8 @@ class OptionBoolean(Option): class OptionFilename(Option): DEFAULT = '' WIDGET_TO_USE = wgfilenamecombo.FilenameCombo - + + class OptionDate(Option): DEFAULT = None - WIDGET_TO_USE = wgdatecombo.DateCombo \ No newline at end of file + WIDGET_TO_USE = wgdatecombo.DateCombo diff --git a/npyscreen/fmActionFormV2.py b/npyscreen/fmActionFormV2.py index aafc8d7..21bf2bd 100644 --- a/npyscreen/fmActionFormV2.py +++ b/npyscreen/fmActionFormV2.py @@ -1,107 +1,113 @@ import operator import weakref -from . import wgwidget as widget -from . import wgbutton + from . import fmForm +from . import wgbutton +from . import wgwidget as widget + class ActionFormV2(fmForm.FormBaseNew): class OK_Button(wgbutton.MiniButtonPress): def whenPressed(self): return self.parent._on_ok() - + class Cancel_Button(wgbutton.MiniButtonPress): def whenPressed(self): return self.parent._on_cancel() - + OKBUTTON_TYPE = OK_Button CANCELBUTTON_TYPE = Cancel_Button CANCEL_BUTTON_BR_OFFSET = (2, 12) - OK_BUTTON_TEXT = "OK" - CANCEL_BUTTON_TEXT = "Cancel" + OK_BUTTON_TEXT = "OK" + CANCEL_BUTTON_TEXT = "Cancel" + def __init__(self, *args, **keywords): super(ActionFormV2, self).__init__(*args, **keywords) self._added_buttons = {} self.create_control_buttons() - def create_control_buttons(self): - self._add_button('ok_button', - self.__class__.OKBUTTON_TYPE, - self.__class__.OK_BUTTON_TEXT, - 0 - self.__class__.OK_BUTTON_BR_OFFSET[0], - 0 - self.__class__.OK_BUTTON_BR_OFFSET[1] - len(self.__class__.OK_BUTTON_TEXT), - None - ) - - self._add_button('cancel_button', - self.__class__.CANCELBUTTON_TYPE, - self.__class__.CANCEL_BUTTON_TEXT, - 0 - self.__class__.CANCEL_BUTTON_BR_OFFSET[0], - 0 - self.__class__.CANCEL_BUTTON_BR_OFFSET[1] - len(self.__class__.CANCEL_BUTTON_TEXT), - None - ) - + self._add_button('cancel_button', + self.__class__.CANCELBUTTON_TYPE, + self.__class__.CANCEL_BUTTON_TEXT, + 0 - self.__class__.CANCEL_BUTTON_BR_OFFSET[0], + 0 - self.__class__.CANCEL_BUTTON_BR_OFFSET[1] - len(self.__class__.CANCEL_BUTTON_TEXT), + None + ) + + self._add_button('ok_button', + self.__class__.OKBUTTON_TYPE, + self.__class__.OK_BUTTON_TEXT, + 0 - self.__class__.OK_BUTTON_BR_OFFSET[0], + 0 - self.__class__.OK_BUTTON_BR_OFFSET[1] - len(self.__class__.OK_BUTTON_TEXT), + None + ) + def on_cancel(self): pass - + def on_ok(self): pass - + def _on_ok(self): self.editing = self.on_ok() - + def _on_cancel(self): - self.editing = self.on_cancel() - + self.editing = self.on_cancel() + def set_up_exit_condition_handlers(self): super(ActionFormV2, self).set_up_exit_condition_handlers() self.how_exited_handers.update({ - widget.EXITED_ESCAPE: self.find_cancel_button + widget.EXITED_ESCAPE: self.find_cancel_button }) def find_cancel_button(self): - self.editw = len(self._widgets__)-2 - + self.editw = len(self._widgets__) - 2 + def _add_button(self, button_name, button_type, button_text, button_rely, button_relx, button_function): tmp_rely, tmp_relx = self.nextrely, self.nextrelx this_button = self.add_widget( - button_type, - name=button_text, - rely=button_rely, - relx=button_relx, - when_pressed_function = button_function, - use_max_space=True, - ) + button_type, + name=button_text, + rely=button_rely, + relx=button_relx, + when_pressed_function=button_function, + use_max_space=True, + ) self._added_buttons[button_name] = this_button self.nextrely, self.nextrelx = tmp_rely, tmp_relx - - + def pre_edit_loop(self): - self._widgets__.sort(key=operator.attrgetter('relx')) + # these next two lines do not work as the author expected... + self._widgets__.sort(key=operator.attrgetter('relx')) # is overridden by next line - why do it? self._widgets__.sort(key=operator.attrgetter('rely')) + # update the _widgets_by_id also + for w in self._widgets__: + self._widgets_by_id[self._widgets__.index(w)] = weakref.proxy(w) if not self.preserve_selected_widget: self.editw = 0 - if not self._widgets__[self.editw].editable: + if not self._widgets__[self.editw].editable: self.find_next_editable() - + def post_edit_loop(self): - pass - + pass + def _during_edit_loop(self): pass + class ActionFormExpandedV2(ActionFormV2): - BLANK_LINES_BASE = 1 - OK_BUTTON_BR_OFFSET = (1,6) + BLANK_LINES_BASE = 1 + OK_BUTTON_BR_OFFSET = (1, 6) CANCEL_BUTTON_BR_OFFSET = (1, 12) -class ActionFormMinimal(ActionFormV2): - def create_control_buttons(self): - self._add_button('ok_button', - self.__class__.OKBUTTON_TYPE, - self.__class__.OK_BUTTON_TEXT, - 0 - self.__class__.OK_BUTTON_BR_OFFSET[0], - 0 - self.__class__.OK_BUTTON_BR_OFFSET[1] - len(self.__class__.OK_BUTTON_TEXT), - None - ) +class ActionFormMinimal(ActionFormV2): + def create_control_buttons(self): + self._add_button('ok_button', + self.__class__.OKBUTTON_TYPE, + self.__class__.OK_BUTTON_TEXT, + 0 - self.__class__.OK_BUTTON_BR_OFFSET[0], + 0 - self.__class__.OK_BUTTON_BR_OFFSET[1] - len(self.__class__.OK_BUTTON_TEXT), + None + ) diff --git a/npyscreen/fmForm.py b/npyscreen/fmForm.py index 9145c77..9ac466c 100755 --- a/npyscreen/fmForm.py +++ b/npyscreen/fmForm.py @@ -1,39 +1,38 @@ #!/usr/bin/python -from . import proto_fm_screen_area -from . import wgwidget as widget -from . import wgbutton as button -import weakref -from . import npyspmfuncs as pmfuncs -#import Menu -import curses import _curses +# import Menu +import curses +import weakref + +from . import fm_form_edit_loop as form_edit_loop from . import npysGlobalOptions -from . import wgwidget_proto -from . import fm_form_edit_loop as form_edit_loop -from . import util_viewhelp from . import npysGlobalOptions as GlobalOptions +from . import proto_fm_screen_area +from . import util_viewhelp +from . import wgbutton as button +from . import wgwidget as widget +from . import wgwidget_proto from .eveventhandler import EventHandler -from .globals import DISABLE_RESIZE_SYSTEM - -class _FormBase(proto_fm_screen_area.ScreenArea, - widget.InputHandler, - wgwidget_proto._LinePrinter, - EventHandler): - BLANK_COLUMNS_RIGHT= 2 - BLANK_LINES_BASE = 2 - OK_BUTTON_TEXT = 'OK' - OK_BUTTON_BR_OFFSET = (2,6) + + +class _FormBase(proto_fm_screen_area.ScreenArea, + widget.InputHandler, + wgwidget_proto._LinePrinter, + EventHandler): + BLANK_COLUMNS_RIGHT = 2 + BLANK_LINES_BASE = 2 + OK_BUTTON_TEXT = 'OK' + OK_BUTTON_BR_OFFSET = (2, 6) OKBUTTON_TYPE = button.MiniButton DEFAULT_X_OFFSET = 2 - PRESERVE_SELECTED_WIDGET_DEFAULT = False # Preserve cursor location between displays? + PRESERVE_SELECTED_WIDGET_DEFAULT = False # Preserve cursor location between displays? FRAMED = True ALLOW_RESIZE = True - FIX_MINIMUM_SIZE_WHEN_CREATED = True + FIX_MINIMUM_SIZE_WHEN_CREATED = True WRAP_HELP = True - - - def __init__(self, name=None, parentApp=None, framed=None, help=None, color='FORMDEFAULT', - widget_list=None, cycle_widgets=False, *args, **keywords): + + def __init__(self, name=None, parentApp=None, framed=None, help=None, color='FORMDEFAULT', + widget_list=None, cycle_widgets=False, *args, **keywords): super(_FormBase, self).__init__(*args, **keywords) self.initialize_event_handling() self.preserve_selected_widget = self.__class__.PRESERVE_SELECTED_WIDGET_DEFAULT @@ -50,7 +49,7 @@ def __init__(self, name=None, parentApp=None, framed=None, help=None, color='FOR self.framed = self.__class__.FRAMED else: self.framed = framed - self.name=name + self.name = name self.editing = False ## OLD MENU CODE REMOVED self.__menus = [] self._clear_all_widgets() @@ -58,7 +57,7 @@ def __init__(self, name=None, parentApp=None, framed=None, help=None, color='FOR self.help = help self.color = color - + self.cycle_widgets = cycle_widgets self.set_up_handlers() @@ -68,28 +67,27 @@ def __init__(self, name=None, parentApp=None, framed=None, help=None, color='FOR if widget_list: self.create_widgets_from_list(widget_list) self.create() - + if self.FIX_MINIMUM_SIZE_WHEN_CREATED: self.min_l = self.lines self.min_c = self.columns - - + def resize(self): pass def _clear_all_widgets(self, ): - self._widgets__ = [] + self._widgets__ = [] self._widgets_by_id = {} self._next_w_id = 0 self.nextrely = self.DEFAULT_NEXTRELY self.nextrelx = self.DEFAULT_X_OFFSET - self.editw = 0 # Index of widget to edit. + self.editw = 0 # Index of widget to edit. def create_widgets_from_list(self, widget_list): # This code is currently experimental, and the API may change in future releases # (npyscreen.TextBox, {'rely': 2, 'relx': 7, 'editable': False}) for line in widget_list: - w_type = line[0] + w_type = line[0] keywords = line[1] self.add_widget(w_type, **keywords) @@ -100,37 +98,36 @@ def set_value(self, value): _w.when_parent_changes_value() def _resize(self, *args): - global DISABLE_RESIZE_SYSTEM + #global DISABLE_RESIZE_SYSTEM + from .globals import DISABLE_RESIZE_SYSTEM if DISABLE_RESIZE_SYSTEM: return False - + if not self.ALLOW_RESIZE: return False - + if hasattr(self, 'parentApp'): self.parentApp.resize() - + self._create_screen() self.resize() for w in self._widgets__: w._resize() self.DISPLAY() - - def create(self): """Programmers should over-ride this in derived classes, creating widgets here""" pass def set_up_handlers(self): self.complex_handlers = [] - self.handlers = { - curses.KEY_F1: self.h_display_help, - "KEY_F(1)": self.h_display_help, - "^O": self.h_display_help, - "^L": self.h_display, - curses.KEY_RESIZE: self._resize, - } + self.handlers = { + curses.KEY_F1: self.h_display_help, + "KEY_F(1)": self.h_display_help, + "^O": self.h_display_help, + "^L": self.h_display, + curses.KEY_RESIZE: self._resize, + } def set_up_exit_condition_handlers(self): # What happens when widgets exit? @@ -138,23 +135,23 @@ def set_up_exit_condition_handlers(self): # be used to look up the following table. self.how_exited_handers = { - widget.EXITED_DOWN: self.find_next_editable, - widget.EXITED_RIGHT: self.find_next_editable, - widget.EXITED_UP: self.find_previous_editable, - widget.EXITED_LEFT: self.find_previous_editable, - widget.EXITED_ESCAPE: self.do_nothing, - True: self.find_next_editable, # A default value - widget.EXITED_MOUSE: self.get_and_use_mouse_event, - False: self.do_nothing, - None: self.do_nothing, - } + widget.EXITED_DOWN: self.find_next_editable, + widget.EXITED_RIGHT: self.find_next_editable, + widget.EXITED_UP: self.find_previous_editable, + widget.EXITED_LEFT: self.find_previous_editable, + widget.EXITED_ESCAPE: self.do_nothing, + True: self.find_next_editable, # A default value + widget.EXITED_MOUSE: self.get_and_use_mouse_event, + False: self.do_nothing, + None: self.do_nothing, + } def handle_exiting_widgets(self, condition): self.how_exited_handers[condition]() def do_nothing(self, *args, **keywords): pass - + def exit_editing(self, *args, **keywords): self.editing = False try: @@ -165,17 +162,16 @@ def exit_editing(self, *args, **keywords): self._widgets__[self.editw].editing = False except: pass - + def adjust_widgets(self): """This method can be overloaded by derived classes. It is called when editing any widget, as opposed to the while_editing() method, which may only be called when moving between widgets. Since it is called for every keypress, and perhaps more, be careful when selecting what should be done here.""" - def while_editing(self, *args, **keywords): """This function gets called during the edit loop, on each iteration of the loop. It does nothing: it is here to make customising the loop - as easy as overriding this function. A proxy to the currently selected widget is + as easy as overriding this function. A proxy to the currently selected widget is passed to the function.""" def on_screen(self): @@ -192,14 +188,13 @@ def on_screen(self): self.show_from_y = 0 self.show_from_x = 0 - while w.rely + w_my -1 > self.show_from_y + max_y: + while w.rely + w_my - 1 > self.show_from_y + max_y: self.show_from_y += 1 while w.rely < self.show_from_y: self.show_from_y -= 1 - - while w.relx + w_mx -1 > self.show_from_x + max_x: + while w.relx + w_mx - 1 > self.show_from_x + max_x: self.show_from_x += 1 while w.relx < self.show_from_x: @@ -208,11 +203,12 @@ def on_screen(self): def h_display_help(self, input): if self.help == None: return if self.name: - help_name="%s Help" %(self.name) - else: help_name=None + help_name = "%s Help" % (self.name) + else: + help_name = None curses.flushinp() util_viewhelp.view_help(self.help, title=help_name, autowrap=self.WRAP_HELP) - #select.ViewText(self.help, name=help_name) + # select.ViewText(self.help, name=help_name) self.display() return True @@ -224,23 +220,22 @@ def DISPLAY(self): if self.editing and self.editw is not None: self._widgets__[self.editw].display() - def h_display(self, input): self._resize() self.DISPLAY() - + def safe_get_mouse_event(self): try: mouse_event = curses.getmouse() return mouse_event except _curses.error: return None - + def get_and_use_mouse_event(self): mouse_event = self.safe_get_mouse_event() if mouse_event: self.use_mouse_event(mouse_event) - + def use_mouse_event(self, mouse_event): wg = self.find_mouse_handler(mouse_event) if wg: @@ -249,9 +244,9 @@ def use_mouse_event(self, mouse_event): wg.handle_mouse_event(mouse_event) else: curses.beep() - + def find_mouse_handler(self, mouse_event): - #mouse_id, x, y, z, bstate = mouse_event + # mouse_id, x, y, z, bstate = mouse_event for wd in self._widgets__: try: if wd.intersted_in_mouse_event(mouse_event) == True: @@ -259,37 +254,34 @@ def find_mouse_handler(self, mouse_event): except AttributeError: pass return None - + def set_editing(self, wdg): try: self.editw = self._widgets__.index(wdg) except ValueError: pass - def find_next_editable(self, *args): - if not self.editw == len(self._widgets__): - if not self.cycle_widgets: - r = list(range(self.editw+1, len(self._widgets__))) - else: - r = list(range(self.editw+1, len(self._widgets__))) + list(range(0, self.editw)) - for n in r: - if self._widgets__[n].editable and not self._widgets__[n].hidden: - self.editw = n - break + if not self.cycle_widgets: + r = list(range(self.editw + 1, len(self._widgets__))) + else: + r = list(range(self.editw + 1, len(self._widgets__))) + list(range(0, self.editw)) + for n in r: + if self._widgets_by_id[n].editable and not self._widgets_by_id[n].hidden: + self.editw = n + break self.display() - def find_previous_editable(self, *args): - if not self.editw == 0: + if not self.editw == 0: # remember that xrange does not return the 'last' value, # so go to -1, not 0! (fence post error in reverse) - for n in range(self.editw-1, -1, -1 ): - if self._widgets__[n].editable and not self._widgets__[n].hidden: + for n in range(self.editw - 1, -1, -1): + if self._widgets_by_id[n].editable and not self._widgets_by_id[n].hidden: self.editw = n break - #def widget_useable_space(self, rely=0, relx=0): + # def widget_useable_space(self, rely=0, relx=0): # #Slightly misreports space available. # mxy, mxx = self.lines-1, self.columns-1 # return (mxy-1-rely, mxx-1-relx) @@ -306,9 +298,8 @@ def center_on_display(self): else: self.show_atx = 0 - def display(self, clear=False): - #APPLICATION_THEME_MANAGER.setTheme(self) + # APPLICATION_THEME_MANAGER.setTheme(self) if curses.has_colors() and not npysGlobalOptions.DISABLE_ALL_COLORS: self.curses_pad.attrset(0) color_attribute = self.theme_manager.findPair(self, self.color) @@ -326,16 +317,16 @@ def display(self, clear=False): def draw_title_and_help(self): try: if self.name: - _title = self.name[:(self.columns-4)] + _title = self.name[:(self.columns - 4)] _title = ' ' + str(_title) + ' ' - #self.curses_pad.addstr(0,1, ' '+str(_title)+' ') + # self.curses_pad.addstr(0,1, ' '+str(_title)+' ') if isinstance(_title, bytes): _title = _title.decode('utf-8', 'replace') - self.add_line(0,1, - _title, - self.make_attributes_list(_title, curses.A_NORMAL), - self.columns-4 - ) + self.add_line(0, 1, + _title, + self.make_attributes_list(_title, curses.A_NORMAL), + self.columns - 4 + ) except: pass @@ -345,11 +336,11 @@ def draw_title_and_help(self): if isinstance(help_advert, bytes): help_advert = help_advert.decode('utf-8', 'replace') self.add_line( - 0, self.curses_pad.getmaxyx()[1]-len(help_advert)-2, - help_advert, - self.make_attributes_list(help_advert, curses.A_NORMAL), - len(help_advert) - ) + 0, self.curses_pad.getmaxyx()[1] - len(help_advert) - 2, + help_advert, + self.make_attributes_list(help_advert, curses.A_NORMAL), + len(help_advert) + ) except: pass @@ -361,7 +352,6 @@ def draw_form(self): self.curses_pad.border() self.draw_title_and_help() - def add_widget(self, widgetClass, w_id=None, max_height=None, rely=None, relx=None, *args, **keywords): """Add a widget to the form. The form will do its best to decide on placing, unless you override it. The form of this function is add_widget(WidgetClass, ....) with any arguments or keywords supplied to @@ -375,16 +365,16 @@ def add_widget(self, widgetClass, w_id=None, max_height=None, rely=None, relx=No if relx is None: relx = self.nextrelx - if max_height is False: + if max_height in (None, False): max_height = self.curses_pad.getmaxyx()[0] - rely - 1 - _w = widgetClass(self, - rely=rely, - relx=relx, - max_height=max_height, - *args, **keywords) + _w = widgetClass(self, + rely=rely, + relx=relx, + max_height=max_height, + *args, **keywords) - self.nextrely = _w.height + _w.rely + self.nextrely = _w.height + _w.rely self._widgets__.append(_w) w_proxy = weakref.proxy(_w) if not w_id: @@ -399,65 +389,75 @@ def get_widget(self, w_id): add = add_widget + class FormBaseNew(form_edit_loop.FormNewEditLoop, _FormBase): # use the new-style edit loop. pass + # actually defined in _FormBase + #def resize(self): + # super(Form, self).resize() + # self.move_ok_button() + + class Form(form_edit_loop.FormDefaultEditLoop, _FormBase, ): - #use the old-style edit loop + # use the old-style edit loop pass - + def resize(self): super(Form, self).resize() self.move_ok_button() - - - + + class FormBaseNewExpanded(form_edit_loop.FormNewEditLoop, _FormBase): - BLANK_LINES_BASE = 1 - OK_BUTTON_BR_OFFSET = (1,6) + BLANK_LINES_BASE = 1 + OK_BUTTON_BR_OFFSET = (1, 6) # use the new-style edit loop. pass + class FormExpanded(form_edit_loop.FormDefaultEditLoop, _FormBase, ): - BLANK_LINES_BASE = 1 - OK_BUTTON_BR_OFFSET = (1,6) - #use the old-style edit loop + BLANK_LINES_BASE = 1 + OK_BUTTON_BR_OFFSET = (1, 6) + # use the old-style edit loop pass - - - + class TitleForm(Form): """A form without a box, just a title line""" - BLANK_LINES_BASE = 1 - DEFAULT_X_OFFSET = 1 - DEFAULT_NEXTRELY = 1 + BLANK_LINES_BASE = 1 + DEFAULT_X_OFFSET = 1 + DEFAULT_NEXTRELY = 1 BLANK_COLUMNS_RIGHT = 0 - OK_BUTTON_BR_OFFSET = (1,6) - #OKBUTTON_TYPE = button.MiniButton - #DEFAULT_X_OFFSET = 1 + OK_BUTTON_BR_OFFSET = (1, 6) + + # OKBUTTON_TYPE = button.MiniButton + # DEFAULT_X_OFFSET = 1 def draw_form(self): MAXY, MAXX = self.curses_pad.getmaxyx() - self.curses_pad.hline(0, 0, curses.ACS_HLINE, MAXX) + self.curses_pad.hline(0, 0, curses.ACS_HLINE, MAXX) self.draw_title_and_help() - + + class TitleFooterForm(TitleForm): - BLANK_LINES_BASE=1 + BLANK_LINES_BASE = 1 + def draw_form(self): MAXY, MAXX = self.curses_pad.getmaxyx() if self.editing: - self.curses_pad.hline(MAXY-1, 0, curses.ACS_HLINE, - MAXX - self.__class__.OK_BUTTON_BR_OFFSET[1] - 1) + self.curses_pad.hline(MAXY - 1, 0, curses.ACS_HLINE, + MAXX - self.__class__.OK_BUTTON_BR_OFFSET[1] - 1) else: - self.curses_pad.hline(MAXY-1, 0, curses.ACS_HLINE, MAXX-1) + self.curses_pad.hline(MAXY - 1, 0, curses.ACS_HLINE, MAXX - 1) super(TitleFooterForm, self).draw_form() + class SplitForm(Form): MOVE_LINE_ON_RESIZE = False """Just the same as the Title Form, but with a horizontal line""" + def __init__(self, draw_line_at=None, *args, **keywords): super(SplitForm, self).__init__(*args, **keywords) if not hasattr(self, 'draw_line_at'): @@ -465,24 +465,22 @@ def __init__(self, draw_line_at=None, *args, **keywords): self.draw_line_at = draw_line_at else: self.draw_line_at = self.get_half_way() - - def draw_form(self,): + + def draw_form(self, ): MAXY, MAXX = self.curses_pad.getmaxyx() super(SplitForm, self).draw_form() - self.curses_pad.hline(self.draw_line_at, 1, curses.ACS_HLINE, MAXX-2) + self.curses_pad.hline(self.draw_line_at, 1, curses.ACS_HLINE, MAXX - 2) def get_half_way(self): return self.curses_pad.getmaxyx()[0] // 2 - + def resize(self): super(SplitForm, self).resize() if self.MOVE_LINE_ON_RESIZE: self.draw_line_at = self.get_half_way() - + def blank_terminal(): F = _FormBase(framed=False) F.erase() F.display() - - diff --git a/npyscreen/fmFormMultiPage.py b/npyscreen/fmFormMultiPage.py index 095f4d5..55f5276 100644 --- a/npyscreen/fmFormMultiPage.py +++ b/npyscreen/fmFormMultiPage.py @@ -212,7 +212,7 @@ def post_edit_loop(self): return self.edit_return_value -class FormMultiPageWithMenus(fmForm.FormBaseNew): +class FormMultiPageWithMenus(FormMultiPage, wgNMenuDisplay.HasMenus): def __init__(self, *args, **keywords): super(FormMultiPageWithMenus, self).__init__(*args, **keywords) self.initialize_menus() diff --git a/npyscreen/fm_form_edit_loop.py b/npyscreen/fm_form_edit_loop.py index 5072c5e..00e02f3 100755 --- a/npyscreen/fm_form_edit_loop.py +++ b/npyscreen/fm_form_edit_loop.py @@ -23,7 +23,7 @@ def _during_edit_loop(self): def edit_loop(self): self.editing = True self.display() - while not (self._widgets__[self.editw].editable and not self._widgets__[self.editw].hidden): + while not (self._widgets_by_id[self.editw].editable and not self._widgets_by_id[self.editw].hidden): self.editw += 1 if self.editw > len(self._widgets__)-1: self.editing = False @@ -31,16 +31,17 @@ def edit_loop(self): while self.editing: if not self.ALL_SHOWN: self.on_screen() - self.while_editing(weakref.proxy(self._widgets__[self.editw])) + #self.while_editing(weakref.proxy(self._widgets__[self.editw])) + self.while_editing(self._widgets_by_id[self.editw]) self._during_edit_loop() if not self.editing: break - self._widgets__[self.editw].edit() - self._widgets__[self.editw].display() + self._widgets_by_id[self.editw].edit() + self._widgets_by_id[self.editw].display() - self.handle_exiting_widgets(self._widgets__[self.editw].how_exited) + self.handle_exiting_widgets(self._widgets_by_id[self.editw].how_exited) - if self.editw > len(self._widgets__)-1: self.editw = len(self._widgets__)-1 + if self.editw > len(self._widgets_by_id)-1: self.editw = len(self._widgets__)-1 def edit(self): self.pre_edit_loop() @@ -67,12 +68,12 @@ def edit(self): self.editw = len(self._widgets__)-1 if not self.preserve_selected_widget: self.editw = 0 - if not self._widgets__[self.editw].editable: self.find_next_editable() + if not self._widgets_by_id[self.editw].editable: self.find_next_editable() self.display() - while not (self._widgets__[self.editw].editable and not self._widgets__[self.editw].hidden): + while not (self._widgets_by_id[self.editw].editable and not self._widgets_by_id[self.editw].hidden): self.editw += 1 if self.editw > len(self._widgets__)-1: self.editing = False @@ -80,13 +81,13 @@ def edit(self): while self.editing: if not self.ALL_SHOWN: self.on_screen() - self.while_editing(weakref.proxy(self._widgets__[self.editw])) + self.while_editing(weakref.proxy(self._widgets_by_id[self.editw])) if not self.editing: break - self._widgets__[self.editw].edit() - self._widgets__[self.editw].display() + self._widgets_by_id[self.editw].edit() + self._widgets_by_id[self.editw].display() - self.handle_exiting_widgets(self._widgets__[self.editw].how_exited) + self.handle_exiting_widgets(self._widgets_by_id[self.editw].how_exited) if self.editw > len(self._widgets__)-1: self.editw = len(self._widgets__)-1 if self.ok_button.value: diff --git a/npyscreen/npyspmfuncs.py b/npyscreen/npyspmfuncs.py index 61e0300..4149b01 100644 --- a/npyscreen/npyspmfuncs.py +++ b/npyscreen/npyspmfuncs.py @@ -1,10 +1,11 @@ #!/usr/bin/python import curses +import os class ResizeError(Exception): "The screen has been resized" - + def hidecursor(): try: curses.curs_set(0) @@ -22,7 +23,7 @@ def CallSubShell(subshell): expanded by the shell, so make sure it is safe before passing it to this function.""" curses.def_prog_mode() #curses.endwin() # Probably causes a memory leak. - + rtn = os.system("%s" % (subshell)) curses.reset_prog_mode() if rtn != 0: return False diff --git a/npyscreen/utilNotify.py b/npyscreen/utilNotify.py index 6cf8aae..b6166d1 100644 --- a/npyscreen/utilNotify.py +++ b/npyscreen/utilNotify.py @@ -93,26 +93,4 @@ def notify_yes_no(message, title="Message", form_color='STANDOUT', wrap=True, ed F.edit() return F.value -def single_line_input(default_value="Input Text", title="Message", form_color='STANDOUT'): - ''' Convenience function for requesting a single line of user input - - Args: - default_value (str): The default value for the input textfield - title (str): Title for the popup - form_color (str): Color scheme used (as defined by the theme used) - - Returns: - str: - None if the user pressed "Cancel" - - Value of the text input field if the user pressed "OK" - ''' - - F = ConfirmCancelPopup(name=title, color=form_color) - F.preserve_selected_widget = True - tf = F.add(npyscreen.Textfield) - tf.width = tf.width - 1 - tf.value = default_value - F.edit() - if F.value is True: - return tf.value - else: - return None \ No newline at end of file + \ No newline at end of file diff --git a/npyscreen/wgNMenuDisplay.py b/npyscreen/wgNMenuDisplay.py index 85543a9..d89aaf7 100644 --- a/npyscreen/wgNMenuDisplay.py +++ b/npyscreen/wgNMenuDisplay.py @@ -183,7 +183,6 @@ def initialize_menus(self): self._MainMenu = NewMenu.NewMenu self.add_handlers({self.__class__.MENU_KEY: self.root_menu}) - def new_menu(self, name=None, *args, **keywords): if not hasattr(self, '_NMenuList'): self._NMenuList = [] @@ -205,6 +204,13 @@ def root_menu(self, *args): self._NMDisplay.setMenu(_root_menu) self._NMDisplay.edit() self.DISPLAY() + + def use_existing_menu(self, _mnu): + if not hasattr(self, '_NMenuList'): + self._NMenuList = [] + self._NMenuList.append(_mnu) + return weakref.proxy(_mnu) + def popup_menu(self, menu): self._NMDisplay.setMenu(menu) diff --git a/npyscreen/wgboxwidget.py b/npyscreen/wgboxwidget.py index 6be4b11..a03d8c9 100644 --- a/npyscreen/wgboxwidget.py +++ b/npyscreen/wgboxwidget.py @@ -1,7 +1,10 @@ import curses import weakref -from .wgwidget import Widget + from .wgmultiline import MultiLine +from .wgwidget import Widget + + class BoxBasic(Widget): def __init__(self, screen, footer=None, *args, **keywords): super(BoxBasic, self).__init__(screen, *args, **keywords) @@ -10,26 +13,26 @@ def __init__(self, screen, footer=None, *args, **keywords): self.color = keywords['color'] or 'LABEL' else: self.color = 'LABEL' - + def update(self, clear=True): if clear: self.clear() if self.hidden: self.clear() return False HEIGHT = self.height - 1 - WIDTH = self.width - 1 + WIDTH = self.width - 1 # draw box. self.parent.curses_pad.hline(self.rely, self.relx, curses.ACS_HLINE, WIDTH) self.parent.curses_pad.hline(self.rely + HEIGHT, self.relx, curses.ACS_HLINE, WIDTH) self.parent.curses_pad.vline(self.rely, self.relx, curses.ACS_VLINE, self.height) - self.parent.curses_pad.vline(self.rely, self.relx+WIDTH, curses.ACS_VLINE, HEIGHT) - + self.parent.curses_pad.vline(self.rely, self.relx + WIDTH, curses.ACS_VLINE, HEIGHT) + # draw corners self.parent.curses_pad.addch(self.rely, self.relx, curses.ACS_ULCORNER, ) - self.parent.curses_pad.addch(self.rely, self.relx+WIDTH, curses.ACS_URCORNER, ) - self.parent.curses_pad.addch(self.rely+HEIGHT, self.relx, curses.ACS_LLCORNER, ) - self.parent.curses_pad.addch(self.rely+HEIGHT, self.relx+WIDTH, curses.ACS_LRCORNER, ) - + self.parent.curses_pad.addch(self.rely, self.relx + WIDTH, curses.ACS_URCORNER, ) + self.parent.curses_pad.addch(self.rely + HEIGHT, self.relx, curses.ACS_LLCORNER, ) + self.parent.curses_pad.addch(self.rely + HEIGHT, self.relx + WIDTH, curses.ACS_LRCORNER, ) + # draw title if self.name: if isinstance(self.name, bytes): @@ -42,20 +45,21 @@ def update(self, clear=True): name = name.decode(self.encoding, 'replace') name_attributes = curses.A_NORMAL if self.do_colors() and not self.editing: - name_attributes = name_attributes | self.parent.theme_manager.findPair(self, self.color) #| curses.A_BOLD + name_attributes = name_attributes | self.parent.theme_manager.findPair(self, + self.color) # | curses.A_BOLD elif self.editing: name_attributes = name_attributes | self.parent.theme_manager.findPair(self, 'HILIGHT') else: - name_attributes = name_attributes #| curses.A_BOLD - + name_attributes = name_attributes # | curses.A_BOLD + if self.editing: name_attributes = name_attributes | curses.A_BOLD - - self.add_line(self.rely, self.relx+4, name, - self.make_attributes_list(name, name_attributes), - self.width-8) + + self.add_line(self.rely, self.relx + 4, name, + self.make_attributes_list(name, name_attributes), + self.width - 8) # end draw title - + # draw footer if hasattr(self, 'footer') and self.footer: footer_text = self.footer @@ -65,36 +69,36 @@ def update(self, clear=True): footer_text = " " + footer_text + " " if isinstance(footer_text, bytes): footer_text = footer_text.decode(self.encoding, 'replace') - + footer_attributes = self.get_footer_attributes(footer_text) if len(footer_text) <= self.width - 4: placing = self.width - 4 - len(footer_text) else: placing = 4 - - self.add_line(self.rely+HEIGHT, self.relx+placing, footer_text, - footer_attributes, - self.width-placing-2) - - - + + self.add_line(self.rely + HEIGHT, self.relx + placing, footer_text, + footer_attributes, + self.width - placing - 2) + def get_footer_attributes(self, footer_text): footer_attributes = curses.A_NORMAL if self.do_colors() and not self.editing: - footer_attributes = footer_attributes | self.parent.theme_manager.findPair(self, self.color) #| curses.A_BOLD + footer_attributes = footer_attributes | self.parent.theme_manager.findPair(self, + self.color) # | curses.A_BOLD elif self.editing: footer_attributes = footer_attributes | self.parent.theme_manager.findPair(self, 'HILIGHT') else: - footer_attributes = footer_attributes #| curses.A_BOLD - + footer_attributes = footer_attributes # | curses.A_BOLD + if self.editing: footer_attributes = footer_attributes | curses.A_BOLD - #footer_attributes = self.parent.theme_manager.findPair(self, self.color) + # footer_attributes = self.parent.theme_manager.findPair(self, self.color) return self.make_attributes_list(footer_text, footer_attributes) - - + + class BoxTitle(BoxBasic): _contained_widget = MultiLine + def __init__(self, screen, contained_widget_arguments=None, *args, **keywords): super(BoxTitle, self).__init__(screen, *args, **keywords) if contained_widget_arguments: @@ -102,7 +106,7 @@ def __init__(self, screen, contained_widget_arguments=None, *args, **keywords): else: self.make_contained_widget() if 'editable' in keywords: - self.entry_widget.editable=keywords['editable'] + self.entry_widget.editable = keywords['editable'] if 'value' in keywords: self.value = keywords['value'] if 'values' in keywords: @@ -110,25 +114,25 @@ def __init__(self, screen, contained_widget_arguments=None, *args, **keywords): if 'scroll_exit' in keywords: self.entry_widget.scroll_exit = keywords['scroll_exit'] if 'slow_scroll' in keywords: - self.entry_widget.scroll_exit = keywords['slow_scroll'] - - + self.entry_widget.slow_scroll = keywords['slow_scroll'] + def make_contained_widget(self, contained_widget_arguments=None): self._my_widgets = [] if contained_widget_arguments: - self._my_widgets.append(self._contained_widget(self.parent, - rely=self.rely+1, relx = self.relx+2, - max_width=self.width-4, max_height=self.height-2, - **contained_widget_arguments - )) - + self._my_widgets.append(self._contained_widget(self.parent, + rely=self.rely + 1, relx=self.relx + 2, + max_width=self.width - 4, max_height=self.height - 2, + **contained_widget_arguments + )) + else: - self._my_widgets.append(self._contained_widget(self.parent, - rely=self.rely+1, relx = self.relx+2, - max_width=self.width-4, max_height=self.height-2, - )) + self._my_widgets.append(self._contained_widget(self.parent, + rely=self.rely + 1, relx=self.relx + 2, + max_width=self.width - 4, max_height=self.height - 2, + )) self.entry_widget = weakref.proxy(self._my_widgets[0]) - + self.entry_widget.parent_widget = weakref.proxy(self) + def update(self, clear=True): if self.hidden and clear: self.clear() @@ -138,22 +142,20 @@ def update(self, clear=True): super(BoxTitle, self).update(clear=clear) for w in self._my_widgets: w.update(clear=clear) - + def resize(self): super(BoxTitle, self).resize() self.entry_widget.resize() - + def edit(self): - self.editing=True + self.editing = True self.display() self.entry_widget.edit() - #self.value = self.textarea.value + # self.value = self.textarea.value self.how_exited = self.entry_widget.how_exited - self.editing=False + self.editing = False self.display() - - def get_value(self): if hasattr(self, 'entry_widget'): return self.entry_widget.value @@ -161,48 +163,53 @@ def get_value(self): return self.__tmp_value else: return None + def set_value(self, value): if hasattr(self, 'entry_widget'): self.entry_widget.value = value else: # probably trying to set the value before the textarea is initialised self.__tmp_value = value + def del_value(self): del self.entry_widget.value + value = property(get_value, set_value, del_value) - + def get_values(self): - if hasattr(self, 'entry_widget'): + if hasattr(self, 'entry_widget'): return self.entry_widget.values elif hasattr(self, '__tmp_value'): return self.__tmp_values else: return None + def set_values(self, value): - if hasattr(self, 'entry_widget'): + if hasattr(self, 'entry_widget'): self.entry_widget.values = value elif hasattr(self, '__tmp_value'): # probably trying to set the value before the textarea is initialised self.__tmp_values = value + def del_values(self): del self.entry_widget.value + values = property(get_values, set_values, del_values) - + def get_editable(self): - if hasattr(self, 'entry_widget'): + if hasattr(self, 'entry_widget'): return self.entry_widget.editable else: return None + def set_editable(self, value): - if hasattr(self, 'entry_widget'): + if hasattr(self, 'entry_widget'): self.entry_widget.editable = value elif hasattr(self, '__tmp_value'): # probably trying to set the value before the textarea is initialised self.__tmp_values = value + def del_editable(self): del self.entry_widget.editable + editable = property(get_editable, set_editable, del_editable) - - - - diff --git a/npyscreen/wgcheckbox.py b/npyscreen/wgcheckbox.py index 1c9b0bc..db087f9 100755 --- a/npyscreen/wgcheckbox.py +++ b/npyscreen/wgcheckbox.py @@ -1,36 +1,38 @@ #!/usr/bin/python -from .wgtextbox import Textfield -from .wgwidget import Widget -#from .wgmultiline import MultiLine -from . import wgwidget as widget import curses +# from .wgmultiline import MultiLine +from . import wgwidget as widget +from .wgtextbox import Textfield +from .wgwidget import Widget + + class _ToggleControl(Widget): def set_up_handlers(self): super(_ToggleControl, self).set_up_handlers() - + self.handlers.update({ - curses.ascii.SP: self.h_toggle, - ord('x'): self.h_toggle, - curses.ascii.NL: self.h_select_exit, - curses.ascii.CR: self.h_select_exit, - ord('j'): self.h_exit_down, - ord('k'): self.h_exit_up, - ord('h'): self.h_exit_left, - ord('l'): self.h_exit_right, - }) - + curses.ascii.SP: self.h_toggle, + ord('x'): self.h_toggle, + curses.ascii.NL: self.h_select_exit, + curses.ascii.CR: self.h_select_exit, + ord('j'): self.h_exit_down, + ord('k'): self.h_exit_up, + ord('h'): self.h_exit_left, + ord('l'): self.h_exit_right, + }) + def h_toggle(self, ch): - if self.value is False or self.value is None or self.value == 0: + if self.value is False or self.value is None or self.value == 0: self.value = True - else: + else: self.value = False self.whenToggled() - + def whenToggled(self): pass - + def h_select_exit(self, ch): if not self.value: self.h_toggle(ch) @@ -40,16 +42,16 @@ def h_select_exit(self, ch): class CheckboxBare(_ToggleControl): False_box = '[ ]' - True_box = '[X]' - - def __init__(self, screen, value = False, **keywords): + True_box = '[X]' + + def __init__(self, screen, value=False, **keywords): super(CheckboxBare, self).__init__(screen, **keywords) self.value = value - self.hide = False - + self.hide = False + def calculate_area_needed(self): return 1, 4 - + def update(self, clear=True): if clear: self.clear() if self.hidden: @@ -61,52 +63,48 @@ def update(self, clear=True): cb_display = self.__class__.True_box else: cb_display = self.__class__.False_box - - if self.do_colors(): - self.parent.curses_pad.addstr(self.rely, self.relx, cb_display, self.parent.theme_manager.findPair(self, 'CONTROL')) + + if self.do_colors(): + self.parent.curses_pad.addstr(self.rely, self.relx, cb_display, + self.parent.theme_manager.findPair(self, 'CONTROL')) else: self.parent.curses_pad.addstr(self.rely, self.relx, cb_display) - + if self.editing: if self.value: char_under_cur = 'X' else: char_under_cur = ' ' if self.do_colors(): - self.parent.curses_pad.addstr(self.rely, self.relx + 1, char_under_cur, self.parent.theme_manager.findPair(self) | curses.A_STANDOUT) + self.parent.curses_pad.addstr(self.rely, self.relx + 1, char_under_cur, + self.parent.theme_manager.findPair(self) | curses.A_STANDOUT) else: - self.parent.curses_pad.addstr(self.rely, self.relx + 1, curses.A_STANDOUT) - - - - + self.parent.curses_pad.addstr(self.rely, self.relx + 1, curses.A_STANDOUT) class Checkbox(_ToggleControl): False_box = '[ ]' - True_box = '[X]' - - def __init__(self, screen, value = False, **keywords): + True_box = '[X]' + + def __init__(self, screen, value=False, **keywords): self.value = value super(Checkbox, self).__init__(screen, **keywords) - + self._create_label_area(screen) - - + self.show_bold = False self.highlight = False self.important = False - self.hide = False - + self.hide = False + def _create_label_area(self, screen): l_a_width = self.width - 5 - + if l_a_width < 1: - raise ValueError("Width of checkbox + label must be at least 6") - - self.label_area = Textfield(screen, rely=self.rely, relx=self.relx+5, - width=self.width-5, value=self.name) - + raise ValueError("Width of checkbox + label must be at least 6") + + self.label_area = Textfield(screen, rely=self.rely, relx=self.relx + 5, + width=self.width - 5, value=self.name) def update(self, clear=True): if clear: self.clear() @@ -119,9 +117,10 @@ def update(self, clear=True): cb_display = self.__class__.True_box else: cb_display = self.__class__.False_box - - if self.do_colors(): - self.parent.curses_pad.addstr(self.rely, self.relx, cb_display, self.parent.theme_manager.findPair(self, 'CONTROL')) + + if self.do_colors(): + self.parent.curses_pad.addstr(self.rely, self.relx, cb_display, + self.parent.theme_manager.findPair(self, 'CONTROL')) else: self.parent.curses_pad.addstr(self.rely, self.relx, cb_display) @@ -130,52 +129,71 @@ def update(self, clear=True): def _update_label_area(self, clear=True): self.label_area.value = self.name self._update_label_row_attributes(self.label_area, clear=clear) - + def _update_label_row_attributes(self, row, clear=True): + emitted = False if self.editing: - row.highlight = True + if not row.highlight and not emitted: + row.highlight = True + self.on_focusin() + else: + row.highlight = True else: - row.highlight = False - - if self.show_bold: + if row.highlight and not emitted: + row.highlight = False + self.on_focusout() + else: + row.highlight = False + + if self.show_bold: row.show_bold = True - else: + else: row.show_bold = False - + if self.important: row.important = True else: row.important = False - if self.highlight: - row.highlight = True - else: - row.highlight = False + if self.highlight: + if not row.highlight and not emitted: + row.highlight = True + self.on_focusin() + else: + row.highlight = True + else: + if row.highlight and not emitted: + row.highlight = False + self.on_focusout() + else: + row.highlight = False row.update(clear=clear) - + def calculate_area_needed(self): - return 1,0 + return 1, 0 + class CheckBox(Checkbox): pass - + class RoundCheckBox(Checkbox): False_box = '( )' - True_box = '(X)' - + True_box = '(X)' + + class CheckBoxMultiline(Checkbox): - def _create_label_area(self, screen): + def _create_label_area(self, screen): self.label_area = [] for y in range(self.height): self.label_area.append( - Textfield(screen, rely=self.rely+y, - relx=self.relx+5, - width=self.width-5, - value=None) + Textfield(screen, rely=self.rely + y, + relx=self.relx + 5, + width=self.width - 5, + value=None) ) - + def _update_label_area(self, clear=True): for x in range(len(self.label_area)): if x >= len(self.name): @@ -185,12 +203,11 @@ def _update_label_area(self, clear=True): self.label_area[x].value = self.name[x] self.label_area[x].hidden = False self._update_label_row_attributes(self.label_area[x], clear=clear) - + def calculate_area_needed(self): - return 0,0 - + return 0, 0 + + class RoundCheckBoxMultiline(CheckBoxMultiline): False_box = '( )' - True_box = '(X)' - - + True_box = '(X)' diff --git a/npyscreen/wgcombobox.py b/npyscreen/wgcombobox.py index 80f28c1..7420628 100644 --- a/npyscreen/wgcombobox.py +++ b/npyscreen/wgcombobox.py @@ -4,18 +4,27 @@ from . import wgtextbox as textbox from . import wgmultiline as multiline from . import fmForm as Form -from . import fmPopup as Popup -from . import wgtitlefield as titlefield +from . import fmPopup as Popup +from . import wgmultiline as multiline +from . import wgtextbox as textbox +from . import wgtitlefield as titlefield + class ComboBox(textbox.Textfield): ENSURE_STRING_VALUE = False - def __init__(self, screen, value = None, values=None,**keywords): + + def __init__(self, screen, value=None, values=None, popup_columns=None, + popup_lines=None, popup_atx=None, popup_aty=None, **keywords): self.values = values or [] self.value = value or None + self.popup_columns = popup_columns or None + self.popup_lines = popup_lines or None + self.popup_atx = popup_atx or None + self.popup_aty = popup_aty or None if value == 0: self.value = 0 super(ComboBox, self).__init__(screen, **keywords) - + def display_value(self, vl): """Overload this function to change how values are displayed. Should accept one argument (the object to be represented), and return a string.""" @@ -24,7 +33,7 @@ def display_value(self, vl): def update(self, **keywords): keywords.update({'cursor': False}) super(ComboBox, self).update(**keywords) - + def _print(self): if self.value == None or self.value == '': printme = '-unset-' @@ -34,36 +43,46 @@ def _print(self): except IndexError: printme = '-error-' if self.do_colors(): - self.parent.curses_pad.addnstr(self.rely, self.relx, printme, self.width, self.parent.theme_manager.findPair(self)) + self.parent.curses_pad.addnstr(self.rely, self.relx, printme, self.width, + self.parent.theme_manager.findPair(self)) else: self.parent.curses_pad.addnstr(self.rely, self.relx, printme, self.width) def edit(self): - #We'll just use the widget one + # We'll just use the widget one super(textbox.Textfield, self).edit() - + def set_up_handlers(self): super(textbox.Textfield, self).set_up_handlers() - self.handlers.update({curses.ascii.SP: self.h_change_value, - #curses.ascii.TAB: self.h_change_value, - curses.ascii.NL: self.h_change_value, - curses.ascii.CR: self.h_change_value, - ord('x'): self.h_change_value, - ord('k'): self.h_exit_up, - ord('j'): self.h_exit_down, - ord('h'): self.h_exit_left, - ord('l'): self.h_exit_right, - }) - + self.handlers.update({curses.ascii.SP: self.h_change_value, + # curses.ascii.TAB: self.h_change_value, + curses.ascii.NL: self.h_change_value, + curses.ascii.CR: self.h_change_value, + ord('x'): self.h_change_value, + ord('k'): self.h_exit_up, + ord('j'): self.h_exit_down, + ord('h'): self.h_exit_left, + ord('l'): self.h_exit_right, + }) + def h_change_value(self, input): "Pop up a window in which to select the values for the field" - F = Popup.Popup(name = self.name) - l = F.add(multiline.MultiLine, - values = [self.display_value(x) for x in self.values], - return_exit=True, select_exit=True, - value=self.value) + kwargs = {"name": self.name} + if self.popup_atx: + kwargs["show_atx"] = self.popup_atx + if self.popup_aty: + kwargs["show_aty"] = self.popup_aty + if self.popup_columns: + kwargs["columns"] = self.popup_columns + if self.popup_lines: + kwargs["lines"] = self.popup_lines + F = Popup.Popup(name=self.name) + l = F.add(multiline.MultiLine, + values=[self.display_value(x) for x in self.values], + return_exit=True, select_exit=True, + value=self.value) F.display() l.edit() self.value = l.value @@ -71,7 +90,7 @@ def h_change_value(self, input): class TitleCombo(titlefield.TitleText): _entry_type = ComboBox - + def get_values(self): try: return self.entry_widget.values @@ -80,14 +99,14 @@ def get_values(self): return self.__tmp_values except: return None - + def set_values(self, values): try: self.entry_widget.values = values except: # probably trying to set the value before the textarea is initialised self.__tmp_values = values - + def del_values(self): del self.entry_widget.values diff --git a/npyscreen/wgfilenamecombo.py b/npyscreen/wgfilenamecombo.py index 7e6a976..6671382 100644 --- a/npyscreen/wgfilenamecombo.py +++ b/npyscreen/wgfilenamecombo.py @@ -18,7 +18,7 @@ def __init__(self, screen, def _print(self): if self.value == None: - printme = '-unset-' + printme = '- Unset -' else: try: printme = self.display_value(self.value) @@ -45,4 +45,4 @@ def h_change_value(self, *args, **keywords): class TitleFilenameCombo(wgcombobox.TitleCombo): - _entry_type = FilenameCombo + _entry_type = FilenameCombo \ No newline at end of file diff --git a/npyscreen/wggrid.py b/npyscreen/wggrid.py index 4018b5b..b672a07 100755 --- a/npyscreen/wggrid.py +++ b/npyscreen/wggrid.py @@ -15,6 +15,7 @@ def __init__(self, screen, columns = None, values = None, always_show_cursor = False, select_whole_line = False, + on_select_callback = None, **keywords): super(SimpleGrid, self).__init__(screen, **keywords) self.col_margin = col_margin @@ -35,6 +36,8 @@ def __init__(self, screen, columns = None, self.values = None else: self.values = values + + self.on_select_callback = on_select_callback def set_grid_values_from_flat_list(self, new_values, max_cols=None, reset_cursor=True): if not max_cols: @@ -186,6 +189,7 @@ def set_up_handlers(self): ord('g'): self.h_show_beginning, ord('G'): self.h_show_end, curses.ascii.TAB: self.h_exit, + curses.KEY_BTAB: self.h_exit_up, '^P': self.h_exit_up, '^N': self.h_exit_down, #curses.ascii.NL: self.h_exit, @@ -221,10 +225,12 @@ def h_show_beginning(self, inpt): self.begin_col_display_at = 0 self.begin_row_display_at = 0 self.edit_cell = [0, 0] + self.on_select(inpt) def h_show_end(self, inpt): self.edit_cell = [len(self.values) - 1 , len(self.values[-1]) - 1] self.ensure_cursor_on_display_down_right() + self.on_select(inpt) def h_move_cell_left(self, inpt): if self.edit_cell[1] > 0: @@ -232,6 +238,7 @@ def h_move_cell_left(self, inpt): if self.edit_cell[1] < self.begin_col_display_at: self.h_scroll_left(inpt) + self.on_select(inpt) def h_move_cell_right(self, inpt): if self.edit_cell[1] <= len(self.values[self.edit_cell[0]]) -2: # Only allow move to end of current line @@ -239,6 +246,7 @@ def h_move_cell_right(self, inpt): if self.edit_cell[1] > self.begin_col_display_at + self.columns - 1: self.h_scroll_right(inpt) + self.on_select(inpt) def h_move_line_down(self, inpt): if self.edit_cell[0] <= (len(self.values) -2) \ @@ -246,6 +254,7 @@ def h_move_line_down(self, inpt): self.edit_cell[0] += 1 if self.begin_row_display_at + len(self._my_widgets) - 1 < self.edit_cell[0]: self.h_scroll_display_down(inpt) + self.on_select(inpt) def h_move_line_up(self, inpt): if self.edit_cell[0] > 0: @@ -253,10 +262,12 @@ def h_move_line_up(self, inpt): if self.edit_cell[0] < self.begin_row_display_at: self.h_scroll_display_up(inpt) + self.on_select(inpt) def h_scroll_right(self, inpt): if self.begin_col_display_at + self.columns < len(self.values[self.edit_cell[0]]): self.begin_col_display_at += self.columns + self.on_select(inpt) def h_scroll_left(self, inpt): if self.begin_col_display_at > 0: @@ -264,22 +275,26 @@ def h_scroll_left(self, inpt): if self.begin_col_display_at < 0: self.begin_col_display_at = 0 + self.on_select(inpt) def h_scroll_display_down(self, inpt): if self.begin_row_display_at + len(self._my_widgets) < len(self.values): self.begin_row_display_at += len(self._my_widgets) + self.on_select(inpt) def h_scroll_display_up(self, inpt): if self.begin_row_display_at > 0: self.begin_row_display_at -= len(self._my_widgets) if self.begin_row_display_at < 0: self.begin_row_display_at = 0 + self.on_select(inpt) def h_move_page_up(self, inpt): self.edit_cell[0] -= len(self._my_widgets) if self.edit_cell[0] < 0: self.edit_cell[0] = 0 self.ensure_cursor_on_display_up() + self.on_select(inpt) def h_move_page_down(self, inpt): self.edit_cell[0] += len(self._my_widgets) @@ -287,11 +302,22 @@ def h_move_page_down(self, inpt): self.edit_cell[0] = len(self.values) -1 self.ensure_cursor_on_display_down_right() - + self.on_select(inpt) + + def on_select(self, input): + if self.on_select_callback: + self.on_select_callback() + def h_exit(self, ch): self.editing = False self.how_exited = True + def selected_row(self): + try: + return self.values[self.edit_cell[0]] + except KeyError: + pass + \ No newline at end of file diff --git a/npyscreen/wgmultiline.py b/npyscreen/wgmultiline.py old mode 100644 new mode 100755 index 4c20ec3..b1e08aa --- a/npyscreen/wgmultiline.py +++ b/npyscreen/wgmultiline.py @@ -1,93 +1,93 @@ #!/usr/bin/python +import collections import copy -from . import wgwidget as widget -from . import wgtextbox as textbox -import textwrap import curses -from . import wgtitlefield as titlefield -from . import fmPopup as Popup +import textwrap import weakref -import collections -import copy -MORE_LABEL = "-more-" # string to tell user there are more options +from . import fmPopup as Popup +from . import wgtextbox as textbox +from . import wgtitlefield as titlefield +from . import wgwidget as widget + +MORE_LABEL = "-more-" # string to tell user there are more options + class FilterPopupHelper(Popup.Popup): def create(self): super(FilterPopupHelper, self).create() self.filterbox = self.add(titlefield.TitleText, name='Find:', ) self.nextrely += 1 - self.statusline = self.add(textbox.Textfield, color = 'LABEL', editable = False) - + self.statusline = self.add(textbox.Textfield, color='LABEL', editable=False) + def updatestatusline(self): - self.owner_widget._filter = self.filterbox.value + self.owner_widget._filter = self.filterbox.value filtered_lines = self.owner_widget.get_filtered_indexes() len_f = len(filtered_lines) if self.filterbox.value == None or self.filterbox.value == '': self.statusline.value = '' - elif len_f == 0: + elif len_f == 0: self.statusline.value = '(No Matches)' elif len_f == 1: self.statusline.value = '(1 Match)' else: self.statusline.value = '(%s Matches)' % len_f - + def adjust_widgets(self): self.updatestatusline() self.statusline.display() - - - + class MultiLine(widget.Widget): _safe_to_display_cache = True """Display a list of items to the user. By overloading the display_value method, this widget can be made to display different kinds of objects. Given the standard definition, the same effect can be achieved by altering the __str__() method of displayed objects""" - _MINIMUM_HEIGHT = 2 # Raise an error if not given this. + _MINIMUM_HEIGHT = 2 # Raise an error if not given this. _contained_widgets = textbox.Textfield _contained_widget_height = 1 - def __init__(self, screen, values = None, value = None, - slow_scroll=False, scroll_exit=False, - return_exit=False, select_exit=False, - exit_left = False, - exit_right = False, - widgets_inherit_color = False, - always_show_cursor = False, - allow_filtering = True, - **keywords): - - self.never_cache = False - self.exit_left = exit_left - self.exit_right = exit_right + + def __init__(self, screen, values=None, value=None, + slow_scroll=False, scroll_exit=False, + return_exit=False, select_exit=False, + exit_left=False, + exit_right=False, + widgets_inherit_color=False, + always_show_cursor=False, + allow_filtering=True, + **keywords): + + self.never_cache = False + self.exit_left = exit_left + self.exit_right = exit_right self.allow_filtering = allow_filtering self.widgets_inherit_color = widgets_inherit_color super(MultiLine, self).__init__(screen, **keywords) if self.height < self.__class__._MINIMUM_HEIGHT: - raise widget.NotEnoughSpaceForWidget("Height of %s allocated. Not enough space allowed for %s" % (self.height, str(self))) + raise widget.NotEnoughSpaceForWidget( + "Height of %s allocated. Not enough space allowed for %s" % (self.height, str(self))) self.make_contained_widgets() self.value = value - + # does pushing return select and then leave the widget? self.return_exit = return_exit - + # does any selection leave the widget? self.select_exit = select_exit - + # Show cursor even when not editing? self.always_show_cursor = always_show_cursor - - - self.slow_scroll = slow_scroll - self.scroll_exit = scroll_exit - + + self.slow_scroll = slow_scroll + self.scroll_exit = scroll_exit + self.start_display_at = 0 self.cursor_line = 0 self.values = values or [] self._filter = None - - #These are just to do some optimisation tricks + + # These are just to do some optimisation tricks self._last_start_display_at = None self._last_cursor_line = None self._last_values = copy.copy(values) @@ -95,27 +95,26 @@ def __init__(self, screen, values = None, value = None, self._last_filter = None self._filtered_values_cache = [] - #override - it looks nicer. - if self.scroll_exit: self.slow_scroll=True - + # override - it looks nicer. + if self.scroll_exit: self.slow_scroll = True + def resize(self): super(MultiLine, self).resize() self.make_contained_widgets() self.reset_display_cache() self.display() - + def make_contained_widgets(self, ): self._my_widgets = [] for h in range(self.height // self.__class__._contained_widget_height): self._my_widgets.append( - self._contained_widgets(self.parent, - rely=(h*self._contained_widget_height)+self.rely, - relx = self.relx, - max_width=self.width, - max_height=self.__class__._contained_widget_height - ) - ) - + self._contained_widgets(self.parent, + rely=(h * self._contained_widget_height) + self.rely, + relx=self.relx, + max_width=self.width, + max_height=self.__class__._contained_widget_height + ) + ) def display_value(self, vl): """Overload this function to change how values are displayed. @@ -125,79 +124,78 @@ def display_value(self, vl): return self.safe_string(str(vl)) except ReferenceError: return "**REFERENCE ERROR**" - + try: return "Error displaying " + self.safe_string(repr(vl)) except: return "**** Error ****" def calculate_area_needed(self): - return 0,0 - - + return 0, 0 + def reset_cursor(self): self.start_display_at = 0 - self.cursor_line = 0 - + self.cursor_line = 0 + def reset_display_cache(self): self._last_values = False - self._last_value = False - + self._last_value = False + def update(self, clear=True): if self.hidden and clear: self.clear() return False elif self.hidden: return False - + if self.values == None: self.values = [] - + # clear = None is a good value for this widget display_length = len(self._my_widgets) - #self._remake_filter_cache() + # self._remake_filter_cache() self._filtered_values_cache = self.get_filtered_indexes() if self.editing or self.always_show_cursor: if self.cursor_line < 0: self.cursor_line = 0 - if self.cursor_line > len(self.values)-1: self.cursor_line = len(self.values)-1 - + if self.cursor_line > len(self.values) - 1: self.cursor_line = len(self.values) - 1 + if self.slow_scroll: - if self.cursor_line > self.start_display_at+display_length-1: - self.start_display_at = self.cursor_line - (display_length-1) + if self.cursor_line > self.start_display_at + display_length - 1: + self.start_display_at = self.cursor_line - (display_length - 1) if self.cursor_line < self.start_display_at: self.start_display_at = self.cursor_line - + else: - if self.cursor_line > self.start_display_at+(display_length-2): + if self.cursor_line > self.start_display_at + (display_length - 2): self.start_display_at = self.cursor_line if self.cursor_line < self.start_display_at: - self.start_display_at = self.cursor_line - (display_length-2) - if self.start_display_at < 0: self.start_display_at=0 - + self.start_display_at = self.cursor_line - (display_length - 2) + if self.start_display_at < 0: self.start_display_at = 0 + # What this next bit does is to not bother updating the screen if nothing has changed. no_change = False - try: + try: if (self._safe_to_display_cache and \ self._last_value is self.value) and \ - (self.values == self._last_values) and \ - (self.start_display_at == self._last_start_display_at) and \ - (clear != True) and \ - (self._last_cursor_line == self.cursor_line) and \ - (self._last_filter == self._filter) and \ - self.editing: + (self.values == self._last_values) and \ + (self.start_display_at == self._last_start_display_at) and \ + (clear != True) and \ + (self._last_cursor_line == self.cursor_line) and \ + (self._last_filter == self._filter) and \ + self.editing: no_change = True - + else: no_change = False except: - no_change = False + no_change = False if clear: no_change = False if not no_change or clear or self.never_cache: - if clear is True: + if clear is True: self.clear() if (self._last_start_display_at != self.start_display_at) \ @@ -207,7 +205,7 @@ def update(self, clear=True): pass self._last_start_display_at = self.start_display_at - + self._before_print_lines() indexer = 0 + self.start_display_at for line in self._my_widgets[:-1]: @@ -215,57 +213,59 @@ def update(self, clear=True): line.task = "PRINTLINE" line.update(clear=True) indexer += 1 - + # Now do the final line line = self._my_widgets[-1] - - if (len(self.values) <= indexer+1):# or (len(self._my_widgets)*self._contained_widget_height) self.cursor_line: @@ -375,8 +371,8 @@ def move_next_filtered(self, include_this_line=False, *args): self.update() break try: - if self.cursor_line-self.start_display_at > len(self._my_widgets) or \ - self._my_widgets[self.cursor_line-self.start_display_at].task == MORE_LABEL: + if self.cursor_line - self.start_display_at > len(self._my_widgets) or \ + self._my_widgets[self.cursor_line - self.start_display_at].task == MORE_LABEL: if self.slow_scroll: self.start_display_at += 1 else: @@ -384,7 +380,7 @@ def move_next_filtered(self, include_this_line=False, *args): except IndexError: self.cursor_line = 0 self.start_display_at = 0 - + def move_previous_filtered(self, *args): if self._filter == None: return False @@ -402,70 +398,71 @@ def get_selected_objects(self): return None else: return [self.values[x] for x in self.value] - + def handle_mouse_event(self, mouse_event): # unfinished - #mouse_id, x, y, z, bstate = mouse_event - #self.cursor_line = y - self.rely - self.parent.show_aty + self.start_display_at - + # mouse_id, x, y, z, bstate = mouse_event + # self.cursor_line = y - self.rely - self.parent.show_aty + self.start_display_at + mouse_id, rel_x, rel_y, z, bstate = self.interpret_mouse_event(mouse_event) self.cursor_line = rel_y // self._contained_widget_height + self.start_display_at - - - ##if self.cursor_line > len(self.values): - ## self.cursor_line = len(self.values) + + # Toggle here if rel_x == 1? (ie: they clicked IN the checkbox) + if rel_x == 1 and hasattr(self,"h_select_toggle"): # not sure if this should be done elsewhere + self.h_select_toggle(" ") + self.when_check_value_changed() + self.display() def set_up_handlers(self): super(MultiLine, self).set_up_handlers() - self.handlers.update ( { - curses.KEY_UP: self.h_cursor_line_up, - ord('k'): self.h_cursor_line_up, - curses.KEY_LEFT: self.h_cursor_line_up, - curses.KEY_DOWN: self.h_cursor_line_down, - ord('j'): self.h_cursor_line_down, - curses.KEY_RIGHT: self.h_cursor_line_down, - curses.KEY_NPAGE: self.h_cursor_page_down, - curses.KEY_PPAGE: self.h_cursor_page_up, - curses.ascii.TAB: self.h_exit_down, - curses.ascii.NL: self.h_select_exit, - curses.KEY_HOME: self.h_cursor_beginning, - curses.KEY_END: self.h_cursor_end, - ord('g'): self.h_cursor_beginning, - ord('G'): self.h_cursor_end, - ord('x'): self.h_select, - # "^L": self.h_set_filtered_to_selected, - curses.ascii.SP: self.h_select, - curses.ascii.ESC: self.h_exit_escape, - curses.ascii.CR: self.h_select_exit, - } ) - + self.handlers.update({ + curses.KEY_UP: self.h_cursor_line_up, + ord('k'): self.h_cursor_line_up, + curses.KEY_LEFT: self.h_cursor_line_up, + curses.KEY_DOWN: self.h_cursor_line_down, + ord('j'): self.h_cursor_line_down, + curses.KEY_RIGHT: self.h_cursor_line_down, + curses.KEY_NPAGE: self.h_cursor_page_down, + curses.KEY_PPAGE: self.h_cursor_page_up, + curses.ascii.TAB: self.h_exit_down, + curses.ascii.NL: self.h_select_exit, + curses.KEY_HOME: self.h_cursor_beginning, + curses.KEY_END: self.h_cursor_end, + ord('g'): self.h_cursor_beginning, + ord('G'): self.h_cursor_end, + ord('x'): self.h_select, + # "^L": self.h_set_filtered_to_selected, + curses.ascii.SP: self.h_select, + curses.ascii.ESC: self.h_exit_escape, + curses.ascii.CR: self.h_select_exit, + }) + if self.allow_filtering: - self.handlers.update ( { - ord('l'): self.h_set_filter, - ord('L'): self.h_clear_filter, - ord('n'): self.move_next_filtered, - ord('N'): self.move_previous_filtered, - ord('p'): self.move_previous_filtered, + self.handlers.update({ + ord('l'): self.h_set_filter, + ord('L'): self.h_clear_filter, + ord('n'): self.move_next_filtered, + ord('N'): self.move_previous_filtered, + ord('p'): self.move_previous_filtered, # "^L": self.h_set_filtered_to_selected, - - } ) - - + + }) + if self.exit_left: self.handlers.update({ - curses.KEY_LEFT: self.h_exit_left + curses.KEY_LEFT: self.h_exit_left }) - + if self.exit_right: self.handlers.update({ - curses.KEY_RIGHT: self.h_exit_right + curses.KEY_RIGHT: self.h_exit_right }) self.complex_handlers = [ - #(self.t_input_isprint, self.h_find_char) - ] - + # (self.t_input_isprint, self.h_find_char) + ] + def h_find_char(self, input): # The following ought to work, but there is a curses keyname bug # searchingfor = curses.keyname(input).upper() @@ -480,9 +477,11 @@ def h_find_char(self, input): break def t_input_isprint(self, input): - if curses.ascii.isprint(input): return True - else: return False - + if curses.ascii.isprint(input): + return True + else: + return False + def h_set_filter(self, ch): if not self.allow_filtering: return None @@ -492,65 +491,65 @@ def h_set_filter(self, ch): P.filterbox.edit() self._remake_filter_cache() self.jump_to_first_filtered() - + def h_clear_filter(self, ch): self.clear_filter() self.update() - + def h_cursor_beginning(self, ch): self.cursor_line = 0 - + def h_cursor_end(self, ch): - self.cursor_line= len(self.values)-1 + self.cursor_line = len(self.values) - 1 if self.cursor_line < 0: self.cursor_line = 0 def h_cursor_page_down(self, ch): - self.cursor_line += (len(self._my_widgets)-1) # -1 because of the -more- - if self.cursor_line >= len(self.values)-1: - self.cursor_line = len(self.values) -1 - if not (self.start_display_at + len(self._my_widgets) -1 ) > len(self.values): - self.start_display_at += (len(self._my_widgets)-1) - if self.start_display_at > len(self.values) - (len(self._my_widgets)-1): - self.start_display_at = len(self.values) - (len(self._my_widgets)-1) - + self.cursor_line += (len(self._my_widgets) - 1) # -1 because of the -more- + if self.cursor_line >= len(self.values) - 1: + self.cursor_line = len(self.values) - 1 + if not (self.start_display_at + len(self._my_widgets) - 1) > len(self.values): + self.start_display_at += (len(self._my_widgets) - 1) + if self.start_display_at > len(self.values) - (len(self._my_widgets) - 1): + self.start_display_at = len(self.values) - (len(self._my_widgets) - 1) + def h_cursor_page_up(self, ch): - self.cursor_line -= (len(self._my_widgets)-1) + self.cursor_line -= (len(self._my_widgets) - 1) if self.cursor_line < 0: self.cursor_line = 0 - self.start_display_at -= (len(self._my_widgets)-1) + self.start_display_at -= (len(self._my_widgets) - 1) if self.start_display_at < 0: self.start_display_at = 0 - + def h_cursor_line_up(self, ch): self.cursor_line -= 1 - if self.cursor_line < 0: + if self.cursor_line < 0: if self.scroll_exit: self.cursor_line = 0 self.h_exit_up(ch) - else: + else: self.cursor_line = 0 def h_cursor_line_down(self, ch): self.cursor_line += 1 if self.cursor_line >= len(self.values): - if self.scroll_exit: - self.cursor_line = len(self.values)-1 + if self.scroll_exit: + self.cursor_line = len(self.values) - 1 self.h_exit_down(ch) return True - else: - self.cursor_line -=1 + else: + self.cursor_line -= 1 return True - if self._my_widgets[self.cursor_line-self.start_display_at].task == MORE_LABEL: + if self._my_widgets[self.cursor_line - self.start_display_at].task == MORE_LABEL: if self.slow_scroll: self.start_display_at += 1 else: self.start_display_at = self.cursor_line - + def h_exit(self, ch): self.editing = False self.how_exited = True - + def h_set_filtered_to_selected(self, ch): # This is broken on multiline if len(self._filtered_values_cache) < 2: @@ -558,7 +557,7 @@ def h_set_filtered_to_selected(self, ch): else: # There is an error - trying to select too many things. curses.beep() - + def h_select(self, ch): self.value = self.cursor_line if self.select_exit: @@ -569,33 +568,35 @@ def h_select_exit(self, ch): self.h_select(ch) if self.return_exit or self.select_exit: self.editing = False - self.how_exited=True - + self.how_exited = True def edit(self): self.editing = True self.how_exited = None - #if self.value: self.cursor_line = self.value + # if self.value: self.cursor_line = self.value self.display() while self.editing: self.get_and_use_key_press() self.update(clear=None) -## self.clear() -## self.update(clear=False) + ## self.clear() + ## self.update(clear=False) self.parent.refresh() + + ## curses.napms(10) ## curses.flushinp() class MultiLineAction(MultiLine): RAISE_ERROR_IF_EMPTY_ACTION = False + def __init__(self, *args, **keywords): - self.allow_multi_action = False - super(MultiLineAction, self).__init__(*args, **keywords) - + self.allow_multi_action = False + super(MultiLineAction, self).__init__(*args, **keywords) + def actionHighlighted(self, act_on_this, key_press): "Override this Method" pass - + def h_act_on_highlighted(self, ch): try: return self.actionHighlighted(self.values[self.cursor_line], ch) @@ -604,34 +605,33 @@ def h_act_on_highlighted(self, ch): raise else: pass - + def set_up_handlers(self): super(MultiLineAction, self).set_up_handlers() - self.handlers.update ( { - curses.ascii.NL: self.h_act_on_highlighted, - curses.ascii.CR: self.h_act_on_highlighted, - ord('x'): self.h_act_on_highlighted, - curses.ascii.SP: self.h_act_on_highlighted, - } ) - - + self.handlers.update({ + curses.ascii.NL: self.h_act_on_highlighted, + curses.ascii.CR: self.h_act_on_highlighted, + ord('x'): self.h_act_on_highlighted, + curses.ascii.SP: self.h_act_on_highlighted, + }) + + class MultiLineActionWithShortcuts(MultiLineAction): shortcut_attribute_name = 'shortcut' + def set_up_handlers(self): super(MultiLineActionWithShortcuts, self).set_up_handlers() - self.add_complex_handlers( ((self.h_find_shortcut_action, self.h_execute_shortcut_action),) ) - - + self.add_complex_handlers(((self.h_find_shortcut_action, self.h_execute_shortcut_action),)) + def h_find_shortcut_action(self, _input): _input_decoded = curses.ascii.unctrl(_input) for r in range(len(self.values)): if hasattr(self.values[r], self.shortcut_attribute_name): - from . import utilNotify if getattr(self.values[r], self.shortcut_attribute_name) == _input \ - or getattr(self.values[r], self.shortcut_attribute_name) == _input_decoded: + or getattr(self.values[r], self.shortcut_attribute_name) == _input_decoded: return r return False - + def h_execute_shortcut_action(self, _input): l = self.h_find_shortcut_action(_input) if l is False: @@ -639,21 +639,19 @@ def h_execute_shortcut_action(self, _input): self.cursor_line = l self.display() self.h_act_on_highlighted(_input) - - - + class Pager(MultiLine): - def __init__(self, screen, autowrap=False, center=False, **keywords): + def __init__(self, screen, autowrap=False, center=False, **keywords): super(Pager, self).__init__(screen, **keywords) self.autowrap = autowrap self.center = center self._values_cache_for_wrapping = [] - + def reset_display_cache(self): super(Pager, self).reset_display_cache() self._values_cache_for_wrapping = False - + def _wrap_message_lines(self, message_lines, line_length): lines = [] for line in message_lines: @@ -666,15 +664,15 @@ def _wrap_message_lines(self, message_lines, line_length): else: lines.append('') return lines - + def resize(self): super(Pager, self).resize() - #self.values = [str(self.width), str(self._my_widgets[0].width),] + # self.values = [str(self.width), str(self._my_widgets[0].width),] if self.autowrap: self.setValuesWrap(list(self.values)) if self.center: self.centerValues() - + def setValuesWrap(self, lines): if self.autowrap and (lines == self._values_cache_for_wrapping): return False @@ -682,57 +680,55 @@ def setValuesWrap(self, lines): lines = lines.split('\n') except AttributeError: pass - self.values = self._wrap_message_lines(lines, self.width-1) + self.values = self._wrap_message_lines(lines, self.width - 1) self._values_cache_for_wrapping = self.values - + def centerValues(self): - self.values = [ l.strip().center(self.width-1) for l in self.values ] - + self.values = [l.strip().center(self.width - 1) for l in self.values] + def update(self, clear=True): - #we look this up a lot. Let's have it here. + # we look this up a lot. Let's have it here. if self.autowrap: self.setValuesWrap(list(self.values)) - + if self.center: self.centerValues() - + display_length = len(self._my_widgets) values_len = len(self.values) - - if self.start_display_at > values_len - display_length: + + if self.start_display_at > values_len - display_length: self.start_display_at = values_len - display_length if self.start_display_at < 0: self.start_display_at = 0 - + indexer = 0 + self.start_display_at - for line in self._my_widgets[:-1]: + for line in self._my_widgets[:-1]: self._print_line(line, indexer) indexer += 1 - + # Now do the final line line = self._my_widgets[-1] - - if values_len <= indexer+1: + + if values_len <= indexer + 1: self._print_line(line, indexer) else: line.value = MORE_LABEL line.highlight = False line.show_bold = False - - for w in self._my_widgets: + + for w in self._my_widgets: # call update to avoid needless refreshes w.update(clear=True) # There is a bug somewhere that affects the first line. This cures it. # Without this line, the first line inherits the color of the form when not editing. Not clear why. self._my_widgets[0].update() - - - + def edit(self): # Make sure a value never gets set. value = self.value super(Pager, self).edit() self.value = value - + def h_scroll_line_up(self, input): self.start_display_at -= 1 if self.scroll_exit and self.start_display_at < 0: @@ -741,7 +737,7 @@ def h_scroll_line_up(self, input): def h_scroll_line_down(self, input): self.start_display_at += 1 - if self.scroll_exit and self.start_display_at >= len(self.values)-self.start_display_at+1: + if self.scroll_exit and self.start_display_at >= len(self.values) - self.start_display_at + 1: self.editing = False self.how_exited = widget.EXITED_DOWN @@ -752,41 +748,42 @@ def h_scroll_page_up(self, input): self.start_display_at -= len(self._my_widgets) def h_show_beginning(self, input): - self.start_display_at = 0 - + self.start_display_at = 0 + def h_show_end(self, input): self.start_display_at = len(self.values) - len(self._my_widgets) - + def h_select_exit(self, input): self.exit(self, input) - + def set_up_handlers(self): super(Pager, self).set_up_handlers() self.handlers = { - curses.KEY_UP: self.h_scroll_line_up, - curses.KEY_LEFT: self.h_scroll_line_up, - curses.KEY_DOWN: self.h_scroll_line_down, - curses.KEY_RIGHT: self.h_scroll_line_down, - curses.KEY_NPAGE: self.h_scroll_page_down, - curses.KEY_PPAGE: self.h_scroll_page_up, - curses.KEY_HOME: self.h_show_beginning, - curses.KEY_END: self.h_show_end, - curses.KEY_BTAB: self.h_exit_up, - curses.ascii.NL: self.h_exit, - curses.ascii.CR: self.h_exit, - curses.ascii.SP: self.h_scroll_page_down, - curses.ascii.TAB: self.h_exit, - ord('j'): self.h_scroll_line_down, - ord('k'): self.h_scroll_line_up, - ord('x'): self.h_exit, - ord('q'): self.h_exit, - ord('g'): self.h_show_beginning, - ord('G'): self.h_show_end, - curses.ascii.ESC: self.h_exit_escape, - } + curses.KEY_UP: self.h_scroll_line_up, + curses.KEY_LEFT: self.h_scroll_line_up, + curses.KEY_DOWN: self.h_scroll_line_down, + curses.KEY_RIGHT: self.h_scroll_line_down, + curses.KEY_NPAGE: self.h_scroll_page_down, + curses.KEY_PPAGE: self.h_scroll_page_up, + curses.KEY_HOME: self.h_show_beginning, + curses.KEY_END: self.h_show_end, + curses.KEY_BTAB: self.h_exit_up, + curses.ascii.NL: self.h_exit, + curses.ascii.CR: self.h_exit, + curses.ascii.SP: self.h_scroll_page_down, + curses.ascii.TAB: self.h_exit, + ord('j'): self.h_scroll_line_down, + ord('k'): self.h_scroll_line_up, + ord('x'): self.h_exit, + ord('q'): self.h_exit, + ord('g'): self.h_show_beginning, + ord('G'): self.h_show_end, + curses.ascii.ESC: self.h_exit_escape, + } self.complex_handlers = [ - ] + ] + class TitleMultiLine(titlefield.TitleText): _entry_type = MultiLine @@ -795,38 +792,42 @@ def get_selected_objects(self): return self.entry_widget.get_selected_objects() def get_values(self): - if hasattr(self, 'entry_widget'): + if hasattr(self, 'entry_widget'): return self.entry_widget.values elif hasattr(self, '__tmp_value'): return self.__tmp_values else: return None + def set_values(self, value): - if hasattr(self, 'entry_widget'): + if hasattr(self, 'entry_widget'): self.entry_widget.values = value elif hasattr(self, '__tmp_value'): # probably trying to set the value before the textarea is initialised self.__tmp_values = value + def del_values(self): del self.entry_widget.value + values = property(get_values, set_values, del_values) - + class TitlePager(TitleMultiLine): _entry_type = Pager + class BufferPager(Pager): DEFAULT_MAXLEN = None - + def __init__(self, screen, maxlen=False, *args, **keywords): super(BufferPager, self).__init__(screen, *args, **keywords) if maxlen is False: maxlen = self.DEFAULT_MAXLEN self.values = collections.deque(maxlen=maxlen) - + def clearBuffer(self): self.values.clear() - + def setValuesWrap(self, lines): if self.autowrap and (lines == self._values_cache_for_wrapping): return False @@ -834,11 +835,11 @@ def setValuesWrap(self, lines): lines = lines.split('\n') except AttributeError: pass - + self.clearBuffer() - self.buffer(self._wrap_message_lines(lines, self.width-1)) - self._values_cache_for_wrapping = copy.deepcopy(self.values) - + self.buffer(self._wrap_message_lines(lines, self.width - 1)) + self._values_cache_for_wrapping = copy.deepcopy(self.values) + def buffer(self, lines, scroll_end=True, scroll_if_editing=False): "Add data to be displayed in the buffer." self.values.extend(lines) @@ -847,17 +848,13 @@ def buffer(self, lines, scroll_end=True, scroll_if_editing=False): self.start_display_at = len(self.values) - len(self._my_widgets) elif scroll_if_editing: self.start_display_at = len(self.values) - len(self._my_widgets) - + + class TitleBufferPager(TitleMultiLine): _entry_type = BufferPager - + def clearBuffer(self): return self.entry_widget.clearBuffer() - + def buffer(self, *args, **values): return self.entry_widget.buffer(*args, **values) - - - - - diff --git a/npyscreen/wgmultiselect.py b/npyscreen/wgmultiselect.py index 962bf94..5bd7bf9 100755 --- a/npyscreen/wgmultiselect.py +++ b/npyscreen/wgmultiselect.py @@ -68,7 +68,7 @@ def h_act_on_highlighted(self, ch): return self.actionHighlighted(self.values[self.cursor_line], ch) def h_act_on_selected(self, ch): - if self.value: + if self.vale: return self.actionSelected(self.get_selected_objects(), ch) diff --git a/npyscreen/wgtextbox.py b/npyscreen/wgtextbox.py index bd3493f..f5ebe9d 100755 --- a/npyscreen/wgtextbox.py +++ b/npyscreen/wgtextbox.py @@ -3,86 +3,86 @@ import curses.ascii import sys import locale -#import curses.wrapper +# import curses.wrapper from . import wgwidget as widget from . import npysGlobalOptions as GlobalOptions + class TextfieldBase(widget.Widget): ENSURE_STRING_VALUE = True + def __init__(self, screen, value='', highlight_color='CURSOR', highlight_whole_widget=False, - invert_highlight_color=True, - **keywords): + invert_highlight_color=True, + **keywords): try: self.value = value or "" except: self.value = "" - - + super(TextfieldBase, self).__init__(screen, **keywords) if GlobalOptions.ASCII_ONLY or locale.getpreferredencoding() == 'US-ASCII': self._force_ascii = True else: self._force_ascii = False - + self.cursor_position = False - + self.highlight_color = highlight_color self.highlight_whole_widget = highlight_whole_widget self.invert_highlight_color = invert_highlight_color self.show_bold = False self.highlight = False self.important = False - + self.syntax_highlighting = False - self._highlightingdata = None + self._highlightingdata = None self.left_margin = 0 - - self.begin_at = 0 # Where does the display string begin? - + + self.begin_at = 0 # Where does the display string begin? + self.set_text_widths() self.update() - + def set_text_widths(self): if self.on_last_line: self.maximum_string_length = self.width - 2 # Leave room for the cursor - else: + else: self.maximum_string_length = self.width - 1 # Leave room for the cursor at the end of the string. def resize(self): self.set_text_widths() - def calculate_area_needed(self): "Need one line of screen, and any width going" - return 1,0 + return 1, 0 def update(self, clear=True, cursor=True): """Update the contents of the textbox, without calling the final refresh to the screen""" # cursor not working. See later for a fake cursor - #if self.editing: pmfuncs.show_cursor() - #else: pmfuncs.hide_cursor() + # if self.editing: pmfuncs.show_cursor() + # else: pmfuncs.hide_cursor() # Not needed here -- gets called too much! - #pmfuncs.hide_cursor() - + # pmfuncs.hide_cursor() + if clear: self.clear() - + if self.hidden: return True - - value_to_use_for_calculations = self.value - + + value_to_use_for_calculations = self.value + if self.ENSURE_STRING_VALUE: if value_to_use_for_calculations in (None, False, True): value_to_use_for_calculations = '' self.value = '' if self.begin_at < 0: self.begin_at = 0 - + if self.left_margin >= self.maximum_string_length: raise ValueError - + if self.editing: if isinstance(self.value, bytes): # use a unicode version of self.value to work out where the cursor is. @@ -101,60 +101,54 @@ def update(self, clear=True, cursor=True): if self.cursor_position < self.begin_at: self.begin_at = self.cursor_position - while self.cursor_position > self.begin_at + self.maximum_string_length - self.left_margin: # -1: + while self.cursor_position > self.begin_at + self.maximum_string_length - self.left_margin: # -1: self.begin_at += 1 else: if self.do_colors(): - self.parent.curses_pad.bkgdset(' ', self.parent.theme_manager.findPair(self, self.highlight_color) | curses.A_STANDOUT) + self.parent.curses_pad.bkgdset(' ', self.parent.theme_manager.findPair(self, + self.highlight_color) | curses.A_STANDOUT) else: - self.parent.curses_pad.bkgdset(' ',curses.A_STANDOUT) - - + self.parent.curses_pad.bkgdset(' ', curses.A_STANDOUT) # Do this twice so that the _print method can ignore it if needed. if self.highlight: if self.do_colors(): if self.invert_highlight_color: - attributes=self.parent.theme_manager.findPair(self, self.highlight_color) | curses.A_STANDOUT + attributes = self.parent.theme_manager.findPair(self, self.highlight_color) | curses.A_STANDOUT else: - attributes=self.parent.theme_manager.findPair(self, self.highlight_color) + attributes = self.parent.theme_manager.findPair(self, self.highlight_color) self.parent.curses_pad.bkgdset(' ', attributes) else: - self.parent.curses_pad.bkgdset(' ',curses.A_STANDOUT) - + self.parent.curses_pad.bkgdset(' ', curses.A_STANDOUT) if self.show_bold: self.parent.curses_pad.attron(curses.A_BOLD) if self.important and not self.do_colors(): self.parent.curses_pad.attron(curses.A_UNDERLINE) - self._print() - - - # reset everything to normal self.parent.curses_pad.attroff(curses.A_BOLD) self.parent.curses_pad.attroff(curses.A_UNDERLINE) - self.parent.curses_pad.bkgdset(' ',curses.A_NORMAL) + self.parent.curses_pad.bkgdset(' ', curses.A_NORMAL) self.parent.curses_pad.attrset(0) if self.editing and cursor: self.print_cursor() - + def print_cursor(self): # This needs fixing for Unicode multi-width chars. # Cursors do not seem to work on pads. - #self.parent.curses_pad.move(self.rely, self.cursor_position - self.begin_at) + # self.parent.curses_pad.move(self.rely, self.cursor_position - self.begin_at) # let's have a fake cursor _cur_loc_x = self.cursor_position - self.begin_at + self.relx + self.left_margin # The following two lines work fine for ascii, but not for unicode - #char_under_cur = self.parent.curses_pad.inch(self.rely, _cur_loc_x) - #self.parent.curses_pad.addch(self.rely, self.cursor_position - self.begin_at + self.relx, char_under_cur, curses.A_STANDOUT) - #The following appears to work for unicode as well. + # char_under_cur = self.parent.curses_pad.inch(self.rely, _cur_loc_x) + # self.parent.curses_pad.addch(self.rely, self.cursor_position - self.begin_at + self.relx, char_under_cur, curses.A_STANDOUT) + # The following appears to work for unicode as well. try: - #char_under_cur = self.value[self.cursor_position] #use the real value + # char_under_cur = self.value[self.cursor_position] #use the real value char_under_cur = self._get_string_to_print()[self.cursor_position] char_under_cur = self.safe_string(char_under_cur) except IndexError: @@ -162,27 +156,30 @@ def print_cursor(self): except TypeError: char_under_cur = ' ' if self.do_colors(): - self.parent.curses_pad.addstr(self.rely, self.cursor_position - self.begin_at + self.relx + self.left_margin, char_under_cur, self.parent.theme_manager.findPair(self, 'CURSOR_INVERSE')) + self.parent.curses_pad.addstr(self.rely, + self.cursor_position - self.begin_at + self.relx + self.left_margin, + char_under_cur, self.parent.theme_manager.findPair(self, 'CURSOR_INVERSE')) else: - self.parent.curses_pad.addstr(self.rely, self.cursor_position - self.begin_at + self.relx + self.left_margin, char_under_cur, curses.A_STANDOUT) - + self.parent.curses_pad.addstr(self.rely, + self.cursor_position - self.begin_at + self.relx + self.left_margin, + char_under_cur, curses.A_STANDOUT) def print_cursor_pre_unicode(self): # Cursors do not seem to work on pads. - #self.parent.curses_pad.move(self.rely, self.cursor_position - self.begin_at) + # self.parent.curses_pad.move(self.rely, self.cursor_position - self.begin_at) # let's have a fake cursor _cur_loc_x = self.cursor_position - self.begin_at + self.relx + self.left_margin # The following two lines work fine for ascii, but not for unicode - #char_under_cur = self.parent.curses_pad.inch(self.rely, _cur_loc_x) - #self.parent.curses_pad.addch(self.rely, self.cursor_position - self.begin_at + self.relx, char_under_cur, curses.A_STANDOUT) - #The following appears to work for unicode as well. + # char_under_cur = self.parent.curses_pad.inch(self.rely, _cur_loc_x) + # self.parent.curses_pad.addch(self.rely, self.cursor_position - self.begin_at + self.relx, char_under_cur, curses.A_STANDOUT) + # The following appears to work for unicode as well. try: char_under_cur = self.display_value(self.value)[self.cursor_position] except: char_under_cur = ' ' - self.parent.curses_pad.addstr(self.rely, self.cursor_position - self.begin_at + self.relx + self.left_margin, char_under_cur, curses.A_STANDOUT) - + self.parent.curses_pad.addstr(self.rely, self.cursor_position - self.begin_at + self.relx + self.left_margin, + char_under_cur, curses.A_STANDOUT) def display_value(self, value): if value == None: @@ -193,14 +190,13 @@ def display_value(self, value): except UnicodeEncodeError: str_value = self.safe_string(value) return str_value - except ReferenceError: + except ReferenceError: return ">*ERROR*ERROR*ERROR*<" return self.safe_string(str_value) - def find_width_of_char(self, ch): return 1 - + def _print_unicode_char(self, ch): # return the ch to print. For python 3 this is just ch if self._force_ascii: @@ -209,57 +205,59 @@ def _print_unicode_char(self, ch): return ch else: return ch.encode('utf-8', 'strict') - + def _get_string_to_print(self): string_to_print = self.display_value(self.value) if not string_to_print: return None - string_to_print = string_to_print[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin] - + string_to_print = string_to_print[self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin] + if sys.version_info[0] >= 3: - string_to_print = self.display_value(self.value)[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin] + string_to_print = self.display_value(self.value)[ + self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin] else: # ensure unicode only here encoding here. dv = self.display_value(self.value) if isinstance(dv, bytes): dv = dv.decode(self.encoding, 'replace') - string_to_print = dv[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin] + string_to_print = dv[self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin] return string_to_print - - + def _print(self): string_to_print = self._get_string_to_print() if not string_to_print: return None - string_to_print = string_to_print[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin] - + string_to_print = string_to_print[self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin] + if sys.version_info[0] >= 3: - string_to_print = self.display_value(self.value)[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin] + string_to_print = self.display_value(self.value)[ + self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin] else: # ensure unicode only here encoding here. dv = self.display_value(self.value) if isinstance(dv, bytes): dv = dv.decode(self.encoding, 'replace') - string_to_print = dv[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin] - + string_to_print = dv[self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin] + column = 0 place_in_string = 0 if self.syntax_highlighting: - self.update_highlighting(start=self.begin_at, end=self.maximum_string_length+self.begin_at-self.left_margin) + self.update_highlighting(start=self.begin_at, + end=self.maximum_string_length + self.begin_at - self.left_margin) while column <= (self.maximum_string_length - self.left_margin): - if not string_to_print or place_in_string > len(string_to_print)-1: + if not string_to_print or place_in_string > len(string_to_print) - 1: break width_of_char_to_print = self.find_width_of_char(string_to_print[place_in_string]) if column - 1 + width_of_char_to_print > self.maximum_string_length: - break + break try: - highlight = self._highlightingdata[self.begin_at+place_in_string] + highlight = self._highlightingdata[self.begin_at + place_in_string] except: - highlight = curses.A_NORMAL - self.parent.curses_pad.addstr(self.rely,self.relx+column+self.left_margin, - self._print_unicode_char(string_to_print[place_in_string]), - highlight - ) + highlight = curses.A_NORMAL + self.parent.curses_pad.addstr(self.rely, self.relx + column + self.left_margin, + self._print_unicode_char(string_to_print[place_in_string]), + highlight + ) column += self.find_width_of_char(string_to_print[place_in_string]) place_in_string += 1 else: @@ -279,80 +277,86 @@ def _print(self): color = curses.A_NORMAL while column <= (self.maximum_string_length - self.left_margin): - if not string_to_print or place_in_string > len(string_to_print)-1: + if not string_to_print or place_in_string > len(string_to_print) - 1: if self.highlight_whole_widget: - self.parent.curses_pad.addstr(self.rely,self.relx+column+self.left_margin, - ' ', - color - ) + self.parent.curses_pad.addstr(self.rely, self.relx + column + self.left_margin, + ' ', + color + ) column += width_of_char_to_print place_in_string += 1 continue else: break - + width_of_char_to_print = self.find_width_of_char(string_to_print[place_in_string]) if column - 1 + width_of_char_to_print > self.maximum_string_length: - break - self.parent.curses_pad.addstr(self.rely,self.relx+column+self.left_margin, - self._print_unicode_char(string_to_print[place_in_string]), - color - ) + break + self.parent.curses_pad.addstr(self.rely, self.relx + column + self.left_margin, + self._print_unicode_char(string_to_print[place_in_string]), + color + ) column += width_of_char_to_print place_in_string += 1 - - - - - + def _print_pre_unicode(self): # This method was used to print the string before we became interested in unicode. - + string_to_print = self.display_value(self.value) if string_to_print == None: return - + if self.syntax_highlighting: - self.update_highlighting(start=self.begin_at, end=self.maximum_string_length+self.begin_at-self.left_margin) - for i in range(len(string_to_print[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin])): + self.update_highlighting(start=self.begin_at, + end=self.maximum_string_length + self.begin_at - self.left_margin) + for i in range( + len(string_to_print[self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin])): try: - highlight = self._highlightingdata[self.begin_at+i] + highlight = self._highlightingdata[self.begin_at + i] except: highlight = curses.A_NORMAL - self.parent.curses_pad.addstr(self.rely,self.relx+i+self.left_margin, - string_to_print[self.begin_at+i], - highlight - ) - + self.parent.curses_pad.addstr(self.rely, self.relx + i + self.left_margin, + string_to_print[self.begin_at + i], + highlight + ) + elif self.do_colors(): coltofind = 'DEFAULT' if self.show_bold and self.color == 'DEFAULT': coltofind = 'BOLD' if self.show_bold: - self.parent.curses_pad.addstr(self.rely,self.relx+self.left_margin, string_to_print[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin], - self.parent.theme_manager.findPair(self, coltofind) | curses.A_BOLD) + self.parent.curses_pad.addstr(self.rely, self.relx + self.left_margin, string_to_print[ + self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin], + self.parent.theme_manager.findPair(self, coltofind) | curses.A_BOLD) elif self.important: coltofind = 'IMPORTANT' - self.parent.curses_pad.addstr(self.rely,self.relx+self.left_margin, string_to_print[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin], - self.parent.theme_manager.findPair(self, coltofind) | curses.A_BOLD) + self.parent.curses_pad.addstr(self.rely, self.relx + self.left_margin, string_to_print[ + self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin], + self.parent.theme_manager.findPair(self, coltofind) | curses.A_BOLD) else: - self.parent.curses_pad.addstr(self.rely,self.relx+self.left_margin, string_to_print[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin], - self.parent.theme_manager.findPair(self)) + self.parent.curses_pad.addstr(self.rely, self.relx + self.left_margin, string_to_print[ + self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin], + self.parent.theme_manager.findPair(self)) else: if self.important: - self.parent.curses_pad.addstr(self.rely,self.relx+self.left_margin, - string_to_print[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin], curses.A_BOLD) + self.parent.curses_pad.addstr(self.rely, self.relx + self.left_margin, + string_to_print[ + self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin], + curses.A_BOLD) elif self.show_bold: - self.parent.curses_pad.addstr(self.rely,self.relx+self.left_margin, - string_to_print[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin], curses.A_BOLD) + self.parent.curses_pad.addstr(self.rely, self.relx + self.left_margin, + string_to_print[ + self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin], + curses.A_BOLD) else: - self.parent.curses_pad.addstr(self.rely,self.relx+self.left_margin, - string_to_print[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin]) - + self.parent.curses_pad.addstr(self.rely, self.relx + self.left_margin, + string_to_print[ + self.begin_at:self.maximum_string_length + self.begin_at - self.left_margin]) + def update_highlighting(self, start=None, end=None, clear=False): if clear or (self._highlightingdata == None): self._highlightingdata = [] - + string_to_print = self.display_value(self.value) @@ -361,21 +365,20 @@ def show_brief_message(self, message): curses.beep() keep_for_a_moment = self.value self.value = message - self.editing=False + self.editing = False self.display() curses.napms(1200) - self.editing=True + self.editing = True self.value = keep_for_a_moment - def edit(self): self.editing = 1 if self.cursor_position is False: self.cursor_position = len(self.value or '') self.parent.curses_pad.keypad(1) - + self.old_value = self.value - + self.how_exited = False while self.editing: @@ -391,45 +394,44 @@ def edit(self): # Handlers and methods def set_up_handlers(self): - super(Textfield, self).set_up_handlers() - + super(Textfield, self).set_up_handlers() + # For OS X del_key = curses.ascii.alt('~') - - self.handlers.update({curses.KEY_LEFT: self.h_cursor_left, - curses.KEY_RIGHT: self.h_cursor_right, - curses.KEY_DC: self.h_delete_right, - curses.ascii.DEL: self.h_delete_left, - curses.ascii.BS: self.h_delete_left, - curses.KEY_BACKSPACE: self.h_delete_left, - # mac os x curses reports DEL as escape oddly - # no solution yet - "^K": self.h_erase_right, - "^U": self.h_erase_left, - }) + + self.handlers.update({curses.KEY_LEFT: self.h_cursor_left, + curses.KEY_RIGHT: self.h_cursor_right, + curses.KEY_DC: self.h_delete_right, + curses.ascii.DEL: self.h_delete_left, + curses.ascii.BS: self.h_delete_left, + curses.KEY_BACKSPACE: self.h_delete_left, + # mac os x curses reports DEL as escape oddly + # no solution yet + "^K": self.h_erase_right, + "^U": self.h_erase_left, + }) self.complex_handlers.extend(( - (self.t_input_isprint, self.h_addch), - # (self.t_is_ck, self.h_erase_right), - # (self.t_is_cu, self.h_erase_left), - )) + (self.t_input_isprint, self.h_addch), + # (self.t_is_ck, self.h_erase_right), + # (self.t_is_cu, self.h_erase_left), + )) def t_input_isprint(self, inp): if self._last_get_ch_was_unicode and inp not in '\n\t\r': return True if curses.ascii.isprint(inp) and \ - (chr(inp) not in '\n\t\r'): + (chr(inp) not in '\n\t\r'): return True - else: + else: return False - - + def h_addch(self, inp): if self.editable: - #self.value = self.value[:self.cursor_position] + curses.keyname(input) \ + # self.value = self.value[:self.cursor_position] + curses.keyname(input) \ # + self.value[self.cursor_position:] - #self.cursor_position += len(curses.keyname(input)) - + # self.cursor_position += len(curses.keyname(input)) + # workaround for the metamode bug: if self._last_get_ch_was_unicode == True and isinstance(self.value, bytes): # probably dealing with python2. @@ -443,13 +445,13 @@ def h_addch(self, inp): except TypeError: ch_adding = input self.value = self.value[:self.cursor_position] + ch_adding \ - + self.value[self.cursor_position:] + + self.value[self.cursor_position:] self.cursor_position += len(ch_adding) # or avoid it entirely: - #self.value = self.value[:self.cursor_position] + curses.ascii.unctrl(input) \ + # self.value = self.value[:self.cursor_position] + curses.ascii.unctrl(input) \ # + self.value[self.cursor_position:] - #self.cursor_position += len(curses.ascii.unctrl(input)) + # self.cursor_position += len(curses.ascii.unctrl(input)) def h_cursor_left(self, input): self.cursor_position -= 1 @@ -459,45 +461,43 @@ def h_cursor_right(self, input): def h_delete_left(self, input): if self.editable and self.cursor_position > 0: - self.value = self.value[:self.cursor_position-1] + self.value[self.cursor_position:] - + self.value = self.value[:self.cursor_position - 1] + self.value[self.cursor_position:] + self.cursor_position -= 1 self.begin_at -= 1 - def h_delete_right(self, input): if self.editable: - self.value = self.value[:self.cursor_position] + self.value[self.cursor_position+1:] + self.value = self.value[:self.cursor_position] + self.value[self.cursor_position + 1:] def h_erase_left(self, input): if self.editable: self.value = self.value[self.cursor_position:] - self.cursor_position=0 - + self.cursor_position = 0 + def h_erase_right(self, input): if self.editable: self.value = self.value[:self.cursor_position] self.cursor_position = len(self.value) self.begin_at = 0 - + def handle_mouse_event(self, mouse_event): - #mouse_id, x, y, z, bstate = mouse_event - #rel_mouse_x = x - self.relx - self.parent.show_atx + # mouse_id, x, y, z, bstate = mouse_event + # rel_mouse_x = x - self.relx - self.parent.show_atx mouse_id, rel_x, rel_y, z, bstate = self.interpret_mouse_event(mouse_event) self.cursor_position = rel_x + self.begin_at self.display() - + class FixedText(TextfieldBase): def set_up_handlers(self): super(FixedText, self).set_up_handlers() - self.handlers.update({curses.KEY_LEFT: self.h_cursor_left, - curses.KEY_RIGHT: self.h_cursor_right, - ord('k'): self.h_exit_up, - ord('j'): self.h_exit_down, - }) - - + self.handlers.update({curses.KEY_LEFT: self.h_cursor_left, + curses.KEY_RIGHT: self.h_cursor_right, + ord('k'): self.h_exit_up, + ord('j'): self.h_exit_down, + }) + def h_cursor_left(self, input): if self.begin_at > 0: self.begin_at -= 1 @@ -506,17 +506,17 @@ def h_cursor_right(self, input): if len(self.value) - self.begin_at > self.maximum_string_length: self.begin_at += 1 - def update(self, clear=True,): + def update(self, clear=True, cursor=False): super(FixedText, self).update(clear=clear, cursor=False) - + def edit(self): self.editing = 1 self.highlight = False self.cursor_position = 0 self.parent.curses_pad.keypad(1) - + self.old_value = self.value - + self.how_exited = False while self.editing: @@ -529,3 +529,29 @@ def edit(self): return self.how_exited, self.value + +class Numericfield(Textfield): + """a Textfield that accepts only numeric input""" + def __init__(self, *args, **keywords): + super(Numericfield, self).__init__(*args, **keywords) + self.add_complex_handlers([(self.t_input_isdigit, self.h_addch)]) + self.remove_complex_handler(self.t_input_isprint) + + # only allows numbers to be input (ie: 0 to 9) + def t_input_isdigit(self, inp): + import curses + if curses.ascii.isdigit(inp): + return True + else: + return False + + def safe_to_exit(self): + try: + parent_widget = getattr(self, "parent_widget") + except AttributeError: + return True + try: + ste = getattr(parent_widget, "safe_to_exit") + except AttributeError: + return True + return ste() diff --git a/npyscreen/wgtitlefield.py b/npyscreen/wgtitlefield.py index 0d212ec..22bad3e 100755 --- a/npyscreen/wgtitlefield.py +++ b/npyscreen/wgtitlefield.py @@ -26,7 +26,7 @@ def __init__(self, screen, if self.name is None: self.name = 'NoName' if use_two_lines is None: - if len(self.name)+2 >= begin_entry_at: + if len(self.name) >= begin_entry_at: self.use_two_lines = True else: self.use_two_lines = False @@ -170,5 +170,19 @@ def editable(self, value): except AttributeError: self._editable = value + def add_handlers(self, handler_dictionary): + """ + Pass handlers to entry_widget + """ + self.entry_widget.add_handlers(handler_dictionary) + class TitleFixedText(TitleText): _entry_type = textbox.FixedText + +class TitleNumeric(TitleText): + """a TitleText box that accepts only numeric input""" + _entry_type = textbox.Numericfield + + def __init__(self, *args, **keywords): + self.last_value = None + super(TitleNumeric, self).__init__(*args, **keywords) \ No newline at end of file diff --git a/npyscreen/wgwidget.py b/npyscreen/wgwidget.py index 35cba6f..bf189d5 100755 --- a/npyscreen/wgwidget.py +++ b/npyscreen/wgwidget.py @@ -1,32 +1,27 @@ #!/usr/bin/python -import codecs import copy -import sys import curses import curses.ascii -#import curses.wrapper +import locale +import sys +# import curses.wrapper import weakref + from . import npysGlobalOptions as GlobalOptions from . import wgwidget_proto -import locale -import warnings - -from .globals import DEBUG - # experimental from .eveventhandler import EventHandler +from .globals import DEBUG - - -EXITED_DOWN = 1 -EXITED_UP = -1 -EXITED_LEFT = -2 -EXITED_RIGHT = 2 -EXITED_ESCAPE= 127 +EXITED_DOWN = 1 +EXITED_UP = -1 +EXITED_LEFT = -2 +EXITED_RIGHT = 2 +EXITED_ESCAPE = 127 EXITED_MOUSE = 130 -SETMAX = 'SETMAX' -RAISEERROR = 'RAISEERROR' +SETMAX = 'SETMAX' +RAISEERROR = 'RAISEERROR' ALLOW_NEW_INPUT = True @@ -35,7 +30,7 @@ 'TEST_INPUT_LOG': [], 'CONTINUE_AFTER_TEST_INPUT': False, 'INPUT_GENERATOR': None, - } +} def add_test_input_from_iterable(test_input): @@ -43,20 +38,23 @@ def add_test_input_from_iterable(test_input): if not TEST_SETTINGS['TEST_INPUT']: TEST_SETTINGS['TEST_INPUT'] = [] TEST_SETTINGS['TEST_INPUT'].extend([ch for ch in test_input]) - + + def add_test_input_ch(test_input): global TEST_SETTINGS if not TEST_SETTINGS['TEST_INPUT']: TEST_SETTINGS['TEST_INPUT'] = [] TEST_SETTINGS['TEST_INPUT'].append(test_input) - + class ExhaustedTestInput(Exception): pass + class NotEnoughSpaceForWidget(Exception): pass + class InputHandler(object): "An object that can handle user input" @@ -66,26 +64,25 @@ def handle_input(self, _input): runs the methods in self.complex_handlers (if any), which is an array of form (test_func, dispatch_func). If test_func(input) returns true, then dispatch_func(input) is called. Check to see if parent can handle. No further action taken after that point.""" - + if _input in self.handlers: self.handlers[_input](_input) return True - + try: _unctrl_input = curses.ascii.unctrl(_input) except TypeError: _unctrl_input = None - + if _unctrl_input and (_unctrl_input in self.handlers): self.handlers[_unctrl_input](_input) return True - - if not hasattr(self, 'complex_handlers'): + if not hasattr(self, 'complex_handlers'): return False else: for test, handler in self.complex_handlers: - if test(_input) is not False: + if test(_input) is not False: handler(_input) return True if hasattr(self, 'parent_widget') and hasattr(self.parent_widget, 'handle_input'): @@ -97,34 +94,34 @@ def handle_input(self, _input): else: pass - # If we've got here, all else has failed, so: + # If we've got here, all else has failed, so: return False def set_up_handlers(self): """This function should be called somewhere during object initialisation (which all library-defined widgets do). You might like to override this in your own definition, but in most cases the add_handers or add_complex_handlers methods are what you want.""" - #called in __init__ + # called in __init__ self.handlers = { - curses.ascii.NL: self.h_exit_down, - curses.ascii.CR: self.h_exit_down, - curses.ascii.TAB: self.h_exit_down, - curses.KEY_BTAB: self.h_exit_up, - curses.KEY_DOWN: self.h_exit_down, - curses.KEY_UP: self.h_exit_up, - curses.KEY_LEFT: self.h_exit_left, - curses.KEY_RIGHT: self.h_exit_right, - "^P": self.h_exit_up, - "^N": self.h_exit_down, - curses.ascii.ESC: self.h_exit_escape, - curses.KEY_MOUSE: self.h_exit_mouse, - } + curses.ascii.NL: self.h_exit_down, + curses.ascii.CR: self.h_exit_down, + curses.ascii.TAB: self.h_exit_down, + curses.KEY_BTAB: self.h_exit_up, + curses.KEY_DOWN: self.h_exit_down, + curses.KEY_UP: self.h_exit_up, + curses.KEY_LEFT: self.h_exit_left, + curses.KEY_RIGHT: self.h_exit_right, + "^P": self.h_exit_up, + "^N": self.h_exit_down, + curses.ascii.ESC: self.h_exit_escape, + curses.KEY_MOUSE: self.h_exit_mouse, + } self.complex_handlers = [] def add_handlers(self, handler_dictionary): """Update the dictionary of simple handlers. Pass in a dictionary with keyname (eg "^P" or curses.KEY_DOWN) as the key, and the function that key should call as the values """ self.handlers.update(handler_dictionary) - + def add_complex_handlers(self, handlers_list): """add complex handlers: format of the list is pairs of (test_function, callback) sets""" @@ -132,7 +129,7 @@ def add_complex_handlers(self, handlers_list): for pair in handlers_list: assert len(pair) == 2 self.complex_handlers.extend(handlers_list) - + def remove_complex_handler(self, test_function): _new_list = [] for pair in self.complex_handlers: @@ -140,28 +137,38 @@ def remove_complex_handler(self, test_function): _new_list.append(pair) self.complex_handlers = _new_list -########################################################################################### -# Handler Methods here - npc convention - prefix with h_ + ########################################################################################### + # Handler Methods here - npc convention - prefix with h_ def h_exit_down(self, _input): """Called when user leaves the widget to the next widget""" + if not self._test_safe_to_exit(): + return False self.editing = False self.how_exited = EXITED_DOWN - + def h_exit_right(self, _input): + if not self._test_safe_to_exit(): + return False self.editing = False self.how_exited = EXITED_RIGHT def h_exit_up(self, _input): + if not self._test_safe_to_exit(): + return False """Called when the user leaves the widget to the previous widget""" self.editing = False self.how_exited = EXITED_UP - + def h_exit_left(self, _input): + if not self._test_safe_to_exit(): + return False self.editing = False self.how_exited = EXITED_LEFT - + def h_exit_escape(self, _input): + if not self._test_safe_to_exit(): + return False self.editing = False self.how_exited = EXITED_ESCAPE @@ -170,38 +177,36 @@ def h_exit_mouse(self, _input): if mouse_event and self.intersted_in_mouse_event(mouse_event): self.handle_mouse_event(mouse_event) else: - if mouse_event: + if mouse_event and self._test_safe_to_exit(): curses.ungetmouse(*mouse_event) ch = self.parent.curses_pad.getch() assert ch == curses.KEY_MOUSE self.editing = False self.how_exited = EXITED_MOUSE - - class Widget(InputHandler, wgwidget_proto._LinePrinter, EventHandler): "A base class for widgets. Do not use directly" - + _SAFE_STRING_STRIPS_NL = True - + def destroy(self): """Destroy the widget: methods should provide a mechanism to destroy any references that might case a memory leak. See select. module for an example""" pass - - def __init__(self, screen, - relx=0, rely=0, name=None, value=None, - width = False, height = False, - max_height = False, max_width=False, - editable=True, - hidden=False, - color='DEFAULT', - use_max_space=False, - check_value_change=True, - check_cursor_move=True, - value_changed_callback=None, - **keywords): + + def __init__(self, screen, + relx=0, rely=0, name=None, value=None, + width=False, height=False, + max_height=False, max_width=False, + editable=True, + hidden=False, + color='DEFAULT', + use_max_space=False, + check_value_change=True, + check_cursor_move=True, + value_changed_callback=None, + **keywords): """The following named arguments may be supplied: name= set the name of the widget. width= set the width of the widget. @@ -215,30 +220,29 @@ def __init__(self, screen, value_changed_callback - should be None or a Function. If it is a function, it will have be called when the value changes and passed the keyword argument widget=self. """ - self.check_value_change=check_value_change - self.check_cursor_move =check_cursor_move + self.check_value_change = check_value_change + self.check_cursor_move = check_cursor_move self.hidden = hidden - self.interested_in_mouse_even_when_not_editable = False# used only for rare widgets to allow user to click - # even if can't actually select the widget. See mutt-style forms - + self.interested_in_mouse_even_when_not_editable = False # used only for rare widgets to allow user to click + # even if can't actually select the widget. See mutt-style forms + try: self.parent = weakref.proxy(screen) except TypeError: self.parent = screen - self.use_max_space = use_max_space + self.use_max_space = use_max_space self.set_relyx(rely, relx) - #self.relx = relx - #self.rely = rely + # self.relx = relx + # self.rely = rely self.color = color - self.encoding = 'utf-8'#locale.getpreferredencoding() + self.encoding = 'utf-8' # locale.getpreferredencoding() if GlobalOptions.ASCII_ONLY or locale.getpreferredencoding() == 'US-ASCII': self._force_ascii = True else: self._force_ascii = False - - + self.set_up_handlers() - + # To allow derived classes to set this and then call this method safely... try: self.value @@ -249,29 +253,31 @@ def __init__(self, screen, try: self.name except: - self.name=name - - self.request_width = width # widgets should honour if possible - self.request_height = height # widgets should honour if possible + self.name = name + + self.request_width = width # widgets should honour if possible + self.request_height = height # widgets should honour if possible self.max_height = max_height self.max_width = max_width self.set_size() - self.editing = False # Change to true during an edit - + self.editing = False # Change to true during an edit + self.editable = editable - if self.parent.curses_pad.getmaxyx()[0]-1 == self.rely: self.on_last_line = True - else: self.on_last_line = False - + if self.parent.curses_pad.getmaxyx()[0] - 1 == self.rely: + self.on_last_line = True + else: + self.on_last_line = False + if value_changed_callback: self.value_changed_callback = value_changed_callback else: self.value_changed_callback = None - + self.initialize_event_handling() - + def set_relyx(self, y, x): """ Set the position of the widget on the Form. If y or x is a negative value, @@ -288,120 +294,135 @@ def set_relyx(self, y, x): self._requested_rely = y self.rely = self.parent.curses_pad.getmaxyx()[0] + y # I don't think there is any real value in using these margins - #if self.parent.BLANK_LINES_BASE and not self.use_max_space: + # if self.parent.BLANK_LINES_BASE and not self.use_max_space: # self.rely -= self.parent.BLANK_LINES_BASE if self.rely < 0: self.rely = 0 if x >= 0: - self.relx = x + self.relx = x else: self.relx = self.parent.curses_pad.getmaxyx()[1] + x # I don't think there is any real value in using these margins - #if self.parent.BLANK_COLUMNS_RIGHT and not self.use_max_space: + # if self.parent.BLANK_COLUMNS_RIGHT and not self.use_max_space: # self.relx -= self.parent.BLANK_COLUMNS_RIGHT if self.relx < 0: self.relx = 0 - + def _move_widget_on_terminal_resize(self): if self._requested_rely < 0 or self._requested_relx < 0: self.set_relyx(self._requested_rely, self._requested_relx) - + def _resize(self): "Internal Method. This will be the method called when the terminal resizes." self._move_widget_on_terminal_resize() self._recalculate_size() - if self.parent.curses_pad.getmaxyx()[0]-1 == self.rely: self.on_last_line = True - else: self.on_last_line = False + if self.parent.curses_pad.getmaxyx()[0] - 1 == self.rely: + self.on_last_line = True + else: + self.on_last_line = False self.resize() self.when_resized() - + def resize(self): "Widgets should override this to control what should happen when they are resized." pass - + def _recalculate_size(self): return self.set_size() - + def when_resized(self): # this method is called when the widget has been resized. pass - - + + def on_focusin(self): + """ + Event function to be called when the widget is focused. Note that this + is not called by the framework by default, the widget has to implement + calling this event itself. + """ + pass + + def on_focusout(self): + """ + Counterpart to on_focusin. + """ + pass + def do_colors(self): "Returns True if the widget should try to paint in coloour." if curses.has_colors() and not GlobalOptions.DISABLE_ALL_COLORS: return True else: return False - + def space_available(self): """The space available left on the screen, returned as rows, columns""" if self.use_max_space: y, x = self.parent.useable_space(self.rely, self.relx) else: y, x = self.parent.widget_useable_space(self.rely, self.relx) - return y,x + return y, x - def calculate_area_needed(self): + def calculate_area_needed(self): """Classes should provide a function to calculate the screen area they need, returning either y,x, or 0,0 if they want all the screen they can. However, do not use this to say how big a given widget is ... use .height and .width instead""" - return 0,0 + return 0, 0 def set_size(self): """Set the size of the object, reconciling the user's request with the space available""" my, mx = self.space_available() - #my = my+1 # Probably want to remove this. + # my = my+1 # Probably want to remove this. ny, nx = self.calculate_area_needed() - + max_height = self.max_height - max_width = self.max_width + max_width = self.max_width # What to do if max_height or max_width is negative if max_height not in (None, False) and max_height < 0: max_height = my + max_height if max_width not in (None, False) and max_width < 0: max_width = mx + max_width - + if max_height not in (None, False) and max_height <= 0: - raise NotEnoughSpaceForWidget("Not enough space for requested size") + raise NotEnoughSpaceForWidget("Not enough space for requested size") if max_width not in (None, False) and max_width <= 0: raise NotEnoughSpaceForWidget("Not enough space for requested size") - + if ny > 0: - if my >= ny: + if my >= ny: self.height = ny - else: + else: self.height = RAISEERROR elif max_height: - if max_height <= my: + if max_height <= my: self.height = max_height - else: - self.height = self.request_height - else: + else: + self.height = (self.request_height or my) + else: self.height = (self.request_height or my) - - #if mx <= 0 or my <= 0: - # raise Exception("Not enough space for widget") + # if mx <= 0 or my <= 0: + # raise Exception("Not enough space for widget") - if nx > 0: # if a minimum space is specified.... - if mx >= nx: # if max width is greater than needed space - self.width = nx # width is needed space - else: - self.width = RAISEERROR # else raise an error - elif self.max_width: # otherwise if a max width is speciied - if max_width <= mx: + if nx > 0: # if a minimum space is specified.... + if mx >= nx: # if max width is greater than needed space + self.width = nx # width is needed space + else: + self.width = RAISEERROR # else raise an error + elif self.max_width: # otherwise if a max width is speciied + if max_width <= mx: self.width = max_width - else: + else: self.width = RAISEERROR - else: + else: self.width = self.request_width or mx if self.height == RAISEERROR or self.width == RAISEERROR: # Not enough space for widget - raise NotEnoughSpaceForWidget("Not enough space: max y and x = %s , %s. Height and Width = %s , %s " % (my, mx, self.height, self.width) ) # unsafe. Need to add error here. - + raise NotEnoughSpaceForWidget("Not enough space: max y and x = %s , %s. Height and Width = %s , %s " % ( + my, mx, self.height, self.width)) # unsafe. Need to add error here. + def update(self, clear=True): """How should object display itself on the screen. Define here, but do not actually refresh the curses display, since this should be done as little as possible. This base widget puts nothing on screen.""" @@ -419,26 +440,30 @@ def display(self): self.parent.refresh() def set_editable(self, value): - if value: self._is_editable = True - else: self._is_editable = False + if value: + self._is_editable = True + else: + self._is_editable = False def get_editable(self): - return(self._is_editable) + return (self._is_editable) def clear(self, usechar=' '): """Blank the screen area used by this widget, ready for redrawing""" for y in range(self.height): -#This method is too slow -# for x in range(self.width+1): -# try: -# # We are in a try loop in case the cursor is moved off the bottom right corner of the screen -# self.parent.curses_pad.addch(self.rely+y, self.relx + x, usechar) -# except: pass -#Use this instead + # This method is too slow + # for x in range(self.width+1): + # try: + # # We are in a try loop in case the cursor is moved off the bottom right corner of the screen + # self.parent.curses_pad.addch(self.rely+y, self.relx + x, usechar) + # except: pass + # Use this instead if self.do_colors(): - self.parent.curses_pad.addstr(self.rely+y, self.relx, usechar * (self.width), self.parent.theme_manager.findPair(self, self.parent.color)) # used to be width + 1 + self.parent.curses_pad.addstr(self.rely + y, self.relx, usechar * (self.width), + self.parent.theme_manager.findPair(self, + self.parent.color)) # used to be width + 1 else: - self.parent.curses_pad.addstr(self.rely+y, self.relx, usechar * (self.width)) # used to be width + 1 + self.parent.curses_pad.addstr(self.rely + y, self.relx, usechar * (self.width)) # used to be width + 1 def edit(self): """Allow the user to edit the widget: ie. start handling keypresses.""" @@ -455,7 +480,7 @@ def _pre_edit(self): def _edit_loop(self): if not self.parent.editing: _i_set_parent_editing = True - self.parent.editing = True + self.parent.editing = True else: _i_set_parent_editing = False while self.editing and self.parent.editing: @@ -463,27 +488,27 @@ def _edit_loop(self): self.get_and_use_key_press() if _i_set_parent_editing: self.parent.editing = False - + if self.editing: - self.editing = False + self.editing = False self.how_exited = True def _post_edit(self): self.highlight = 0 self.update() - + def _get_ch(self): - #try: + # try: # # Python3.3 and above - returns unicode # ch = self.parent.curses_pad.get_wch() # self._last_get_ch_was_unicode = True - #except AttributeError: - + # except AttributeError: + # For now, disable all attempt to use get_wch() # but everything that follows could be in the except clause above. - - # Try to read utf-8 if possible. - _stored_bytes =[] + + # Try to read utf-8 if possible. + _stored_bytes = [] self._last_get_ch_was_unicode = True global ALLOW_NEW_INPUT if ALLOW_NEW_INPUT == True and locale.getpreferredencoding() == 'UTF-8': @@ -499,26 +524,26 @@ def _get_ch(self): # if we are here, we need to read 1, 2 or 3 more bytes. # all of the subsequent bytes should be in the range 128 - 191, # but we'll risk not checking... - elif 194 <= ch <= 223: - # 2 bytes - _stored_bytes.append(ch) - _stored_bytes.append(self.parent.curses_pad.getch()) - elif 224 <= ch <= 239: - # 3 bytes - _stored_bytes.append(ch) - _stored_bytes.append(self.parent.curses_pad.getch()) - _stored_bytes.append(self.parent.curses_pad.getch()) - elif 240 <= ch <= 244: - # 4 bytes - _stored_bytes.append(ch) - _stored_bytes.append(self.parent.curses_pad.getch()) - _stored_bytes.append(self.parent.curses_pad.getch()) - _stored_bytes.append(self.parent.curses_pad.getch()) + elif 194 <= ch <= 223: + # 2 bytes + _stored_bytes.append(ch) + _stored_bytes.append(self.parent.curses_pad.getch()) + elif 224 <= ch <= 239: + # 3 bytes + _stored_bytes.append(ch) + _stored_bytes.append(self.parent.curses_pad.getch()) + _stored_bytes.append(self.parent.curses_pad.getch()) + elif 240 <= ch <= 244: + # 4 bytes + _stored_bytes.append(ch) + _stored_bytes.append(self.parent.curses_pad.getch()) + _stored_bytes.append(self.parent.curses_pad.getch()) + _stored_bytes.append(self.parent.curses_pad.getch()) elif ch >= 245: # probably a control character self._last_get_ch_was_unicode = False return ch - + if sys.version_info[0] >= 3: ch = bytes(_stored_bytes).decode('utf-8', errors='strict') else: @@ -527,7 +552,7 @@ def _get_ch(self): else: ch = self.parent.curses_pad.getch() self._last_get_ch_was_unicode = False - + # This line should not be in the except clause. return ch @@ -539,8 +564,7 @@ def try_adjust_widgets(self): self.parent.parentApp._internal_adjust_widgets() if hasattr(self.parent.parentApp, "adjust_widgets"): self.parent.parentApp.adjust_widgets() - - + def try_while_waiting(self): if hasattr(self.parent, "while_waiting"): self.parent.while_waiting() @@ -567,13 +591,13 @@ def get_and_use_key_press(self): ch = self._get_ch() # handle escape-prefixed rubbish. if ch == curses.ascii.ESC: - #self.parent.curses_pad.timeout(1) + # self.parent.curses_pad.timeout(1) self.parent.curses_pad.nodelay(1) ch2 = self.parent.curses_pad.getch() - if ch2 != -1: + if ch2 != -1: ch = curses.ascii.alt(ch2) - self.parent.curses_pad.timeout(-1) # back to blocking mode - #curses.flushinp() + self.parent.curses_pad.timeout(-1) # back to blocking mode + # curses.flushinp() elif (TEST_SETTINGS['INPUT_GENERATOR']): self._last_get_ch_was_unicode = True try: @@ -595,42 +619,66 @@ def get_and_use_key_press(self): return else: raise ExhaustedTestInput - + self.handle_input(ch) if self.check_value_change: self.when_check_value_changed() if self.check_cursor_move: self.when_check_cursor_moved() - - + self.try_adjust_widgets() - + def intersted_in_mouse_event(self, mouse_event): if not self.editable and not self.interested_in_mouse_even_when_not_editable: return False mouse_id, x, y, z, bstate = mouse_event x += self.parent.show_from_x y += self.parent.show_from_y - if self.relx <= x <= self.relx + self.width-1 + self.parent.show_atx: - if self.rely <= y <= self.rely + self.height-1 + self.parent.show_aty: + if self.relx <= x <= self.relx + self.width - 1 + self.parent.show_atx: + if self.rely <= y <= self.rely + self.height - 1 + self.parent.show_aty: return True return False - + def handle_mouse_event(self, mouse_event): # mouse_id, x, y, z, bstate = mouse_event pass - + def interpret_mouse_event(self, mouse_event): mouse_id, x, y, z, bstate = mouse_event x += self.parent.show_from_x y += self.parent.show_from_y - rel_y = y - self.rely - self.parent.show_aty + rel_y = y - self.rely - self.parent.show_aty rel_x = x - self.relx - self.parent.show_atx return (mouse_id, rel_x, rel_y, z, bstate) - - #def when_parent_changes_value(self): - # Can be called by forms when they chage their value. - #pass + + # def when_parent_changes_value(self): + # Can be called by forms when they chage their value. + # pass + + def _safe_to_exit(self): + return True + + # if this isn't overridden, we should check if it's a TitleText or TitleNumeric + def safe_to_exit(self): + theclass = self.__class__ + try: + parent_widget = getattr(self, "parent_widget") + except AttributeError: + return True + try: + ste = getattr(parent_widget, "safe_to_exit") + except AttributeError: + return True + return ste() + + # def safe_to_exit(self): + # return True + + def _test_safe_to_exit(self): + if self._safe_to_exit() and self.safe_to_exit(): + return True + else: + return False def when_check_value_changed(self): "Check whether the widget's value has changed and call when_valued_edited if so." @@ -648,16 +696,16 @@ def when_check_value_changed(self): self.parent_widget.when_value_edited() self.parent_widget._internal_when_value_edited() return True - + def _internal_when_value_edited(self): if self.value_changed_callback: return self.value_changed_callback(widget=self) - + def when_value_edited(self): """Called when the user edits the value of the widget. Will usually also be called the first time that the user edits the widget.""" pass - + def when_check_cursor_moved(self): if hasattr(self, 'cursor_line'): cursor = self.cursor_line @@ -677,7 +725,7 @@ def when_check_cursor_moved(self): self.when_cursor_moved() if hasattr(self, 'parent_widget'): self.parent_widget.when_cursor_moved() - + def when_cursor_moved(self): "Called when the cursor moves" pass @@ -692,17 +740,17 @@ def safe_string(self, this_string): on python2 """ try: - if not this_string: + if not this_string: return "" - #this_string = str(this_string) + # this_string = str(this_string) # In python 3 - #if sys.version_info[0] >= 3: + # if sys.version_info[0] >= 3: # return this_string.replace('\n', ' ') if self.__class__._SAFE_STRING_STRIPS_NL == True: rtn_value = this_string.replace('\n', ' ') else: rtn_value = this_string - + # Does the terminal want ascii? if self._force_ascii: if isinstance(rtn_value, bytes): @@ -717,7 +765,7 @@ def safe_string(self, this_string): # even on python3, in this case, we want a string that # contains only ascii chars - but in unicode, so: rtn_value = rtn_value.encode('ascii', 'replace').decode() - return rtn_value + return rtn_value else: return rtn_value.encode('ascii', 'replace') return rtn_value @@ -732,7 +780,7 @@ def safe_string(self, this_string): # Python2.6 rtn_value = rtn_value.decode(self.encoding, 'replace') if sys.version_info[0] >= 3: - return rtn_value + return rtn_value else: return rtn_value.encode('utf-8', 'replace') else: @@ -743,7 +791,7 @@ def safe_string(self, this_string): raise else: return "*ERROR DISPLAYING STRING*" - + def safe_filter(self, this_string): try: this_string = this_string.decode(self.encoding, 'replace') @@ -757,47 +805,55 @@ def safe_filter(self, this_string): pass s = [] for cha in this_string.replace('\n', ' '): - #if curses.ascii.isprint(cha): + # if curses.ascii.isprint(cha): # s.append(cha) - #else: + # else: # s.append('?') try: s.append(str(cha)) except: s.append('?') s = ''.join(s) - + self._safe_filter_value_cache = (this_string, s) - + return s - - #s = '' - #for cha in this_string.replace('\n', ''): + + # s = '' + # for cha in this_string.replace('\n', ''): # try: # s += cha.encode('ascii') # except: # s += '?' - #return s - + # return s + + class DummyWidget(Widget): "This widget is invisible and does nothing. Which is sometimes important." + def __init__(self, screen, *args, **keywords): super(DummyWidget, self).__init__(screen, *args, **keywords) self.height = 0 self.widget = 0 self.parent = screen + def display(self): pass + def update(self, clear=False): pass + def set_editable(self, value): - if value: self._is_editable = True - else: self._is_editable = False + if value: + self._is_editable = True + else: + self._is_editable = False + def get_editable(self): - return(self._is_editable) + return (self._is_editable) + def clear(self, usechar=' '): pass - def calculate_area_needed(self): - return 0,0 - + def calculate_area_needed(self): + return 0, 0 diff --git a/setup.py b/setup.py index 3fc779c..3f19dc2 100755 --- a/setup.py +++ b/setup.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -from distutils.core import setup +from setuptools import setup setup( name="npyscreen", - version="4.9.1", + version="4.11.1", description="Writing user interfaces without all that ugly mucking about in hyperspace", author="Nicholas Cole", author_email="n@npcole.com", @@ -28,7 +28,7 @@ There is a very wide variety of default widgets - everything from simple text fields to more complex tree and grid views. -I have used versions of this library for private scripts and small applications for around ten years. As a result, it is fairly mature. +I have used versions of this library for private scripts and small applications for around ten years. As a result, it is fairly mature. Documentation is online at http://npyscreen.readthedocs.org @@ -39,12 +39,23 @@ *Latest Changes*: +Version 4.11.1: Merged pull request #120 with code to access curses screen directly + +Version 4.11.0: Cleaned up a couple of syntax errors, added Numericfield (in addition to Textfield), and corrected behavior of safe_to_exit() so that it works with TitleText and TitleNumeric classes. + +Version 4.10.5: Merged in bug-fixes and enhancements suggested by Nathan Lewis. + +Version 4.10.0: All widgets have a safe_to_exit() method that will be called +(at least by the default handlers) before exiting a widget. Users can +perform input verification functions here. Return True if the widget should +allow exiting, or False if it should not. Various minor bug-fixes. + Version 4.9.1: minor change to Multiline widgets to make custom versions easier (final widget value is never set to MORE_LABEL). Version 4.9: new function blank_terminal() added. (User request) Improvements to facilities for writing unit tests. (user request) Bugs related to hidden widgets fixed. -Version 4.8.7 New methods added to the Multiline class to assist widget authors. +Version 4.8.7 New methods added to the Multiline class to assist widget authors. Version 4.8.6 MultiLineAction widgets no longer raise an exception if empty @@ -84,10 +95,10 @@ -Version 4.6.0 introduces a way to define a callback for when widget values change. The help system has been improved by minor interface changes. +Version 4.6.0 introduces a way to define a callback for when widget values change. The help system has been improved by minor interface changes. Both of these were user suggestions. Thank you to those who suggested them. -Version 4.5.0 introduces a greater control of colour for certain widgets. +Version 4.5.0 introduces a greater control of colour for certain widgets. Version 4.4.0 introduces the new tree class TreeData. This is a new version of NPSTreeData that follows PEP 8 conventions for method names. NPSTreeData is now deprecated. The form class ActionFormMinimal has been added at a user request. This is a special version of ActionFrom that only features an OK button by default. @@ -95,32 +106,32 @@ Version 4.3.5 introduces the new classes SliderNoLabel, TitleSliderNoLabel, SliderPercent, TitleSliderPercent. -Version 4.3.4 Minor bugfixes. The notify functions and ActionPopups derived from them now use the ActionFormV2 widgets. +Version 4.3.4 Minor bugfixes. The notify functions and ActionPopups derived from them now use the ActionFormV2 widgets. This change should not affect existing code, but let me know if there are problems. Version 4.3.0 allows you to specify a negative value for rely or relx when creating a widget. This will cause the widget to be aligned -with the bottom or right of the screen. The new method *set_relyx(y, x)* can be used to set the position of the widget on the Form if you never need to do that manually. +with the bottom or right of the screen. The new method *set_relyx(y, x)* can be used to set the position of the widget on the Form if you never need to do that manually. The classes *ActionFormV2*, *ActionFormExpandedV2* and *ActionFormV2WithMenus* have been introduced. -These feature cleaner code that should be easier to subclass. +These feature cleaner code that should be easier to subclass. The *ButtonPress* class can now be created with the argument *when_pressed_function=None*, which can be used in place of overriding the *whenPressed* method. Note that this might create a reference cycle -within your application, so use with care. +within your application, so use with care. Version 4.2.0 introduces the ability of Grid widgets to highlight the whole line that the cursor is on (user request). -Version 4.1.0 introduces support for hvc consoles (thanks to wu.fuheng@********* for the bug report). Title widgets can now define a when_cursor_moved() method directly -on themselves that will be called as expected by the contained entry_widget during its edit loop (user request). +Version 4.1.0 introduces support for hvc consoles (thanks to wu.fuheng@********* for the bug report). Title widgets can now define a when_cursor_moved() method directly +on themselves that will be called as expected by the contained entry_widget during its edit loop (user request). Version 4.0.0 introduces a new version scheme. Due to a packaging error in the 3.0 release series some users were having problems obtaining the latest version. This is most easily fixed with a new major version release. -Version 3.10 MultiLineEditable, MultiLineEditableTitle, MultiLineEditableBoxed classes added, allowing the user to edit lists of items. -See EXAMPLE-MultilineEditable for an example. +Version 3.10 MultiLineEditable, MultiLineEditableTitle, MultiLineEditableBoxed classes added, allowing the user to edit lists of items. +See EXAMPLE-MultilineEditable for an example. Version 3.6 Title.. widgets should now resize properly. Menu items can now be specified with arguments and keywords. @@ -132,13 +143,13 @@ Version 3.3 and the subsequent minor releases fix some bugs, mainly related to changes caused by allowing resized forms. -Version 3.2 adds CheckboxBare - a checkbox without a label. Added at user request. +Version 3.2 adds CheckboxBare - a checkbox without a label. Added at user request. -Version 3.0 *IMPORTANT* The version number has changed to version 3.0. -This is because newer versions of pip distinguish between pre-release and released versions, -and this will allow more flexibility in future releases. A version '2.0' might have caused confusion at this stage. +Version 3.0 *IMPORTANT* The version number has changed to version 3.0. +This is because newer versions of pip distinguish between pre-release and released versions, +and this will allow more flexibility in future releases. A version '2.0' might have caused confusion at this stage. -Version 3.0 fixes the specification of max_width values for titled widgets (Thanks to Phil Rich for the bug report). +Version 3.0 fixes the specification of max_width values for titled widgets (Thanks to Phil Rich for the bug report). Please report any further problems. Version 2.0pre90 introduces a new BufferPager and TitleBufferPager class. (User request, suggested by dennis@wsec.be) @@ -150,7 +161,7 @@ Version 2.0pre87 Updates the documentation and contains various bug fixes. -Version 2.0pre85 and 2.0pre86 are both bugfix releases. +Version 2.0pre85 and 2.0pre86 are both bugfix releases. Version 2.0pre84 introduces an experimental system for editing lists of options. See documentation for details. @@ -180,7 +191,7 @@ Version 2.0pre70 introduces the MLTreeMultiSelect class. -Version 2.0pre69 fixes and tidies up some of the new tree classes. There is an API change assocatied with this, noted in the documentation, though backward compatibility should have been maintained. +Version 2.0pre69 fixes and tidies up some of the new tree classes. There is an API change assocatied with this, noted in the documentation, though backward compatibility should have been maintained. Version 2.0pre68 setting a form's .editing attribute to False now causes it to exit immediately, even if a widget is still being edited. @@ -194,7 +205,7 @@ Version 2.0pre64 extends multi-page support and includes revision to the documentation. -Version 2.0pre63 adds initial support for multi-page forms. See documentation on the +Version 2.0pre63 adds initial support for multi-page forms. See documentation on the FormMultiPage class for details. Version 2.0pre57 fixes color support - it should now be possible to display