    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2008, 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     * 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
    043    package org.jfree.chart.block;
    044
    045    import java.awt.Graphics2D;
    046    import java.awt.geom.Rectangle2D;
    047    import java.io.Serializable;
    048    import java.util.ArrayList;
    049    import java.util.List;
    050
    051    import org.jfree.ui.HorizontalAlignment;
    052    import org.jfree.ui.Size2D;
    053    import org.jfree.ui.VerticalAlignment;
    054
    055    /**
    056     * Arranges blocks in a flow layout.  This class is immutable.
    057     */
    058    public 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    }