    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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, 2005, 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.1 2005/10/25 20:39:38 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     *
    046     */
    047
    048    package org.jfree.chart.block;
    049
    050    import java.awt.Graphics2D;
    051    import java.awt.geom.Rectangle2D;
    052    import java.io.IOException;
    053    import java.io.ObjectInputStream;
    054    import java.io.ObjectOutputStream;
    055    import java.io.Serializable;
    056
    057    import org.jfree.data.Range;
    058    import org.jfree.io.SerialUtilities;
    059    import org.jfree.ui.RectangleInsets;
    060    import org.jfree.ui.Size2D;
    061
    062    /**
    063     * A convenience class for creating new classes that implement
    064     * the {@link Block} interface.
    065     */
    066    public class AbstractBlock implements Serializable {
    067
    068        /** For serialization. */
    069        private static final long serialVersionUID = 7689852412141274563L;
    070
    071        /** The id for the block. */
    072        private String id;
    073
    074        /** The margin around the outside of the block. */
    075        private RectangleInsets margin;
    076
    077        /** The border for the block. */
    078        private BlockBorder border;
    079
    080        /** The padding between the block content and the border. */
    081        private RectangleInsets padding;
    082
    083        /**
    084         * The natural width of the block (may be overridden if there are
    085         * constraints in sizing).
    086         */
    087        private double width;
    088
    089        /**
    090         * The natural height of the block (may be overridden if there are
    091         * constraints in sizing).
    092         */
    093        private double height;
    094
    095        /**
    096         * The current bounds for the block (position of the block in Java2D space).
    097         */
    098        private transient Rectangle2D bounds;
    099
    100        /**
    101         * Creates a new block.
    102         */
    103        protected AbstractBlock() {
    104            this.id = null;
    105            this.width = 0.0;
    106            this.height = 0.0;
    107            this.bounds = new Rectangle2D.Float();
    108            this.margin = RectangleInsets.ZERO_INSETS;
    109            this.border = BlockBorder.NONE;
    110            this.padding = RectangleInsets.ZERO_INSETS;
    111        }
    112
    113        /**
    114         * Returns the id.
    115         *
    116         * @return The id (possibly <code>null</code>).
    117         */
    118        public String getID() {
    119            return this.id;
    120        }
    121
    122        /**
    123         * Sets the id for the block.
    124         *
    125         * @param id  the id (<code>null</code> permitted).
    126         */
    127        public void setID(String id) {
    128            this.id = id;
    129        }
    130
    131        /**
    132         * Returns the natural width of the block, if this is known in advance.
    133         * The actual width of the block may be overridden if layout constraints
    134         * make this necessary.
    135         *
    136         * @return The width.
    137         */
    138        public double getWidth() {
    139            return this.width;
    140        }
    141
    142        /**
    143         * Sets the natural width of the block, if this is known in advance.
    144         *
    145         * @param width  the width (in Java2D units)
    146         */
    147        public void setWidth(double width) {
    148            this.width = width;
    149        }
    150
    151        /**
    152         * Returns the natural height of the block, if this is known in advance.
    153         * The actual height of the block may be overridden if layout constraints
    154         * make this necessary.
    155         *
    156         * @return The height.
    157         */
    158        public double getHeight() {
    159            return this.height;
    160        }
    161
    162        /**
    163         * Sets the natural width of the block, if this is known in advance.
    164         *
    165         * @param height  the width (in Java2D units)
    166         */
    167        public void setHeight(double height) {
    168            this.height = height;
    169        }
    170
    171        /**
    172         * Returns the margin.
    173         *
    174         * @return The margin (never <code>null</code>).
    175         */
    176        public RectangleInsets getMargin() {
    177            return this.margin;
    178        }
    179
    180        /**
    181         * Sets the margin (use {@link RectangleInsets#ZERO_INSETS} for no
    182         * padding).
    183         *
    184         * @param margin  the margin (<code>null</code> not permitted).
    185         */
    186        public void setMargin(RectangleInsets margin) {
    187            if (margin == null) {
    188                throw new IllegalArgumentException("Null 'margin' argument.");
    189            }
    190            this.margin = margin;
    191        }
    192
    193        /**
    194         * Sets the margin.
    195         *
    196         * @param top  the top margin.
    197         * @param left  the left margin.
    198         * @param bottom  the bottom margin.
    199         * @param right  the right margin.
    200         */
    201        public void setMargin(double top, double left, double bottom,
    202                              double right) {
    203            setMargin(new RectangleInsets(top, left, bottom, right));
    204        }
    205
    206        /**
    207         * Returns the border.
    208         *
    209         * @return The border (never <code>null</code>).
    210         */
    211        public BlockBorder getBorder() {
    212            return this.border;
    213        }
    214
    215        /**
    216         * Sets the border for the block (use {@link BlockBorder#NONE} for
    217         * no border).
    218         *
    219         * @param border  the border (<code>null</code> not permitted).
    220         */
    221        public void setBorder(BlockBorder border) {
    222            if (border == null) {
    223                throw new IllegalArgumentException("Null 'border' argument.");
    224            }
    225            this.border = border;
    226        }
    227
    228        /**
    229         * Sets a black border with the specified line widths.
    230         *
    231         * @param top  the top border line width.
    232         * @param left  the left border line width.
    233         * @param bottom  the bottom border line width.
    234         * @param right  the right border line width.
    235         */
    236        public void setBorder(double top, double left, double bottom,
    237                              double right) {
    238            setBorder(new BlockBorder(top, left, bottom, right));
    239        }
    240
    241        /**
    242         * Returns the padding.
    243         *
    244         * @return The padding (never <code>null</code>).
    245         */
    246        public RectangleInsets getPadding() {
    247            return this.padding;
    248        }
    249
    250        /**
    251         * Sets the padding (use {@link RectangleInsets#ZERO_INSETS} for no
    252         * padding).
    253         *
    254         * @param padding  the padding (<code>null</code> not permitted).
    255         */
    256        public void setPadding(RectangleInsets padding) {
    257            if (padding == null) {
    258                throw new IllegalArgumentException("Null 'padding' argument.");
    259            }
    260            this.padding = padding;
    261        }
    262
    263        public double getContentXOffset() {
    264            return this.margin.getLeft() + this.border.getInsets().getLeft() + this.padding.getLeft();
    265        }
    266
    267        public double getContentYOffset() {
    268            return this.margin.getTop() + this.border.getInsets().getTop() + this.padding.getTop();
    269        }
    270
    271        /**
    272         * Sets the padding.
    273         *
    274         * @param top  the top padding.
    275         * @param left  the left padding.
    276         * @param bottom  the bottom padding.
    277         * @param right  the right padding.
    278         */
    279        public void setPadding(double top, double left, double bottom,
    280                               double right) {
    281            setPadding(new RectangleInsets(top, left, bottom, right));
    282        }
    283
    284        /**
    285         * Arranges the contents of the block, with no constraints, and returns
    286         * the block size.
    287         *
    288         * @param g2  the graphics device.
    289         *
    290         * @return The block size (in Java2D units, never <code>null</code>).
    291         */
    292        public Size2D arrange(Graphics2D g2) {
    293            return arrange(g2, RectangleConstraint.NONE);
    294        }
    295
    296        /**
    297         * Arranges the contents of the block, within the given constraints, and
    298         * returns the block size.
    299         *
    300         * @param g2  the graphics device.
    301         * @param constraint  the constraint (<code>null</code> not permitted).
    302         *
    303         * @return The block size (in Java2D units, never <code>null</code>).
    304         */
    305        public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
    306            Size2D base = new Size2D(getWidth(), getHeight());
    307            return constraint.calculateConstrainedSize(base);
    308        }
    309
    310        /**
    311         * Returns the current bounds of the block.
    312         *
    313         * @return The bounds.
    314         */
    315        public Rectangle2D getBounds() {
    316            return this.bounds;
    317        }
    318
    319        /**
    320         * Sets the bounds of the block.
    321         *
    322         * @param bounds  the bounds (<code>null</code> not permitted).
    323         */
    324        public void setBounds(Rectangle2D bounds) {
    325            if (bounds == null) {
    326                throw new IllegalArgumentException("Null 'bounds' argument.");
    327            }
    328            this.bounds = bounds;
    329        }
    330
    331        /**
    332         * Calculate the width available for content after subtracting
    333         * the margin, border and padding space from the specified fixed
    334         * width.
    335         *
    336         * @param fixedWidth  the fixed width.
    337         *
    338         * @return The available space.
    339         */
    340        protected double trimToContentWidth(double fixedWidth) {
    341            double result = this.margin.trimWidth(fixedWidth);
    342            result = this.border.getInsets().trimWidth(result);
    343            result = this.padding.trimWidth(result);
    344            return Math.max(result, 0.0);
    345        }
    346
    347        /**
    348         * Calculate the height available for content after subtracting
    349         * the margin, border and padding space from the specified fixed
    350         * height.
    351         *
    352         * @param fixedHeight  the fixed height.
    353         *
    354         * @return The available space.
    355         */
    356        protected double trimToContentHeight(double fixedHeight) {
    357            double result = this.margin.trimHeight(fixedHeight);
    358            result = this.border.getInsets().trimHeight(result);
    359            result = this.padding.trimHeight(result);
    360            return Math.max(result, 0.0);
    361        }
    362
    363        /**
    364         * Returns a constraint for the content of this block that will result in
    365         * the bounds of the block matching the specified constraint.
    366         *
    367         * @param c  the outer constraint (<code>null</code> not permitted).
    368         *
    369         * @return The content constraint.
    370         */
    371        protected RectangleConstraint toContentConstraint(RectangleConstraint c) {
    372            if (c == null) {
    373                throw new IllegalArgumentException("Null 'c' argument.");
    374            }
    375            if (c.equals(RectangleConstraint.NONE)) {
    376                return c;
    377            }
    378            double w = c.getWidth();
    379            Range wr = c.getWidthRange();
    380            double h = c.getHeight();
    381            Range hr = c.getHeightRange();
    382            double ww = trimToContentWidth(w);
    383            double hh = trimToContentHeight(h);
    384            Range wwr = trimToContentWidth(wr);
    385            Range hhr = trimToContentHeight(hr);
    386            return new RectangleConstraint(
    387                ww, wwr, c.getWidthConstraintType(),
    388                hh, hhr, c.getHeightConstraintType()
    389            );
    390        }
    391
    392        private Range trimToContentWidth(Range r) {
    393            if (r == null) {
    394                return null;
    395            }
    396            double lowerBound = 0.0;
    397            double upperBound = Double.POSITIVE_INFINITY;
    398            if (r.getLowerBound() > 0.0) {
    399                lowerBound = trimToContentWidth(r.getLowerBound());
    400            }
    401            if (r.getUpperBound() < Double.POSITIVE_INFINITY) {
    402                upperBound = trimToContentWidth(r.getUpperBound());
    403            }
    404            return new Range(lowerBound, upperBound);
    405        }
    406
    407        private Range trimToContentHeight(Range r) {
    408            if (r == null) {
    409                return null;
    410            }
    411            double lowerBound = 0.0;
    412            double upperBound = Double.POSITIVE_INFINITY;
    413            if (r.getLowerBound() > 0.0) {
    414                lowerBound = trimToContentHeight(r.getLowerBound());
    415            }
    416            if (r.getUpperBound() < Double.POSITIVE_INFINITY) {
    417                upperBound = trimToContentHeight(r.getUpperBound());
    418            }
    419            return new Range(lowerBound, upperBound);
    420        }
    421
    422        /**
    423         * Adds the margin, border and padding to the specified content width.
    424         *
    425         * @param contentWidth  the content width.
    426         *
    427         * @return The adjusted width.
    428         */
    429        protected double calculateTotalWidth(double contentWidth) {
    430            double result = contentWidth;
    431            result = this.padding.extendWidth(result);
    432            result = this.border.getInsets().extendWidth(result);
    433            result = this.margin.extendWidth(result);
    434            return result;
    435        }
    436
    437        /**
    438         * Adds the margin, border and padding to the specified content height.
    439         *
    440         * @param contentHeight  the content height.
    441         *
    442         * @return The adjusted height.
    443         */
    444        protected double calculateTotalHeight(double contentHeight) {
    445            double result = contentHeight;
    446            result = this.padding.extendHeight(result);
    447            result = this.border.getInsets().extendHeight(result);
    448            result = this.margin.extendHeight(result);
    449            return result;
    450        }
    451
    452        /**
    453         * Reduces the specified area by the amount of space consumed
    454         * by the margin.
    455         *
    456         * @param area  the area (<code>null</code> not permitted).
    457         *
    458         * @return The trimmed area.
    459         */
    460        protected Rectangle2D trimMargin(Rectangle2D area) {
    461            // defer argument checking...
    462            this.margin.trim(area);
    463            return area;
    464        }
    465
    466        /**
    467         * Reduces the specified area by the amount of space consumed
    468         * by the border.
    469         *
    470         * @param area  the area (<code>null</code> not permitted).
    471         *
    472         * @return The trimmed area.
    473         */
    474        protected Rectangle2D trimBorder(Rectangle2D area) {
    475            // defer argument checking...
    476            this.border.getInsets().trim(area);
    477            return area;
    478        }
    479
    480        /**
    481         * Reduces the specified area by the amount of space consumed
    482         * by the padding.
    483         *
    484         * @param area  the area (<code>null</code> not permitted).
    485         *
    486         * @return The trimmed area.
    487         */
    488        protected Rectangle2D trimPadding(Rectangle2D area) {
    489            // defer argument checking...
    490            this.padding.trim(area);
    491            return area;
    492        }
    493
    494        /**
    495         * Draws the border around the perimeter of the specified area.
    496         *
    497         * @param g2  the graphics device.
    498         * @param area  the area.
    499         */
    500        protected void drawBorder(Graphics2D g2, Rectangle2D area) {
    501            this.border.draw(g2, area);
    502        }
    503
    504        /**
    505         * Tests this block for equality with an arbitrary object.
    506         *
    507         * @param obj  the object (<code>null</code> permitted).
    508         *
    509         * @return A boolean.
    510         */
    511        public boolean equals(Object obj) {
    512            if (obj == this) {
    513                return true;
    514            }
    515            if (!(obj instanceof AbstractBlock)) {
    516                return false;
    517            }
    518            AbstractBlock that = (AbstractBlock) obj;
    519            if (!this.border.equals(that.border)) {
    520                return false;
    521            }
    522            if (!this.bounds.equals(that.bounds)) {
    523                return false;
    524            }
    525            if (!this.margin.equals(that.margin)) {
    526                return false;
    527            }
    528            if (!this.padding.equals(that.padding)) {
    529                return false;
    530            }
    531            if (this.height != that.height) {
    532                return false;
    533            }
    534            if (this.width != that.width) {
    535                return false;
    536            }
    537            return true;
    538        }
    539
    540        /**
    541         * Provides serialization support.
    542         *
    543         * @param stream  the output stream.
    544         *
    545         * @throws IOException if there is an I/O error.
    546         */
    547        private void writeObject(ObjectOutputStream stream) throws IOException {
    548            stream.defaultWriteObject();
    549            SerialUtilities.writeShape(this.bounds, stream);
    550        }
    551
    552        /**
    553         * Provides serialization support.
    554         *
    555         * @param stream  the input stream.
    556         *
    557         * @throws IOException  if there is an I/O error.
    558         * @throws ClassNotFoundException  if there is a classpath problem.
    559         */
    560        private void readObject(ObjectInputStream stream)
    561            throws IOException, ClassNotFoundException {
    562            stream.defaultReadObject();
    563            this.bounds = (Rectangle2D) SerialUtilities.readShape(stream);
    564        }
    565
    566    }