Written by Jared Riley
Snack is a python library based on newt that can be used to create a simple text based User interface. This is the package that was used by Red Hat to create their installation along with a number of configuration tools. It is an ideal platform upon which to create installation and configuration scripts, particularly if you don't want to rely on X, or you want to avoid complexity.
The newt RPM on Red Hat Linux comes with two sample python programs: peanuts.py, and popcorn.py. From these it is expected that the programmer will be able to figure out what to do. There is also an SGML guide to the newt C library. From this you can also make some inferences, but some things are different in the python world. You should read this document anyway because it gives an overview of how newt/snack programs work.
I have used snack to create a configuration program for software that will run on a server without X Windows installed. Along the way, I had to read the sample programs, Red Hat's installation program, and the C Library documentation. Hopefully the information here will help you do it with far less effort.
I have only documented what I have used. If you would like to undertake more complex programs or use features not discussed, here are some other places to look. Unless otherwise mentioned, these files are contained in the newt or newt-devel RPMS on Red Hat. Their location may be different on your version of Linux.
This is the interface to the newt library. Whatever is in this file is what you are allowed to access from your program.
This file documents some of the calls in the newt library for C. Not every call is documented, and not every call is available through snack.
If you are like me and you can't figure out how to get something readable out of sgml in less than half a day, you can find it on the internet. I found it here.
These sample programs come with the newt-devel RPM on Red Hat. Between them, they use most of the widgets that are available in snack.
To use the snack module, import it into your program.
from snack import *
Snack provides the SnackScreen class, which you must allocate
before you can do anything.
screen = SnackScreen()
This call will paint your console a lovely blue. It will stay
this way forever, unless you call the finish method.
screen.finish()
Calling the finish method is very important. If you don't call it, then your screen will be left in a fairly unusable state when you exit python. To recover, you will have to call reset from the shell. You may also have to fix your interrupt and end of file keys (stty intr ^C; stty eof ^D).
Another caveat is that newt takes over your terminal and prevents signals due to keystrokes. ^Z and ^C will not work, so you should make an effort to provide a way to exit the program cleanly. There is a way to allow job control keystrokes to function correctly that will be discussed later.
You can't do very much with snack without using Forms and Grids. These two items have been combined into the GridFormHelp and GridForm classes, but I will leave those for you to figure out. Instead, I will show how to use basic Forms and Grids to create your display. In a later section I will show the types of Widgets that can be used.
screen = SnackScreen()
upperleft = Widget()
upperright = Widget()
lowerleft = Widget()
lowerright = Widget()
g = Grid(2, 2)
g.setField(upperleft, 0, 0, (0, 0, 1, 1))
g.setField(upperright, 1, 0)
g.setField(lowerleft, 0, 1)
g.setField(lowerright, 1, 1, growx = 1, growy = 1)
screen.gridWrappedWindow(g, "Title Text")
f = Form()
f.add(g)
result = f.run()
screen.popWindow()
screen.finish()
The call to grid sets the size of the grid. The width comes before the height, so in this case, the grid will be two widgets wide and 2 widgets high.
After creating the grid, the widgets are added into the grid using the setField method. The second and third parameters are the location in the grid where the widget is to be placed. Again, the horizontal parameter comes before the vertical setting. Since python is zero based, the available positions are 0 to horizontal size - 1.
snack will not complain if you put more than one widget into the same location in the grid. However, it will put all of the elements on top of each other, so you are unlikely to be pleased with the result.
In the first call to setField, I have used the optional padding argument. This is a tuple listing how much room is to be left around the widget in the grid. The order of the widget is (Left, Top, Right, Bottom).
The last call to setField has used the growx and growy parameters. These are used when you have different sized widgets. In order to make your window appear balanced, snack will add extra space into a smaller widget if it can so that the size of that widget will be grown to be the size of the widgets around it.
You may create a grid, and then put that grid into another grid's field. In this way you can create windows with different numbers of fields in different rows or columns.
Before the grid can be displayed on the screen, you must call screen.gridWrappedWindow(). This configures the grid and readies it for display.
The Form is the thing that holds all of the widgets and displays them on the screen. It returns the widget that caused the form to be exited. Once the form has returned, you must call the screen.popWindow() method to remove the window from the screen.
You must add each widget to the form. However, there is a shortcut. If you have put all of your elements into a grid, then you can just add the grid. This will recursively add each element from the grid to the form.
This is the only function in the snack library. reflow takes some text and reformats it to fit into the parameters given. flexDown and flexUp are guidelines to the library as to how many lines it should try to fit the text into while it is reconfiguring. reflow takes line breaks literally, so if you are going to use strings enclosed by """, then you should use line continuation characters to keep each paragraph together.
reflowreturns a tuple of the new wrapped text, the width of the text, and the actual height after adjustments.
Here is a list of the different types of Widgets, and some of the parameters to the init function, and their methods:
Buttons are pretty simple. The text that you pass to the button is what will be displayed on the button. There isn't really a way to change the text later, so to do that, you would have to create a new button.
These are the same as buttons, but they don't have a nice border. Use them when you are cramped for space on the screen.
This produces a box that can be turned on or off. It is independent of other checkboxes that you might have in the form. The text is displayed adjacent to the box. isOn is optional, and sets the initial value of the box.
The Checkbox has the following methods:
Returns whether the box is on or not.
Returns whether the box is currently selected or not. This is different from whether or not the box is on as returned by value().
setFlags is used to change whether or not a user can actually select the checkbox. The possible values of sense are FLAGS_SET, FLAGS_RESET, and FLAGS_TOGGLE. FLAGS_SET sets a particular flag to true. FLAGS_RESET sets the flag to false. FLAGS_TOGGLE sets it to the opposite value of what it currently is.
The only flag currently allowed is FLAG_DISABLED. This flag sets whether or not the checkbox can be selected, and thereby changed.
Sets the current value of the checkbox.
A Radio button is a widget, of which only one in a group can be selected. group is the group of radio buttons to which this button belongs. If there are no other buttons in the group yet, then pass in None. If you have other buttons, pass in one of the other buttons to associate the buttons together.
It has the following methods:
Returns whether this button is the one in the group that is selected.
A list box offers a list of selection. Only one thing can be selected at a time. If you want to have multiple selections, use a CheckboxTree.
height is how many lines there should be in the box. scroll determines whether or not a scroll bar is present. returnExit says that if the ENTER key is pressed while in the box, the form should be exited. width is the number of characters wide the Listbox should be. showCursor sets the status of the text cursor. In general, you want to have it off because having it on is fairly ugly.
There is quite a bit you can do with a list box.
Adds an entry to the end of the Listbox. text is whatever text you want to be displayed in the list box. item is any object you would like. You can pass in an integer, a string, or anything at all.
Inserts an entry before another entry in the Listbox. before is whatever object you passed in for another item in the Listbox (not the display text). If you pass in "None" for before, then this item will be inserted at the beginning of the list.
Removes something from the Listbox. item is whatever you passed in to the listbox before when you added the element to the list.
Replaces another element with this element. The element it replaces will be given by item. The new element in the list will still return the same item.
Returns the item corresponding to the current selection in the Listbox. It does not return the text.
Selects the entry in the Listbox given by item.
Empties the Listbox.
A Textbox displays some text. width, height, and text are fairly obvious. They set the size of the box and the text that should be displayed in it. scroll determines whether or not there is a vertical scrollbar on the box. There is no such thing as a horizontal scrollbar in snack, so you should probably use wrap to keep everything inside the box.
Textbox offers only one method. setText(text) lets you change the text that will be displayed in the box.
This is a Textbox that uses reflow to configure the text that will be displayed.
Scales are different from other widgets in that they are intended to be updated on the fly. This means that you must use some other features of the Form and SnackScreen classes that are not required by other widgets.
width is the character width of the scale on the screen. total is the amount that the scale should be out of.
The Scale class has only one method:
amount is a value between 0 and the total parameter used to set up the Scale.
Here is an example of using a scale. Notice that instead
of running the form, the draw method is used instead. Also,
the screen must be refreshed every time that we want to see
what has happened.
screen = SnackScreen()
g = Grid(1, 1)
s = Scale(40, 1000)
s.set(0)
g.setField(s, 0, 0)
screen.gridWrappedWindow(g, "Scale Example")
f = Form()
f.add(s)
for i in range(1000):
for j in range(10000):
pass
s.set(i)
f.draw()
screen.refresh()
screen.finish()
If you had other components on the form, and you wanted to get user input, then you would have to run the form at this point to get the result of the user's input. However, the scale would no longer be updated once the form was run.
width is the size of the field where text is entered. text is the initial value of the entry. hidden hides the text from view. The user can't tell that anything is there, and when they edit, they can't tell what they are typing. password displays the value of the entry as a series of '*'. scroll allows the user to scroll horizontally if the text of the entry is too wide to fit in the space it has. returnExit indicates that the containing Form should exit when the ENTER key is pressed in the box.
Entry boxes have the following methods:
This returns the text that is currently in the Entry.
Changes the text that is currently in the Entry.
setFlags is used to change whether or not a user can actually select the Entry and change it's value. The possible values of sense are FLAGS_SET, FLAGS_RESET, and FLAGS_TOGGLE. FLAGS_SET sets a particular flag to true. FLAGS_RESET sets the flag to false. FLAGS_TOGGLE sets it to the opposite value of what it currently is.
The only flag currently allowed is FLAG_DISABLED. This flag sets whether or not the Entry can be selected, and thereby changed.
There are several classes which have been defined for convenience because they are so common.
This class provides an easy way to set up a group of radio buttons, instead of doing everything yourself.
buttonlist is a list of tuples containing information about each button to be contained in the group. Each tuple has three elements. The first is a title for the button. The second element is an object to return if the Radio button is currently selected. The third is either 0 or 1. If 1, it indicates that this button is the default.
RadioBar has the following methods:
This method creates a new Radio Button and adds it to the group. value is an object associated with the button that will be returned by getSelection
Returns the value associated with the Radio button that is currently selected.
This class provides an easy way to set up a group of buttons without having to do everything yourself. The comments in snack.py suggest that when a ButtonBar is added to a grid, the growx parameter be set.
buttonlist is a list of buttons that can take three different types of button descriptions in the list.
If the element is a tuple consisting of three elements, then the elements are assumed to be the following:
The text to display in the button.
The value to return to the user when the buttonPressed method is called.
A hotkey to be associated with the button.
A tuple consisting of only two elements is the same as for three elements, but without a hotkey.
A string is also acceptable. In this case, the text for the button is set to the string. The value for the button is set to the lower case value of the string.
A ButtonBar has only one method: buttonPressed(result). result is the return value from calling Form.run(). If the Form exited because of a hotkey mapped to one of the buttons, or because one of the buttons was pressed, this function will return the value associated with that button. Otherwise, it will return None.
This class creates a container for a tree of Checkboxes. An entry in the tree can either be a branch with checkboxes and branches under it, or it can be a checkbox.
There are several methods associated with a CheckboxTree:
This method adds an element to the end of the top level of the CheckboxTree.
As usual, text is the display text for this item. item is an object of your choice. It should be unique for any elements that will be accessed later by the class. If you don't supply an item, the text will be used. selected indicates whether the item is initially selected.
This method allows you to add an item to the CheckboxTree anywhere in the tree. The parameters are the same as for the append method, except for the path parameter.
path is a tuple of integers which indicates to
the CheckboxTree where this item should be placed in the
tree. The last element of the tuple should be
snackArgs['append'] to indicated that the tuple
is terminated. Here is an example of adding several things to a
CheckboxTree:
tree = CheckboxTree(height = 5, scroll = 1)
tree.addItem("First", (snackArgs['append'], ))
tree.addItem("Second", (snackArgs['append'], ))
tree.addItem("A", (0, snackArgs['append']))
tree.addItem("B", (0, snackArgs['append']))
tree.addItem("X", (0, 1, snackArgs['append']))
This would produce a tree with containing five entries. Initially, only the items labeled First and Second would be displayed. Second would be a Checkbox, while First would be expandable. If you expanded First, you would see underneath it the A, and B items. A would be a checkbox, and B would be expandable. Finally, if you expanded B, you would see a Checkbox for X.
Returns the item that the cursor is currently highlighting.
Returns a list of all items that are currently selected.
Changes the text associated with this particular item.
Set whether or not the entry associated with this item is selected.
Return whether or not the entry associated with this item is selected.
There are three functions that can be used to display common dialog boxes.
This function displays a dialog with some descriptive text, a listbox, and buttons at the bottom. It returns a tuple consisting of the button that was pressed to exit the dialog, and the entry in the list that was selected. It's parameters are:
A valid SnackScreen
The title for the dialog box.
Text that should be displayed at the top of the dialog box before the Listbox.
This can be a list or tuple of things to display in the Listbox. Each item in the list should be either a string or a tuple. If it is a tuple, the first field is the text to display, and the second is an item associated with that text. If a string, then the text to display is set to the string, and the item returned will be the numeric position of the entry in the Listbox.
The buttons parameter is passed directly through to a ButtonBar.
This is used as the width of the text box at the top of the dialog.
All of these items are given to the Listbox.
This is the argument passed to the help callback function. See the section on help for more information.
This function displays some text to the user and a set of buttons for them to press at the bottom of the dialog. It returns the button that was pressed.
The parameters mean the following things:
A valid SnackScreen.
The title for the dialog box.
The text to be displayed in the dialog box.
The buttons parameter is passed directly through to a ButtonBar.
This is used as the width of the Textbox.
These parameters are used to set the location of the Window on the screen. If they are not given, the window is centered, which is probably what you want.
This is the argument passed to the help callback function. See the section on help for more information.
This function displays a window with a set of Entries in it to be filled in by the user. It returns the button that was pressed to exit the dialog.
The parameters mean the following things:
A valid SnackScreen.
The title for the dialog box.
The text to be displayed in the dialog box.
The buttons parameter is passed directly through to a ButtonBar.
This is a list of the prompts to be used as labels for each Entry. A string is used as the label for the Entry. On the other hand, if the prompt is a tuple, then the first element of the tuple is used as the tuple, and the second element is assumed to be an already existing Entry.
This isn't actually used for anything in the version of snack that I worked with.
This is used as the width of the Textbox.
This value is used as the width for each Entry.
As usual, this is passed through to a ButtonBar widget.
This is the argument passed to the help callback function. See the section on help for more information.
There are two ways to provide help to the user.
The first method is to display a single line of help on the bottom of the screen and is quite easy to do. This is done using the pushHelpLine and popHelpLine methods of the SnackScreen class. These calls must be bracketing. That is, each pushHelpLine must match with a call to popHelpLine.
A default help line is displayed if none is supplied, or if you try to set the help line to None. If you wish to remove the help line entirely, push a string with a space (" ").
The second method for providing help is to do something when the user presses <F1>. An example action for <F1> would be to bring up a dialog box with detailed explanations of the screen, and instructions on how to fill out the dialog box. To do this, you must create a function, and arrange for it to be called when the user presses <F1>. The helpCallback method of SnackScreen lets you set the help function.
Your help callback function should take two parameters (in addition to self). The first is the SnackScreen which is making the call. The second is an object which is used to indicate what screen the callback is coming from. Writing the actual function is fairly simple. Use the object to look up the help text that should be displayed in a dictionary and display it Alternatively, you could just put everything into the main object so that all the help callback has to do is display what it is given.
The next thing to do is arrange for the help callback to be called with the correct object at the right time. This is done when you create your Form. The Form class takes an option help parameter. It is this parameter that will be passed to the callback.
Hotkeys are useful for providing your users with a way to navigate your dialog boxes without being forced to always tab to a button. If you have several widgets in a form, tabbing to the correct button quickly becomes tedious.
In general, <F1> is always defined as help, while <F12> is defined as exit the Form. The ButtonBar widget provides an easy way to use hot keys. Passing in a third element in a tuple for a button causes the hot key to be set to that element.
If you want to add a hot key to a Form, then you can use the addHotKey method. This method will be called automatically for any widgets with a hotkeys list as member of the class.
Available hot keys are defined in the hotkeys dictionary in the snack.py module. You can see there that the correct way to use a hot key is to pass in it's matching string.
If a hot key is pressed to exit the form, it will be the return value of running the form. You can check this directly, or pass it to your ButtonBar to find out the equivalent button.
It is actually possible to add your own hot keys to the hotkeys
dictionary.
import snack
ESC = '^['
snack.hotkeys[ESC] = ord(ESC)
snack.hotkeys[ord(ESC)] = ESC
In this example, ESC is set to the actual value of the escape key. You can produce this by typing Ctrl-V ESC in vi, or Ctrl-Q ESC in emacs. Note that the key is double mapped in the dictionary.