Writing Programs Using newt | ||
---|---|---|
Prev |
Components are the basic user interface element newt provides. A single component may be (for example) a listbox, push button checkbox, a collection of other components. Most components are used to display information in a window, provide a place for the user to enter data, or a combination of these two functions. Forms, however, are a component whose primary purpose is not noticed by the user at all. Forms are collections of components (a form may contain another form) which logically relate the components to one another. Once a form is created and had all of its constituent components added to it, applications normally then run the form. This gives control of the application to the form, which then lets the user enter data onto the form. When the user is done (a number of different events qualify as ``done''), the form returns control to the part of the application which invoked it. The application may then read the information the user provided and continue appropriately. All newt components are stored in a common data type, a newtComponent (some of the particulars of newtComponents have already been mentioned. While this makes it easy for programmers to pass components around, it does force them to make sure they don't pass entry boxes to routines expecting push buttons, as the compiler can't ensure that for them. We start off with a brief introduction to forms. While not terribly complete, this introduction is enough to let us illustrate the rest of the components with some sample code. We'll then discuss the remainder of the components, and end this section with a more exhaustive description of forms.
As we've mentioned, forms are simply collections of components. As only one form can be active (or running) at a time, every component which the user should be able to access must be on the running form (or on a subform of the running form). A form is itself a component, which means forms are stored in newtComponent data structures.
newtComponent newtForm(newtComponent vertBar, const char * help, int flags); |
newtComponent myForm; myForm = newtForm(NULL, NULL, 0); |
void newtFormAddComponent(newtComponent form, newtComponent co); void newtFormAddComponents(newtComponent form, ...); |
newtComponent newtRunForm(newtComponent form); |
void newtFormDestroy(newtComponent form); |
Non-form components are the most important user-interface component for users. They determine how users interact with newt and how information is presented to them.
There are a couple of functions which work on more then one type of components. The description of each component indicates which (if any) of these functions are valid for that particular component.
typedef void (*newtCallback)(newtComponent, void *); void newtComponentAddCallback(newtComponent co, newtCallback f, void * data); void newtComponentTakesFocus(newtComponent co, int val); |
Nearly all forms contain at least one button. Newt buttons come in two flavors, full buttons and compact buttons. Full buttons take up quit a bit of screen space, but look much better then the single-row compact buttons. Other then their size, both button styles behave identically. Different functions are used to create the two types of buttons.
newtComponent newtButton(int left, int top, const char * text); newtComponent newtCompactButton(int left, int top, const char * text); |
Here is a simple example of both full and compact buttons. It also illustrates opening and closing windows, as well a simple form.
#include <newt.h> #include <stdlib.h> void main(void) { newtComponent form, b1, b2; newtInit(); newtCls(); newtOpenWindow(10, 5, 40, 6, "Button Sample"); b1 = newtButton(10, 1, "Ok"); b2 = newtCompactButton(22, 2, "Cancel"); form = newtForm(NULL, NULL, 0); newtFormAddComponents(form, b1, b2, NULL); newtRunForm(form); newtFormDestroy(form); newtFinished(); } |
Labels are newt's simplest component. They display some given text and don't allow any user input.
newtComponent newtLabel(int left, int top, const char * text); void newtLabelSetText(newtComponent co, const char * text); |
Entry boxes allow the user to enter a text string into the form which the application can later retrieve.
typedef int (*newtEntryFilter)(newtComponent entry, void * data, int ch, int cursor); newtComponent newtEntry(int left, int top, const char * initialValue, int width, char ** resultPtr, int flags); void newtEntrySet(newtComponent co, const char * value, int cursorAtEnd); char * newtEntryGetValue(newtComponent co); void newtEntrySetFilter(newtComponent co, newtEntryFilter filter, void * data); |
newtEntry() creates a new entry box. After the location of the entry box, the initial value for the entry box is passed, which may be NULL if the box should start off empty. Next, the width of the physical box is given. This width may or may not limit the length of the string the user is allowed to enter; that depends on the flags. The resultPtr must be the address of a char *. Until the entry box is destroyed by newtFormDestroy(), that char * will point to the current value of the entry box. It's important that applications make a copy of that value before destroying the form if they need to use it later. The resultPtr may be NULL, in which case the user must use the newtEntryGetValue() function to get the value of the entry box. Entry boxes support a number of flags:
If this flag is not specified, the user cannot enter text into the entry box which is wider then the entry box itself. This flag removes this limitation, and lets the user enter data of an arbitrary length.
If this flag is specified, the value of the entry box is not displayed. This is useful when the application needs to read a password, for example.
When this flag is given, the entry box will cause the form to stop running if the user pressed return inside of the entry box. This can provide a nice shortcut for users.
The entry box which had data entered into it
The data pointer which was registered along with the filter
The new character which newt is considering inserting into the entry box
The current cursor position (0 is the leftmost position)
#include <newt.h> #include <stdlib.h> #include <stdio.h> void main(void) { newtComponent form, label, entry, button; char * entryValue; newtInit(); newtCls(); newtOpenWindow(10, 5, 40, 8, "Entry and Label Sample"); label = newtLabel(1, 1, "Enter a string"); entry = newtEntry(16, 1, "sample", 20, &entryValue, NEWT_FLAG_SCROLL | NEWT_FLAG_RETURNEXIT); button = newtButton(17, 3, "Ok"); form = newtForm(NULL, NULL, 0); newtFormAddComponents(form, label, entry, button, NULL); newtRunForm(form); newtFinished(); printf("Final string was: %s\n", entryValue); /* We cannot destroy the form until after we've used the value from the entry widget. */ newtFormDestroy(form); } |
Most widget sets include checkboxes which toggle between two value (checked or not checked). Newt checkboxes are more flexible. When the user presses the space bar on a checkbox, the checkbox's value changes to the next value in an arbitrary sequence (which wraps). Most checkboxes have two items in that sequence, checked or not, but newt allows an arbitrary number of value. This is useful when the user must pick from a limited number of choices. Each item in the sequence is a single character, and the sequence itself is represented as a string. The checkbox components displays the character which currently represents its value the left of a text label, and returns the same character as its current value. The default sequence for checkboxes is " *", with ' ' indicating false and '*' true.
newtComponent newtCheckbox(int left, int top, const char * text, char defValue, const char * seq, char * result); char newtCheckboxGetValue(newtComponent co); |
Like most components, the position of the checkbox is the first thing passed to the function that creates one. The next parameter, text, is the text which is displayed to the right of the area which is checked. The defValue is the initial value for the checkbox, and seq is the sequence which the checkbox should go through (defValue must be in seq. seq may be NULL, in which case " *" is used. The final parameter, result, should point to a character which the checkbox should always record its current value in. If result is NULL, newtCheckboxGetValue() must be used to get the current value of the checkbox. newtCheckboxGetValue() is straightforward, returning the character in the sequence which indicates the current value of the checkbox If a callback is attached to a checkbox, the callback is invoked whenever the checkbox responds to a user's keystroke. The entry box may respond by taking focus or giving up focus, as well as by changing its current value.
Radio buttons look very similar to checkboxes. The key difference between the two is that radio buttons are grouped into sets, and exactly one radio button in that set may be turned on. If another radio button is selected, the button which was selected is automatically deselected.
newtComponent newtRadiobutton(int left, int top, const char * text, int isDefault, newtComponent prevButton); newtComponent newtRadioGetCurrent(newtComponent setMember); |
#include <newt.h> #include <stdlib.h> #include <stdio.h> void main(void) { newtComponent form, checkbox, rb[3], button; char cbValue; int i; newtInit(); newtCls(); newtOpenWindow(10, 5, 40, 11, "Checkboxes and Radio buttons"); checkbox = newtCheckbox(1, 1, "A checkbox", ' ', " *X", &cbValue); rb[0] = newtRadiobutton(1, 3, "Choice 1", 1, NULL); rb[1] = newtRadiobutton(1, 4, "Choice 2", 0, rb[0]); rb[2] = newtRadiobutton(1, 5, "Choice 3", 0, rb[1]); button = newtButton(1, 7, "Ok"); form = newtForm(NULL, NULL, 0); newtFormAddComponent(form, checkbox); for (i = 0; i < 3; i++) newtFormAddComponent(form, rb[i]); newtFormAddComponent(form, button); newtRunForm(form); newtFinished(); /* We cannot destroy the form until after we've found the current radio button */ for (i = 0; i < 3; i++) if (newtRadioGetCurrent(rb[0]) == rb[i]) printf("radio button picked: %d\n", i); newtFormDestroy(form); /* But the checkbox's value is stored locally */ printf("checkbox value: '%c'\n", cbValue); } |
It's common for programs to need to display a progress meter on the terminal while it performs some length operation (it behaves like an anesthetic). The scale component is a simple way of doing this. It displays a horizontal bar graph which the application can update as the operation continues.
newtComponent newtScale(int left, int top, int width, long long fullValue); void newtScaleSet(newtComponent co, unsigned long long amount); |
Textboxes display a block of text on the terminal, and is appropriate for display large amounts of text.
newtComponent newtTextbox(int left, int top, int width, int height, int flags); void newtTextboxSetText(newtComponent co, const char * text); |
All text in the textbox should be wrapped to fit the width of the textbox. If this flag is not specified, each newline delimited line in the text is truncated if it is too long to fit. When newt wraps text, it tries not to break lines on spaces or tabs. Literal newline characters are respected, and may be used to force line breaks.
The text box should be scrollable. When this option is used, the scrollbar which is added increases the width of the area used by the textbox by 2 characters; that is the textbox is 2 characters wider then the width passed to newtTextbox().
After a textbox has been created, text may be added to it through newtTextboxSetText(), which takes only the textbox and the new text as parameters. If the textbox already contained text, that text is replaced by the new text. The textbox makes its own copy of the passed text, so these is no need to keep the original around unless it's convenient.
When applications need to display large amounts of text, it's common not to know exactly where the linebreaks should go. While textboxes are quite willing to scroll the text, the programmer still must know what width the text will look ``best'' at (where ``best'' means most exactly rectangular; no lines much shorter or much longer then the rest). This common is especially prevalent in internationalized programs, which need to make a wide variety of message string look god on a screen. To help with this, newt provides routines to reformat text to look good. It tries different widths to figure out which one will look ``best'' to the user. As these commons are almost always used to format text for textbox components, newt makes it easy to construct a textbox with reflowed text.
char * newtReflowText(char * text, int width, int flexDown, int flexUp, int * actualWidth, int * actualHeight); newtComponent newtTextboxReflowed(int left, int top, char * text, int width, int flexDown, int flexUp, int flags); int newtTextboxGetNumLines(newtComponent co); |
newtReflowText() reflows the text to a target width of width. The actual width of the longest line in the returned string is between width - flexDown and width + flexUp; the actual maximum line length is chosen to make the displayed check look rectangular. The ints pointed to by actualWidth and actualHeight are set to the width of the longest line and the number of lines in in the returned text, respectively. Either one may be NULL. The return value points to the reflowed text, and is allocated through malloc(). When the reflowed text is being placed in a textbox it may be easier to use newtTextboxReflowed(), which creates a textbox, reflows the text, and places the reflowed text in the listbox. It's parameters consist of the position of the final textbox, the width and flex values for the text (which are identical to the parameters passed to newtReflowText(), and the flags for the textbox (which are the same as the flags for newtTextbox(). This function does not let you limit the height of the textbox, however, making limiting it's use to constructing textboxes which don't need to scroll. To find out how tall the textbox created by newtTextboxReflowed() is, use newtTextboxGetNumLines(), which returns the number of lines in the textbox. For textboxes created by newtTextboxReflowed(), this is always the same as the height of the textbox. Here's a simple program which uses a textbox to display a message.
#include <newt.h> #include <stdlib.h> char message[] = "This is a pretty long message. It will be displayed " "in a newt textbox, and illustrates how to construct " "a textbox from arbitrary text which may not have " "very good line breaks.\n\n" "Notice how literal \\n characters are respected, and " "may be used to force line breaks and blank lines."; void main(void) { newtComponent form, text, button; newtInit(); newtCls(); text = newtTextboxReflowed(1, 1, message, 30, 5, 5, 0); button = newtButton(12, newtTextboxGetNumLines(text) + 2, "Ok"); newtOpenWindow(10, 5, 37, newtTextboxGetNumLines(text) + 7, "Textboxes"); form = newtForm(NULL, NULL, 0); newtFormAddComponents(form, text, button, NULL); newtRunForm(form); newtFormDestroy(form); newtFinished(); } |
Scrollbars (which, currently, are always vertical in newt), may be attached to forms to let them contain more data then they have space for. While the actual process of making scrolling forms is discussed at the end of this section, we'll go ahead and introduce scrollbars now so you'll be ready.
newtComponent newtVerticalScrollbar(int left, int top, int height, int normalColorset, int thumbColorset); |
Listboxes are the most complicated components newt provides. They can allow a single selection or multiple selection, and are easy to update. Unfortunately, their API is also the least consistent of newt's components. Each entry in a listbox is a ordered pair of the text which should be displayed for that item and a key, which is a void * that uniquely identifies that listbox item. Many applications pass integers in as keys, but using arbitrary pointers makes many applications significantly easier to code.
Let's start off by looking at the most important listbox functions.
newtComponent newtListbox(int left, int top, int height, int flags); int newtListboxAppendEntry(newtComponent co, const char * text, const void * data); void * newtListboxGetCurrent(newtComponent co); void newtListboxSetWidth(newtComponent co, int width); void newtListboxSetCurrent(newtComponent co, int num); void newtListboxSetCurrentByKey(newtComponent co, void * key); |
A listbox is created at a certain position and a given height. The height is used for two things. First of all, it is the minimum height the listbox will use. If there are less items in the listbox then the height, suggests the listbox will still take up that minimum amount of space. Secondly, if the listbox is set to be scrollable (by setting the NEWT_FLAG_SCROLL flag, the height is also the maximum height of the listbox. If the listbox may not scroll, it increases its height to display all of its items. The following flags may be used when creating a listbox:
The listbox should scroll to display all of the items it contains.
When the user presses return on an item in the list, the form should return.
A frame is drawn around the listbox, which can make it easier to see which listbox has the focus when a form contains multiple listboxes.
By default, a listbox only lets the user select one item in the list at a time. When this flag is specified, they may select multiple items from the list.
Once a listbox has been created, items are added to it by invoking newtListboxAppendEntry(), which adds new items to the end of the list. In addition to the listbox component, newtListboxAppendEntry() needs both elements of the (text, key) ordered pair. For lists which only allow a single selection, newtListboxGetCurrent() should be used to find out which listbox item is currently selected. It returns the key of the currently selected item. Normally, a listbox is as wide as its widest element, plus space for a scrollbar if the listbox is supposed to have one. To make the listbox any larger then that, use newtListboxSetWidth(), which overrides the natural list of the listbox. Once the width has been set, it's fixed. The listbox will no longer grow to accommodate new entries, so bad things may happen! An application can change the current position of the listbox (where the selection bar is displayed) by calling newtListboxSetCurrent() or newtListboxSetCurrentByKey(). The first sets the current position to the entry number which is passed as the second argument, with 0 indicating the first entry. newtListboxSetCurrentByKey() sets the current position to the entry whose key is passed into the function.
While the contents of many listboxes never need to change, some applications need to change the contents of listboxes regularly. Newt includes complete support for updating listboxes. These new functions are in addition to newtListboxAppendEntry(), which was already discussed.
void newtListboxSetEntry(newtComponent co, void * key, const char * text); int newtListboxInsertEntry(newtComponent co, const char * text, const void * data, void * key); int newtListboxDeleteEntry(newtComponent co, void * key); void newtListboxClear(newtComponent co); |
The first of these, newtListboxSetEntry(), updates the text for a key which is already in the listbox. The key specifies which listbox entry should be modified, and text becomes the new text for that entry in the listbox. newtListboxInsertEntry() inserts a new listbox entry after an already existing entry, which is specified by the key parameter. The text and data parameters specify the new entry which should be added. Already-existing entries are removed from a listbox with newtListboxDeleteEntry(). It removes the listbox entry with the specified key. If you want to remove all of the entries from a listbox, use newtListboxClear().
When a listbox is created with NEWT_FLAG_MULTIPLE, the user can select multiple items from the list. When this option is used, a different set of functions must be used to manipulate the listbox selection.
void newtListboxClearSelection(newtComponent co); void **newtListboxGetSelection(newtComponent co, int *numitems); void newtListboxSelectItem(newtComponent co, const void * key, enum newtFlagsSense sense); |
Forms, which tie components together, are quite important in the world of newt. While we've already discussed the basics of forms, we've omitted many of the details.
Forms return control to the application for a number of reasons:
A component can force the form to exit. Buttons do this whenever they are pushed, and other components exit when NEWT_FLAG_RETURNEXIT has been specified.
Applications can setup hot keys which cause the form to exit when they are pressed.
Newt can exit when file descriptors are ready to be read or ready to be written to.
void newtFormAddHotKey(newtComponent co, int key); void newtFormWatchFd(newtComponent form, int fd, int fdFlags); |
void newtDrawForm(newtComponent form); newtComponent newtFormGetCurrent(newtComponent co); void newtFormSetCurrent(newtComponent co, newtComponent subco); void newtFormRun(newtComponent co, struct newtExitStruct * es); |
newtComponent newtForm(newtComponent vertBar, const char * help, int flags); void newtFormSetBackground(newtComponent co, int color); void newtFormSetHeight(newtComponent co, int height); void newtFormSetWidth(newtComponent co, int width); |