/* * Copyright (c) 2002-@year@, University of Maryland * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions * and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions * and the following disclaimer in the documentation and/or other materials provided with the * distribution. * * Neither the name of the University of Maryland nor the names of its contributors may be used to * endorse or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Piccolo was written at the Human-Computer Interaction Laboratory www.cs.umd.edu/hcil by Jesse Grosjean * under the supervision of Ben Bederson. The Piccolo website is www.cs.umd.edu/hcil/piccolo. */ package edu.umd.cs.piccolo.util; import java.awt.geom.Dimension2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; /** * <b>PBounds</b> is simply a Rectangle2D.Double with extra methods that more * properly deal with the case when the rectangle is "empty". A PBounds * has an extra bit to store emptiness. In this state, adding new geometry * replaces the current geometry. A PBounds is emptied with the reset() method. * A useful side effect of the reset method is that it only modifies the fIsEmpty * variable, the other x, y, with, height variables are left alone. This is used * by Piccolo's layout management system to see if a the full bounds of a node * has really changed when it is recomputed. See PNode.validateLayout. * <P> * @version 1.0 * @author Jesse Grosjean */ public class PBounds extends Rectangle2D.Double implements Serializable { private boolean isEmpty = true; public PBounds() { super(); } public PBounds(PBounds aBounds) { this(aBounds.x, aBounds.y, aBounds.width, aBounds.height); isEmpty = aBounds.isEmpty(); } public PBounds(Rectangle2D aBounds) { this(aBounds.getX(), aBounds.getY(), aBounds.getWidth(), aBounds.getHeight()); isEmpty = aBounds.isEmpty(); } public PBounds(Point2D aCenterPoint, double insetX, double insetY) { this(aCenterPoint.getX(), aCenterPoint.getY(), 0, 0); inset(insetX, insetY); } public PBounds(double x, double y, double width, double height) { super(x, y, width, height); isEmpty = false; } public Object clone() { return new PBounds(this); } public boolean isEmpty() { return isEmpty; } public PBounds reset() { isEmpty = true; return this; } public PBounds resetToZero() { x = 0; y = 0; width = 0; height = 0; isEmpty = true; return this; } public void setRect(Rectangle2D r) { super.setRect(r); isEmpty = false; } public void setRect(PBounds b) { isEmpty = b.isEmpty; x = b.x; y = b.y; width = b.width; height = b.height; } public void setRect(double x, double y, double w, double h) { this.x = x; this.y = y; this.width = w; this.height = h; isEmpty = false; } public void add(double newx, double newy) { if (isEmpty) { setRect(newx, newy, 0, 0); isEmpty = false; } else { super.add(newx, newy); } } public void add(Rectangle2D r) { if (isEmpty) { setRect(r); } else { super.add(r); } } // optimized add when adding two PBounds together. public void add(PBounds r) { if (r.isEmpty) { return; } else if (isEmpty) { x = r.x; y = r.y; width = r.width; height = r.height; isEmpty = false; } else { double x1 = (x <= r.x) ? x : r.x; double y1 = (y <= r.y) ? y : r.y; double x2 = ((x + width) >= (r.x + r.width)) ? (x + width) : (r.x + r.width); double y2 = ((y + height) >= (r.y + r.height)) ? (y + height) : (r.y + r.height); x = x1; y = y1; width = x2 - x1; height = y2 - y1; isEmpty = false; } } public Point2D getOrigin() { return new Point2D.Double(x, y); } public PBounds setOrigin(double x, double y) { this.x = x; this.y = y; isEmpty = false; return this; } public Dimension2D getSize() { return new PDimension(width, height); } public void setSize(double width, double height) { setRect(x, y, width, height); } public Point2D getCenter2D() { return new Point2D.Double(getCenterX(), getCenterY()); } public PBounds moveBy(double dx, double dy) { setOrigin(x + dx, y + dy); return this; } public void expandNearestIntegerDimensions() { x = Math.floor(x); y = Math.floor(y); width = Math.ceil(width); height = Math.ceil(height); } public PBounds inset(double dx, double dy) { setRect(x + dx, y + dy, width - (dx*2), height - (dy*2)); return this; } public PDimension deltaRequiredToCenter(Rectangle2D b) { PDimension result = new PDimension(); double xDelta = getCenterX() - b.getCenterX(); double yDelta = getCenterY() - b.getCenterY(); result.setSize(xDelta, yDelta); return result; } public PDimension deltaRequiredToContain(Rectangle2D b) { PDimension result = new PDimension(); if (!contains(b)) { double bMaxX = b.getMaxX(); double bMinX = b.getMinX(); double bMaxY = b.getMaxY(); double bMinY = b.getMinY(); double maxX = getMaxX(); double minX = getMinX(); double maxY = getMaxY(); double minY = getMinY(); if (!(bMaxX > maxX && bMinX < minX)) { if (bMaxX > maxX || bMinX < minX) { double difMaxX = bMaxX - maxX; double difMinX = bMinX - minX; if (Math.abs(difMaxX) < Math.abs(difMinX)) { result.width = difMaxX; } else { result.width = difMinX; } } } if (!(bMaxY > maxY && bMinY < minY)) { if (bMaxY > maxY || bMinY < minY) { double difMaxY = bMaxY - maxY; double difMinY = bMinY - minY; if (Math.abs(difMaxY) < Math.abs(difMinY)) { result.height = difMaxY; } else { result.height = difMinY; } } } } return result; } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeDouble(x); out.writeDouble(y); out.writeDouble(width); out.writeDouble(height); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); x = in.readDouble(); y = in.readDouble(); width = in.readDouble(); height = in.readDouble(); } public String toString() { StringBuffer result = new StringBuffer(); result.append(getClass().getName().replaceAll(".*\\.", "")); result.append('['); if (isEmpty) { result.append("EMPTY"); } else { result.append("x="); result.append(x); result.append(",y="); result.append(y); result.append(",width="); result.append(width); result.append(",height="); result.append(height); } result.append(']'); return result.toString(); } }