    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     * Changes:
    036     * --------
    037     * 22-Oct-2004 : Version 1 (DG);
    038     * 02-Feb-2005 : Added accessor methods for margin (DG);
    039     * 04-Feb-2005 : Added equals() method and implemented Serializable (DG);
    040     * 03-May-2005 : Added null argument checks (DG);
    041     * 06-May-2005 : Added convenience methods for setting margin, border and
    042     *               padding (DG);
    043     * ------------- JFREECHART 1.0.x ---------------------------------------------
    044     * 16-Mar-2007 : Changed border from BlockBorder to BlockFrame, updated
    045     *               equals(), and implemented Cloneable (DG);
    046     *
    047     */
    048
    049    package org.jfree.chart.block;
    050
    051    import java.awt.Graphics2D;
    052    import java.awt.geom.Rectangle2D;
    053    import java.io.IOException;
    054    import java.io.ObjectInputStream;
    055    import java.io.ObjectOutputStream;
    056    import java.io.Serializable;
    057
    058    import org.jfree.data.Range;
    059    import org.jfree.io.SerialUtilities;
    060    import org.jfree.ui.RectangleInsets;
    061    import org.jfree.ui.Size2D;
    062    import org.jfree.util.ObjectUtilities;
    063    import org.jfree.util.PublicCloneable;
    064    import org.jfree.util.ShapeUtilities;
    065
    066    /**
    067     * A convenience class for creating new classes that implement
    068     * the {@link Block} interface.
    069     */
    070    public class AbstractBlock implements Cloneable, Serializable {
    071
    072        /** For serialization. */
    073        private static final long serialVersionUID = 7689852412141274563L;
    074
    075        /** The id for the block. */
    076        private String id;
    077
    078        /** The margin around the outside of the block. */
    079        private RectangleInsets margin;
    080
    081        /** The frame (or border) for the block. */
    082        private BlockFrame frame;
    083
    084        /** The padding between the block content and the border. */
    085        private RectangleInsets padding;
    086
    087        /**
    088         * The natural width of the block (may be overridden if there are
    089         * constraints in sizing).
    090         */
    091        private double width;
    092
    093        /**
    094         * The natural height of the block (may be overridden if there are
    095         * constraints in sizing).
    096         */
    097        private double height;
    098
    099        /**
    100         * The current bounds for the block (position of the block in Java2D space).
    101         */
    102        private transient Rectangle2D bounds;
    103
    104        /**
    105         * Creates a new block.
    106         */
    107        protected AbstractBlock() {
    108            this.id = null;
    109            this.width = 0.0;
    110            this.height = 0.0;
    111            this.bounds = new Rectangle2D.Float();
    112            this.margin = RectangleInsets.ZERO_INSETS;
    113            this.frame = BlockBorder.NONE;
    114            this.padding = RectangleInsets.ZERO_INSETS;
    115        }
    116
    117        /**
    118         * Returns the id.
    119         *
    120         * @return The id (possibly <code>null</code>).
    121         *
    122         * @see #setID(String)
    123         */
    124        public String getID() {
    125            return this.id;
    126        }
    127
    128        /**
    129         * Sets the id for the block.
    130         *
    131         * @param id  the id (<code>null</code> permitted).
    132         *
    133         * @see #getID()
    134         */
    135        public void setID(String id) {
    136            this.id = id;
    137        }
    138
    139        /**
    140         * Returns the natural width of the block, if this is known in advance.
    141         * The actual width of the block may be overridden if layout constraints
    142         * make this necessary.
    143         *
    144         * @return The width.
    145         *
    146         * @see #setWidth(double)
    147         */
    148        public double getWidth() {
    149            return this.width;
    150        }
    151
    152        /**
    153         * Sets the natural width of the block, if this is known in advance.
    154         *
    155         * @param width  the width (in Java2D units)
    156         *
    157         * @see #getWidth()
    158         */
    159        public void setWidth(double width) {
    160            this.width = width;
    161        }
    162
    163        /**
    164         * Returns the natural height of the block, if this is known in advance.
    165         * The actual height of the block may be overridden if layout constraints
    166         * make this necessary.
    167         *
    168         * @return The height.
    169         *
    170         * @see #setHeight(double)
    171         */
    172        public double getHeight() {
    173            return this.height;
    174        }
    175
    176        /**
    177         * Sets the natural width of the block, if this is known in advance.
    178         *
    179         * @param height  the width (in Java2D units)
    180         *
    181         * @see #getHeight()
    182         */
    183        public void setHeight(double height) {
    184            this.height = height;
    185        }
    186
    187        /**
    188         * Returns the margin.
    189         *
    190         * @return The margin (never <code>null</code>).
    191         *
    192         * @see #getMargin()
    193         */
    194        public RectangleInsets getMargin() {
    195            return this.margin;
    196        }
    197
    198        /**
    199         * Sets the margin (use {@link RectangleInsets#ZERO_INSETS} for no
    200         * padding).
    201         *
    202         * @param margin  the margin (<code>null</code> not permitted).
    203         *
    204         * @see #getMargin()
    205         */
    206        public void setMargin(RectangleInsets margin) {
    207            if (margin == null) {
    208                throw new IllegalArgumentException("Null 'margin' argument.");
    209            }
    210            this.margin = margin;
    211        }
    212
    213        /**
    214         * Sets the margin.
    215         *
    216         * @param top  the top margin.
    217         * @param left  the left margin.
    218         * @param bottom  the bottom margin.
    219         * @param right  the right margin.
    220         *
    221         * @see #getMargin()
    222         */
    223        public void setMargin(double top, double left, double bottom,
    224                              double right) {
    225            setMargin(new RectangleInsets(top, left, bottom, right));
    226        }
    227
    228        /**
    229         * Returns the border.
    230         *
    231         * @return The border (never <code>null</code>).
    232         *
    233         * @deprecated Use {@link #getFrame()} instead.
    234         */
    235        public BlockBorder getBorder() {
    236            if (this.frame instanceof BlockBorder) {
    237                return (BlockBorder) this.frame;
    238            }
    239            else {
    240                return null;
    241            }
    242        }
    243
    244        /**
    245         * Sets the border for the block (use {@link BlockBorder#NONE} for
    246         * no border).
    247         *
    248         * @param border  the border (<code>null</code> not permitted).
    249         *
    250         * @see #getBorder()
    251         *
    252         * @deprecated Use {@link #setFrame(BlockFrame)} instead.
    253         */
    254        public void setBorder(BlockBorder border) {
    255            setFrame(border);
    256        }
    257
    258        /**
    259         * Sets a black border with the specified line widths.
    260         *
    261         * @param top  the top border line width.
    262         * @param left  the left border line width.
    263         * @param bottom  the bottom border line width.
    264         * @param right  the right border line width.
    265         */
    266        public void setBorder(double top, double left, double bottom,
    267                              double right) {
    268            setFrame(new BlockBorder(top, left, bottom, right));
    269        }
    270
    271        /**
    272         * Returns the current frame (border).
    273         *
    274         * @return The frame.
    275         *
    276         * @since 1.0.5
    277         * @see #setFrame(BlockFrame)
    278         */
    279        public BlockFrame getFrame() {
    280            return this.frame;
    281        }
    282
    283        /**
    284         * Sets the frame (or border).
    285         *
    286         * @param frame  the frame (<code>null</code> not permitted).
    287         *
    288         * @since 1.0.5
    289         * @see #getFrame()
    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    }