AWT


Delegation

In the last issue we mentioned the "deprecation" of some AWT methods as of the 1.1 release. Some of these methods are isolated, but the basic event handling model of the AWT has itself changed in 1.1, and it's worth illustrating the new model.

The old model was based on inheritance, with subclassing of AWT components and overriding of the action() and handleEvent() methods. An overriding method either would handle events and pass back "true", or else pass back "false" and propagate the event up to the next component in the hierarchy.

This model is somewhat unwieldy, with a requirement for lots of subclasses, complex logic to process events, and lack of separation between application and interface.

As of 1.1, a new "delegation" model has been implemented. In the new model, a "listener" object expresses interest in events from one or more "source" objects. When an event occurs, it is passed from the source to all registered listeners. Since the listener object implements a standard interface (like ActionListener), the source can invoke a known method on the listener object, to pass the details of the event.

To illustrate this further, consider the following example:
        import java.awt.*;
        import java.awt.event.*;

        class Dispatch implements ActionListener {
                static final int COMMAND1 = 1;
                static final int COMMAND2 = 2;
                static final int EXIT = 3;

                int id;
                Delegate d;

                public Dispatch(int id, Delegate d)
                {
                        this.id = id;
                        this.d = d;
                }

                public void actionPerformed(ActionEvent e)
                {
                        switch (id) {
                                case COMMAND1:
                                        d.command1();
                                        break;
                                case COMMAND2:
                                        d.command2();
                                        break;
                                case EXIT:
                                        d.exit();
                                        break;
                        }
                }
        }

        class Interface {
                public Interface(Delegate d)
                {
                        Frame f = new Frame();
                        f.setLayout(new FlowLayout());

                        Dispatch cmd1 = new Dispatch(Dispatch.COMMAND1, d);
                        Dispatch cmd2 = new Dispatch(Dispatch.COMMAND2, d);
                        Dispatch exit = new Dispatch(Dispatch.EXIT, d);

                        Button b = null;

                        b = new Button("Command #1");
                        f.add(b);
                        b.addActionListener(cmd1);

                        b = new Button("Command #2");
                        f.add(b);
                        b.addActionListener(cmd2);

                        b = new Button("Exit");
                        f.add(b);
                        b.addActionListener(exit);

                        f.pack();
                        f.show();
                }
        }

        public class Delegate {
                public void command1()
                {
                        System.out.println("command #1 invoked");
                }

                public void command2()
                {
                        System.out.println("command #2 invoked");
                }

                public void exit()
                {
                        System.exit(0);
                }

                public static void main(String args[])
                {
                        Delegate d = new Delegate();
                        Interface i = new Interface(d);
                }
        }


In this example, Delegate is our application, and Interface the interface for it. Dispatch is a helper class that implements the ActionListener interface. We tie particular object instances of Dispatch to individual buttons. When a button is selected, an event is passed to the listener for the button, and it in turn invokes the appropriate application method.

There is a lot more that can be said about this approach. A paper that describes it in some detail can be found at:

http://www.javasoft.com/products/jdk/1.1/docs/guide/awt/designspec/events.html

We will be using the delegation model in future AWT examples.


Printing

We saw a simple example above of printing text. Another approach to printing uses the AWT:
        import java.awt.*;

        public class print {

                public static void main(String args[])
                {
                        Frame f = new Frame("test");

                        PrintJob pj =
                            f.getToolkit().getPrintJob(null, "print1", null);

                        Graphics g = pj.getGraphics();

                        g.drawLine(0, 0, 150, 150);

                        pj.end();
                }

        }

This example creates a frame, and retrieves its toolkit. The toolkit is the interface between the AWT proper and an actual GUI/window implementation.

From the toolkit we get a PrintJob instance. This causes a print job screen to be popped up. It asks about how many copies are desired and similar types of questions.

Given a PrintJob instance, we can get a Graphics object, and draw a line to it, and then end the print job.

This feature is in 1.1 and 1.1.1, but without any documentation in the source code, so this presentation is a bit sketchy. There are additional method calls for getting page dimensions and resolution.

As compared to the method illustrated above, using device files, the AWT approach to printing offers platform independence (you don't have to worry about pathnames like "/dev/lp"). This approach also offers graphics and Unicode support.

We will likely have more to say about printing in the future.


Applet Serialization

In previous issues we've discussed some of the aspects of applet programming, and also mentioned a new technique for serializing objects, that is, turning an object into a stream of bytes that can be saved to a file or sent across a network.

We can combine these techniques in order to serialize an applet, that is, to capture it in some current state, and then restore that state later. This technique has various uses, for example to perform one-time initialization.

Consider first an applet:
        import java.awt.*;

        public class applet extends java.applet.Applet {

                private int cnt = 1;

                public void paint(Graphics g)
                {
                        String s = "Hello world #" + cnt++;
                        g.drawString(s, 25, 25);
                }

        }

and some HTML that drives it:
        <html>
        <head>
        <title>Applet Example</title>
        </head>
        <body>
        <applet code="applet.class" width=150 height=150></applet>
        </body>
        </html>

If we run this HTML with the JDK 1.1.2 appletviewer program (1.1 gives an exception for what we're going to do), we can Stop and Start the applet a few times, using the appletviewer menu. Each start results in the displayed count being bumped up by one.

Then, if we select Save, with a name like "applet.ser", the applet object is serialized and written to the indicated file. We can start up the appletviewer again with HTML code of:
        <html>
        <head>
        <title>Applet Example</title>
        </head>
        <body>
        <applet object="applet.ser" width=150 height=150></applet>
        </body>
        </html>

and the applet will take up where it left off.

This technique is quite powerful and useful in a variety of applications. But if you are considering using this approach, you should investigate some of the issues around serialization. For example, not all objects can be serialized. One case of this would be machine-specific resources like file descriptors.


Custom Components

In this issue we will use a longish example of a calculator program to illustrate how one would go about designing a custom AWT component using JDK 1.1.

For a calculator program, where there are several buttons representing digits and operators like "+", one approach would be use the Button component in the AWT. But we have chosen to design our own component for this purpose, which consists of a reddish oval with a number or operator inside. This class is called my_Button in the code below.

When we design our own component, there are several important points to note. One is that the component should have a constructor, to create new instances.

Another is that the component needs a paint() method, to draw itself on the screen.

The component should define getPreferredSize() and getMinimumSize(), to declare its size to the AWT. This is important in setting up the bounding box for the component and for doing layout.

A custom component may be a subclass of an AWT class like Canvas, or in 1.1, it may extend java.awt.Component directly. This latter approach results in what is called a "lightweight" component, one without a native peer in the local windowing system. Lightweight components can be more efficient, because of less overhead. my_Button in the example below is lightweight.

Finally, we should mention how event handling is done in this example. Each of the calculator buttons has a single instance of "docalc" registered as a listener for it, so that when the button is selected, docalc.actionPerformed() is called. This is the method that actually does all the calculating work.

How do we know when a button is selected, given that we are doing our own buttons as custom components? For this, we have another level of event handling, where we register a mouse listener for each instance of my_Button. When the mouse is clicked, we detect that, and dispatch in turn to the listener for the selected button. We could have the mouse clicks dispatch straight through to the docalc instance, but such an architecture is not clean. It's desirable that my_Button act like a regular button, handling its selection of itself within itself and dispatching to a registered listener.
        import java.awt.*;
        import java.awt.event.*;

        class my_Button extends Component implements MouseListener {
                private static final Font        f   =  new Font("Monospaced", 12, Font.PLAIN);
                private static final FontMetrics fm  = Toolkit.getDefaultToolkit().getFontMetrics(f);
                private static final int         asc = fm.getLeading() + fm.getMaxAscent();
                private static final int         h   = asc + fm.getMaxDescent();
                private static final int         PAD = 3;

                private String         str    = null;
                private int            w      = 0;
                private ActionListener listen = null;

                public my_Button(String s, ActionListener al)
                {
                        str = s;

                        w = fm.stringWidth(s);
                        listen = al;
                        addMouseListener(this);
                }

                public void mouseEntered(MouseEvent e) {}
                public void mouseExited(MouseEvent e) {}
                public void mousePressed(MouseEvent e) {}
                public void mouseReleased(MouseEvent e) {}

                public void mouseClicked(MouseEvent e)
                {
                        listen.actionPerformed(new ActionEvent(this, 0, str));
                }

                public Dimension getPreferredSize()
                {
                        return new Dimension(w + PAD * 2, h + PAD * 2);
                }

                public Dimension getMinimumSize()
                {
                        return getPreferredSize();
                }

                public void paint(Graphics g)
                {
                        Color c = g.getColor();

                        g.setFont(f);

                        g.setColor(Color.red);
                        g.fillOval(0, 0, w + PAD * 2 - 1, h + PAD * 2 - 1);
                        g.setColor(Color.white);
                        g.drawString(str, PAD, asc + PAD);
                        g.setColor(c);
                }

        }

        class docalc implements ActionListener {

                private String str_accum = "";
                private String str_num   = "";
                private double accum     = 0.0;
                private double num       = 0.0;
                private String oper      = null;

                private void setText(String s)
                {
                        calc.tf.setText(s);
                        calc.tf.setCaretPosition(s.length());
                }

                public void actionPerformed(ActionEvent e)
                {
                        String str = e.getActionCommand();

                        if (str.equals("Exit"))
                                System.exit(0);

                        if (Character.isDigit(str.charAt(0))) {
                                str_num += str;
                                str_accum += str;
                                setText(str_accum);
                        }
                        else {
                                if (str_num.length() >= 1) {
                                        num = Long.parseLong(str_num);
                                        str_num = "";
                                        if (oper != null) {
                                                switch (oper.charAt(0)) {
                                                case '+':   accum += num;      break;
                                                case '-':   accum -= num;      break;
                                                case '*':   accum *= num;      break;
                                                case '/':   accum /= num;      break;
                                                }
                                        }
                                        else {
                                                accum = num;
                                        }
                                        str_accum += str;
                                        setText(str_accum);
                                        oper = str;
                                }
                                if (str.equals("=")) {
                                        String s = Double.toString(accum);
                                        setText(s);
                                        str_accum = "";
                                        accum = 0.0;
                                        oper = null;
                                }
                        }
                }
        }

        public class calc {

                public static TextField tf = null;

                private static my_Button b(String s, docalc dc)
                {
                        return new my_Button(s, dc);
                }

                private static void setup(Frame f, docalc dc)
                {
                        tf = new TextField(25);
                        tf.setEditable(false);
                        tf.setBackground(Color.blue);
                        tf.setForeground(Color.white);

                        GridBagConstraints gbc = new GridBagConstraints();

                        f.setLayout(new GridBagLayout());

                        gbc.insets = new Insets(2, 2, 2, 2);

                        gbc.gridx = 0;
                        gbc.gridy = 0;
                        gbc.gridwidth = 10;
                        gbc.anchor = GridBagConstraints.WEST;
                        f.add(tf, gbc);

                        gbc.gridy = 1;
                        gbc.gridwidth = 1;
                        gbc.anchor = GridBagConstraints.CENTER;

                        gbc.gridx = 0;
                        f.add(b("0", dc), gbc);

                        gbc.gridx = 1;
                        f.add(b("1", dc), gbc);

                        gbc.gridx = 2;
                        f.add(b("2", dc), gbc);

                        gbc.gridx = 3;
                        f.add(b("3", dc), gbc);

                        gbc.gridx = 4;
                        f.add(b("4", dc), gbc);

                        gbc.gridy = 2;

                        gbc.gridx = 0;
                        f.add(b("5", dc), gbc);

                        gbc.gridx = 1;
                        f.add(b("6", dc), gbc);

                        gbc.gridx = 2;
                        f.add(b("7", dc), gbc);

                        gbc.gridx = 3;
                        f.add(b("8", dc), gbc);

                        gbc.gridx = 4;
                        f.add(b("9", dc), gbc);

                        gbc.gridy = 3;

                        gbc.gridx = 0;
                        f.add(b("+", dc), gbc);

                        gbc.gridx = 1;
                        f.add(b("-", dc), gbc);

                        gbc.gridx = 2;
                        f.add(b("*", dc), gbc);

                        gbc.gridx = 3;
                        f.add(b("/", dc), gbc);

                        gbc.gridx = 4;
                        f.add(b("=", dc), gbc);

                        gbc.gridy = 2;
                        gbc.gridx = 6;
                        gbc.weightx = 1;
                        f.add(b("Exit", dc), gbc);
                }

                public static void main(String args[])
                {
                        Frame f = new Frame("calculator");

                        setup(f, new docalc());

                        f.pack();
                        f.show();
                }
        }


ScrollPane

In previous issues we've seen some examples of the use of scrollbars in GUI applications. With Java 1.1, there is an additional technique for performing scrolling within an application. This approach uses the ScrollPane class in the AWT. ScrollPane does "automatic" scrolling of a specified AWT component, with some user customization possible.

For example, with this code:
        import java.awt.*;

        class CirclePanel extends Component {

                public Dimension getPreferredSize()
                {
                        return new Dimension(150, 150);
                }

                public Dimension getMinimumSize()
                {
                        return getPreferredSize();
                }

                public void paint(Graphics g)
                {
                        g.fillOval(5, 5, 125, 125);
                }
        }

        public class scroll {

                public static void main(String args[])
                {
                        Frame f = new Frame("testing");

                        ScrollPane sp = new ScrollPane();
                        sp.setSize(100, 100);

                        sp.add(new CirclePanel());

                        f.add(sp);

                        f.pack();
                        f.setVisible(true);

                        sp.setScrollPosition(65, 65);

                        Adjustable a = sp.getVAdjustable();
                        a.setUnitIncrement(5);
                }

        }

we create our our own component (CirclePanel) with a filled-in circle inside of it, of size 150 x 150. We create a 100 x 100 ScrollPane object and add this component to it. Without any further settings, this will create a scroll pane with scrollbars, and unit increments for the horizontal and vertical scrollbars.

In this particular example, we have modified the default settings in two ways, one to set the current scroll position to (65,65), and the other to set the vertical scroll increment to 5.

There are several other available features for controlling ScrollPane objects.


Popup Menus

A new feature in JDK 1.1 is popup menus, where a menu is popped up over a GUI component in response to a user mouse action. A simple example of this looks like:
        import java.awt.*;
        import java.awt.event.*;

        class MyPopup extends Component implements ActionListener {

                PopupMenu pum = null;

                String labels[] = {"Command 1", "Command 2", "Command 3"};
                String cmds[]   = {"command1", "command2", "command3"};

                public void actionPerformed(ActionEvent e)
                {
                        String cmd = e.getActionCommand();
                        System.out.println("command was: " + cmd);
                }

                public MyPopup()
                {
                        pum = new PopupMenu();

                        for (int i = 0; i < labels.length; i++) {
                                MenuItem mi = new MenuItem(labels[i]);
                                mi.setActionCommand(cmds[i]);
                                mi.addActionListener(this);
                                pum.add(mi);
                        }

                        add(pum);

                        enableEvents(AWTEvent.MOUSE_EVENT_MASK);
                }

                public void processMouseEvent(MouseEvent e)
                {
                        if (e.isPopupTrigger())
                                pum.show(this, e.getX(), e.getY());
                        else
                                super.processMouseEvent(e);
                }

                public Dimension getPreferredSize()
                {
                        return new Dimension(300, 300);
                }

        }

        public class popup {

                public static void main(String args[])
                {
                        Frame f = new Frame("testing");

                        Panel p = new Panel();

                        p.add("Center", new MyPopup());
                        f.add(p);

                        f.pack();
                        f.setVisible(true);
                }
        }

In this example we create a lightweight component of size 300 x 300, and add a popup menu to it. The menu is created by adding MenuItems to it, with separation of menu labels and commands (a rudimentary step toward internationalization).

We also set up to handle mouse events, which is required to catch the event that triggers the popup. The actual event handling is found in processMouseEvent(), where popup events are handled directly, and others are passed on. When actionPerformed() is called, we can retrieve the action command set earlier.


System Colors

SystemColor is a new 1.1 class that supports access to system colors, that is, colors configured by a user for such things as text, window borders, scrollbars, and so on.

For example, this simple program draws a graphical object that is the same color as a window title bar:
        import java.awt.*;

        class MyCanvas extends Canvas {

                public Dimension getPreferredSize()
                {
                        return new Dimension(200, 200);
                }

                public void paint(Graphics g)
                {
                        g.setColor(SystemColor.activeCaption);
                        g.fillOval(50, 50, 100, 100);
                }
        }

        public class color {

                public static void main(String args[])
                {
                        Frame f = new Frame("testing");

                        Panel p = new Panel();
                        p.add(new MyCanvas());

                        f.add(p);

                        f.pack();
                        f.setVisible(true);
                }
        }

The actual RGB values of a system color may change over time, because a user can customize such colors (for example, using Control Panel with Windows NT). The method getRGB() can be applied to a color to obtain its current color settings.


Custom Cursors

We talked earlier in this issue about focus traversal, a new AWT feature in 1.1. Another new 1.1 feature is custom cursors. With 1.0, one could only set a customized cursor at the Frame level, but with 1.1, a cursor may be set for any component. For example, in this code:
        import java.awt.*;
        import java.awt.event.*;

        public class testcursor implements ActionListener {

                public void actionPerformed(ActionEvent e)
                {
                        System.exit(0);
                }

                public static void main(String args[])
                {
                        Frame f = new Frame("testing");
                        f.setLayout(new BorderLayout());

                        Button b = new Button("OK");
                        b.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
                        b.addActionListener(new testcursor());

                        f.add("North", b);

                        f.setSize(200, 200);
                        f.setVisible(true);
                }
        }

we set a crosshair cursor that is effective only within the area of the Button object that we create.


Keyboard Focus Traversal

We've been spending some time discussing some new features of Java 1.1. Another of these is keyboard focus traversal, which is a fancy way of saying that you can use Tab to move between AWT components, giving each the focus in turn.

For example, with this simple program:
        import java.awt.*;

        public class focus {

                public static void main(String args[])
                {
                        Frame f = new Frame("testing");
                        f.setLayout(new FlowLayout());

                        Button b1 = new Button("Button #1");
                        Button b2 = new Button("Button #2");
                        f.add(b1);
                        f.add(b2);

                        f.pack();
                        f.show();
                }
        }

you can move between the two buttons by pressing Tab, if you are using Java 1.1. Tab is ignored for this same program using 1.0.

Components are traversed in the order added, so button #1 will be visited before button #2. No other knowledge or setup is required to use this feature.


List

Una List e' un elemento grafico costituito da una serie di righe all'interno di una area scrollabile (se necessario), consente la selezione di un Item tra quelli selezionabili (e' pero' possibile eseguire anche selezioni multiple).
In altri linguaggi, come Visual Basic, viene definito ListBox.

La classe List e' descritta nella documentazione come:
"Una lista scollabile di voci testuali."

Costruttori

Metodi

Examples

  public void testChoice()
  {
  }


Choice

Un Choice e' un elemento grafico che sostanzialmente assomiglia ad un Menu, consente la selezione di un Item tra quelli selezionabili.
In altri linguaggi, come Visual Basic, viene definito ComboBox.
Cliccando sul tasto a destra del Choice viene presentato l'elenco degli item da cui scegliere il desiderato.
Con un Choice e' possibile scegliere una sola tra piu' scelte.

Examples

  public void testChoice()
  {
    Button bu_ok     = new Button( "   OK   " );
    Choice ch_one    = new Choice();
    Choice ch_two    = new Choice();

    dlg = new DialogBase( form.frame, "testChoice", true );
    dlg.setSize( 400, 300 );
    dlg.setLayout( new FlowLayout() );

    // add listeners
    // bu_ok.addActionListener( this );
    ch_one.addItemListener( this );
    ch_two.addItemListener( this );

    ch_one.setName( "ONE" );          // set name of Choice
    ch_one.add( "1" );                // add items
    ch_one.add( "2" );
    ch_one.add( "3" );

    ch_two.setName( "TWO" );          // set name of Choice
    ch_two.add( "A" );                // add items
    ch_two.add( "B" );
    ch_two.add( "C" );

    // add objects to the Dialog
    dlg.add( ch_one );
    dlg.add( ch_two );
    dlg.add( bu_ok );

    dlg.setVisible( true );
  }

  // manage Choices
  public void itemStateChanged( ItemEvent e )
  {
    if( e.getSource() instanceof Choice )
    {
      String obj  = ((Component)e.getSource()).getName();
      String item = (String) e.getItem();
      if( obj.equals( "ONE" ) )
      {
        if( item.equals( "1" ) ) System.out.println( "(UNO:1)" );
        if( item.equals( "2" ) ) System.out.println( "(UNO:2)" );
        if( item.equals( "3" ) ) System.out.println( "(UNO:3)" );
      }
      else if( obj.equals( "TWO" ) )
      {
        if( item.equals( "A" ) ) System.out.println( "(UNO:A)" );
        if( item.equals( "B" ) ) System.out.println( "(UNO:B)" );
        if( item.equals( "C" ) ) System.out.println( "(UNO:C)" );
      }
    }
  }


Center a Window

  Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
  awindow.move(   ( screen.width  - awindow.size().width  ) / 2,
                  ( screen.height - awindow.size().height ) / 2  );


Example 1

In previous issues we've discussed various aspects of applet programming. Starting with this issue, we're going to branch out a bit and consider the more general topic of constructing Graphical User Interfaces (GUIs) using the AWT (Abstract Windowing Toolkit). The examples we present will be actual Java standalone programs rather than applets. That is, they'll have their own main() method.

The AWT can be viewed as a high-level abstract means of describing what a window or windows should look like. The AWT is actually implemented differently on different platforms, using windowing primitives appropriate to each platform. You can view GUI programming using the AWT as construction of a complex data structure describing a window, which is interpreted at program execution time.

Let's start out by looking at a simple example. This program takes a single argument, which is the name of a text file, and displays that file in a scrollable window. This is kind of like the "more" program available in UNIX and DOS:
        import java.awt.*;
        import java.io.*;

        public class More extends Frame {

                private static Button   b1 = new Button( "New File" );
                private static Button   b2 = new Button( "Exit" );
                private static TextArea ta = new TextArea( 24, 80 );

                // handle mouse events

                public boolean action(Event e, Object arg)
                {
                        if (e.target instanceof Button) {
                                if (e.target == b1) {
                                        new_file();
                                        return true;
                                }
                                if (e.target == b2) {
                                        System.exit(0);
                                        return true;
                                }
                                return false;
                        }
                        else {
                                return false;
                        }
                }

                // select a new file to view

                private void new_file()
                {
                        FileDialog fd = new FileDialog(this, "Open File",
                            FileDialog.LOAD);
                        fd.show();
                        if (fd.getDirectory() == null ||
                            fd.getDirectory().equals(""))
                                return;
                        if (fd.getFile() == null || fd.getFile().equals(""))
                                return;
                        if (fd.getFile().equals("*.*"))
                                return;
                        String fn = fd.getDirectory() + File.separator +
                            fd.getFile();
                        load(fn);
                }

                // load a file

                private void load(String fn)
                {
                        RandomAccessFile raf = null;

                        StringBuffer sb = new StringBuffer();
                        try {
                                String s = null;
                                raf = new RandomAccessFile(fn, "r");
                                while ((s = raf.readLine()) != null) {
                                        sb.append(s);
                                        sb.append('\n');
                                }
                                raf.close();
                        }
                        catch (Throwable e) {
                                System.err.println("file I/O error");
                                System.exit(1);
                        }
                        ta.setText(sb.toString());
                }

                // constructor

                public More(String title, String fn)
                {
                        super(title);

                        resize(600, 450);

                        Panel p1 = new Panel();
                        p1.setLayout(new FlowLayout( FlowLayout.CENTER, 100, 0) );
                        p1.add(b1);
                        p1.add(b2);

                        Panel p2 = new Panel();
                        ta.setEditable(false);
                        p2.add(ta);

                        setLayout(new FlowLayout());

                        add(p1);
                        add(p2);

                        load(fn);

                        show();
                }

                public static void main(String args[])
                {
                        Frame f = new More( "More", args[0] );
                }

        }

We start by constructing a Frame, an AWT type suitable for representing a top-level application window. We resize the frame, and then add a couple of panels to it. What are panels? A panel is an AWT entity useful for holding or representing a logical chunk of a larger interface. In this example, one of the panels holds a couple of buttons used for switching files and for exiting, and the other holds the text area that actually displays the file.

Given two buttons within a panel, or two panels in a frame, how does the AWT know how to order or arrange these items? We tell the AWT about this via a layout manager, in this case by establishing a FlowLayout object and attaching it to the frame. This type of layout arranges objects in rows left to right, and goes to the next row when it runs out of space. So the two buttons are arranged left to right within panel p1. Panel p2 is laid out on the next "row" after p1, because you can't fit a large text area right next to a set of buttons.

Actual file loading is straightforward. We read the lines in from the file, append them together, and when done call setText() to set the text for the TextArea object. A TextArea object already has scroll bars with it and we don't need to worry about those.

If we want to switch files, we can use a FileDialog entity, which handles most of the work of file selection. This brings up a menu of files that looks similar to what you see on a PC running Windows.

Finally, we define an action() method for handling events in the frame, in this case file switching or exiting.

This program has a couple of deficiencies, most notably that it reads a whole file in at one time. With fairly slow I/O this can be a problem for very large files. But the program does serve to illustrate some of the basics of AWT use. We'll be looking at some of these areas in more detail in future issues.


Example 2

In the last issue we started a series on AWT (Abstract Window Toolkit) programming. We presented an example of writing a "more" program, one that can be used to page through text from a file. In this issue we'll show another more complicated and powerful way of achieving the same end.

Before diving into this, a few general comments are in order. This code is written against JDK 1.1. It gives some "deprecation" warnings, about methods which have changed in the JDK. As such, the code needs to be updated, but doing so would make it not work with earlier versions. This is simply something that we have to put up with in an evolving language.

Also, this code reveals a bug in the JDK 1.1 implementation for Windows NT. If you compile this program and run it, you will notice that scroll bars are flaky. This is a known bug that is supposed to be fixed in a bug fix release of 1.1.

Finally, we are getting into an area that is perhaps not as stable as some other parts of Java, and still evolving (as is my understanding of it). Some aspects of the example below, notably layout manager usage, are a bit tricky.

As you will recall, the previous version of the "more" program had one big deficiency, namely, that it read in a whole file before displaying it. This is not acceptable for a very large file, especially with relatively slow I/O. We've fixed that problem, and to do so, set up our own text windowing scheme with scroll bars which we manage.

Here is the actual code. If you scan down the left side, you can see interspersed comments starting with "//" in the left margin.
        import java.awt.*;
        import java.io.*;

        // a text area in the window

        // This is the class used to manage a text area, with scroll bars.
        // It is passed a RandomAccessFile object to read lines from, and
        // reads them on demand only.
        // A Canvas is an AWT object suitable for displaying text on.

        class Text extends Canvas {

                private static final int LISTSIZ = 16;
                private String           list[]  = null;
                private int              scnt    = 0;
                private int              currpos = 0;
                private RandomAccessFile raf     = null;
                private boolean          ateof   = false;

                // initialize
                void init(RandomAccessFile r)
                {
                        if (raf != null) {
                                try {
                                        raf.close();
                                }
                                catch (Throwable e) {
                                        System.err.println("close err");
                                        System.exit(1);
                                }
                        }
                        raf = r;
                        ateof = false;
                        currpos = 0;

                        scnt = 0;
                        list = new String[LISTSIZ];
                }

        		// We need to detab Strings that are displayed, because tabs
        		// are handled in a funny way by Graphics.drawString().
                private static String detab(String s)
                {
                        StringBuffer sb = new StringBuffer();
                        int len = s.length();
                        int pos = 0;
                        for (int i = 0; i < len; i++) {
                                char c = s.charAt(i);
                                if (c == '\r' || c == '\n') {
                                }
                                else if (c == '\t') {
                                        do {
                                                sb.append(' ');
                                                pos++;
                                        } while (pos % 8 != 0);
                                }
                                else {
                                        sb.append(c);
                                        pos++;
                                }
                        }
                        return sb.toString();
                }

        		// This is the internal list used to store lines to be displayed.
        		// We could also use java.util.Vector to manage this list, and
        		// System.arraycopy() to copy the list.
                // add a String to the list
                void add(String s)
                {
                        if (scnt == list.length) {
                                String x[] = new String[list.length * 3 / 2];
                                for (int i = 0; i < scnt; i++)
                                        x[i] = list[i];
                                list = x;
                        }
                        list[scnt++] = detab(s);
                }

        		// Called by the handleEvent() method below.
                // scroll up or down a line
                void scroll(int dir)
                {
                        currpos += dir;

                        if (currpos < 0)
                                currpos = 0;

                        repaint();
                }

        		// This is the paint() method, used to draw lines in the text window.
        		// We use FontMetrics to determine how tall a character is, and
        		// display as many lines as will fit.
                public void paint(Graphics g)
                {
                        Dimension d = size();
                        int startpos = More.HEIGHT - d.height;
                        int pos = startpos;
                        int sp = g.getFontMetrics().getHeight();
                        int i = currpos;

                        while (pos + sp < d.height) {
                                while (i >= scnt && !ateof) {
                                        String s = null;
                                        try {
                                                s = raf.readLine();
                                        }
                                        catch (Throwable e) {
                                                System.err.println("I/O err");
                                                System.exit(1);
                                        }
                                        if (s == null)
                                                ateof = true;
                                        else
                                                add(s);
                                }
                                if (i >= scnt)
                                        break;
                                g.drawString(list[i], 5, pos);
                                i++;
                                pos += sp;
                        }
                }

        }

        // We use a Panel to contain the text area, along with scroll bars.
        // A Panel is an AWT object that can contain other objects.
        // a Panel for the text area, with scroll bars

        class Panel_ta extends Panel {

                private Scrollbar vbar = null;
                private Text t = null;

                // constructor
                Panel_ta(Text ta)
                {
                        vbar = new Scrollbar(Scrollbar.VERTICAL);

                        // Put the text area in the center, and the scroll bar on the "East" side.
                        setLayout(new BorderLayout(0, 0));
                        add("Center", ta);
                        add("East", vbar);
                        t = ta;
                }

                // handleEvent() is called for actual scrolling.
                public boolean handleEvent(Event e)
                {
                        if (e.target == vbar) {
                                switch (e.id) {
                                        case Event.SCROLL_LINE_UP:
                                                t.scroll(-1);
                                                break;
                                        case Event.SCROLL_LINE_DOWN:
                                                t.scroll(1);
                                                break;
                                }
                                return true;
                        }
                        else {
                                return super.handleEvent(e);
                        }
                }

        }

        // The actual "More" class.

        public class More extends Frame {

                private Button b1 = new Button("New File");
                private Button b2 = new Button("Exit");
                private Text   ta = new Text();

                static final int WIDTH  = 600;
                static final int HEIGHT = 450;

                // A dialog invoked when the user selects "New File".
                // open a new file
                private void new_file()
                {
                        FileDialog fd = new FileDialog(this, "Open File",
                            FileDialog.LOAD);
                        fd.show();
                        if (fd.getDirectory() == null ||
                            fd.getDirectory().equals(""))
                                return;
                        if (fd.getFile() == null || fd.getFile().equals(""))
                                return;
                        if (fd.getFile().equals("*.*"))
                                return;
                        String fn = fd.getDirectory() + File.separator +
                            fd.getFile();
                        load(fn);
                }

                // Called when buttons are selected.
                // handle an action
                public boolean action(Event e, Object arg)
                {
                        if (e.target instanceof Button) {
                                if (e.target == b1) {
                                        new_file();
                                        return true;
                                }
                                if (e.target == b2) {
                                        dispose();
                                        System.exit(0);
                                        return true;
                                }
                                return false;
                        }
                        else {
                                return false;
                        }
                }

                // Load a new file in.
                private void load(String fn)
                {
                        try {
                                RandomAccessFile raf =
                                    new RandomAccessFile(fn, "r");
                                ta.init(raf);
                        }
                        catch (Throwable e) {
                                System.err.println("open err");
                                System.exit(1);
                        }

                        ta.repaint();
                }

                // Constructor for More.
                public More(String title, String fn)
                {
                        super(title);

                        resize(WIDTH, HEIGHT);

        // Set a layout manager for the buttons, flowing across the screen.
                        Panel p1 = new Panel();
                        p1.setLayout(new FlowLayout(FlowLayout.CENTER,50,0));

                        p1.add(b1);
                        p1.add(b2);

        // Set a fixed-width font, size 12.
                        ta.setFont(new Font("Courier", Font.PLAIN, 12));

                        Panel p2 = new Panel_ta(ta);

        // Set a layout manager for the buttons at the top, and the text area
        // at the bottom.  GridBagLayout is the most complicated and powerful
        // of the layout managers.  You can specify cell positions, cell
        // weights, and so on.  Each object to be laid out has its constraints
        // set for it.
        // In this case, we give the top row of buttons a weight of 1, and the
        // text area a weight of 7, so the text area will take 7/8 of the
        // total window.

                        GridBagLayout      gbl = new GridBagLayout();
                        GridBagConstraints gbc = new GridBagConstraints();

                        setLayout(gbl);

                        gbc.gridx   = 0;
                        gbc.gridy   = 0;
                        gbc.weightx = 1;
                        gbc.weighty = 1;
                        gbc.fill    = GridBagConstraints.BOTH;
                        gbc.anchor  = GridBagConstraints.CENTER;

                        gbl.setConstraints(p1, gbc);

                        gbc.gridy = 1;
                        gbc.weighty = 7;

                        gbl.setConstraints(p2, gbc);

                        add(p1);
                        add(p2);

                        load(fn);

                        show();
                }

                // Driver main() method.
                public static void main(String args[])
                {
                        Frame f = new More("More", args[0]);
                }
        }

This example is fairly complicated, but illustrates several important points about the AWT. There are other ways to approach this problem. Another feature we could add would be one to search through the file for a specified string, and display the line it's found on.