2D Graphics GUI Java

/*
 * Copyright (c) 2000 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 2nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book (recommended),
 * visit http://www.davidflanagan.com/javaexamples2.
 */
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.LineMetrics;
import java.awt.geom.Rectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.Pageable;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.util.ArrayList;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import PrintableDocument.ParentView;
/**
 * This class implements the Pageable and Printable interfaces and allows the
 * contents of any JTextComponent to be printed using the java.awt.print
 * printing API.
 */
public class PrintableDocument implements Pageable, Printable {
  View root; // The root View to be printed
  PageFormat format; // Paper plus page orientation
  int numPages; // How many pages in the document
  double printX, printY; // coordinates of upper-left of print area
  double printWidth; // Width of the printable area
  double printHeight; // Height of the printable area
  Rectangle drawRect; // The rectangle in which the document is painted
  // How lenient are we with the bottom margin in widow and orphan prevention?
  static final double MARGIN_ADJUST = .97;
  // The font we use for printing page numbers
  static final Font headerFont = new Font("Serif", Font.PLAIN, 12);
  /**
   * This constructor allows printing the contents of any JTextComponent using
   * a default PageFormat
   */
  public PrintableDocument(JTextComponent textComponent) {
    this(textComponent, new PageFormat());
  }
  /**
   * This constructor allows the contents of any JTextComponent to be printed,
   * using any specified PageFormat object
   */
  public PrintableDocument(JTextComponent textComponent, PageFormat format) {
    // Remember the page format, and ask it for the printable area
    this.format = format;
    this.printX = format.getImageableX();
    this.printY = format.getImageableY();
    this.printWidth = format.getImageableWidth();
    this.printHeight = format.getImageableHeight();
    double paperWidth = format.getWidth();
    // Get the document and its root Element from the text component
    Document document = textComponent.getDocument();
    Element rootElement = document.getDefaultRootElement();
    // Get the EditorKit and its ViewFactory from the text component
    EditorKit editorKit = textComponent.getUI().getEditorKit(textComponent);
    ViewFactory viewFactory = editorKit.getViewFactory();
    // Use the ViewFactory to create a root View object for the document
    // This is the object we'll print.
    root = viewFactory.create(rootElement);
    // The Swing text architecture requires us to call setParent() on
    // our root View before we use it for anything. In order to do this,
    // we need a View object that can serve as the parent. We use a
    // custom implementation defined below.
    root.setParent(new ParentView(root, viewFactory, textComponent));
    // Tell the view how wide the page is; it has to format itself
    // to fit within this width. The height doesn't really matter here
    root.setSize((float) printWidth, (float) printHeight);
    // Now that the view has formatted itself for the specified width,
    // Ask it how tall it is.
    double documentHeight = root.getPreferredSpan(View.Y_AXIS);
    // Set up the rectangle that tells the view where to draw itself
    // We'll use it in other methods of this class.
    drawRect = new Rectangle((int) printX, (int) printY, (int) printWidth,
        (int) documentHeight);
    // Now if the document is taller than one page, we have to
    // figure out where the page breaks are.
    if (documentHeight > printHeight)
      paginate(root, drawRect);
    // Once we've broken it into pages, figure out how man pages.
    numPages = pageLengths.size() + 1;
  }
  // This is the starting offset of the page we're currently working on
  double pageStart = 0;
  /**
   * This method loops through the children of the specified view, recursing
   * as necessary, and inserts pages breaks when needed. It makes a
   * rudimentary attempt to avoid "widows" and "orphans".
   */
  protected void paginate(View v, Rectangle2D allocation) {
    // Figure out how tall this view is, and tell it to allocate
    // that space among its children
    double myheight = v.getPreferredSpan(View.Y_AXIS);
    v.setSize((float) printWidth, (float) myheight);
    // Now loop through each of the children
    int numkids = v.getViewCount();
    for (int i = 0; i < numkids; i++) {
      View kid = v.getView(i); // this is the child we're working with
      // Figure out its size and location
      Shape kidshape = v.getChildAllocation(i, allocation);
      if (kidshape == null)
        continue;
      Rectangle2D kidbox = kidshape.getBounds2D();
      // This is the Y coordinate of the bottom of the child
      double kidpos = kidbox.getY() + kidbox.getHeight() - pageStart;
      // If this is the first child of a group, then we want to ensure
      // that it doesn't get left by itself at the bottom of a page.
      // I.e. we want to prevent "widows"
      if ((numkids > 1) && (i == 0)) {
        // If it is not near the end of the page, then just move
        // on to the next child
        if (kidpos < printY + printHeight * MARGIN_ADJUST)
          continue;
        // Otherwise, the child is near the bottom of the page, so
        // break the page before this child and place this child on
        // the new page.
        breakPage(kidbox.getY());
        continue;
      }
      // If this is the last child of a group, we don't want it to
      // appear by itself at the top of a new page, so allow it to
      // squeeze past the bottom margin if necessary. This helps to
      // prevent "orphans"
      if ((numkids > 1) && (i == numkids - 1)) {
        // If it fits normally, just move on to the next one
        if (kidpos < printY + printHeight)
          continue;
        // Otherwise, if it fits with extra space, then break the
        // at the end of the group
        if (kidpos < printY + printHeight / MARGIN_ADJUST) {
          breakPage(allocation.getY() + allocation.getHeight());
          continue;
        }
      }
      // If the child is not the first or last of a group, then we use
      // the bottom margin strictly. If the child fits on the page,
      // then move on to the next child.
      if (kidpos < printY + printHeight)
        continue;
      // If we get here, the child doesn't fit on this page. If it has
      // no children, then break the page before this child and continue.
      if (kid.getViewCount() == 0) {
        breakPage(kidbox.getY());
        continue;
      }
      // If we get here, then the child did not fit on the page, but it
      // has kids of its own, so recurse to see if any of those kids
      // will fit on the page.
      paginate(kid, kidbox);
    }
  }
  // For a document of n pages, this list stores the lengths of pages
  // 0 through n-2. The last page is assumed to have a full length
  ArrayList pageLengths = new ArrayList();
  // For a document of n pages, this list stores the starting offset of
  // pages 1 through n-1. The offset of page 0 is always 0
  ArrayList pageOffsets = new ArrayList();
  /**
   * Break a page at the specified Y coordinate. Store the necessary
   * information into the pageLengths and pageOffsets lists
   */
  void breakPage(double y) {
    double pageLength = y - pageStart - printY;
    pageStart = y - printY;
    pageLengths.add(new Double(pageLength));
    pageOffsets.add(new Double(pageStart));
  }
  /** Return the number of pages. This is a Pageable method. */
  public int getNumberOfPages() {
    return numPages;
  }
  /**
   * Return the PageFormat object for the specified page. This implementation
   * uses the computed length of the page in the returned PageFormat object.
   * The PrinterJob will use this as a clipping region, which will prevent
   * extraneous parts of the document from being drawn in the top and bottom
   * margins.
   */
  public PageFormat getPageFormat(int pagenum) {
    // On the last page, just return the user-specified page format
    if (pagenum == numPages - 1)
      return format;
    // Otherwise, look up the height of this page and return an
    // appropriate PageFormat.
    double pageLength = ((Double) pageLengths.get(pagenum)).doubleValue();
    PageFormat f = (PageFormat) format.clone();
    Paper p = f.getPaper();
    if (f.getOrientation() == PageFormat.PORTRAIT)
      p.setImageableArea(printX, printY, printWidth, pageLength);
    else
      p.setImageableArea(printY, printX, pageLength, printWidth);
    f.setPaper(p);
    return f;
  }
  /**
   * This Printable method returns the Printable object for the specified
   * page. Since this class implements both Pageable and Printable, it just
   * returns this.
   */
  public Printable getPrintable(int pagenum) {
    return this;
  }
  /**
   * This is the basic Printable method that prints a specified page
   */
  public int print(Graphics g, PageFormat format, int pageIndex) {
    // Return an error code on attempts to print past the end of the doc
    if (pageIndex >= numPages)
      return NO_SUCH_PAGE;
    // Cast the Graphics object so we can use Java2D operations
    Graphics2D g2 = (Graphics2D) g;
    // Display a page number centered in the area of the top margin.
    // Set a new clipping region so we can draw into the top margin
    // But remember the original clipping region so we can restore it
    Shape originalClip = g.getClip();
    g.setClip(new Rectangle(0, 0, (int) printWidth, (int) printY));
    // Compute the header to display, measure it, then display it
    String numString = "- " + (pageIndex + 1) + " -";
    Rectangle2D numBounds = // Get the width and height of the string
    headerFont.getStringBounds(numString, g2.getFontRenderContext());
    LineMetrics metrics = // Get the ascent and descent of the font
    headerFont.getLineMetrics(numString, g2.getFontRenderContext());
    g.setFont(headerFont); // Set the font
    g.setColor(Color.black); // Print with black ink
    g.drawString(numString, // Display the string
        (int) (printX + (printWidth - numBounds.getWidth()) / 2),
        (int) ((printY - numBounds.getHeight()) / 2 + metrics
            .getAscent()));
    g.setClip(originalClip); // Restore the clipping region
    // Figure out the staring position of the page within the document
    double pageStart = 0.0;
    if (pageIndex > 0)
      pageStart = ((Double) pageOffsets.get(pageIndex - 1)).doubleValue();
    // Scroll so that the appropriate part of the document is lined up
    // with the upper-left corner of the page
    g2.translate(0.0, -pageStart);
    // Now paint the entire document. The PrinterJob will have
    // established a clipping region, so that only the desired portion
    // of the document will actually be drawn on this sheet of paper.
    root.paint(g, drawRect);
    // Finally return a success code
    return PAGE_EXISTS;
  }
  /**
   * This inner class is a concrete implementation of View, with a couple of
   * key method implementations. An instance of this class is used as the
   * parent of the root View object we want to print
   */
  static class ParentView extends View {
    ViewFactory viewFactory; // The ViewFactory for the hierarchy of views
    Container container; // The Container for the hierarchy of views
    public ParentView(View v, ViewFactory viewFactory, Container container) {
      super(v.getElement());
      this.viewFactory = viewFactory;
      this.container = container;
    }
    // These methods return key pieces of information required by
    // the View hierarchy.
    public ViewFactory getViewFactory() {
      return viewFactory;
    }
    public Container getContainer() {
      return container;
    }
    // These methods are abstract in View, so we've got to provide
    // dummy implementations of them here, even though they're never used.
    public void paint(Graphics g, Shape allocation) {
    }
    public float getPreferredSpan(int axis) {
      return 0.0f;
    }
    public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
      return 0;
    }
    public Shape modelToView(int pos, Shape a, Position.Bias b)
        throws BadLocationException {
      return a;
    }
  }
}