Java 1.2 Unleashed

Contents


- 7 -

Working with the Canvas


In this chapter you'll learn the basics of using the Canvas and Graphics classes of the java.awt package. You'll also learn how to use the Font class to control the way text is displayed. Understanding these classes is essential to developing GUI-based applets and applications. More advanced use of canvas- and graphics-related classes is covered in Chapter 18, "Printing," and Chapter 20, "Working with 2D and 3D Graphics."

The Canvas and Graphics Classes

The Canvas class of java.awt provides a general GUI component for drawing images and text on the screen. It does not support any drawing methods of its own, but provides access to a Graphics object through its paint() method. The paint() method is invoked upon the creation and update of a canvas so that the Graphics object associated with a Canvas object can be updated. The paint() method should not be directly invoked, but it can be indirectly accessed using the repaint() method. The Canvas class is used to provide custom drawing and event handling. You can use the Graphics object associated with your applet's class by overriding its paint() method.


NOTE: The Canvas and Graphics objects can be used by Java applications as well as Java applets.

The Graphics class is where all of the low-level drawing methods are implemented. These methods can be used directly to draw objects and text, or can be combined to display more elaborate screen objects. The Graphics drawing methods allow a number of geometrical shapes to be drawn and filled, including lines, arcs, ovals, rectangles, rounded rectangles, and polygons. A special draw3DRect() method is provided for drawing rectangles that are shaded to give them a three-dimensional appearance. The Graphics class also provides the capability to draw bitmapped images and text on the canvas. The "Using Text and Fonts" section later in this chapter covers the drawing of text and introduces the Font and FontMetrics classes. These classes control the specific manner in which text is displayed.

Displaying Bitmapped Images

The drawImage() method of the Graphics class is used to display bitmapped images on the Graphics object associated with a canvas. It takes as its arguments an object of the Image class, an object that implements the ImageObserver interface, the x- and y-coordinates where the image is to be displayed, and other parameters.

The Image class is an abstract class that provides format-independent access to graphical images. Image objects are created by invoking methods of other classes that create images. Examples of these image-creating methods are the createImage() methods of the Component and Toolkit classes and the getImage() methods of the Toolkit and Applet classes. The getImage() methods are the most handy methods for retrieving an image that is stored in a disk file or at a URL. Java currently supports GIF- and JPEG-formatted images through these methods.

The ImageObserver interface is defined in the java.awt.image package. This interface provides a set of constants and methods that support the creation and loading of images. The Component class implements the ImageObserver interface, and in most cases, the ImageObserver object used as the parameter to the drawImage() method can be supplied using the this identifier to reference the current Canvas or Frame object being painted.

The DisplayImage Applet

The DisplayImage applet shows how bitmapped images can be drawn in an applet window using the drawImage() method of the Graphics class. Its source code is shown in Listing 7.1. Listing 7.2 provides an HTML file to be used to display the applet.

LISTING 7.1. THE SOURCE CODE FOR THE DisplayImage APPLET.

import java.awt.*;

import java.applet.*;

import java.net.*;

public class DisplayImage extends Applet {

 int screenWidth = 400;

 int screenHeight = 400;

 Image image;

 public void init() {

  setBackground(Color.white);

  image = getImage(getDocumentBase(),"test.gif");

  resize(screenWidth,screenHeight);

 }

 public void paint(Graphics g) {

  g.drawImage(image,0,0,this);

 }

}

LISTING 7.2. THE DisplayImage.htm FILE USED WITH THE DisplayImage APPLET.

<HTML>

<HEAD>

<TITLE>Displaying an Image</TITLE>

</HEAD>

<BODY>

<APPLET CODE="DisplayImage.class" HEIGHT=500 WIDTH=500>

</APPLET>

</BODY>

</HTML>

Before running the DisplayImage applet, copy the test.gif image from the \ju\ch07 directory of the CD-ROM to your ju\ch07 directory. The DisplayImage program uses the test.gif file.

DisplayImage shows how a bitmapped image can be displayed using the Graphics class. When you run the applet, it will display the bitmapped image shown in Figure 7.1.


NOTE: Make sure that your TCP/IP software is operating before displaying any of the applets in this chapter. The best way to do this is to connect to the Internet.

FIGURE 7.1. The DisplayImage applet.

The functionality of the DisplayImage applet isn't all that astounding. Its purpose is to illustrate the use of the methods involved in loading and displaying image files. You can easily upgrade the applet to display arbitrary GIF or JPEG files by passing the image file name as an applet parameter.

DisplayImage declares three field variables. The screenWidth and screenHeight variables control the size of the applet window. The image variable is used to refer to the loaded image.

The setBackground() method of the Component class is used to set the applet background to white. The getImage() method of the Applet class is used to load the image in the test.gif file and assign it to the image variable. The getDocumentBase() method is used to obtain the URL from which the applet's HTML file is loaded. This URL is then used in the loading of test.gif.

The paint() method draws the image referenced by the image variable on the default Graphics object of the applet window. It accomplishes this using the drawImage() method of the Graphics class. The arguments to drawImage() are the image to be displayed, the x- and y-coordinates where the image is to be drawn, and the object implementing the ImageObserver interface associated with the image. The this identifier is used to indicate that the applet window is the ImageObserver.

Drawing and Painting

Some programs, such as the Microsoft Windows Paint program, are used to construct images by painting on the screen. These paint programs create an image array of color pixels and update the array based on user paint commands. These commands may consist of pixel-level drawing operations or more general operations that draw geometrical objects such as circles, rectangles, and lines. Painting programs are characterized by the fact that the pixel array is the focus for the drawing that takes place.

Drawing programs, such as CorelDRAW, support drawing operations using a more object-oriented approach. When you draw a circle or line with a drawing program, you do not merely update the pixels of the canvas--you add an object to the list of objects that are displayed on the canvas. Because drawing programs operate at a higher object level, you can select, move, resize, group, and perform other operations on the objects that you've drawn.

The Graphics class is oriented toward providing the methods that are needed to support higher-level drawing programs rather than lower-level painting programs. However, it does support important painting operations, such as displaying bitmapped images, as you saw in the DisplayImage program.

When using the Graphics class to support graphical operations, you will generally maintain a list of the objects that you've drawn and use that list of objects to repaint the screen as required.

The Draw Applet

The Draw applet shows how the higher-level drawing operations of the Graphics class are used to display and maintain a list of the objects that are drawn on a canvas. The source code of the Draw applet is shown in Listing 7.3. Its corresponding HTML file is shown in Listing 7.4.

LISTING 7.3. THE SOURCE CODE FOR THE Draw APPLET.

import java.applet.*;

import java.awt.*;

import java.awt.event.*;

import java.lang.Math;

import java.util.Vector;

public class Draw extends Applet {

 Button lineButton = new Button("Line");

 Button ovalButton = new Button("Oval");

 Button rectButton = new Button("Rectangle");

 Button clearButton = new Button("Clear");

 MyCanvas canvas = new MyCanvas(TwoPointObject.LINE);

 int screenWidth = 400;

 int screenHeight = 400;

 public void init() {

  setBackground(Color.white);

  setLayout(new BorderLayout());

  add("Center",canvas);

  setupButtons();

  resize(screenWidth,screenHeight);

 }

 void setupButtons() {

  lineButton.addActionListener(new ButtonHandler());

  ovalButton.addActionListener(new ButtonHandler());

  rectButton.addActionListener(new ButtonHandler());

  clearButton.addActionListener(new ButtonHandler());

  Panel panel = new Panel();

  panel.add(lineButton);

  panel.add(ovalButton); 

  panel.add(rectButton);

  panel.add(clearButton);

  add("North",panel);

 }

 class ButtonHandler implements ActionListener {

  public void actionPerformed(ActionEvent ev){

   String s=ev.getActionCommand();

   if(s.equals("Clear")) canvas.clear();

   else if(s.equals("Line"))

    canvas.setTool(TwoPointObject.LINE);

   else if(s.equals("Oval"))

    canvas.setTool(TwoPointObject.OVAL);

   else if(s.equals("Rectangle"))

    canvas.setTool(TwoPointObject.RECTANGLE);

  }

 }

}

class MyCanvas extends Canvas {

 int tool = TwoPointObject.LINE;

 Vector objects = new Vector();

 TwoPointObject current;

 boolean newObject = false;

 public MyCanvas(int toolType) {

  super();

  tool = toolType;

  addMouseListener(new MouseHandler());

  addMouseMotionListener(new MouseMotionHandler());

 }

 public void setTool(int toolType) {

  tool = toolType;

 }

 public void clear() {

  objects.removeAllElements();

  repaint();

 }

 public void paint(Graphics g) {

  int numObjects = objects.size();

  for(int i=0;i<numObjects;++i) {

   TwoPointObject obj = (TwoPointObject) objects.elementAt(i);

   obj.draw(g);

  }

  if(newObject) current.draw(g);

 }

 class MouseHandler extends MouseAdapter {

  public void mousePressed(MouseEvent e){

   current = new TwoPointObject(tool,e.getX(),e.getY());

   newObject = true;

  }

  public void mouseReleased(MouseEvent e){

   if(newObject) {

    objects.addElement(current);

    newObject = false;

   }

  }

}

 class MouseMotionHandler extends MouseMotionAdapter {

  public void mouseDragged(MouseEvent e){

   int x = e.getX();

   int y = e.getY();

   if(newObject) {

    int oldX = current.endX;

    int oldY = current.endY;

    if(tool != TwoPointObject.LINE) {

     if(x > current.startX) current.endX = x;

     if(y > current.startY) current.endY = y;

     int width = Math.max(oldX,current.endX) - current.startX + 1;

     int height = Math.max(oldY,current.endY) - current.startY + 1;

     repaint(current.startX,current.startY,width,height);

    }else{

     current.endX = x;

     current.endY = y;

     int startX = Math.min(Math.min(current.startX,current.endX),oldX);

     int startY = Math.min(Math.min(current.startY,current.endY),oldY);

     int endX = Math.max(Math.max(current.startX,current.endX),oldX);

     int endY = Math.max(Math.max(current.startY,current.endY),oldY);

     repaint(startX,startY,endX-startX+1,endY-startY+1);

    }

   }

  }

 }

}

class TwoPointObject {

 public static int LINE = 0;

 public static int OVAL = 1;

 public static int RECTANGLE = 2;

 public int type, startX, startY, endX, endY;

 public TwoPointObject(int objectType,int x1,int y1,int x2,int y2) {

  type = objectType;

  startX = x1;

  startY = y1;

  endX = x2;

  endY = y2;

 }

 public TwoPointObject(int objectType,int x,int y) {

  this(objectType,x,y,x,y);

 }

 public TwoPointObject() {

  this(LINE,0,0,0,0);

 }

 public void draw(Graphics g) {

  if(type == LINE) g.drawLine(startX,startY,endX,endY);

  else{

   int w = Math.abs(endX - startX);

   int l = Math.abs(endY - startY);

   if(type == OVAL) g.drawOval(startX,startY,w,l);

   else g.drawRect(startX,startY,w,l);

  }

 }

}

LISTING 7.4. THE Draw.htm FILE.

<HTML>

<HEAD>

<TITLE>A Drawing Applet</TITLE>

</HEAD>

<BODY>

<APPLET CODE="Draw.class" HEIGHT=400 WIDTH=400>

</APPLET>

</BODY>

</HTML>

The Draw applet is quite a bit more sophisticated than the DisplayImage applet with respect to the capabilities that it provides. When you run Draw with your browser, you will see the opening window shown in Figure 7.2.

FIGURE 7.2. The Draw opening window

.The Draw applet is initially configured for you to draw lines in its window area. You can draw a line by clicking the left mouse button and dragging the mouse. When you have finished drawing the line, release the left mouse button and the drawn line will be completed. The coordinate where you press the left mouse button is the beginning of the line, and the coordinate where you release the left mouse button is the end of the line. Go ahead and draw several lines, as shown in Figure 7.3.

FIGURE 7.3. Drawing lines with the Draw applet.

The Draw applet supports the drawing of lines, ovals, and rectangles. Click the Oval button to change the drawing tool to draw ovals. You draw an oval in the same way that you draw a line. When you click the left button of your mouse, mark the upper-left corner of the oval. Drag the mouse to where you want the lower-right corner of the oval and release the left mouse button. Try drawing a few ovals, as shown in Figure 7.4.

Now click the Rectangle button to begin drawing rectangles. Draw rectangles in the same way that you draw ovals. Go ahead and draw a rectangle, as shown in Figure 7.5.

You can experiment with the applet before going on to find out how it works. If you want to clear the drawing screen, click the Clear button.

FIGURE 7.4. Drawing ovals with the Draw applet.

FIGURE 7.5. Drawing rectangles with the Draw applet.

The Draw applet is a little (but not much) longer than the applets you've developed so far in this book. It consists of three major classes and three event handling inner classes. The Draw class is the main class used to implement the applet. The MyCanvas class is used to implement the main canvas component of the applet. The TwoPointObject class is used to implement the line, oval, and rectangle objects that are drawn on the screen. It is called TwoPointObject because it supports objects that can be characterized by a starting point (mouse down) and an ending point (mouse up).

The Draw applet declares several variables. Four button variables are declared and initialized to implement the Clear, Line, Oval, and Rectangle buttons. The canvas variable is used to refer to the MyCanvas object that implements the applet drawing. This object is constructed by passing the TwoPointObject.LINE constant as an argument. This tells the constructed object that the line tool should be initially used to support drawing. The height and width of the Draw window is set to 400¥400 pixels.

The applet's init() method sets the background color to white, sets the applet's layout to a BorderLayout object, and adds the MyCanvas object to the center of the applet window. It then sets up the button event handlers and lays out the buttons via setupButtons().

The actionPerformed() method of the MenuItemHandler class handles the clicking of the buttons. The Clear button is handled by invoking the clear() method of the MyCanvas class to clear the canvas to a blank state. The Line, Oval, and Rectangle buttons are handled by invoking the setTool() method of the MyCanvas class to set the current drawing tool. It uses the LINE, OVAL, and RECTANGLE constants defined in the TwoPointObject class.

MyCanvas

The MyCanvas class extends the Canvas class to provide custom drawing capabilities. The tool variable is used to identify the current drawing tool that is in effect. The objects variable is declared as a Vector. It is used to store all of the objects drawn by the user. The current variable is used to refer to the current TwoPointObject object being drawn by the user. The newObject flag is used to track whether the user has begun drawing a new object.

The MyCanvas constructor invokes the constructor of the Canvas class using the superclass constructor call statement, and then sets the tool variable to the toolType argument passed to the constructor.

The setTool() method changes the tool used to draw an object.

The clear() method invokes the removeAllElements() method of the Vector class to remove all drawing objects stored in the Vector referenced by the objects variable.

The paint() method is used to paint and repaint the screen. It uses the size() method of the Vector class to determine how many objects are stored in the objects vector and sets the numObjects variable to this value. It then iterates through each object stored in objects and draws each one on the canvas. The elementAt() method of the Vector class is used to retrieve an object from the objects vector. The object is cast into an object of class TwoPointObject and assigned to the obj variable. The draw() method of the TwoPointObject class is invoked to draw the object on the current Graphics context.

Notice that the paint() method does not have to know how to support limited area repainting. Only full canvas painting needs to be implemented by paint(). Support of limited area repainting is provided by the local AWT implementation.

The MouseHandler and MouseMotionHandler inner classes handle the events associated with pressing, releasing, and dragging the mouse. They do this by extending the MouseAdapter and MouseMotionAdapter classes of java.awt.event. The MouseHandler class handles the pressing and releasing of the mouse button via the mousePressed() and mouseReleased() methods. The MouseMotionHandler class handles the dragging of the mouse via the mouseDragged() method.

The mousePressed() method handles the event that is generated when the user clicks the left mouse button in the canvas. The method is called by the Java runtime system with the position of the mouse click. A new TwoPointObject object is created, with the tool variable and the position of the mouse click as its arguments. The newly created object is assigned to the current variable, and the newObject flag is set to true.

The mouseReleased() method is used to handle the event that is generated when the user releases the left mouse button. This action marks the completion of the drawing of an object. The event is handled by adding the object referenced by the current variable to the objects vector. The newObject flag is then set to False. The object referenced by the current variable is updated with its ending position during the processing of the mouseDragged() event handling method. The newObject flag is checked to make sure that the mouse was not clicked outside of the current window and then released.

The mouseDragged() method performs somewhat more sophisticated event handling than the mousePressed() and mouseReleased() methods. It checks the newObject flag to make sure that an object is currently being drawn. It then sets the oldX and oldY variables to the ending position of the object being drawn. These variables will be used to determine which portion of the canvas needs to be repainted. Repainting the entire canvas is not visually appealing because it causes previously drawn objects to flicker.

If the current drawing tool is not a line, an oval or a rectangle is being drawn by the user. The x- and y-coordinates of the mouse motion are provided via the MouseEvent argument to the mouseDragged() method. These coordinates are checked to determine whether the mouse was dragged below and to the right of the object being drawn. If this is the case, the ending position of the current object is updated. If the mouse is dragged to the left or above the starting point of the object, the current position of the mouse is ignored. This is to ensure that the starting position of the oval or rectangle is indeed its upper-left corner. The new width and height of the area to be repainted are calculated as the maximum area covered by the previous ending position and the current object ending position. This is to ensure that the repaint operation will erase any previous boundaries of the object being drawn. The max() method of the java.lang.Math class is used to determine this maximum area. The repaint() method of the Component class is then used to repaint the area updated as the result of the mouse drag. This version of the repaint() method takes as its parameters the x- and y-coordinates of the upper-left corner of the area to be redrawn and the width and height of this area.

Line drawing is not restricted in the same manner as oval and rectangle drawing. If it were, you would not be able to draw lines that go up and to the right or down and to the left. The else part of the if statement updates the starting position of the area to be repainted as the upper-leftmost point of the line being redrawn. It then updates the ending position of the area to be repainted as the lower-rightmost point of the line. The canvas is then repainted using the starting coordinates and the updated width and height of the repainted area.

To get a better feel for the process of local screen repainting, try experimenting with the way the repaint() method is used to update the canvas display.

TwoPointObject

The TwoPointObject class is used to keep track of the objects drawn by the user. It records the type of object and its starting and ending coordinates. It also draws the objects on a Graphics object passed as a parameter.

TwoPointObject defines the LINE, OVAL, and RECTANGLE constants, which are also used by the MyCanvas class. The type variable is used to record the type of object being drawn. The startX, startY, endX, and endY variables identify the starting and ending coordinates of the object.

Three TwoPointObject constructors are declared. The first constructor takes as its parameters the type of object being drawn and its starting and ending coordinates. The second constructor leaves out the ending coordinates and sets them to be the same as the starting coordinates. The last constructor takes no parameters and creates a line at the coordinates 0,0.

The draw() method checks the type variable to determine which type of object is to be drawn. If the object is a line, it uses the drawLine() method of the Graphics class to draw a line from its starting to ending coordinates. If the object is an oval or a line, the w and l variables are assigned the width and length of the object to be drawn. The drawOval() and drawRect() methods are used to draw an oval or rectangle, respectively.

Using Text and Fonts

The Font class of java.awt provides a platform-independent method of specifying and using fonts. The Font class constructor constructs Font objects using the font's name, style (PLAIN, BOLD, ITALIC, or BOLD + ITALIC), and point size. Java's fonts are named in a platform-independent manner and then mapped to local fonts that are supported by the operating system on which it executes. The getName() method returns the logical Java font name of a particular font, and the getFamily()method returns the operating system-specific name of the font. You'll learn the name of the standard Java fonts in the next programming example in this chapter.

The FontMetrics class is used to return the specific parameters for a particular Font object. An object of this class is created using the getFontMetrics() methods supported by the Component class and other classes, such as the Graphics and Toolkit classes. The FontMetrics methods provide access to the details of the implementation of a Font object.

The bytesWidth(), charWidth(), charsWidth(), getWidths(), and stringWidth() methods are used to determine the width of a text object in pixels. These methods are essential for determining the horizontal position of text on the screen.

When text characters are displayed, they are displayed relative to a baseline. The baseline is the line drawn through the bottom of nondescending characters. For example, if you drew a line at the bottom of most text displayed on this line, you would get the text's baseline. Some characters, such as g and y, descend below the baseline. The number of pixels that the characters of a font descend below the baseline is known as the font's descent. The number of pixels that the characters of a font extend above the baseline is known as the font's ascent.

In addition to a font's ascent and descent, a third parameter, referred to as the font's leading, is used to describe the amount of vertical spacing, in pixels, used between the descent of a line of text and the ascent of the line of text below it. The overall height of a font is the sum of its leading, ascent, and descent, and is equal to the distance between baselines (in pixels) of vertically adjacent lines of text. The getLeading(), getAscent(), getDescent(), and getHeight() methods of the FontMetrics class are used to access these important font-related parameters. Figure 7.6 provides a graphical description of these parameters.


FIGURE 7.6. Font parameters.

The getMaxAdvance(), getMaxAscent(), and getMaxDescent() methods are provided for backward-compatibility with earlier Java versions.

Using the Toolkit Class

The Toolkit class provides a link between the platform-independent Java implementation and its platform-specific characteristics. Among the many interesting methods implemented by this class are the getFontList(), getFontMetrics(), getScreenSize(), and getScreenResolution() methods. The getFontList()method returns a list of fonts that are accessible from Java. The getFontMetrics() method identifies the font metrics for a particular font. The getScreenSize() method identifies the screen dimension in terms of horizontal and vertical dots. The getScreenResolution() method identifies the screen resolution in dots per inch.

The getFontList() is the method of interest for this chapter. You'll use it to get a list of the fonts available to Java in the next section.

The getFontList() method is deprecated in JDK 1.2. With the advent of the Java 2D API (refer to Chapter 20), the getFontFamilyNames() method of the GraphicsEnvironment class is now preferred.

The FontTest Applet

The FontTest applet illustrates the use of the Font, FontMetrics, and Toolkit classes and shows how to draw text on a Graphics object. Its source code is shown in Listing 7.5. Listing 7.6 provides an HTML file for displaying the applet.

LISTING 7.5. THE SOURCE CODE OF THE FontTest APPLET.

import java.applet.*;

import java.awt.*;

public class FontTest extends Applet {

 Toolkit toolkit;

 Font defaultFont;

 String fontNames[];

 int screenWidth = 400;

 int screenHeight = 400;

 public void init() {

  setupFonts();

  setSize(screenWidth,screenHeight);

 }

 void setupFonts() {

  toolkit = getToolkit();

  defaultFont = getFont();

  fontNames = toolkit.getFontList();

}

 public void paint(Graphics g) {

  int styles[] = {Font.PLAIN,Font.BOLD,Font.ITALIC};

  String styleNames[] = {"Plain","Bold","Italic"};

  int size = 12;

  int y=10;

  for(int i=0;i<fontNames.length;++i) {

   if(fontNames[i]!="ZapfDingbats") {

    for(int j=0;j<styles.length;++j) {

     Font newFont = new Font(fontNames[i],styles[j],size);

     FontMetrics fm = g.getFontMetrics(newFont);

     g.setFont(newFont);

     String text = fontNames[i]+"-"+styleNames[j];

     int x = (screenWidth - fm.stringWidth(text))/2;

     g.drawString(text,x,y+fm.getLeading()+fm.getAscent());

     y += fm.getHeight();

    }

   }

  }

 }

}

LISTING 7.6. THE FontTest.htm FILE.

<HTML>

<HEAD>

<TITLE>Using Fonts</TITLE>

</HEAD>

<BODY>

<APPLET CODE="FontTest.class" HEIGHT=400 WIDTH=400>

</APPLET>

</BODY>

</HTML>

The FontTest applet does not provide much functionality. Just run it and it will display a list of the fonts that are currently available to Java, with each name written in its font. Figure 7.7 shows its display. The applet's importance is not in what it does, but in how it does it. By closely examining this applet, you'll be able to quickly come up to speed on working with Java fonts.

FIGURE 7.7. The FontTest output.

The FontTest class declares a number of field variables. The toolkit variable is used to refer to the Toolkit object associated with the applet window. The defaultFont variable identifies the default font used by the applet. The fontNames[] array is used to store the names of the fonts that are accessible to Java.

The setupFonts() method obtains the Toolkit object associated with the applet's window, using the getToolkit() method, and assigns this object to the toolkit variable. The current font used by the applet is accessed by getFont() and assigned to the defaultFont variable. The Toolkit object is then used to obtain the current list of font names via the getFontList() method of the Toolkit class. That's all for the applet's setup.

The paint() method is where the primary processing of interest takes place. The styles[] and styleNames[] arrays are used to identify the various text styles and their associated string descriptions. The y variable identifies the vertical screen position where text is displayed. The size variable identifies the point size used to display a font.

The paint() method uses two for statements. The outer statement iterates through the list of font names, and the inner statement iterates through the font styles. The ZapfDingbats font, a symbol font, is skipped. At each pass through the inner loop, a new font is created with the specified name, style, and size. The getFontMetrics() method of the Graphics class is used to obtain the FontMetrics object associated with the newly created font, and this object is assigned to the fm variable. The setFont() method of the Graphics class is used to set the current font to the new font.

The next line of text to be displayed is created by concatenating the font name and its style name. The horizontal position at which the text is to be displayed in order for it to be centered is calculated based upon the width of the text (in pixels) returned by the stringWidth() method of the FontMetrics class and the initial width of the applet window area. The vertical position where the text is to be displayed is its baseline, and is determined by adding the leading and ascent values of the font with the y variable. These values are obtained using the getLeading() and getAscent() methods of the current FontMetrics object. The y variable identifies the point of maximum descent of the previously displayed line of text. It is then updated for the current line of text by adding the height of the current font returned by the getHeight() method of the FontMetrics class.

Fonts, Colors, and Text Components

The Font and FontMetrics classes are not confined to text that is drawn on Graphics objects. These classes can also be used with the TextField and TextArea classes. These classes automatically calculate the correct text-display locations using the native text objects supported by the local operating-system platform. In addition to changing text fonts, the TextField and TextArea classes also support the display of text using different foreground and background colors. The following applet shows how fonts and colors can be quickly incorporated into a Java applet to implement features associated with What-You-See-Is-What-You-Get (WYSIWYG) editors.

The Edit Applet

The Edit applet shows how the Font and Color classes can be used with a TextArea component. Its source code is shown in Listing 7.7. Its HTML file is provided in Listing 7.8.

The Edit applet uses the FontDialog and ColorDialog classes that are introduced in subsequent sections. In order to compile and run Edit.java, you will need the FontDialog.java and ColorDialog.java files. Java will automatically compile the FontDialog.java and ColorDialog.java files when Edit.java is compiled.

LISTING 7.7. THE SOURCE CODE OF THE Edit APPLET.

import java.applet.*;

import java.awt.*;

import java.awt.event.*;

public class Edit extends Applet {

 Button clearButton = new Button("Clear");

 Button fontButton = new Button("Font");

 Button colorButton = new Button("Color");

 TextArea text;

 Frame frame;

 FontDialog fd;

 ColorDialog cd;

 Font currentFont = new Font("Courier",Font.PLAIN,12);

 Color currentColor = Color.black;

 public void init() {

  setBackground(Color.white);

  setLayout(new BorderLayout());

  text = new TextArea(25,80);

  text.setFont(currentFont);

  add("Center",text);

  setupButtons();

 }

 void setupButtons() {

clearButton.addActionListener(new ButtonHandler());

  fontButton.addActionListener(new ButtonHandler());

  colorButton.addActionListener(new ButtonHandler());

  Panel panel = new Panel();

  panel.add(clearButton);

  panel.add(fontButton);

  panel.add(colorButton);

  add("North",panel);

 }

 class ButtonHandler implements ActionListener {

  public void actionPerformed(ActionEvent ev){

   String s=ev.getActionCommand();

   if(s=="Clear"){

    text.setText("");

   }else if(s=="Font"){

    frame = new Frame();

    frame.show();

    fd = new FontDialog(frame,currentFont,

     new FontSelectHandler());

    fd.show();

   }else if(s=="Color"){

    frame = new Frame();

    frame.show();

    cd = new ColorDialog(frame,currentColor,

     new ColorSelectHandler());

    cd.show();

   }

  }

 }

 class FontSelectHandler implements ActionListener {

  public void actionPerformed(ActionEvent e){

   currentFont = fd.getFont();

   fd.dispose();

   frame.dispose();

   requestFocus();

   text.setFont(currentFont);

  }

 }

 class ColorSelectHandler implements ActionListener {

  public void actionPerformed(ActionEvent e){

   currentColor = cd.getColor();

   cd.dispose();

   frame.dispose();

   requestFocus();

   text.setForeground(currentColor);

   text.setText(text.getText());

   text.setVisible(true);

  }

 }

}

LISTING 7.8. THE Edit.htm FILE.

<HTML>

<HEAD>

<TITLE>Text Editing</TITLE>

</HEAD>

<BODY>

<APPLET CODE="Edit.class" HEIGHT=400 WIDTH=600>

</APPLET>

</BODY>

</HTML>

The Edit applet opening display is shown in Figure 7.8.

Type some text into the applet's text area. Click the Font button to launch the font dialog box, shown in Figure 7.9. Use this dialog box to select a 24-point Bold Italic Helvetica font. The text's display is updated, as shown in Figure 7.10.

FIGURE 7.8. The Edit applet opening display.

FIGURE 7.9. The font dialog box.

Select the Color menu item from the Format menu. The color dialog box is displayed, as shown in Figure 7.11. Use this dialog box to change the color associated with the text's display. Try using primary colors such as blue or green. Other colors might not display correctly, depending on the number of colors supported by your video card and the current color map associated with the display.

The Edit applet makes use of the FontDialog and ColorDialog classes covered in the following sections. It declares variables to implement the Clear, Font, and Color buttons, the text area, and a frame window for displaying dialog boxes. It also declares variables for referencing the font and color dialog boxes and the current font and color in use.

The init() method sets the background color and layout, creates the TextArea object, and sets the current font. It then adds the TextArea object to the center of the applet's display and invokes setupButtons().

FIGURE 7.10. Updated text.

FIGURE 7.11. The color dialog box.

The setupButtons() method sets up each button's event handler, organizes the buttons into a Panel object, and adds the panel to the applet's display area.

The ButtonHandler class handles the clicking of the buttons. The Clear button is handled by invoking the TextArea object's setText() method to clear its text. Clicking of the Font button results in a new FontDialog object being created to solicit the user to change the current font. You'll learn about this class in the next section. The clicking of the Color button results in a ColorDialog object being created to provide the user with opportunity to change the color of the current text. The ColorDialog class is covered later in the chapter. Note that a Frame object is created to provide a window in which the FontDialog and ColorDialog boxes are displayed.

The FontSelectHandler class handles the event that occurs when the user closes the font dialog box. You'll create this event in the FontDialog class. The actionPerformed() method invokes the getFont() method of the FontDialog class to retrieve the font selected by the user. It then disposes of the dialog box and its frame, and sets the current font of the TextArea object to the retrieved font.

The ColorSelectHandler class handles the event that occurs when the user closes a color dialog box. You'll create this event in the ColorDialog class. The actionPerformed() method invokes the getColor() method of the ColorDialog class to retrieve the color selected by the user. It then disposes of the dialog box and its frame, and sets the current color of the TextArea object to the retrieved color. The setText() and getText() methods are used to reset the text using the new color. The setVisible() method causes the TextArea object to be redisplayed.

The FontDialog Class

The FontDialog class provides a handy encapsulation of the dialog boxes commonly used to select a font from the list of available fonts provided by the system. The source code of the FontDialog class is shown in Listing 7.9.

LISTING 7.9. THE SOURCE CODE OF THE FontDialog CLASS.

import java.awt.*;

import java.awt.event.*;

public class FontDialog extends Dialog {

 String fontName;

 int fontStyle;

 int fontSize;

 String fontNames[];

 String styleNames[] = {"Plain","Bold","Italic","Bold Italic"};

 String sizeNames[] = {"10","12","14","18","24","36","72"};

 int styles[] = {Font.PLAIN,Font.BOLD,Font.ITALIC,Font.BOLD+Font.ITALIC};

 int sizes[] = {10,12,14,18,24,36,72};

 MyList fontList;

 MyList styleList = new MyList(5,false,styleNames);

 MyList sizeList = new MyList(5,false,sizeNames);

 Toolkit toolkit;

 Font newFont;

 boolean fontChanged;

 ActionListener ah;

 public FontDialog(Frame parent,Font currentFont,ActionListener ah) {

  super(parent,"Select a font:",true);

  toolkit = parent.getToolkit();

  newFont = currentFont;

  setupFonts();

  setupPanels();

  setBackground(Color.lightGray);

  setForeground(Color.black);

  this.ah=ah;

  pack();

  addWindowListener(new WindowEventHandler());

 }

 void setupFonts() {

  fontName=newFont.getName();

  fontStyle=newFont.getStyle();

  fontSize=newFont.getSize();

  fontNames = toolkit.getFontList();

  fontList = new MyList(5,false,fontNames);

 }

 void setupPanels() {

  Panel mainPanel = new Panel();

  mainPanel.setLayout(new GridLayout(1,3));

  Panel fontPanel = new Panel();

  fontPanel.setLayout(new BorderLayout());

  Label fontLabel = new Label("Font:");

  fontPanel.add("North",fontLabel);

  fontPanel.add("Center",fontList);

  Panel stylePanel = new Panel();

  stylePanel.setLayout(new BorderLayout());

  Label styleLabel = new Label("Style:");

  stylePanel.add("North",styleLabel);

  stylePanel.add("Center",styleList);

  Panel sizePanel = new Panel();

  sizePanel.setLayout(new BorderLayout());

  Label sizeLabel = new Label("Size:");

  sizePanel.add("North",sizeLabel);

  sizePanel.add("Center",sizeList);

  mainPanel.add(fontPanel);

  mainPanel.add(stylePanel);

  mainPanel.add(sizePanel);

  Font plainFont = new Font("Helvetica",Font.PLAIN,12);

  Font boldFont = new Font("Helvetica",Font.BOLD,12);

  mainPanel.setFont(plainFont);

  fontLabel.setFont(boldFont);

  styleLabel.setFont(boldFont);

  sizeLabel.setFont(boldFont);

  Panel buttonPanel = new Panel();

  buttonPanel.setLayout(new FlowLayout());

  Button selectButton = new Button("Select");

  Button cancelButton = new Button("Cancel");

  ButtonHandler bh = new ButtonHandler();

  selectButton.addActionListener(bh); 

  cancelButton.addActionListener(bh);

  buttonPanel.add(selectButton);

  buttonPanel.add(cancelButton);

buttonPanel.setFont(boldFont);

  add("Center",mainPanel);

  add("South",buttonPanel);

 }

 public boolean isChanged() {

  return fontChanged;

 }

 public Font getFont() {

  return newFont;

 }

 void updateNewFont() {

  if(fontList.getSelectedIndex() != -1) fontName =    ÂfontList.getSelectedItem();

  if(styleList.getSelectedIndex() != -1)

   fontStyle = styles[styleList.getSelectedIndex()];

  if(sizeList.getSelectedIndex() != -1)

   fontSize = sizes[sizeList.getSelectedIndex()];

  newFont = new Font(fontName,fontStyle,fontSize);

  fontChanged = true;

 }

 class ButtonHandler implements ActionListener {

  public void actionPerformed(ActionEvent e){

   String s = e.getActionCommand();

   if("Select".equals(s)) {

    updateNewFont();

    ah.actionPerformed(new ActionEvent(FontDialog.this,

     ActionEvent.ACTION_PERFORMED,"Select"));

    FontDialog.this.setVisible(false);

   }else if("Cancel".equals(s)) {

    FontDialog.this.dispose();

   }

  }

 }

 class WindowEventHandler extends WindowAdapter {

  public void windowClosing(WindowEvent e){

   FontDialog.this.dispose();

  }

 }

}

The FontDialog class creates the font dialog box, shown in Figure 7.9. This type of dialog box is used in most text-processing applications. You can reuse the FontDialog class, as it is currently defined, in your Java applets. You can also subclass FontDialog and add your own custom enhancements.

The FontDialog class declares a number of variables that are used in the generation and processing of the font dialog box. The fontName, fontStyle, and fontSize variables are used to keep track of the parameters of the currently selected font. The fontNames array identifies the names of the fonts that are currently supported by the system. The styles, styleNames, sizes, and sizeNames arrays are used to maintain int and String lists of the font styles and sizes that are displayed in the dialog box. The fontList, styleList, and sizeList variables refer to the MyList objects displayed in the dialog box. The MyList class is shown in Listing 7.10. The toolkit variable refers to the Toolkit object of the window containing the font dialog box. The fontChanged variable keeps track of whether the user has selected a new font, and the newFont variable maintains the Font object that is selected by the user.

The FontDialog constructor uses the superclass constructor call statement to create a modal dialog box with the title Select a font:. The toolkit associated with the window containing the dialog box is obtained using the getToolkit() method of the Window class. The newFont variable, representing the user's font selection, is set to the default value of the currently selected font. This font is passed to the FontDialog constructor using the currentFont parameter. The FontDialog constructor invokes the setupFonts() and setupPanels() methods to perform the bulk of the dialog box setup. It then sets the background and foreground colors and stores the ActionListener object passed via the ah variable. This ActionListener object is used to handle an event generated by the FontDialog class. The constructor then packs the dialog box window and assigns an event handler to it.

The setupFonts() method assigns default values to the fontName, fontStyle, and fontSize variables based on the values of the current font stored in the newFont variable. The getFontList() method of the Toolkit class is used to set the fontNames[] array to the list of fonts currently supported by the system. These names are converted to a list using the MyList() constructor.

The setupPanels() method performs all of the grunt work, adding the lists to the dialog box and rearranging them in an appealing fashion. The mainPanel variable is used to refer to the overall panel into which the fontPanel, stylePanel, and sizePanel objects are inserted. The mainPanel is layed out as a three-column set of subpanels. These sub-panels are identified by the fontPanel, stylePanel, and sizePanel variables. Each of these subpanels is layed out using a BorderLayout object. The label identifying the contents of the panel is added to the top of the panel. The center of each panel contains the three MyList objects identified by the fontList, styleList, and sizeList variables.

The Helvetica font is used for the contents of the font dialog box. The labels at the top of each column are set in a boldface style. A second panel, referred to by the buttonPanel variable, is created with two buttons: Select and Cancel. These buttons provide the user with controls needed to accept or abort a font selection. An object of the ButtonHandler class is used as the buttons' event handler. The mainPanel is added to the center of the font dialog box, and the buttonPanel is added to the bottom.

Two access methods are provided with the FontDialog class. The isChanged() method is used to query a FontDialog object to determine whether the user made a font selection. The getFont() method returns the font selected by the user.

The ButtonHandler class handles the clicking of the Select and Cancel buttons. The Cancel button results in the destruction of the FontDialog object. The object is destroyed using the dispose() method of the Window class. The Select button invokes the updateNewFont() method to create a font based on the user's current list selections and assign that font to the newFont variable. The actionPerformed() method of the ActionListener object passed to the FontDialog constructor is invoked. This enables additional event handling to be performed outside the FontDialog class. The font dialog box is then hidden but not destroyed. Note that an ActionEvent object is passed as an argument to the actionPerformed() method. The setVisible() method of the Component class is used to hide the dialog box.

The updateNewFont() method checks the MyList objects referred to by the fontList, styleList, and sizeList variables to update the fontName, fontStyle, and fontSize variables based on the user's selection. These variables are then used to construct a new Font object, which is assigned to the newFont variable. The fontChanged flag is then set to indicate that a user font selection has occurred.

LISTING 7.10. THE MyList CLASS.

import java.awt.*;

public class MyList extends List {

 public MyList(int rows,boolean multiple,String labels[]) {

  super(rows,multiple);

  int length = labels.length;

  for(int i=0;i<length;++i) {

   try {

    add(labels[i]);

   }catch (NullPointerException ex) {

    add("");

   }

  }

 }

}

The MyList class, shown in Listing 7.10, provides a constructor that simplifies the construction of List objects.

The ColorDialog Class

The ColorDialog class is very similar to, but simpler than, the FontDialog class. It allows the user to select a color from the list of colors defined in the Color class. It provides a dialog box that is similar to that of FontDialog, but is much simpler because only one list--the list of available colors--is supported. The source code of the ColorDialog class is shown in Listing 7.11.

LISTING 7.11. THE SOURCE CODE OF THE ColorDialog CLASS.

import java.awt.*;

import java.awt.event.*;

public class ColorDialog extends Dialog {

 Color colors[] = {Color.black,Color.blue,Color.cyan,Color.darkGray,Color.gray,

Color.green,Color.lightGray,Color.magenta,Color.orange,Color.pink,

ÂColor.red,

  Color.white,Color.yellow};

 String colorNames[] = {"black","blue","cyan","darkGray","gray","green",

  "lightGray","magenta","orange","pink","red",

"white","yellow"};

 MyList colorList = new MyList(5,false,colorNames);

 Color newColor;

 boolean colorChanged;

 ActionListener ah;

 public ColorDialog(Frame parent,Color currentColor,ActionListener ah) {

  super(parent,"Select a color:",true);

  setupPanels();

  setBackground(Color.lightGray);

  setForeground(Color.black);

  this.ah=ah;

  pack();

  addWindowListener(new WindowEventHandler());

 }

 void setupPanels() {

  Panel colorPanel = new Panel();

  colorPanel.setLayout(new BorderLayout());

  Label colorLabel = new Label("Color:");

  colorPanel.add("North",colorLabel);

  colorPanel.add("Center",colorList); 

  Font plainFont = new Font("Helvetica",Font.PLAIN,12);

  Font boldFont = new Font("Helvetica",Font.BOLD,12);

  colorLabel.setFont(boldFont);

  colorList.setFont(plainFont);

  Panel buttonPanel = new Panel();

  buttonPanel.setLayout(new FlowLayout());

  Button selectButton = new Button("Select");

  Button cancelButton = new Button("Cancel");

  ButtonHandler bh = new ButtonHandler();

  selectButton.addActionListener(bh); 

  cancelButton.addActionListener(bh);

  buttonPanel.add(selectButton);

  buttonPanel.add(cancelButton);

  buttonPanel.setFont(boldFont);

  add("Center",colorPanel);

  add("South",buttonPanel);

 }

 public boolean isChanged() {

  return colorChanged;

 }

 public Color getColor() {

  return newColor;

 }

 class ButtonHandler implements ActionListener {

  public void actionPerformed(ActionEvent e){

   String s = e.getActionCommand();

   if("Select".equals(s)) {

    if(colorList.getSelectedIndex() != -1)

     newColor = colors[colorList.getSelectedIndex()];

    colorChanged = true;

    ah.actionPerformed(new ActionEvent(ColorDialog.this,

     ActionEvent.ACTION_PERFORMED,"Select"));

    ColorDialog.this.setVisible(false);

   }else if("Cancel".equals(s)) {

    ColorDialog.this.dispose();

   }

  }

 }

 class WindowEventHandler extends WindowAdapter {

  public void windowClosing(WindowEvent e){

   ColorDialog.this.dispose();

  }

 }

}

The ColorDialog class declares the colors array as an array of color constants and the colorNames array as the names associated with these color constants. The colorList variable refers to the MyList object that presents the colorNames array to the user. The newColor variable identifies the color selected by the user, and the colorChanged variable indicates whether a user color selection has been made.

The ColorDialog constructor invokes the Dialog constructor to set the title of the dialog box. It then invokes the setupPanels() method to perform most of the setup of the dialog box's internal components. The foreground and background colors are set and then the dialog box is packed and resized.

The setupPanels() method creates and adds two panels to the dialog box. These panels are identified by the colorPanel and buttonPanel variables. The panel identified by the colorPanel variable contains the Color: label and the MyList object referred to by the colorList variable. The button panel is implemented in the same manner as in the FontDialog class.

The isChanged() and getColor() methods are used to determine whether the user has selected a color and, if so, to return the color selected.

The ButtonHandler class handles the clicking of the Select and Cancel buttons. The Select button is handled by invoking the getSelectedIndex() method of the List class to see if a color was selected and setting the newColor variable to the selected color. The colorChanged flag is updated to indicate that a color has been selected. The actionPerformed() method of the ActionListener object passed to the ColorDialog constructor is invoked in the same manner as in the FontDialog class. The setVisible() method causes the dialog box to be hidden.

The Cancel button is handled by simply disposing of the dialog box.

Summary

This chapter covers the details of using the Canvas and Graphics classes. It also shows you how to use the font-related classes of the java.awt package. The DisplayImage applet demonstrates Java's support of bitmapped images. The Draw applet illustrates the drawing methods of the Graphics class, and the FontTest applet shows you how to draw text on a Canvas object. The Edit applet shows how fonts and colors are used with text components. Chapter 8, "Applet Security," shows you how to use the applet security features of JDK 1.2.


Contents

© Copyright, Macmillan Computer Publishing. All rights reserved.