/* =========================================================== * JFreeChart : a free chart library for the Java(tm) platform * =========================================================== * * (C) Copyright 2000-2014, by Object Refinery Limited and Contributors. * * Project Info: http://www.jfree.org/jfreechart/index.html * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners.] * * ------------------ * AbstractBlock.java * ------------------ * (C) Copyright 2004-2014, by Object Refinery Limited. * * Original Author: David Gilbert (for Object Refinery Limited); * Contributor(s): -; * * Changes: * -------- * 22-Oct-2004 : Version 1 (DG); * 02-Feb-2005 : Added accessor methods for margin (DG); * 04-Feb-2005 : Added equals() method and implemented Serializable (DG); * 03-May-2005 : Added null argument checks (DG); * 06-May-2005 : Added convenience methods for setting margin, border and * padding (DG); * ------------- JFREECHART 1.0.x --------------------------------------------- * 16-Mar-2007 : Changed border from BlockBorder to BlockFrame, updated * equals(), and implemented Cloneable (DG); * 15-Jun-2012 : Removed JCommon dependencies (DG); * */ package org.jfree.chart.block; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import org.jfree.chart.ui.RectangleInsets; import org.jfree.chart.ui.Size2D; import org.jfree.chart.util.ObjectUtils; import org.jfree.chart.util.ParamChecks; import org.jfree.chart.util.PublicCloneable; import org.jfree.chart.util.ShapeUtils; import org.jfree.chart.util.SerialUtils; import org.jfree.data.Range; /** * A convenience class for creating new classes that implement * the {@link Block} interface. */ public class AbstractBlock implements Cloneable, Serializable { /** For serialization. */ private static final long serialVersionUID = 7689852412141274563L; /** The id for the block. */ private String id; /** The margin around the outside of the block. */ private RectangleInsets margin; /** The frame (or border) for the block. */ private BlockFrame frame; /** The padding between the block content and the border. */ private RectangleInsets padding; /** * The natural width of the block (may be overridden if there are * constraints in sizing). */ private double width; /** * The natural height of the block (may be overridden if there are * constraints in sizing). */ private double height; /** * The current bounds for the block (position of the block in Java2D space). */ private transient Rectangle2D bounds; /** * Creates a new block. */ protected AbstractBlock() { this.id = null; this.width = 0.0; this.height = 0.0; this.bounds = new Rectangle2D.Float(); this.margin = RectangleInsets.ZERO_INSETS; this.frame = BlockBorder.NONE; this.padding = RectangleInsets.ZERO_INSETS; } /** * Returns the id. * * @return The id (possibly <code>null</code>). * * @see #setID(String) */ public String getID() { return this.id; } /** * Sets the id for the block. * * @param id the id (<code>null</code> permitted). * * @see #getID() */ public void setID(String id) { this.id = id; } /** * Returns the natural width of the block, if this is known in advance. * The actual width of the block may be overridden if layout constraints * make this necessary. * * @return The width. * * @see #setWidth(double) */ public double getWidth() { return this.width; } /** * Sets the natural width of the block, if this is known in advance. * * @param width the width (in Java2D units) * * @see #getWidth() */ public void setWidth(double width) { this.width = width; } /** * Returns the natural height of the block, if this is known in advance. * The actual height of the block may be overridden if layout constraints * make this necessary. * * @return The height. * * @see #setHeight(double) */ public double getHeight() { return this.height; } /** * Sets the natural width of the block, if this is known in advance. * * @param height the width (in Java2D units) * * @see #getHeight() */ public void setHeight(double height) { this.height = height; } /** * Returns the margin. * * @return The margin (never <code>null</code>). * * @see #getMargin() */ public RectangleInsets getMargin() { return this.margin; } /** * Sets the margin (use {@link RectangleInsets#ZERO_INSETS} for no * padding). * * @param margin the margin (<code>null</code> not permitted). * * @see #getMargin() */ public void setMargin(RectangleInsets margin) { ParamChecks.nullNotPermitted(margin, "margin"); this.margin = margin; } /** * Sets the margin. * * @param top the top margin. * @param left the left margin. * @param bottom the bottom margin. * @param right the right margin. * * @see #getMargin() */ public void setMargin(double top, double left, double bottom, double right) { setMargin(new RectangleInsets(top, left, bottom, right)); } /** * Sets a black border with the specified line widths. * * @param top the top border line width. * @param left the left border line width. * @param bottom the bottom border line width. * @param right the right border line width. */ public void setBorder(double top, double left, double bottom, double right) { setFrame(new BlockBorder(top, left, bottom, right)); } /** * Returns the current frame (border). * * @return The frame. * * @since 1.0.5 * @see #setFrame(BlockFrame) */ public BlockFrame getFrame() { return this.frame; } /** * Sets the frame (or border). * * @param frame the frame (<code>null</code> not permitted). * * @since 1.0.5 * @see #getFrame() */ public void setFrame(BlockFrame frame) { ParamChecks.nullNotPermitted(frame, "frame"); this.frame = frame; } /** * Returns the padding. * * @return The padding (never <code>null</code>). * * @see #setPadding(RectangleInsets) */ public RectangleInsets getPadding() { return this.padding; } /** * Sets the padding (use {@link RectangleInsets#ZERO_INSETS} for no * padding). * * @param padding the padding (<code>null</code> not permitted). * * @see #getPadding() */ public void setPadding(RectangleInsets padding) { ParamChecks.nullNotPermitted(padding, "padding"); this.padding = padding; } /** * Sets the padding. * * @param top the top padding. * @param left the left padding. * @param bottom the bottom padding. * @param right the right padding. */ public void setPadding(double top, double left, double bottom, double right) { setPadding(new RectangleInsets(top, left, bottom, right)); } /** * Returns the x-offset for the content within the block. * * @return The x-offset. * * @see #getContentYOffset() */ public double getContentXOffset() { return this.margin.getLeft() + this.frame.getInsets().getLeft() + this.padding.getLeft(); } /** * Returns the y-offset for the content within the block. * * @return The y-offset. * * @see #getContentXOffset() */ public double getContentYOffset() { return this.margin.getTop() + this.frame.getInsets().getTop() + this.padding.getTop(); } /** * Arranges the contents of the block, with no constraints, and returns * the block size. * * @param g2 the graphics device. * * @return The block size (in Java2D units, never <code>null</code>). */ public Size2D arrange(Graphics2D g2) { return arrange(g2, RectangleConstraint.NONE); } /** * Arranges the contents of the block, within the given constraints, and * returns the block size. * * @param g2 the graphics device. * @param constraint the constraint (<code>null</code> not permitted). * * @return The block size (in Java2D units, never <code>null</code>). */ public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) { Size2D base = new Size2D(getWidth(), getHeight()); return constraint.calculateConstrainedSize(base); } /** * Returns the current bounds of the block. * * @return The bounds. * * @see #setBounds(Rectangle2D) */ public Rectangle2D getBounds() { return this.bounds; } /** * Sets the bounds of the block. * * @param bounds the bounds (<code>null</code> not permitted). * * @see #getBounds() */ public void setBounds(Rectangle2D bounds) { ParamChecks.nullNotPermitted(bounds, "bounds"); this.bounds = bounds; } /** * Calculate the width available for content after subtracting * the margin, border and padding space from the specified fixed * width. * * @param fixedWidth the fixed width. * * @return The available space. * * @see #trimToContentHeight(double) */ protected double trimToContentWidth(double fixedWidth) { double result = this.margin.trimWidth(fixedWidth); result = this.frame.getInsets().trimWidth(result); result = this.padding.trimWidth(result); return Math.max(result, 0.0); } /** * Calculate the height available for content after subtracting * the margin, border and padding space from the specified fixed * height. * * @param fixedHeight the fixed height. * * @return The available space. * * @see #trimToContentWidth(double) */ protected double trimToContentHeight(double fixedHeight) { double result = this.margin.trimHeight(fixedHeight); result = this.frame.getInsets().trimHeight(result); result = this.padding.trimHeight(result); return Math.max(result, 0.0); } /** * Returns a constraint for the content of this block that will result in * the bounds of the block matching the specified constraint. * * @param c the outer constraint (<code>null</code> not permitted). * * @return The content constraint. */ protected RectangleConstraint toContentConstraint(RectangleConstraint c) { ParamChecks.nullNotPermitted(c, "c"); if (c.equals(RectangleConstraint.NONE)) { return c; } double w = c.getWidth(); Range wr = c.getWidthRange(); double h = c.getHeight(); Range hr = c.getHeightRange(); double ww = trimToContentWidth(w); double hh = trimToContentHeight(h); Range wwr = trimToContentWidth(wr); Range hhr = trimToContentHeight(hr); return new RectangleConstraint(ww, wwr, c.getWidthConstraintType(), hh, hhr, c.getHeightConstraintType()); } private Range trimToContentWidth(Range r) { if (r == null) { return null; } double lowerBound = 0.0; double upperBound = Double.POSITIVE_INFINITY; if (r.getLowerBound() > 0.0) { lowerBound = trimToContentWidth(r.getLowerBound()); } if (r.getUpperBound() < Double.POSITIVE_INFINITY) { upperBound = trimToContentWidth(r.getUpperBound()); } return new Range(lowerBound, upperBound); } private Range trimToContentHeight(Range r) { if (r == null) { return null; } double lowerBound = 0.0; double upperBound = Double.POSITIVE_INFINITY; if (r.getLowerBound() > 0.0) { lowerBound = trimToContentHeight(r.getLowerBound()); } if (r.getUpperBound() < Double.POSITIVE_INFINITY) { upperBound = trimToContentHeight(r.getUpperBound()); } return new Range(lowerBound, upperBound); } /** * Adds the margin, border and padding to the specified content width. * * @param contentWidth the content width. * * @return The adjusted width. */ protected double calculateTotalWidth(double contentWidth) { double result = contentWidth; result = this.padding.extendWidth(result); result = this.frame.getInsets().extendWidth(result); result = this.margin.extendWidth(result); return result; } /** * Adds the margin, border and padding to the specified content height. * * @param contentHeight the content height. * * @return The adjusted height. */ protected double calculateTotalHeight(double contentHeight) { double result = contentHeight; result = this.padding.extendHeight(result); result = this.frame.getInsets().extendHeight(result); result = this.margin.extendHeight(result); return result; } /** * Reduces the specified area by the amount of space consumed * by the margin. * * @param area the area (<code>null</code> not permitted). * * @return The trimmed area. */ protected Rectangle2D trimMargin(Rectangle2D area) { // defer argument checking... this.margin.trim(area); return area; } /** * Reduces the specified area by the amount of space consumed * by the border. * * @param area the area (<code>null</code> not permitted). * * @return The trimmed area. */ protected Rectangle2D trimBorder(Rectangle2D area) { // defer argument checking... this.frame.getInsets().trim(area); return area; } /** * Reduces the specified area by the amount of space consumed * by the padding. * * @param area the area (<code>null</code> not permitted). * * @return The trimmed area. */ protected Rectangle2D trimPadding(Rectangle2D area) { // defer argument checking... this.padding.trim(area); return area; } /** * Draws the border around the perimeter of the specified area. * * @param g2 the graphics device. * @param area the area. */ protected void drawBorder(Graphics2D g2, Rectangle2D area) { this.frame.draw(g2, area); } /** * Tests this block for equality with an arbitrary object. * * @param obj the object (<code>null</code> permitted). * * @return A boolean. */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof AbstractBlock)) { return false; } AbstractBlock that = (AbstractBlock) obj; if (!ObjectUtils.equal(this.id, that.id)) { return false; } if (!this.frame.equals(that.frame)) { return false; } if (!this.bounds.equals(that.bounds)) { return false; } if (!this.margin.equals(that.margin)) { return false; } if (!this.padding.equals(that.padding)) { return false; } if (this.height != that.height) { return false; } if (this.width != that.width) { return false; } return true; } /** * Returns a clone of this block. * * @return A clone. * * @throws CloneNotSupportedException if there is a problem creating the * clone. */ @Override public Object clone() throws CloneNotSupportedException { AbstractBlock clone = (AbstractBlock) super.clone(); clone.bounds = (Rectangle2D) ShapeUtils.clone(this.bounds); if (this.frame instanceof PublicCloneable) { PublicCloneable pc = (PublicCloneable) this.frame; clone.frame = (BlockFrame) pc.clone(); } return clone; } /** * Provides serialization support. * * @param stream the output stream. * * @throws IOException if there is an I/O error. */ private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); SerialUtils.writeShape(this.bounds, stream); } /** * Provides serialization support. * * @param stream the input stream. * * @throws IOException if there is an I/O error. * @throws ClassNotFoundException if there is a classpath problem. */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); this.bounds = (Rectangle2D) SerialUtils.readShape(stream); } }