NewMenu Help

988 views
Skip to first unread message

blackmesa

unread,
Sep 12, 2012, 1:19:14 AM9/12/12
to npys...@googlegroups.com
Hi All,

I know this is a somewhat basic question, but I'm a BaSH guy trying to move over more completely to Python, and I am having trouble understanding how to properly use the NewMenu class.  Would someone be willing to provide me with a basic example of using the NewMenu and MenuItem classes?

Nicholas Cole

unread,
Sep 12, 2012, 5:20:21 AM9/12/12
to npys...@googlegroups.com
Sure. Let me know if the following code helps.

The quickest way is just to create a menu using a form's add_menu()
method, and then
use the menu's addItemsFromList() method (with the text and call-backs
supplied as a tuple pair within the list).

You can also add submenus to the menu with a call to addNewSubmenu()

If only one menu is defined it will be displayed when the user presses
the menu key ("^X" by default). If more than one top-level menu is
defined, they will be displayed as a list.

#################################################

iimport npyscreen, curses

class MyTestApp(npyscreen.NPSAppManaged):
def onStart(self):
self.registerForm("MAIN", MainForm())

class MainForm(npyscreen.FormWithMenus):
def create(self):
self.add(npyscreen.TitleText, name = "Text:", value= "" )
self.how_exited_handers[npyscreen.wgwidget.EXITED_ESCAPE] =
self.exit_application

# The menus are created here.
self.m1 = self.add_menu(name="Main Menu")
self.m1.addItemsFromList([
("Just Beep", self.whenJustBeep),
("Exit Application", self.exit_application),
])

self.m2 = self.add_menu(name="Another Menu")
self.m2.addItemsFromList([
("Just Beep", self.whenJustBeep),
])

self.m3 = self.m2.addNewSubmenu("A sub menu")
self.m3.addItemsFromList([
("Just Beep", self.whenJustBeep),
])

def whenJustBeep(self):
curses.beep()

def exit_application(self):
curses.beep()
self.parentApp.setNextForm(None)
self.editing = False
self.parentApp.switchFormNow()

def main():
TA = MyTestApp()
TA.run()


if __name__ == '__main__':
main()


#########################################################

Aaron Robinson

unread,
Sep 12, 2012, 8:28:01 AM9/12/12
to npys...@googlegroups.com
Hey Nick,

Thank you!  That code really helps get me started.

This menu is a little different than what I had expected, though; maybe I was thinking more of a Form that has selectable text that call other functions when you press "Enter."  I hadn't figured out how to get that working with regular Forms, and when I looked at the NewMenu code it seemed more like another class akin to Forms than objects to put inside it.

I can work with it, though, if there isn't something else to use.  It's hard to complain when someone takes what you hope would be easy to use (curses) and actually makes it so ;)

Thanks again,

- Aaron


--
You received this message because you are subscribed to the Google Groups "npyscreen" group.
To post to this group, send an email to npys...@googlegroups.com.
To unsubscribe from this group, send email to npyscreen+...@googlegroups.com.
Visit this group at http://groups.google.com/group/npyscreen?hl=en-GB.



Nicholas Cole

unread,
Sep 12, 2012, 8:56:52 AM9/12/12
to npys...@googlegroups.com
On Wed, Sep 12, 2012 at 1:28 PM, Aaron Robinson
<robinso...@gmail.com> wrote:
> Hey Nick,
>
> Thank you! That code really helps get me started.
>
> This menu is a little different than what I had expected, though; maybe I
> was thinking more of a Form that has selectable text that call other
> functions when you press "Enter." I hadn't figured out how to get that
> working with regular Forms, and when I looked at the NewMenu code it seemed
> more like another class akin to Forms than objects to put inside it.
>
> I can work with it, though, if there isn't something else to use. It's hard
> to complain when someone takes what you hope would be easy to use (curses)
> and actually makes it so ;)

Dear Aaron,

Well, that menu system is really intended so that you can put menus on
other forms (a bit like a popup menu or drop down menu in a GUI). But
if you want controls that will let you call other functions directly
from the main form itself, have a look at the widget classes:

MiniButtonPress (override the method whenPressed to perform an
action when the user selects it)

Or else:

MultiLineAction (the selected value and the keypress are passed as
arguments to the method actionHighlighted when the user selects an
option.

If a combination of the menu system and those classes don't do what
you need, let me know, and I'll look at adding something to the
standard library.

Best wishes,

Nicholas

Aaron Robinson

unread,
Sep 12, 2012, 9:24:16 AM9/12/12
to npys...@googlegroups.com
I'll give those a try, too.  Thanks, I'll let you know!

- Aaron

Aaron Robinson

unread,
Sep 19, 2012, 12:05:17 AM9/19/12
to npys...@googlegroups.com
Hi Nick,

Alright, I have made what I thought was progess.  I looked through your multiple screens example, and I made an override menuButtonOne here that should allow me to change screens when I press it.  Overriding the whenPressed function does work, since I can put an exit() there and it exits, and if I mistype a function then it errors out when I press the button.  However, I cannot seem to get it to change screens.  What I have written there just doesn't work; instead it just exits out.  It doesn't print my debug line, either, even though it seems to get to the self.parent.parentApp.changeForm() line.

Here is the code:

<<< Begin Paste >>>
#!/usr/bin/python
import sys
sys.path.append(r'/home/family/Downloads/npyscreen-2.0pre62')
import npyscreen

class MyApplication(npyscreen.NPSAppManaged):
    def onStart(self):
        self.addForm("MAIN", Screen1, name='Forage Scanning System')
        self.addForm("ADD", AddScreen, name='Adding Items...')
    def changeForm(self, name):
        print >> sys.stderr, name
        self.switchForm(name)
        self.resetHistory()

class Screen1(npyscreen.ActionForm):
    def afterEditing(self):
        self.parentApp.NEXT_ACTIVE_FORM = None

    def create(self):
        self.menu1 = self.add(menuButtonOne, name="Add item(s)...")

class AddScreen(npyscreen.Form):
    def create(self):
        self.UPCInput = self.add(npyscreen.TitleText, name='UPC: ')


class menuButtonOne(npyscreen.MiniButtonPress):
    def whenPressed(self):
        print >> sys.stderr, "Got to whenPressed."
        self.parent.parentApp.changeForm("ADD")


if __name__ == '__main__':
    TestApp = MyApplication().run()
<<< End Paste >>>

Sorry to have to punt to you again; I'm hoping it's just something really obvious.

Thanks again for all the help,

- Aaron

Nicholas Cole

unread,
Sep 19, 2012, 2:59:52 AM9/19/12
to npys...@googlegroups.com
On Wed, Sep 19, 2012 at 5:05 AM, Aaron Robinson
<robinso...@gmail.com> wrote:
> Hi Nick,
>
> Alright, I have made what I thought was progess. I looked through your
> multiple screens example, and I made an override menuButtonOne here that
> should allow me to change screens when I press it. Overriding the
> whenPressed function does work, since I can put an exit() there and it
> exits, and if I mistype a function then it errors out when I press the
> button. However, I cannot seem to get it to change screens. What I have
> written there just doesn't work; instead it just exits out. It doesn't
> print my debug line, either, even though it seems to get to the
> self.parent.parentApp.changeForm() line.
>
> Here is the code:
>
[SNIP]
>
> Sorry to have to punt to you again; I'm hoping it's just something really
> obvious.
>
> Thanks again for all the help,
>
> - Aaron

Dear Aaron,

Yes. Trivial Fix:

<<<< BEGIN >>>>
#!/usr/bin/python
import sys
sys.path.append(r'/home/family/Downloads/npyscreen-2.0pre62')
import npyscreen

class MyApplication(npyscreen.NPSAppManaged):
def onStart(self):
self.addForm("MAIN", Screen1, name='Forage Scanning System')
self.addForm("ADD", AddScreen, name='Adding Items...')
def changeForm(self, name):
print >> sys.stderr, name
self.switchForm(name)
self.resetHistory()

class Screen1(npyscreen.ActionForm):
def afterEditing(self):
pass#self.parentApp.NEXT_ACTIVE_FORM = None

def create(self):
self.menu1 = self.add(menuButtonOne, name="Add item(s)...")

class AddScreen(npyscreen.Form):
def create(self):
self.UPCInput = self.add(npyscreen.TitleText, name='UPC: ')


class menuButtonOne(npyscreen.MiniButtonPress):
def whenPressed(self):
print >> sys.stderr, "Got to whenPressed."
self.parent.parentApp.changeForm("ADD")


if __name__ == '__main__':
TestApp = MyApplication().run()
<<<<<END>>>>>>>>

You don't need that "afterEditing" method. The only thing you were
doing there was telling the application to end. "afterEditing" still
gets called when you use the .parentApp.changeForm and you were
telling the application to exit.

Best wishes,

Nicholas

Aaron Robinson

unread,
Sep 19, 2012, 3:12:45 AM9/19/12
to npys...@googlegroups.com
Yep, that took care of it.  Maybe I can actually start using this thing now ;)

Thanks again!

- Aaron


Best wishes,

Nicholas

Aaron Robinson

unread,
Sep 22, 2012, 7:03:40 PM9/22/12
to npys...@googlegroups.com
Hey Nick,

So I have a few more questions.

First, I am trying to put text on the screen that is _not_ selectable or editable.  The FixedText object is close to what I want, but it still shows up as a selection.  I have also been using this as a formatting tool (to put spacing between things with a blank one), but I run into having to arrow down twice to get to the next item if I do that.  Do you have another object for just displaying text on the screen that I am missing, or is there another way to override this object to do that?  I have tried overriding the edit() and update() methods without any success so far.

Second, I would like to have a box that takes input (like a UPC in my case) and then runs another function that updates some variables, makes some sqlite database queries, and then calls the form again while displaying some of those new values.  I am fine with the sqlite part and displaying new values in the form again, I think, but I haven't been able to figure out how to run that other method following a carriage return.  The textfield only seems to have special handlers for cursor keys, and I wasn't sure how to add another for the carriage return.

The last things are niceties, but I'll throw them out anyway.  I am displaying my curses program on a very small (7") screen, and I only have 30x20 characters to work with.  Thus, the "OK" button and the rest of the outline of the form get lost on the screen because it doesn't scale that small.  Is there a way around this?  The other thing was to just turn off the "OK" button for the Form, as I don't need it (Although I wouldn't mind using it since it's on the bottom-right if I could override the text and functionality).

Do you have any ideas?

Thanks,

- Aaron

Nicholas Cole

unread,
Sep 23, 2012, 5:11:51 AM9/23/12
to npys...@googlegroups.com
Dear Aaron,

Everything you need is already built in.

> First, I am trying to put text on the screen that is _not_ selectable or
> editable. [snip]

You can set .editable = False on any widget, or pass editable=False in
to the constructor and the user will not be able to select it.

> Second, I would like to have a box that takes input (like a UPC in my case)
> and then runs another function that updates some variables, makes some
> sqlite database queries, and then calls the form again while displaying some
> of those new values. I am fine with the sqlite part and displaying new
> values in the form again, I think, but I haven't been able to figure out how
> to run that other method following a carriage return. The textfield only
> seems to have special handlers for cursor keys, and I wasn't sure how to add
> another for the carriage return.

Several ways to achieve this. If you want just exactly what you are
asking for, you could subclass the basic text widget - and give it a
method:

def set_up_handlers(self):
super(YOURCLASSNAME, self).set_up_handlers()
handlers.update({
curses.ascii.NL: self.YOURFUNCTION,
curses.ascii.CR: self.YOURFUNCTION,
})


However, there is a set of classes that I've written explicitly to
handle this kind of situation. Have a look at the classes
FormMuttActive, ActionControllerSimple, and TextCommandBox. The idea
is that all of your sqlite code would go in to an ActionController
class, that would be controlled from a TextCommandBox (which can also
give you command history) and be displayed to the user with one of the
multiline widgets. I need to update the documentation properly for
all of that, but I'll post more about it if it looks useful. There is
a minimal example of this sort of thing called EXAMPLE-muttactive.py
included.


> The last things are niceties, but I'll throw them out anyway. I am
> displaying my curses program on a very small (7") screen, and I only have
> 30x20 characters to work with. Thus, the "OK" button and the rest of the
> outline of the form get lost on the screen because it doesn't scale that
> small. Is there a way around this? The other thing was to just turn off
> the "OK" button for the Form, as I don't need it (Although I wouldn't mind
> using it since it's on the bottom-right if I could override the text and
> functionality).


Sure. If the screen size is too small, then create your Form using:

Form(minimum_lines = 20, minimum_columns = 30,) or whatever you need.

If you don't like the OK button, then use one of the Form classes
without it. FormBaseNew() is a minimal class that lacks any
additional controls.

Hope that all helps.

Best wishes,

Nicholas

Nicholas Cole

unread,
Sep 24, 2012, 7:31:27 PM9/24/12
to npys...@googlegroups.com
Dear Aaron and others,

I'm not sure if what I wrote last week helped you. But you might like
to know I've now extended the documentation for the more complex
classes. I don't know if they will help with your problem, but I'll
cut and past the documentation and example code here.

I want to include this in the next release, so if anyone can think of
a way I can make it clearer, do say.

Best,

N.

=========


Writing More Complex Forms
==========================

A very typical style of programming for terminal applications has been
to have a screen that has a command line, typically at the bottom of
the screen, and then some kind of list widget or other display taking
up most of the screen, with a title bar at the top and a status bar
above the command line. Variations on this scheme are found in
applications like Mutt, less, Vim, irssi and so on.

To make writing these kinds of form easier, npyscreen provides a
series of classes that are intended to work together.

FormMuttActive, FormMuttActiveWithMenus, FormMuttActiveTraditional,
FormMuttActiveTraditionalWithMenus
These classes define the basic form. The following *class
attributes* dictate exactly how the form is created::

MAIN_WIDGET_CLASS = wgmultiline.MultiLine
MAIN_WIDGET_CLASS_START_LINE = 1
STATUS_WIDGET_CLASS = wgtextbox.Textfield
STATUS_WIDGET_X_OFFSET = 0
COMMAND_WIDGET_CLASS= wgtextbox.Textfield
COMMAND_WIDGET_NAME = None
COMMAND_WIDGET_BEGIN_ENTRY_AT = None
COMMAND_ALLOW_OVERRIDE_BEGIN_ENTRY_AT = True

DATA_CONTROLER = npysNPSFilteredData.NPSFilteredDataList

ACTION_CONTROLLER = ActionControllerSimple

The default definition makes the following instance attributes
available after initalization::

# Widgets -
self.wStatus1 # by default a title bar
self.wStatus2 # just above the command line
self.wMain # the main area of the form - by default a
MultiLine object
self.wCommand # the command widget

self.action_controller # not a widget. See below.

The form's *.value* attribute is set to an instance of the object
specified by DATA_CONTROLLER.

Typically, and application will want to define its own
DATA_CONTROLLER and ACTION_CONTROLLER.

The difference between the traditional and non-traditional forms
is that in the traditional form, the focus stays always with the
command line widget, although some keypresses will be passed to the
MAIN_WIDGET_CLASS - so that, from the user's point of view, it looks
as if he/she is interacting with both at once.

TextCommandBox
The TextCommandBox is like a usual text box, except that it passes
what the user types to the action_controller. In addition, it can
keep a history of the commands entered. See the documentation on
ActionControllerSimple for more details.

TextCommandBoxTraditional
This is the same as the TextCommandBox, except that it
additionally will pass certain keystrokes to the widget specified by
*self.linked_widget*. In the default case, any keystroke that does
not match a handler in TextCommandBoxTraditional will be passed to the
linked widget. Additionally, any keystroke that is listed in the list
*self.always_pass_to_linked_widget* will be handled by the linked
widget. However, if the current command line begins with any
character that is listed in the class attribute
*BEGINNING_OF_COMMAND_LINE_CHARS*, the user input will be handled by
this class, not by the linked widget.

This is rather complicated, but an example will make it clearer.
The default BEGINNING_OF_COMMAND_LINE_CHARS specifies that ':' or '/'
marks the beginning of a command. After that point, keypresses are
handled by this widget, not by the linked widget, so that the up and
down arrows start to navigate the command history. However, if the
command line is currently empty, those keys navigate instead the
linked widget.

As in the TextCommandBox widget, the value of the command line is
passed to the parent form's action_controller object.

ActionControllerSimple
This object receives command lines and executes call-back functions.

It recognises two types of command line - a "live" command line,
where an action is taken with every change in the command line, and a
command that is executed when the return key is pressed.

Callbacks are added using the *add_action(ident, function, live)*,
method. 'ident' is a regular expression that will be matched against
the command line, *function* is the callback itself and *live* is
either True or False, to specify whether the callback should be
executed with every keypress (assuming that 'ident' matches).

Command lines that match the regular expression 'ident' cause the
call-back to be called with the following arguments:
*call_back(command_line, control_widget_proxy, live=True)*. Here
*command_line* is the string that is the command line,
*control_widget_proxy* is a weak reference to the command line widget,
and live specifies whether the function is being called 'live' or as a
result of a return.

The method *create()* can be overridden. It is called when the
object is created. The default does nothing. You probably want to use
this as a place to call *self.add_action*.

NPSFilteredDataBase
The default *NPSFilteredDataBase* class suggests how the code to
manage the display might be separated out into a separate object. The
precise methods will be very application dependent. This is not an
essential part of this kind of application, but it is good practice to
keep the logic of (for example) database access separate from the
logic of the user interface.



Example Code
************

The following example shows how this model works. The application
creates an ActionController that has a search action. This action
calls the user-defined function set_search, which communicates with
the Form's parent.value (actually a NPSFilteredDataBase class). It
then uses this class to set the values in wMain.values and calls
wMain.display() to update the display.

FmSearchActive is simply a FormMuttActiveTraditional class, with a
class attribute that specifies that the form should use our action
controller::
class ActionControllerSearch(npyscreen.ActionControllerSimple):
def create(self):
self.add_action('^/.*', self.set_search, True)

def set_search(self, command_line, widget_proxy, live):
self.parent.value.set_filter(command_line[1:])
self.parent.wMain.values = self.parent.value.get()
self.parent.wMain.display()


class FmSearchActive(npyscreen.FormMuttActiveTraditional):
ACTION_CONTROLLER = ActionControllerSearch

class TestApp(npyscreen.NPSApp):
def main(self):
F = FmSearchActive()
F.wStatus1.value = "Status Line "
F.wStatus2.value = "Second Status Line "
F.value.set_values([str(x) for x in range(500)])
F.wMain.values = F.value.get()

F.edit()


if __name__ == "__main__":
App = TestApp()
App.run()
Reply all
Reply to author
Forward
0 new messages