Writing Programs Using newt

Erik Troan, <ewt@redhat.com>

v0.31, 2003-Jan-06

The newt windowing system is a terminal-based window and widget library designed for writing applications with a simple, but user-friendly, interface. While newt is not intended to provide the rich feature set advanced applications may require, it has proven to be flexible enough for a wide range of applications (most notably, Red Hat's installation process). This tutorial explains the design philosophy behind newt and how to use newt from your programs.


Introduction

Newt has a definite design philosophy behind it, and knowing that design makes it significantly easier to craft robust newt applications. This tutorial documents newt 0.30 --- older versions of newt had annoying inconsistencies in it (which writing this tutorial pointed out), which were removed while this tutorial was written. The latest version of newt is always available from Red Hat.

Background

Newt was originally designed for use in the install code for Red Hat Linux. As this install code runs in an environment with limited resources (most importantly limited filesystem space), newt's size was immediately an issue. To help minimize its size, the following design decisions were made early in its implementation:

  • newt does not use an event-driven architecture.

  • newt is written in C, not C++. While there has been interest in constructing C++ wrapper classes around the newt API, nothing has yet come of those ideas.

  • Windows must be created and destroyed as a stack (in other words, all newt windows behave as modal dialogs). This is probably the greatest functionality restriction of newt.

  • The tty keyboard is the only supported input device.

  • Many behaviours, such as widget traversal order, are difficult or impossible to change.

While newt provides a complete API, it does not handle the low-level screen drawing itself. Instead, newt is layered on top of the screen management capabilities of John E. Davis's S-Lang library.

Designing newt applications

As newt is not event driven and forces modal windows (forcing window order to behave like a stack), newt applications tend to look quite like other text-mode programs. It is quite straightforward to convert a command line program which uses simple user prompts into a newt application. Some of the programs run as part of the Red Hat installation process (such as Xconfigurator and mouseconfig) were originally written as simple terminal mode programs which used line-oriented menus to get input from the user and were later converted into newt applications (through a process affectionately known as newtering). Such a conversion does not require changes to the control flow of most applications. Programming newt is dramatically different from writing programs for most other windowing systems as newt's API is not event driven. This means that newt applications look dramatically different from programs written for event-driven architectures such as Motif, gtk, or even Borland's old TurboVision libraries. When you're designing your newt program, keep this differentiation in mind. As long as you plan your application to call a function to get input and then continue (rather then having your program called when input is ready), programming with the newt libraries should be simple.

Components

Displayable items in newt are known as components, which are analogous to the widgets provided by most Unix widget sets. There are two main types of components in newt, forms and everything else. Forms logically group components into functional sets. When an application is ready to get input from a user, it ``runs a form'', which makes the form active and lets the user enter information into the components the form contains. A form may contain any other component, including other forms. Using subforms in this manner lets the application change the details of how the user tabs between components on the form, scroll regions of the screen, and control background colors for portions of windows. Every component is of type newtComponent, which is an opaque type. It's guaranteed to be a pointer though, which lets applications move it through void pointers if the need arises. Variables of type newtComponent should never be directly manipulated -- they should only be passed to newt functions. As newtComponent variables are pointers, remember that they are always passed by value -- if you pass a newtComponent to a function which manipulates it, that component is manipulated everywhere, not just inside of that function (which is nearly always the behaviour you want).

Conventions

Newt uses a number of conventions to make it easier for programmers to use.

  • All functions which manipulate data structures take the data structure being modified as their first parameter. For example, all of the functions which manipulate forms expect the newtComponent for that form to be the first parameter.

  • As newt is loosely typed (forcing all of the components into a single variable makes coding easier, but nullifies the value of type checking), newt functions include the name of the type they are manipulating. An example of this is newtFormAddComponent(), which adds a component to a form. Note that the first parameter to this function is a form, as the name would suggest.

  • When screen coordinates are passed into a function, the x location precedes the y location. To help keep this clear, we'll use the words ``left'' and ``top'' to describe those indicators (with left corresponding to the x position).

  • When box sizes are passed, the horizontal width precedes the vertical width.

  • When both a screen location and a box size are being passed, the screen location precedes the box size.

  • When any component other then a form is created, the first two parameters are always the (left, right) location.

  • Many functions take a set of flags as the final parameter. These flags may be logically ORed together to pass more then one flag at a time.

  • Newt uses callback functions to convey certain events to the application. While callbacks differ slightly in their parameters, most of them allow the application to specify an arbitrary argument to be passed to the callback when the callback is invoked. This argument is always a void *, which allows the application great flexibility.