Skip to main content.

Web Based Programming Tutorials

Homepage | Forum - Join the forum to discuss anything related to programming! | Programming Resources

Developing Professional Java Applets

Chapter 4 -- Enhancing the Spreadsheet Applet

Chapter 4

Enhancing the Spreadsheet Applet


CONTENTS




The previous chapter laid the foundations for the spreadsheet applet to be developed throughout this part of the book. In creating this first version of the spreadsheet, Chapter 3, "Building a Spreadsheet Applet," introduces many of the fundamental concepts of AWT programming. It also discusses exception handling and practical ways to handle errors in a Java application. However, this first version of the spreadsheet applet also includes elements such as menus and frames that were not discussed at the time they were presented. These concepts are expanded on in this chapter.

While exploring these aspects of Java programming, this chapter will illustrate more features of the AWT package, and discussing the Dialog classes will lead to the practical addition of dialog boxes to the spreadsheet applet. Custom dialog boxes will be created for the purposes of selecting the color and font of a spreadsheet cell; they will be accessed through an expanded menu and their selections will be made through upgraded classes. This chapter also includes an introduction to streams programming, which is used to add the capability of saving and reading a spreadsheet file. Security issues related to file storage in Java will be an important part of this discussion.

Overview of AWT: Part 2

The previous chapter's version of the spreadsheet applet did not cover how to use the Frame and Menu classes. These features of AWT are actually closely related. The Frame class also has a common relationship with the Dialog class because they both are from the same superclass, Window; therefore, dialog boxes will also be covered in this section.

AWT applications can also be enhanced through the support of multiple colors and fonts. Using them can not only make an applet more visually pleasing, but can also give the user more freedom in customizing the spreadsheet's display. The Color, Font, and FontMetrics classes that make this possible are, therefore, also an appropriate topic of discussion here.

Windows and Frames

The Window class is used in AWT to create "popup" windows that appear outside the constraints of the normal browser area allocated to an applet. The Window class is derived from the Container class and so can contain other components. Unlike applet components tied directly to a browser page, Window classes are not restricted to a prespecified area of the screen. Window objects can be resized as their immediate requirements dictate. AWT can perform this automatically through the Window class's pack() method; it works with the Window layout (by default, BorderLayout) to arrive at the optimal presentation of the window, given its contained components and screen resolution. Typically, pack() is called before a window is displayed. Windows are not made visible until the show() method is called. They are removed from the screen, and their resources freed, when the dispose() method is invoked.

The Frame class extends Window by adding a title bar, a border for resizing, support for menus, and the ability to modify the system cursor to various states such as waiting or moving. For most GUI platforms, the Frame's title bar will be tied to system control boxes, such as minimize, maximize, or destroy. Consequently, the Frame class has all the elements necessary to make an applet look like a "real" application, complete with menus and system controls.

Figure 4.1 and Listing 4.1 present a simple Frame applet that changes the cursor to a state based on the button selected. The applet class, FrameCursorApplet, does little more than launch the main frame, FrameCursor. The constructor for this frame begins by calling the frame super constructor. The sole parameter in this case is the caption displayed on the title bar. The layout for the Frame is then set. The default is BorderLayout, but in this case, you want a three by two grid matrix, hence the use of GridLayout. The buttons are added to the frame next, with names representing the cursor state to be selected. After all the components have been added, the pack() method is invoked so that the button placement can be optimized. Since this optimized placement will result in a small frame (six buttons sized tightly around the label text doesn't take much space), the resize() method is called to make the frame a larger size. Finally, the frame is displayed with the show() method.

Figure 4.1 : Frame applets for changing the state of the cursor.

When a button is selected, the custom method SetCursor() is invoked. This method takes the button label and figures out which cursor should be displayed. The Frame setCursor() method is used to set the cursor state; its parameter is a static integer defined as part of the Frame class.

Note
You might see the message Untrusted Java Applet Window in your Netscape browser when applet frames are displayed. However, this is not cause for alarm-it is just a security message.


Listing 4.1. Code for Frame applet that changes the cursor state.
import java.awt.*;
import java.lang.*;
import java.applet.*;

// This applet simply starts up the frame used to
// show different frame cursors...
public class FrameCursorApplet extends Applet  {
    public void init() {
      // Create the frame with a title...
      new FrameCursor("Frame Cursors");
   }
}

// The frame for letting the user pick different
// cursors to display...
class FrameCursor extends Frame {
   // Create the frame with a title...
   public FrameCursor(String title) {
      // Call the superclass constructor...
      super(title);
      // Create a grid layout to place the buttons...
      setLayout(new GridLayout(3,2));
      // Add the buttons for choosing the cursor...
      add(new Button("Default"));
      add(new Button("Wait"));
      add(new Button("Hand"));
      add(new Button("Move"));
      add(new Button("Text"));
      add(new Button("SE Resize"));
      // Pack and display...
      pack();
      resize(300,200); // Make it a reasonable size...
      show();
   }

   // Handle events...
   public boolean handleEvent(Event e) {
      switch(e.id) {
         case e.WINDOW_DESTROY:
            dispose();  // Erase frame
            return true;
            case Event.ACTION_EVENT:
            if (e.target instanceof Button)
               SetCursor((Button)e.target);
            return true;
         default:
            return false;
      }
   }

   // Set the cursor based on the button chosen...
   void SetCursor(Button btn) {
      // Get the label of the button...
      String selection = btn.getLabel();
      //Set the cursor based on that label...
      if (selection.equals("Wait"))
            setCursor(Frame.WAIT_CURSOR);
      else if (selection.equals("Hand"))
            setCursor(Frame.HAND_CURSOR);
      else if (selection.equals("Move"))
            setCursor(Frame.MOVE_CURSOR);
      else if (selection.equals("Text"))
            setCursor(Frame.TEXT_CURSOR);
      else if (selection.equals("SE Resize"))
            setCursor(Frame.SE_RESIZE_CURSOR);
      else // Just use the default...
            setCursor(Frame.DEFAULT_CURSOR);
   }
}

The current state of the cursor can be retrieved with the getCursorType() method, and the getTitle() and setTitle() Frame methods can be used for getting and setting the title bar caption, respectively. Similarly, the getIconImage() and setIconImage() methods can be used to set the image display of an iconized frame.

Menus

Frames and menus are closely related since the Frame class is the only AWT class with built-in support for menus. Frames implement the MenuContainer interface, which is used to contain menu components. These components are defined by the MenuComponent class, the superclass of all menu components. It defines a set of methods pertinent to individual menu items. For example, the getFont() and setFont() MenuComponent methods are used to control the font selection of the menu object.

Menus can be illustrated by modifying the previous example to use menus, instead of buttons, to change the state of a cursor. Listing 4.2 shows the code that performs this. The constructor of the Frame class, called FrameMenuCursor, shows how to add menus to a frame. The first step is creating a MenuBar object. The menu bar is tied to the frame with the setMenuBar() method of the Frame class. This makes the MenuBar object the default menu for the frame.

The next step is to add the individual menus to the menubar. The Menu class is used as a container of individual menu items or other menus. In this applet, the two menus are File and Cursor. The Menu constructor takes these strings as its sole parameter, thus defining the text to be associated with the menu. The menus are then added to the menu bar through the MenuBar add() method. The remove() method can be used to delete a Menu from a MenuBar.

The final step in menu creation is to add the individual menu items, defined by the MenuItem class. This class is a subclass of MenuComponent and provides additional operations to be performed on a menu item. MenuItem methods can be used to set the menu label and to disable or enable an item. The constructor for the MenuItem has as its sole parameter the item label that will be initially displayed. The add() method is used to add an instance of MenuItem to a Menu.


Listing 4.2. Code for applet that uses menus to change cursor state.
import java.awt.*;
import java.lang.*;
import java.applet.*;

// This applet simply starts up the frame
// which provides a menu for setting cursors...
public class FrameMenuCursorApplet extends Applet {
   public void init() {
      // Create the frame with a title...
      new FrameMenuCursor("Menu Based Cursors");
   }
}

// The frame for letting the user pick different
// cursors to display...
class FrameMenuCursor extends Frame {
   // Create the frame with a title...
   public FrameMenuCursor(String title) {
      // Call the superclass constructor...
      super(title);

      // Add the menus...
      // First create the menu bar
      MenuBar mbar = new MenuBar();
      setMenuBar(mbar); // Attach to the frame...

      // Add the File submenu...
      Menu m = new Menu("File");
      mbar.add(m);  // Add to menu bar
      // Add Quit to the submenu...
      m.add(new MenuItem("Quit"));

      // Add the Cursor submenu...
      m = new Menu("Cursor");
      mbar.add(m);  // Add to menu bar

      // Add the cursor selections to the submenu...
      m.add(new MenuItem("Default"));
      m.add(new MenuItem("Wait"));
      m.add(new MenuItem("Hand"));
      m.add(new MenuItem("Move"));
      m.add(new MenuItem("Text"));
      m.add(new MenuItem("SE Resize"));

      // Pack and display...
      pack();
      resize(300,200); // Make it a reasonable size...
      show();
   }

   // Handle events...
   public boolean handleEvent(Event e) {
      switch(e.id) {
         case e.WINDOW_DESTROY:
            dispose();  // Erase frame
            return true;
         case e.ACTION_EVENT:
            // Process menu selection...
            if (e.target instanceof MenuItem) {
               // Get the name of the menu selection..
               String menuName = e.arg.toString();
               // Dispose of frame if quit is chosen...
               if (menuName.equals("Quit"))
                     dispose();
               // Otherwise, set the cursor...
               if (menuName.equals("Default"))
                     setCursor(Frame.DEFAULT_CURSOR);
               if (menuName.equals("Wait"))
                     setCursor(Frame.WAIT_CURSOR);
               if (menuName.equals("Hand"))
                     setCursor(Frame.HAND_CURSOR);
               if (menuName.equals("Move"))
                     setCursor(Frame.MOVE_CURSOR);
               if (menuName.equals("Text"))
                     setCursor(Frame.TEXT_CURSOR);
               if (menuName.equals("SE Resize"))
                     setCursor(Frame.SE_RESIZE_CURSOR);
               return true;
            } // end if
            return true;
         default:
            return false;
      }
   }
}

A MenuItem can be removed from a Menu with the remove() method, much as it is in the MenuBar class. A separator, dividing menu items, can be added with the addSeparator() method of the Menu class. This is useful for menus that have different categories of options.

The current example also illustrates how to process menu selections. When a menu item is selected, an ACTION_EVENT is issued. The instanceof operator can be used in the handler to verify that the target of the action is a menu. By taking the Event argument (the arg variable) and converting it to a string, the program can get the name of the menu item. Through the name, the appropriate course of action can then be taken. In this example, the Quit menu item forces the frame to shut down by using the dispose() method. The other menu choices result in a new state for the cursor.

It should be mentioned that the CheckboxMenuItem class can be implemented for menus that need to use checkmarks to indicate whether the item has been selected. This is handy for menu items that toggle.

Dialogs

Like the Frame class, the Dialog class is a subclass of Window. Dialogs differ from Frames in a couple of subtle ways, however. The most important of these differences is that Dialogs can be modal. When a modal Dialog is displayed, input to other windows in the Applet is blocked until the Dialog is disposed. This feature points to the general purpose of Dialogs, which is to give the user a warning or a decision to be made before the program can continue. Although non-modal, or modeless, Dialogs are supported, most Dialogs are modal.

There are two constructors for the Dialog class. Both take a Frame object as a parameter; they also take a boolean flag, which indicates whether the dialog should be modal. If the flag is set to true, the dialog is modal. The constructors differ only in a parameter that specifies whether the dialog should have a title caption. It is this constructor that is used in the example that follows.

Figure 4.2 shows a variation of the previous applet, except that a Dialog is used to change the cursor state. Listing 4.3 shows the code for the Dialog class, called ChangeCursorDialog. The Frame class (still called FrameMenuCursor from the previous example) declares the dialog and instantiates it as follows:

Figure 4.2 : Using Dialog to change the state of the cursor

ChangeCursorDialog dlg;
dlg = new ChangeCursorDialog(this,true,"Change the cursor");

The menu for this application is different from the previous example. The frame constructs the menu as follows:

// First create the menu bar
MenuBar mbar = new MenuBar();
setMenuBar(mbar); // Attach to the frame...

// Add the File submenu...
Menu m = new Menu("File");
mbar.add(m);  // Add to menu bar
// Add Dialog to the submenu...
m.add(new MenuItem("Cursor Dialog"));
// Add a separator
m.addSeparator();
// Add Quit to the submenu...
m.add(new MenuItem("Quit"));
Note that a separator is added to divide the two menu items.
When the Cursor Dialog menu item is chosen, the dialog box is presented with the following code:
if (menuName.equals("Cursor Dialog"))
    dlg.show(); // Make the dialog visible...
Listing 4.3. Code for a Dialog to change the cursor state.
import java.awt.*;
import java.lang.*;
import java.applet.*;

// Dialog that presents a grid of buttons
// for choosing the Frame cursor. A Cancel
// button exits the dialog...
class ChangeCursorDialog extends Dialog {
FrameMenuCursor fr;
// Create the dialog and store the title string...
public ChangeCursorDialog(Frame parent,boolean modal,String title) {
// Create dialog with title
super(parent,title,modal);
fr = (FrameMenuCursor)parent;
// The layout is Grid layout...
setLayout(new GridLayout(3,2));
// Add the button options
add(new Button("Default"));
add(new Button("Wait"));
add(new Button("Hand"));
add(new Button("Move"));
add(new Button("Text"));
add(new Button("Cancel"));
// Pack and size for display...
pack();
resize(300,200);
}
// Look for button selections to
// change the cursor...
public boolean action(Event e,Object arg) {
     // If button was selected then exit dialog..
if (e.target instanceof Button) {
         // And possibly change the cursor...
         if (arg.equals("Default"))
            fr.setCursor(Frame.DEFAULT_CURSOR);
         if (arg.equals("Wait"))
            fr.setCursor(Frame.WAIT_CURSOR);
         if (arg.equals("Hand"))
            fr.setCursor(Frame.HAND_CURSOR);
         if (arg.equals("Move"))
            fr.setCursor(Frame.MOVE_CURSOR);
         if (arg.equals("Text"))
            fr.setCursor(Frame.TEXT_CURSOR);
         dispose();
    }
    return false;
}
}

Another AWT class called FileDialog is a subclass of Dialog. It is used for creating stock Load and Save dialog boxes. The upcoming tutorial has examples of the FileDialog class.

Colors

The Color class is used to set an object's colors. This class represents a color as a combination of RGB values. RGB is a 24-bit representation of a color composed of red, green, and blue byte values. This 24-bit representation is virtual since many platforms do not support 24-bit color. In these situations, Java maps the color to the appropriate value; this will usually be an index into a palette.

The Color class defines a set of stock color values, implemented as static variables. For example, Color.red is available with an RGB value of 255,0,0. Other colors supported are white, black, gray, green, blue, yellow, magenta, and cyan. Various Color constructors can also be used to define other colors. For example, the statement

Color col = new Color(0,0,120);

produces a dark blue color. Other methods, such as darker() and brighter(), can also be used to create a new Color object from an existing one.

There are generally two approaches to tying a color to an object. The first approach is to use methods provided by the Component class. The setBackground() and setForeground() methods are used to set the colors of an object in the two respective positions. For example, the following code creates a Panel object and sets its background to white:

Panel p = new Panel();
p.setBackground(Color.white);

You can use the respective methods prefixed by "get" to return the background and foreground colors of a component.

The other approach to setting a color occurs in the paint() method. Recall that a Graphics object is passed to this method. The setColor() method can then be used to set the color of the next items to be painted. For example, the following code paints a blue framed rectangle:

public synchronized void paint (Graphics g)
g.setColor(Color.blue);
g.drawRect(0,0,100,40);

The tutorial in this section gives more examples of using color. A dialog box presents a choice of colors through the use of colored components. The spreadsheet is modified to use these chosen colors when it is painted.

Fonts

Fonts are a critical aspect of graphics-based programming. Not only are they important for making an application attractive, they are a seminal part of programming a visual interface. Often how a component is sized or placed turns on what font is being used. The spreadsheet tutorial in this chapter, for example, uses the current dimensions of the font to determine how the spreadsheet cell should be sized and located. Therefore, there are two aspects of font programming: tying fonts to display components and getting font information to program how graphical objects are positioned. The former aspect generally falls in the domain of the Font class; the latter is tied to the FontMetrics class, although Font is involved here also.

To construct a Font object, you need a font name, style, and size. The font name represents a family of fonts, such as Courier. The font families available to an applet depend on where the program is running, and so decisions about what font is to be used should be made dynamically. The java.awt.Toolkit has a method called getFontList() that returns a string array of font names on the host system. An example of how this method is used is found in the next section and in the font dialog in the tutorial. The family of a Font can be retrieved through the getFamily() method.

The size of a font is its point size, such as 8 for a small font and 72 for a large one. There are three styles defined as constants in the Font class: PLAIN, BOLD, or ITALIC. The latter two can be combined to produce a font with both features. For example, you could create a Helvetica font that has a 36-point size and is both bold and italicized as follows:

Font f = new Font("Helvetica", Font.BOLD + Font.ITALIC,36);

Fonts can be tied to a component through the setFont() method. To create the previous font and tie it to a label, the following code could be invoked:

Label l = new Label("My Label");
l.setFont(new Font("Helvetica", Font.BOLD + Font.ITALIC,36));

A reference to a component's Font can be retrieved through getFont().

Another way to use fonts is in the paint() method. The Graphics class used in paint() can have the current font set through the setFont() method. For example, Listing 4.4 provides the code for an applet that paints a series of Strings containing a row number. Figure 4.3 shows the output. When the paint() method begins, a new font is created and set to the graphics object; this font is now the current font used for the following graphics calls. The drawString() method is then called repeatedly with the y coordinate being dynamically calculated.

Figure 4.3 : A Font applet using hardcoded coordinates


Listing 4.4. Code for Font applet using hard-coded coordinates.
import java.awt.*;
import java.lang.*;
import java.applet.*;

// This aspect throws a string into the applet
// display separated by a height difference of the
// current font
public class FontTest extends Applet  {
    public void paint(Graphics g) {
      int y;
      Font f = new Font("Helvetica",Font.BOLD + Font.PLAIN,16);
      g.setFont(f);
      for (int i = 0; i < 5; ++i) {
         y = 40 + (i * 20);
         g.drawString("Row " + i + " of text output.",10,y);
      } // end for
   }
}

There are a couple of things about this applet that are not quite optimal. First of all, it isn't good to create a new Font in the paint routine. Painting occurs frequently and font creation is an expensive operation. It is faster and more efficient to create the font only once, as in the init() method.

The other thing that might be avoided is using hard-coded values, which is notorious for displays that do not appear uniformly across different platforms. (After all, that is why AWT has layouts!) Although this applet might look decent across platforms, it gives an excuse for showing a better way of using coordinates. This is where the FontMetrics class comes in.

FontMetrics is used to get a variety of information about a Font object. Such information includes the font's dimensions, how big a String or character using that font would be, and so forth. Table 4.1 lists some of the methods of the FontMetrics class.

Table 4.1. FontMetrics methods.

MethodDescription
charWidth Returns width of character using this font.
GetAscent Returns the ascent of the font (distance between the baseline and top of the character).
GetDescent Returns descent of font (distance from the baseline of the font and its bottom).
getHeight Returns total height of font (ascent + descent + leading).
getLeading Returns the line spacing of the font.
stringWidth Returns the number of pixels a String using this font will take.

You can retrieve a FontMetrics object for a specific font in a couple of ways. First of all, the FontMetrics object can be created from scratch by using its constructor; it takes the Font in question as its only parameter. The reference to the FontMetrics can also be taken directly from the Graphics class by using the getFontMetrics() method. The first technique is used when the program is not painting, but that is when the latter technique is applied. Finally, it could retrieve from the default toolkit with a method also named getFontMetrics().The next section explains how the Toolkit class works.

Figure 4.4 shows an improved version of the Font applet through the use of FontMetrics; Listing 4.5 gives the new code. In this version of the applet, each of the String displays are equally incremented by the height of the font. The code calculates the height increment from the getHeight() method of the FontMetrics reference of the Graphics object. The new code is also more efficient than the previous example since it creates the font only once, at initialization.

Figure 4.4 : A Font applet using FontMetrics.


Listing 4.5. Code for a Font applet that uses FontMetrics.
import java.awt.*;
import java.lang.*;
import java.applet.*;

// This aspect throws a string into the applet
// display separated by a height difference of the
// current font
public class FontTest extends Applet {
   Font f;
   // Create the font...
   public void init() {
     f  = new Font("Helvetica",Font.BOLD + Font.PLAIN,16);
   }
   // Paint using TextMetrics to set the height in even
   // increments...
   public void paint(Graphics g) {
      // Set the font
      g.setFont(f);
      // Always increment by twice the height of the font...
      int heightIncrement =  2 * g.getFontMetrics().getHeight();
      int y = 0;
      for (int i = 0; i < 5; ++i) {
         y += heightIncrement;
         g.drawString("Row " + i + " of text output.",10,y);
      } // end for
   }
}

The tutorial that follows will have even more examples of fonts, including a dialog box that can be used to select a font family, size, and style.

The Toolkit Class

The AWT Toolkit class provides the link between the AWT system-independent interface and the underlying platform-based toolkit (such as Windows). The class is abstract, so the getDefaultToolkit() method must be called to get a reference to the actual Toolkit object. Once this is done, all kinds of information about the underlying environment can be revealed. By working with the Toolkit, this underlying information can be used in a way that does not compromise portability.

For example, the following code gets a list of all the fonts available on the native platform and prints them out:

String fontList[] = Toolkit.getDefaultToolkit().getFontList();
    for (int i = 0; i < fontList.length; ++i)
       System.out.println(fontList[i]);

If the fonts used in an applet are derived from this list, versus using hard-coded font names, the portability of an applet will be improved. The getFontMetrics() method returns the screen metrics of a font.

Another useful Toolkit method is getScreenSize(). This returns the full-screen dimensions of the native platform. If you want to create a window that fills up the screen, these dimensions will be useful. The getScreenResolution() method returns the dots-per-inch resolution of the screen.

The Toolkit class also provides a variety of methods for using images, including a method for synchronizing the screen's graphics state. This sync() method can be used in animation to avoid flicker.

I/O and Streams

Streams are a traditional way of representing a connection between two objects. The types of streams that programmers are most familiar with are ones between I/O devices (such as files) and processes or streams between two memory structures. Each endpoint of a stream connection can have one or two channels: an input channel and an output channel. When a process is reading from a file, it reads from a stream that receives data from the file device's output channel. From the point of view of the process, it is managing an input stream. From the viewpoint of the file device, it is sending an output stream. Figure 4.5 illustrates the relationship.

Figure 4.5 : Stream relationships when a process reads from a file.

The JDK package for managing input and output interactions, called java.io, has the stream concept at its foundation. It is structured on the notion of input and output streams just discussed. These stream classes permeate the Java programming environment. Their use ranges from file I/O to network programming to the widely used System.out.println() method. There are few things that will increase the capabilities of a Java programmer more than a full understanding of Java streams.

Structure of the java.io Package

As just stated, the java.io package is based heavily on the notion of input and output streams. This is made clear by the diagram of significant java.io classes, shown in Figure 4.6. The most important of these are, not surprisingly, the InputStream and OutputStream classes, which are generally used to read and write from a stream, respectively. They form the basis of an elaborate network of stream classes that make it easy to build high-level streams, such as those that work on newline delimited ASCII data, to low-level streams, such as one that manages data on a byte-by-byte level. How this works will be explored in the upcoming discussion of the two foundation classes and their subclasses.

Figure 4.6 : Significant classes of the java to hierarchy.

Before engaging in a discussion of the input and output stream classes, it's useful to look at some of the other aspects of Java I/O programming. The File class can be used to get information about a file or directory on the host file system. For example, the following code checks to see whether a file exists in the current directory and lists what is in the grandparent directory.

myFile = new File("File.txt");
      System.out.println("The file " + myFile.getName()
            + " existence is " + myFile.exists());
      File myDirectory = new File("..\\..");
      String s[] = myDirectory.list();
      System.out.println("Directory contents: ");
      for (int i = 0; i < s.length; ++i)
         System.out.println(s[i]);

As you might have observed in the constructor for the directory object, the naming conventions are based on the host file system. Consequently, hard coding such conventions can result in nonportable code. Fortunately, the class provides some constants that can hide the platform-specific conventions. For example, the following code makes the directory constructor portable:

File myDirectory = new File(".." + File.separator + "..");

The File class also offers a variety of other file operations. This includes the ability to delete or rename a file, create a new directory, and inspect a file's attributes. Note, however, that these operations are subject to the native environment's security constraints, discussed in the following section.

I/O and Security

The Java language and its native environment (such as a browser) give you a sophisticated system of security layers. At the top of this security hierarchy is protection for the local file system. After all, the most prevalent methods for a virus to inflict pain on its victim is to destroy his or her file system. Consequently, protection of the file system where the applet is running is paramount.

A Java environment enforces a security policy through its security manager. This is implemented as an object through the SecurityManager class and is created once and only once by a browser at startup. Once established, a SecurityManager cannot be changed or replaced. The reasons for this are obvious. If a rogue applet can modify the SecurityManager object, then it can simply remove all access restrictions and wield unlimited power over your hard disk. However, it is possible to get a reference to the SecurityManager through the System object's getSecurityManager() method. Once obtained, methods can be applied on the object to see what security constraints are currently being applied.

The strictness of a security policy depends on the runtime Java environment. Netscape Navigator, for example, cannot perform any file operations. The simple File class example in the previous section causes a security violation. This occurs when the File exists() method is invoked, which is the first file operation in the code (the File constructor simply associates the String name with the object).

Sun's HotJava browser allows some file operations on the client running an applet. Note that where the applet is loaded from is important. The client is the site running the applet; the server is where the applet was loaded from. The security that concerns Java the most is that of the client site. In Hot Java, the operations allowed on the client are based on the concept of access control lists. By default, any file not covered in the list cannot be accessed in any fashion, including the simple File exists() operation. The access control list can be found in the properties file of the .hotjava directory located under the parent.

File security is even weaker for the appletviewer program that programmers can use to test an applet. In this environment, most file operations are allowed for applets loaded from the client. Some file operations performed by applets loaded from a server over the network are also permitted. Standalone Java applications have no file security. If a sophisticated Java application is being developed, such as over an internal corporate network, it probably would be best to create a subclass of SecurityManager to set up a security policy appropriate for that environment.

When a file operation violates a security policy, a SecurityException object is thrown. This can be caught by the program to prevent abnormal termination.

I/O Exceptions

Besides thrown SecurityException objects, Java I/O programs need to be concerned with handling other exceptions. In particular, they need to catch IOException objects that are thrown. The IOException class embraces the category of errors related to input/output operations. Many of the methods in the java.io package throw IOException objects that must be caught. The typical structure of code that performs I/O operations is, therefore, as follows:

try {
 // IO operations
}
catch (IOException e) {
  // Handle the error
}

Two subclasses of IOException are noteworthy. A FileNotFoundException object is thrown when there is an attempt to open a file that doesn't exist. For example, the constructor for the FileInputStream class tries to open the file specified in its parameter. If it cannot do so, an object of class FileNotFoundException is thrown.

Another notable subclass of IOException is EOFException, which typically occurs in read operations when an end of file has been reached. This indicates that there are no more bytes to be read.

InputStream Classes

Figure 4.7 illustrates the class hierarchy that descends from the InputStream class. InputStream is an abstract class that defines the basic operations that must be implemented by its subclasses. The most noteworthy of these is the read() method. This is used to read in bytes one at a time or into a byte array. The System.in variable, which represents that standard input stream, is based on the InputStream class and is now used to illustrate the read() method:

Figure 4.7 : Input stream classes

try {
         int b;
         b = System.in.read();
}
catch (IOException e) {
  // Handle any exception…
}

This example reads in a byte that is, confusingly, converted to an integer by the read() method. The method returns -1 if there is no input to be read. For many subclasses of InputStream, an EOFException object is thrown in these circumstances.

Table 4.2 lists other notable methods of the abstract InputStream class.

Table 4.2. Notable InputStream methods.

MethodDescription
available A non-blocking way to find out number of bytes available to read.
Close Closes the input stream.
mark Marks the current stream position.
read Reads a byte or array of bytes.
reset Resets stream to last marked position.
skip Skips over specified number of input bytes.

FilterInputStream classes

The FilterInputStream class is at the top of a branch of the InputStream hierarchy that can be used to implement some very powerful operations. In particular, this class is used to chain a series of FilterInputStream classes so that one stream can process data before passing it "up" to the next stream. This chaining technique is excellent to use when encapsulating classes that interact with streams at a lower level (such as bytes) into streams that read in data at a higher level, such as Strings. The following example illustrates how this works.

As seen in the previous overview of the InputStream class, the System.in object reads data in a byte at a time. This will prove to be cumbersome for many standard input operations that typically want to work on String objects. Fortunately, FilterInputStream classes make it easy to abstract this byte-level functionality into a higher and more usable interface. The code in Listing 4.6 takes the System.in object and creates a DataInputStream object that can be used to read in a String of text delimited by a newline or EOF. This is then sent to the System.out object, which prints the string to the standard output stream.


Listing 4.6. Chaining Input Stream classes.
try {
         // Create the data input stream...
         DataInputStream dis =
            new DataInputStream(
               new BufferedInputStream(System.in));
         // Read in a line of text...
         String s = dis.readLine();
         // Send it to standard output...
         System.out.println(s);
      }
      // Handle any exceptions caused by the process...
      catch (IOException e) {
         // Just print out the error...
         System.out.println(e.getMessage());
      }

The first line inside the try clause is full of material; the innermost part of the clause introduces the BufferedInputStream class:

new BufferedInputStream(System.in)

This subclass of FilterInputStream simply allows the reading in of data more efficiently. Reading a file so that every byte request requires a new input read is very slow. The BufferedInputStream class reads data into a buffer in a series of large chunks. This prevents each request for a byte of data resulting in a read from the underlying input stream, such as a file or network read. If the BufferedInputStream already has the data in the buffer, it can return the data directly from its memory cache. This is also useful for operations that require movement back and forth in a stream, such as a "push back" of an unneeded byte. Since this class will improve the efficiency of input stream operations, it should be used liberally.

Once the BufferedInputStream object is created, it is used as the constructor for a DataInputStream object:

DataInputStream dis =
             new DataInputStream(
               new BufferedInputStream(System.in));

This code means that the DataInputStream object's request for input is actually made to a BufferedInputStream object. This object in turn makes its requests for input to the System.in object (which is based on InputStream) when it needs more data.

The code can now use DataInputStream methods, such as readLine(), to hide the underlying request of data at a byte-by-byte level. The DataInputStream methods return the data formatted in a data type that the user needs. In this case, readLine() reads in a String of text delimited by a new Line (a carriage return/line feed, or EOF). The delimiter is not included in the stream.

The DataInputStream class supports over a dozen data type operations, as listed in Table 4.3. These include most of the basic data types supported from Java, ranging from a single byte to an integer to a floating point number to a full-text String in Unicode format. The DataInputStream class is an implementation of the DataInput stream interface, which defines the data type methods that need to be supported.

Table 4.3. Data-type reads supported by DataInputStream.

MethodDescription
read Reads data into byte array.
readBoolean Reads a boolean.
readByte Reads an 8-bit byte.
readChar Reads a 16-bit char.
readDouble Reads a 64-bit double number.
readFloat Reads a 32-bit floating point number.
readInt Reads a 32-bit integer.
readLine Reads String terminated by newline or EOF.
readLong Reads a 64-bit long.
readShort Reads a 16-bit short.
readUTF Reads a Unicode formatted String.
readUnsignedByte Reads an unsigned byte.
readUnsignedShort Reads an unsigned 16-bit short.

From the powerful chaining operation supported by FilterInputStream, it's easy to imagine interesting new possible classes. For example, a WordProcessStream could be written to read paragraphs and images into a word processing application.

Two other FilterInputStream classes are worth mentioning. The LineNumberInputStream class associates the data streams with line numbers. This means that the class supports such operations as setLineNumber() and getLineNumber(). The PushBackInputStream class is used for the kind of operations that parsers typically perform. Parsers need methods such as unread(), which pushes a character back into a stream. This kind of operation is needed when a token has been identified by the parser.

Other InputStream classes

Although the FilterInputStream classes are the most powerful part of the InputStream hierarchy, other classes are also useful. If applets read from a file, an instance of the FileInputStream class will probably be used. It includes the same operations as its superclass, InputStream. Therefore, it can be used to pass data to FilterInputStream classes, as in the previous section's example. Listing 4.7 shows how to use chaining to read an ASCII file and send its contents to standard output. Instead of creating the FilterInputStream classes from the System.in InputStream object, it is built from a FileInputStream object tied to the filename provided in the method parameter; if the file cannot be opened because it does not, an IOException object will be thrown.


Listing 4.7. Reading a file to standard output.
void readFileToStandardOut(String filename) {
      System.out.println("***** BEGIN " + filename + " *****");
      try {
         // Open the file...
         DataInputStream dis = new DataInputStream(
          new BufferedInputStream(
            new FileInputStream(filename)) );
         // Read lines until EOF is reached...
         String s;
         while((s = dis.readLine()) != null)
            System.out.println(s);
         // Close the file when done...
         dis.close();
      }
      // Handle any exceptions caused by the process...
      catch (IOException e) {
         // Just print out the error...
         System.out.println(e.getMessage());
      }
      System.out.println("***** END " + filename + " *****");
    }

As this example further illustrates, the powerful technique of stream chaining makes it easy to abstract away from the low-level implementation of reading from a file. In fact, except for the innermost part of the constructor, with this code it really doesn't matter if you're working on a file, the standard input stream, or even from a network!

However, if it's necessary to work with streams at the level of bytes, the ByteArrayInputStream class could be used. This takes a large array of bytes and uses it as the basis for input stream requests. Therefore, this is the constructor for the class:

ByteArrayInputStream bis = new ByteArrayInputStream(byteArray)

The reset() method sets the current position back to the beginning of the stream buffer. The StringBufferInputStream class functions in a manner similar to ByteArrayInputStream, except it works on a StringBuffer. Like ByteArrayInputStream, however, the read() methods return a single byte or an array of bytes.

The PipedInputStream class works with the PipedOutputStream class to send a stream of data from one channel to another. This is well suited for thread processing, in which one thread is producing data (the output) to another thread that's consuming it (the input). Piped streams is a classic technique of interprocess communication. Chapter 8, "Adding Threads to Applets," will provide a concrete example of piped streams.

Finally, the SequenceInputStream class can be used to take a series of InputStreams and treat them as if they were one stream. This could be useful for operations such as concatenating files.

OutputStream Classes

The OutputStream class is an abstract class that marks the top of the hierarchy of classes used for streamed output. Its major operations are represented by write() methods, which can be used to write an integer, a byte, or an array of bytes to a stream. The other methods of OutputStream are the close() method, which is used to close a stream, and the flush() method, which writes the contents of a buffered cache to the stream-this applies only to derivatives of OutputStream that implement buffering.

Figure 4.8 lists the output classes that descend from OutputStream. In many cases, the classes mirror the input classes shown in Figure 4.7. The most noteworthy of these, not surprisingly, is the FilterOutputStream class, which, like its FilterInputStream counterpart, is used to chain streams together. This class will be used as the starting point for a tour through the OutputStream classes.

Figure 4.8 : Output Stream classes

FilterOutputStream Classes and FileOutputStream

The FilterOutputStream class is used to chain streams together so that one stream can process data before passing it to the next stream in the chain. This is useful for cases in which the lowest level stream reads in data at a primitive level (such as bytes) and passes up to the interface layer, which manages streams in terms of Strings and other data types. This is well suited for operations such as writing to a file, as the following example illustrates.

Listing 4.8 shows a method that takes a string array and writes it to a file, which is formatted for ASCII text. As in the InputStream example of Listing 4.7, the key part of this method is the constructor. Its innermost statement consists of a constructor for a stream that writes to a file:

new FileOutputStream(filename)

Listing 4.8. Writing a string array to an ASCII file.
void writeStringsToFile(String s[],String filename) {
      try {
         // Create the output stream to a file...
         DataOutputStream out = new DataOutputStream(
          new BufferedOutputStream(
            new FileOutputStream(filename)) );
         // Write the strings out...
         for (int i = 0; i < s.length; ++i)
            out.writeBytes(s[i]);
         // Flush and close...
         out.flush();
         out.close();
      }
      // Handle any exceptions caused by the process...
      catch (IOException e) {
         // Just print out the error...
         System.out.println(e.getMessage());
      }
    }

The FileOutputStream opens a file with the designated filename. If the file previously exists, its contents will be lost. The FileOutputStream class has an alternate constructor that takes a File object. The File class was described in the section "Structure of the java.io Package." The created file can then be written to with the write() methods, which can take a single integer or a byte array.

In the example, the FileOutputStream is fed as input into the BufferedOutputStream. The use of this class is very useful for file write operations since it prevents disk writes from occurring every time the buffer is given some data. It only writes when the buffer's cache is full or when the flush() method is called.

Finally, the newly created BufferedOutputStream object becomes the source for the DataOutputStream object. Like the DataInputStream counterpart, the DataOutputStream works with general Java data types, such as bytes, integers, doubles, and Strings. Table 4.4 summarizes the write operations supported by DataOutputStream. These are fairly straightforward, except for the three methods used for writing out Strings. Recall that Java treats characters not as 8-bit ASCII bytes, but as 16-bit Unicode characters. This is important for creating international software. However, programmers often need to read and write data in its native form. The writeBytes() method takes a String and writes out each character in the 8-bit ASCII format. This is the method used in the example. The writeChars() method, on the other hand, writes data out in 16-bit Unicode characters. A file created this way will not look or behave like an ASCII file, so this method wasn't used in the example. The writeUTF() method writes a String out in a special Unicode format.

Table 4.4. Data-type writes supported by DataOutputStream.

MethodDescription
write(byte) Writes a byte.
Write(byte [],int, int) Writes a subarray of bytes.
WriteBoolean(boolean) Writes a boolean.
WriteByte(int) Writes an 8-bit byte.
WriteBytes(String) Writes a String out as 8-bit bytes. Use for writing out ASCII text.
WriteChar(int) Writes a 16-bit char.
WriteChars(String) Writes a String out as 16-bit Unicode characters.
WriteDouble(double) Writes a 64-bit double number.
WriteFloat(float) Writes a 32-bit floating point number.
WriteInt(int) Writes a 32-bit integer.
WriteLong(long) Writes a 64-bit long.
WriteShort(int) Writes a 16-bit short.
WriteUTF(String) Reads a Unicode formatted String.

The DataOutputStream class implements the DataOutput class. This defines the data-type write methods that need to be supported by the classes that implement data output streams.

At the end of the example, there are calls to flush() and close(). The flush() method forces the BufferedOutputStream object to send its cached contents to the FileOutputStream. This could also be called inside the write loop at appropriate intervals (such as when a prespecified number of bytes or Strings have been written). The close() method call closes in the FileOutputStream stream, and the file is now complete.

The other FilterOutputStream class is PrintStream. This is the class used to implement the standard output and error objects, System.out and System.err. Of course, its most noteworthy method is println(). This is actually an overloaded method that takes any of a variety of data types and writes it to the PrintStream (usually standard output) followed by a new line. The println() method is most widely used with String objects. This method is also noteworthy in that the stream is flushed every time it is called. The print() method functions similarly but with no newline appended. A println() or flush() method will cause output from print() to be written to the stream. Finally, a byte-based write() is also supported by PrintStream.

Other Output Classes

The PipedOutputStream class works with the PipedInputStream class to send a stream of data from one channel to another. Pipes are a classic technique of interprocess communication. You will find a concrete example of piped streams in Chapter 8.

The ByteArrayOutputStream is used for working with arrays of bytes. Its most interesting feature is that its internal array will grow as it receives data.

Other I/O Classes

Two other I/O classes need to be mentioned. The RandomAccessFile class implements both the DataInput and DataOutput interfaces. Consequently, it is the only stream class that supports both the read and write operations. This is good for more traditional programming in which files are read and updated interactively. The RandomAccessFile also supports file-positioning operations, such as seek().

The StreamTokenizer stream can be used for tokenizing a stream. This is useful for the lexical analysis operations of a parser.

Tutorial

The spreadsheet applet from the previous chapter is enhanced in this tutorial to support dialog boxes and to introduce file storage to the spreadsheet data. Some new classes were written to create the dialog boxes, and classes from the previous section were modified to support new functioning.

For ease of reading, Figure 4.9 shows the spreadsheet applet again as it appears in a browser.

Figure 4.9 : The spreadsheet applet.

Class Organization

Table 4.5 enumerates the classes used in this chapter's version of the spreadsheet applet. Since many of these classes were created in the previous section, the new classes are delimited by having their name in bold. The classes that were modified have their names italicized.

Table 4.5. Spreadsheet classes.

ClassDescription
CellContains a String and evaluated value corresponding to a single cell.
CellContainerContains a matrix of Cells. The String in each Cell is evaluated according to whether it is a formula, a literal numeric value, or an empty cell. Can write or read its contents from a file.
FormulaParserUsed to parse out the individual string fragments that make up a single formula. Converts literal strings to their numeric values.
FormulaParserExceptionAn Exception that is thrown if a formula is poorly constructed.
ArgValueA FormulaParser helper class used to store information about an argument in a formula.
ChooseColorDialogDialog for choosing the color of a spreadsheet element.
ColorDisplayA canvas class that ChooseColorDialog uses to display how a spreadsheet cell would appear if certain colors were picked.
ColoredCheckboxDraws a checkbox with its color set to a different color.
ColoredCheckboxGroupCreates a group of colored checkboxes.
ChooseFontDialogDialog for choosing a font for the spreadsheet.
FontDisplayA canvas class that ChooseFontDialog uses to display how a font looks.
SpreadsheetCellProvides for the visual presentation of a single Cell.
SpreadsheetContainerManages the visual presentation of a matrix of SpreadsheetCells. Provides an interface for changing the value of a cell. Supports the display of a currently highlighted cell. Sets fonts and colors of SpreadsheetCells.
SpreadsheetFrameProvides the main presentation of the spreadsheet by displaying the SpreadsheetContainer, managing mouse selections on that spreadsheet, reading a text field for changing cell values, and handling a menu for actions such as invoking dialog boxes.
SpreadsheetAppletResponsible for creating, showing and hiding the SpreadsheetFrame that provides the visual display of this applet.

Adding the Color Dialog

The color dialog box lets you associate a color with various elements of a spreadsheet cell. You can set the foreground and background color of a cell in its normal state and also change the colors of the cell when it is highlighted. Figure 4.10 shows how the dialog appears in a browser.

Figure 4.10 : The color dialog box.

How the Dialog Box Is Used

When the dialog box appears, the current settings of the spreadsheet cell colors are displayed. A list control in the top left shows which elements of the spreadsheet cell can be modified. These colors are normal foreground, normal background, highlighted foreground, and highlighted background. When you select a list item, the radio button for the corresponding color is highlighted. A text display underneath the list shows what the spreadsheet cell would look like with the given colors. The text display shows either the normal state (with both foreground and background colors) or the background state. If you select a new color with the radio button, the canvas object is updated to show what the new foreground and background combination would look like. The spreadsheet is updated with the color settings for the current list item when you select the Update button.

The Construction of the Color Dialog Box

Four classes are used to construct the color dialog box. The ChooseColorDialog class is a subclass of Dialog and controls the main display and control of the dialog box. The colorDisplay class is a Canvas class derivative that draws text with colors corresponding to the selected foreground and background display. The ColoredCheckbox class draws a checkbox associated with a certain Color object; the background of the box is drawn according to that color. The ColoredCheckboxGroup class groups ColoredCheckbox items together so that they can function as part of a radio button group.

The discussion of the color dialog box begins with its underlying components. Listing 4.9 shows the code for the ColoredCheckbox class. Its most interesting feature is that it associates itself with a given Color object. It paints its background according to the Color and, through the setIfColorMatches() method, turns its checkbox on if the Color sent to it matches its internal color. The checkbox in this case is a radio button because the class is associated with a CheckboxGroup. Checkbox objects have radio buttons only if they are associated with a CheckboxGroup object; only one radio button in a checkbox group can be on at one time. If no checkbox group is specified for a checkbox object, then there are no restrictions on which boxes can be selected.


Listing 4.9. The ColoredCheckbox class.
// Class for creating a checkbox associated
// with a given color...
class ColoredCheckbox extends Checkbox {
   Color color;  // The color of this checkbox...
   // Constructor creates checkbox with specified color...
   public ColoredCheckbox(Color color, String label,
         CheckboxGroup grp, boolean set) {
         // Call the default constructor...
         super(label,grp,set);
         this.color = color;
         setBackground(color);
   }
   // Sets itself to true if it matches the color
   public void setIfColorMatches(Color match) {
      if (color == match)
         setState(true);
      else
         setState(false);
   }
   // Return the color matching this box...
   public Color getColor() {
      return color;
   }
}

The ColoredCheckboxGroup is used to contain ColoredCheckbox objects. Its constructor creates a preselected number of colored checkboxes to be associated with a Panel object. Here are the first few lines of the ColoredCheckboxGroup class declaration:

class ColoredCheckboxGroup extends CheckboxGroup {
   // Array to hold checkboxes...
   ColoredCheckbox c[] = new ColoredCheckbox[12];
   // Constructor. Create the checkboxes with
   // no default color chosen...
   public ColoredCheckboxGroup(Panel p) {
      // Call the default constructor...
      super();
      // Create the checkboxes and store in panel and reference array...
      c[0] = new ColoredCheckbox(Color.black,"Black",this,false);
      p.add(c[0]);
      c[1] = new ColoredCheckbox(Color.cyan,"Cyan",this,false);
      p.add(c[1]);

Strangely enough, ColoredCheckboxGroup is not a Container object. Consequently, the checkboxes need to be associated with a Panel to meet the needs at hand. Note the use of the Color constants in constructing the ColoredCheckbox objects. The reference array (variable c) is used in the other method of the class, setMatchingColor(), which is used to set the radio button of the ColoredCheckbox that matches a certain color:

public void setMatchingColor(Color match) {
      for (int i = 0; i < c.length; ++i)
         c[i].setIfColorMatches(match);
   }

Since ColoredCheckbox objects are self-identifying by color, this technique prevents a long and cumbersome walk through hard-coded color names to see which radio button should be turned on.

The colorDisplay class is a Canvas derivative that draws text (specified in the displayText String variable) with a specified foreground and background color. Listing 4.10 highlights some of the more interesting features of the class. The paint() method draws the canvas if a background and foreground color have been selected. It first starts getting the size of its drawing area through the size() method; this method is a standard part of subclasses of Component. It fills in the background color through the setColor() and fillRect() methods of the Graphics class. It then sets the color of the text to be displayed (the foreground). By getting the current FontMetrics, the canvas can figure out a good location for the text string; the getHeight() method returns the total height of the font. The drawString() method then draws the text at the specified location.


Listing 4.10. Portions of the colorDisplay class.
// The layout will call this to get the minimum size
   // of the object.  In this case, you want it to be at
   // least big enough to fit the display test...
   public Dimension minimumSize() {
      // Get the metrics of the current font...
      FontMetrics fm = getFontMetrics(getFont());
      return new Dimension(fm.stringWidth(displayText),
         2 * fm.getHeight());
   }

   // Paint the colors and text...
   public synchronized void paint(Graphics g) {
      if ((foreground == null) || (background == null))
         return;
      // Set background...
      Dimension dm = size();
      g.setColor(background);
      g.fillRect(0,0,dm.width,dm.height);
      // Draw the string
      g.setColor(foreground);
      // Set dimensions. Move just from left...
      FontMetrics fm = getFontMetrics(getFont());
      int x = fm.charWidth('W');
      // And center in height...
      int y = fm.getHeight();
      g.drawString(displayText,x,y);
   }

The minimumSize() method is used with layouts, which were discussed in the last chapter. When AWT is constructing the display of a group of components, it works with the layouts to decide what position and size a component should have. Sometimes, you might want a component to exercise some input into what its size will be. Two methods of the Component class can be invoked to do this. The preferredSize() method returns the Dimensions of the preferred size of the component. The minimumSize() method returns the smallest size in which the component should be made. In the case of the colorDisplay() class, it returns that the component should be wide enough to display the text String and twice as high as its current font. It does this by getting the FontMetrics of the current font and calling the stringWidth() and getHeight() methods respectively.

Finally, the dialog box is ready to be constructed. Figure 4.11 highlights the declarations and methods used to construct the color dialog box. The createComponents() method adds the components to the dialog box by using the complex GridBagLayout() class. The main thing that needs to be done here is to have most of the space taken up by the list control set to half the dialog box and the color checkboxes on the other half, as shown in Figure 4.10. The key reason for doing this is to set the GridBagConstraints weighty and gridheight variables to the appropriate values. By setting the former to 1.0, this tells the layout that the associated components should be given preeminence in terms of the layout's height. When weighty is set to 0.0, then the height of the corresponding components is given lower priority.

Figure 4.11 : The font dialog box.

The preferredSize() method in Listing 4.11 returns the desired dimensions of the dialog box. It needs to be three times as wide as the longest string in the list component, and 24 times as high as the current font. This way everything should fit comfortably in the dialog box.


Listing 4.11. The construction of ChooseColorDialog.
// Dialog box for choosing display colors...
public class ChooseColorDialog extends Dialog {
   SpreadsheetFrame fr;   // What to update...
   ColoredCheckboxGroup colorGrp;  // To hold radio buttons of colors...
   SpreadsheetContainer s;
   List choiceList;  // List of color choices...
   colorDisplay d; // This is the text display...
   // Defines for listbox values...
   static int NORMAL_FORE = 0;
   static int NORMAL_BACK = 1;
   static int HILITE_FORE = 2;
   static int HILITE_BACK = 3;
   // Construct dialog to allow color to be chosen...
   public ChooseColorDialog(Frame parent,boolean modal) {
      // Create dialog with title
      super(parent,"Color Dialog",modal);
      fr = (SpreadsheetFrame)parent;
      // Create the dialog components...
      createComponents();
      pack();  // Compact...
      // Resize to fit everything...
      resize(preferredSize());
   }

// The layout will call this to get the preferred size
   // of the dialog.  Make it big enough for the listbox text
   // the checkboxes, canvas, and buttons...
   public Dimension preferredSize() {
      // Get the metrics of the current font...
      FontMetrics fm = getFontMetrics(getFont());
      int width = 3 * fm.stringWidth("Highlighted foreground");
      int height = 24 * fm.getHeight();
      return new Dimension(width,height);
   }

   // Create the main display panel...
   void createComponents() {
    // Use gridbag constraints...
    GridBagLayout g = new GridBagLayout();
    setLayout(g);
    GridBagConstraints gbc = new GridBagConstraints();
    // Set the constraints for the top objects...
    gbc.fill = GridBagConstraints.BOTH;
    gbc.weightx = 1.0;
    gbc.weighty = 1.0;
    gbc.gridheight = 10;

    // Add the listbox of choices...
    choiceList = new List();
    choiceList.addItem("Normal foreground");
    choiceList.addItem("Normal background");
    choiceList.addItem("Highlighted foreground");
    choiceList.addItem("Highlighted background");
    g.setConstraints(choiceList,gbc);
    add(choiceList);

    // Create the checkbox panel
    Panel checkboxPanel = new Panel();
    checkboxPanel.setLayout(new GridLayout(12,1));
    // Create the checkbox group and add radio buttons...
    colorGrp = new ColoredCheckboxGroup(checkboxPanel);
    colorGrp.setMatchingColor(Color.magenta);

    // Create checkbox panel to right...
gbc.gridwidth = GridBagConstraints.REMAINDER;
    g.setConstraints(checkboxPanel,gbc);
    add(checkboxPanel);

    // Display the color chosen...
    d = new colorDisplay("This is how the text looks.");

    // Add to grid bag...
    gbc.weighty = 0.0;
    gbc.weightx = 1.0;
    gbc.gridwidth = GridBagConstraints.REMAINDER;
    gbc.gridheight = 1;
    g.setConstraints(d,gbc);
    add(d);

    // Two buttons: "Update" and "Cancel"
    Panel p = new Panel();
    p.add(new Button("Update"));
    p.add(new Button("Cancel"));

    // Add to grid bag...
    gbc.gridwidth = GridBagConstraints.REMAINDER;
    g.setConstraints(p,gbc);
    add(p);
   }

Using the Dialog Box

Once the dialog box is displayed, its event loop is entered. Listing 4.12 details the handleEvent() method of the dialog box. This code has several subtleties worth noting. Most of the work is performed when an action occurs, as indicated by the ACTION_EVENT method. When a button is selected, the argument of the Event object is set to the name of the button. The handleEvent() method looks at this name to decide what to do. If the name is "Cancel," then the dialog box is removed from the screen with the dispose() method, and control returns to the calling frame. The Update button sets the spreadsheet colors according to what is currently highlighted. How this works will be discussed shortly.


Listing 4.12. The handleEvent() method of ChooseColorDialog.
// Wait for Cancel or OK buttons to be chosen...
public boolean handleEvent(Event e) {
    switch(e.id) {
       case Event.ACTION_EVENT:
         // Kill the dialog...
         if (e.arg.equals("Cancel")) {
            dispose();  // Remove Dialog...
            return true;
         }  // end if
         // Update colors on the spreadsheet...
         if (e.arg.equals("Update")) {
            setSpreadsheetColors();
            return true;
         }  // end if
         if (e.target instanceof Checkbox) {
            selectedRadioItem();
            return false;
         }
         return false;
       // User selected a listbox item...
       case Event.LIST_SELECT:
         // Set up caption colors and color choice highlight...
         if (e.target instanceof List) {
            selectedChoiceListItem();
            return false;
         }  // end list if
         return false;
       default:
         return false;
   } // end switch
}

If the target of the ACTION_EVENT is a Checkbox, then it means a radio button is selected, which calls the selectedRadioItems() method. This method sets the colorDisplay object's foreground or background color according to the radio button chosen and the current selection in the list.

If you click on a list item, a LIST_SELECT event is issued. In this case, this invokes the selectedChoiceListItems() method. It sets the colorDisplay object's settings according to the current list selection; this process needs to take into account both the foreground and background colors. The ColoredCheckboxGroup's setMatchingColor() method is called to set the radio button of the ColoredCheckbox object corresponding to the current list color.

See this book's accompanying CD-ROM for the full source code of the selectedRadioItems() and selectedChoiceListItems() methods.

Calling the Dialog Box

The SpreadsheetFrame object is responsible for bringing up the color dialog box. It declares a variable of the color dialog class as follows:

ChooseColorDialog colorDialog;  // Color Dialog...

In its constructor, the frame instantiates the color dialog box with the following:

colorDialog = new ChooseColorDialog(this, true);

This states that the frame is the parent of the dialog box and its appearance is modal. Recall that a modal dialog box does not allow input to other windows while it is being displayed.

A dialog box does not automatically appear when it is constructed; you do this with the show() method. The SpreadsheetFrame object is modified in this chapter's tutorial to add menu options for the new dialog boxes. Here is some of the code from the frame's handleEvent() method that results in the ChooseColorDialog object being displayed:

public boolean handleEvent(Event evt) {
switch(evt.id) {
  case Event.ACTION_EVENT: {
   String menu_name = evt.arg.toString();
   if (evt.target instanceof MenuItem) {
    // Set colors...
    if(menu_name.equals("Colors..."))
      colorDialog.show();
    // … other menu choices…

The color dialog box overrides the show() method so it can do some setup before the dialog box appears:

public synchronized void show() {
      super.show(); // Call the default show method...
      // Get the spreadsheet container...
      s = fr.getSpreadsheetContainer();
      // Set the listbox default...
      choiceList.select(0);
      // Set up caption colors and color choice
highlight...
      selectedChoiceListItem();
}

After calling the superclass show method, the dialog box gets a reference to the frame's SpreadsheetContainer object. It will need this to get and set the spreadsheet's colors. It then sets the list box selection to the first item. The last method called, selectedChoiceListItem(), sets the radio buttons and display canvas to values corresponding to the current list selection.

Setting the Spreadsheet Colors

The SpreadsheetContainer class needs to be modified slightly to support dynamically selecting colors. Fortunately, these changes are small since the SpreadsheetCell class, which represents the individual cells in the spreadsheet, was written to support colors in its first version presented in Chapter 3, "Building a Spreadsheet Applet."

Four Color variables are introduced to the SpreadsheetContainer class so that colors can be set dynamically:

Color normalForeColor = Color.black;
Color normalBackColor = Color.white;
Color highlightedForeColor = Color.white;
Color highlightedBackColor = Color.red;

Associated with the variable declarations are the default colors. These SpreadsheetCell colors have the background and foreground colors set, respectively, in code such as the following:

matrix[index].setFillColor(normalBackColor);
matrix[index].setTextColor(normalForeColor);

This code is from the SpreadsheetContainer constructor. The two setFillColor() and setTextColor() methods are also called when a cell is given the current highlight focus. In this case, the cell is given the highlight colors. Some code is repeated here from the SpreadsheetCell object's paint() method to illustrate how these colors are applied to the cell:

// Set up drawing rectangle...
g.setColor(Color.blue);
g.drawRect(x,y,width,height);
g.setColor(fillColor);
g.fillRect(x + 1,y + 1,width - 2,height - 2);
// Draw the string
g.setColor(textColor);
g.drawString(textPaint,x + 4,y + (height - 2));

The fillColor and textColor variables are set by the setFillColor() and setTextColor() methods, respectively.

Four accessor methods are added to the SpreadsheetContainer class for getting the normal and highlight Color variables. These are called by the selectedChoiceListItem() method of the ChooseColorDialog class to retrieve the colors to be displayed in the colorDisplay object.

When you click the Update button, the color dialog box's setSpreadsheetColors() is invoked to update the spreadsheet colors. This in turn calls the SpreadsheetContainer's setNewColors() method, which applies the four normal and highlight colors to each of the SpreadsheetCell objects that are not row or column headers. The container is then repainted, displaying the cells in their new colors.

Font Dialog Box

The discussion of the spreadsheet applet's font dialog box will not be as lengthy as the preceeding overview of the color dialog. In many ways, it is very similar, so a detailed explanation doesn't need to be repeated; refer to this book's CD-ROM for any details not covered in this overview.

Figure 4.11 shows the font dialog box. It is based on the ChooseFontDialog class, which displays its components in a two-column style similar to the color dialog box. The current font family, style, and size is shown in a Canvas display object of the fontDisplay class. This class is very similar to the colorDisplay class.

The list component on the left side of the dialog box shows the fonts available on the current platform. It uses the AWT Toolkit class to get this information. Here is the code that creates the control and adds the font families:

// Add the listbox of choices...
   // Get the selection from the toolkit...
    choiceList = new List();
    String fontList[] =
Toolkit.getDefaultToolkit().getFontList();
    for (int i = 0; i < fontList.length; ++i)
       choiceList.addItem(fontList[i]);

A choice box is added to the dialog box to enumerate font sizes that can be used. Two checkboxes are used to set the bold and italicized styles. If none of these are set, the font's style is set to plain.

Every time one of these controls is changed, the font display is updated with a new font. This happens in the paintSample() method, whose text is found in Listing 4.13. The variable d in the code represents the fontDisplay object.


Listing 4.13. The paintSample() method for displaying a font with selected attributes.
// Set the display canvas to show itself with
   // the currently selected font
   private synchronized void paintSample() {
      // Get the family to display
String fontName = choiceList.getSelectedItem();
      // Get its point size
      String fontSize = choiceSize.getSelectedItem();
      // Set its style
      int fontStyle = Font.PLAIN;
      if (checkItalics.getState())
            fontStyle += Font.ITALIC;
      if (checkBold.getState())
            fontStyle += Font.BOLD;
      // Create a font with the proper attributes...
      currentFont = new Font(fontName,fontStyle,
            Integer.parseInt(fontSize));
      // Set the new font on the canvas...
      d.setFont(currentFont);
      // Repaint it so the new font is displayed..
      d.repaint();
   }

When you select OK, the setFont() method of the SpreadsheetContainer class is called; the font created in the previous paintSample() code is passed as the parameter. This setFont() method overrides the default setFont() method, as seen in Listing 4.14. The method goes through every font in the container and sets it to the new font. The container is then repainted to show the new font. Figure 4.12 shows what the spreadsheet looks like when it is set with a Helvetica Bold 16-point font.

Figure 4.12 : The spreadsheet applet set to Helvetica Bold 16-point font.


Listing 4.14. The SpreadsheetContainer setFont() method.
// Set the font for all the individual
  // spreadsheet cells...
   public synchronized void setFont(Font f) {
     super.setFont(f);
     int index;
     // Reload the font for each cell...
     for (int i = 0; i < numRows; ++i) {
      for (int j = 0; j < numColumns; ++j) {
       index = (i * (numColumns)) + j;
       // Set the colors...
       matrix[index].setFont(f);
      }  // end inner for...
     } // end outer for
     // Repaint to show new font...
     repaint();
   }

The FileDialog Class

The FileDialog class is a subclass of Dialog used to provide a platform-independent approach to letting the user select what files are to be loaded or saved. Instances of the FileDialog class will usually mirror the underlying GUI conventions. For example, a FontDialog object for loading a file on the Windows 95 environment is shown in Figure 4.13. As you can see from the example, the dialog box follows the Windows 95 Open dialog box conventions.

Figure 4.13 : The load file font Dialog.

The FileDialog can be constructed to be in a mode to load a file or save a file. These dialog boxes look similar but perform slightly differently. For example, the Save version of the dialog box will notify the user that a file exists if a filename is given for a preexisting file. The SpreadsheetFrame class constructs a dialog box for both the load and save cases:

FileDialog openFileDialog;  // File Open Dialog...
FileDialog saveFileDialog;  // File Save Dialog...
openFileDialog = new FileDialog(this,"Open File",
               FileDialog.LOAD);
saveFileDialog = new FileDialog(this,"Save File",
               FileDialog.SAVE);

The integer flag in the last parameter of the constructors indicates the mode in which the dialog box should function.

When the dialog box is displayed with the show() method, it acts in a modal fashion so that input to the frame is blocked while it is up. After a choice has been made, the chosen file and directory can be retrieved with the getFile() and getDirectory() methods. The former will return a null value if the user had canceled out of the dialog box. If the names are valid, the SpreadsheetFrame class either saves or opens the specified spreadsheet file.

It is interesting to note that the FileDialog objects constructed here do not appear when the applet is run in Netscape Navigator. Recall that Netscape does not allow any file-based methods to be invoked from an applet. Consequently, there is no reason to even display FileDialog objects.

Saving a Spreadsheet File

The process of saving a spreadsheet file is surprisingly simple. What is saved is not the contents of the SpreadsheetContainer object, but the CellContainer object. This is because the former has some useless information like row/column headers, but the real data is in the CellContainer.

Listing 4.15 shows the CellContainer method for saving a file. This code was easy to generate because of the FilterOutputStream classes discussed in the overview of streams. The DataOutputStream variable fs is used to hide the complexities of the underlying BufferedOutputStream and FileOutputStream objects. The former is used as a cache so that every stream write does not result in a disk write. In short, BufferedOutputStream is used to improve performance. The inner part of the constructor, FileOutputStream, actually opens the file for writing. If there is a problem opening or writing the file at any point in the process, an IOException will be thrown. The saveFile() method simply rethrows the exception, forcing the calling method to catch the problem.


Listing 4.15. The CellContainer method for saving a file.
// Save the cell container to a file...
   public void saveFile(String filename) throws IOException {
      // Perform these file operations.  If anything goes wrong
      // an exception will be thrown.  This must be caught
      // by the calling method...
      // Open the file...
      DataOutputStream fs = new DataOutputStream(
            new BufferedOutputStream(
               new FileOutputStream(filename)));
      // Write out magic number
      fs.writeInt(magicNumber);
      // Write out row and column sizes
      fs.writeInt(numRows);
      fs.writeInt(numColumns);

      // Now go through each row and column and save...
      // Write out literal value followed by delimited...
      int numCells = numRows * numColumns;
      for (int i = 0; i < numCells; ++i) {
         fs.writeBytes(matrix[i].getCellString());
         fs.writeByte(delimiter);
      } // end for

      // Close the file. You're done!
      fs.close();
   }

The first thing written to the file is a magic integer number. This is used to indicate that the file is a spreadsheet file. The method for opening a spreadsheet file (discussed in the next section) will check for the existence of the magic number at the beginning of the file. If it isn't there, an exception will be thrown.

The saveFile() method writes out the magic number and the number of rows and columns out as integers, using the DataOutputStream writeInt() method. The method then loops through each element in the CellContainer matrix, writing out each cell's String contents followed by a delimiter (in this case, a tab). Recall that the CellContainer needs only the Cell's String value; the evaluated value can be generated on the fly by calling the CellContainer reevaluate() method.

The Cell string is written out through the writeBytes() method. Why not use a writeString() method? This does not exist because Java is a Unicode-based system. This means that 8-bit ASCII character strings need to be treated differently than 16-bit Unicode values. The writeBytes() method is used here so that ASCII data can be written out.

Opening a Spreadsheet File

Once a spreadsheet's contents have been saved to a file, it can be reopened through the CellContainer's readFile() method. This is detailed in Listing 4.16. Although it's similar to the saveFile() method in the previous section, it differs in some important ways. A major difference is that the readFile() method generates the Cell matrix of the CellContainer because readFile() is called from a new CellContainer constructor, which takes a filename as its parameter. This constructor does little more than call readFile(), which becomes responsible for setting up the Cell matrix.


Listing 4.16. The CellContainer method for reading in a file.
// Read in a file and create a new matrix to
   // accommodate the data...
   public void readFile(String filename) throws IOException {
      // Set everything to empty...
      numRows = 0;
      numColumns = 0;
      int magic;
      byte b;
      DataInputStream fs;
      try {
         // Open the file...
         fs = new DataInputStream(new BufferedInputStream(
            new FileInputStream(filename)) );
      }
      // Can't open file, quit out...
      catch (IOException e) {
         throw e;
      }
      // Now try to load the data...
      try {
           // Check the magic number
           magic  = fs.readInt();
           if (magic != magicNumber)
               throw (new IOException("Invalid magic number"));
           // Get the rows and columns...
           numRows = fs.readInt();
           numColumns = fs.readInt();
           if ((numRows <= 0) || (numColumns <=0) ||
            (numRows > maxRows) || (numColumns > maxColumns) )
               throw (new IOException("Invalid row/col settings"));
           int numCells = numRows * numColumns;
           // Allocate matrix...
           matrix = new Cell[numCells];
           // Now load the individual cells...
           for (int i = 0; i < numCells; ++i) {
               // Load the string up until delimited...
               StringBuffer s = new StringBuffer();
               while ((b = fs.readByte()) != delimiter)
                     s.append((char)b);
               // Add it to new Cell...
               matrix[i] = new Cell();
               matrix[i].setCellString(s);
           } // end for
           // Success! Close the file and leave...
           fs.close();
      }
      // If exception, then close file
      // and reset everything...
      catch (IOException e) {
         numRows = 0;
         numColumns = 0;
         fs.close();
         throw e;
      }
   }

Of course, the other difference between saveFile() and readFile() is that the latter works with an InputStream. The DataInputStream object fs builds on BufferedInputStream and FileInputStream objects, the latter of which actually opens the file for reading. The code uses readInt() to check the first few bytes of the file to make sure the magic number is present. If it isn't, an exception is thrown. The readFile() method throws an IOException if the magic number fails, the file is not found, or there is a problem with the read. The constructor does not catch thrown IOException objects, so it's up to the calling object to catch IOException objects.

The individual Cell Strings are created through multiple calls to readByte(). This is used with the String append() method to add byte characters to a new String until the delimiter is found. When this happens, a new Cell is created with the String and is assigned to the CellContainer matrix.

The SpreadsheetFrame object calls the CellContainer constructor that loads a file when the load FileDialog object is used. It takes the newly created CellContainer and sets it to the SpreadsheetContainer with the newCellContainer() method discussed in the previous chapter. This method uses the loaded CellContainer object to generate new SpreadsheetCell objects. It then repaints itself, displaying the new spreadsheet contents.

Summary

This chapter's tutorial provides several examples of how to use Dialogs. In doing so, it also illustrates more principles for using the Graphics object to paint colors and fonts, working with layouts to create more complex displays, and learning the subtleties of event handling. It also gives you an overview of Java streams with examples of how to read and write to files. This latter capability will be restricted depending on the browser environment (or lack thereof) in which a Java applet is being executed.