    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 * FlowArrangement.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 * 04-Feb-2005 : Implemented equals() and made serializable (DG);
    039 * 08-Feb-2005 : Updated for changes in RectangleConstraint (DG);
    040 *
    041 */
    042
    043package org.jfree.chart.block;
    044
    045import java.awt.Graphics2D;
    046import java.awt.geom.Rectangle2D;
    047import java.io.Serializable;
    048import java.util.ArrayList;
    049import java.util.List;
    050
    051import org.jfree.ui.HorizontalAlignment;
    052import org.jfree.ui.Size2D;
    053import org.jfree.ui.VerticalAlignment;
    054
    055/**
    056 * Arranges blocks in a flow layout.  This class is immutable.
    057 */
    058public class FlowArrangement implements Arrangement, Serializable {
    059
    060    /** For serialization. */
    061    private static final long serialVersionUID = 4543632485478613800L;
    062
    063    /** The horizontal alignment of blocks. */
    064    private HorizontalAlignment horizontalAlignment;
    065
    066    /** The vertical alignment of blocks within each row. */
    067    private VerticalAlignment verticalAlignment;
    068
    069    /** The horizontal gap between items within rows. */
    070    private double horizontalGap;
    071
    072    /** The vertical gap between rows. */
    073    private double verticalGap;
    074
    075    /**
    076     * Creates a new instance.
    077     */
    078    public FlowArrangement() {
    079        this(HorizontalAlignment.CENTER, VerticalAlignment.CENTER, 2.0, 2.0);
    080    }
    081
    082    /**
    083     * Creates a new instance.
    084     *
    085     * @param hAlign  the horizontal alignment (currently ignored).
    086     * @param vAlign  the vertical alignment (currently ignored).
    087     * @param hGap  the horizontal gap.
    088     * @param vGap  the vertical gap.
    089     */
    090    public FlowArrangement(HorizontalAlignment hAlign, VerticalAlignment vAlign,
    091                           double hGap, double vGap) {
    092        this.horizontalAlignment = hAlign;
    093        this.verticalAlignment = vAlign;
    094        this.horizontalGap = hGap;
    095        this.verticalGap = vGap;
    096    }
    097
    098    /**
    099     * Adds a block to be managed by this instance.  This method is usually
    100     * called by the {@link BlockContainer}, you shouldn't need to call it
    101     * directly.
    102     *
    103     * @param block  the block.
    104     * @param key  a key that controls the position of the block.
    105     */
    106    public void add(Block block, Object key) {
    107        // since the flow layout is relatively straightforward,
    108        // no information needs to be recorded here
    109    }
    110
    111    /**
    112     * Calculates and sets the bounds of all the items in the specified
    113     * container, subject to the given constraint.  The <code>Graphics2D</code>
    114     * can be used by some items (particularly items containing text) to
    115     * calculate sizing parameters.
    116     *
    117     * @param container  the container whose items are being arranged.
    118     * @param constraint  the size constraint.
    119     * @param g2  the graphics device.
    120     *
    121     * @return The size of the container after arrangement of the contents.
    122     */
    123    public Size2D arrange(BlockContainer container, Graphics2D g2,
    124                          RectangleConstraint constraint) {
    125
    126        LengthConstraintType w = constraint.getWidthConstraintType();
    127        LengthConstraintType h = constraint.getHeightConstraintType();
    128        if (w == LengthConstraintType.NONE) {
    129            if (h == LengthConstraintType.NONE) {
    130                return arrangeNN(container, g2);
    131            }
    132            else if (h == LengthConstraintType.FIXED) {
    133                return arrangeNF(container, g2, constraint);
    134            }
    135            else if (h == LengthConstraintType.RANGE) {
    136                throw new RuntimeException("Not implemented.");
    137            }
    138        }
    139        else if (w == LengthConstraintType.FIXED) {
    140            if (h == LengthConstraintType.NONE) {
    141                return arrangeFN(container, g2, constraint);
    142            }
    143            else if (h == LengthConstraintType.FIXED) {
    144                return arrangeFF(container, g2, constraint);
    145            }
    146            else if (h == LengthConstraintType.RANGE) {
    147                return arrangeFR(container, g2, constraint);
    148            }
    149        }
    150        else if (w == LengthConstraintType.RANGE) {
    151            if (h == LengthConstraintType.NONE) {
    152                return arrangeRN(container, g2, constraint);
    153            }
    154            else if (h == LengthConstraintType.FIXED) {
    155                return arrangeRF(container, g2, constraint);
    156            }
    157            else if (h == LengthConstraintType.RANGE) {
    158                return arrangeRR(container, g2, constraint);
    159            }
    160        }
    161        throw new RuntimeException("Unrecognised constraint type.");
    162
    163    }
    164
    165    /**
    166     * Arranges the blocks in the container with a fixed width and no height
    167     * constraint.
    168     *
    169     * @param container  the container.
    170     * @param constraint  the constraint.
    171     * @param g2  the graphics device.
    172     *
    173     * @return The size.
    174     */
    175    protected Size2D arrangeFN(BlockContainer container, Graphics2D g2,
    176                               RectangleConstraint constraint) {
    177
    178        List blocks = container.getBlocks();
    179        double width = constraint.getWidth();
    180
    181        double x = 0.0;
    182        double y = 0.0;
    183        double maxHeight = 0.0;
    184        List itemsInRow = new ArrayList();
    185        for (int i = 0; i < blocks.size(); i++) {
    186            Block block = (Block) blocks.get(i);
    187            Size2D size = block.arrange(g2, RectangleConstraint.NONE);
    188            if (x + size.width <= width) {
    189                itemsInRow.add(block);
    190                block.setBounds(
    191                    new Rectangle2D.Double(x, y, size.width, size.height)
    192                );
    193                x = x + size.width + this.horizontalGap;
    194                maxHeight = Math.max(maxHeight, size.height);
    195            }
    196            else {
    197                if (itemsInRow.isEmpty()) {
    198                    // place in this row (truncated) anyway
    199                    block.setBounds(
    200                        new Rectangle2D.Double(
    201                            x, y, Math.min(size.width, width - x), size.height
    202                        )
    203                    );
    204                    x = 0.0;
    205                    y = y + size.height + this.verticalGap;
    206                }
    207                else {
    208                    // start new row
    209                    itemsInRow.clear();
    210                    x = 0.0;
    211                    y = y + maxHeight + this.verticalGap;
    212                    maxHeight = size.height;
    213                    block.setBounds(
    214                        new Rectangle2D.Double(
    215                            x, y, Math.min(size.width, width), size.height
    216                        )
    217                    );
    218                    x = size.width + this.horizontalGap;
    219                    itemsInRow.add(block);
    220                }
    221            }
    222        }
    223        return new Size2D(constraint.getWidth(), y + maxHeight);
    224    }
    225
    226    /**
    227     * Arranges the blocks in the container with a fixed width and a range
    228     * constraint on the height.
    229     *
    230     * @param container  the container.
    231     * @param constraint  the constraint.
    232     * @param g2  the graphics device.
    233     *
    234     * @return The size following the arrangement.
    235     */
    236    protected Size2D arrangeFR(BlockContainer container, Graphics2D g2,
    237                               RectangleConstraint constraint) {
    238
    239        Size2D s = arrangeFN(container, g2, constraint);
    240        if (constraint.getHeightRange().contains(s.height)) {
    241            return s;
    242        }
    243        else {
    244            RectangleConstraint c = constraint.toFixedHeight(
    245                constraint.getHeightRange().constrain(s.getHeight())
    246            );
    247            return arrangeFF(container, g2, c);
    248        }
    249    }
    250
    251    /**
    252     * Arranges the blocks in the container with the overall height and width
    253     * specified as fixed constraints.
    254     *
    255     * @param container  the container.
    256     * @param constraint  the constraint.
    257     * @param g2  the graphics device.
    258     *
    259     * @return The size following the arrangement.
    260     */
    261    protected Size2D arrangeFF(BlockContainer container, Graphics2D g2,
    262                               RectangleConstraint constraint) {
    263
    264        // TODO: implement this properly
    265        return arrangeFN(container, g2, constraint);
    266    }
    267
    268    /**
    269     * Arranges the blocks with the overall width and height to fit within
    270     * specified ranges.
    271     *
    272     * @param container  the container.
    273     * @param constraint  the constraint.
    274     * @param g2  the graphics device.
    275     *
    276     * @return The size after the arrangement.
    277     */
    278    protected Size2D arrangeRR(BlockContainer container, Graphics2D g2,
    279                               RectangleConstraint constraint) {
    280
    281        // first arrange without constraints, and see if this fits within
    282        // the required ranges...
    283        Size2D s1 = arrangeNN(container, g2);
    284        if (constraint.getWidthRange().contains(s1.width)) {
    285            return s1;  // TODO: we didn't check the height yet
    286        }
    287        else {
    288            RectangleConstraint c = constraint.toFixedWidth(
    289                constraint.getWidthRange().getUpperBound()
    290            );
    291            return arrangeFR(container, g2, c);
    292        }
    293    }
    294
    295    /**
    296     * Arranges the blocks in the container with a range constraint on the
    297     * width and a fixed height.
    298     *
    299     * @param container  the container.
    300     * @param constraint  the constraint.
    301     * @param g2  the graphics device.
    302     *
    303     * @return The size following the arrangement.
    304     */
    305    protected Size2D arrangeRF(BlockContainer container, Graphics2D g2,
    306                               RectangleConstraint constraint) {
    307
    308        Size2D s = arrangeNF(container, g2, constraint);
    309        if (constraint.getWidthRange().contains(s.width)) {
    310            return s;
    311        }
    312        else {
    313            RectangleConstraint c = constraint.toFixedWidth(
    314                constraint.getWidthRange().constrain(s.getWidth())
    315            );
    316            return arrangeFF(container, g2, c);
    317        }
    318    }
    319
    320    /**
    321     * Arranges the block with a range constraint on the width, and no
    322     * constraint on the height.
    323     *
    324     * @param container  the container.
    325     * @param constraint  the constraint.
    326     * @param g2  the graphics device.
    327     *
    328     * @return The size following the arrangement.
    329     */
    330    protected Size2D arrangeRN(BlockContainer container, Graphics2D g2,
    331                               RectangleConstraint constraint) {
    332        // first arrange without constraints, then see if the width fits
    333        // within the required range...if not, call arrangeFN() at max width
    334        Size2D s1 = arrangeNN(container, g2);
    335        if (constraint.getWidthRange().contains(s1.width)) {
    336            return s1;
    337        }
    338        else {
    339            RectangleConstraint c = constraint.toFixedWidth(
    340                constraint.getWidthRange().getUpperBound()
    341            );
    342            return arrangeFN(container, g2, c);
    343        }
    344    }
    345
    346    /**
    347     * Arranges the blocks without any constraints.  This puts all blocks
    348     * into a single row.
    349     *
    350     * @param container  the container.
    351     * @param g2  the graphics device.
    352     *
    353     * @return The size after the arrangement.
    354     */
    355    protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) {
    356        double x = 0.0;
    357        double width = 0.0;
    358        double maxHeight = 0.0;
    359        List blocks = container.getBlocks();
    360        int blockCount = blocks.size();
    361        if (blockCount > 0) {
    362            Size2D[] sizes = new Size2D[blocks.size()];
    363            for (int i = 0; i < blocks.size(); i++) {
    364                Block block = (Block) blocks.get(i);
    365                sizes[i] = block.arrange(g2, RectangleConstraint.NONE);
    366                width = width + sizes[i].getWidth();
    367                maxHeight = Math.max(sizes[i].height, maxHeight);
    368                block.setBounds(
    369                    new Rectangle2D.Double(
    370                        x, 0.0, sizes[i].width, sizes[i].height
    371                    )
    372                );
    373                x = x + sizes[i].width + this.horizontalGap;
    374            }
    375            if (blockCount > 1) {
    376                width = width + this.horizontalGap * (blockCount - 1);
    377            }
    378            if (this.verticalAlignment != VerticalAlignment.TOP) {
    379                for (int i = 0; i < blocks.size(); i++) {
    380                    //Block b = (Block) blocks.get(i);
    381                    if (this.verticalAlignment == VerticalAlignment.CENTER) {
    382                        //TODO: shift block down by half
    383                    }
    384                    else if (this.verticalAlignment
    385                            == VerticalAlignment.BOTTOM) {
    386                        //TODO: shift block down to bottom
    387                    }
    388                }
    389            }
    390        }
    391        return new Size2D(width, maxHeight);
    392    }
    393
    394    /**
    395     * Arranges the blocks with no width constraint and a fixed height
    396     * constraint.  This puts all blocks into a single row.
    397     *
    398     * @param container  the container.
    399     * @param constraint  the constraint.
    400     * @param g2  the graphics device.
    401     *
    402     * @return The size after the arrangement.
    403     */
    404    protected Size2D arrangeNF(BlockContainer container, Graphics2D g2,
    405                               RectangleConstraint constraint) {
    406        // TODO: for now we are ignoring the height constraint
    407        return arrangeNN(container, g2);
    408    }
    409
    410    /**
    411     * Clears any cached information.
    412     */
    413    public void clear() {
    414        // no action required.
    415    }
    416
    417    /**
    418     * Tests this instance for equality with an arbitrary object.
    419     *
    420     * @param obj  the object (<code>null</code> permitted).
    421     *
    422     * @return A boolean.
    423     */
    424    public boolean equals(Object obj) {
    425        if (obj == this) {
    426            return true;
    427        }
    428        if (!(obj instanceof FlowArrangement)) {
    429            return false;
    430        }
    431        FlowArrangement that = (FlowArrangement) obj;
    432        if (this.horizontalAlignment != that.horizontalAlignment) {
    433            return false;
    434        }
    435        if (this.verticalAlignment != that.verticalAlignment) {
    436            return false;
    437        }
    438        if (this.horizontalGap != that.horizontalGap) {
    439            return false;
    440        }
    441        if (this.verticalGap != that.verticalGap) {
    442            return false;
    443        }
    444        return true;
    445    }
    446
    447}