//******************************************************************************
//
// File:    Drawing.java
// Package: edu.rit.draw
// Unit:    Class edu.rit.draw.Drawing
//
// This Java source file is copyright (C) 2008 by Alan Kaminsky. All rights
// reserved. For further information, contact the author, Alan Kaminsky, at
// ark@cs.rit.edu.
//
// This Java source file is part of the Parallel Java Library ("PJ"). PJ is free
// software; you can redistribute it and/or modify it under the terms of the GNU
// General Public License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// PJ is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// A copy of the GNU General Public License is provided in the file gpl.txt. You
// may also obtain a copy of the GNU General Public License on the World Wide
// Web at http://www.gnu.org/licenses/gpl.html.
//
//******************************************************************************

package edu.rit.draw;

import edu.rit.draw.item.ColorFill;
import edu.rit.draw.item.DrawingItem;
import edu.rit.draw.item.Group;
import edu.rit.draw.item.Point;
import edu.rit.draw.item.Size;

import edu.rit.swing.Displayable;
import edu.rit.swing.DisplayableFrame;
import edu.rit.swing.Viewable;

import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.Stroke;

import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;

import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

import java.util.LinkedList;

/**
 * Class Drawing provides a drawing. A drawing consists of a sequence of
 * {@linkplain DrawingItem}s, which are displayed in order. Thus, a drawing item
 * later in the sequence will obscure a drawing item earlier in the sequence if
 * the items overlap.
 * 

* A drawing contains a {@linkplain ColorFill} object that tells how to color * the drawing's background. *

* A drawing contains the size of the rectangular region within which the * drawing items are to be displayed. By default, the display region is just * large enough to contain all the drawing items, plus a border. Methods are * provided to specify a different display region if desired. *

* An instance of class Drawing may be serialized. Class Drawing provides static * convenience methods to write a drawing to a file and read a drawing from a * file. A drawing file is simply a binary file that contains a serialized * drawing object. The {@linkplain View} program can be used to display the * drawing file. *

* There is a default drawing. Initially, the default drawing is a new drawing * object. The default drawing may be changed, or set to null to signify that * there is no default drawing. A {@linkplain DrawingItem}'s add() and * addFirst() methods add the drawing item to the default drawing (if * there is one). * * @author Alan Kaminsky * @version 18-Jul-2008 */ public class Drawing implements Externalizable, Viewable { // Exported enumerations. /** * Enumeration Drawing.Alignment specifies how a {@linkplain Drawing}'s * items are to be aligned within the drawing's display region. * * @author Alan Kaminsky * @version 10-Jul-2006 */ public enum Alignment { /** * No alignment. Items are drawn at their own locations and are not * aligned to the display region. */ NONE, /** * Align the northwest corner of the drawing items to the northwest * corner point of the display region, inset by the border. */ NORTHWEST, /** * Align the north middle of the drawing items to the north middle point * of the display region, inset by the border. */ NORTH, /** * Align the northeast corner of the drawing items to the northeast * corner point of the display region, inset by the border. */ NORTHEAST, /** * Align the west middle of the drawing items to the west middle point * of the display region, inset by the border. */ WEST, /** * Align the center of the drawing items to the center point of the * display region, inset by the border. */ CENTER, /** * Align the east middle of the drawing items to the east middle point * of the display region, inset by the border. */ EAST, /** * Align the southwest corner of the drawing items to the southwest * corner point of the display region, inset by the border. */ SOUTHWEST, /** * Align the south middle of the drawing items to the south middle point * of the display region, inset by the border. */ SOUTH, /** * Align the southeast corner of the drawing items to the southeast * corner point of the display region, inset by the border. */ SOUTHEAST, } // Exported constants. /** * Signifies that the drawing's display region's size should be determined * automatically based on the drawing items in the drawing. A {@linkplain * Size} of (0,0) is used for this purpose. */ public static final Size AUTOMATIC_SIZE = new Size (0.0, 0.0); /** * The normal display region size (automatic). */ public static final Size NORMAL_SIZE = AUTOMATIC_SIZE; /** * The normal display region border width (2). */ public static final double NORMAL_BORDER = 2.0; /** * The normal display region/drawing item alignment (northwest). */ public static final Alignment NORMAL_ALIGNMENT = Alignment.NORTHWEST; /** * The normal background color (white). */ public static final ColorFill NORMAL_BACKGROUND = ColorFill.WHITE; // Hidden data members. private static final long serialVersionUID = -3240330399842161140L; private LinkedList myItems = new LinkedList(); private Size mySize = NORMAL_SIZE; private double myBorder = NORMAL_BORDER; private Alignment myAlignment = NORMAL_ALIGNMENT; private ColorFill myBackground = NORMAL_BACKGROUND; private Point myItemsNW; private Point myItemsSE; private String myTitle; private static Drawing theDefaultDrawing = new Drawing(); // Exported constructors. /** * Construct a new drawing. The drawing contains no drawing items. The * normal size (automatic), normal border (2), normal alignment (northwest), * and normal background color (white) are used. */ public Drawing() { } // Exported operations. /** * Returns the default drawing. * * @return Default drawing, or null if there is no default drawing. */ public static Drawing defaultDrawing() { return theDefaultDrawing; } /** * Set the default drawing. * * @param theDrawing Default drawing, or null if there is no default * drawing. */ public static void defaultDrawing (Drawing theDrawing) { theDefaultDrawing = theDrawing; } /** * Clear this drawing's sequence of drawing items. */ public void clear() { myItems.clear(); } /** * Add the given drawing item to the end of this drawing's sequence of * drawing items. * * @param theItem Drawing item. * * @exception NullPointerException * (unchecked exception) Thrown if theItem is null. */ public void add (DrawingItem theItem) { if (theItem == null) throw new NullPointerException(); myItems.add (theItem); } /** * Add the given drawing item to the beginning of this drawing's sequence of * drawing items. * * @param theItem Drawing item. * * @exception NullPointerException * (unchecked exception) Thrown if theItem is null. */ public void addFirst (DrawingItem theItem) { if (theItem == null) throw new NullPointerException(); myItems.addFirst (theItem); } /** * Returns this drawing's display region's size. This includes the border if * any. If the return value is equal to Drawing.AUTOMATIC_SIZE * (0,0), it signifies that the drawing's display region's size should be * determined automatically based on the drawing items in the drawing. * * @return Display region size. */ public Size size() { return mySize; } /** * Set this drawing's display region's size. This includes the border if * any. If theSize is equal to Drawing.AUTOMATIC_SIZE * (0,0), or if either the width or the height of theSize is * negative, it signifies that the drawing's display region's size should be * determined automatically based on the drawing items in the drawing. * * @param theSize Display region size. * * @return This drawing object. * * @exception NullPointerException * (unchecked exception) Thrown if theSize is null. */ public Drawing size (Size theSize) { if (theSize.width() <= 0.0 || theSize.height() <= 0.0) { mySize = AUTOMATIC_SIZE; } else { mySize = theSize; } return this; } /** * Returns this drawing's display region's border. * * @return Display region border width. */ public double border() { return myBorder; } /** * Set this drawing's display region's border. * * @param theBorder Display region border width. * * @return This drawing object. * * @exception IllegalArgumentException * (unchecked exception) Thrown if theBorder is less than 0. */ public Drawing border (double theBorder) { if (theBorder < 0.0) throw new IllegalArgumentException(); myBorder = theBorder; return this; } /** * Returns this drawing's alignment. The alignment determines where this * drawing's drawing items are displayed relative to this drawing's display * region. * * @return Alignment. */ public Alignment alignment() { return myAlignment; } /** * Set this drawing's alignment. The alignment determines where this * drawing's drawing items are displayed relative to this drawing's display * region. * * @param theAlignment Alignment. * * @return This drawing object. * * @exception NullPointerException * (unchecked exception) Thrown if theAlignment is null. */ public Drawing alignment (Alignment theAlignment) { if (theAlignment == null) throw new NullPointerException(); myAlignment = theAlignment; return this; } /** * Returns this drawing's background color fill. * * @return Background color fill. */ public ColorFill background() { return myBackground; } /** * Set this drawing's background color fill. * * @param theBackground Background color fill. * * @return This drawing object. * * @exception NullPointerException * (unchecked exception) Thrown if theBackground is null. */ public Drawing background (ColorFill theBackground) { if (theBackground == null) throw new NullPointerException(); myBackground = theBackground; return this; } /** * Returns a {@linkplain edu.rit.draw.item.Group Group} consisting of the * drawing items in this drawing. In this way, one drawing can be embedded * (as a Grouip) inside another drawing. * * @return Group. */ public Group asGroup() { Group group = new Group(); for (DrawingItem item : myItems) { group.append (item); } return group; } /** * Write this drawing to the given object output stream. * * @param out Object output stream. * * @exception IOException * Thrown if an I/O error occurred. */ public void writeExternal (ObjectOutput out) throws IOException { out.writeInt (myItems.size()); for (DrawingItem item : myItems) { out.writeObject (item); } out.writeObject (mySize); out.writeDouble (myBorder); out.writeObject (myAlignment); out.writeObject (myBackground); out.writeObject (myTitle); } /** * Read this drawing from the given object input stream. * * @param in Object input stream. * * @exception IOException * Thrown if an I/O error occurred. * @exception ClassNotFoundException * Thrown if a class needed to deserialize this drawing cannot be found. */ public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException { int n = in.readInt(); myItems.clear(); for (int i = 0; i < n; ++ i) { myItems.add ((DrawingItem) in.readObject()); } mySize = (Size) in.readObject(); myBorder = in.readDouble(); myAlignment = (Alignment) in.readObject(); myBackground = (ColorFill) in.readObject(); myTitle = (String) in.readObject(); } /** * Write the default drawing to the file with the given name. The default * drawing is written in serialized form to the file. If there is no default * drawing, the write() method does nothing. * * @param theFileName File name. * * @exception IOException * Thrown if an I/O error occurred. */ public static void write (String theFileName) throws IOException { if (theDefaultDrawing != null) { write (theDefaultDrawing, new File (theFileName)); } } /** * Write the default drawing to the given file. The default drawing is * written in serialized form to theFile. If there is no default * drawing, the write() method does nothing. * * @param theFile File. * * @exception IOException * Thrown if an I/O error occurred. */ public static void write (File theFile) throws IOException { if (theDefaultDrawing != null) { write (theDefaultDrawing, theFile); } } /** * Write the given drawing to the file with the given name. * theDrawing is written in serialized form to the file. * * @param theDrawing Drawing. * @param theFileName File name. * * @exception IOException * Thrown if an I/O error occurred. */ public static void write (Drawing theDrawing, String theFileName) throws IOException { write (theDrawing, new File (theFileName)); } /** * Write the given drawing to the given file. theDrawing is written * in serialized form to theFile. * * @param theDrawing Drawing. * @param theFile File. * * @exception IOException * Thrown if an I/O error occurred. */ public static void write (Drawing theDrawing, File theFile) throws IOException { FileOutputStream fos = null; ObjectOutputStream oos = null; try { fos = new FileOutputStream (theFile); oos = new ObjectOutputStream (fos); oos.writeObject (theDrawing); oos.close(); } catch (IOException exc) { if (fos != null) { try { fos.close(); } catch (IOException exc2) {} } throw exc; } } /** * Read a drawing from the file with the given name. The file must contain * one instance of class Drawing in serialized form; for example, as written * by the static Drawing.write() method. * * @param theFileName File name. * * @return Drawing. * * @exception IOException * Thrown if an I/O error occurred. * @exception ClassNotFoundException * Thrown if any class needed to deserialize the drawing cannot be * found. */ public static Drawing read (String theFileName) throws IOException, ClassNotFoundException { return read (new File (theFileName)); } /** * Read a drawing from the given file. The file must contain one instance of * class Drawing in serialized form; for example, as written by the static * Drawing.write() method. * * @param theFile File. * * @return Drawing. * * @exception IOException * Thrown if an I/O error occurred. * @exception ClassNotFoundException * Thrown if any class needed to deserialize the drawing cannot be * found. */ public static Drawing read (File theFile) throws IOException, ClassNotFoundException { FileInputStream fis = null; ObjectInputStream ois = null; Drawing result = null; try { fis = new FileInputStream (theFile); ois = new ObjectInputStream (fis); result = (Drawing) ois.readObject(); ois.close(); return result; } catch (IOException exc) { if (fis != null) { try { fis.close(); } catch (IOException exc2) {} } throw exc; } } // Exported operations implemented from interface Drawable. /** * Draw this drawable object in the given graphics context. Upon return from * this method, the given graphics context's state (color, font, transform, * clip, and so on) is the same as it was upon entry to this method. * * @param g2d 2-D graphics context. */ public void draw (Graphics2D g2d) { // Compute translation needed to put the drawing items at the right // spot. computeItemsNWSE(); Size itemSize = myItemsSE.difference (myItemsNW); Size displaySize = mySize.equals (AUTOMATIC_SIZE) ? itemSize.add (2.0 * myBorder) : mySize; double dwidth = displaySize.width() - itemSize.width(); double dheight = displaySize.height() - itemSize.height(); double dx = 0.0; double dy = 0.0; switch (myAlignment) { case NONE: break; case NORTHWEST: dx = myBorder - myItemsNW.x(); dy = myBorder - myItemsNW.y(); break; case NORTH: dx = 0.5 * dwidth - myItemsNW.x(); dy = myBorder - myItemsNW.y(); break; case NORTHEAST: dx = dwidth - myBorder - myItemsNW.x(); dy = myBorder - myItemsNW.y(); break; case WEST: dx = myBorder - myItemsNW.x(); dy = 0.5 * dheight - myItemsNW.y(); break; case CENTER: dx = 0.5 * dwidth - myItemsNW.x(); dy = 0.5 * dheight - myItemsNW.y(); break; case EAST: dx = dwidth - myBorder - myItemsNW.x(); dy = 0.5 * dheight - myItemsNW.y(); break; case SOUTHWEST: dx = myBorder - myItemsNW.x(); dy = dheight - myBorder - myItemsNW.y(); break; case SOUTH: dx = 0.5 * dwidth - myItemsNW.x(); dy = dheight - myBorder - myItemsNW.y(); break; case SOUTHEAST: dx = dwidth - myBorder - myItemsNW.x(); dy = dheight - myBorder - myItemsNW.y(); break; } // Save graphics context's state. Stroke oldStroke = g2d.getStroke(); Paint oldPaint = g2d.getPaint(); AffineTransform oldTransform = g2d.getTransform(); Object oldAntialiasing = g2d.getRenderingHint (RenderingHints.KEY_ANTIALIASING); Object oldTextAntialiasing = g2d.getRenderingHint (RenderingHints.KEY_TEXT_ANTIALIASING); Object oldFractionalMetrics = g2d.getRenderingHint (RenderingHints.KEY_FRACTIONALMETRICS); // Turn on antialiasing. g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint (RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2d.setRenderingHint (RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); // Iterate over all drawing items. for (DrawingItem item : myItems) { // Restore graphics context. g2d.setStroke (oldStroke); g2d.setPaint (oldPaint); g2d.setTransform (oldTransform); g2d.translate (dx, dy); // Draw item. item.draw (g2d); } // Restore graphics context's state. g2d.setStroke (oldStroke); g2d.setPaint (oldPaint); g2d.setTransform (oldTransform); g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING, oldAntialiasing); g2d.setRenderingHint (RenderingHints.KEY_TEXT_ANTIALIASING, oldTextAntialiasing); g2d.setRenderingHint (RenderingHints.KEY_FRACTIONALMETRICS, oldFractionalMetrics); } // Exported operations implemented from interface Displayable. /** * Returns this displayable object's bounding box. This is the smallest * rectangle that encloses all of this displayable object. */ public Rectangle2D getBoundingBox() { computeItemsNWSE(); Size displaySize = mySize.equals (AUTOMATIC_SIZE) ? myItemsSE.difference (myItemsNW) .add (2.0 * myBorder) : mySize; return new Rectangle2D.Double (0.0, 0.0, displaySize.width(), displaySize.height()); } /** * Returns this displayable object's background paint. */ public Paint getBackgroundPaint() { return myBackground.color(); } // Exported operations implemented from interface Viewable. /** * Get a displayable frame in which to view this viewable object. Initially, * the returned frame is displaying this viewable object. * * @return Displayable frame. */ public DisplayableFrame getFrame() { return new DrawingFrame (getTitle(), this); } /** * Get the title for the frame used to view this viewable object. If the * title is null, a default title is used. * * @return Title. */ public String getTitle() { return myTitle; } /** * Set the title for the frame used to view this viewable object. If the * title is null, a default title is used. * * @param theTitle Title. */ public void setTitle (String theTitle) { myTitle = theTitle; } // Hidden operations. /** * Compute the furthest northwest and furthest southeast points of all this * drawing's drawing items. Store the results in myItemsNW and myItemsSE. */ private void computeItemsNWSE() { Rectangle2D bbox = new Rectangle2D.Double(); for (DrawingItem item : myItems) { bbox = bbox.createUnion (item.boundingBox()); } myItemsNW = new Point (bbox.getX(), bbox.getY()); myItemsSE = new Point (bbox.getX()+bbox.getWidth(), bbox.getY()+bbox.getHeight()); } }

Ads help maintain this website.