/* * 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. */ package com.davidflanagan.examples.datatransfer; import java.awt.*; import java.awt.geom.*; import java.awt.datatransfer.*; import java.io.Serializable; import java.util.StringTokenizer; /** * This class represents a scribble composed of any number of "polylines". * Each "polyline" is set of connected line segments. A scribble is created * through a series of calls to the moveto() and lineto() methods. moveto() * specifies the starting point of a new polyline, and lineto() adds a new * point to the end of the current polyline(). * * This class implements the Shape interface which means that it can be drawn * using the Java2D graphics API * * It also implements the Transferable interface, which means that it can * easily be used with cut-and-paste and drag-and-drop. It defines a custom * DataFlavor, scribbleDataFlavor, which transfers Scribble objects as Java * objects. However, it also supports cut-and-paste and drag-and-drop based * on a portable string representation of the scribble. The toString() * and parse() methods write and read this string format **/ public class Scribble implements Shape, Transferable, Serializable, Cloneable { protected double[] points = new double[64]; // The scribble data protected int numPoints = 0; // The current number of points double maxX = Double.NEGATIVE_INFINITY; // The bounding box double maxY = Double.NEGATIVE_INFINITY; double minX = Double.POSITIVE_INFINITY; double minY = Double.POSITIVE_INFINITY; /** * Begin a new polyline at (x,y). Note the use of Double.NaN in the * points array to mark the beginning of a new polyline **/ public void moveto(double x, double y) { if (numPoints + 3 > points.length) reallocate(); // Mark this as the beginning of a new line points[numPoints++] = Double.NaN; // The rest of this method is just like lineto(); lineto(x, y); } /** * Add the point (x,y) to the end of the current polyline **/ public void lineto(double x, double y) { if (numPoints + 2 > points.length) reallocate(); points[numPoints++] = x; points[numPoints++] = y; // See if the point enlarges our bounding box if (x > maxX) maxX = x; if (x < minX) minX = x; if (y > maxY) maxY = y; if (y < minY) minY = y; } /** * Append the Scribble s to this Scribble **/ public void append(Scribble s) { int n = numPoints + s.numPoints; double[] newpoints = new double[n]; System.arraycopy(points, 0, newpoints, 0, numPoints); System.arraycopy(s.points, 0, newpoints, numPoints, s.numPoints); points = newpoints; numPoints = n; minX = Math.min(minX, s.minX); maxX = Math.max(maxX, s.maxX); minY = Math.min(minY, s.minY); maxY = Math.max(maxY, s.maxY); } /** * Translate the coordinates of all points in the Scribble by x,y **/ public void translate(double x, double y) { for(int i = 0; i < numPoints; i++) { if (Double.isNaN(points[i])) continue; points[i++] += x; points[i] += y; } minX += x; maxX += x; minY += y; maxY += y; } /** An internal method to make more room in the data array */ protected void reallocate() { double[] newpoints = new double[points.length * 2]; System.arraycopy(points, 0, newpoints, 0, numPoints); points = newpoints; } /** Clone a Scribble object and its internal array of data */ public Object clone() { try { Scribble s = (Scribble) super.clone(); // make a copy of all fields s.points = (double[]) points.clone(); // copy the entire array return s; } catch (CloneNotSupportedException e) { // This should never happen return this; } } /** Convert the scribble data to a textual format */ public String toString() { StringBuffer b = new StringBuffer(); for(int i = 0; i < numPoints; i++) { if (Double.isNaN(points[i])) { b.append("m "); } else { b.append(points[i]); b.append(' '); } } return b.toString(); } /** * Create a new Scribble object and initialize it by parsing a string of * coordinate data in the format produced by toString() **/ public static Scribble parse(String s) throws NumberFormatException { StringTokenizer st = new StringTokenizer(s); Scribble scribble = new Scribble(); while(st.hasMoreTokens()) { String t = st.nextToken(); if (t.charAt(0) == 'm') { scribble.moveto(Double.parseDouble(st.nextToken()), Double.parseDouble(st.nextToken())); } else { scribble.lineto(Double.parseDouble(t), Double.parseDouble(st.nextToken())); } } return scribble; } // ========= The following methods implement the Shape interface ======== /** Return the bounding box of the Shape */ public Rectangle getBounds() { return new Rectangle((int)(minX-0.5f), (int)(minY-0.5f), (int)(maxX-minX+0.5f), (int)(maxY-minY+0.5f)); } /** Return the bounding box of the Shape */ public Rectangle2D getBounds2D() { return new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY); } /** Our shape is an open curve, so it never contains anything */ public boolean contains(Point2D p) { return false; } public boolean contains(Rectangle2D r) { return false; } public boolean contains(double x, double y) { return false; } public boolean contains(double x, double y, double w, double h) { return false; } /** * Determine if the scribble intersects the specified rectangle by testing * each line segment individually **/ public boolean intersects(Rectangle2D r) { if (numPoints < 4) return false; int i = 0; double x1, y1, x2 = 0.0, y2 = 0.0; while(i < numPoints) { if (Double.isNaN(points[i])) { // If we're beginning a new line i++; // Skip the NaN x2 = points[i++]; y2 = points[i++]; } else { x1 = x2; y1 = y2; x2 = points[i++]; y2 = points[i++]; if (r.intersectsLine(x1, y1, x2, y2)) return true; } } return false; } /** Test for intersection by invoking the method above */ public boolean intersects(double x, double y, double w, double h){ return intersects(new Rectangle2D.Double(x,y,w,h)); } /** * Return a PathIterator object that tells Java2D how to draw this scribble **/ public PathIterator getPathIterator(AffineTransform at) { return new ScribbleIterator(at); } /** * Return a PathIterator that doesn't include curves. Ours never does. **/ public PathIterator getPathIterator(AffineTransform at, double flatness) { return getPathIterator(at); } /** * This inner class implements the PathIterator interface to describe * the shape of a scribble. Since a Scribble is composed of arbitrary * movetos and linetos, we simply return their coordinates **/ public class ScribbleIterator implements PathIterator { protected int i = 0; // Position in array protected AffineTransform transform; public ScribbleIterator(AffineTransform transform) { this.transform = transform; } /** How to determine insideness and outsideness for this shape */ public int getWindingRule() { return PathIterator.WIND_NON_ZERO; } /** Have we reached the end of the scribble path yet? */ public boolean isDone() { return i >= numPoints; } /** Move on to the next segment of the path */ public void next() { if (Double.isNaN(points[i])) i += 3; else i += 2; } /** * Get the coordinates of the current moveto or lineto as floats **/ public int currentSegment(float[] coords) { int retval; if (Double.isNaN(points[i])) { // If its a moveto coords[0] = (float)points[i+1]; coords[1] = (float)points[i+2]; retval = SEG_MOVETO; } else { coords[0] = (float)points[i]; coords[1] = (float)points[i+1]; retval = SEG_LINETO; } // If a transform was specified, use it on the coordinates if (transform != null) transform.transform(coords, 0, coords, 0,1); return retval; } /** * Get the coordinates of the current moveto or lineto as doubles **/ public int currentSegment(double[] coords) { int retval; if (Double.isNaN(points[i])) { coords[0] = points[i+1]; coords[1] = points[i+2]; retval = SEG_MOVETO; } else { coords[0] = points[i]; coords[1] = points[i+1]; retval = SEG_LINETO; } if (transform != null) transform.transform(coords, 0, coords, 0,1); return retval; } } //====== The following methods implement the Transferable interface ===== // This is the custom DataFlavor for Scribble objects public static DataFlavor scribbleDataFlavor = new DataFlavor(Scribble.class, "Scribble"); // This is a list of the flavors we know how to work with public static DataFlavor[] supportedFlavors = { scribbleDataFlavor, DataFlavor.stringFlavor }; /** Return the data formats or "flavors" we know how to transfer */ public DataFlavor[] getTransferDataFlavors() { return (DataFlavor[]) supportedFlavors.clone(); } /** Check whether we support a given flavor */ public boolean isDataFlavorSupported(DataFlavor flavor) { return (flavor.equals(scribbleDataFlavor) || flavor.equals(DataFlavor.stringFlavor)); } /** * Return the scribble data in the requested format, or throw an exception * if we don't support the requested format **/ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { if (flavor.equals(scribbleDataFlavor)) { return this; } else if (flavor.equals(DataFlavor.stringFlavor)) { return toString(); } else throw new UnsupportedFlavorException(flavor); } }