├── .gitignore ├── CHANGELOG ├── EXAMPLE-BOX.py ├── EXAMPLE-BoWTheme.py ├── EXAMPLE-Colourful.py ├── EXAMPLE-ManagedApp.py ├── EXAMPLE-Menus.py ├── EXAMPLE-MultilineEditable.py ├── EXAMPLE-OptionList.py ├── EXAMPLE-ThemeTransparent.py ├── EXAMPLE-WIDGETLIST.py ├── EXAMPLE-address-book.py ├── EXAMPLE-annotation.py ├── EXAMPLE-basic-editor.py ├── EXAMPLE-fuller.py ├── EXAMPLE-multipage_experimental.py ├── EXAMPLE-multiple-screens.py ├── EXAMPLE-mutt-like-annotated.py ├── EXAMPLE-mutt-like.py ├── EXAMPLE-muttactive.py ├── EXAMPLE-muttactivetraditional.py ├── EXAMPLE-nocolor.py ├── EXAMPLE-unicode-py3.py ├── EXAMPLE-unicode.py ├── EXAMPLE-waiting.py ├── EXAMPLE.py ├── LICENCE ├── PKG-INFO ├── README.md ├── docs ├── Makefile ├── make.bat └── sphinx ├── npyscreen ├── __init__.py ├── apNPSApplication.py ├── apNPSApplicationAdvanced.py ├── apNPSApplicationEvents.py ├── apNPSApplicationManaged.py ├── apOptions.py ├── compatibility_code │ ├── __init__.py │ ├── npysNPSTree.py │ └── oldtreeclasses.py ├── eveventhandler.py ├── fmActionForm.py ├── fmActionFormV2.py ├── fmFileSelector.py ├── fmForm.py ├── fmFormMultiPage.py ├── fmFormMutt.py ├── fmFormMuttActive.py ├── fmFormWithMenus.py ├── fmPopup.py ├── fm_form_edit_loop.py ├── globals.py ├── muMenu.py ├── muNewMenu.py ├── npysGlobalOptions.py ├── npysNPSFilteredData.py ├── npysThemeManagers.py ├── npysThemes.py ├── npysTree.py ├── npyspmfuncs.py ├── npyssafewrapper.py ├── proto_fm_screen_area.py ├── stdfmemail.py ├── utilNotify.py ├── util_viewhelp.py ├── wgFormControlCheckbox.py ├── wgNMenuDisplay.py ├── wgannotatetextbox.py ├── wgautocomplete.py ├── wgboxwidget.py ├── wgbutton.py ├── wgcheckbox.py ├── wgcombobox.py ├── wgdatecombo.py ├── wgeditmultiline.py ├── wgfilenamecombo.py ├── wggrid.py ├── wggridcoltitles.py ├── wgmonthbox.py ├── wgmultiline.py ├── wgmultilineeditable.py ├── wgmultilinetree.py ├── wgmultilinetreeselectable.py ├── wgmultiselect.py ├── wgmultiselecttree.py ├── wgpassword.py ├── wgselectone.py ├── wgslider.py ├── wgtextbox.py ├── wgtextbox_controlchrs.py ├── wgtextboxunicode.py ├── wgtexttokens.py ├── wgtitlefield.py ├── wgwidget.py └── wgwidget_proto.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | __pycache__/ 3 | *.pyc -------------------------------------------------------------------------------- /EXAMPLE-BOX.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import npyscreen 5 | #npyscreen.disableColor() 6 | class TestApp(npyscreen.NPSApp): 7 | def main(self): 8 | F = npyscreen.Form(name = "Welcome to Npyscreen",) 9 | t = F.add(npyscreen.BoxBasic, name = "Basic Box:", max_width=30, relx=2, max_height=3) 10 | t.footer = "This is a footer" 11 | 12 | t1 = F.add(npyscreen.BoxBasic, name = "Basic Box:", rely=2, relx=32, 13 | max_width=30, max_height=3) 14 | 15 | 16 | t2 = F.add(npyscreen.BoxTitle, name="Box Title:", max_height=6) 17 | t3 = F.add(npyscreen.BoxTitle, name="Box Title2:", max_height=6, 18 | scroll_exit = True, 19 | contained_widget_arguments={ 20 | 'color': "WARNING", 21 | 'widgets_inherit_color': True,} 22 | ) 23 | 24 | 25 | t2.entry_widget.scroll_exit = True 26 | t2.values = ["Hello", 27 | "This is a Test", 28 | "This is another test", 29 | "And here is another line", 30 | "And here is another line, which is really very long. abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", 31 | "And one more."] 32 | t3.values = t2.values 33 | 34 | 35 | F.edit() 36 | 37 | 38 | if __name__ == "__main__": 39 | App = TestApp() 40 | App.run() 41 | -------------------------------------------------------------------------------- /EXAMPLE-BoWTheme.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | class TestApp(npyscreen.NPSApp): 4 | def main(self): 5 | # These lines create the form and populate it with widgets. 6 | # A fairly complex screen in only 8 or so lines of code - a line for each control. 7 | npyscreen.setTheme(npyscreen.Themes.BlackOnWhiteTheme) 8 | F = npyscreen.ActionFormWithMenus(name = "Welcome to Npyscreen",) 9 | t = F.add(npyscreen.TitleText, name = "Text:", ) 10 | fn = F.add(npyscreen.TitleFilename, name = "Filename:") 11 | dt = F.add(npyscreen.TitleDateCombo, name = "Date:") 12 | s = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider") 13 | ml= F.add(npyscreen.MultiLineEdit, 14 | value = """try typing here! Mutiline text, press ^R to reformat.\n""", 15 | max_height=5, rely=9) 16 | ms= F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One", 17 | values = ["Option1","Option2","Option3"], scroll_exit=True) 18 | 19 | # This lets the user play with the Form. 20 | F.edit() 21 | 22 | 23 | if __name__ == "__main__": 24 | App = TestApp() 25 | App.run() 26 | -------------------------------------------------------------------------------- /EXAMPLE-Colourful.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | class TestApp(npyscreen.NPSApp): 4 | def main(self): 5 | # These lines create the form and populate it with widgets. 6 | # A fairly complex screen in only 8 or so lines of code - a line for each control. 7 | npyscreen.setTheme(npyscreen.Themes.ColorfulTheme) 8 | F = npyscreen.ActionFormWithMenus(name = "Welcome to Npyscreen",) 9 | t = F.add(npyscreen.TitleText, name = "Text:", ) 10 | fn = F.add(npyscreen.TitleFilename, name = "Filename:") 11 | dt = F.add(npyscreen.TitleDateCombo, name = "Date:") 12 | s = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider") 13 | ml= F.add(npyscreen.MultiLineEdit, 14 | value = """try typing here! Mutiline text, press ^R to reformat.\n""", 15 | max_height=5, rely=9) 16 | ms= F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One", 17 | values = ["Option1","Option2","Option3"], scroll_exit=True) 18 | ms2= F.add(npyscreen.TitleMultiSelect, max_height=4, value = [1,], name="Pick Several", 19 | values = ["Option1","Option2","Option3"], scroll_exit=True) 20 | 21 | # This lets the user play with the Form. 22 | F.edit() 23 | 24 | 25 | if __name__ == "__main__": 26 | App = TestApp() 27 | App.run() 28 | -------------------------------------------------------------------------------- /EXAMPLE-ManagedApp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | ExampleManaged.py 5 | 6 | Created by Nicholas Cole on 2007-02-22. 7 | """ 8 | 9 | import npyscreen, curses 10 | 11 | class MyTestApp(npyscreen.NPSAppManaged): 12 | def onStart(self): 13 | self.registerForm("MAIN", MainForm()) 14 | 15 | class MainForm(npyscreen.Form): 16 | def create(self): 17 | self.add(npyscreen.TitleText, name = "Text:", value= "Press Escape to quit application" ) 18 | self.how_exited_handers[npyscreen.wgwidget.EXITED_ESCAPE] = self.exit_application 19 | 20 | def exit_application(self): 21 | curses.beep() 22 | self.parentApp.setNextForm(None) 23 | self.editing = False 24 | 25 | def main(): 26 | TA = MyTestApp() 27 | TA.run() 28 | 29 | 30 | if __name__ == '__main__': 31 | main() 32 | 33 | -------------------------------------------------------------------------------- /EXAMPLE-Menus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import npyscreen, curses 5 | 6 | class MyTestApp(npyscreen.NPSAppManaged): 7 | def onStart(self): 8 | self.registerForm("MAIN", MainForm()) 9 | 10 | class MainForm(npyscreen.FormWithMenus): 11 | def create(self): 12 | self.add(npyscreen.TitleText, name = "Text:", value= "Just some text." ) 13 | self.how_exited_handers[npyscreen.wgwidget.EXITED_ESCAPE] = self.exit_application 14 | 15 | # The menus are created here. 16 | self.m1 = self.add_menu(name="Main Menu", shortcut="^M") 17 | self.m1.addItemsFromList([ 18 | ("Display Text", self.whenDisplayText, None, None, ("some text",)), 19 | ("Just Beep", self.whenJustBeep, "e"), 20 | ("Exit Application", self.exit_application, "é"), 21 | ]) 22 | 23 | self.m2 = self.add_menu(name="Another Menu", shortcut="b",) 24 | self.m2.addItemsFromList([ 25 | ("Just Beep", self.whenJustBeep), 26 | ]) 27 | 28 | self.m3 = self.m2.addNewSubmenu("A sub menu", "^F") 29 | self.m3.addItemsFromList([ 30 | ("Just Beep", self.whenJustBeep), 31 | ]) 32 | 33 | def whenDisplayText(self, argument): 34 | npyscreen.notify_confirm(argument) 35 | 36 | def whenJustBeep(self): 37 | curses.beep() 38 | 39 | def exit_application(self): 40 | curses.beep() 41 | self.parentApp.setNextForm(None) 42 | self.editing = False 43 | self.parentApp.switchFormNow() 44 | 45 | def main(): 46 | TA = MyTestApp() 47 | TA.run() 48 | 49 | 50 | if __name__ == '__main__': 51 | main() 52 | 53 | -------------------------------------------------------------------------------- /EXAMPLE-MultilineEditable.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import npyscreen 4 | #npyscreen.disableColor() 5 | 6 | 7 | class TestApp(npyscreen.NPSApp): 8 | def main(self): 9 | value_list = [ 10 | "This is the first", 11 | "This is the second", 12 | "This is the third", 13 | "This is the fourth", 14 | ] 15 | F = npyscreen.Form(name = "Welcome to Npyscreen",) 16 | t = F.add(npyscreen.MultiLineEditableBoxed, 17 | max_height=20, 18 | name='List of Values', 19 | footer="Press i or o to insert values", 20 | values=value_list, 21 | slow_scroll=False) 22 | 23 | # This lets the user play with the Form. 24 | F.edit() 25 | 26 | if __name__ == "__main__": 27 | App = TestApp() 28 | App.run() 29 | -------------------------------------------------------------------------------- /EXAMPLE-OptionList.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | 5 | # The system here is an experimental one. See documentation for details. 6 | 7 | 8 | 9 | 10 | import npyscreen 11 | class TestApp(npyscreen.NPSApp): 12 | def main(self): 13 | Options = npyscreen.OptionList() 14 | 15 | # just for convenience so we don't have to keep writing Options.options 16 | options = Options.options 17 | 18 | options.append(npyscreen.OptionFreeText('FreeText', value='', documentation="This is some documentation.")) 19 | options.append(npyscreen.OptionMultiChoice('Multichoice', choices=['Choice 1', 'Choice 2', 'Choice 3'])) 20 | options.append(npyscreen.OptionFilename('Filename', )) 21 | options.append(npyscreen.OptionDate('Date', )) 22 | options.append(npyscreen.OptionMultiFreeText('Multiline Text', value='')) 23 | options.append(npyscreen.OptionMultiFreeList('Multiline List')) 24 | 25 | try: 26 | Options.reload_from_file('/tmp/test') 27 | except FileNotFoundError: 28 | pass 29 | 30 | F = npyscreen.Form(name = "Welcome to Npyscreen",) 31 | 32 | ms = F.add(npyscreen.OptionListDisplay, name="Option List", 33 | values = options, 34 | scroll_exit=True, 35 | max_height=None) 36 | 37 | F.edit() 38 | 39 | Options.write_to_file('/tmp/test') 40 | 41 | if __name__ == "__main__": 42 | App = TestApp() 43 | App.run() 44 | -------------------------------------------------------------------------------- /EXAMPLE-ThemeTransparent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import npyscreen 5 | #npyscreen.disableColor() 6 | class TestApp(npyscreen.NPSApp): 7 | def main(self): 8 | npyscreen.setTheme(npyscreen.Themes.TransparentThemeDarkText) 9 | # These lines create the form and populate it with widgets. 10 | # A fairly complex screen in only 8 or so lines of code - a line for each control. 11 | F = npyscreen.Form(name = "Welcome to Npyscreen",) 12 | t = F.add(npyscreen.TitleText, name = "Text:",) 13 | fn = F.add(npyscreen.TitleFilename, name = "Filename:") 14 | dt = F.add(npyscreen.TitleDateCombo, name = "Date:") 15 | s = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider") 16 | ml= F.add(npyscreen.MultiLineEdit, 17 | value = """try typing here!\nMutiline text, press ^R to reformat.\n""", 18 | max_height=5, rely=9) 19 | ms= F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One", 20 | values = ["Option1","Option2","Option3"], scroll_exit=True) 21 | ms2= F.add(npyscreen.TitleMultiSelect, max_height =-2, value = [1,], name="Pick Several", 22 | values = ["Option1","Option2","Option3"], scroll_exit=True) 23 | 24 | # This lets the user play with the Form. 25 | F.edit() 26 | 27 | 28 | if __name__ == "__main__": 29 | App = TestApp() 30 | App.run() 31 | -------------------------------------------------------------------------------- /EXAMPLE-WIDGETLIST.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | 4 | 5 | # npyscreen.disableColor() 6 | class ActionFormExample(npyscreen.ActionForm): 7 | initialWidgets = [ 8 | (npyscreen.TitleText, {'w_id': 'TextLine', 'name': "Text:"}), 9 | (npyscreen.TitleFilename, {'name': "Filename:"}), 10 | (npyscreen.TitleFilename, {'name': "Filename:"}), 11 | (npyscreen.TitleDateCombo, {'name': "Date:"}), 12 | (npyscreen.TitleSlider, {'out_of': 12, 'name': "Slider"}), 13 | (npyscreen.MultiLineEdit, 14 | {'value': """try typing here!\nMutiline text, press ^R to reformat.\n""", 'max_height': 5, }) 15 | ] 16 | 17 | 18 | class TestApp(npyscreen.NPSApp): 19 | def main(self): 20 | # These lines create the form and populate it with widgets. 21 | # A fairly complex screen in only 8 or so lines of code - a line for each control. 22 | F = ActionFormExample(name="Welcome to Npyscreen", ) 23 | # t = F.add(npyscreen.TitleText, name = "Text:", ) 24 | # fn = F.add(npyscreen.TitleFilename, name = "Filename:") 25 | # dt = F.add(npyscreen.TitleDateCombo, name = "Date:") 26 | # s = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider") 27 | # ml= F.add(npyscreen.MultiLineEdit, 28 | # value = """try typing here!\nMutiline text, press ^R to reformat.\n""", 29 | # max_height=5, rely=9) 30 | # ms= F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One", 31 | # values = ["Option1","Option2","Option3"], scroll_exit=True) 32 | # ms2= F.add(npyscreen.TitleMultiSelect, max_height=4, value = [1,], name="Pick Several", 33 | # values = ["Option1","Option2","Option3"], scroll_exit=True) 34 | # 35 | # This lets the user play with the Form. 36 | F.edit() 37 | return F.get_widget('TextLine').value 38 | 39 | 40 | if __name__ == "__main__": 41 | App = TestApp() 42 | App.run() 43 | -------------------------------------------------------------------------------- /EXAMPLE-address-book.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | import sqlite3 4 | 5 | 6 | class AddressDatabase(object): 7 | def __init__(self, filename="example-addressbook.db"): 8 | self.dbfilename = filename 9 | db = sqlite3.connect(self.dbfilename) 10 | c = db.cursor() 11 | c.execute( 12 | "CREATE TABLE IF NOT EXISTS records\ 13 | ( record_internal_id INTEGER PRIMARY KEY, \ 14 | last_name TEXT, \ 15 | other_names TEXT, \ 16 | email_address TEXT \ 17 | )" \ 18 | ) 19 | db.commit() 20 | c.close() 21 | 22 | def add_record(self, last_name = '', other_names='', email_address=''): 23 | db = sqlite3.connect(self.dbfilename) 24 | c = db.cursor() 25 | c.execute('INSERT INTO records(last_name, other_names, email_address) \ 26 | VALUES(?,?,?)', (last_name, other_names, email_address)) 27 | db.commit() 28 | c.close() 29 | 30 | def update_record(self, record_id, last_name = '', other_names='', email_address=''): 31 | db = sqlite3.connect(self.dbfilename) 32 | c = db.cursor() 33 | c.execute('UPDATE records set last_name=?, other_names=?, email_address=? \ 34 | WHERE record_internal_id=?', (last_name, other_names, email_address, \ 35 | record_id)) 36 | db.commit() 37 | c.close() 38 | 39 | def delete_record(self, record_id): 40 | db = sqlite3.connect(self.dbfilename) 41 | c = db.cursor() 42 | c.execute('DELETE FROM records where record_internal_id=?', (record_id,)) 43 | db.commit() 44 | c.close() 45 | 46 | def list_all_records(self, ): 47 | db = sqlite3.connect(self.dbfilename) 48 | c = db.cursor() 49 | c.execute('SELECT * from records') 50 | records = c.fetchall() 51 | c.close() 52 | return records 53 | 54 | def get_record(self, record_id): 55 | db = sqlite3.connect(self.dbfilename) 56 | c = db.cursor() 57 | c.execute('SELECT * from records WHERE record_internal_id=?', (record_id,)) 58 | records = c.fetchall() 59 | c.close() 60 | return records[0] 61 | 62 | class RecordList(npyscreen.MultiLineAction): 63 | def __init__(self, *args, **keywords): 64 | super(RecordList, self).__init__(*args, **keywords) 65 | self.add_handlers({ 66 | "^A": self.when_add_record, 67 | "^D": self.when_delete_record 68 | }) 69 | 70 | def display_value(self, vl): 71 | return "%s, %s" % (vl[1], vl[2]) 72 | 73 | def actionHighlighted(self, act_on_this, keypress): 74 | self.parent.parentApp.getForm('EDITRECORDFM').value =act_on_this[0] 75 | self.parent.parentApp.switchForm('EDITRECORDFM') 76 | 77 | def when_add_record(self, *args, **keywords): 78 | self.parent.parentApp.getForm('EDITRECORDFM').value = None 79 | self.parent.parentApp.switchForm('EDITRECORDFM') 80 | 81 | def when_delete_record(self, *args, **keywords): 82 | self.parent.parentApp.myDatabase.delete_record(self.values[self.cursor_line][0]) 83 | self.parent.update_list() 84 | 85 | 86 | class RecordListDisplay(npyscreen.FormMutt): 87 | MAIN_WIDGET_CLASS = RecordList 88 | def beforeEditing(self): 89 | self.update_list() 90 | 91 | def update_list(self): 92 | self.wMain.values = self.parentApp.myDatabase.list_all_records() 93 | self.wMain.display() 94 | 95 | 96 | class EditRecord(npyscreen.ActionForm): 97 | def create(self): 98 | self.value = None 99 | self.wgLastName = self.add(npyscreen.TitleText, name = "Last Name:",) 100 | self.wgOtherNames = self.add(npyscreen.TitleText, name = "Other Names:") 101 | self.wgEmail = self.add(npyscreen.TitleText, name = "Email:") 102 | 103 | def beforeEditing(self): 104 | if self.value: 105 | record = self.parentApp.myDatabase.get_record(self.value) 106 | self.name = "Record id : %s" % record[0] 107 | self.record_id = record[0] 108 | self.wgLastName.value = record[1] 109 | self.wgOtherNames.value = record[2] 110 | self.wgEmail.value = record[3] 111 | else: 112 | self.name = "New Record" 113 | self.record_id = '' 114 | self.wgLastName.value = '' 115 | self.wgOtherNames.value = '' 116 | self.wgEmail.value = '' 117 | 118 | def on_ok(self): 119 | if self.record_id: # We are editing an existing record 120 | self.parentApp.myDatabase.update_record(self.record_id, 121 | last_name=self.wgLastName.value, 122 | other_names = self.wgOtherNames.value, 123 | email_address = self.wgEmail.value, 124 | ) 125 | else: # We are adding a new record. 126 | self.parentApp.myDatabase.add_record(last_name=self.wgLastName.value, 127 | other_names = self.wgOtherNames.value, 128 | email_address = self.wgEmail.value, 129 | ) 130 | self.parentApp.switchFormPrevious() 131 | 132 | def on_cancel(self): 133 | self.parentApp.switchFormPrevious() 134 | 135 | 136 | 137 | 138 | class AddressBookApplication(npyscreen.NPSAppManaged): 139 | def onStart(self): 140 | self.myDatabase = AddressDatabase() 141 | self.addForm("MAIN", RecordListDisplay) 142 | self.addForm("EDITRECORDFM", EditRecord) 143 | 144 | if __name__ == '__main__': 145 | myApp = AddressBookApplication() 146 | myApp.run() -------------------------------------------------------------------------------- /EXAMPLE-annotation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | 4 | # This example shows how to display the contents of 5 | # a dictionary to a user. 6 | 7 | class KeyValueLine(npyscreen.AnnotateTextboxBase): 8 | ANNOTATE_WIDTH = 20 9 | def getAnnotationAndColor(self): 10 | if self.value: 11 | return (self.value[0][0:self.ANNOTATE_WIDTH-2], self.annotation_color) 12 | else: 13 | return ('', self.annotation_color) 14 | 15 | def display_value(self, vl): 16 | if vl: 17 | return self.safe_string(str(vl[1])) 18 | else: 19 | return '' 20 | 21 | class KeyValueMultiline(npyscreen.MultiLine): 22 | _contained_widgets = KeyValueLine 23 | def when_parent_changes_value(self): 24 | self.values = self.parent.value.items() 25 | 26 | def display_value(self, vl): 27 | # pass the real object to subwidgets 28 | return vl 29 | 30 | class MyForm(npyscreen.Form): 31 | def create(self): 32 | self.wgdisplay = self.add(KeyValueMultiline) 33 | 34 | class MyTestApp(npyscreen.NPSAppManaged): 35 | def onStart(self): 36 | mainform = self.addForm("MAIN", MyForm) 37 | 38 | test_dict= {} 39 | for i in range(10000): 40 | test_dict[str(i)] = 'test %s' % i 41 | 42 | 43 | # The following line is the one you want to replace, I suspect. 44 | #mainform.set_value(globals().copy()) 45 | mainform.set_value(test_dict) 46 | 47 | if __name__ == "__main__": 48 | MyTestApp().run() 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /EXAMPLE-basic-editor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | 4 | class EditorFormExample(npyscreen.FormMutt): 5 | MAIN_WIDGET_CLASS = npyscreen.MultiLineEdit 6 | 7 | class TestApp(npyscreen.NPSApp): 8 | def main(self): 9 | F = EditorFormExample() 10 | F.wStatus1.value = "Status Line " 11 | F.wStatus2.value = "Second Status Line " 12 | 13 | F.edit() 14 | 15 | 16 | if __name__ == "__main__": 17 | App = TestApp() 18 | App.run() 19 | -------------------------------------------------------------------------------- /EXAMPLE-fuller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | 4 | class TestApp(npyscreen.NPSApp): 5 | def main(self): 6 | # These lines create the form and populate it with widgets. 7 | # A fairly complex screen in only 8 or so lines of code - a line for each control. 8 | F = npyscreen.ActionFormWithMenus(name = "Welcome to Npyscreen",) 9 | f = F.add(npyscreen.TitleFixedText, name = "Fixed Text:" , value="This is fixed text") 10 | t = F.add(npyscreen.TitleText, name = "Text:", ) 11 | p = F.add(npyscreen.TitlePassword, name = "Password:") 12 | fn = F.add(npyscreen.TitleFilename, name = "Filename:") 13 | dt = F.add(npyscreen.TitleDateCombo, name = "Date:") 14 | cb = F.add(npyscreen.Checkbox, name = "A Checkbox") 15 | s = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider") 16 | ml= F.add(npyscreen.MultiLineEdit, 17 | value = """try typing here! Mutiline text, press ^R to reformat.\nPress ^X for automatically created list of menus""", 18 | max_height=5, rely=9) 19 | ms= F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One", 20 | values = ["Option1","Option2","Option3"], scroll_exit=True, width=30) 21 | ms2= F.add(npyscreen.MultiSelect, max_height=4, value = [1,], 22 | values = ["Option1","Option2","Option3"], scroll_exit=True, width=20) 23 | 24 | bn = F.add(npyscreen.MiniButton, name = "Button",) 25 | 26 | #gd = F.add(npyscreen.SimpleGrid, relx = 42, rely=15, width=20) 27 | gd = F.add(npyscreen.GridColTitles, relx = 42, rely=15, width=20, col_titles = ['1','2','3','4']) 28 | gd.values = [] 29 | for x in range(36): 30 | row = [] 31 | for y in range(x, x+36): 32 | row.append(y) 33 | gd.values.append(row) 34 | 35 | 36 | # This lets the user play with the Form. 37 | F.edit() 38 | 39 | if __name__ == "__main__": 40 | App = TestApp() 41 | App.run() 42 | -------------------------------------------------------------------------------- /EXAMPLE-multipage_experimental.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import npyscreen 5 | #npyscreen.disableColor() 6 | class TestApp(npyscreen.NPSApp): 7 | def main(self): 8 | # These lines create the form and populate it with widgets. 9 | # A fairly complex screen in only 8 or so lines of code - a line for each control. 10 | F = npyscreen.FormMultiPageActionWithMenus(name = "Welcome to Npyscreen",) 11 | t = F.add(npyscreen.TitleText, name = "Text:",) 12 | fn = F.add(npyscreen.TitleFilename, name = "Filename:") 13 | dt = F.add(npyscreen.TitleDateCombo, name = "Date:") 14 | s = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider") 15 | 16 | # The new page is created here. 17 | new_page = F.add_page() 18 | 19 | ml= F.add(npyscreen.MultiLineEdit, 20 | value = """try typing here!\nMutiline text, press ^R to reformat.\n""", 21 | max_height=5,) 22 | ms= F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One", 23 | values = ["Option1","Option2","Option3"], scroll_exit=True) 24 | ms2= F.add(npyscreen.TitleMultiSelect, max_height =-2, value = [1,], name="Pick Several", 25 | values = ["Option1","Option2","Option3"], scroll_exit=True) 26 | 27 | F.switch_page(0) 28 | 29 | def on_ok(): 30 | npyscreen.notify_confirm("OK Button Pressed!") 31 | F.on_ok = on_ok 32 | # This lets the user play with the Form. 33 | F.edit() 34 | 35 | 36 | 37 | if __name__ == "__main__": 38 | App = TestApp() 39 | App.run() 40 | -------------------------------------------------------------------------------- /EXAMPLE-multiple-screens.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen, curses 3 | 4 | class MyTestApp(npyscreen.NPSAppManaged): 5 | def onStart(self): 6 | # When Application starts, set up the Forms that will be used. 7 | # These two forms are persistent between each edit. 8 | self.addForm("MAIN", MainForm, name="Screen 1", color="IMPORTANT",) 9 | self.addForm("SECOND", MainForm, name="Screen 2", color="WARNING", ) 10 | # This one will be re-created each time it is edited. 11 | self.addFormClass("THIRD", MainForm, name="Screen 3", color="CRITICAL",) 12 | 13 | def onCleanExit(self): 14 | npyscreen.notify_wait("Goodbye!") 15 | 16 | def change_form(self, name): 17 | # Switch forms. NB. Do *not* call the .edit() method directly (which 18 | # would lead to a memory leak and ultimately a recursion error). 19 | # Instead, use the method .switchForm to change forms. 20 | self.switchForm(name) 21 | 22 | # By default the application keeps track of every form visited. 23 | # There's no harm in this, but we don't need it so: 24 | self.resetHistory() 25 | 26 | class MainForm(npyscreen.ActionForm): 27 | def create(self): 28 | self.add(npyscreen.TitleText, name = "Text:", value= "Press ^T to change screens" ) 29 | 30 | self.add_handlers({"^T": self.change_forms}) 31 | 32 | def on_ok(self): 33 | # Exit the application if the OK button is pressed. 34 | self.parentApp.switchForm(None) 35 | 36 | def change_forms(self, *args, **keywords): 37 | if self.name == "Screen 1": 38 | change_to = "SECOND" 39 | elif self.name == "Screen 2": 40 | change_to = "THIRD" 41 | else: 42 | change_to = "MAIN" 43 | 44 | # Tell the MyTestApp object to change forms. 45 | self.parentApp.change_form(change_to) 46 | 47 | 48 | 49 | 50 | def main(): 51 | TA = MyTestApp() 52 | TA.run() 53 | 54 | 55 | if __name__ == '__main__': 56 | main() 57 | -------------------------------------------------------------------------------- /EXAMPLE-mutt-like-annotated.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | 4 | class TestListClass(npyscreen.MultiLine): 5 | _contained_widgets = npyscreen.AnnotateTextboxBase 6 | 7 | class TestForm(npyscreen.FormMutt): 8 | MAIN_WIDGET_CLASS = TestListClass 9 | 10 | class TestApp(npyscreen.NPSApp): 11 | def main(self): 12 | F = TestForm() 13 | F.wStatus1.value = "Status Line " 14 | F.wStatus2.value = "Second Status Line " 15 | F.wMain.values = [str(x) for x in range(500)] 16 | 17 | F.edit() 18 | 19 | 20 | if __name__ == "__main__": 21 | App = TestApp() 22 | App.run() 23 | -------------------------------------------------------------------------------- /EXAMPLE-mutt-like.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | 4 | class TestApp(npyscreen.NPSApp): 5 | def main(self): 6 | F = npyscreen.FormMutt() 7 | F.add 8 | F.wStatus1.value = "Status Line " 9 | F.wStatus2.value = "Second Status Line " 10 | F.wMain.values = [str(x) for x in range(500)] 11 | 12 | F.edit() 13 | 14 | 15 | if __name__ == "__main__": 16 | App = TestApp() 17 | App.run() 18 | -------------------------------------------------------------------------------- /EXAMPLE-muttactive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | import curses 4 | 5 | 6 | class ActionControllerSearch(npyscreen.ActionControllerSimple): 7 | def create(self): 8 | self.add_action('^/.*', self.set_search, True) 9 | 10 | def set_search(self, command_line, widget_proxy, live): 11 | self.parent.value.set_filter(command_line[1:]) 12 | self.parent.wMain.values = self.parent.value.get() 13 | self.parent.wMain.display() 14 | 15 | 16 | class FmSearchActive(npyscreen.FormMuttActiveTraditional): 17 | ACTION_CONTROLLER = ActionControllerSearch 18 | 19 | class TestApp(npyscreen.NPSApp): 20 | def main(self): 21 | F = FmSearchActive() 22 | F.wStatus1.value = "Status Line " 23 | F.wStatus2.value = "Second Status Line " 24 | F.value.set_values([str(x) for x in range(500)]) 25 | F.wMain.values = F.value.get() 26 | 27 | F.edit() 28 | 29 | 30 | if __name__ == "__main__": 31 | App = TestApp() 32 | App.run() 33 | -------------------------------------------------------------------------------- /EXAMPLE-muttactivetraditional.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | import curses 4 | 5 | 6 | class ActionControllerSearch(npyscreen.ActionControllerSimple): 7 | def create(self): 8 | self.add_action('^/.*', self.set_search, True) 9 | 10 | def set_search(self, command_line, widget_proxy, live): 11 | self.parent.value.set_filter(command_line[1:]) 12 | self.parent.wMain.values = self.parent.value.get() 13 | self.parent.wMain.display() 14 | 15 | 16 | class FmSearchActive(npyscreen.FormMuttActiveTraditional): 17 | ACTION_CONTROLLER = ActionControllerSearch 18 | 19 | class TestApp(npyscreen.NPSApp): 20 | def main(self): 21 | F = FmSearchActive() 22 | F.wStatus1.value = "Status Line " 23 | F.wStatus2.value = "Second Status Line " 24 | F.value.set_values([str(x) for x in range(500)]) 25 | F.wMain.values = F.value.get() 26 | 27 | F.edit() 28 | 29 | 30 | if __name__ == "__main__": 31 | App = TestApp() 32 | App.run() 33 | -------------------------------------------------------------------------------- /EXAMPLE-nocolor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import npyscreen 5 | npyscreen.disableColor() 6 | class TestApp(npyscreen.NPSApp): 7 | def main(self): 8 | # These lines create the form and populate it with widgets. 9 | # A fairly complex screen in only 8 or so lines of code - a line for each control. 10 | F = npyscreen.Form(name = "Welcome to Npyscreen",) 11 | t = F.add(npyscreen.TitleText, name = "Text:",) 12 | fn = F.add(npyscreen.TitleFilename, name = "Filename:") 13 | dt = F.add(npyscreen.TitleDateCombo, name = "Date:") 14 | s = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider") 15 | ml= F.add(npyscreen.MultiLineEdit, 16 | value = """try typing here!\nMutiline text, press ^R to reformat.\n""", 17 | max_height=5, rely=9) 18 | ms= F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One", 19 | values = ["Option1","Option2","Option3"], scroll_exit=True) 20 | ms2= F.add(npyscreen.TitleMultiSelect, max_height =-2, value = [1,], name="Pick Several", 21 | values = ["Option1","Option2","Option3"], scroll_exit=True) 22 | 23 | # This lets the user play with the Form. 24 | F.edit() 25 | 26 | 27 | if __name__ == "__main__": 28 | App = TestApp() 29 | App.run() 30 | -------------------------------------------------------------------------------- /EXAMPLE-unicode-py3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import npyscreen 5 | #npyscreen.disableColor() 6 | class TestApp(npyscreen.NPSApp): 7 | def main(self): 8 | # These lines create the form and populate it with widgets. 9 | # A fairly complex screen in only 8 or so lines of code - a line for each control. 10 | F = npyscreen.ActionForm(name = "Welcome to Npyscreen",) 11 | t = F.add(npyscreen.TitleText, name = "Text:", value= "This is unicode: éé ≈∂ƒ© and it works on unicode terminals") 12 | fn = F.add(npyscreen.TitleFilename, name = "Filename:") 13 | dt = F.add(npyscreen.TitleDateCombo, name = "Date:") 14 | s = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider") 15 | ml= F.add(npyscreen.MultiLineEdit, 16 | value = """try typing here!\nMutiline text, press ^R to reformat.\n""", 17 | max_height=5, rely=9) 18 | cb = F.add(npyscreen.Checkbox, name = "A Checkbox é") 19 | bn = F.add(npyscreen.MiniButton, name = "Button Testing Testing éß",) 20 | 21 | ms= F.add(npyscreen.TitleSelectOne, max_height=2, value = [1,], name="Pick One", 22 | values = ["Option1","Option2","Option3"], scroll_exit=True) 23 | 24 | 25 | ms2= F.add(npyscreen.TitleMultiSelect, max_height=2, value = [1,], name="Pick Several", 26 | values = ["Option1","Option2","Option3"], scroll_exit=True) 27 | 28 | # This lets the user play with the Form. 29 | F.edit() 30 | 31 | 32 | if __name__ == "__main__": 33 | App = TestApp() 34 | App.run() 35 | App = TestApp() 36 | App.run() 37 | 38 | -------------------------------------------------------------------------------- /EXAMPLE-unicode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import npyscreen 5 | #npyscreen.disableColor() 6 | class TestApp(npyscreen.NPSApp): 7 | def main(self): 8 | # These lines create the form and populate it with widgets. 9 | # A fairly complex screen in only 8 or so lines of code - a line for each control. 10 | F = npyscreen.ActionForm(name = "Welcome to Npyscreen",) 11 | t = F.add(npyscreen.TitleText, name = "Text:", value= "This is unicode: éé ≈∂ƒ© and it works on unicode terminals") 12 | fn = F.add(npyscreen.TitleFilename, name = "Filename:") 13 | dt = F.add(npyscreen.TitleDateCombo, name = "Date:") 14 | s = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider") 15 | ml= F.add(npyscreen.MultiLineEdit, 16 | value = """try typing here!\nMutiline text, press ^R to reformat.\n""", 17 | max_height=5, rely=9) 18 | cb = F.add(npyscreen.Checkbox, name = "A Checkbox é") 19 | bn = F.add(npyscreen.MiniButton, name = "Button Testing Testing éß",) 20 | 21 | ms= F.add(npyscreen.TitleSelectOne, max_height=2, value = [1,], name="Pick One", 22 | values = ["Option1","Option2","Option3"], scroll_exit=True) 23 | 24 | 25 | ms2= F.add(npyscreen.TitleMultiSelect, max_height=2, value = [1,], name="Pick Several", 26 | values = ["Option1","Option2","Option3"], scroll_exit=True) 27 | 28 | # This lets the user play with the Form. 29 | F.edit() 30 | 31 | 32 | if __name__ == "__main__": 33 | App = TestApp() 34 | App.run() 35 | App = TestApp() 36 | App.run() 37 | 38 | -------------------------------------------------------------------------------- /EXAMPLE-waiting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import npyscreen 3 | import curses 4 | #npyscreen.disableColor() 5 | 6 | class BeepForm(npyscreen.ActionForm): 7 | def create(self, *args, **keywords): 8 | super(BeepForm, self).create(*args, **keywords) 9 | #self.keypress_timeout = 10 10 | 11 | def while_waiting(self): 12 | curses.beep() 13 | 14 | 15 | class TestApp(npyscreen.NPSApp): 16 | def while_waiting(self): 17 | curses.beep() 18 | 19 | def main(self): 20 | # These lines create the form and populate it with widgets. 21 | # A fairly complex screen in only 8 or so lines of code - a line for each control. 22 | self.keypress_timeout_default = 10 23 | F = BeepForm(parentApp=self, name = "Welcome to Npyscreen",) 24 | t = F.add(npyscreen.TitleText, name = "Text:", ) 25 | fn = F.add(npyscreen.TitleFilename, name = "Filename:") 26 | dt = F.add(npyscreen.TitleDateCombo, name = "Date:") 27 | s = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider", color='DANGER') 28 | ml= F.add(npyscreen.MultiLineEdit, 29 | value = """try typing here!\nMutiline text, press ^R to reformat.\n""", 30 | max_height=5, rely=9) 31 | ms= F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One", 32 | values = ["Option1","Option2","Option3"], scroll_exit=True) 33 | 34 | # This lets the user play with the Form. 35 | F.edit() 36 | 37 | 38 | if __name__ == "__main__": 39 | App = TestApp() 40 | App.run() 41 | -------------------------------------------------------------------------------- /EXAMPLE.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import npyscreen 5 | #npyscreen.disableColor() 6 | class TestApp(npyscreen.NPSApp): 7 | def main(self): 8 | # These lines create the form and populate it with widgets. 9 | # A fairly complex screen in only 8 or so lines of code - a line for each control. 10 | F = npyscreen.Form(name = "Welcome to Npyscreen") 11 | t = F.add(npyscreen.TitleText, name = "Text:", ) 12 | fn = F.add(npyscreen.TitleFilename, name = "Filename:",) 13 | fn2 = F.add(npyscreen.TitleFilenameCombo, name="Filename2:") 14 | dt = F.add(npyscreen.TitleDateCombo, name = "Date:") 15 | s = F.add(npyscreen.TitleSliderPercent, accuracy=0, out_of=12, name = "Slider") 16 | ml = F.add(npyscreen.MultiLineEdit, 17 | value = """try typing here!\nMutiline text, press ^R to reformat.\n""", 18 | max_height=5, rely=9) 19 | ms = F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One", 20 | values = ["Option1","Option2","Option3"], scroll_exit=True) 21 | ms2= F.add(npyscreen.TitleMultiSelect, max_height =-2, value = [1,], name="Pick Several", 22 | values = ["Option1","Option2","Option3"], scroll_exit=True) 23 | 24 | # This lets the user play with the Form. 25 | F.edit() 26 | 27 | if __name__ == "__main__": 28 | App = TestApp() 29 | App.run() 30 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004--2009, Nicholas P. S. Cole (n@npcole.com) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ''AS IS'' AND ANY 14 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NpyScreen 2 | Fork from https://code.google.com/archive/p/npyscreen/ 3 | 4 | ## Changes 5 | Added custom highlighting for widget and multiline 6 | Example: 7 | ```python 8 | import npyscreen 9 | class MyForm(npyscreen.FormBaseNew): 10 | def create(self): 11 | # BoxTitle used multiline 12 | obj = self.add(npyscreen.BoxTitle, name="test", custom_highlighting=True, values=["first line", "second line"]) 13 | 14 | # get colors 15 | color1 = self.theme_manager.findPair(self, 'DANGER') 16 | color2 = self.theme_manager.findPair(self, 'IMPORTANT') 17 | 18 | # fill line 19 | obj.entry_widget.highlighting_arr_color_data = [[color1,color1,color2],[color2,color1,color2,color2]] 20 | 21 | class App(npyscreen.StandardApp): 22 | def onStart(self): 23 | self.addForm("MAIN", MyForm) 24 | 25 | obj = App() 26 | obj.run() 27 | 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/npyscreen.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/npyscreen.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/npyscreen" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/npyscreen" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\npyscreen.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\npyscreen.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/sphinx: -------------------------------------------------------------------------------- 1 | You must give at least one requirement to install (see "pip help install") 2 | -------------------------------------------------------------------------------- /npyscreen/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from .globals import DEBUG, DISABLE_RESIZE_SYSTEM 4 | 5 | from .wgwidget import TEST_SETTINGS, ExhaustedTestInput, add_test_input_from_iterable, add_test_input_ch 6 | 7 | from .npyssafewrapper import wrapper, wrapper_basic 8 | 9 | from .npysThemeManagers import ThemeManager, disableColor, enableColor 10 | from . import npysThemes as Themes 11 | from .apNPSApplication import NPSApp 12 | from .apNPSApplicationManaged import NPSAppManaged 13 | from .proto_fm_screen_area import setTheme 14 | from .fmForm import FormBaseNew, Form, TitleForm, TitleFooterForm, SplitForm, FormExpanded, FormBaseNewExpanded, blank_terminal 15 | from .fmActionForm import ActionForm, ActionFormExpanded 16 | from .fmActionFormV2 import ActionFormV2, ActionFormExpandedV2, ActionFormMinimal 17 | from .fmFormWithMenus import FormWithMenus, ActionFormWithMenus, \ 18 | FormBaseNewWithMenus, SplitFormWithMenus, \ 19 | ActionFormV2WithMenus 20 | from .fmPopup import Popup, MessagePopup, ActionPopup, PopupWide, ActionPopupWide 21 | from .fmFormMutt import FormMutt, FormMuttWithMenus 22 | from .fmFileSelector import FileSelector, selectFile 23 | 24 | from .fmFormMuttActive import ActionControllerSimple, TextCommandBox, \ 25 | FormMuttActive, FormMuttActiveWithMenus 26 | from .fmFormMuttActive import FormMuttActiveTraditional, FormMuttActiveTraditionalWithMenus 27 | 28 | 29 | from .fmFormMultiPage import FormMultiPage, FormMultiPageAction,\ 30 | FormMultiPageActionWithMenus, FormMultiPageWithMenus 31 | 32 | from .npysNPSFilteredData import NPSFilteredDataBase, NPSFilteredDataList 33 | 34 | from .wgbutton import MiniButton 35 | from .wgbutton import MiniButtonPress 36 | from .wgbutton import MiniButton as Button 37 | from .wgbutton import MiniButtonPress as ButtonPress 38 | 39 | from .wgtextbox import Textfield, FixedText 40 | from .wgtitlefield import TitleText, TitleFixedText 41 | from .wgpassword import PasswordEntry, TitlePassword 42 | from .wgannotatetextbox import AnnotateTextboxBase 43 | from .wgannotatetextbox import AnnotateTextboxBaseRight 44 | 45 | from .wgslider import Slider, TitleSlider 46 | from .wgslider import SliderNoLabel, TitleSliderNoLabel 47 | from .wgslider import SliderPercent, TitleSliderPercent 48 | 49 | from .wgwidget import DummyWidget, NotEnoughSpaceForWidget 50 | from . import wgwidget as widget 51 | 52 | from .wgmultiline import MultiLine, Pager, TitleMultiLine, TitlePager, MultiLineAction, BufferPager, TitleBufferPager 53 | from .wgmultiselect import MultiSelect, TitleMultiSelect, MultiSelectFixed, \ 54 | TitleMultiSelectFixed, MultiSelectAction 55 | from .wgeditmultiline import MultiLineEdit 56 | from .wgcombobox import ComboBox, TitleCombo 57 | from .wgcheckbox import Checkbox, RoundCheckBox, CheckBoxMultiline, RoundCheckBoxMultiline, CheckBox, CheckboxBare 58 | from .wgFormControlCheckbox import FormControlCheckbox 59 | from .wgautocomplete import TitleFilename, Filename, Autocomplete 60 | from .muMenu import Menu 61 | from .wgselectone import SelectOne, TitleSelectOne 62 | from .wgdatecombo import DateCombo, TitleDateCombo 63 | 64 | from .npysTree import TreeData 65 | from .wgmultilinetree import MLTree, MLTreeAnnotated, MLTreeAction, MLTreeAnnotatedAction 66 | from .wgmultilinetreeselectable import MLTreeMultiSelect, TreeLineSelectable 67 | from .wgmultilinetreeselectable import MLTreeMultiSelectAnnotated, TreeLineSelectableAnnotated 68 | 69 | 70 | # The following are maintained for compatibility with old code only. ########################################## 71 | 72 | from .compatibility_code.oldtreeclasses import MultiLineTree, SelectOneTree 73 | from .compatibility_code.oldtreeclasses import MultiLineTreeNew, MultiLineTreeNewAction, TreeLine, TreeLineAnnotated # Experimental 74 | from .compatibility_code.oldtreeclasses import MultiLineTreeNewAnnotatedAction, MultiLineTreeNewAnnotated # Experimental 75 | from .compatibility_code.npysNPSTree import NPSTreeData 76 | 77 | # End compatibility. ########################################################################################### 78 | 79 | from .wgfilenamecombo import FilenameCombo, TitleFilenameCombo 80 | from .wgboxwidget import BoxBasic, BoxTitle 81 | from .wgmultiline import MultiLineActionWithShortcuts 82 | from .wgmultilineeditable import MultiLineEditable, MultiLineEditableTitle, MultiLineEditableBoxed 83 | 84 | from .wgmonthbox import MonthBox 85 | from .wggrid import SimpleGrid 86 | from .wggridcoltitles import GridColTitles 87 | 88 | from .muNewMenu import NewMenu, MenuItem 89 | from .wgNMenuDisplay import MenuDisplay, MenuDisplayScreen 90 | 91 | from .npyspmfuncs import CallSubShell 92 | 93 | from .utilNotify import notify, notify_confirm, notify_wait, notify_ok_cancel, notify_yes_no 94 | 95 | # Base classes for overriding: 96 | 97 | # Standard Forms: 98 | from . import stdfmemail 99 | 100 | # Experimental Only 101 | from .wgtextboxunicode import TextfieldUnicode 102 | from .wgtexttokens import TextTokens, TitleTextTokens 103 | 104 | # Very experimental. Don't use for anything serious 105 | from .apOptions import SimpleOptionForm 106 | from .apOptions import OptionListDisplay, OptionChanger, OptionList, OptionLimitedChoices, OptionListDisplayLine 107 | from .apOptions import OptionFreeText, OptionSingleChoice, OptionMultiChoice, OptionMultiFreeList, \ 108 | OptionBoolean, OptionFilename, OptionDate, OptionMultiFreeText 109 | 110 | 111 | # This really is about as experimental as it gets 112 | from .apNPSApplicationEvents import StandardApp 113 | from .eveventhandler import Event 114 | 115 | 116 | -------------------------------------------------------------------------------- /npyscreen/apNPSApplication.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import curses 3 | import locale 4 | import _curses 5 | 6 | from . import npyssafewrapper 7 | 8 | 9 | class AlreadyOver(Exception): 10 | pass 11 | 12 | class NPSApp(object): 13 | _run_called = 0 14 | def main(self): 15 | """Overload this method to create your application""" 16 | 17 | def resize(self): 18 | pass 19 | 20 | def __remove_argument_call_main(self, screen, enable_mouse=True): 21 | # screen disgarded. 22 | if enable_mouse: 23 | curses.mousemask(curses.ALL_MOUSE_EVENTS) 24 | del screen 25 | return self.main() 26 | 27 | def run(self, fork=None): 28 | """Run application. Calls Mainloop wrapped properly.""" 29 | if fork is None: 30 | return npyssafewrapper.wrapper(self.__remove_argument_call_main) 31 | else: 32 | return npyssafewrapper.wrapper(self.__remove_argument_call_main, fork=fork) 33 | -------------------------------------------------------------------------------- /npyscreen/apNPSApplicationAdvanced.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | from . import apNPSApplicationManaged 5 | from . import fmForm 6 | import weakref 7 | 8 | class NPSAppAdvanced(apNPSApplicationManaged.NPSAppManaged): 9 | """EXPERIMENTAL and NOT for use. This class of application will eventually replace the 10 | standard method of user input handling and deal with everything at the application level.""" 11 | 12 | def _main_loop(self): 13 | pass -------------------------------------------------------------------------------- /npyscreen/apNPSApplicationEvents.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import weakref 3 | from .apNPSApplicationManaged import NPSAppManaged 4 | from .eveventhandler import EventHandler 5 | 6 | class NPSEventQueue(object): 7 | def __init__(self): 8 | self.interal_queue = collections.deque() 9 | 10 | def get(self, maximum=None): 11 | if maximum is None: 12 | maximum = -1 13 | counter = 1 14 | while counter != maximum: 15 | try: 16 | yield self.interal_queue.pop() 17 | except IndexError: 18 | raise StopIteration 19 | counter += 1 20 | 21 | def put(self, event): 22 | self.interal_queue.append(event) 23 | 24 | class StandardApp(NPSAppManaged, EventHandler): 25 | MAINQUEUE_TYPE = NPSEventQueue 26 | keypress_timeout_default = 2 27 | max_events_per_queue = 50 28 | """This class adds event queue functionality to the existing NPSAppManaged. The name reflects the fact that future applications 29 | are expected to use this class as standard. However, it is currently an experimental class. The API is unlikely to change, but 30 | no promises are made at this time. 31 | """ 32 | def __init__(self): 33 | super(StandardApp, self).__init__() 34 | self.event_directory = {} 35 | self.event_queues = {} 36 | self.initalize_application_event_queues() 37 | self.initialize_event_handling() 38 | 39 | def _internal_while_waiting(self): 40 | # Parent NPSAppManaged does not define this, so no need to call. 41 | self.process_event_queues(max_events_per_queue=self.max_events_per_queue) 42 | 43 | 44 | def initalize_application_event_queues(self): 45 | # in the standard application the event queue is not threaded so... 46 | main_queue = self.MAINQUEUE_TYPE() 47 | self.event_queues['MAINQUEUE'] = main_queue 48 | 49 | def process_event_queues(self, max_events_per_queue=None): 50 | for queue in self.event_queues.values(): 51 | for event in queue.get(maximum=max_events_per_queue): 52 | self.process_event(event) 53 | 54 | def register_for_event(self, registering_object, event_name): 55 | if event_name not in self.event_directory: 56 | self.event_directory[event_name] = weakref.WeakSet() 57 | self.event_directory[event_name].add(registering_object) 58 | 59 | def queue_event(self, event, queue='MAINQUEUE'): 60 | self.event_queues[queue].put(event) 61 | 62 | def process_event(self, event): 63 | discard_list = [] 64 | if event.name not in self.event_directory: 65 | return True 66 | if not self.event_directory[event.name]: 67 | del self.event_directory[event.name] 68 | return True 69 | for registered_object in self.event_directory[event.name]: 70 | result = registered_object.handle_event(event) 71 | if result is False: 72 | discard_list.append(registered_object) 73 | 74 | for registered_object in discard_list: 75 | self.event_directory[event.name].discard(registered_object) -------------------------------------------------------------------------------- /npyscreen/compatibility_code/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vtr0n/npyscreen/ed9a0a6e735e55fb051aa2a2a17247597c0ed2d3/npyscreen/compatibility_code/__init__.py -------------------------------------------------------------------------------- /npyscreen/compatibility_code/npysNPSTree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import weakref 3 | import collections 4 | import operator 5 | 6 | class NPSTreeData(object): 7 | CHILDCLASS = None 8 | def __init__(self, content=None, parent=None, selected=False, selectable=True, 9 | highlight=False, expanded=True, ignoreRoot=True, sort_function=None): 10 | self.setParent(parent) 11 | self.setContent(content) 12 | self.selectable = selectable 13 | self.selected = selected 14 | self.highlight = highlight 15 | self.expanded = expanded 16 | self._children = [] 17 | self.ignoreRoot = ignoreRoot 18 | self.sort = False 19 | self.sort_function = sort_function 20 | self.sort_function_wrapper = True 21 | 22 | 23 | def getContent(self): 24 | return self.content 25 | 26 | def getContentForDisplay(self): 27 | return str(self.content) 28 | 29 | def setContent(self, content): 30 | self.content = content 31 | 32 | def isSelected(self): 33 | return self.selected 34 | 35 | def isHighlighted(self): 36 | return self.highlight 37 | 38 | def setParent(self, parent): 39 | if parent == None: 40 | self._parent = None 41 | else: 42 | self._parent = weakref.proxy(parent) 43 | 44 | def getParent(self): 45 | return self._parent 46 | 47 | 48 | def findDepth(self, d=0): 49 | depth = d 50 | parent = self.getParent() 51 | while parent: 52 | d += 1 53 | parent = parent.getParent() 54 | return d 55 | # Recursive 56 | #if self._parent == None: 57 | # return d 58 | #else: 59 | # return(self._parent.findDepth(d+1)) 60 | 61 | def isLastSibling(self): 62 | if self.getParent(): 63 | if list(self.getParent().getChildren())[-1] == self: 64 | return True 65 | else: 66 | return False 67 | else: 68 | return None 69 | 70 | def hasChildren(self): 71 | if len(self._children) > 0: 72 | return True 73 | else: 74 | return False 75 | 76 | def getChildren(self): 77 | for c in self._children: 78 | try: 79 | yield weakref.proxy(c) 80 | except: 81 | yield c 82 | 83 | def getChildrenObjects(self): 84 | return self._children[:] 85 | 86 | def _getChildrenList(self): 87 | return self._children 88 | 89 | def newChild(self, *args, **keywords): 90 | if self.CHILDCLASS: 91 | cld = self.CHILDCLASS 92 | else: 93 | cld = type(self) 94 | c = cld(parent=self, *args, **keywords) 95 | self._children.append(c) 96 | return weakref.proxy(c) 97 | 98 | def removeChild(self, child): 99 | new_children = [] 100 | for ch in self._children: 101 | # do it this way because of weakref equality bug. 102 | if not ch.getContent() == child.getContent(): 103 | new_children.append(ch) 104 | else: 105 | ch.setParent(None) 106 | self._children = new_children 107 | 108 | 109 | def create_wrapped_sort_function(self, this_function): 110 | def new_function(the_item): 111 | if the_item: 112 | the_real_item = the_item.getContent() 113 | return this_function(the_real_item) 114 | else: 115 | return the_item 116 | return new_function 117 | 118 | def walkParents(self): 119 | p = self.getParent() 120 | while p: 121 | yield p 122 | p = p.getParent() 123 | 124 | def walkTree(self, onlyExpanded=True, ignoreRoot=True, sort=None, sort_function=None): 125 | #Iterate over Tree 126 | if sort is None: 127 | sort = self.sort 128 | 129 | if sort_function is None: 130 | sort_function = self.sort_function 131 | 132 | # example sort function # sort = True 133 | # example sort function # def sort_function(the_item): 134 | # example sort function # import email.utils 135 | # example sort function # if the_item: 136 | # example sort function # if the_item.getContent(): 137 | # example sort function # frm = the_item.getContent().get('from') 138 | # example sort function # try: 139 | # example sort function # frm = email.utils.parseaddr(frm)[0] 140 | # example sort function # except: 141 | # example sort function # pass 142 | # example sort function # return frm 143 | # example sort function # else: 144 | # example sort function # return the_item 145 | #key = operator.methodcaller('getContent',) 146 | 147 | if self.sort_function_wrapper and sort_function: 148 | # def wrapped_sort_function(the_item): 149 | # if the_item: 150 | # the_real_item = the_item.getContent() 151 | # return sort_function(the_real_item) 152 | # else: 153 | # return the_item 154 | # _this_sort_function = wrapped_sort_function 155 | _this_sort_function = self.create_wrapped_sort_function(sort_function) 156 | else: 157 | _this_sort_function = sort_function 158 | 159 | key = _this_sort_function 160 | if not ignoreRoot: 161 | yield self 162 | nodes_to_yield = collections.deque() # better memory management than a list for pop(0) 163 | if self.expanded or not onlyExpanded: 164 | if sort: 165 | # This and the similar block below could be combined into a nested function 166 | if key: 167 | nodes_to_yield.extend(sorted(self.getChildren(), key=key,)) 168 | else: 169 | nodes_to_yield.extend(sorted(self.getChildren())) 170 | else: 171 | nodes_to_yield.extend(self.getChildren()) 172 | while nodes_to_yield: 173 | child = nodes_to_yield.popleft() 174 | if child.expanded or not onlyExpanded: 175 | # This and the similar block above could be combined into a nested function 176 | if sort: 177 | if key: 178 | # must be reverse because about to use extendleft() below. 179 | nodes_to_yield.extendleft(sorted(child.getChildren(), key=key, reverse=True)) 180 | else: 181 | nodes_to_yield.extendleft(sorted(child.getChildren(), reverse=True)) 182 | else: 183 | #for node in child.getChildren(): 184 | # if node not in nodes_to_yield: 185 | # nodes_to_yield.appendleft(node) 186 | yield_these = list(child.getChildren()) 187 | yield_these.reverse() 188 | nodes_to_yield.extendleft(yield_these) 189 | del yield_these 190 | yield child 191 | 192 | def _walkTreeRecursive(self,onlyExpanded=True, ignoreRoot=True,): 193 | #This is an old, recursive version 194 | if (not onlyExpanded) or (self.expanded): 195 | for child in self.getChildren(): 196 | for node in child.walkTree(onlyExpanded=onlyExpanded, ignoreRoot=False): 197 | yield node 198 | 199 | def getTreeAsList(self, onlyExpanded=True, sort=None, key=None): 200 | _a = [] 201 | for node in self.walkTree(onlyExpanded=onlyExpanded, ignoreRoot=self.ignoreRoot, sort=sort): 202 | try: 203 | _a.append(weakref.proxy(node)) 204 | except: 205 | _a.append(node) 206 | return _a 207 | 208 | -------------------------------------------------------------------------------- /npyscreen/eveventhandler.py: -------------------------------------------------------------------------------- 1 | import weakref 2 | 3 | class Event(object): 4 | # a basic event class 5 | def __init__(self, name, payload=None): 6 | self.name = name 7 | self.payload = payload 8 | 9 | 10 | class EventHandler(object): 11 | # This partial base class provides the framework to handle events. 12 | 13 | def initialize_event_handling(self): 14 | self.event_handlers = {} 15 | 16 | def add_event_hander(self, event_name, handler): 17 | if not event_name in self.event_handlers: 18 | self.event_handlers[event_name] = set() # weakref.WeakSet() #Why doesn't the WeakSet work? 19 | self.event_handlers[event_name].add(handler) 20 | 21 | parent_app = self.find_parent_app() 22 | if parent_app: 23 | parent_app.register_for_event(self, event_name) 24 | else: 25 | # Probably are the parent App! 26 | # but could be a form outside a proper application environment 27 | try: 28 | self.register_for_event(self, event_name) 29 | except AttributeError: 30 | pass 31 | 32 | def remove_event_handler(self, event_name, handler): 33 | if event_name in self.event_handlers: 34 | self.event_handlers[event_name].remove(handler) 35 | if not self.event_handlers[event_name]: 36 | self.event_handlers.pop({}) 37 | 38 | 39 | def handle_event(self, event): 40 | "return True if the event was handled. Return False if the application should stop sending this event." 41 | if event.name not in self.event_handlers: 42 | return False 43 | else: 44 | remove_list = [] 45 | for handler in self.event_handlers[event.name]: 46 | try: 47 | handler(event) 48 | except weakref.ReferenceError: 49 | remove_list.append(handler) 50 | for dead_handler in remove_list: 51 | self.event_handlers[event.name].remove(handler) 52 | return True 53 | 54 | def find_parent_app(self): 55 | if hasattr(self, "parentApp"): 56 | return self.parentApp 57 | elif hasattr(self, "parent") and hasattr(self.parent, "parentApp"): 58 | return self.parent.parentApp 59 | else: 60 | return None 61 | 62 | -------------------------------------------------------------------------------- /npyscreen/fmActionForm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import weakref 3 | from . import fmForm 4 | from . import wgwidget as widget 5 | class ActionForm(fmForm.Form): 6 | """A form with OK and Cancel buttons. Users should override the on_ok and on_cancel methods.""" 7 | CANCEL_BUTTON_BR_OFFSET = (2, 12) 8 | OK_BUTTON_TEXT = "OK" 9 | CANCEL_BUTTON_TEXT = "Cancel" 10 | 11 | def set_up_exit_condition_handlers(self): 12 | super(ActionForm, self).set_up_exit_condition_handlers() 13 | self.how_exited_handers.update({ 14 | widget.EXITED_ESCAPE: self.find_cancel_button 15 | }) 16 | 17 | def find_cancel_button(self): 18 | self.editw = len(self._widgets__)-2 19 | 20 | def edit(self): 21 | # Add ok and cancel buttons. Will remove later 22 | tmp_rely, tmp_relx = self.nextrely, self.nextrelx 23 | 24 | c_button_text = self.CANCEL_BUTTON_TEXT 25 | cmy, cmx = self.curses_pad.getmaxyx() 26 | cmy -= self.__class__.CANCEL_BUTTON_BR_OFFSET[0] 27 | cmx -= len(c_button_text)+self.__class__.CANCEL_BUTTON_BR_OFFSET[1] 28 | self.c_button = self.add_widget(self.__class__.OKBUTTON_TYPE, name=c_button_text, rely=cmy, relx=cmx, use_max_space=True) 29 | c_button_postion = len(self._widgets__)-1 30 | self.c_button.update() 31 | 32 | my, mx = self.curses_pad.getmaxyx() 33 | ok_button_text = self.OK_BUTTON_TEXT 34 | my -= self.__class__.OK_BUTTON_BR_OFFSET[0] 35 | mx -= len(ok_button_text)+self.__class__.OK_BUTTON_BR_OFFSET[1] 36 | self.ok_button = self.add_widget(self.__class__.OKBUTTON_TYPE, name=ok_button_text, rely=my, relx=mx, use_max_space=True) 37 | ok_button_postion = len(self._widgets__)-1 38 | # End add buttons 39 | 40 | self.editing=True 41 | if self.editw < 0: self.editw=0 42 | if self.editw > len(self._widgets__)-1: 43 | self.editw = len(self._widgets__)-1 44 | if not self.preserve_selected_widget: 45 | self.editw = 0 46 | 47 | 48 | if not self._widgets__[self.editw].editable: self.find_next_editable() 49 | self.ok_button.update() 50 | 51 | self.display() 52 | 53 | while not self._widgets__[self.editw].editable: 54 | self.editw += 1 55 | if self.editw > len(self._widgets__)-2: 56 | self.editing = False 57 | return False 58 | 59 | self.edit_return_value = None 60 | while self.editing: 61 | if not self.ALL_SHOWN: self.on_screen() 62 | try: 63 | self.while_editing(weakref.proxy(self._widgets__[self.editw])) 64 | except TypeError: 65 | self.while_editing() 66 | self._widgets__[self.editw].edit() 67 | self._widgets__[self.editw].display() 68 | 69 | self.handle_exiting_widgets(self._widgets__[self.editw].how_exited) 70 | 71 | if self.editw > len(self._widgets__)-1: self.editw = len(self._widgets__)-1 72 | if self.ok_button.value or self.c_button.value: 73 | self.editing = False 74 | 75 | if self.ok_button.value: 76 | self.ok_button.value = False 77 | self.edit_return_value = self.on_ok() 78 | elif self.c_button.value: 79 | self.c_button.value = False 80 | self.edit_return_value = self.on_cancel() 81 | 82 | self.ok_button.destroy() 83 | self.c_button.destroy() 84 | del self._widgets__[ok_button_postion] 85 | del self.ok_button 86 | del self._widgets__[c_button_postion] 87 | del self.c_button 88 | self.nextrely, self.nextrelx = tmp_rely, tmp_relx 89 | self.display() 90 | self.editing = False 91 | 92 | return self.edit_return_value 93 | 94 | def on_cancel(self): 95 | pass 96 | 97 | def on_ok(self): 98 | pass 99 | 100 | def move_ok_button(self): 101 | super(ActionForm, self).move_ok_button() 102 | if hasattr(self, 'c_button'): 103 | c_button_text = self.CANCEL_BUTTON_TEXT 104 | cmy, cmx = self.curses_pad.getmaxyx() 105 | cmy -= self.__class__.CANCEL_BUTTON_BR_OFFSET[0] 106 | cmx -= len(c_button_text)+self.__class__.CANCEL_BUTTON_BR_OFFSET[1] 107 | self.c_button.rely = cmy 108 | self.c_button.relx = cmx 109 | 110 | 111 | 112 | class ActionFormExpanded(ActionForm): 113 | BLANK_LINES_BASE = 1 114 | OK_BUTTON_BR_OFFSET = (1,6) 115 | CANCEL_BUTTON_BR_OFFSET = (1, 12) 116 | 117 | -------------------------------------------------------------------------------- /npyscreen/fmActionFormV2.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import weakref 3 | from . import wgwidget as widget 4 | from . import wgbutton 5 | from . import fmForm 6 | 7 | class ActionFormV2(fmForm.FormBaseNew): 8 | class OK_Button(wgbutton.MiniButtonPress): 9 | def whenPressed(self): 10 | return self.parent._on_ok() 11 | 12 | class Cancel_Button(wgbutton.MiniButtonPress): 13 | def whenPressed(self): 14 | return self.parent._on_cancel() 15 | 16 | OKBUTTON_TYPE = OK_Button 17 | CANCELBUTTON_TYPE = Cancel_Button 18 | CANCEL_BUTTON_BR_OFFSET = (2, 12) 19 | OK_BUTTON_TEXT = "OK" 20 | CANCEL_BUTTON_TEXT = "Cancel" 21 | def __init__(self, *args, **keywords): 22 | super(ActionFormV2, self).__init__(*args, **keywords) 23 | self._added_buttons = {} 24 | self.create_control_buttons() 25 | 26 | 27 | def create_control_buttons(self): 28 | self._add_button('ok_button', 29 | self.__class__.OKBUTTON_TYPE, 30 | self.__class__.OK_BUTTON_TEXT, 31 | 0 - self.__class__.OK_BUTTON_BR_OFFSET[0], 32 | 0 - self.__class__.OK_BUTTON_BR_OFFSET[1] - len(self.__class__.OK_BUTTON_TEXT), 33 | None 34 | ) 35 | 36 | self._add_button('cancel_button', 37 | self.__class__.CANCELBUTTON_TYPE, 38 | self.__class__.CANCEL_BUTTON_TEXT, 39 | 0 - self.__class__.CANCEL_BUTTON_BR_OFFSET[0], 40 | 0 - self.__class__.CANCEL_BUTTON_BR_OFFSET[1] - len(self.__class__.CANCEL_BUTTON_TEXT), 41 | None 42 | ) 43 | 44 | def on_cancel(self): 45 | pass 46 | 47 | def on_ok(self): 48 | pass 49 | 50 | def _on_ok(self): 51 | self.editing = self.on_ok() 52 | 53 | def _on_cancel(self): 54 | self.editing = self.on_cancel() 55 | 56 | def set_up_exit_condition_handlers(self): 57 | super(ActionFormV2, self).set_up_exit_condition_handlers() 58 | self.how_exited_handers.update({ 59 | widget.EXITED_ESCAPE: self.find_cancel_button 60 | }) 61 | 62 | def find_cancel_button(self): 63 | self.editw = len(self._widgets__)-2 64 | 65 | def _add_button(self, button_name, button_type, button_text, button_rely, button_relx, button_function): 66 | tmp_rely, tmp_relx = self.nextrely, self.nextrelx 67 | this_button = self.add_widget( 68 | button_type, 69 | name=button_text, 70 | rely=button_rely, 71 | relx=button_relx, 72 | when_pressed_function = button_function, 73 | use_max_space=True, 74 | ) 75 | self._added_buttons[button_name] = this_button 76 | self.nextrely, self.nextrelx = tmp_rely, tmp_relx 77 | 78 | 79 | def pre_edit_loop(self): 80 | self._widgets__.sort(key=operator.attrgetter('relx')) 81 | self._widgets__.sort(key=operator.attrgetter('rely')) 82 | if not self.preserve_selected_widget: 83 | self.editw = 0 84 | if not self._widgets__[self.editw].editable: 85 | self.find_next_editable() 86 | 87 | def post_edit_loop(self): 88 | pass 89 | 90 | def _during_edit_loop(self): 91 | pass 92 | 93 | class ActionFormExpandedV2(ActionFormV2): 94 | BLANK_LINES_BASE = 1 95 | OK_BUTTON_BR_OFFSET = (1,6) 96 | CANCEL_BUTTON_BR_OFFSET = (1, 12) 97 | 98 | class ActionFormMinimal(ActionFormV2): 99 | def create_control_buttons(self): 100 | self._add_button('ok_button', 101 | self.__class__.OKBUTTON_TYPE, 102 | self.__class__.OK_BUTTON_TEXT, 103 | 0 - self.__class__.OK_BUTTON_BR_OFFSET[0], 104 | 0 - self.__class__.OK_BUTTON_BR_OFFSET[1] - len(self.__class__.OK_BUTTON_TEXT), 105 | None 106 | ) 107 | 108 | -------------------------------------------------------------------------------- /npyscreen/fmFormMutt.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python 2 | import curses 3 | from . import fmForm 4 | from . import fmFormWithMenus 5 | from . import wgtextbox 6 | from . import wgmultiline 7 | #import grid 8 | #import editmultiline 9 | 10 | 11 | class FormMutt(fmForm.FormBaseNew): 12 | BLANK_LINES_BASE = 0 13 | BLANK_COLUMNS_RIGHT = 0 14 | DEFAULT_X_OFFSET = 2 15 | FRAMED = False 16 | MAIN_WIDGET_CLASS = wgmultiline.MultiLine 17 | MAIN_WIDGET_CLASS_START_LINE = 1 18 | STATUS_WIDGET_CLASS = wgtextbox.Textfield 19 | STATUS_WIDGET_X_OFFSET = 0 20 | COMMAND_WIDGET_CLASS= wgtextbox.Textfield 21 | COMMAND_WIDGET_NAME = None 22 | COMMAND_WIDGET_BEGIN_ENTRY_AT = None 23 | COMMAND_ALLOW_OVERRIDE_BEGIN_ENTRY_AT = True 24 | #MAIN_WIDGET_CLASS = grid.SimpleGrid 25 | #MAIN_WIDGET_CLASS = editmultiline.MultiLineEdit 26 | def __init__(self, cycle_widgets = True, *args, **keywords): 27 | super(FormMutt, self).__init__(cycle_widgets=cycle_widgets, *args, **keywords) 28 | 29 | 30 | def draw_form(self): 31 | MAXY, MAXX = self.lines, self.columns #self.curses_pad.getmaxyx() 32 | self.curses_pad.hline(0, 0, curses.ACS_HLINE, MAXX-1) 33 | self.curses_pad.hline(MAXY-2-self.BLANK_LINES_BASE, 0, curses.ACS_HLINE, MAXX-1) 34 | 35 | def create(self): 36 | MAXY, MAXX = self.lines, self.columns 37 | 38 | self.wStatus1 = self.add(self.__class__.STATUS_WIDGET_CLASS, rely=0, 39 | relx=self.__class__.STATUS_WIDGET_X_OFFSET, 40 | editable=False, 41 | ) 42 | 43 | if self.__class__.MAIN_WIDGET_CLASS: 44 | self.wMain = self.add(self.__class__.MAIN_WIDGET_CLASS, 45 | rely=self.__class__.MAIN_WIDGET_CLASS_START_LINE, 46 | relx=0, max_height = -2, 47 | ) 48 | self.wStatus2 = self.add(self.__class__.STATUS_WIDGET_CLASS, rely=MAXY-2-self.BLANK_LINES_BASE, 49 | relx=self.__class__.STATUS_WIDGET_X_OFFSET, 50 | editable=False, 51 | ) 52 | 53 | if not self.__class__.COMMAND_WIDGET_BEGIN_ENTRY_AT: 54 | self.wCommand = self.add(self.__class__.COMMAND_WIDGET_CLASS, name=self.__class__.COMMAND_WIDGET_NAME, 55 | rely = MAXY-1-self.BLANK_LINES_BASE, relx=0,) 56 | else: 57 | self.wCommand = self.add( 58 | self.__class__.COMMAND_WIDGET_CLASS, name=self.__class__.COMMAND_WIDGET_NAME, 59 | rely = MAXY-1-self.BLANK_LINES_BASE, relx=0, 60 | begin_entry_at = self.__class__.COMMAND_WIDGET_BEGIN_ENTRY_AT, 61 | allow_override_begin_entry_at = self.__class__.COMMAND_ALLOW_OVERRIDE_BEGIN_ENTRY_AT 62 | ) 63 | 64 | self.wStatus1.important = True 65 | self.wStatus2.important = True 66 | self.nextrely = 2 67 | 68 | def h_display(self, input): 69 | super(FormMutt, self).h_display(input) 70 | if hasattr(self, 'wMain'): 71 | if not self.wMain.hidden: 72 | self.wMain.display() 73 | 74 | def resize(self): 75 | super(FormMutt, self).resize() 76 | MAXY, MAXX = self.lines, self.columns 77 | self.wStatus2.rely = MAXY-2-self.BLANK_LINES_BASE 78 | self.wCommand.rely = MAXY-1-self.BLANK_LINES_BASE 79 | 80 | class FormMuttWithMenus(FormMutt, fmFormWithMenus.FormBaseNewWithMenus): 81 | def __init__(self, *args, **keywords): 82 | super(FormMuttWithMenus, self).__init__(*args, **keywords) 83 | self.initialize_menus() 84 | -------------------------------------------------------------------------------- /npyscreen/fmFormWithMenus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import curses 4 | from . import fmForm 5 | from . import fmActionForm 6 | from . import fmActionFormV2 7 | from . import wgNMenuDisplay 8 | 9 | class FormBaseNewWithMenus(fmForm.FormBaseNew, wgNMenuDisplay.HasMenus): 10 | """The FormBaseNew class, but with a handling system for menus as well. See the HasMenus class for details.""" 11 | def __init__(self, *args, **keywords): 12 | super(FormBaseNewWithMenus, self).__init__(*args, **keywords) 13 | self.initialize_menus() 14 | 15 | def display_menu_advert_at(self): 16 | return self.lines-1, 1 17 | 18 | def draw_form(self): 19 | super(FormBaseNewWithMenus, self).draw_form() 20 | menu_advert = " " + self.__class__.MENU_KEY + ": Menu " 21 | if isinstance(menu_advert, bytes): 22 | menu_advert = menu_advert.decode('utf-8', 'replace') 23 | y, x = self.display_menu_advert_at() 24 | self.add_line(y, x, 25 | menu_advert, 26 | self.make_attributes_list(menu_advert, curses.A_NORMAL), 27 | self.columns - x - 1 28 | ) 29 | 30 | 31 | class FormWithMenus(fmForm.Form, wgNMenuDisplay.HasMenus): 32 | """The Form class, but with a handling system for menus as well. See the HasMenus class for details.""" 33 | def __init__(self, *args, **keywords): 34 | super(FormWithMenus, self).__init__(*args, **keywords) 35 | self.initialize_menus() 36 | 37 | def display_menu_advert_at(self): 38 | return self.lines-1, 1 39 | 40 | def draw_form(self): 41 | super(FormWithMenus, self).draw_form() 42 | menu_advert = " " + self.__class__.MENU_KEY + ": Menu " 43 | y, x = self.display_menu_advert_at() 44 | if isinstance(menu_advert, bytes): 45 | menu_advert = menu_advert.decode('utf-8', 'replace') 46 | self.add_line(y, x, 47 | menu_advert, 48 | self.make_attributes_list(menu_advert, curses.A_NORMAL), 49 | self.columns - x - 1 50 | ) 51 | 52 | # The following class does not inherit from FormWithMenus and so some code is duplicated. 53 | # The pig is getting to inherit edit() from ActionForm, but draw_form from FormWithMenus 54 | class ActionFormWithMenus(fmActionForm.ActionForm, wgNMenuDisplay.HasMenus): 55 | def __init__(self, *args, **keywords): 56 | super(ActionFormWithMenus, self).__init__(*args, **keywords) 57 | self.initialize_menus() 58 | 59 | def display_menu_advert_at(self): 60 | return self.lines-1, 1 61 | 62 | def draw_form(self): 63 | super(ActionFormWithMenus, self).draw_form() 64 | menu_advert = " " + self.__class__.MENU_KEY + ": Menu " 65 | y, x = self.display_menu_advert_at() 66 | 67 | if isinstance(menu_advert, bytes): 68 | menu_advert = menu_advert.decode('utf-8', 'replace') 69 | self.add_line(y, x, 70 | menu_advert, 71 | self.make_attributes_list(menu_advert, curses.A_NORMAL), 72 | self.columns - x - 1 73 | ) 74 | 75 | class ActionFormV2WithMenus(fmActionFormV2.ActionFormV2, wgNMenuDisplay.HasMenus): 76 | def __init__(self, *args, **keywords): 77 | super(ActionFormV2WithMenus, self).__init__(*args, **keywords) 78 | self.initialize_menus() 79 | 80 | 81 | 82 | class SplitFormWithMenus(fmForm.SplitForm, FormWithMenus): 83 | """Just the same as the Title Form, but with a horizontal line""" 84 | def draw_form(self): 85 | super(SplitFormWithMenus, self).draw_form() 86 | 87 | -------------------------------------------------------------------------------- /npyscreen/fmPopup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # encoding: utf-8 3 | 4 | from . import fmForm 5 | from . import fmActionFormV2 6 | import curses 7 | 8 | 9 | class Popup(fmForm.Form): 10 | DEFAULT_LINES = 12 11 | DEFAULT_COLUMNS = 60 12 | SHOW_ATX = 10 13 | SHOW_ATY = 2 14 | 15 | class ActionPopup(fmActionFormV2.ActionFormV2): 16 | DEFAULT_LINES = 12 17 | DEFAULT_COLUMNS = 60 18 | SHOW_ATX = 10 19 | SHOW_ATY = 2 20 | 21 | 22 | class MessagePopup(Popup): 23 | def __init__(self, *args, **keywords): 24 | from . import wgmultiline as multiline 25 | super(MessagePopup, self).__init__(*args, **keywords) 26 | self.TextWidget = self.add(multiline.Pager, scroll_exit=True, max_height=self.widget_useable_space()[0]-2) 27 | 28 | class PopupWide(Popup): 29 | DEFAULT_LINES = 14 30 | DEFAULT_COLUMNS = None 31 | SHOW_ATX = 0 32 | SHOW_ATY = 0 33 | 34 | class ActionPopupWide(fmActionFormV2.ActionFormV2): 35 | DEFAULT_LINES = 14 36 | DEFAULT_COLUMNS = None 37 | SHOW_ATX = 0 38 | SHOW_ATY = 0 39 | -------------------------------------------------------------------------------- /npyscreen/fm_form_edit_loop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | form_edit_loop.py 5 | 6 | Created by Nicholas Cole on 2008-03-31. 7 | Copyright (c) 2008 __MyCompanyName__. All rights reserved. 8 | """ 9 | 10 | import sys 11 | import os 12 | import weakref 13 | 14 | class FormNewEditLoop(object): 15 | "Edit Fields .editing = False" 16 | def pre_edit_loop(self): 17 | pass 18 | def post_edit_loop(self): 19 | pass 20 | def _during_edit_loop(self): 21 | pass 22 | 23 | def edit_loop(self): 24 | self.editing = True 25 | self.display() 26 | while not (self._widgets__[self.editw].editable and not self._widgets__[self.editw].hidden): 27 | self.editw += 1 28 | if self.editw > len(self._widgets__)-1: 29 | self.editing = False 30 | return False 31 | 32 | while self.editing: 33 | if not self.ALL_SHOWN: self.on_screen() 34 | self.while_editing(weakref.proxy(self._widgets__[self.editw])) 35 | self._during_edit_loop() 36 | if not self.editing: 37 | break 38 | self._widgets__[self.editw].edit() 39 | self._widgets__[self.editw].display() 40 | 41 | self.handle_exiting_widgets(self._widgets__[self.editw].how_exited) 42 | 43 | if self.editw > len(self._widgets__)-1: self.editw = len(self._widgets__)-1 44 | 45 | def edit(self): 46 | self.pre_edit_loop() 47 | self.edit_loop() 48 | self.post_edit_loop() 49 | 50 | class FormDefaultEditLoop(object): 51 | def edit(self): 52 | """Edit the fields until the user selects the ok button added in the lower right corner. Button will 53 | be removed when editing finishes""" 54 | # Add ok button. Will remove later 55 | tmp_rely, tmp_relx = self.nextrely, self.nextrelx 56 | my, mx = self.curses_pad.getmaxyx() 57 | ok_button_text = self.__class__.OK_BUTTON_TEXT 58 | my -= self.__class__.OK_BUTTON_BR_OFFSET[0] 59 | mx -= len(ok_button_text)+self.__class__.OK_BUTTON_BR_OFFSET[1] 60 | self.ok_button = self.add_widget(self.__class__.OKBUTTON_TYPE, name=ok_button_text, rely=my, relx=mx, use_max_space=True) 61 | ok_button_postion = len(self._widgets__)-1 62 | self.ok_button.update() 63 | # End add buttons 64 | self.editing=True 65 | if self.editw < 0: self.editw=0 66 | if self.editw > len(self._widgets__)-1: 67 | self.editw = len(self._widgets__)-1 68 | if not self.preserve_selected_widget: 69 | self.editw = 0 70 | if not self._widgets__[self.editw].editable: self.find_next_editable() 71 | 72 | 73 | self.display() 74 | 75 | while not (self._widgets__[self.editw].editable and not self._widgets__[self.editw].hidden): 76 | self.editw += 1 77 | if self.editw > len(self._widgets__)-1: 78 | self.editing = False 79 | return False 80 | 81 | while self.editing: 82 | if not self.ALL_SHOWN: self.on_screen() 83 | self.while_editing(weakref.proxy(self._widgets__[self.editw])) 84 | if not self.editing: 85 | break 86 | self._widgets__[self.editw].edit() 87 | self._widgets__[self.editw].display() 88 | 89 | self.handle_exiting_widgets(self._widgets__[self.editw].how_exited) 90 | 91 | if self.editw > len(self._widgets__)-1: self.editw = len(self._widgets__)-1 92 | if self.ok_button.value: 93 | self.editing = False 94 | 95 | self.ok_button.destroy() 96 | del self._widgets__[ok_button_postion] 97 | del self.ok_button 98 | self.nextrely, self.nextrelx = tmp_rely, tmp_relx 99 | self.display() 100 | 101 | #try: 102 | # self.parentApp._FORM_VISIT_LIST.pop() 103 | #except: 104 | # pass 105 | 106 | 107 | self.editing = False 108 | self.erase() 109 | 110 | def move_ok_button(self): 111 | if hasattr(self, 'ok_button'): 112 | my, mx = self.curses_pad.getmaxyx() 113 | my -= self.__class__.OK_BUTTON_BR_OFFSET[0] 114 | mx -= len(self.__class__.OK_BUTTON_TEXT)+self.__class__.OK_BUTTON_BR_OFFSET[1] 115 | self.ok_button.relx = mx 116 | self.ok_button.rely = my 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /npyscreen/globals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | DEBUG = False 4 | DISABLE_RESIZE_SYSTEM = False -------------------------------------------------------------------------------- /npyscreen/muMenu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | from . import wgmultiline 7 | from . import fmForm 8 | import weakref 9 | 10 | 11 | class Menu(object): 12 | "This class is obsolete and Depricated. Use NewMenu instead." 13 | 14 | def __init__(self, name=None, show_atx=None, show_aty=None): 15 | self.__menu_items = [] 16 | self.name = name 17 | self.__show_atx = show_atx 18 | self.__show_aty = show_aty 19 | 20 | def before_item_select(self): 21 | pass 22 | 23 | def add_item(self, text, func): 24 | self.__menu_items.append((text, func)) 25 | 26 | def set_menu(self, pairs): 27 | """Pass in a list of pairs of text labels and functions""" 28 | self.__menu_items = [] 29 | for pair in pairs: 30 | self.add_item(pair[0], pair[1]) 31 | 32 | def edit(self, *args, **keywords): 33 | """Display choice to user, execute function associated""" 34 | 35 | menu_text = [x[0] for x in self.__menu_items] 36 | 37 | longest_text = 0 38 | #Slightly different layout if we are showing a title 39 | if self.name: longest_text=len(self.name)+2 40 | for item in menu_text: 41 | if len(item) > longest_text: 42 | longest_text = len(item) 43 | 44 | height = len(menu_text) 45 | if self.name: 46 | height +=3 47 | else: 48 | height +=2 49 | 50 | if height > 14: 51 | height = 13 52 | 53 | atx = self.__show_atx or 20 54 | aty = self.__show_aty or 2 55 | 56 | popup = fmForm.Form(name=self.name, 57 | lines=height, columns=longest_text+4, 58 | show_aty=aty, show_atx=atx, ) 59 | if not self.name: popup.nextrely = 1 60 | l = popup.add(wgmultiline.MultiLine, 61 | values=menu_text, 62 | #exit_left=True, 63 | return_exit=True) 64 | 65 | popup.display() 66 | l.edit() 67 | if l.value is not None: 68 | self.before_item_select() 69 | self.__menu_items[l.value][1]() 70 | 71 | -------------------------------------------------------------------------------- /npyscreen/muNewMenu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import weakref 4 | 5 | 6 | class NewMenu(object): 7 | """docstring for NewMenu""" 8 | def __init__(self, name=None, shortcut=None, preDisplayFunction=None, pdfuncArguments=None, pdfuncKeywords=None): 9 | self.name = name 10 | self._menuList = [] 11 | self.enabled = True 12 | self.shortcut = shortcut 13 | self.pre_display_function = preDisplayFunction 14 | self.pdfunc_arguments= pdfuncArguments or () 15 | self.pdfunc_keywords = pdfuncKeywords or {} 16 | 17 | def addItemsFromList(self, item_list): 18 | for l in item_list: 19 | if isinstance(l, MenuItem): 20 | self.addNewSubmenu(*l) 21 | else: 22 | self.addItem(*l) 23 | 24 | def addItem(self, *args, **keywords): 25 | _itm = MenuItem(*args, **keywords) 26 | self._menuList.append(_itm) 27 | 28 | def addSubmenu(self, submenu): 29 | "Not recommended. Use addNewSubmenu instead" 30 | _itm = submenu 31 | self._menuList.append(submenu) 32 | 33 | def addNewSubmenu(self, *args, **keywords): 34 | _mnu = NewMenu(*args, **keywords) 35 | self._menuList.append(_mnu) 36 | return weakref.proxy(_mnu) 37 | 38 | def getItemObjects(self): 39 | return [itm for itm in self._menuList if itm.enabled] 40 | 41 | def do_pre_display_function(self): 42 | if self.pre_display_function: 43 | return self.pre_display_function(*self.pdfunc_arguments, **self.pdfunc_keywords) 44 | 45 | class MenuItem(object): 46 | """docstring for MenuItem""" 47 | def __init__(self, text='', onSelect=None, shortcut=None, document=None, arguments=None, keywords=None): 48 | self.setText(text) 49 | self.setOnSelect(onSelect) 50 | self.setDocumentation(document) 51 | self.shortcut = shortcut 52 | self.enabled = True 53 | self.arguments = arguments or () 54 | self.keywords = keywords or {} 55 | 56 | def setText(self, text): 57 | self._text = text 58 | 59 | def getText(self): 60 | return self._text 61 | 62 | def setOnSelect(self, onSelect): 63 | self.onSelectFunction = onSelect 64 | 65 | def setDocumentation(self, document): 66 | self._help = document 67 | 68 | def getDocumentation(self): 69 | return self._help 70 | 71 | def getHelp(self): 72 | return self._help 73 | 74 | def do(self): 75 | if self.onSelectFunction: 76 | return self.onSelectFunction(*self.arguments, **self.keywords) 77 | -------------------------------------------------------------------------------- /npyscreen/npysGlobalOptions.py: -------------------------------------------------------------------------------- 1 | DISABLE_ALL_COLORS = False 2 | ASCII_ONLY = False # See the safe_string function in wgwidget. At the moment the encoding is not safe -------------------------------------------------------------------------------- /npyscreen/npysNPSFilteredData.py: -------------------------------------------------------------------------------- 1 | class NPSFilteredDataBase(object): 2 | def __init__(self, values=None): 3 | self._values = None 4 | self._filter = None 5 | self._filtered_values = None 6 | self.set_values(values) 7 | 8 | def set_values(self, value): 9 | self._values = value 10 | 11 | def get_all_values(self): 12 | return self._values 13 | 14 | def set_filter(self, this_filter): 15 | self._filter = this_filter 16 | self._apply_filter() 17 | 18 | def filter_data(self): 19 | # should set self._filtered_values to the filtered values 20 | raise Exception("You need to define the way the filter operates") 21 | 22 | def get(self): 23 | self._apply_filter() 24 | return self._filtered_values 25 | 26 | def _apply_filter(self): 27 | # Could do some caching here - but the default definition does not. 28 | self._filtered_values = self.filter_data() 29 | 30 | class NPSFilteredDataList(NPSFilteredDataBase): 31 | def filter_data(self): 32 | if self._filter and self.get_all_values(): 33 | return [x for x in self.get_all_values() if self._filter in x] 34 | else: 35 | return self.get_all_values() 36 | 37 | 38 | -------------------------------------------------------------------------------- /npyscreen/npysThemeManagers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import curses 4 | from . import npysGlobalOptions 5 | 6 | def disableColor(): 7 | npysGlobalOptions.DISABLE_ALL_COLORS = True 8 | 9 | def enableColor(): 10 | npysGlobalOptions.DISABLE_ALL_COLORS = False 11 | 12 | class ThemeManager(object): 13 | # a tuple with (color_number, (r, g, b)) 14 | # you can use this to redefine colour values. 15 | # This will only work on compatible terminals. 16 | # Betware that effects will last beyond the end of the 17 | # application. 18 | _color_values = ( 19 | #(curses.COLOR_GREEN, (150,250,100)), 20 | ) 21 | 22 | 23 | _colors_to_define = ( 24 | # DO NOT DEFINE THE WHITE_BLACK COLOR - THINGS BREAK 25 | #('WHITE_BLACK', DO_NOT_DO_THIS, DO_NOT_DO_THIS), 26 | ('BLACK_WHITE', curses.COLOR_BLACK, curses.COLOR_WHITE), 27 | #('BLACK_ON_DEFAULT', curses.COLOR_BLACK, -1), 28 | #('WHITE_ON_DEFAULT', curses.COLOR_WHITE, -1), 29 | ('BLUE_BLACK', curses.COLOR_BLUE, curses.COLOR_BLACK), 30 | ('CYAN_BLACK', curses.COLOR_CYAN, curses.COLOR_BLACK), 31 | ('GREEN_BLACK', curses.COLOR_GREEN, curses.COLOR_BLACK), 32 | ('MAGENTA_BLACK', curses.COLOR_MAGENTA, curses.COLOR_BLACK), 33 | ('RED_BLACK', curses.COLOR_RED, curses.COLOR_BLACK), 34 | ('YELLOW_BLACK', curses.COLOR_YELLOW, curses.COLOR_BLACK), 35 | ('BLACK_RED', curses.COLOR_BLACK, curses.COLOR_RED), 36 | ('BLACK_GREEN', curses.COLOR_BLACK, curses.COLOR_GREEN), 37 | ('BLACK_YELLOW', curses.COLOR_BLACK, curses.COLOR_YELLOW), 38 | ('BLACK_CYAN', curses.COLOR_BLACK, curses.COLOR_CYAN), 39 | ('BLUE_WHITE', curses.COLOR_BLUE, curses.COLOR_WHITE), 40 | ('CYAN_WHITE', curses.COLOR_CYAN, curses.COLOR_WHITE), 41 | ('GREEN_WHITE', curses.COLOR_GREEN, curses.COLOR_WHITE), 42 | ('MAGENTA_WHITE', curses.COLOR_MAGENTA, curses.COLOR_WHITE), 43 | ('RED_WHITE', curses.COLOR_RED, curses.COLOR_WHITE), 44 | ('YELLOW_WHITE', curses.COLOR_YELLOW, curses.COLOR_WHITE), 45 | ) 46 | 47 | default_colors = { 48 | 'DEFAULT' : 'WHITE_BLACK', 49 | 'FORMDEFAULT' : 'WHITE_BLACK', 50 | 'NO_EDIT' : 'BLUE_BLACK', 51 | 'STANDOUT' : 'CYAN_BLACK', 52 | 'CURSOR' : 'WHITE_BLACK', 53 | 'CURSOR_INVERSE': 'BLACK_WHITE', 54 | 'LABEL' : 'GREEN_BLACK', 55 | 'LABELBOLD' : 'WHITE_BLACK', 56 | 'CONTROL' : 'YELLOW_BLACK', 57 | 'IMPORTANT' : 'GREEN_BLACK', 58 | 'SAFE' : 'GREEN_BLACK', 59 | 'WARNING' : 'YELLOW_BLACK', 60 | 'DANGER' : 'RED_BLACK', 61 | 'CRITICAL' : 'BLACK_RED', 62 | 'GOOD' : 'GREEN_BLACK', 63 | 'GOODHL' : 'GREEN_BLACK', 64 | 'VERYGOOD' : 'BLACK_GREEN', 65 | 'CAUTION' : 'YELLOW_BLACK', 66 | 'CAUTIONHL' : 'BLACK_YELLOW', 67 | } 68 | def __init__(self): 69 | #curses.use_default_colors() 70 | self.define_colour_numbers() 71 | self._defined_pairs = {} 72 | self._names = {} 73 | try: 74 | self._max_pairs = curses.COLOR_PAIRS - 1 75 | do_color = True 76 | except AttributeError: 77 | # curses.start_color has failed or has not been called 78 | do_color = False 79 | # Disable all color use across the application 80 | disableColor() 81 | if do_color and curses.has_colors(): 82 | self.initialize_pairs() 83 | self.initialize_names() 84 | 85 | def define_colour_numbers(self): 86 | if curses.can_change_color(): 87 | for c in self._color_values: 88 | curses.init_color(c[0], *c[1]) 89 | 90 | 91 | def findPair(self, caller, request='DEFAULT'): 92 | if not curses.has_colors() or npysGlobalOptions.DISABLE_ALL_COLORS: 93 | return False 94 | 95 | if request=='DEFAULT': 96 | request = caller.color 97 | # Locate the requested colour pair. Default to default if not found. 98 | try: 99 | pair = self._defined_pairs[self._names[request]] 100 | except: 101 | pair = self._defined_pairs[self._names['DEFAULT']] 102 | 103 | # now make the actual attribute 104 | color_attribute = curses.color_pair(pair[0]) 105 | 106 | return color_attribute 107 | 108 | def setDefault(self, caller): 109 | return False 110 | 111 | def initialize_pairs(self): 112 | # White on Black is fixed as color_pair 0 113 | self._defined_pairs['WHITE_BLACK'] = (0, curses.COLOR_WHITE, curses.COLOR_BLACK) 114 | for cp in self.__class__._colors_to_define: 115 | if cp[0] == 'WHITE_BLACK': 116 | # silently protect the user from breaking things. 117 | continue 118 | self.initalize_pair(cp[0], cp[1], cp[2]) 119 | 120 | def initialize_names(self): 121 | self._names.update(self.__class__.default_colors) 122 | 123 | def initalize_pair(self, name, fg, bg): 124 | # Initialize a color_pair for the required colour and return the number. Raise an exception if this is not possible. 125 | if (len(list(self._defined_pairs.keys()))+1) == self._max_pairs: 126 | raise Exception("Too many colours") 127 | 128 | _this_pair_number = len(list(self._defined_pairs.keys())) + 1 129 | 130 | curses.init_pair(_this_pair_number, fg, bg) 131 | 132 | self._defined_pairs[name] = (_this_pair_number, fg, bg) 133 | 134 | return _this_pair_number 135 | 136 | def get_pair_number(self, name): 137 | return self._defined_pairs[name][0] 138 | -------------------------------------------------------------------------------- /npyscreen/npysThemes.py: -------------------------------------------------------------------------------- 1 | import curses 2 | from . import npysThemeManagers as ThemeManagers 3 | 4 | class DefaultTheme(ThemeManagers.ThemeManager): 5 | default_colors = { 6 | 'DEFAULT' : 'WHITE_BLACK', 7 | 'FORMDEFAULT' : 'WHITE_BLACK', 8 | 'NO_EDIT' : 'BLUE_BLACK', 9 | 'STANDOUT' : 'CYAN_BLACK', 10 | 'CURSOR' : 'WHITE_BLACK', 11 | 'CURSOR_INVERSE': 'BLACK_WHITE', 12 | 'LABEL' : 'GREEN_BLACK', 13 | 'LABELBOLD' : 'WHITE_BLACK', 14 | 'CONTROL' : 'YELLOW_BLACK', 15 | 'WARNING' : 'RED_BLACK', 16 | 'CRITICAL' : 'BLACK_RED', 17 | 'GOOD' : 'GREEN_BLACK', 18 | 'GOODHL' : 'GREEN_BLACK', 19 | 'VERYGOOD' : 'BLACK_GREEN', 20 | 'CAUTION' : 'YELLOW_BLACK', 21 | 'CAUTIONHL' : 'BLACK_YELLOW', 22 | } 23 | 24 | class ElegantTheme(ThemeManagers.ThemeManager): 25 | default_colors = { 26 | 'DEFAULT' : 'WHITE_BLACK', 27 | 'FORMDEFAULT' : 'WHITE_BLACK', 28 | 'NO_EDIT' : 'BLUE_BLACK', 29 | 'STANDOUT' : 'CYAN_BLACK', 30 | 'CURSOR' : 'CYAN_BLACK', 31 | 'CURSOR_INVERSE': 'BLACK_CYAN', 32 | 'LABEL' : 'GREEN_BLACK', 33 | 'LABELBOLD' : 'WHITE_BLACK', 34 | 'CONTROL' : 'YELLOW_BLACK', 35 | 'WARNING' : 'RED_BLACK', 36 | 'CRITICAL' : 'BLACK_RED', 37 | 'GOOD' : 'GREEN_BLACK', 38 | 'GOODHL' : 'GREEN_BLACK', 39 | 'VERYGOOD' : 'BLACK_GREEN', 40 | 'CAUTION' : 'YELLOW_BLACK', 41 | 'CAUTIONHL' : 'BLACK_YELLOW', 42 | } 43 | 44 | 45 | class ColorfulTheme(ThemeManagers.ThemeManager): 46 | default_colors = { 47 | 'DEFAULT' : 'RED_BLACK', 48 | 'FORMDEFAULT' : 'YELLOW_BLACK', 49 | 'NO_EDIT' : 'BLUE_BLACK', 50 | 'STANDOUT' : 'CYAN_BLACK', 51 | 'CURSOR' : 'WHITE_BLACK', 52 | 'CURSOR_INVERSE': 'BLACK_WHITE', 53 | 'LABEL' : 'BLUE_BLACK', 54 | 'LABELBOLD' : 'YELLOW_BLACK', 55 | 'CONTROL' : 'GREEN_BLACK', 56 | 'WARNING' : 'RED_BLACK', 57 | 'CRITICAL' : 'BLACK_RED', 58 | 'GOOD' : 'GREEN_BLACK', 59 | 'GOODHL' : 'GREEN_BLACK', 60 | 'VERYGOOD' : 'BLACK_GREEN', 61 | 'CAUTION' : 'YELLOW_BLACK', 62 | 'CAUTIONHL' : 'BLACK_YELLOW', 63 | } 64 | 65 | class BlackOnWhiteTheme(ThemeManagers.ThemeManager): 66 | default_colors = { 67 | 'DEFAULT' : 'BLACK_WHITE', 68 | 'FORMDEFAULT' : 'BLACK_WHITE', 69 | 'NO_EDIT' : 'BLUE_WHITE', 70 | 'STANDOUT' : 'CYAN_WHITE', 71 | 'CURSOR' : 'BLACK_WHITE', 72 | 'CURSOR_INVERSE': 'WHITE_BLACK', 73 | 'LABEL' : 'RED_WHITE', 74 | 'LABELBOLD' : 'BLACK_WHITE', 75 | 'CONTROL' : 'BLUE_WHITE', 76 | 'WARNING' : 'RED_WHITE', 77 | 'CRITICAL' : 'BLACK_RED', 78 | 'GOOD' : 'GREEN_BLACK', 79 | 'GOODHL' : 'GREEN_WHITE', 80 | 'VERYGOOD' : 'WHITE_GREEN', 81 | 'CAUTION' : 'YELLOW_WHITE', 82 | 'CAUTIONHL' : 'BLACK_YELLOW', 83 | } 84 | 85 | class TransparentThemeDarkText(ThemeManagers.ThemeManager): 86 | _colors_to_define = ( 87 | ('BLACK_WHITE', curses.COLOR_BLACK, curses.COLOR_WHITE), 88 | ('BLUE_BLACK', curses.COLOR_BLUE, curses.COLOR_BLACK), 89 | ('CYAN_BLACK', curses.COLOR_CYAN, curses.COLOR_BLACK), 90 | ('GREEN_BLACK', curses.COLOR_GREEN, curses.COLOR_BLACK), 91 | ('MAGENTA_BLACK', curses.COLOR_MAGENTA, curses.COLOR_BLACK), 92 | ('RED_BLACK', curses.COLOR_RED, curses.COLOR_BLACK), 93 | ('YELLOW_BLACK', curses.COLOR_YELLOW, curses.COLOR_BLACK), 94 | ('BLACK_RED', curses.COLOR_BLACK, curses.COLOR_RED), 95 | ('BLACK_GREEN', curses.COLOR_BLACK, curses.COLOR_GREEN), 96 | ('BLACK_YELLOW', curses.COLOR_BLACK, curses.COLOR_YELLOW), 97 | 98 | ('BLUE_WHITE', curses.COLOR_BLUE, curses.COLOR_WHITE), 99 | ('CYAN_WHITE', curses.COLOR_CYAN, curses.COLOR_WHITE), 100 | ('GREEN_WHITE', curses.COLOR_GREEN, curses.COLOR_WHITE), 101 | ('MAGENTA_WHITE', curses.COLOR_MAGENTA, curses.COLOR_WHITE), 102 | ('RED_WHITE', curses.COLOR_RED, curses.COLOR_WHITE), 103 | ('YELLOW_WHITE', curses.COLOR_YELLOW, curses.COLOR_WHITE), 104 | 105 | ('BLACK_ON_DEFAULT', curses.COLOR_BLACK, -1), 106 | ('WHITE_ON_DEFAULT', curses.COLOR_WHITE, -1), 107 | ('BLUE_ON_DEFAULT', curses.COLOR_BLUE, -1), 108 | ('CYAN_ON_DEFAULT', curses.COLOR_CYAN, -1), 109 | ('GREEN_ON_DEFAULT', curses.COLOR_GREEN, -1), 110 | ('MAGENTA_ON_DEFAULT', curses.COLOR_MAGENTA, -1), 111 | ('RED_ON_DEFAULT', curses.COLOR_RED, -1), 112 | ('YELLOW_ON_DEFAULT', curses.COLOR_YELLOW, -1), 113 | ) 114 | 115 | default_colors = { 116 | 'DEFAULT' : 'BLACK_ON_DEFAULT', 117 | 'FORMDEFAULT' : 'BLACK_ON_DEFAULT', 118 | 'NO_EDIT' : 'BLUE_ON_DEFAULT', 119 | 'STANDOUT' : 'CYAN_ON_DEFAULT', 120 | 'CURSOR' : 'BLACK_WHITE', 121 | 'CURSOR_INVERSE': 'WHITE_BLACK', 122 | 'LABEL' : 'RED_ON_DEFAULT', 123 | 'LABELBOLD' : 'BLACK_ON_DEFAULT', 124 | 'CONTROL' : 'BLUE_ON_DEFAULT', 125 | 'WARNING' : 'RED_WHITE', 126 | 'CRITICAL' : 'BLACK_RED', 127 | 'GOOD' : 'GREEN_BLACK', 128 | 'GOODHL' : 'GREEN_WHITE', 129 | 'VERYGOOD' : 'WHITE_GREEN', 130 | 'CAUTION' : 'YELLOW_WHITE', 131 | 'CAUTIONHL' : 'BLACK_YELLOW', 132 | } 133 | 134 | 135 | def __init__(self, *args, **keywords): 136 | curses.use_default_colors() 137 | super(TransparentThemeDarkText, self).__init__(*args, **keywords) 138 | 139 | class TransparentThemeLightText(TransparentThemeDarkText): 140 | default_colors = { 141 | 'DEFAULT' : 'WHITE_ON_DEFAULT', 142 | 'FORMDEFAULT' : 'WHITE_ON_DEFAULT', 143 | 'NO_EDIT' : 'BLUE_ON_DEFAULT', 144 | 'STANDOUT' : 'CYAN_ON_DEFAULT', 145 | 'CURSOR' : 'WHITE_BLACK', 146 | 'CURSOR_INVERSE': 'BLACK_WHITE', 147 | 'LABEL' : 'RED_ON_DEFAULT', 148 | 'LABELBOLD' : 'BLACK_ON_DEFAULT', 149 | 'CONTROL' : 'BLUE_ON_DEFAULT', 150 | 'WARNING' : 'RED_BLACK', 151 | 'CRITICAL' : 'BLACK_RED', 152 | 'GOOD' : 'GREEN_BLACK', 153 | 'GOODHL' : 'GREEN_BLACK', 154 | 'VERYGOOD' : 'BLACK_GREEN', 155 | 'CAUTION' : 'YELLOW_BLACK', 156 | 'CAUTIONHL' : 'BLACK_YELLOW', 157 | } 158 | 159 | -------------------------------------------------------------------------------- /npyscreen/npysTree.py: -------------------------------------------------------------------------------- 1 | import weakref 2 | import collections 3 | 4 | class TreeData(object): 5 | # This is a new version of NPSTreeData that follows PEP8. 6 | CHILDCLASS = None 7 | def __init__(self, content=None, parent=None, selected=False, selectable=True, 8 | highlight=False, expanded=True, ignore_root=True, sort_function=None): 9 | self.set_parent(parent) 10 | self.set_content(content) 11 | self.selectable = selectable 12 | self.selected = selected 13 | self.highlight = highlight 14 | self.expanded = expanded 15 | self._children = [] 16 | self.ignore_root = ignore_root 17 | self.sort = False 18 | self.sort_function = sort_function 19 | self.sort_function_wrapper = True 20 | 21 | 22 | def get_content(self): 23 | return self.content 24 | 25 | def get_content_for_display(self): 26 | return str(self.content) 27 | 28 | def set_content(self, content): 29 | self.content = content 30 | 31 | def is_selected(self): 32 | return self.selected 33 | 34 | def is_highlighted(self): 35 | return self.highlight 36 | 37 | def set_parent(self, parent): 38 | if parent == None: 39 | self._parent = None 40 | else: 41 | self._parent = weakref.proxy(parent) 42 | 43 | def get_parent(self): 44 | return self._parent 45 | 46 | 47 | def find_depth(self, d=0): 48 | parent = self.get_parent() 49 | while parent: 50 | d += 1 51 | parent = parent.get_parent() 52 | return d 53 | # Recursive 54 | #if self._parent == None: 55 | # return d 56 | #else: 57 | # return(self._parent.findDepth(d+1)) 58 | 59 | def is_last_sibling(self): 60 | if self.get_parent(): 61 | if list(self.get_parent().get_children())[-1] == self: 62 | return True 63 | else: 64 | return False 65 | else: 66 | return None 67 | 68 | def has_children(self): 69 | if len(self._children) > 0: 70 | return True 71 | else: 72 | return False 73 | 74 | def get_children(self): 75 | for c in self._children: 76 | try: 77 | yield weakref.proxy(c) 78 | except: 79 | yield c 80 | 81 | def get_children_objects(self): 82 | return self._children[:] 83 | 84 | def _get_children_list(self): 85 | return self._children 86 | 87 | def new_child(self, *args, **keywords): 88 | if self.CHILDCLASS: 89 | cld = self.CHILDCLASS 90 | else: 91 | cld = type(self) 92 | c = cld(parent=self, *args, **keywords) 93 | self._children.append(c) 94 | return weakref.proxy(c) 95 | 96 | def remove_child(self, child): 97 | new_children = [] 98 | for ch in self._children: 99 | # do it this way because of weakref equality bug. 100 | if not ch.get_content() == child.get_content(): 101 | new_children.append(ch) 102 | else: 103 | ch.set_parent(None) 104 | self._children = new_children 105 | 106 | 107 | def create_wrapped_sort_function(self, this_function): 108 | def new_function(the_item): 109 | if the_item: 110 | the_real_item = the_item.get_content() 111 | return this_function(the_real_item) 112 | else: 113 | return the_item 114 | return new_function 115 | 116 | def walk_parents(self): 117 | p = self.get_parent() 118 | while p: 119 | yield p 120 | p = p.get_parent() 121 | 122 | def walk_tree(self, only_expanded=True, ignore_root=True, sort=None, sort_function=None): 123 | #Iterate over Tree 124 | if sort is None: 125 | sort = self.sort 126 | 127 | if sort_function is None: 128 | sort_function = self.sort_function 129 | 130 | # example sort function # sort = True 131 | # example sort function # def sort_function(the_item): 132 | # example sort function # import email.utils 133 | # example sort function # if the_item: 134 | # example sort function # if the_item.getContent(): 135 | # example sort function # frm = the_item.getContent().get('from') 136 | # example sort function # try: 137 | # example sort function # frm = email.utils.parseaddr(frm)[0] 138 | # example sort function # except: 139 | # example sort function # pass 140 | # example sort function # return frm 141 | # example sort function # else: 142 | # example sort function # return the_item 143 | #key = operator.methodcaller('getContent',) 144 | 145 | if self.sort_function_wrapper and sort_function: 146 | # def wrapped_sort_function(the_item): 147 | # if the_item: 148 | # the_real_item = the_item.getContent() 149 | # return sort_function(the_real_item) 150 | # else: 151 | # return the_item 152 | # _this_sort_function = wrapped_sort_function 153 | _this_sort_function = self.create_wrapped_sort_function(sort_function) 154 | else: 155 | _this_sort_function = sort_function 156 | 157 | key = _this_sort_function 158 | if not ignore_root: 159 | yield self 160 | nodes_to_yield = collections.deque() # better memory management than a list for pop(0) 161 | if self.expanded or not only_expanded: 162 | if sort: 163 | # This and the similar block below could be combined into a nested function 164 | if key: 165 | nodes_to_yield.extend(sorted(self.get_children(), key=key,)) 166 | else: 167 | nodes_to_yield.extend(sorted(self.get_children())) 168 | else: 169 | nodes_to_yield.extend(self.get_children()) 170 | while nodes_to_yield: 171 | child = nodes_to_yield.popleft() 172 | if child.expanded or not only_expanded: 173 | # This and the similar block above could be combined into a nested function 174 | if sort: 175 | if key: 176 | # must be reverse because about to use extendleft() below. 177 | nodes_to_yield.extendleft(sorted(child.get_children(), key=key, reverse=True)) 178 | else: 179 | nodes_to_yield.extendleft(sorted(child.get_children(), reverse=True)) 180 | else: 181 | #for node in child.getChildren(): 182 | # if node not in nodes_to_yield: 183 | # nodes_to_yield.appendleft(node) 184 | yield_these = list(child.get_children()) 185 | yield_these.reverse() 186 | nodes_to_yield.extendleft(yield_these) 187 | del yield_these 188 | yield child 189 | 190 | def get_tree_as_list(self, only_expanded=True, sort=None, key=None): 191 | _a = [] 192 | for node in self.walk_tree(only_expanded=only_expanded, ignore_root=self.ignore_root, sort=sort): 193 | try: 194 | _a.append(weakref.proxy(node)) 195 | except: 196 | _a.append(node) 197 | return _a 198 | -------------------------------------------------------------------------------- /npyscreen/npyspmfuncs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import curses 4 | import os 5 | 6 | class ResizeError(Exception): 7 | "The screen has been resized" 8 | 9 | def hidecursor(): 10 | try: 11 | curses.curs_set(0) 12 | except: 13 | pass 14 | 15 | def showcursor(): 16 | try: 17 | curses.curs_set(1) 18 | except: 19 | pass 20 | 21 | def CallSubShell(subshell): 22 | """Call this function if you need to execute an external command in a subshell (os.system). All the usual warnings apply -- the command line will be 23 | expanded by the shell, so make sure it is safe before passing it to this function.""" 24 | curses.def_prog_mode() 25 | #curses.endwin() # Probably causes a memory leak. 26 | 27 | rtn = os.system("%s" % (subshell)) 28 | curses.reset_prog_mode() 29 | if rtn is not 0: return False 30 | else: return True 31 | 32 | curses.reset_prog_mode() 33 | 34 | hide_cursor = hidecursor 35 | show_cursor = showcursor 36 | -------------------------------------------------------------------------------- /npyscreen/npyssafewrapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import curses 4 | import _curses 5 | #import curses.wrapper 6 | import locale 7 | import os 8 | #import pty 9 | import subprocess 10 | import sys 11 | import warnings 12 | 13 | _NEVER_RUN_INITSCR = True 14 | _SCREEN = None 15 | 16 | def wrapper_basic(call_function): 17 | #set the locale properly 18 | locale.setlocale(locale.LC_ALL, '') 19 | return curses.wrapper(call_function) 20 | 21 | #def wrapper(call_function): 22 | # locale.setlocale(locale.LC_ALL, '') 23 | # screen = curses.initscr() 24 | # curses.noecho() 25 | # curses.cbreak() 26 | # 27 | # return_code = call_function(screen) 28 | # 29 | # curses.nocbreak() 30 | # curses.echo() 31 | # curses.endwin() 32 | 33 | def wrapper(call_function, fork=None, reset=True): 34 | global _NEVER_RUN_INITSCR 35 | if fork: 36 | wrapper_fork(call_function, reset=reset) 37 | elif fork == False: 38 | wrapper_no_fork(call_function) 39 | else: 40 | if _NEVER_RUN_INITSCR: 41 | wrapper_no_fork(call_function) 42 | else: 43 | wrapper_fork(call_function, reset=reset) 44 | 45 | def wrapper_fork(call_function, reset=True): 46 | pid = os.fork() 47 | if pid: 48 | # Parent 49 | os.waitpid(pid, 0) 50 | if reset: 51 | external_reset() 52 | else: 53 | locale.setlocale(locale.LC_ALL, '') 54 | _SCREEN = curses.initscr() 55 | try: 56 | curses.start_color() 57 | except: 58 | pass 59 | _SCREEN.keypad(1) 60 | curses.noecho() 61 | curses.cbreak() 62 | curses.def_prog_mode() 63 | curses.reset_prog_mode() 64 | return_code = call_function(_SCREEN) 65 | _SCREEN.keypad(0) 66 | curses.echo() 67 | curses.nocbreak() 68 | curses.endwin() 69 | sys.exit(0) 70 | 71 | def external_reset(): 72 | subprocess.call(['reset', '-Q']) 73 | 74 | def wrapper_no_fork(call_function, reset=False): 75 | global _NEVER_RUN_INITSCR 76 | if not _NEVER_RUN_INITSCR: 77 | warnings.warn("""Repeated calls of endwin may cause a memory leak. Use wrapper_fork to avoid.""") 78 | global _SCREEN 79 | return_code = None 80 | if _NEVER_RUN_INITSCR: 81 | _NEVER_RUN_INITSCR = False 82 | locale.setlocale(locale.LC_ALL, '') 83 | _SCREEN = curses.initscr() 84 | try: 85 | curses.start_color() 86 | except: 87 | pass 88 | curses.noecho() 89 | curses.cbreak() 90 | _SCREEN.keypad(1) 91 | 92 | curses.noecho() 93 | curses.cbreak() 94 | _SCREEN.keypad(1) 95 | 96 | try: 97 | return_code = call_function(_SCREEN) 98 | finally: 99 | _SCREEN.keypad(0) 100 | curses.echo() 101 | curses.nocbreak() 102 | # Calling endwin() and then refreshing seems to cause a memory leak. 103 | curses.endwin() 104 | if reset: 105 | external_reset() 106 | return return_code 107 | -------------------------------------------------------------------------------- /npyscreen/proto_fm_screen_area.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import curses 4 | import curses.panel 5 | #import curses.wrapper 6 | from . import npyspmfuncs as pmfuncs 7 | import os 8 | from . import npysThemeManagers as ThemeManagers 9 | 10 | 11 | # For more complex method of getting the size of screen 12 | try: 13 | import fcntl, termios, struct, sys 14 | except: 15 | # Win32 platforms do not have fcntl 16 | pass 17 | 18 | 19 | APPLICATION_THEME_MANAGER = None 20 | 21 | def setTheme(theme): 22 | global APPLICATION_THEME_MANAGER 23 | APPLICATION_THEME_MANAGER = theme() 24 | 25 | def getTheme(): 26 | return APPLICATION_THEME_MANAGER 27 | 28 | 29 | 30 | class ScreenArea(object): 31 | BLANK_LINES_BASE =0 32 | BLANK_COLUMNS_RIGHT=0 33 | DEFAULT_NEXTRELY=2 34 | DEFAULT_LINES = 0 35 | DEFAULT_COLUMNS = 0 36 | SHOW_ATX = 0 37 | SHOW_ATY = 0 38 | 39 | """A screen area that can be safely resized. But this is a low-level class, not the 40 | object you are looking for.""" 41 | 42 | def __init__(self, lines=0, columns=0, 43 | minimum_lines = 24, 44 | minimum_columns = 80, 45 | show_atx = 0, 46 | show_aty = 0, 47 | **keywords): 48 | 49 | 50 | # Putting a default in here will override the system in _create_screen. For testing? 51 | if not lines: 52 | lines = self.__class__.DEFAULT_LINES 53 | if not columns: 54 | columns = self.__class__.DEFAULT_COLUMNS 55 | 56 | if lines: minimum_lines = lines 57 | if columns: minimum_columns = columns 58 | 59 | self.lines = lines #or 25 60 | self.columns = columns #or 80 61 | 62 | self.min_l = minimum_lines 63 | self.min_c = minimum_columns 64 | 65 | # Panels can be bigger than the screen area. These two variables 66 | # set which bit of the panel should be visible. 67 | # ie. They are about the virtual, not the physical, screen. 68 | self.show_from_y = 0 69 | self.show_from_x = 0 70 | self.show_atx = show_atx or self.__class__.SHOW_ATX 71 | self.show_aty = show_aty or self.__class__.SHOW_ATY 72 | self.ALL_SHOWN = False 73 | 74 | global APPLICATION_THEME_MANAGER 75 | if APPLICATION_THEME_MANAGER is None: 76 | self.theme_manager = ThemeManagers.ThemeManager() 77 | else: 78 | self.theme_manager = APPLICATION_THEME_MANAGER 79 | 80 | self.keypress_timeout = None 81 | 82 | 83 | self._create_screen() 84 | 85 | def _create_screen(self): 86 | 87 | try: 88 | if self.lines_were_auto_set: self.lines = None 89 | if self.cols_were_auto_set: self.columns = None 90 | except: pass 91 | 92 | 93 | if not self.lines: 94 | self.lines = self._max_physical()[0]+1 95 | self.lines_were_auto_set = True 96 | if not self.columns: 97 | self.columns = self._max_physical()[1]+1 98 | self.cols_were_auto_set = True 99 | 100 | if self.min_l > self.lines: 101 | self.lines = self.min_l 102 | 103 | if self.min_c > self.columns: 104 | self.columns = self.min_c 105 | 106 | #self.area = curses.newpad(self.lines, self.columns) 107 | self.curses_pad = curses.newpad(self.lines, self.columns) 108 | #self.max_y, self.max_x = self.lines, self.columns 109 | self.max_y, self.max_x = self.curses_pad.getmaxyx() 110 | 111 | def _max_physical(self): 112 | "How big is the physical screen?" 113 | # On OS X newwin does not correctly get the size of the screen. 114 | # let's see how big we could be: create a temp screen 115 | # and see the size curses makes it. No good to keep, though 116 | try: 117 | mxy, mxx = struct.unpack('hh', fcntl.ioctl(sys.stderr.fileno(), termios.TIOCGWINSZ, 'xxxx')) 118 | if (mxy, mxx) == (0,0): 119 | raise ValueError 120 | except (ValueError, NameError): 121 | mxy, mxx = curses.newwin(0,0).getmaxyx() 122 | 123 | # return safe values, i.e. slightly smaller. 124 | return (mxy-1, mxx-1) 125 | 126 | def useable_space(self, rely=0, relx=0): 127 | mxy, mxx = self.lines, self.columns 128 | return (mxy-rely, mxx-1-relx) # x - 1 because can't use last line bottom right. 129 | 130 | def widget_useable_space(self, rely=0, relx=0): 131 | #Slightly misreports space available. 132 | #mxy, mxx = self.lines, self.columns-1 133 | mxy, mxx = self.useable_space(rely=rely, relx=relx) 134 | return (mxy-self.BLANK_LINES_BASE, mxx-self.BLANK_COLUMNS_RIGHT) 135 | 136 | def refresh(self): 137 | pmfuncs.hide_cursor() 138 | _my, _mx = self._max_physical() 139 | self.curses_pad.move(0,0) 140 | 141 | # Since we can have pannels larger than the screen 142 | # let's allow for scrolling them 143 | 144 | # Getting strange errors on OS X, with curses sometimes crashing at this point. 145 | # Suspect screen size not updated in time. This try: seems to solve it with no ill effects. 146 | try: 147 | self.curses_pad.refresh(self.show_from_y,self.show_from_x,self.show_aty,self.show_atx,_my,_mx) 148 | except curses.error: 149 | pass 150 | if self.show_from_y is 0 and \ 151 | self.show_from_x is 0 and \ 152 | (_my >= self.lines) and \ 153 | (_mx >= self.columns): 154 | self.ALL_SHOWN = True 155 | 156 | else: 157 | self.ALL_SHOWN = False 158 | 159 | def erase(self): 160 | self.curses_pad.erase() 161 | self.refresh() 162 | 163 | -------------------------------------------------------------------------------- /npyscreen/utilNotify.py: -------------------------------------------------------------------------------- 1 | from . import fmPopup 2 | from . import wgmultiline 3 | from . import fmPopup 4 | import curses 5 | import textwrap 6 | 7 | class ConfirmCancelPopup(fmPopup.ActionPopup): 8 | def on_ok(self): 9 | self.value = True 10 | def on_cancel(self): 11 | self.value = False 12 | 13 | class YesNoPopup(ConfirmCancelPopup): 14 | OK_BUTTON_TEXT = "Yes" 15 | CANCEL_BUTTON_TEXT = "No" 16 | 17 | def _prepare_message(message): 18 | if isinstance(message, list) or isinstance(message, tuple): 19 | return "\n".join([ s.rstrip() for s in message]) 20 | #return "\n".join(message) 21 | else: 22 | return message 23 | 24 | def _wrap_message_lines(message, line_length): 25 | lines = [] 26 | for line in message.split('\n'): 27 | lines.extend(textwrap.wrap(line.rstrip(), line_length)) 28 | return lines 29 | 30 | def notify(message, title="Message", form_color='STANDOUT', 31 | wrap=True, wide=False, 32 | ): 33 | message = _prepare_message(message) 34 | if wide: 35 | F = fmPopup.PopupWide(name=title, color=form_color) 36 | else: 37 | F = fmPopup.Popup(name=title, color=form_color) 38 | F.preserve_selected_widget = True 39 | mlw = F.add(wgmultiline.Pager,) 40 | mlw_width = mlw.width-1 41 | if wrap: 42 | message = _wrap_message_lines(message, mlw_width) 43 | mlw.values = message 44 | F.display() 45 | 46 | def notify_confirm(message, title="Message", form_color='STANDOUT', wrap=True, wide=False, 47 | editw = 0,): 48 | message = _prepare_message(message) 49 | if wide: 50 | F = fmPopup.PopupWide(name=title, color=form_color) 51 | else: 52 | F = fmPopup.Popup(name=title, color=form_color) 53 | F.preserve_selected_widget = True 54 | mlw = F.add(wgmultiline.Pager,) 55 | mlw_width = mlw.width-1 56 | if wrap: 57 | message = _wrap_message_lines(message, mlw_width) 58 | else: 59 | message = message.split("\n") 60 | mlw.values = message 61 | F.editw = editw 62 | F.edit() 63 | 64 | def notify_wait(*args, **keywords): 65 | notify(*args, **keywords) 66 | curses.napms(3000) 67 | curses.flushinp() 68 | 69 | 70 | def notify_ok_cancel(message, title="Message", form_color='STANDOUT', wrap=True, editw = 0,): 71 | message = _prepare_message(message) 72 | F = ConfirmCancelPopup(name=title, color=form_color) 73 | F.preserve_selected_widget = True 74 | mlw = F.add(wgmultiline.Pager,) 75 | mlw_width = mlw.width-1 76 | if wrap: 77 | message = _wrap_message_lines(message, mlw_width) 78 | mlw.values = message 79 | F.editw = editw 80 | F.edit() 81 | return F.value 82 | 83 | def notify_yes_no(message, title="Message", form_color='STANDOUT', wrap=True, editw = 0,): 84 | message = _prepare_message(message) 85 | F = YesNoPopup(name=title, color=form_color) 86 | F.preserve_selected_widget = True 87 | mlw = F.add(wgmultiline.Pager,) 88 | mlw_width = mlw.width-1 89 | if wrap: 90 | message = _wrap_message_lines(message, mlw_width) 91 | mlw.values = message 92 | F.editw = editw 93 | F.edit() 94 | return F.value 95 | 96 | -------------------------------------------------------------------------------- /npyscreen/util_viewhelp.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | 3 | 4 | def view_help(message, title="Message", form_color="STANDOUT", scroll_exit=False, autowrap=False): 5 | from . import fmForm 6 | from . import wgmultiline 7 | F = fmForm.Form(name=title, color=form_color) 8 | mlw = F.add(wgmultiline.Pager, scroll_exit=True, autowrap=autowrap) 9 | mlw_width = mlw.width-1 10 | 11 | message_lines = [] 12 | for line in message.splitlines(): 13 | line = textwrap.wrap(line, mlw_width) 14 | if line == []: 15 | message_lines.append('') 16 | else: 17 | message_lines.extend(line) 18 | mlw.values = message_lines 19 | F.edit() 20 | del mlw 21 | del F 22 | 23 | -------------------------------------------------------------------------------- /npyscreen/wgFormControlCheckbox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pyton 2 | 3 | from . import wgcheckbox 4 | import weakref 5 | 6 | class FormControlCheckbox(wgcheckbox.Checkbox): 7 | def __init__(self, *args, **keywords): 8 | super(FormControlCheckbox, self).__init__(*args, **keywords) 9 | self._visibleWhenSelected = [] 10 | self._notVisibleWhenSelected = [] 11 | 12 | def addVisibleWhenSelected(self, w): 13 | """Add a widget to be visible only when this box is selected""" 14 | self._register(w, vws=True) 15 | 16 | def addInvisibleWhenSelected(self, w): 17 | self._register(w, vws=False) 18 | 19 | def _register(self, w, vws=True): 20 | if vws: 21 | working_list = self._visibleWhenSelected 22 | else: 23 | working_list = self._notVisibleWhenSelected 24 | 25 | if w in working_list: 26 | pass 27 | else: 28 | try: 29 | working_list.append(weakref.proxy(w)) 30 | except TypeError: 31 | working_list.append(w) 32 | 33 | self.updateDependents() 34 | 35 | def updateDependents(self): 36 | # This doesn't yet work. 37 | if self.value: 38 | for w in self._visibleWhenSelected: 39 | w.hidden = False 40 | w.editable = True 41 | for w in self._notVisibleWhenSelected: 42 | w.hidden = True 43 | w.editable = False 44 | else: 45 | for w in self._visibleWhenSelected: 46 | w.hidden = True 47 | w.editable = False 48 | for w in self._notVisibleWhenSelected: 49 | w.hidden = False 50 | w.editable = True 51 | self.parent.display() 52 | 53 | def h_toggle(self, *args): 54 | super(FormControlCheckbox, self).h_toggle(*args) 55 | self.updateDependents() 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /npyscreen/wgNMenuDisplay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | from . import muNewMenu as NewMenu 4 | from . import fmForm as Form 5 | from . import wgmultiline as multiline 6 | from . import wgannotatetextbox 7 | from . import utilNotify 8 | import weakref 9 | import curses 10 | 11 | class MenuViewerController(object): 12 | def __init__(self, menu=None): 13 | self.setMenu(menu) 14 | self.create() 15 | self._menuStack = [] 16 | self._editing = False 17 | 18 | def create(self): 19 | pass 20 | 21 | def setMenu(self, mnu): 22 | self._menuStack = [] 23 | self._setMenuWithoutResettingStack(mnu) 24 | 25 | def _setMenuWithoutResettingStack(self, mnu): 26 | self._menu = mnu 27 | self._DisplayArea._menuListWidget.value = None 28 | 29 | def _goToSubmenu(self, mnu): 30 | self._menuStack.append(self._menu) 31 | self._menu = mnu 32 | 33 | def _returnToPrevious(self): 34 | self._menu = self._menuStack.pop() 35 | 36 | 37 | def _executeSelection(self, sel): 38 | self._editing = False 39 | return sel() 40 | 41 | def edit(self): 42 | try: 43 | if self._menu is None: 44 | raise ValueError("No Menu Set") 45 | except AttributeError: 46 | raise ValueError("No Menu Set") 47 | self._editing = True 48 | while self._editing: 49 | if self._menu is not None: 50 | self._DisplayArea.name = self._menu.name 51 | if hasattr(self._menu, 'do_pre_display_function'): 52 | self._menu.do_pre_display_function() 53 | self._DisplayArea.display() 54 | self._DisplayArea._menuListWidget.value = None 55 | self._DisplayArea._menuListWidget.cursor_line = 0 56 | _menulines = [] 57 | _actionsToTake = [] 58 | if len(self._menuStack) > 0: 59 | _menulines.append(PreviousMenu()) 60 | _returnToPreviousSet = True 61 | _actionsToTake.append((self._returnToPrevious, )) 62 | else: 63 | _returnToPreviousSet = False 64 | 65 | for itm in self._menu.getItemObjects(): 66 | if isinstance(itm, NewMenu.MenuItem): 67 | _menulines.append(itm) 68 | _actionsToTake.append((self._executeSelection, itm.do)) 69 | elif isinstance(itm, NewMenu.NewMenu): 70 | _menulines.append(itm) 71 | _actionsToTake.append((self._goToSubmenu, itm)) 72 | else: 73 | raise ValueError("menu %s contains objects I don't know how to handle." % self._menu.name) 74 | 75 | 76 | self._DisplayArea._menuListWidget.values = _menulines 77 | self._DisplayArea.display() 78 | self._DisplayArea._menuListWidget.edit() 79 | _vlu = self._DisplayArea._menuListWidget.value 80 | if _vlu is None: 81 | self.editing = False 82 | return None 83 | try: 84 | _fctn = _actionsToTake[_vlu][0] 85 | _args = _actionsToTake[_vlu][1:] 86 | except IndexError: 87 | try: 88 | _fctn = _actionsToTake[_vlu] 89 | _args = [] 90 | except IndexError: 91 | # Menu must be empty. 92 | return False 93 | _return_value = _fctn(*_args) 94 | 95 | return _return_value 96 | 97 | 98 | class PreviousMenu(NewMenu.NewMenu): 99 | pass 100 | 101 | 102 | class MenuDisplay(MenuViewerController): 103 | def __init__(self, color='CONTROL', lines=15, columns=39, show_atx=5, show_aty=2, *args, **keywords): 104 | self._DisplayArea = MenuDisplayScreen(lines=lines, 105 | columns=columns, 106 | show_atx=show_atx, 107 | show_aty=show_aty, 108 | color=color) 109 | super(MenuDisplay, self).__init__(*args, **keywords) 110 | 111 | class MenuDisplayFullScreen(MenuViewerController): 112 | def __init__(self, *args, **keywords): 113 | self._DisplayArea = MenuDisplayScreen() 114 | super(MenuDisplayFullScreen, self).__init__(*args, **keywords) 115 | 116 | 117 | 118 | class wgMenuLine(wgannotatetextbox.AnnotateTextboxBaseRight): 119 | ANNOTATE_WIDTH = 3 120 | def getAnnotationAndColor(self,): 121 | try: 122 | if self.value.shortcut: 123 | return (self.safe_string(self.value.shortcut), 'LABEL') 124 | else: 125 | return ('', 'LABEL') 126 | except AttributeError: 127 | return ('', 'LABEL') 128 | 129 | def display_value(self, vl): 130 | # if this function raises an exception, it gets masked. 131 | # this is a bug. 132 | if not vl: 133 | return None 134 | if isinstance(vl, PreviousMenu): 135 | return '<-- Back' 136 | elif isinstance(vl, NewMenu.NewMenu): 137 | return ('%s -->' % self.safe_string(self.value.name)) 138 | elif isinstance(vl, NewMenu.MenuItem): 139 | return self.safe_string(self.value.getText()) 140 | else: 141 | return self.safe_string(str(self.value)) 142 | 143 | 144 | class wgMenuListWithSortCuts(multiline.MultiLineActionWithShortcuts): 145 | _contained_widgets = wgMenuLine 146 | def __init__(self, screen, allow_filtering=False, *args, **keywords): 147 | return super(wgMenuListWithSortCuts, self).__init__(screen, allow_filtering=allow_filtering, *args, **keywords) 148 | 149 | #def actionHighlighted(self, act_on_this, key_press): 150 | # if isinstance(act_on_this, MenuItem): 151 | # return act_on_this.do() 152 | # else: 153 | # return act_on_this 154 | def actionHighlighted(self, act_on_this, key_press): 155 | return self.h_select_exit(key_press) 156 | 157 | def display_value(self, vl): 158 | return vl 159 | 160 | class MenuDisplayScreen(Form.Form): 161 | def __init__(self, *args, **keywords): 162 | super(MenuDisplayScreen, self).__init__(*args, **keywords) 163 | #self._menuListWidget = self.add(multiline.MultiLine, return_exit=True) 164 | self._menuListWidget = self.add(wgMenuListWithSortCuts, return_exit=True) 165 | self._menuListWidget.add_handlers({ 166 | ord('q'): self._menuListWidget.h_exit_down, 167 | ord('Q'): self._menuListWidget.h_exit_down, 168 | ord('x'): self._menuListWidget.h_select_exit, 169 | curses.ascii.SP: self._menuListWidget.h_select_exit, 170 | }) 171 | 172 | class HasMenus(object): 173 | MENU_KEY = "^X" 174 | MENU_DISPLAY_TYPE = MenuDisplay 175 | MENU_WIDTH = None 176 | def initialize_menus(self): 177 | if self.MENU_WIDTH: 178 | self._NMDisplay = self.MENU_DISPLAY_TYPE(columns=self.MENU_WIDTH) 179 | else: 180 | self._NMDisplay = self.MENU_DISPLAY_TYPE() 181 | if not hasattr(self, '_NMenuList'): 182 | self._NMenuList = [] 183 | self._MainMenu = NewMenu.NewMenu 184 | self.add_handlers({self.__class__.MENU_KEY: self.root_menu}) 185 | 186 | def new_menu(self, name=None, *args, **keywords): 187 | if not hasattr(self, '_NMenuList'): 188 | self._NMenuList = [] 189 | _mnu = NewMenu.NewMenu(name=name, *args, **keywords) 190 | self._NMenuList.append(_mnu) 191 | return weakref.proxy(_mnu) 192 | 193 | def add_menu(self, *args, **keywords): 194 | return self.new_menu(*args, **keywords) 195 | 196 | def root_menu(self, *args): 197 | if len(self._NMenuList) == 1: 198 | self._NMDisplay.setMenu(self._NMenuList[0]) 199 | self._NMDisplay.edit() 200 | else: 201 | _root_menu = NewMenu.NewMenu(name="Menus") 202 | for mnu in self._NMenuList: 203 | _root_menu.addSubmenu(mnu) 204 | self._NMDisplay.setMenu(_root_menu) 205 | self._NMDisplay.edit() 206 | self.DISPLAY() 207 | 208 | def use_existing_menu(self, _mnu): 209 | if not hasattr(self, '_NMenuList'): 210 | self._NMenuList = [] 211 | self._NMenuList.append(_mnu) 212 | return weakref.proxy(_mnu) 213 | 214 | 215 | def popup_menu(self, menu): 216 | self._NMDisplay.setMenu(menu) 217 | self._NMDisplay.edit() 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /npyscreen/wgannotatetextbox.py: -------------------------------------------------------------------------------- 1 | from . import wgwidget 2 | from .wgtextbox import Textfield 3 | 4 | 5 | 6 | class AnnotateTextboxBase(wgwidget.Widget): 7 | """A base class intented for customization. Note in particular the annotationColor and annotationNoColor methods 8 | which you should override.""" 9 | ANNOTATE_WIDTH = 5 10 | 11 | def __init__(self, screen, value = False, annotation_color='CONTROL', **keywords): 12 | self.value = value 13 | self.annotation_color = annotation_color 14 | super(AnnotateTextboxBase, self).__init__(screen, **keywords) 15 | 16 | self._init_text_area(screen) 17 | 18 | if hasattr(self, 'display_value'): 19 | self.text_area.display_value = self.display_value 20 | self.show_bold = False 21 | self.highlight = False 22 | self.important = False 23 | self.hide = False 24 | 25 | def _init_text_area(self, screen): 26 | self.text_area = Textfield(screen, rely=self.rely, relx=self.relx+self.ANNOTATE_WIDTH, 27 | width=self.width-self.ANNOTATE_WIDTH, value=self.name) 28 | 29 | def _display_annotation_at(self): 30 | return (self.rely, self.relx) 31 | 32 | 33 | def getAnnotationAndColor(self): 34 | return ('xxx', 'CONTROL') 35 | 36 | def annotationColor(self): 37 | displayy, displayx = self._display_annotation_at() 38 | _annotation, _color = self.getAnnotationAndColor() 39 | self.parent.curses_pad.addnstr(displayy, displayx, _annotation, self.ANNOTATE_WIDTH, self.parent.theme_manager.findPair(self, _color)) 40 | 41 | def annotationNoColor(self): 42 | displayy, displayx = self._display_annotation_at() 43 | _annotation, _color = self.getAnnotationAndColor() 44 | self.parent.curses_pad.addnstr(displayy, displayx, _annotation, self.ANNOTATE_WIDTH) 45 | 46 | def update(self, clear=True): 47 | if clear: 48 | self.clear() 49 | if self.hidden: 50 | self.clear() 51 | return False 52 | if self.hide: 53 | return True 54 | 55 | self.text_area.value = self.value 56 | 57 | if self.do_colors(): 58 | self.annotationColor() 59 | else: 60 | self.annotationNoColor() 61 | 62 | 63 | if self.editing: 64 | self.text_area.highlight = True 65 | else: 66 | self.text_area.highlight = False 67 | 68 | if self.show_bold: 69 | self.text_area.show_bold = True 70 | else: 71 | self.text_area.show_bold = False 72 | 73 | if self.important: 74 | self.text_area.important = True 75 | else: 76 | self.text_area.important = False 77 | 78 | if self.highlight: 79 | self.text_area.highlight = True 80 | else: 81 | self.text_area.highlight = False 82 | 83 | self.text_area.update(clear=clear) 84 | 85 | def calculate_area_needed(self): 86 | return 1,0 87 | 88 | class AnnotateTextboxBaseRight(AnnotateTextboxBase): 89 | def _init_text_area(self, screen): 90 | self.text_area = Textfield(screen, rely=self.rely, relx=self.relx, 91 | width=self.width-self.ANNOTATE_WIDTH, value=self.name) 92 | 93 | def _display_annotation_at(self): 94 | return (self.rely, self.relx+self.width-self.ANNOTATE_WIDTH) 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /npyscreen/wgautocomplete.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import curses 3 | from . import wgtextbox as textbox 4 | from . import wgmultiline as multiline 5 | from . import wgtitlefield as titlefield 6 | import os 7 | from . import fmForm as Form 8 | from . import fmPopup as Popup 9 | 10 | class Autocomplete(textbox.Textfield): 11 | """This class is fairly useless, but override auto_complete to change that. See filename class for example""" 12 | def set_up_handlers(self): 13 | super(Autocomplete, self).set_up_handlers() 14 | 15 | self.handlers.update({curses.ascii.TAB: self.auto_complete}) 16 | 17 | def auto_complete(self, input): 18 | curses.beep() 19 | 20 | def get_choice(self, values): 21 | # If auto_complete needs the user to select from a list of values, this function lets them do that. 22 | 23 | #tmp_window = Form.TitleForm(name=self.name, framed=True) 24 | tmp_window = Popup.Popup(name=self.name, framed=True) 25 | sel = tmp_window.add_widget(multiline.MultiLine, 26 | values=values, 27 | value=self.value, 28 | return_exit=True, select_exit=True) 29 | #sel = multiline.MultiLine(tmp_window, values=values, value=self.value) 30 | tmp_window.display() 31 | sel.value=0 32 | sel.edit() 33 | return sel.value 34 | 35 | 36 | class Filename(Autocomplete): 37 | def auto_complete(self, input): 38 | # expand ~ 39 | self.value = os.path.expanduser(self.value) 40 | 41 | for i in range(1): 42 | dir, fname = os.path.split(self.value) 43 | # Let's have absolute paths. 44 | dir = os.path.abspath(dir) 45 | 46 | if self.value == '': 47 | self.value=dir 48 | break 49 | 50 | try: 51 | flist = os.listdir(dir) 52 | except: 53 | self.show_brief_message("Can't read directory!") 54 | break 55 | 56 | flist = [os.path.join(dir, x) for x in flist] 57 | possibilities = list(filter( 58 | (lambda x: os.path.split(x)[1].startswith(fname)), flist 59 | )) 60 | 61 | if len(possibilities) is 0: 62 | # can't complete 63 | curses.beep() 64 | break 65 | 66 | if len(possibilities) is 1: 67 | if self.value != possibilities[0]: 68 | self.value = possibilities[0] 69 | if os.path.isdir(self.value) \ 70 | and not self.value.endswith(os.sep): 71 | self.value = self.value + os.sep 72 | else: 73 | if not os.path.isdir(self.value): 74 | self.h_exit_down(None) 75 | break 76 | 77 | if len(possibilities) > 1: 78 | filelist = possibilities 79 | else: 80 | filelist = flist #os.listdir(os.path.dirname(self.value)) 81 | 82 | filelist = list(map((lambda x: os.path.normpath(os.path.join(self.value, x))), filelist)) 83 | files_only = [] 84 | dirs_only = [] 85 | 86 | if fname.startswith('.'): 87 | filelist = list(filter((lambda x: os.path.basename(x).startswith('.')), filelist)) 88 | else: 89 | filelist = list(filter((lambda x: not os.path.basename(x).startswith('.')), filelist)) 90 | 91 | for index1 in range(len(filelist)): 92 | if os.path.isdir(filelist[index1]) and not filelist[index1].endswith(os.sep): 93 | filelist[index1] = filelist[index1] + os.sep 94 | 95 | if os.path.isdir(filelist[index1]): 96 | dirs_only.append(filelist[index1]) 97 | 98 | else: 99 | files_only.append(filelist[index1]) 100 | 101 | dirs_only.sort() 102 | files_only.sort() 103 | combined_list = dirs_only + files_only 104 | combined_list.insert(0, self.value) 105 | self.value = combined_list[self.get_choice(combined_list)] 106 | break 107 | 108 | # Can't complete 109 | curses.beep() 110 | #os.path.normpath(self.value) 111 | os.path.normcase(self.value) 112 | self.cursor_position=len(self.value) 113 | 114 | class TitleFilename(titlefield.TitleText): 115 | _entry_type = Filename 116 | 117 | 118 | -------------------------------------------------------------------------------- /npyscreen/wgbutton.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import curses 3 | import locale 4 | import weakref 5 | from . import npysGlobalOptions as GlobalOptions 6 | from . import wgwidget as widget 7 | from . import wgcheckbox as checkbox 8 | 9 | class MiniButton(checkbox._ToggleControl): 10 | DEFAULT_CURSOR_COLOR = None 11 | def __init__(self, screen, name='Button', cursor_color=None, *args, **keywords): 12 | self.encoding = 'utf-8' 13 | self.cursor_color = cursor_color or self.__class__.DEFAULT_CURSOR_COLOR 14 | if GlobalOptions.ASCII_ONLY or locale.getpreferredencoding() == 'US-ASCII': 15 | self._force_ascii = True 16 | else: 17 | self._force_ascii = False 18 | self.name = self.safe_string(name) 19 | self.label_width = len(name) + 2 20 | super(MiniButton, self).__init__(screen, *args, **keywords) 21 | if 'color' in keywords: 22 | self.color = keywords['color'] 23 | else: 24 | self.color = 'CONTROL' 25 | 26 | def calculate_area_needed(self): 27 | return 1, self.label_width+2 28 | 29 | def update(self, clear=True): 30 | if clear: self.clear() 31 | if self.hidden: 32 | self.clear() 33 | return False 34 | 35 | 36 | if self.value and self.do_colors(): 37 | self.parent.curses_pad.addstr(self.rely, self.relx, '>', self.parent.theme_manager.findPair(self)) 38 | self.parent.curses_pad.addstr(self.rely, self.relx+self.width-1, '<', self.parent.theme_manager.findPair(self)) 39 | elif self.value: 40 | self.parent.curses_pad.addstr(self.rely, self.relx, '>') 41 | self.parent.curses_pad.addstr(self.rely, self.relx+self.width-1, '<') 42 | 43 | 44 | if self.editing: 45 | button_state = curses.A_STANDOUT 46 | else: 47 | button_state = curses.A_NORMAL 48 | 49 | button_name = self.name 50 | if isinstance(button_name, bytes): 51 | button_name = button_name.decode(self.encoding, 'replace') 52 | button_name = button_name.center(self.label_width) 53 | 54 | if self.do_colors(): 55 | if self.cursor_color: 56 | if self.editing: 57 | button_attributes = self.parent.theme_manager.findPair(self, self.cursor_color) 58 | else: 59 | button_attributes = self.parent.theme_manager.findPair(self, self.color) 60 | else: 61 | button_attributes = self.parent.theme_manager.findPair(self, self.color) | button_state 62 | else: 63 | button_attributes = button_state 64 | 65 | self.add_line(self.rely, self.relx+1, 66 | button_name, 67 | self.make_attributes_list(button_name, button_attributes), 68 | self.label_width 69 | ) 70 | 71 | 72 | class MiniButtonPress(MiniButton): 73 | # NB. The when_pressed_function functionality is potentially dangerous. It can set up 74 | # a circular reference that the garbage collector will never free. 75 | # If this is a risk for your program, it is best to subclass this object and 76 | # override when_pressed_function instead. Otherwise your program will leak memory. 77 | def __init__(self, screen, when_pressed_function=None, *args, **keywords): 78 | super(MiniButtonPress, self).__init__(screen, *args, **keywords) 79 | self.when_pressed_function = when_pressed_function 80 | 81 | def set_up_handlers(self): 82 | super(MiniButtonPress, self).set_up_handlers() 83 | 84 | self.handlers.update({ 85 | curses.ascii.NL: self.h_toggle, 86 | curses.ascii.CR: self.h_toggle, 87 | }) 88 | 89 | def destroy(self): 90 | self.when_pressed_function = None 91 | del self.when_pressed_function 92 | 93 | def h_toggle(self, ch): 94 | self.value = True 95 | self.display() 96 | if self.when_pressed_function: 97 | self.when_pressed_function() 98 | else: 99 | self.whenPressed() 100 | self.value = False 101 | self.display() 102 | 103 | def whenPressed(self): 104 | pass 105 | -------------------------------------------------------------------------------- /npyscreen/wgcheckbox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from .wgtextbox import Textfield 4 | from .wgwidget import Widget 5 | #from .wgmultiline import MultiLine 6 | from . import wgwidget as widget 7 | import curses 8 | 9 | class _ToggleControl(Widget): 10 | def set_up_handlers(self): 11 | super(_ToggleControl, self).set_up_handlers() 12 | 13 | self.handlers.update({ 14 | curses.ascii.SP: self.h_toggle, 15 | ord('x'): self.h_toggle, 16 | curses.ascii.NL: self.h_select_exit, 17 | curses.ascii.CR: self.h_select_exit, 18 | ord('j'): self.h_exit_down, 19 | ord('k'): self.h_exit_up, 20 | ord('h'): self.h_exit_left, 21 | ord('l'): self.h_exit_right, 22 | }) 23 | 24 | def h_toggle(self, ch): 25 | if self.value is False or self.value is None or self.value == 0: 26 | self.value = True 27 | else: 28 | self.value = False 29 | self.whenToggled() 30 | 31 | def whenToggled(self): 32 | pass 33 | 34 | def h_select_exit(self, ch): 35 | if not self.value: 36 | self.h_toggle(ch) 37 | self.editing = False 38 | self.how_exited = widget.EXITED_DOWN 39 | 40 | 41 | class CheckboxBare(_ToggleControl): 42 | False_box = '[ ]' 43 | True_box = '[X]' 44 | 45 | def __init__(self, screen, value = False, **keywords): 46 | super(CheckboxBare, self).__init__(screen, **keywords) 47 | self.value = value 48 | self.hide = False 49 | 50 | def calculate_area_needed(self): 51 | return 1, 4 52 | 53 | def update(self, clear=True): 54 | if clear: self.clear() 55 | if self.hidden: 56 | self.clear() 57 | return False 58 | if self.hide: return True 59 | 60 | if self.value: 61 | cb_display = self.__class__.True_box 62 | else: 63 | cb_display = self.__class__.False_box 64 | 65 | if self.do_colors(): 66 | self.parent.curses_pad.addstr(self.rely, self.relx, cb_display, self.parent.theme_manager.findPair(self, 'CONTROL')) 67 | else: 68 | self.parent.curses_pad.addstr(self.rely, self.relx, cb_display) 69 | 70 | if self.editing: 71 | if self.value: 72 | char_under_cur = 'X' 73 | else: 74 | char_under_cur = ' ' 75 | if self.do_colors(): 76 | self.parent.curses_pad.addstr(self.rely, self.relx + 1, char_under_cur, self.parent.theme_manager.findPair(self) | curses.A_STANDOUT) 77 | else: 78 | self.parent.curses_pad.addstr(self.rely, self.relx + 1, curses.A_STANDOUT) 79 | 80 | 81 | 82 | 83 | 84 | 85 | class Checkbox(_ToggleControl): 86 | False_box = '[ ]' 87 | True_box = '[X]' 88 | 89 | def __init__(self, screen, value = False, **keywords): 90 | self.value = value 91 | super(Checkbox, self).__init__(screen, **keywords) 92 | 93 | self._create_label_area(screen) 94 | 95 | 96 | self.show_bold = False 97 | self.highlight = False 98 | self.important = False 99 | self.hide = False 100 | 101 | def _create_label_area(self, screen): 102 | l_a_width = self.width - 5 103 | 104 | if l_a_width < 1: 105 | raise ValueError("Width of checkbox + label must be at least 6") 106 | 107 | self.label_area = Textfield(screen, rely=self.rely, relx=self.relx+5, 108 | width=self.width-5, value=self.name) 109 | 110 | 111 | def update(self, clear=True): 112 | if clear: self.clear() 113 | if self.hidden: 114 | self.clear() 115 | return False 116 | if self.hide: return True 117 | 118 | if self.value: 119 | cb_display = self.__class__.True_box 120 | else: 121 | cb_display = self.__class__.False_box 122 | 123 | if self.do_colors(): 124 | self.parent.curses_pad.addstr(self.rely, self.relx, cb_display, self.parent.theme_manager.findPair(self, 'CONTROL')) 125 | else: 126 | self.parent.curses_pad.addstr(self.rely, self.relx, cb_display) 127 | 128 | self._update_label_area() 129 | 130 | def _update_label_area(self, clear=True): 131 | self.label_area.value = self.name 132 | self._update_label_row_attributes(self.label_area, clear=clear) 133 | 134 | def _update_label_row_attributes(self, row, clear=True): 135 | if self.editing: 136 | row.highlight = True 137 | else: 138 | row.highlight = False 139 | 140 | if self.show_bold: 141 | row.show_bold = True 142 | else: 143 | row.show_bold = False 144 | 145 | if self.important: 146 | row.important = True 147 | else: 148 | row.important = False 149 | 150 | if self.highlight: 151 | row.highlight = True 152 | else: 153 | row.highlight = False 154 | 155 | row.update(clear=clear) 156 | 157 | def calculate_area_needed(self): 158 | return 1,0 159 | 160 | class CheckBox(Checkbox): 161 | pass 162 | 163 | 164 | class RoundCheckBox(Checkbox): 165 | False_box = '( )' 166 | True_box = '(X)' 167 | 168 | class CheckBoxMultiline(Checkbox): 169 | def _create_label_area(self, screen): 170 | self.label_area = [] 171 | for y in range(self.height): 172 | self.label_area.append( 173 | Textfield(screen, rely=self.rely+y, 174 | relx=self.relx+5, 175 | width=self.width-5, 176 | value=None) 177 | ) 178 | 179 | def _update_label_area(self, clear=True): 180 | for x in range(len(self.label_area)): 181 | if x >= len(self.name): 182 | self.label_area[x].value = '' 183 | self.label_area[x].hidden = True 184 | else: 185 | self.label_area[x].value = self.name[x] 186 | self.label_area[x].hidden = False 187 | self._update_label_row_attributes(self.label_area[x], clear=clear) 188 | 189 | def calculate_area_needed(self): 190 | return 0,0 191 | 192 | class RoundCheckBoxMultiline(CheckBoxMultiline): 193 | False_box = '( )' 194 | True_box = '(X)' 195 | 196 | 197 | -------------------------------------------------------------------------------- /npyscreen/wgcombobox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import curses 3 | 4 | from . import wgtextbox as textbox 5 | from . import wgmultiline as multiline 6 | from . import fmForm as Form 7 | from . import fmPopup as Popup 8 | from . import wgtitlefield as titlefield 9 | 10 | class ComboBox(textbox.Textfield): 11 | ENSURE_STRING_VALUE = False 12 | def __init__(self, screen, value = None, values=None,**keywords): 13 | self.values = values or [] 14 | self.value = value or None 15 | if value is 0: 16 | self.value = 0 17 | super(ComboBox, self).__init__(screen, **keywords) 18 | 19 | def display_value(self, vl): 20 | """Overload this function to change how values are displayed. 21 | Should accept one argument (the object to be represented), and return a string.""" 22 | return str(vl) 23 | 24 | def update(self, **keywords): 25 | keywords.update({'cursor': False}) 26 | super(ComboBox, self).update(**keywords) 27 | 28 | def _print(self): 29 | if self.value == None or self.value is '': 30 | printme = '-unset-' 31 | else: 32 | try: 33 | printme = self.display_value(self.values[self.value]) 34 | except IndexError: 35 | printme = '-error-' 36 | if self.do_colors(): 37 | self.parent.curses_pad.addnstr(self.rely, self.relx, printme, self.width, self.parent.theme_manager.findPair(self)) 38 | else: 39 | self.parent.curses_pad.addnstr(self.rely, self.relx, printme, self.width) 40 | 41 | 42 | def edit(self): 43 | #We'll just use the widget one 44 | super(textbox.Textfield, self).edit() 45 | 46 | def set_up_handlers(self): 47 | super(textbox.Textfield, self).set_up_handlers() 48 | 49 | self.handlers.update({curses.ascii.SP: self.h_change_value, 50 | #curses.ascii.TAB: self.h_change_value, 51 | curses.ascii.NL: self.h_change_value, 52 | curses.ascii.CR: self.h_change_value, 53 | ord('x'): self.h_change_value, 54 | ord('k'): self.h_exit_up, 55 | ord('j'): self.h_exit_down, 56 | ord('h'): self.h_exit_left, 57 | ord('l'): self.h_exit_right, 58 | }) 59 | 60 | def h_change_value(self, input): 61 | "Pop up a window in which to select the values for the field" 62 | F = Popup.Popup(name = self.name) 63 | l = F.add(multiline.MultiLine, 64 | values = [self.display_value(x) for x in self.values], 65 | return_exit=True, select_exit=True, 66 | value=self.value) 67 | F.display() 68 | l.edit() 69 | self.value = l.value 70 | 71 | 72 | class TitleCombo(titlefield.TitleText): 73 | _entry_type = ComboBox 74 | 75 | def get_values(self): 76 | try: 77 | return self.entry_widget.values 78 | except: 79 | try: 80 | return self.__tmp_values 81 | except: 82 | return None 83 | 84 | def set_values(self, values): 85 | try: 86 | self.entry_widget.values = values 87 | except: 88 | # probably trying to set the value before the textarea is initialised 89 | self.__tmp_values = values 90 | 91 | def del_values(self): 92 | del self.entry_widget.values 93 | 94 | values = property(get_values, set_values, del_values) 95 | 96 | -------------------------------------------------------------------------------- /npyscreen/wgdatecombo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from . import wgtextbox as textbox 3 | from . import wgtitlefield as titlefield 4 | from . import wgmonthbox as monthbox 5 | from . import fmPopup as Popup 6 | from . import fmForm as Form 7 | import datetime 8 | import curses 9 | 10 | 11 | class DateCombo(textbox.Textfield, monthbox.DateEntryBase): 12 | def __init__(self, screen, allowPastDate=True, allowTodaysDate=True, allowClear=True, **keywords): 13 | super(DateCombo, self).__init__(screen, **keywords) 14 | self.allow_date_in_past = allowPastDate 15 | self.allow_todays_date = allowTodaysDate 16 | self.allow_clear = allowClear 17 | 18 | def update(self, **keywords): 19 | keywords.update({'cursor': False}) 20 | super(DateCombo, self).update(**keywords) 21 | 22 | def edit(self): 23 | #We'll just use the widget one 24 | super(textbox.Textfield, self).edit() 25 | 26 | def display_value(self, vl): 27 | if self.value: 28 | try: 29 | # in python 2.4 this will raise ValueError if date is before 1900 30 | #return self.value.strftime("%a, %d %B, %Y") 31 | return self.value.strftime("%d %B, %Y") 32 | except ValueError: 33 | return self.value.isoformat() 34 | except AttributeError: 35 | return "-error-" 36 | else: 37 | return "-unset-" 38 | 39 | def _print(self): 40 | if self.do_colors(): 41 | self.parent.curses_pad.addnstr(self.rely, self.relx, self.display_value(self.value), self.width, self.parent.theme_manager.findPair(self,)) 42 | else: 43 | self.parent.curses_pad.addnstr(self.rely, self.relx, self.display_value(self.value), self.width) 44 | 45 | def h_change_value(self, *arg): 46 | # Remember to leave extra space at the end of the popup, or the clear function can't work properly. 47 | _old_date = self.value 48 | F = Popup.Popup(name = self.name, 49 | columns = (monthbox.MonthBox.DAY_FIELD_WIDTH * 7) + 4, 50 | lines=13, 51 | ) 52 | #F = Form.Form() 53 | m = F.add(monthbox.MonthBox, 54 | allowPastDate = self.allow_date_in_past, 55 | allowTodaysDate = self.allow_todays_date, 56 | use_max_space = True, 57 | use_datetime = self.use_datetime, 58 | allowClear = self.allow_clear, 59 | ) 60 | try: 61 | # Is it a date, do we think? 62 | self.value.isoformat() 63 | m.value = self.value 64 | except: 65 | # if not, we could do worse than today 66 | m.value = self.date_or_datetime().today() 67 | # But make sure that that is acceptable 68 | m._check_today_validity() 69 | F.display() 70 | m.edit() 71 | self.value = m.value 72 | # The following is perhaps confusing. 73 | #if self.value == _old_date: 74 | # self.h_exit_down('') 75 | 76 | def set_up_handlers(self): 77 | super(textbox.Textfield, self).set_up_handlers() 78 | self.handlers.update({curses.ascii.SP: self.h_change_value, 79 | #curses.ascii.TAB: self.h_change_value, 80 | curses.ascii.CR: self.h_change_value, 81 | curses.ascii.NL: self.h_change_value, 82 | ord('x'): self.h_change_value, 83 | ord('j'): self.h_exit_down, 84 | ord('k'): self.h_exit_up, 85 | }) 86 | 87 | class TitleDateCombo(titlefield.TitleText): 88 | _entry_type = DateCombo 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /npyscreen/wgfilenamecombo.py: -------------------------------------------------------------------------------- 1 | from . import fmFileSelector 2 | from . import wgcombobox 3 | 4 | class FilenameCombo(wgcombobox.ComboBox): 5 | def __init__(self, screen, 6 | # The following are all options taken from the FileSelector 7 | select_dir=False, #Select a dir, not a file 8 | must_exist=False, #Selected File must already exist 9 | confirm_if_exists=False, 10 | sort_by_extension=True, 11 | *args, **keywords): 12 | self.select_dir = select_dir 13 | self.must_exist = must_exist 14 | self.confirm_if_exists = confirm_if_exists 15 | self.sort_by_extension = sort_by_extension 16 | 17 | super(FilenameCombo, self).__init__(screen, *args, **keywords) 18 | 19 | def _print(self): 20 | if self.value == None: 21 | printme = '- Unset -' 22 | else: 23 | try: 24 | printme = self.display_value(self.value) 25 | except IndexError: 26 | printme = '-error-' 27 | if self.do_colors(): 28 | self.parent.curses_pad.addnstr(self.rely, self.relx, printme, self.width, self.parent.theme_manager.findPair(self)) 29 | else: 30 | self.parent.curses_pad.addnstr(self.rely, self.relx, printme, self.width) 31 | 32 | 33 | 34 | def h_change_value(self, *args, **keywords): 35 | self.value = fmFileSelector.selectFile( 36 | starting_value = self.value, 37 | select_dir = self.select_dir, 38 | must_exist = self.must_exist, 39 | confirm_if_exists = self.confirm_if_exists, 40 | sort_by_extension = self.sort_by_extension 41 | ) 42 | if self.value == '': 43 | self.value = None 44 | self.display() 45 | 46 | 47 | class TitleFilenameCombo(wgcombobox.TitleCombo): 48 | _entry_type = FilenameCombo -------------------------------------------------------------------------------- /npyscreen/wggridcoltitles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | import curses 4 | from . import wggrid as grid 5 | from . import wgtextbox as textbox 6 | 7 | class GridColTitles(grid.SimpleGrid): 8 | additional_y_offset = 2 9 | _col_widgets = textbox.Textfield 10 | def __init__(self, screen, col_titles = None, *args, **keywords): 11 | if col_titles: 12 | self.col_titles = col_titles 13 | else: 14 | self.col_titles = [] 15 | super(GridColTitles, self).__init__(screen, *args, **keywords) 16 | 17 | def make_contained_widgets(self): 18 | super(GridColTitles, self).make_contained_widgets() 19 | self._my_col_titles = [] 20 | for title_cell in range(self.columns): 21 | x_offset = title_cell * (self._column_width+self.col_margin) 22 | self._my_col_titles.append(self._col_widgets(self.parent, rely=self.rely, relx = self.relx + x_offset, width=self._column_width, height=1)) 23 | 24 | 25 | def update(self, clear=True): 26 | super(GridColTitles, self).update(clear = True) 27 | 28 | _title_counter = 0 29 | for title_cell in self._my_col_titles: 30 | try: 31 | title_text = self.col_titles[self.begin_col_display_at+_title_counter] 32 | except IndexError: 33 | title_text = None 34 | self.update_title_cell(title_cell, title_text) 35 | _title_counter += 1 36 | 37 | self.parent.curses_pad.hline(self.rely+1, self.relx, curses.ACS_HLINE, self.width) 38 | 39 | def update_title_cell(self, cell, cell_title): 40 | cell.value = cell_title 41 | cell.update() 42 | 43 | -------------------------------------------------------------------------------- /npyscreen/wgmultilineeditable.py: -------------------------------------------------------------------------------- 1 | import curses 2 | from . import wgwidget 3 | from . import wgmultiline 4 | from . import wgtextbox as textbox 5 | from . import wgboxwidget 6 | 7 | 8 | class MultiLineEditable(wgmultiline.MultiLine): 9 | _contained_widgets = textbox.Textfield 10 | CHECK_VALUE = True 11 | ALLOW_CONTINUE_EDITING = True 12 | CONTINUE_EDITING_AFTER_EDITING_ONE_LINE = True 13 | 14 | def get_new_value(self): 15 | return '' 16 | 17 | def check_line_value(self, vl): 18 | if not vl: 19 | return False 20 | else: 21 | return True 22 | 23 | def edit_cursor_line_value(self): 24 | if len(self.values) == 0: 25 | self.insert_line_value() 26 | return False 27 | try: 28 | active_line = self._my_widgets[(self.cursor_line-self.start_display_at)] 29 | except IndexError: 30 | self._my_widgets[0] 31 | self.cursor_line = 0 32 | self.insert_line_value() 33 | return True 34 | active_line.highlight = False 35 | active_line.edit() 36 | try: 37 | self.values[self.cursor_line] = active_line.value 38 | except IndexError: 39 | self.values.append(active_line.value) 40 | if not self.cursor_line: 41 | self.cursor_line = 0 42 | self.cursor_line = len(self.values) - 1 43 | self.reset_display_cache() 44 | 45 | if self.CHECK_VALUE: 46 | if not self.check_line_value(self.values[self.cursor_line]): 47 | self.delete_line_value() 48 | return False 49 | 50 | self.display() 51 | return True 52 | 53 | def insert_line_value(self): 54 | if self.cursor_line is None: 55 | self.cursor_line = 0 56 | self.values.insert(self.cursor_line, self.get_new_value()) 57 | self.display() 58 | cont = self.edit_cursor_line_value() 59 | if cont and self.ALLOW_CONTINUE_EDITING: 60 | self._continue_editing() 61 | 62 | def delete_line_value(self): 63 | if len(self.values) > 0: 64 | del self.values[self.cursor_line] 65 | self.display() 66 | 67 | def _continue_editing(self): 68 | active_line = self._my_widgets[(self.cursor_line-self.start_display_at)] 69 | continue_editing = self.ALLOW_CONTINUE_EDITING 70 | if hasattr(active_line, 'how_exited'): 71 | while active_line.how_exited == wgwidget.EXITED_DOWN and continue_editing: 72 | self.values.insert(self.cursor_line+1, self.get_new_value()) 73 | self.cursor_line += 1 74 | self.display() 75 | continue_editing = self.edit_cursor_line_value() 76 | active_line = self._my_widgets[(self.cursor_line-self.start_display_at)] 77 | 78 | 79 | 80 | 81 | def h_insert_next_line(self, ch): 82 | if len(self.values) == self.cursor_line - 1 or len(self.values) == 0: 83 | self.values.append(self.get_new_value()) 84 | self.cursor_line += 1 85 | self.display() 86 | cont = self.edit_cursor_line_value() 87 | if cont and self.ALLOW_CONTINUE_EDITING: 88 | self._continue_editing() 89 | 90 | else: 91 | self.cursor_line += 1 92 | self.insert_line_value() 93 | 94 | def h_edit_cursor_line_value(self, ch): 95 | continue_line = self.edit_cursor_line_value() 96 | if continue_line and self.CONTINUE_EDITING_AFTER_EDITING_ONE_LINE: 97 | self._continue_editing() 98 | 99 | def h_insert_value(self, ch): 100 | return self.insert_line_value() 101 | 102 | def h_delete_line_value(self, ch): 103 | self.delete_line_value() 104 | 105 | def set_up_handlers(self): 106 | super(MultiLineEditable, self).set_up_handlers() 107 | self.handlers.update ( { 108 | ord('i'): self.h_insert_value, 109 | ord('o'): self.h_insert_next_line, 110 | curses.ascii.CR: self.h_edit_cursor_line_value, 111 | curses.ascii.NL: self.h_edit_cursor_line_value, 112 | curses.ascii.SP: self.h_edit_cursor_line_value, 113 | 114 | curses.ascii.DEL: self.h_delete_line_value, 115 | curses.ascii.BS: self.h_delete_line_value, 116 | curses.KEY_BACKSPACE: self.h_delete_line_value, 117 | } ) 118 | 119 | class MultiLineEditableTitle(wgmultiline.TitleMultiLine): 120 | _entry_type = MultiLineEditable 121 | 122 | class MultiLineEditableBoxed(wgboxwidget.BoxTitle): 123 | _contained_widget = MultiLineEditable 124 | -------------------------------------------------------------------------------- /npyscreen/wgmultilinetreeselectable.py: -------------------------------------------------------------------------------- 1 | import curses 2 | from . import wgmultilinetree 3 | 4 | class TreeLineSelectable(wgmultilinetree.TreeLine): 5 | # NB - as print is currently defined, it is assumed that these will 6 | # NOT contain multi-width characters, and that len() will correctly 7 | # give an indication of the correct offset 8 | CAN_SELECT = '[ ]' 9 | CAN_SELECT_SELECTED = '[*]' 10 | CANNOT_SELECT = ' ' 11 | CANNOT_SELECT_SELECTED = ' * ' 12 | 13 | def _print_select_controls(self): 14 | SELECT_DISPLAY = None 15 | 16 | if self._tree_real_value.selectable: 17 | if self.value.selected: 18 | SELECT_DISPLAY = self.CAN_SELECT_SELECTED 19 | else: 20 | SELECT_DISPLAY = self.CAN_SELECT 21 | else: 22 | if self.value.selected: 23 | SELECT_DISPLAY = self.CANNOT_SELECT_SELECTED 24 | else: 25 | SELECT_DISPLAY = self.CANNOT_SELECT 26 | 27 | 28 | if self.do_colors(): 29 | attribute_list = self.parent.theme_manager.findPair(self, 'CONTROL') 30 | else: 31 | attribute_list = curses.A_NORMAL 32 | 33 | 34 | #python2 compatibility 35 | if isinstance(SELECT_DISPLAY, bytes): 36 | SELECT_DISPLAY = SELECT_DISPLAY.decode() 37 | 38 | 39 | 40 | self.add_line(self.rely, 41 | self.left_margin+self.relx, 42 | SELECT_DISPLAY, 43 | self.make_attributes_list(SELECT_DISPLAY, attribute_list), 44 | self.width-self.left_margin, 45 | ) 46 | 47 | return len(SELECT_DISPLAY) 48 | 49 | 50 | def _print(self, left_margin=0): 51 | if not hasattr(self._tree_real_value, 'selected'): 52 | return None 53 | self.left_margin = left_margin 54 | self.parent.curses_pad.bkgdset(' ',curses.A_NORMAL) 55 | self.left_margin += self._print_tree(self.relx) 56 | 57 | self.left_margin += self._print_select_controls() + 1 58 | 59 | 60 | if self.highlight: 61 | self.parent.curses_pad.bkgdset(' ',curses.A_STANDOUT) 62 | super(wgmultilinetree.TreeLine, self)._print() 63 | 64 | 65 | class TreeLineSelectableAnnotated(TreeLineSelectable, wgmultilinetree.TreeLineAnnotated): 66 | def _print(self, left_margin=0): 67 | if not hasattr(self._tree_real_value, 'selected'): 68 | return None 69 | self.left_margin = left_margin 70 | self.parent.curses_pad.bkgdset(' ',curses.A_NORMAL) 71 | self.left_margin += self._print_tree(self.relx) 72 | self.left_margin += self._print_select_controls() + 1 73 | if self.do_colors(): 74 | self.left_margin += self.annotationColor(self.left_margin+self.relx) 75 | else: 76 | self.left_margin += self.annotationNoColor(self.left_margin+self.relx) 77 | if self.highlight: 78 | self.parent.curses_pad.bkgdset(' ',curses.A_STANDOUT) 79 | super(wgmultilinetree.TreeLine, self)._print() 80 | 81 | 82 | 83 | class MLTreeMultiSelect(wgmultilinetree.MLTree): 84 | _contained_widgets = TreeLineSelectable 85 | def __init__(self, screen, select_cascades=True, *args, **keywords): 86 | super(MLTreeMultiSelect, self).__init__(screen, *args, **keywords) 87 | self.select_cascades = select_cascades 88 | 89 | def h_select(self, ch): 90 | vl = self.values[self.cursor_line] 91 | vl_to_set = not vl.selected 92 | if self.select_cascades: 93 | for v in self._walk_tree(vl, only_expanded=False, ignore_root=False): 94 | if v.selectable: 95 | v.selected = vl_to_set 96 | else: 97 | vl.selected = vl_to_set 98 | if self.select_exit: 99 | self.editing = False 100 | self.how_exited = True 101 | self.display() 102 | 103 | def get_selected_objects(self, return_node=True): 104 | for v in self._walk_tree(self._myFullValues, only_expanded=False, ignore_root=False): 105 | if v.selected: 106 | if return_node: 107 | yield v 108 | else: 109 | yield self._get_content(v) 110 | 111 | class MLTreeMultiSelectAnnotated(MLTreeMultiSelect): 112 | _contained_widgets = TreeLineSelectableAnnotated 113 | -------------------------------------------------------------------------------- /npyscreen/wgmultiselect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from . import wgmultiline as multiline 3 | from . import wgselectone as selectone 4 | from . import wgcheckbox as checkbox 5 | import curses 6 | 7 | class MultiSelect(selectone.SelectOne): 8 | _contained_widgets = checkbox.Checkbox 9 | 10 | def set_up_handlers(self): 11 | super(MultiSelect, self).set_up_handlers() 12 | self.handlers.update({ 13 | ord("x"): self.h_select_toggle, 14 | curses.ascii.SP: self.h_select_toggle, 15 | ord("X"): self.h_select, 16 | "^U": self.h_select_none, 17 | }) 18 | 19 | def h_select_none(self, input): 20 | self.value = [] 21 | 22 | def h_select_toggle(self, input): 23 | if self.cursor_line in self.value: 24 | self.value.remove(self.cursor_line) 25 | else: 26 | self.value.append(self.cursor_line) 27 | 28 | def h_set_filtered_to_selected(self, ch): 29 | self.value = self._filtered_values_cache 30 | 31 | def h_select_exit(self, ch): 32 | if not self.cursor_line in self.value: 33 | self.value.append(self.cursor_line) 34 | if self.return_exit: 35 | self.editing = False 36 | self.how_exited=True 37 | 38 | def get_selected_objects(self): 39 | if self.value == [] or self.value == None: 40 | return None 41 | else: 42 | return [self.values[x] for x in self.value] 43 | 44 | class MultiSelectAction(MultiSelect): 45 | always_act_on_many = False 46 | def actionHighlighted(self, act_on_this, key_press): 47 | "Override this Method" 48 | pass 49 | 50 | def actionSelected(self, act_on_these, keypress): 51 | "Override this Method" 52 | pass 53 | 54 | def set_up_handlers(self): 55 | super(MultiSelectAction, self).set_up_handlers() 56 | self.handlers.update ( { 57 | curses.ascii.NL: self.h_act_on_highlighted, 58 | curses.ascii.CR: self.h_act_on_highlighted, 59 | ord(';'): self.h_act_on_selected, 60 | # "^L": self.h_set_filtered_to_selected, 61 | curses.ascii.SP: self.h_act_on_highlighted, 62 | } ) 63 | 64 | def h_act_on_highlighted(self, ch): 65 | if self.always_act_on_many: 66 | return self.h_act_on_selected(ch) 67 | else: 68 | return self.actionHighlighted(self.values[self.cursor_line], ch) 69 | 70 | def h_act_on_selected(self, ch): 71 | if self.vale: 72 | return self.actionSelected(self.get_selected_objects(), ch) 73 | 74 | 75 | class MultiSelectFixed(MultiSelect): 76 | # This does not allow the user to change Values, but does allow the user to move around. 77 | # Useful for displaying Data. 78 | def user_set_value(self, input): 79 | pass 80 | 81 | def set_up_handlers(self): 82 | super(MultiSelectFixed, self).set_up_handlers() 83 | self.handlers.update({ 84 | ord("x"): self.user_set_value, 85 | ord("X"): self.user_set_value, 86 | curses.ascii.SP: self.user_set_value, 87 | "^U": self.user_set_value, 88 | curses.ascii.NL: self.h_exit_down, 89 | curses.ascii.CR: self.h_exit_down, 90 | 91 | }) 92 | 93 | class TitleMultiSelect(multiline.TitleMultiLine): 94 | _entry_type = MultiSelect 95 | 96 | 97 | 98 | class TitleMultiSelectFixed(multiline.TitleMultiLine): 99 | _entry_type = MultiSelectFixed 100 | 101 | 102 | -------------------------------------------------------------------------------- /npyscreen/wgmultiselecttree.py: -------------------------------------------------------------------------------- 1 | from . import wgmultilinetree as multilinetree 2 | from . import wgcheckbox as checkbox 3 | import weakref 4 | import curses 5 | 6 | 7 | class MultiSelectTree(multilinetree.SelectOneTree): 8 | _contained_widgets = checkbox.Checkbox 9 | 10 | def set_up_handlers(self): 11 | super(MultiSelectTree, self).set_up_handlers() 12 | self.handlers.update({ 13 | ord("x"): self.h_select_toggle, 14 | curses.ascii.SP: self.h_select_toggle, 15 | ord("X"): self.h_select, 16 | "^U": self.h_select_none, 17 | }) 18 | 19 | def h_select_none(self, input): 20 | self.value = [] 21 | 22 | def h_select_toggle(self, input): 23 | try: 24 | working_with = weakref.proxy(self.values[self.cursor_line]) 25 | except TypeError: 26 | working_with = self.values[self.cursor_line] 27 | if working_with in self.value: 28 | self.value.remove(working_with) 29 | else: 30 | self.value.append(working_with) 31 | 32 | def h_set_filtered_to_selected(self, ch): 33 | self.value = self.get_filtered_values() 34 | 35 | def h_select_exit(self, ch): 36 | try: 37 | working_with = weakref.proxy(self.values[self.cursor_line]) 38 | except TypeError: 39 | working_with = self.values[self.cursor_line] 40 | 41 | if not working_with in self.value: 42 | self.value.append(working_with) 43 | if self.return_exit: 44 | self.editing = False 45 | self.how_exited=True 46 | -------------------------------------------------------------------------------- /npyscreen/wgpassword.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import curses 3 | from .wgtextbox import Textfield 4 | from . import wgtitlefield as titlefield 5 | 6 | 7 | class PasswordEntry(Textfield): 8 | def _print(self): 9 | strlen = len(self.value) 10 | if self.maximum_string_length < strlen: 11 | tmp_x = self.relx 12 | for i in range(self.maximum_string_length): 13 | self.parent.curses_pad.addch(self.rely, tmp_x, '-') 14 | tmp_x += 1 15 | 16 | else: 17 | tmp_x = self.relx 18 | for i in range(strlen): 19 | self.parent.curses_pad.addstr(self.rely, tmp_x, '-') 20 | tmp_x += 1 21 | 22 | class TitlePassword(titlefield.TitleText): 23 | _entry_type = PasswordEntry 24 | 25 | -------------------------------------------------------------------------------- /npyscreen/wgselectone.py: -------------------------------------------------------------------------------- 1 | from . import wgmultiline as multiline 2 | from . import wgcheckbox as checkbox 3 | 4 | class SelectOne(multiline.MultiLine): 5 | _contained_widgets = checkbox.RoundCheckBox 6 | 7 | def update(self, clear=True): 8 | if self.hidden: 9 | self.clear() 10 | return False 11 | # Make sure that self.value is a list 12 | if not hasattr(self.value, "append"): 13 | if self.value is not None: 14 | self.value = [self.value, ] 15 | else: 16 | self.value = [] 17 | 18 | super(SelectOne, self).update(clear=clear) 19 | 20 | def h_select(self, ch): 21 | self.value = [self.cursor_line,] 22 | 23 | def _print_line(self, line, value_indexer): 24 | try: 25 | display_this = self.display_value(self.values[value_indexer]) 26 | line.value = display_this 27 | line.hide = False 28 | if hasattr(line, 'selected'): 29 | if (value_indexer in self.value and (self.value is not None)): 30 | line.selected = True 31 | else: 32 | line.selected = False 33 | # Most classes in the standard library use this 34 | else: 35 | if (value_indexer in self.value and (self.value is not None)): 36 | line.show_bold = True 37 | line.name = display_this 38 | line.value = True 39 | else: 40 | line.show_bold = False 41 | line.name = display_this 42 | line.value = False 43 | 44 | if value_indexer in self._filtered_values_cache: 45 | line.important = True 46 | else: 47 | line.important = False 48 | 49 | 50 | except IndexError: 51 | line.name = None 52 | line.hide = True 53 | 54 | line.highlight= False 55 | 56 | class TitleSelectOne(multiline.TitleMultiLine): 57 | _entry_type = SelectOne 58 | -------------------------------------------------------------------------------- /npyscreen/wgslider.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import curses 3 | from . import wgwidget as widget 4 | from . import wgtitlefield as titlefield 5 | 6 | class Slider(widget.Widget): 7 | DEFAULT_BLOCK_COLOR = None 8 | def __init__(self, screen, value=0, 9 | out_of=100, step=1, lowest=0, 10 | label=True, 11 | block_color = None, 12 | **keywords): 13 | self.out_of = out_of 14 | self.value = value 15 | self.step = step 16 | self.lowest = lowest 17 | self.block_color = block_color or self.__class__.DEFAULT_BLOCK_COLOR 18 | super(Slider, self).__init__(screen, **keywords) 19 | if self.parent.curses_pad.getmaxyx()[0]-1 == self.rely: self.on_last_line = True 20 | else: self.on_last_line = False 21 | if self.on_last_line: 22 | self.maximum_string_length = self.width - 1 23 | else: 24 | self.maximum_string_length = self.width 25 | self.label = label 26 | 27 | def calculate_area_needed(self): 28 | return 1,0 29 | 30 | def translate_value(self): 31 | """What do different values mean? If you subclass this object, and override this 32 | method, you can change how the labels are displayed. This method should return a 33 | unicode string, to be displayed to the user. You probably want to ensure this is a fixed width.""" 34 | 35 | stri = "%s / %s" %(self.value, self.out_of) 36 | if isinstance(stri, bytes): 37 | stri = stri.decode(self.encoding, 'replace') 38 | l = (len(str(self.out_of)))*2+4 39 | stri = stri.rjust(l) 40 | return stri 41 | 42 | def update(self, clear=True): 43 | if clear: self.clear() 44 | if self.hidden: 45 | self.clear() 46 | return False 47 | length_of_display = self.width + 1 48 | blocks_on_screen = length_of_display 49 | 50 | if self.label: 51 | label_str = self.translate_value() 52 | if isinstance(label_str, bytes): 53 | label_str = label_str.decode(self.encoding, 'replace') 54 | blocks_on_screen -= len(label_str)+3 55 | if self.do_colors(): 56 | label_attributes = self.parent.theme_manager.findPair(self) 57 | else: 58 | label_attributes = curses.A_NORMAL 59 | self.add_line( 60 | self.rely, self.relx+blocks_on_screen+2, 61 | label_str, 62 | self.make_attributes_list(label_str, label_attributes), 63 | len(label_str) 64 | ) 65 | 66 | # If want to handle neg. numbers, this line would need changing. 67 | blocks_to_fill = (float(self.value) / float(self.out_of)) * int(blocks_on_screen) 68 | 69 | if self.editing: 70 | self.parent.curses_pad.attron(curses.A_BOLD) 71 | #self.parent.curses_pad.bkgdset(curses.ACS_HLINE) 72 | #self.parent.curses_pad.bkgdset(">") 73 | #self.parent.curses_pad.bkgdset(curses.A_NORMAL) 74 | BACKGROUND_CHAR = ">" 75 | BARCHAR = curses.ACS_HLINE 76 | else: 77 | self.parent.curses_pad.attroff(curses.A_BOLD) 78 | self.parent.curses_pad.bkgdset(curses.A_NORMAL) 79 | #self.parent.curses_pad.bkgdset(curses.ACS_HLINE) 80 | BACKGROUND_CHAR = curses.ACS_HLINE 81 | BARCHAR = " " 82 | 83 | 84 | for n in range(blocks_on_screen): 85 | xoffset = self.relx 86 | if self.do_colors(): 87 | self.parent.curses_pad.addch(self.rely,n+xoffset, BACKGROUND_CHAR, curses.A_NORMAL | self.parent.theme_manager.findPair(self)) 88 | else: 89 | self.parent.curses_pad.addch(self.rely,n+xoffset, BACKGROUND_CHAR, curses.A_NORMAL) 90 | 91 | for n in range(int(blocks_to_fill)): 92 | if self.do_colors(): 93 | if self.block_color: 94 | self.parent.curses_pad.addch(self.rely,n+xoffset, BARCHAR, self.parent.theme_manager.findPair(self, self.block_color)) 95 | else: 96 | self.parent.curses_pad.addch(self.rely,n+xoffset, BARCHAR, curses.A_STANDOUT | self.parent.theme_manager.findPair(self)) 97 | else: 98 | self.parent.curses_pad.addch(self.rely,n+xoffset, BARCHAR, curses.A_STANDOUT) #curses.ACS_BLOCK) 99 | 100 | self.parent.curses_pad.attroff(curses.A_BOLD) 101 | self.parent.curses_pad.bkgdset(curses.A_NORMAL) 102 | 103 | def set_value(self, val): 104 | #"We can only represent ints or floats, and must be less than what we are out of..." 105 | if val is None: val = 0 106 | if not isinstance(val, int) and not isinstance(val, float): 107 | raise ValueError 108 | 109 | else: 110 | self.__value = val 111 | 112 | if self.__value > self.out_of: raise ValueError 113 | 114 | def get_value(self): 115 | return float(self.__value) 116 | value = property(get_value, set_value) 117 | 118 | def set_up_handlers(self): 119 | super(widget.Widget, self).set_up_handlers() 120 | 121 | self.handlers.update({ 122 | curses.KEY_LEFT: self.h_decrease, 123 | curses.KEY_RIGHT: self.h_increase, 124 | ord('+'): self.h_increase, 125 | ord('-'): self.h_decrease, 126 | ord('h'): self.h_decrease, 127 | ord('l'): self.h_increase, 128 | ord('j'): self.h_exit_down, 129 | ord('k'): self.h_exit_up, 130 | }) 131 | 132 | def h_increase(self, ch): 133 | if (self.value + self.step <= self.out_of): self.value += self.step 134 | 135 | def h_decrease(self, ch): 136 | if (self.value - self.step >= self.lowest): self.value -= self.step 137 | 138 | 139 | class TitleSlider(titlefield.TitleText): 140 | _entry_type = Slider 141 | 142 | class SliderNoLabel(Slider): 143 | def __init__(self, screen, label=False, *args, **kwargs): 144 | super(SliderNoLabel, self).__init__(screen, label=label, *args, **kwargs) 145 | 146 | def translate_value(self): 147 | return '' 148 | 149 | class TitleSliderNoLabel(TitleSlider): 150 | _entry_type = SliderNoLabel 151 | 152 | class SliderPercent(Slider): 153 | def __init__(self, screen, accuracy=2, *args, **kwargs): 154 | super(SliderPercent, self).__init__(screen, *args, **kwargs) 155 | self.accuracy = accuracy 156 | 157 | def translate_value(self): 158 | pc = float(self.value) / float(self.out_of) * 100 159 | return '%.*f%%' % (int(self.accuracy), pc) 160 | 161 | class TitleSliderPercent(TitleSlider): 162 | _entry_type = SliderPercent -------------------------------------------------------------------------------- /npyscreen/wgtextbox_controlchrs.py: -------------------------------------------------------------------------------- 1 | import curses 2 | from . import wgtextbox as textbox 3 | 4 | class TextfieldCtrlChars(textbox.Textfield): 5 | "Implements a textfield, but which can be prefixed with special curses graphics. Currently unfinished. Not for use." 6 | def __init__(self, *args, **keywords): 7 | self.ctr_chars = [] 8 | super(TextfieldCtrlChars, self).__init__(*args, **keywords) 9 | 10 | def _get_maximum_string_length(self): 11 | if self.on_last_line: 12 | _maximum_string_length = self.width - 1 13 | else: 14 | _maximum_string_length = self.width 15 | 16 | _maximum_string_length -= (len(self.ctr_chars) + 1) 17 | 18 | return _maximum_string_length 19 | 20 | def _set_maxiumum_string_length(self, *args): 21 | pass 22 | 23 | def _del_maxiumum_string_length(self): 24 | pass 25 | 26 | maximum_string_length = property(_get_maximum_string_length, _set_maxiumum_string_length, _del_maxiumum_string_length) 27 | 28 | 29 | -------------------------------------------------------------------------------- /npyscreen/wgtextboxunicode.py: -------------------------------------------------------------------------------- 1 | from . import wgtextbox 2 | 3 | import unicodedata 4 | import curses 5 | 6 | 7 | 8 | class TextfieldUnicode(wgtextbox.Textfield): 9 | width_mapping = {'F':2, 'H': 1, 'W': 2, 'Na': 1, 'N': 1} 10 | def find_apparent_cursor_position(self, ): 11 | string_to_print = self.display_value(self.value)[self.begin_at:self.maximum_string_length+self.begin_at-self.left_margin] 12 | cursor_place_in_visible_string = self.cursor_position - self.begin_at 13 | counter = 0 14 | columns = 0 15 | while counter < cursor_place_in_visible_string: 16 | columns += self.find_width_of_char(string_to_print[counter]) 17 | counter += 1 18 | return columns 19 | 20 | def find_width_of_char(self, char): 21 | return 1 22 | w = unicodedata.east_asian_width(char) 23 | if w == 'A': 24 | # Abiguous - allow 1, but be aware that this could well be wrong 25 | return 1 26 | else: 27 | return self.__class__.width_mapping[w] 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /npyscreen/wgtexttokens.py: -------------------------------------------------------------------------------- 1 | import curses 2 | import sys 3 | from . import wgwidget 4 | from . import wgtextbox 5 | from . import wgtitlefield 6 | 7 | class TextTokens(wgtextbox.Textfield,wgwidget.Widget): 8 | """This is an experiemental widget""" 9 | 10 | # NB IT DOES NOT CURRENTLY SUPPORT THE HIGHLIGHTING COLORS 11 | # OF THE TEXTFIELD CLASS. 12 | 13 | 14 | def __init__(self, *args, **keywords): 15 | super(TextTokens, self).__init__(*args, **keywords) 16 | self.begin_at = 0 # which token to begin display with 17 | self.maximum_string_length = self.width - 2 18 | self.left_margin = 0 19 | self.cursor_position = 0 20 | 21 | self.important = False 22 | self.highlight = False 23 | self.show_bold = False 24 | 25 | def find_cursor_offset_on_screen(self, position): 26 | index = self.begin_at 27 | offset = 0 28 | while index < position: 29 | offset += len(self.decode_token(self.value[index])) 30 | index += 1 31 | return offset - self.begin_at # I don't quite understand 32 | # why the - self.begin_at is needed 33 | # but without it the cursor and screen 34 | # get out of sync 35 | 36 | def decode_token(self, tk): 37 | r = ''.join(tk) 38 | if len(r) > 1: 39 | r = ' [' + r + '] ' 40 | if isinstance(r, bytes): 41 | r = r.decode(self.encoding, 'replace') 42 | return r 43 | 44 | # text and highlighting generator. 45 | def get_literal_text_and_highlighting_generator(self, start_at=0,): 46 | # could perform initialization here. 47 | index = start_at 48 | string_length = 0 49 | output = '' 50 | while string_length <= self.maximum_string_length and len(self.value) > index: 51 | token_output = self.decode_token(self.value[index]) 52 | if isinstance(token_output, bytes): 53 | token_output = token_output.decode(self.encoding, 'replace') 54 | highlighting = [curses.A_NORMAL for c in token_output] 55 | yield(token_output, highlighting) 56 | index += 1 57 | 58 | def get_literal_text_to_display(self, start_at=0,): 59 | g = self.get_literal_text_and_highlighting_generator(start_at=start_at) 60 | txt = [] 61 | highlighting = [] 62 | for i in g: 63 | txt += i[0] 64 | highlighting += i[1] 65 | return txt, highlighting 66 | 67 | 68 | def update(self, clear=True, cursor=True): 69 | if clear: self.clear() 70 | if self.begin_at < 0: self.begin_at = 0 71 | if self.left_margin >= self.maximum_string_length: 72 | raise ValueError 73 | 74 | if self.cursor_position < 0: 75 | self.cursor_position = 0 76 | if self.cursor_position > len(self.value): 77 | self.cursor_position = len(self.value) 78 | 79 | if self.cursor_position < self.begin_at: 80 | self.begin_at = self.cursor_position 81 | 82 | while self.find_cursor_offset_on_screen(self.cursor_position) > \ 83 | self.find_cursor_offset_on_screen(self.begin_at) + \ 84 | self.maximum_string_length - self.left_margin -1: # -1: 85 | self.begin_at += 1 86 | 87 | 88 | text, highlighting = self.get_literal_text_to_display(start_at=self.begin_at) 89 | if self.do_colors(): 90 | if self.important: 91 | color = self.parent.theme_manager.findPair(self, 'IMPORTANT') | curses.A_BOLD 92 | else: 93 | color = self.parent.theme_manager.findPair(self, self.color) 94 | if self.show_bold: 95 | color = color | curses.A_BOLD 96 | if self.highlight: 97 | if not self.editing: 98 | color = color | curses.A_STANDOUT 99 | else: 100 | color = color | curses.A_UNDERLINE 101 | highlighting = [color for c in highlighting if c == curses.A_NORMAL] 102 | else: 103 | color = curses.A_NORMAL 104 | if self.important or self.show_bold: 105 | color = color | curses.A_BOLD 106 | if self.important: 107 | color = color | curses.A_UNDERLINE 108 | if self.highlight: 109 | if not self.editing: 110 | color = color | curses.A_STANDOUT 111 | else: 112 | color = color | curses.A_UNDERLINE 113 | highlighting = [color for c in highlighting if c == curses.A_NORMAL] 114 | 115 | self._print(text, highlighting) 116 | 117 | if self.editing and cursor: 118 | self.print_cursor() 119 | 120 | 121 | def _print(self, text, highlighting): 122 | self.add_line(self.rely, 123 | self.relx + self.left_margin, 124 | text, 125 | highlighting, 126 | self.maximum_string_length - self.left_margin 127 | ) 128 | def print_cursor(self): 129 | _cur_loc_x = self.cursor_position - self.begin_at + self.relx + self.left_margin 130 | try: 131 | char_under_cur = self.decode_token(self.value[self.cursor_position]) #use the real value 132 | char_under_cur = self.safe_string(char_under_cur) 133 | except IndexError: 134 | char_under_cur = ' ' 135 | 136 | if isinstance(char_under_cur, bytes): 137 | char_under_cur = char_under_cur.decode(self.encoding, 'replace') 138 | 139 | offset = self.find_cursor_offset_on_screen(self.cursor_position) 140 | if self.do_colors(): 141 | ATTR_LIST = self.parent.theme_manager.findPair(self) | curses.A_STANDOUT 142 | else: 143 | ATTR_LIST = curses.A_STANDOUT 144 | 145 | self.add_line(self.rely, 146 | self.begin_at + self.relx + self.left_margin + offset, 147 | char_under_cur, 148 | self.make_attributes_list(char_under_cur, ATTR_LIST), 149 | # I don't understand why the "- self.begin_at" is needed in the following line 150 | # but it is or the cursor can end up overrunning the end of the widget. 151 | self.maximum_string_length+1 - self.left_margin - offset - self.begin_at, 152 | ) 153 | 154 | def h_addch(self, inp): 155 | if self.editable: 156 | #self.value = self.value[:self.cursor_position] + curses.keyname(input) \ 157 | # + self.value[self.cursor_position:] 158 | #self.cursor_position += len(curses.keyname(input)) 159 | 160 | # workaround for the metamode bug: 161 | if self._last_get_ch_was_unicode == True and isinstance(self.value, bytes): 162 | # probably dealing with python2. 163 | ch_adding = inp 164 | self.value = self.value.decode() 165 | elif self._last_get_ch_was_unicode == True: 166 | ch_adding = inp 167 | else: 168 | try: 169 | ch_adding = chr(inp) 170 | except TypeError: 171 | ch_adding = input 172 | self.value = self.value[:self.cursor_position] + [ch_adding,] \ 173 | + self.value[self.cursor_position:] 174 | self.cursor_position += len(ch_adding) 175 | 176 | def display_value(self, vl): 177 | return vl 178 | 179 | 180 | def calculate_area_needed(self): 181 | "Need one line of screen, and any width going" 182 | return 1,0 183 | 184 | 185 | 186 | class TitleTextTokens(wgtitlefield.TitleText): 187 | _entry_type = TextTokens 188 | 189 | -------------------------------------------------------------------------------- /npyscreen/wgtitlefield.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import curses 3 | import weakref 4 | from . import wgtextbox as textbox 5 | from . import wgwidget as widget 6 | 7 | class TitleText(widget.Widget): 8 | _entry_type = textbox.Textfield 9 | 10 | def __init__(self, screen, 11 | begin_entry_at = 16, 12 | field_width = None, 13 | value = None, 14 | use_two_lines = None, 15 | hidden=False, 16 | labelColor='LABEL', 17 | allow_override_begin_entry_at=True, 18 | **keywords): 19 | 20 | self.text_field_begin_at = begin_entry_at 21 | self.field_width_request = field_width 22 | self.labelColor = labelColor 23 | self.allow_override_begin_entry_at = allow_override_begin_entry_at 24 | super(TitleText, self).__init__(screen, **keywords) 25 | 26 | if self.name is None: self.name = 'NoName' 27 | 28 | if use_two_lines is None: 29 | if len(self.name)+2 >= begin_entry_at: 30 | self.use_two_lines = True 31 | else: 32 | self.use_two_lines = False 33 | else: 34 | self.use_two_lines = use_two_lines 35 | 36 | self._passon = keywords.copy() 37 | for dangerous in ('relx', 'rely','value',):# 'width','max_width'): 38 | try: 39 | self._passon.pop(dangerous) 40 | except: 41 | pass 42 | 43 | if self.field_width_request: 44 | self._passon['width'] = self.field_width_request 45 | else: 46 | if 'max_width' in self._passon.keys(): 47 | if self._passon['max_width'] > 0: 48 | if self._passon['max_width'] < self.text_field_begin_at: 49 | raise ValueError("The maximum width specified is less than the text_field_begin_at value.") 50 | else: 51 | self._passon['max_width'] -= self.text_field_begin_at+1 52 | 53 | if 'width' in self._passon: 54 | #if 0 < self._passon['width'] < self.text_field_begin_at: 55 | # 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)) 56 | if self._passon['width'] > 0: 57 | self._passon['width'] -= self.text_field_begin_at+1 58 | 59 | if self.use_two_lines: 60 | if 'max_height' in self._passon and self._passon['max_height']: 61 | if self._passon['max_height'] == 1: 62 | raise ValueError("I don't know how to resolve this: max_height == 1 but widget using 2 lines.") 63 | self._passon['max_height'] -= 1 64 | if 'height' in self._passon and self._passon['height']: 65 | raise ValueError("I don't know how to resolve this: height == 1 but widget using 2 lines.") 66 | self._passon['height'] -= 1 67 | 68 | 69 | self.make_contained_widgets() 70 | self.set_value(value) 71 | self.hidden = hidden 72 | 73 | 74 | 75 | def resize(self): 76 | super(TitleText, self).resize() 77 | self.label_widget.relx = self.relx 78 | self.label_widget.rely = self.rely 79 | self.entry_widget.relx = self.relx + self.text_field_begin_at 80 | self.entry_widget.rely = self.rely + self._contained_rely_offset 81 | self.label_widget._resize() 82 | self.entry_widget._resize() 83 | self.recalculate_size() 84 | 85 | def make_contained_widgets(self): 86 | self.label_widget = textbox.Textfield(self.parent, relx=self.relx, rely=self.rely, width=len(self.name)+1, value=self.name, color=self.labelColor) 87 | if self.label_widget.on_last_line and self.use_two_lines: 88 | # we're in trouble here. 89 | if len(self.name) > 12: 90 | ab_label = 12 91 | else: 92 | ab_label = len(self.name) 93 | self.use_two_lines = False 94 | self.label_widget = textbox.Textfield(self.parent, relx=self.relx, rely=self.rely, width=ab_label+1, value=self.name) 95 | if self.allow_override_begin_entry_at: 96 | self.text_field_begin_at = ab_label + 1 97 | if self.use_two_lines: 98 | self._contained_rely_offset = 1 99 | else: 100 | self._contained_rely_offset = 0 101 | 102 | self.entry_widget = self.__class__._entry_type(self.parent, 103 | relx=(self.relx + self.text_field_begin_at), 104 | rely=(self.rely+self._contained_rely_offset), value = self.value, 105 | **self._passon) 106 | self.entry_widget.parent_widget = weakref.proxy(self) 107 | self.recalculate_size() 108 | 109 | 110 | def recalculate_size(self): 111 | self.height = self.entry_widget.height 112 | if self.use_two_lines: self.height += 1 113 | else: pass 114 | self.width = self.entry_widget.width + self.text_field_begin_at 115 | 116 | def edit(self): 117 | self.editing=True 118 | self.display() 119 | self.entry_widget.edit() 120 | #self.value = self.textarea.value 121 | self.how_exited = self.entry_widget.how_exited 122 | self.editing=False 123 | self.display() 124 | 125 | def update(self, clear = True): 126 | if clear: self.clear() 127 | if self.hidden: return False 128 | if self.editing: 129 | self.label_widget.show_bold = True 130 | self.label_widget.color = 'LABELBOLD' 131 | else: 132 | self.label_widget.show_bold = False 133 | self.label_widget.color = self.labelColor 134 | self.label_widget.update() 135 | self.entry_widget.update() 136 | 137 | def handle_mouse_event(self, mouse_event): 138 | if self.entry_widget.intersted_in_mouse_event(mouse_event): 139 | self.entry_widget.handle_mouse_event(mouse_event) 140 | 141 | def get_value(self): 142 | if hasattr(self, 'entry_widget'): 143 | return self.entry_widget.value 144 | elif hasattr(self, '__tmp_value'): 145 | return self.__tmp_value 146 | else: 147 | return None 148 | def set_value(self, value): 149 | if hasattr(self, 'entry_widget'): 150 | self.entry_widget.value = value 151 | else: 152 | # probably trying to set the value before the textarea is initialised 153 | self.__tmp_value = value 154 | def del_value(self): 155 | del self.entry_widget.value 156 | value = property(get_value, set_value, del_value) 157 | 158 | @property 159 | def editable(self): 160 | try: 161 | return self.entry_widget.editable 162 | except AttributeError: 163 | return self._editable 164 | 165 | @editable.setter 166 | def editable(self, value): 167 | self._editable = value 168 | try: 169 | self.entry_widget.editable = value 170 | except AttributeError: 171 | self._editable = value 172 | 173 | def add_handlers(self, handler_dictionary): 174 | """ 175 | Pass handlers to entry_widget 176 | """ 177 | self.entry_widget.add_handlers(handler_dictionary) 178 | 179 | class TitleFixedText(TitleText): 180 | _entry_type = textbox.FixedText 181 | -------------------------------------------------------------------------------- /npyscreen/wgwidget_proto.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class _LinePrinter(object): 4 | """A base class for printing lines to the screen. 5 | Do not use directly. For internal use only. 6 | Experimental. 7 | """ 8 | def find_width_of_char(self, ch): 9 | # will eventually need changing. 10 | return 1 11 | 12 | def _print_unicode_char(self, ch, force_ascii=None): 13 | if hasattr(self, '_force_ascii') and force_ascii is None: 14 | force_ascii = self._force_ascii 15 | # return the ch to print. For python 3 this is just ch 16 | if force_ascii: 17 | return ch.encode('ascii', 'replace') 18 | elif sys.version_info[0] >= 3: 19 | return ch 20 | else: 21 | return ch.encode('utf-8', 'replace') 22 | 23 | def add_line(self, realy, realx, 24 | unicode_string, 25 | attributes_list, max_columns, 26 | force_ascii=False): 27 | if isinstance(unicode_string, bytes): 28 | raise ValueError("This class prints unicode strings only.") 29 | 30 | if len(unicode_string) != len(attributes_list): 31 | raise ValueError("Must supply an attribute for every character.") 32 | 33 | column = 0 34 | place_in_string = 0 35 | 36 | if hasattr(self, 'curses_pad'): 37 | # we are a form 38 | print_on = self.curses_pad 39 | else: 40 | # we are a widget 41 | print_on = self.parent.curses_pad 42 | 43 | 44 | while column <= (max_columns-1): 45 | try: 46 | width_of_char_to_print = self.find_width_of_char(unicode_string[place_in_string]) 47 | except IndexError: 48 | break 49 | if column - 1 + width_of_char_to_print > max_columns: 50 | break 51 | try: 52 | print_on.addstr(realy,realx+column, 53 | self._print_unicode_char(unicode_string[place_in_string]), 54 | attributes_list[place_in_string] 55 | ) 56 | except IndexError: 57 | break 58 | column += width_of_char_to_print 59 | place_in_string += 1 60 | 61 | def make_attributes_list(self, unicode_string, attribute): 62 | """A convenience function. Retuns a list the length of the unicode_string 63 | provided, with each entry of the list containing a copy of attribute.""" 64 | if isinstance(unicode_string, bytes): 65 | raise ValueError("This class is intended for unicode strings only.") 66 | 67 | atb_array = [] 68 | ln = len(unicode_string) 69 | for x in range(ln): 70 | atb_array.append(attribute) 71 | return atb_array --------------------------------------------------------------------------------