Bruce Eckel's Thinking in Java Contents | Prev | Next

Java 1.1 UI APIs

Java 1.1 has also added some important new functionality, including focus traversal, desktop color access, printing “inside the sandbox,” and the beginnings of clipboard support.

Focus traversal is quite easy, since it’s transparently present in the AWT library components and you don’t have to do anything to make it work. If you make your own components and want them to handle focus traversal, you override isFocusTraversable( ) to return true. If you want to capture the keyboard focus on a mouse click, you catch the mouse down event and call requestFocus( ).

Desktop colors

The desktop colors provide a way for you to know what the various color choices are on the current user’s desktop. This way, you can use those colors in your program if you desire. The colors are automatically initialized and placed in static members of class SystemColor, so all you need to do is read the member you’re interested in. The names are intentionally self-explanatory: desktop, activeCaption, activeCaptionText, activeCaptionBorder, inactiveCaption, inactiveCaptionText, inactiveCaptionBorder, window, windowBorder, windowText, menu, menuText, text, textText, textHighlight, textHighlightText, textInactiveText, control, controlText, controlHighlight, controlLtHighlight, controlShadow, controlDkShadow, scrollbar, info (for help), and infoText (for help text).

Printing

Unfortunately, there isn’t much that’s automatic with printing. Instead you must go through a number of mechanical, non-OO steps in order to print. Printing a component graphically can be slightly more automatic: by default, the print( ) method calls paint( ) to do its work. There are times when this is satisfactory, but if you want to do anything more specialized you must know that you’re printing so you can in particular find out the page dimensions.

The following example demonstrates the printing of both text and graphics, and the different approaches you can use for printing graphics. In addition, it tests the printing support:

//: PrintDemo.java
// Printing with Java 1.1
import java.awt.*;
import java.awt.event.*;

public class PrintDemo extends Frame {
  Button 
    printText = new Button("Print Text"),
    printGraphics = new Button("Print Graphics");
  TextField ringNum = new TextField(3);
  Choice faces = new Choice();
  Graphics g = null;
  Plot plot = new Plot3(); // Try different plots
  Toolkit tk = Toolkit.getDefaultToolkit();
  public PrintDemo() {
    ringNum.setText("3");
    ringNum.addTextListener(new RingL());
    Panel p = new Panel();
    p.setLayout(new FlowLayout());
    printText.addActionListener(new TBL());
    p.add(printText);
    p.add(new Label("Font:"));
    p.add(faces);
    printGraphics.addActionListener(new GBL());
    p.add(printGraphics);
    p.add(new Label("Rings:"));
    p.add(ringNum);
    setLayout(new BorderLayout());
    add(p, BorderLayout.NORTH);
    add(plot, BorderLayout.CENTER);
    String[] fontList = tk.getFontList();
    for(int i = 0; i < fontList.length; i++)
      faces.add(fontList[i]);
    faces.select("Serif");
  }
  class PrintData {
    public PrintJob pj;
    public int pageWidth, pageHeight;
    PrintData(String jobName) {
      pj = getToolkit().getPrintJob(
        PrintDemo.this, jobName, null);
      if(pj != null) {
        pageWidth = pj.getPageDimension().width;
        pageHeight= pj.getPageDimension().height;
        g = pj.getGraphics();
      }
    }
    void end() { pj.end(); }
  }
  class ChangeFont {
    private int stringHeight;
    ChangeFont(String face, int style,int point){
      if(g != null) {
        g.setFont(new Font(face, style, point));
        stringHeight = 
          g.getFontMetrics().getHeight();
      }
    }
    int stringWidth(String s) {
      return g.getFontMetrics().stringWidth(s);
    }
    int stringHeight() { return stringHeight; }
  }
  class TBL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      PrintData pd = 
        new PrintData("Print Text Test");
      // Null means print job canceled:
      if(pd == null) return;
      String s = "PrintDemo";
      ChangeFont cf = new ChangeFont(
        faces.getSelectedItem(), Font.ITALIC,72);
      g.drawString(s, 
        (pd.pageWidth - cf.stringWidth(s)) / 2,
        (pd.pageHeight - cf.stringHeight()) / 3);

      s = "A smaller point size";
      cf = new ChangeFont(
        faces.getSelectedItem(), Font.BOLD, 48);
      g.drawString(s, 
        (pd.pageWidth - cf.stringWidth(s)) / 2,
        (int)((pd.pageHeight - 
           cf.stringHeight())/1.5));
      g.dispose();
      pd.end();
    }
  }
  class GBL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      PrintData pd = 
        new PrintData("Print Graphics Test");
      if(pd == null) return;
      plot.print(g);
      g.dispose();
      pd.end();
    }
  }
  class RingL implements TextListener {
    public void textValueChanged(TextEvent e) {
      int i = 1;
      try {
        i = Integer.parseInt(ringNum.getText());
      } catch(NumberFormatException ex) {
        i = 1;
      }
      plot.rings = i;
      plot.repaint();
    }
  }
  public static void main(String[] args) {
    Frame pdemo = new PrintDemo();
    pdemo.setTitle("Print Demo");
    pdemo.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    pdemo.setSize(500, 500);
    pdemo.setVisible(true);
  }
}

class Plot extends Canvas {
  public int rings = 3;
}

class Plot1 extends Plot {
  // Default print() calls paint():
  public void paint(Graphics g) {
    int w = getSize().width;
    int h = getSize().height;
    int xc = w / 2;
    int yc = w / 2;
    int x = 0, y = 0;
    for(int i = 0; i < rings; i++) {
      if(x < xc && y < yc) {
        g.drawOval(x, y, w, h);
        x += 10; y += 10;
        w -= 20; h -= 20;
      }
    }
  }
} 

class Plot2 extends Plot {
  // To fit the picture to the page, you must
  // know whether you're printing or painting:
  public void paint(Graphics g) {
    int w, h;
    if(g instanceof PrintGraphics) {
      PrintJob pj = 
        ((PrintGraphics)g).getPrintJob();
      w = pj.getPageDimension().width;
      h = pj.getPageDimension().height;
    } 
    else {
      w = getSize().width;
      h = getSize().height;
    }
    int xc = w / 2;
    int yc = w / 2;
    int x = 0, y = 0;
    for(int i = 0; i < rings; i++) {
      if(x < xc && y < yc) {
        g.drawOval(x, y, w, h);
        x += 10; y += 10;
        w -= 20; h -= 20;
      }
    }
  }
} 

class Plot3 extends Plot {
  // Somewhat better. Separate 
  // printing from painting:
  public void print(Graphics g) {
    // Assume it's a PrintGraphics object:
    PrintJob pj = 
      ((PrintGraphics)g).getPrintJob();
    int w = pj.getPageDimension().width;
    int h = pj.getPageDimension().height;
    doGraphics(g, w, h);
  }
  public void paint(Graphics g) {
    int w = getSize().width;
    int h = getSize().height;
    doGraphics(g, w, h);
  }
  private void doGraphics(
      Graphics g, int w, int h) {
    int xc = w / 2;
    int yc = w / 2;
    int x = 0, y = 0;
    for(int i = 0; i < rings; i++) {
      if(x < xc && y < yc) {
        g.drawOval(x, y, w, h);
        x += 10; y += 10;
        w -= 20; h -= 20;
      }
    }
  }
} ///:~ 

The program allows you to select fonts from a Choice list (and you’ll see that the number of fonts available in Java 1.1 is still extremely limited, and has nothing to do with any extra fonts you install on your machine). It uses these to print out text in bold, italic, and in different sizes. In addition, a new type of component called a Plot is created to demonstrate graphics. A Plot has rings that it will display on the screen and print onto paper, and the three derived classes Plot1, Plot2, and Plot3 perform these tasks in different ways so that you can see your alternatives when printing graphics. Also, you can change the number of rings in a plot – this is interesting because it shows the printing fragility in Java 1.1. On my system, the printer gave error messages and didn’t print correctly when the ring count got “too high” (whatever that means), but worked fine when the count was “low enough.” You will notice, too, that the page dimensions produced when printing do not seem to correspond to the actual dimensions of the page. This might be fixed in a future release of Java, and you can use this program to test it.

This program encapsulates functionality inside inner classes whenever possible, to facilitate reuse. For example, whenever you want to begin a print job (whether for graphics or text), you must create a PrintJob object, which has its own Graphics object along with the width and height of the page. The creation of a PrintJob and extraction of page dimensions is encapsulated in the PrintData class.

Printing text

Conceptually, printing text is straightforward: you choose a typeface and size, decide where the string should go on the page, and draw it with Graphics.drawString( ). This means, however, that you must perform the calculations of exactly where each line will go on the page to make sure it doesn’t run off the end of the page or collide with other lines. If you want to make a word processor, your work is cut out for you.

ChangeFont encapsulates a little of the process of changing from one font to another by automatically creating a new Font object with your desired typeface, style ( Font.BOLD or Font.ITALIC – there’s no support for underline, strikethrough, etc.), and point size. It also simplifies the calculation of the width and height of a string.

When you press the “Print text” button, the TBL listener is activated. You can see that it goes through two iterations of creating a ChangeFont object and calling drawString( ) to print out the string in a calculated position, centered, one-third, and two-thirds down the page, respectively. Notice whether these calculations produce the expected results. (They didn’t with the version I used.)

Printing graphics

When you press the “Print graphics” button the GBL listener is activated. The creation of a PrintData object initializes g, and then you simply call print( ) for the component you want to print. To force printing you must call dispose( ) for the Graphics object and end( ) for the PrintData object (which turns around and calls end( ) for the PrintJob).

The work is going on inside the Plot object. You can see that the base-class Plot is simple – it extends Canvas and contains an int called rings to indicate how many concentric rings to draw on this particular Canvas. The three derived classes show different approaches to accomplishing the same goal: drawing on both the screen and on the printed page.

Plot1 takes the simplest approach to coding: ignore the fact that there are differences in painting and printing, and just override paint( ). The reason this works is that the default print( ) method simply turns around and calls paint( ). However, you’ll notice that the size of the output depends on the size of the on-screen canvas, which makes sense since the width and height are determined by calling Canvas.getSize( ). The other situation in which this is acceptable is if your image is always a fixed size.

When the size of the drawing surface is important, then you must discover the dimensions. Unfortunately, this turns out to be awkward, as you can see in Plot2. For some possibly good reason that I don’t know, you cannot simply ask the Graphics object the dimensions of its drawing surface. This would have made the whole process quite elegant. Instead, to see if you’re printing rather than painting, you must detect the PrintGraphics using the RTTI instanceof keyword (described in Chapter 11), then downcast and call the sole PrintGraphics method: getPrintJob( ). Now you have a handle to the PrintJob and you can find out the width and height of the paper. This is a hacky approach, but perhaps there is some rational reason for it. (On the other hand, you’ve seen some of the other library designs by now so you might get the impression that the designers were, in fact, just hacking around...)

You can see that paint( ) in Plot2 goes through both possibilities of printing or painting. But since the print( ) method should be called when printing, why not use that? This approach is used in Plot3, and it eliminates the need to use instanceof since inside print( ) you can assume that you can cast to a PrintGraphics object. This is a little better. The situation is improved by placing the common drawing code (once the dimensions have been detected) inside a separate method doGraphics( ).

Running Frames within applets

What if you’d like to print from within an applet? Well, to print anything you must get a PrintJob object through a Toolkit object’s getPrintJob( ) method, which takes only a Frame object and not an Applet. Thus it would seem that it’s possible to print from within an application, but not an applet. However, it turns out that you can create a Frame from within an applet (which is the reverse of what I’ve been doing for the applet/application examples so far, which has been making an applet and putting inside a Frame). This is a useful technique since it allows you to use many applications within applets (as long as they don’t violate applet security). When the application window comes up within an applet, however, you’ll notice that the Web browser sticks a little caveat on it, something to the effect of “Warning: Applet Window.”

You can see that it’s quite straightforward to put a Frame inside an applet. The only thing that you must add is code to dispose( ) of the Frame when the user closes it (instead of calling System.exit( )):

//: PrintDemoApplet.java
// Creating a Frame from within an Applet
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class PrintDemoApplet extends Applet {
  public void init() {
    Button b = new Button("Run PrintDemo");
    b.addActionListener(new PDL());
    add(b);
  }
  class PDL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      final PrintDemo pd = new PrintDemo();
      pd.addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          pd.dispose();
        }
      });
      pd.setSize(500, 500);
      pd.show();
    }
  }
} ///:~ 

There’s some confusion involved with Java 1.1 printing support. Some of the publicity seemed to claim that you’d be able to print from within an applet. However, the Java security system contains a feature that could lock out an applet from initiating its own print job, requiring that the initiation be done via a Web browser or applet viewer. At the time of this writing, this seemed to remain an unresolved issue. When I ran this program from within a Web browser, the PrintDemo window came up just fine, but it wouldn’t print from the browser.

The clipboard

Java 1.1 supports limited operations with the system clipboard (in the java.awt.datatransfer package). You can copy String objects to the clipboard as text, and you can paste text from the clipboard into String objects. Of course, the clipboard is designed to hold any type of data, but how this data is represented on the clipboard is up to the program doing the cutting and pasting. Although it currently supports only string data, the Java clipboard API provides for extensibility through the concept of a “flavor.” When data comes off the clipboard, it has an associated set of flavors that it can be converted to (for example, a graph might be represented as a string of numbers or as an image) and you can see if that particular clipboard data supports the flavor you’re interested in.

The following program is a simple demonstration of cut, copy, and paste with String data in a TextArea. One thing you’ll notice is that the keyboard sequences you normally use for cutting, copying, and pasting also work. But if you look at any TextField or TextArea in any other program you’ll find that they also automatically support the clipboard key sequences. This example simply adds programmatic control of the clipboard, and you could use these techniques if you want to capture clipboard text into some non- TextComponent.

//: CutAndPaste.java
// Using the clipboard from Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;

public class CutAndPaste extends Frame {
  MenuBar mb = new MenuBar();
  Menu edit = new Menu("Edit");
  MenuItem
    cut = new MenuItem("Cut"),
    copy = new MenuItem("Copy"),
    paste = new MenuItem("Paste");
  TextArea text = new TextArea(20,20);
  Clipboard clipbd = 
    getToolkit().getSystemClipboard();
  public CutAndPaste() {
    cut.addActionListener(new CutL());
    copy.addActionListener(new CopyL());
    paste.addActionListener(new PasteL());
    edit.add(cut);
    edit.add(copy);
    edit.add(paste);
    mb.add(edit);
    setMenuBar(mb);
    add(text, BorderLayout.CENTER);
  }
  class CopyL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      String selection = text.getSelectedText();
      StringSelection clipString = 
        new StringSelection(selection);
      clipbd.setContents(clipString, clipString);
    }
  }
  class CutL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      String selection = text.getSelectedText();
      StringSelection clipString = 
        new StringSelection(selection);
      clipbd.setContents(clipString, clipString);
      text.replaceRange("", 
        text.getSelectionStart(), 
        text.getSelectionEnd());
    }
  }
  class PasteL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      Transferable clipData =
        clipbd.getContents(CutAndPaste.this);
      try {
        String clipString = 
          (String)clipData.
            getTransferData(
              DataFlavor.stringFlavor);
        text.replaceRange(clipString, 
          text.getSelectionStart(), 
          text.getSelectionEnd());
      } catch(Exception ex) {
        System.out.println("not String flavor");
      }
    }
  }
  public static void main(String[] args) {
    CutAndPaste cp = new CutAndPaste();
    cp.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    cp.setSize(300,200);
    cp.setVisible(true);
  }
} ///:~ 

The creation and addition of the menu and TextArea should by now seem a pedestrian activity. What’s different is the creation of the Clipboard field clipbd, which is done through the Toolkit.

All the action takes place in the listeners. The CopyL and CutL listeners are the same except for the last line of CutL, which erases the line that’s been copied. The special two lines are the creation of a StringSelection object from the String and the call to setContents( ) with this StringSelection. That’s all there is to putting a String on the clipboard.

In PasteL, data is pulled off the clipboard using getContents( ). What comes back is a fairly anonymous Transferable object, and you don’t really know what it contains. One way to find out is to call getTransferDataFlavors( ), which returns an array of DataFlavor objects indicating which flavors are supported by this particular object. You can also ask it directly with isDataFlavorSupported( ), passing in the flavor you’re interested in. Here, however, the bold approach is taken: getTransferData( ) is called assuming that the contents supports the String flavor, and if it doesn’t the problem is sorted out in the exception handler.

In the future you can expect more data flavors to be supported.

Contents | Prev | Next