    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * ------------------
    028 * AbstractBlock.java
    029 * ------------------
    030 * (C) Copyright 2004-2008, 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
    049package org.jfree.chart.block;
    050
    051import java.awt.Graphics2D;
    052import java.awt.geom.Rectangle2D;
    053import java.io.IOException;
    054import java.io.ObjectInputStream;
    055import java.io.ObjectOutputStream;
    056import java.io.Serializable;
    057
    058import org.jfree.data.Range;
    059import org.jfree.io.SerialUtilities;
    060import org.jfree.ui.RectangleInsets;
    061import org.jfree.ui.Size2D;
    062import org.jfree.util.ObjectUtilities;
    063import org.jfree.util.PublicCloneable;
    064import org.jfree.util.ShapeUtilities;
    065
    066/**
    067 * A convenience class for creating new classes that implement
    068 * the {@link Block} interface.
    069 */
    070public 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}