Chapter 40

Implementing Java Interfaces for Non-Traditional Devices

by Mark Wutka


CONTENTS

One of the most interesting aspects of Java is that it was originally designed to work on a small hand-held device, making it very friendly toward these devices. Java is also a self-contained environment; that is, it is more than just a programming language. You can run Java programs wherever you can implement the Java runtime environment.

Java promises to allow you to run programs in places you never expected. For example, you can have a mobile terminal mounted in your car, maybe as an extension to your cellular phone. Within the next year, you may see television sets that can also access the Internet and run Java programs. You will soon see Java showing up in cellular phones and Personal Digital Assistants (PDAs).

These devices come with their own set of advantages and limitations. If you get your systems ready for these devices, you can get a jump on your competition.

Characteristics of Non-Traditional Devices

One of the most common characteristics you find in these new computing devices is that they have little or no local disk storage. While this may change in the future, you can design around it fairly easily. In fact, you have probably already tackled this problem thanks to the current applet security restrictions.

Many of these new devices have small display screens. This stretches your user interface design skills quite a bit, because you need to come up with creative ways to display information without requiring reading glasses.

You will also have an interesting time getting input from these devices. For example, a cellular phone typically has about 16 to 20 buttons, 12 of which are the standard telephone buttons of 0 through 9, #, and *. Many PDAs have a full keyboard, while others, like the Newton, have only a pen. You may also be dealing with touch-screen interfaces, which are often approached like pen-based systems.

One of the things you should be able to count on for most of these devices is network connectivity. After all, with no local storage, they either have to have all their code built-in, or they must be able to download code from the network.

The New Computing Model

Right now, you probably have a number of different information sources in your home. You have one or more phone lines, maybe even a digital ISDN line, probably cable TV or a satellite dish, and, of course, you have television and radio waves drifting through the air. For each of these information sources, you have different receivers that relay the information to you. Figure 40.1 shows the typical information sources coming into a home and their receivers.

Figure 40.1 : Your home has many different information sources.

You also have a number of information sources that you bring into your house manually-that is, not electronically. For instance, you may receive a newspaper. You also buy books, videotapes, and CDs.

These information sources are beginning to blend together, however, thanks to improvements in technology. Newspapers are now available on the Web, as are recent news reports. There are online books and magazines, and audio and video clips are becoming very popular.

Cable TV companies are beginning to offer cable modems that allow you to access the Web with a direct digital connection, similar to ISDN. Within the next few years, you may start seeing phone, cable TV, and Internet access combined into a single service, available over a single connection to your house. Figure 40.2 illustrates how this changes your sources of information.

Figure 40.2 : You may soon get several kinds of information from a single source.

This combination of information sources is only half the picture. You soon will have many more devices in your house that want to communicate with you and other devices. Right now, you use your personal computer for surfing the Web, but when you get a Web-enabled TV, you may want both of them to share your Internet access. You may already have a PDA or a laptop computer, and have probably experienced the fun of trying to share data between the PDA or laptop and your desktop system.

As more and more of your household devices become Java-enabled, more of them will require network access. You will soon have a need for your own home network. Figure 40.3 illustrates a typical home network configuration.

Figure 40.3 : With so many network-aware devices at home, you will need your own home network.

As more and more of the applications you use every day become Java-enabled, you will be able to run them in the different devices on your home network. For instance, you may have an address book program that you might need to access from your phone, your TV, or your desktop computer (if you still have one). Applications for these new devices will consist of one or more applets implementing the user interface and communicating with a server application, as shown in Figure 40.4.

Figure 40.4 : The Java-enabled devices in your home will often just implement a user interface for an application.

It is entirely possible that your desktop computer, as you know it, will change. Right now, you probably have a keyboard, a monitor, a CPU, a modem, and a printer. Figure 40.5 illustrates the typical home computer of today.

Figure 40.5 : A typical home computer configuration.

In your future home network, you still need these devices, of course, but some of these components need to be shared with other devices in your house. Your CPU becomes the computing server for your house. All your applications run on it. Your printer either is connected to your CPU or plugs directly into the network. Your monitor and keyboard are replaced by a network computer or a Web TV. You probably can buy a cheap box that turns your current monitor and keyboard into a network computer. Your modem is either attached to the network or replaced by whatever device gives you connectivity to the outside world-maybe a cable or an ISDN terminal adaptor. Figure 40.6 shows how the components of your home computer fit into a home network.

Figure 40.6 : You still need the same computing components, just rearranged differently.

You may eventually be able to control your lights and thermostat from a home network. After you have a network running throughout your home, the possibilities for new devices are endless. Figure 40.7 illustrates some of the possible devices that can be on your home network.

Figure 40.7 : You can attach numerous devices to your home network.

This brings up a sticky situation, however. The more things that are accessible on your home network, the more critical it is to prevent people from accessing your home network from the outside.

Unfortunately, you can't simply block out any incoming requests, because you may want to access information on your home server from your car, which may also have its own network. You need a home firewall system that prevents malicious network attacks, and enables you to control who can access information in your home and what information they can access.

It is possible that, some day, you will no longer need your CPU. You will be able to buy computing services like you buy phone or cable services. Your computing services provider supplies the applications you use over the Internet. This provider also maintains the applications, making sure that the most recent versions are available. In addition, the provider would perform the often-ignored task of backing up your data.

There are advantages and disadvantages to giving up the administration of your computing server. On the positive side, you no longer have to keep up with the current versions of software. You don't have to go out and install new packages; your provider should have whole application suites ready for immediate use. You don't have to make backups, either. In the simplest case, you can subscribe to such a service, then go out and buy a Java-enabled TV and a network printer, plug them into the wall, and go.

Designing Applications to Support Non-Traditional Devices

In case you haven't noticed, this book has been pushing you toward an application model that is friendly to small, non-traditional devices. The specific design principles for this application model are as follows:

Separating the User Interface from the Application

Many times, the separation of the application from the user interface is a difficult task. After you have done it a few times, however, it gets easier. When an application's goal is the rendering of an image, the line between application and user interface becomes a little fuzzier.

For example, suppose you are writing an application that displays weather radar images. The application needs to retrieve the weather images, but the user interface is responsible for displaying them. How do you separate them?

Actually, there's not always a clear-cut answer to this. Sometimes the distinction can be gray. If your weather application allows you to zoom in on an image or overlay other information on top of the image, do you do that in the application or the user interface?

Tip
If your application needs to know the characteristics of the display area, that is a good indication that you haven't clearly separated the user interface from the application.

There's a tradeoff involved here. It is extremely costly for your server to generate images for every client whenever the client needs to zoom the image. You would be much better off if the client were doing that processing. You must balance that against the fact that you have to put the zooming code onto the client, which may be a burden to the client.

Tip
Design involves a series of tradeoffs that result in different decisions for different applications.

It is often very easy to let pieces of the user interface leak into the application. For instance, suppose you create an ordering system that presents the user with a list of parts. The user can select any number of parts on the list, and press the Order button to order the parts. You might be tempted to have the application keep track of which parts the user has selected. In other words, the interface between the application and the user interface might include methods to select and deselect parts, and then place the order, like this:

public void selectPart(String partName);
public void deselectPart(String partName);
public void placeOrder();

This is not a good separation, however. The user interface has leaked over into the application. This often happens because you have taken too granular a view of what is going on. The application is responsible for ordering parts. It is not responsible for identifying which parts should be ordered.

Tip
If the interface between an application and a user interface closely reflects the various components of the user interface, you may not have separated them very well.

When you look at the selectPart-deselectPart-placeOrder interface, you can almost see what the designer was thinking-a multiple-selection scrolling list of parts and an Order button. The interface should really just contain a method to place an order, like this:

public void placeOrder(String[] partNames);

Now the application doesn't have any notion of the interaction between the user and the user interface, as it did when it had methods for selecting and deselecting parts.

Avoiding Large, Monolithic Applications

When you design an application, you may think in terms of the whole application, treating all the little things it does as a part of the whole. This results in huge applications that take a long time to download.

Take a word processor, for instance. You have a spelling checker, a thesaurus, a mailing list handler, an outline generator, and all sorts of other features. However, you don't necessarily want to load all of these features when the program starts up.

Note
A word processor not only serves as a good example of a typically monolithic application, it also illustrates one of the compromises you have to make when separating the user interface from the application. Conceptually, the document you are editing belongs to the application side. If you were to send every little editing command over to the application, you may never get anything done, unless you happen to have a very fast network. You have to create a balance, where you bring over portions of the text for editing, and occasionally mirror the text back to the application.

Different features of your application have their own special interfaces. Rather than downloading these to the client at startup time, you can use Java's dynamic loading mechanism to postpone the loading of these special interfaces.

You can also cut down on the interdependence between various interface components if you create each interface component as an AWT panel, or better yet, as an applet. That's really where the term applet comes from. They aren't whole applications, they are pieces of applications.

Because the special interfaces are panels or applets (which are panels themselves), you can create a special section of your display for the special interfaces, or use a card layout manager to switch between different panels. The other parts of your user interface don't have to know anything else about the interface.

The Java component interface, known as Beans, promises to provide additional support for this, allowing your special interfaces to plug themselves into the current application in a seamless way.

Sticking to Standard Libraries

One of the toughest things to balance when developing for small devices is the use of third-party libraries. There are many excellent third-party libraries available that allow you to do some really fancy things, but these libraries have to be downloaded to the client, which means extra startup time.

Tip
Third-party libraries don't always mean extra download time. If a library provides functionality that you would have to write anyway and it is smaller than anything you could write in a reasonable amount of time, go ahead and use it.

The important thing here is that you don't write anything that is already available in the standard Java libraries. If you have your own special remote method invocation system, consider replacing it with RMI, at least for Java client-server communications. You save a tremendous amount of time, because the RMI code is part of the standard Java libraries (as of Java 1.1).

Tip
Keep in mind that non-Java components, such as ActiveX, are probably not going to be available on these smaller clients. You should be very wary of these components when designing user interfaces that may run on small clients.

Avoiding Long, Complex Transactions

If you are performing a long, complex task, there is a good chance you won't be able to get around a long, complex transaction from the user interface. Many times, however, the task itself is simple, but the way you access the data is complex. Just because you have a complex database doesn't mean you have to punish the user by presenting them with a complex user interface. In other words, don't make something complicated out of a simple task.

For example, if you have an address book program, provide a way to simply look up someone by name, like this:

public String getAddress(String name);

If you can define an operation as a single method call, do it. Don't open up a session, create a logon, or require multiple method calls if you can avoid it.

Designing User Interfaces for Small Devices

For someone coming from a background of designing user interfaces for desktop systems, the world of small hand-held devices is a rude awakening. Instead of focusing on pretty interfaces, you must focus on simple, self-documenting interfaces.

When designing user interfaces for small hand-held devices, there is a set of design principles that you should follow:

Creating Obvious, Self-Documenting Interfaces

Is there a help key on an automatic teller machine? That's a tough question to answer, because you've probably never needed one. When you create user interfaces for hand-held devices, you want the interface to be so obvious that no one ever needs a help key or a manual.

Note
You can get a better understanding of what makes a good interface and what makes a bad one by studying different devices. An automatic teller is one such example. Your VCR is another. How many VCRs have you seen that are actually simple to program? Even with on-screen programming? Why are they more difficult? Consider how you might make a better one.

Avoiding Extraneous Pictures or Information

There are two main reasons for avoiding extraneous pictures in your interface: time and space. You shouldn't waste time downloading images that aren't necessary, and you shouldn't waste precious screen space.

Note
Screen space doesn't just refer to the number of pixels on the screen when it comes to small devices. The screens are typically only a few inches wide. It doesn't matter if they are 50 pixels wide or 1,000 pixels wide. There is a limit to the amount of readable information you can show.

This doesn't mean that you should avoid pictures altogether. You may find that a few simple icons describe the use of the device just as well as text. The advantage to the icons is that they are not language-dependent. Anyone can use an icon-based user interface no matter what language they speak.

When you create icons for your interface, however, keep them simple. If you need arrows pointing in certain directions, don't download some ray-traced image of a marble arrow. Either download a simple arrow image, or use the Graphics class to paint the arrow yourself.

Keeping Everything Readable

Any user interface that requires generous amounts of squinting or high-powered reading glasses is a bad interface. If you have to cram that much information onto the screen, you should break it up into multiple screens that you can scroll through back and forth. Imagine if you made a hard-to-read interface that was used in a car. You don't want people wrecking because they had to lean over and put their faces an inch from the screen to read it.

Because many small devices have poor resolution, the larger you make your letters, the more readable they are. Obviously, bigger letters are more readable. The point here is that letters on a small device are generally less readable than letters of the same size on a desktop computer.

Supporting Multiple Sources of Input

Input sources present almost as big a portability problem as varying processors or operating systems. If you want to create a single user interface that runs on any device, you will face a huge challenge.

Some of the different types of input sources you might find on a hand-held device are as follows:

The fact that some devices may not even have separate input sources poses a huge problem. If you have to support a pen or touch-screen interface, you have to leave room on the screen for the items the user can select.

Because you can't always count on letters being present when you have a keypad, you have to support the limited set of buttons on a telephone keypad.

These are serious issues that may be addressed by the manufacturers of these devices as they become more prevalent. Unfortunately, this technology is too new for anyone to have considered the problem. In the past, you had to write custom programs for each hand-held device, so you could make safe assumptions about what kind of input source might have been present.

With the advent of Java, you can no longer make that assumption, so you must adjust. The following are some strategies you can take when approaching this problem:

Creating Reusable Components for Small Devices

As you begin to build applets for small devices, you will need a toolkit of components to help you build interfaces quickly, without having to add a lot of custom code to adapt to different input sources. If anything has to adapt, it should be the components and not the applets.

Using the CardLayout Layout Manager as a Stack

On a small device, you don't have the luxury of multiple window frames. Because you usually need all the space you can get, you can use a card layout to achieve something similar to a dialog box. The idea is that when you would normally open a dialog box, you create a new panel and use the card layout to display the new panel.

You are really using the card layout like a stack. Whenever you add a new panel, you are pushing it on top of what you are doing now. When you are through, you want to go back to the previous card. For a scheme like this, you need a panel that knows how to push itself onto the card stack and pop itself back off.

For convenience, define a StackLayout class that is really just a CardLayout. This helps cut down on the confusion if you happen to use the CardLayout class in several places. Listing 40.1 shows the StackLayout class.


Listing 40.1  Source Code for StackLayout.java
import java.awt.CardLayout;
// This class is used as a layout manager for use with the PushablePanel
// class. It works exactly like the CardLayout, but the PushablePanel
// looks for a StackLayout explicitly, so you can safely use CardLayouts
// in your panels without pushing a PushablePanel on top of them.
public class StackLayout extends CardLayout
{
     public StackLayout()
     {
     }
     public StackLayout(int hgap, int vgap)
     {
          super(hgap, vgap);
     }
}

Now, when a panel pushes itself onto a stack layout, it adds itself to the end of the stack and tells the stack layout to display the last element. You may want to make custom user interface components that can pop up panels at any time. This presents a small difficulty, because you always add the panels to the container that uses a stack layout. This means that each user interface component would have to have a reference to that container. If a component is nested several containers deep, this is unacceptable.

However, you can make a pushable panel that searches for the correct container. Whenever a component pops up one of these panels, the panel searches for the container that uses a stack layout for a layout manager. It does this by using the getParent method in the current component and continuing to search parents. Listing 40.2 shows the PushablePanel class that works with the StackLayout layout manager.


Listing 40.2  Source Code for PushablePanel.java
import java.awt.*;
// This class implements a panel that can push itself onto a StackLayout
// and pop itself off again.
public class PushablePanel extends Panel
{
     protected Container parentContainer;
     protected StackLayout stackLayout;
     public PushablePanel()
     {
          parentContainer = null;
          stackLayout = null;
     }
// Push this panel onto the current stack layout. Given a component, find
// a container whose layout manager is a stack layout, and push this panel
// on top of it. By doing this, any object can push a new panel without
// having a reference to the layout manager.
     public void push(Component comp)
     {
          while (comp != null) {
// If the current component is a Container, see if it uses a StackLayout
               if (comp instanceof Container) {
                    Container cont = (Container) comp;
                    LayoutManager layout = cont.getLayout();
// If the current container uses a StackLayout, we've found our container
                    if (layout instanceof StackLayout) {
                         parentContainer = cont;
                         stackLayout = (StackLayout)layout;
                         break;
                    }
               }
// Try the next component up the line
               comp = comp.getParent();
          }
// If we found a container with a StackLayout, add this component to
// the container.
          if (parentContainer != null) {
               parentContainer.add(this);
               stackLayout.last(parentContainer);
          }
     }
     public void pop()
     {
// To pop this panel off the stack, move the stack layout to the previous
// panel (the StackLayout is really a CardLayout) and then remove this
// panel from the stack.
          if (parentContainer != null) {
               stackLayout.previous(parentContainer);
               parentContainer.remove(this);
          }
     }
}

Creating a Keyboard/Keypad Input Filter

One of the difficulties in creating a user interface that works for different hand-held devices is that you can't always count on certain input devices being present. If you are running an applet on a cellular phone, you may not be able to count on a mouse-like pointing device. This is a problem for creating buttons. You have to come up with a way to make hot keys for each button on the panel. This requires the cooperation of the button and the panel itself. The panel has to examine each keystroke and figure out whether the keystroke is a hot key for a button. Remember, you can't count on a mouse to change the input focus, so when you hit a key, the key press event may first go to another component and then filter up to the panel.

Listing 40.3 shows a SmallDevicePanel that runs all keystrokes through a filter class that relays them to other components. The panel is also a pushable panel.


Listing 40.3  Source Code for SmallDevicePanel.java
import java.awt.*;
// This class implements a PushablePanel that can filter keystrokes
// to implement hot keys for various user interface components.
public class SmallDevicePanel extends PushablePanel
{
// InputFilter maps key codes to components and passes keystroke events
// to those components.
     protected InputFilter filter;
// The empty constructor creates an input filter by default
     public SmallDevicePanel()
     {
          filter = new InputFilter();
     }
// This constructor allows you to create an unfiltered panel, which
// makes this object nothing more than a PushablePanel.
     public SmallDevicePanel(boolean filtered)
     {
          if (filtered) {
               filter = new InputFilter();
          } else {
               filter = null;
          }
     }
// Add a component and if it can have an input filter, set its filter
     public Component add(Component comp)
     {
          if ((filter != null) &&
               (comp instanceof FilteredComponent)) {
               ((FilteredComponent)comp).setFilter(filter);
          }
          return super.add(comp);
     }
// Add a component and if it can have an input filter, set its filter
     public Component add(String name, Component comp)
     {
          if ((filter != null) &&
               (comp instanceof FilteredComponent)) {
               ((FilteredComponent)comp).setFilter(filter);
          }
          return super.add(name, comp);
     }
// Add a component and if it can have an input filter, set its filter
     public Component add(Component comp, int position)
     {
          if ((filter != null) &&
               (comp instanceof FilteredComponent)) {
               ((FilteredComponent)comp).setFilter(filter);
          }
          return super.add(comp, position);
     }
// Filter any keypresses and pass them to the input filter class
     public boolean handleEvent(Event evt)
     {
          if (filter == null) return super.handleEvent(evt);
          if (evt.id == Event.KEY_PRESS) {
               return filter.filter(evt);
          }
          return super.handleEvent(evt);
     }
}

The SmallDevicePanel class requires a helper class called InputFilter that actually maps the keystrokes to a component. The components register the keystrokes they want with the input filter. Listing 40.4 shows the InputFilter class.


Listing 40.4  Source Code for InputFilter.java
import java.awt.*;
import java.util.*;
// This class implements a keystroke filter that allows you to
// create hot keys for various components. It uses a hash table
// to look up the keystrokes.
public class InputFilter extends Object
{
     protected Hashtable filterTable;
     protected boolean filtering;
     public InputFilter()
     {
          filterTable = new Hashtable();
          filtering = false;
     }
// Map a single key value to a component
     public void add(int ch, Component receiver)
     {
          filterTable.put(new Integer(ch), receiver);
     }
// Map a range of key values to a component 
     public void add(int from, int to, Component receiver)
     {
          for (int i=from; i <= to; i++) {
               filterTable.put(new Integer(i), receiver);
          }
     }
// Unmap a key, but only if it belongs to this receiver.
     public void remove(int ch, Component receiver)
     {
          Integer key = new Integer(ch);
          if (filterTable.get(key) == receiver) {
               filterTable.remove(key);
          }
     }
// Unmap a range of keys, but only if they belong to this receiver
     public void remove(int from, int to, Component receiver)
     {
          for (int i=from; i <= to; i++) {
               Integer key = new Integer(i);
               if (filterTable.get(key) == receiver) {
                    filterTable.remove(key);
               }
          }
     }
// This method actually performs the filtering. It uses a flag to
// see if it is already filtering an event. This way, if it passes
// the event to a component and the event gets all the way back up to
// the panel that has the filter, we don't filter it again. Otherwise, we'd
// have an infinite recursion, and that is a bad thing.
     public synchronized boolean filter(Event evt)
     {
// If we're already filtering an event, go away
          if (filtering) return false;
// Now we definitely are filtering an event
          filtering = true;
// See if there's a component that wants this keystroke
          Component comp = (Component) filterTable.get(
               new Integer(evt.key));
// If nobody wanted this keystroke, unset the filtering flag and return
          if (comp == null) {
               filtering = false;
               return false;
          }
// Send this event to the component that wants it
          boolean retval = comp.postEvent(evt);
// We're through filtering
          filtering = false;
// Return the result that came from postEvent
          return retval;
     }
}

An interesting feature of the SmallDevicePanel and the InputFilter is that they allow the components themselves to specify what keystrokes they want to receive. The SmallDevicePanel class checks each component to see whether it implements the FilteredComponent interface, shown in Listing 40.5.


Listing 40.5  Source Code for FilteredComponent.java
// This interface is implemented by any component that wants
// hot keys controlled by the InputFilter class.
public interface FilteredComponent
{
     public void setFilter(InputFilter filter);
}

If a component implements the FilteredComponent interface, the SmallDevicePanel class calls the setFilter method in the component. At that time, the component tells the filter what keystrokes it is interested in. You can safely use regular components with the SmallDevicePanel class, because it explicitly checks for the FilteredComponent interface first. It doesn't do anything if a component doesn't implement the interface.

Listing 40.6 shows the ShortcutButton class, which allows you to specify a character as a shortcut for the button.


Listing 40.6  Source Code for ShortcutButton.java
import java.awt.*;
// This class implements a button that has a shortcut character.
// It works in conjunction with the SmallDevicePanel and InputFilter classes.
public class ShortcutButton extends Button implements FilteredComponent
{
     protected int shortcut;
     protected InputFilter filter;
// Create a button with a specific label and shortcut character
     public ShortcutButton(String label, int shortcut)
     {
          super(label);
          filter = null;
          this.shortcut = shortcut;
     }
// Whenever this button becomes enabled, re-register the shortcut key
// with the input filter.
     public synchronized void enable()
     {
          if (filter != null) {
               filter.add(shortcut, this);
          }
          super.enable();
     }
// Whenever this button becomes disabled, unregister the shortcut key
     public synchronized void disable()
     {
          if (filter != null) {
               filter.remove(shortcut, this);
          }
          super.disable();
     }
     
// If we get a keypress event and the key pressed is the shortcut key,
// generate an ACTION_EVENT event for this button.
     public boolean handleEvent(Event evt)
     {
          if ((evt.id == Event.KEY_PRESS) &&
               (evt.key == shortcut)) {
               return postEvent(new Event(this,
                    Event.ACTION_EVENT, getLabel()));
          }
          return super.handleEvent(evt);
     }
// setFilter is called by the SmallDevicePanel class when this button
// is added to the panel. The button then registers the shortcut key
// with the input filter.
     public void setFilter(InputFilter filter)
     {
          this.filter = filter;
          if ((filter != null) && isEnabled()) {
               filter.add(shortcut, this);
          }
     }
}

Creating a Pop-Up Keypad for Pen and Touch-Screen Users

One of the ways you can address the problem of multiple input sources is by creating special entry pads for those users with only a pen or touch-screen interface. For example, if you have a field where you are entering numbers, allow the pen-based users to click the field and pop up a numeric input pad with buttons for the various digits.

Listing 40.7 shows a NumericInputField that is geared toward small devices. It supports the FilteredComponent interface, so it gets keystrokes via the InputFilter class, if necessary. Furthermore, if you click the field itself, it creates a NumberPad class, which is a SmallDevicePanel, and pushes the pad onto the current display.


Listing 40.7  Source Code for NumericInputField.java
import java.awt.*;
// This class implements a text field for entering integers.
// Because of some of the peculiarities of text fields, it sets
// the field to be non-editable and handles the keystroke events
// manually. It allows * to be used as a delete key to key out
// potential cell-phone users.
public class NumericInputField extends TextField implements FilteredComponent
{
     protected int numDigits;
     protected InputFilter filter;
// When you create the field, you give a limit to the number of digits
     public NumericInputField(int numDigits)
     {
          super(numDigits);
          this.numDigits = numDigits;
          setEditable(false);
     }
     public boolean handleEvent(Event evt)
     {
// If we get a keypress, check to see if the key is a number
          if (evt.id == Event.KEY_PRESS) {
               if ((evt.key >= '0') && (evt.key <= '9')) {
// We got a number, see if there's room to add another digit
                    if (getText().length() >= numDigits) {
                         return true;
                    }
// To add a digit, we create an array of 1 character, turn it into a
// string, and then add that to the current digit string
                    char ch[] = new char[1];
                    ch[0] = (char)evt.key;
                    setText(getText()+new String(ch));
                    return true;
// If we get a '*', remove the last character in the digit string
               } else if (evt.key == '*') {
                    String currText = getText();
                    int len = currText.length();
                    if (len > 0) {
                         setText(currText.substring(0, len-1));
                    }
                    return true;
               }
               return false;
// If we get a mouse down event, pop up a keypad for entering a number
          } else if (evt.id == Event.MOUSE_DOWN) {
               if (getParent() instanceof NumberPad) return true;
               doPad();
               return true;
// If we get an action event, see if it is an action from the number pad.
// When you click "OK" on the number pad, it generates an ACTION_EVENT
// and will send it to you if you ask. In this case, when we get that
// event, we pop the pad back off the stack layout.
          } else if (evt.id == Event.ACTION_EVENT) {
               if (evt.target instanceof NumberPad) {
                    NumberPad pad = (NumberPad)evt.target;
                    setText(""+pad.getValue());
                    pad.pop();
                    return true;
               } else {
                    return false;
               }
          } else {
               return super.handleEvent(evt);
          }
     }
// doPad creates a number pad and pushes it onto the stack layout
     public void doPad()
     {
          NumberPad pad = new NumberPad(numDigits, this);
          pad.push(this);
     }
// getValue returns the numeric value of the digit string
     public int getValue()
     {
          try {
               return Integer.parseInt(getText());
          } catch (Exception e) {
               return 0;
          }
     }
// setFilter tells the input filter what characters we are interested in
     public void setFilter(InputFilter filter)
     {
          this.filter = filter;
          if (isEnabled()) {
               filter.add('0', '9', this);
               filter.add('*', this);
          }
     }
// If this component becomes enabled, re-register the keystrokes
// with the input filter
     public void enable()
     {
          if (filter != null) {
               filter.add('0', '9', this);
               filter.add('*', this);
          }
     }
// If this component becomes disabled, unregister the keystrokes
// with the input filter
     public void disable()
     {
          if (filter != null) {
               filter.remove('0', '9', this);
               filter.remove('*', this);
          }
     }
}

The NumberPad class used by the NumericInputField class is a very simple panel of 12 buttons (0 through 9, *, and #). It passes the digits and the * key on to the NumericInputField class, and uses the # as an OK button, causing the pad to pop off the screen, sending you back to the previous screen. Listing 40.8 shows the NumberPad class.


Listing 40.8  Source Code for NumberPad.java
import java.awt.*;
// NumberPad creates a pushable panel of buttons
// that resembles a telephone keypad. It has the
// digits 0-9 and also * and #. It uses the * key
// as delete and # as OK.
public class NumberPad extends SmallDevicePanel
{
     protected NumericInputField inputField;
     protected int numDigits;
     protected Component notifyMe;
// Creates a number pad which will generate an ACTION_EVENT to
// itself when OK is pressed.
     public NumberPad(int numDigits)
     {
          this.numDigits = numDigits;
          notifyMe = this;
          createPad();
     }
// Creates a number pad that sends the ACTION_EVENT to another
// component when OK is pressed. This allows the NumericInputField
// class to pop up a number pad and receive an action event when
// OK is pressed.
     public NumberPad(int numDigits, Component notifyMe)
     {
          this.numDigits = numDigits;
          this.notifyMe = notifyMe;
          createPad();
     }
// Create the buttons for the pad
     protected void createPad()
     {
          inputField = new NumericInputField(numDigits);
          setLayout(new BorderLayout());
          add("North", inputField);
          Panel buttonPanel = new Panel();
          buttonPanel.setLayout(new GridLayout(4, 0));
          buttonPanel.add(new Button("1"));
          buttonPanel.add(new Button("2"));
          buttonPanel.add(new Button("3"));
          buttonPanel.add(new Button("4"));
          buttonPanel.add(new Button("5"));
          buttonPanel.add(new Button("6"));
          buttonPanel.add(new Button("7"));
          buttonPanel.add(new Button("8"));
          buttonPanel.add(new Button("9"));
          buttonPanel.add(new Button("* DEL"));
          buttonPanel.add(new Button("0"));
          buttonPanel.add(new Button("# OK"));
          add("Center", buttonPanel);
     }
// Return the integer value in the number pad
     public int getValue()
     {
          return inputField.getValue();
     }
// This method handles all the button presses for the keypad. The
// digit that each button represents is conveniently stored as
// the first digit in the label. 
     public boolean action(Event evt, Object whichAction)
     {
// If this event isn't for a button, we don't handle it
          if (!(evt.target instanceof Button)) {
               return false;
          }
          char ch = ((String)whichAction).charAt(0);
// If we get any of the characters that the numeric input field might
// be interested in, pass them along to it.
          if (((ch >= '0') && (ch <= '9')) || 
               (ch == '*')) {
               inputField.postEvent(
                    new Event(evt.target, evt.when,
                         Event.KEY_PRESS,
                         evt.x, evt.y, ch, 0));
               return true;
// If we get a '#', post an action event
          } else if (ch == '#') {
               return notifyMe.postEvent(new Event(this,
                    Event.ACTION_EVENT, new Boolean(false)));
          }
          return super.handleEvent(evt);
     }
}

Listing 40.9 shows a very simple test program that demonstrates the various components presented in this chapter. It creates a numeric input field and a button with a shortcut key of #. The idea is that it can be used by a client who has only a telephone keypad, or by someone who has only a pointing device. If you have only a pointing device, you can click the numeric input field to bring up a keypad to enter a number.


Listing 40.9  Source Code for TestField.java
import java.awt.*;
import java.applet.*;
// This is a simple test applet for the SmallDevicePanel and
// the NumericInputField classes. It creates a numeric field and
// a shortcut button. 
public class TestField extends Applet
{
     NumericInputField inField;
     Button okButton;
     public void init()
     {
          setLayout(new StackLayout());
          SmallDevicePanel startPanel = new SmallDevicePanel();
          startPanel.setLayout(new BorderLayout());
          inField = new NumericInputField(8);
          startPanel.add("North", inField);
          okButton = new ShortcutButton("# OK", '#');
          startPanel.add("South", okButton);
          add(startPanel);
     }
     public boolean action(Event evt, Object whichAction)
     {
          if (evt.target instanceof Button) {
               System.out.println("Your number is "+
                    inField.getValue());
          }
          return false;
     }
}

Figure 40.8 shows the test applet in operation. The applet itself violates one of the design principles in that it is not self-documenting. Its purpose is just to demonstrate the numeric input field.

Figure 40.8 : Your interfaces should support different input sources.

Figure 40.9 shows the number pad that pops up by the numeric input field. You could use this same approach to create a small pop-up keyboard.

Figure 40.9 : Create auxiliary panels to help users with limited input sources.

Whereas these classes may help you get going when designing interfaces for small devices, you really need a full development library geared toward these devices. Hopefully, one will be available by the time Java-enabled hand-held devices become prevalent. Otherwise, you'll have to create many components from scratch.