    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * ------------------
    028     * AbstractBlock.java
    029     * ------------------
    030     * (C) Copyright 2004-2007, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: AbstractBlock.java,v 1.12.2.4 2007/03/20 08:31:20 mungady Exp $
    036     *
    037     * Changes:
    038     * --------
    039     * 22-Oct-2004 : Version 1 (DG);
    040     * 02-Feb-2005 : Added accessor methods for margin (DG);
    041     * 04-Feb-2005 : Added equals() method and implemented Serializable (DG);
    042     * 03-May-2005 : Added null argument checks (DG);
    043     * 06-May-2005 : Added convenience methods for setting margin, border and
    044     *               padding (DG);
    045     * ------------- JFREECHART 1.0.x ---------------------------------------------
    046     * 16-Mar-2007 : Changed border from BlockBorder to BlockFrame, updated
    047     *               equals(), and implemented Cloneable (DG);
    048     *
    049     */
    050
    051    package org.jfree.chart.block;
    052
    053    import java.awt.Graphics2D;
    054    import java.awt.geom.Rectangle2D;
    055    import java.io.IOException;
    056    import java.io.ObjectInputStream;
    057    import java.io.ObjectOutputStream;
    058    import java.io.Serializable;
    059
    060    import org.jfree.data.Range;
    061    import org.jfree.io.SerialUtilities;
    062    import org.jfree.ui.RectangleInsets;
    063    import org.jfree.ui.Size2D;
    064    import org.jfree.util.ObjectUtilities;
    065    import org.jfree.util.PublicCloneable;
    066    import org.jfree.util.ShapeUtilities;
    067
    068    /**
    069     * A convenience class for creating new classes that implement
    070     * the {@link Block} interface.
    071     */
    072    public class AbstractBlock implements Cloneable, Serializable {
    073
    074        /** For serialization. */
    075        private static final long serialVersionUID = 7689852412141274563L;
    076
    077        /** The id for the block. */
    078        private String id;
    079
    080        /** The margin around the outside of the block. */
    081        private RectangleInsets margin;
    082
    083        /** The frame (or border) for the block. */
    084        private BlockFrame frame;
    085
    086        /** The padding between the block content and the border. */
    087        private RectangleInsets padding;
    088
    089        /**
    090         * The natural width of the block (may be overridden if there are
    091         * constraints in sizing).
    092         */
    093        private double width;
    094
    095        /**
    096         * The natural height of the block (may be overridden if there are
    097         * constraints in sizing).
    098         */
    099        private double height;
    100
    101        /**
    102         * The current bounds for the block (position of the block in Java2D space).
    103         */
    104        private transient Rectangle2D bounds;
    105
    106        /**
    107         * Creates a new block.
    108         */
    109        protected AbstractBlock() {
    110            this.id = null;
    111            this.width = 0.0;
    112            this.height = 0.0;
    113            this.bounds = new Rectangle2D.Float();
    114            this.margin = RectangleInsets.ZERO_INSETS;
    115            this.frame = BlockBorder.NONE;
    116            this.padding = RectangleInsets.ZERO_INSETS;
    117        }
    118
    119        /**
    120         * Returns the id.
    121         *
    122         * @return The id (possibly <code>null</code>).
    123         *
    124         * @see #setID(String)
    125         */
    126        public String getID() {
    127            return this.id;
    128        }
    129
    130        /**
    131         * Sets the id for the block.
    132         *
    133         * @param id  the id (<code>null</code> permitted).
    134         *
    135         * @see #getID()
    136         */
    137        public void setID(String id) {
    138            this.id = id;
    139        }
    140
    141        /**
    142         * Returns the natural width of the block, if this is known in advance.
    143         * The actual width of the block may be overridden if layout constraints
    144         * make this necessary.
    145         *
    146         * @return The width.
    147         *
    148         * @see #setWidth(double)
    149         */
    150        public double getWidth() {
    151            return this.width;
    152        }
    153
    154        /**
    155         * Sets the natural width of the block, if this is known in advance.
    156         *
    157         * @param width  the width (in Java2D units)
    158         *
    159         * @see #getWidth()
    160         */
    161        public void setWidth(double width) {
    162            this.width = width;
    163        }
    164
    165        /**
    166         * Returns the natural height of the block, if this is known in advance.
    167         * The actual height of the block may be overridden if layout constraints
    168         * make this necessary.
    169         *
    170         * @return The height.
    171         *
    172         * @see #setHeight(double)
    173         */
    174        public double getHeight() {
    175            return this.height;
    176        }
    177
    178        /**
    179         * Sets the natural width of the block, if this is known in advance.
    180         *
    181         * @param height  the width (in Java2D units)
    182         *
    183         * @see #getHeight()
    184         */
    185        public void setHeight(double height) {
    186            this.height = height;
    187        }
    188
    189        /**
    190         * Returns the margin.
    191         *
    192         * @return The margin (never <code>null</code>).
    193         *
    194         * @see #getMargin()
    195         */
    196        public RectangleInsets getMargin() {
    197            return this.margin;
    198        }
    199
    200        /**
    201         * Sets the margin (use {@link RectangleInsets#ZERO_INSETS} for no
    202         * padding).
    203         *
    204         * @param margin  the margin (<code>null</code> not permitted).
    205         *
    206         * @see #getMargin()
    207         */
    208        public void setMargin(RectangleInsets margin) {
    209            if (margin == null) {
    210                throw new IllegalArgumentException("Null 'margin' argument.");
    211            }
    212            this.margin = margin;
    213        }
    214
    215        /**
    216         * Sets the margin.
    217         *
    218         * @param top  the top margin.
    219         * @param left  the left margin.
    220         * @param bottom  the bottom margin.
    221         * @param right  the right margin.
    222         *
    223         * @see #getMargin()
    224         */
    225        public void setMargin(double top, double left, double bottom,
    226                              double right) {
    227            setMargin(new RectangleInsets(top, left, bottom, right));
    228        }
    229
    230        /**
    231         * Returns the border.
    232         *
    233         * @return The border (never <code>null</code>).
    234         *
    235         * @deprecated Use getBlockFrame() instead.
    236         */
    237        public BlockBorder getBorder() {
    238            if (this.frame instanceof BlockBorder) {
    239                return (BlockBorder) this.frame;
    240            }
    241            else {
    242                return null;
    243            }
    244        }
    245
    246        /**
    247         * Sets the border for the block (use {@link BlockBorder#NONE} for
    248         * no border).
    249         *
    250         * @param border  the border (<code>null</code> not permitted).
    251         *
    252         * @see #getBorder()
    253         *
    254         * @deprecated Use setBorderFrame() instead.
    255         */
    256        public void setBorder(BlockBorder border) {
    257            setFrame(border);
    258        }
    259
    260        /**
    261         * Sets a black border with the specified line widths.
    262         *
    263         * @param top  the top border line width.
    264         * @param left  the left border line width.
    265         * @param bottom  the bottom border line width.
    266         * @param right  the right border line width.
    267         */
    268        public void setBorder(double top, double left, double bottom,
    269                              double right) {
    270            setFrame(new BlockBorder(top, left, bottom, right));
    271        }
    272
    273        /**
    274         * Returns the current frame (border).
    275         *
    276         * @return The frame.
    277         *
    278         * @since 1.0.5
    279         */
    280        public BlockFrame getFrame() {
    281            return this.frame;
    282        }
    283
    284        /**
    285         * Sets the frame (or border).
    286         *
    287         * @param frame  the frame (<code>null</code> not permitted).
    288         *
    289         * @since 1.0.5
    290         */
    291        public void setFrame(BlockFrame frame) {
    292            if (frame == null) {
    293                throw new IllegalArgumentException("Null 'frame' argument.");
    294            }
    295            this.frame = frame;
    296        }
    297
    298        /**
    299         * Returns the padding.
    300         *
    301         * @return The padding (never <code>null</code>).
    302         *
    303         * @see #setPadding(RectangleInsets)
    304         */
    305        public RectangleInsets getPadding() {
    306            return this.padding;
    307        }
    308
    309        /**
    310         * Sets the padding (use {@link RectangleInsets#ZERO_INSETS} for no
    311         * padding).
    312         *
    313         * @param padding  the padding (<code>null</code> not permitted).
    314         *
    315         * @see #getPadding()
    316         */
    317        public void setPadding(RectangleInsets padding) {
    318            if (padding == null) {
    319                throw new IllegalArgumentException("Null 'padding' argument.");
    320            }
    321            this.padding = padding;
    322        }
    323
    324        /**
    325         * Sets the padding.
    326         *
    327         * @param top  the top padding.
    328         * @param left  the left padding.
    329         * @param bottom  the bottom padding.
    330         * @param right  the right padding.
    331         */
    332        public void setPadding(double top, double left, double bottom,
    333                               double right) {
    334            setPadding(new RectangleInsets(top, left, bottom, right));
    335        }
    336
    337        /**
    338         * Returns the x-offset for the content within the block.
    339         *
    340         * @return The x-offset.
    341         *
    342         * @see #getContentYOffset()
    343         */
    344        public double getContentXOffset() {
    345            return this.margin.getLeft() + this.frame.getInsets().getLeft()
    346                + this.padding.getLeft();
    347        }
    348
    349        /**
    350         * Returns the y-offset for the content within the block.
    351         *
    352         * @return The y-offset.
    353         *
    354         * @see #getContentXOffset()
    355         */
    356        public double getContentYOffset() {
    357            return this.margin.getTop() + this.frame.getInsets().getTop()
    358                + this.padding.getTop();
    359        }
    360
    361        /**
    362         * Arranges the contents of the block, with no constraints, and returns
    363         * the block size.
    364         *
    365         * @param g2  the graphics device.
    366         *
    367         * @return The block size (in Java2D units, never <code>null</code>).
    368         */
    369        public Size2D arrange(Graphics2D g2) {
    370            return arrange(g2, RectangleConstraint.NONE);
    371        }
    372
    373        /**
    374         * Arranges the contents of the block, within the given constraints, and
    375         * returns the block size.
    376         *
    377         * @param g2  the graphics device.
    378         * @param constraint  the constraint (<code>null</code> not permitted).
    379         *
    380         * @return The block size (in Java2D units, never <code>null</code>).
    381         */
    382        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
    383            Size2D base = new Size2D(getWidth(), getHeight());
    384            return constraint.calculateConstrainedSize(base);
    385        }
    386
    387        /**
    388         * Returns the current bounds of the block.
    389         *
    390         * @return The bounds.
    391         *
    392         * @see #setBounds(Rectangle2D)
    393         */
    394        public Rectangle2D getBounds() {
    395            return this.bounds;
    396        }
    397
    398        /**
    399         * Sets the bounds of the block.
    400         *
    401         * @param bounds  the bounds (<code>null</code> not permitted).
    402         *
    403         * @see #getBounds()
    404         */
    405        public void setBounds(Rectangle2D bounds) {
    406            if (bounds == null) {
    407                throw new IllegalArgumentException("Null 'bounds' argument.");
    408            }
    409            this.bounds = bounds;
    410        }
    411
    412        /**
    413         * Calculate the width available for content after subtracting
    414         * the margin, border and padding space from the specified fixed
    415         * width.
    416         *
    417         * @param fixedWidth  the fixed width.
    418         *
    419         * @return The available space.
    420         *
    421         * @see #trimToContentHeight(double)
    422         */
    423        protected double trimToContentWidth(double fixedWidth) {
    424            double result = this.margin.trimWidth(fixedWidth);
    425            result = this.frame.getInsets().trimWidth(result);
    426            result = this.padding.trimWidth(result);
    427            return Math.max(result, 0.0);
    428        }
    429
    430        /**
    431         * Calculate the height available for content after subtracting
    432         * the margin, border and padding space from the specified fixed
    433         * height.
    434         *
    435         * @param fixedHeight  the fixed height.
    436         *
    437         * @return The available space.
    438         *
    439         * @see #trimToContentWidth(double)
    440         */
    441        protected double trimToContentHeight(double fixedHeight) {
    442            double result = this.margin.trimHeight(fixedHeight);
    443            result = this.frame.getInsets().trimHeight(result);
    444            result = this.padding.trimHeight(result);
    445            return Math.max(result, 0.0);
    446        }
    447
    448        /**
    449         * Returns a constraint for the content of this block that will result in
    450         * the bounds of the block matching the specified constraint.
    451         *
    452         * @param c  the outer constraint (<code>null</code> not permitted).
    453         *
    454         * @return The content constraint.
    455         */
    456        protected RectangleConstraint toContentConstraint(RectangleConstraint c) {
    457            if (c == null) {
    458                throw new IllegalArgumentException("Null 'c' argument.");
    459            }
    460            if (c.equals(RectangleConstraint.NONE)) {
    461                return c;
    462            }
    463            double w = c.getWidth();
    464            Range wr = c.getWidthRange();
    465            double h = c.getHeight();
    466            Range hr = c.getHeightRange();
    467            double ww = trimToContentWidth(w);
    468            double hh = trimToContentHeight(h);
    469            Range wwr = trimToContentWidth(wr);
    470            Range hhr = trimToContentHeight(hr);
    471            return new RectangleConstraint(
    472                ww, wwr, c.getWidthConstraintType(),
    473                hh, hhr, c.getHeightConstraintType()
    474            );
    475        }
    476
    477        private Range trimToContentWidth(Range r) {
    478            if (r == null) {
    479                return null;
    480            }
    481            double lowerBound = 0.0;
    482            double upperBound = Double.POSITIVE_INFINITY;
    483            if (r.getLowerBound() > 0.0) {
    484                lowerBound = trimToContentWidth(r.getLowerBound());
    485            }
    486            if (r.getUpperBound() < Double.POSITIVE_INFINITY) {
    487                upperBound = trimToContentWidth(r.getUpperBound());
    488            }
    489            return new Range(lowerBound, upperBound);
    490        }
    491
    492        private Range trimToContentHeight(Range r) {
    493            if (r == null) {
    494                return null;
    495            }
    496            double lowerBound = 0.0;
    497            double upperBound = Double.POSITIVE_INFINITY;
    498            if (r.getLowerBound() > 0.0) {
    499                lowerBound = trimToContentHeight(r.getLowerBound());
    500            }
    501            if (r.getUpperBound() < Double.POSITIVE_INFINITY) {
    502                upperBound = trimToContentHeight(r.getUpperBound());
    503            }
    504            return new Range(lowerBound, upperBound);
    505        }
    506
    507        /**
    508         * Adds the margin, border and padding to the specified content width.
    509         *
    510         * @param contentWidth  the content width.
    511         *
    512         * @return The adjusted width.
    513         */
    514        protected double calculateTotalWidth(double contentWidth) {
    515            double result = contentWidth;
    516            result = this.padding.extendWidth(result);
    517            result = this.frame.getInsets().extendWidth(result);
    518            result = this.margin.extendWidth(result);
    519            return result;
    520        }
    521
    522        /**
    523         * Adds the margin, border and padding to the specified content height.
    524         *
    525         * @param contentHeight  the content height.
    526         *
    527         * @return The adjusted height.
    528         */
    529        protected double calculateTotalHeight(double contentHeight) {
    530            double result = contentHeight;
    531            result = this.padding.extendHeight(result);
    532            result = this.frame.getInsets().extendHeight(result);
    533            result = this.margin.extendHeight(result);
    534            return result;
    535        }
    536
    537        /**
    538         * Reduces the specified area by the amount of space consumed
    539         * by the margin.
    540         *
    541         * @param area  the area (<code>null</code> not permitted).
    542         *
    543         * @return The trimmed area.
    544         */
    545        protected Rectangle2D trimMargin(Rectangle2D area) {
    546            // defer argument checking...
    547            this.margin.trim(area);
    548            return area;
    549        }
    550
    551        /**
    552         * Reduces the specified area by the amount of space consumed
    553         * by the border.
    554         *
    555         * @param area  the area (<code>null</code> not permitted).
    556         *
    557         * @return The trimmed area.
    558         */
    559        protected Rectangle2D trimBorder(Rectangle2D area) {
    560            // defer argument checking...
    561            this.frame.getInsets().trim(area);
    562            return area;
    563        }
    564
    565        /**
    566         * Reduces the specified area by the amount of space consumed
    567         * by the padding.
    568         *
    569         * @param area  the area (<code>null</code> not permitted).
    570         *
    571         * @return The trimmed area.
    572         */
    573        protected Rectangle2D trimPadding(Rectangle2D area) {
    574            // defer argument checking...
    575            this.padding.trim(area);
    576            return area;
    577        }
    578
    579        /**
    580         * Draws the border around the perimeter of the specified area.
    581         *
    582         * @param g2  the graphics device.
    583         * @param area  the area.
    584         */
    585        protected void drawBorder(Graphics2D g2, Rectangle2D area) {
    586            this.frame.draw(g2, area);
    587        }
    588
    589        /**
    590         * Tests this block for equality with an arbitrary object.
    591         *
    592         * @param obj  the object (<code>null</code> permitted).
    593         *
    594         * @return A boolean.
    595         */
    596        public boolean equals(Object obj) {
    597            if (obj == this) {
    598                return true;
    599            }
    600            if (!(obj instanceof AbstractBlock)) {
    601                return false;
    602            }
    603            AbstractBlock that = (AbstractBlock) obj;
    604            if (!ObjectUtilities.equal(this.id, that.id)) {
    605                return false;
    606            }
    607            if (!this.frame.equals(that.frame)) {
    608                return false;
    609            }
    610            if (!this.bounds.equals(that.bounds)) {
    611                return false;
    612            }
    613            if (!this.margin.equals(that.margin)) {
    614                return false;
    615            }
    616            if (!this.padding.equals(that.padding)) {
    617                return false;
    618            }
    619            if (this.height != that.height) {
    620                return false;
    621            }
    622            if (this.width != that.width) {
    623                return false;
    624            }
    625            return true;
    626        }
    627
    628        /**
    629         * Returns a clone of this block.
    630         *
    631         * @return A clone.
    632         *
    633         * @throws CloneNotSupportedException if there is a problem creating the
    634         *         clone.
    635         */
    636        public Object clone() throws CloneNotSupportedException {
    637            AbstractBlock clone = (AbstractBlock) super.clone();
    638            clone.bounds = (Rectangle2D) ShapeUtilities.clone(this.bounds);
    639            if (this.frame instanceof PublicCloneable) {
    640                PublicCloneable pc = (PublicCloneable) this.frame;
    641                clone.frame = (BlockFrame) pc.clone();
    642            }
    643            return clone;
    644        }
    645
    646        /**
    647         * Provides serialization support.
    648         *
    649         * @param stream  the output stream.
    650         *
    651         * @throws IOException if there is an I/O error.
    652         */
    653        private void writeObject(ObjectOutputStream stream) throws IOException {
    654            stream.defaultWriteObject();
    655            SerialUtilities.writeShape(this.bounds, stream);
    656        }
    657
    658        /**
    659         * Provides serialization support.
    660         *
    661         * @param stream  the input stream.
    662         *
    663         * @throws IOException  if there is an I/O error.
    664         * @throws ClassNotFoundException  if there is a classpath problem.
    665         */
    666        private void readObject(ObjectInputStream stream)
    667            throws IOException, ClassNotFoundException {
    668            stream.defaultReadObject();
    669            this.bounds = (Rectangle2D) SerialUtilities.readShape(stream);
    670        }
    671
    672    }