/* =========================================================== * JFreeChart : a free chart library for the Java(tm) platform * =========================================================== * * (C) Copyright 2000-2012, by Object Refinery Limited and Contributors. * * Project Info: http://www.jfree.org/jfreechart/index.html * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners.] * * -------------------- * FlowArrangement.java * -------------------- * (C) Copyright 2004-2012, by Object Refinery Limited. * * Original Author: David Gilbert (for Object Refinery Limited); * Contributor(s): -; * * Changes: * -------- * 22-Oct-2004 : Version 1 (DG); * 04-Feb-2005 : Implemented equals() and made serializable (DG); * 08-Feb-2005 : Updated for changes in RectangleConstraint (DG); * 16-Jun-2012 : Removed JCommon dependencies (DG); * */ package org.jfree.chart.block; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.jfree.chart.ui.HorizontalAlignment; import org.jfree.chart.ui.Size2D; import org.jfree.chart.ui.VerticalAlignment; /** * Arranges blocks in a flow layout. This class is immutable. */ public class FlowArrangement implements Arrangement, Serializable { /** For serialization. */ private static final long serialVersionUID = 4543632485478613800L; /** The horizontal alignment of blocks. */ private HorizontalAlignment horizontalAlignment; /** The vertical alignment of blocks within each row. */ private VerticalAlignment verticalAlignment; /** The horizontal gap between items within rows. */ private double horizontalGap; /** The vertical gap between rows. */ private double verticalGap; /** * Creates a new instance. */ public FlowArrangement() { this(HorizontalAlignment.CENTER, VerticalAlignment.CENTER, 2.0, 2.0); } /** * Creates a new instance. * * @param hAlign the horizontal alignment (currently ignored). * @param vAlign the vertical alignment (currently ignored). * @param hGap the horizontal gap. * @param vGap the vertical gap. */ public FlowArrangement(HorizontalAlignment hAlign, VerticalAlignment vAlign, double hGap, double vGap) { this.horizontalAlignment = hAlign; this.verticalAlignment = vAlign; this.horizontalGap = hGap; this.verticalGap = vGap; } /** * Adds a block to be managed by this instance. This method is usually * called by the {@link BlockContainer}, you shouldn't need to call it * directly. * * @param block the block. * @param key a key that controls the position of the block. */ @Override public void add(Block block, Object key) { // since the flow layout is relatively straightforward, // no information needs to be recorded here } /** * Calculates and sets the bounds of all the items in the specified * container, subject to the given constraint. The <code>Graphics2D</code> * can be used by some items (particularly items containing text) to * calculate sizing parameters. * * @param container the container whose items are being arranged. * @param constraint the size constraint. * @param g2 the graphics device. * * @return The size of the container after arrangement of the contents. */ @Override public Size2D arrange(BlockContainer container, Graphics2D g2, RectangleConstraint constraint) { LengthConstraintType w = constraint.getWidthConstraintType(); LengthConstraintType h = constraint.getHeightConstraintType(); if (w == LengthConstraintType.NONE) { if (h == LengthConstraintType.NONE) { return arrangeNN(container, g2); } else if (h == LengthConstraintType.FIXED) { return arrangeNF(container, g2, constraint); } else if (h == LengthConstraintType.RANGE) { throw new RuntimeException("Not implemented."); } } else if (w == LengthConstraintType.FIXED) { if (h == LengthConstraintType.NONE) { return arrangeFN(container, g2, constraint); } else if (h == LengthConstraintType.FIXED) { return arrangeFF(container, g2, constraint); } else if (h == LengthConstraintType.RANGE) { return arrangeFR(container, g2, constraint); } } else if (w == LengthConstraintType.RANGE) { if (h == LengthConstraintType.NONE) { return arrangeRN(container, g2, constraint); } else if (h == LengthConstraintType.FIXED) { return arrangeRF(container, g2, constraint); } else if (h == LengthConstraintType.RANGE) { return arrangeRR(container, g2, constraint); } } throw new RuntimeException("Unrecognised constraint type."); } /** * Arranges the blocks in the container with a fixed width and no height * constraint. * * @param container the container. * @param constraint the constraint. * @param g2 the graphics device. * * @return The size. */ protected Size2D arrangeFN(BlockContainer container, Graphics2D g2, RectangleConstraint constraint) { List<Block> blocks = container.getBlocks(); double width = constraint.getWidth(); double x = 0.0; double y = 0.0; double maxHeight = 0.0; List<Block> itemsInRow = new ArrayList<Block>(); for (Block block : blocks) { Size2D size = block.arrange(g2, RectangleConstraint.NONE); if (x + size.width <= width) { itemsInRow.add(block); block.setBounds( new Rectangle2D.Double(x, y, size.width, size.height) ); x = x + size.width + this.horizontalGap; maxHeight = Math.max(maxHeight, size.height); } else { if (itemsInRow.isEmpty()) { // place in this row (truncated) anyway block.setBounds( new Rectangle2D.Double( x, y, Math.min(size.width, width - x), size.height ) ); x = 0.0; y = y + size.height + this.verticalGap; } else { // start new row itemsInRow.clear(); x = 0.0; y = y + maxHeight + this.verticalGap; maxHeight = size.height; block.setBounds( new Rectangle2D.Double( x, y, Math.min(size.width, width), size.height ) ); x = size.width + this.horizontalGap; itemsInRow.add(block); } } } return new Size2D(constraint.getWidth(), y + maxHeight); } /** * Arranges the blocks in the container with a fixed width and a range * constraint on the height. * * @param container the container. * @param constraint the constraint. * @param g2 the graphics device. * * @return The size following the arrangement. */ protected Size2D arrangeFR(BlockContainer container, Graphics2D g2, RectangleConstraint constraint) { Size2D s = arrangeFN(container, g2, constraint); if (constraint.getHeightRange().contains(s.height)) { return s; } else { RectangleConstraint c = constraint.toFixedHeight( constraint.getHeightRange().constrain(s.getHeight()) ); return arrangeFF(container, g2, c); } } /** * Arranges the blocks in the container with the overall height and width * specified as fixed constraints. * * @param container the container. * @param constraint the constraint. * @param g2 the graphics device. * * @return The size following the arrangement. */ protected Size2D arrangeFF(BlockContainer container, Graphics2D g2, RectangleConstraint constraint) { // TODO: implement this properly return arrangeFN(container, g2, constraint); } /** * Arranges the blocks with the overall width and height to fit within * specified ranges. * * @param container the container. * @param constraint the constraint. * @param g2 the graphics device. * * @return The size after the arrangement. */ protected Size2D arrangeRR(BlockContainer container, Graphics2D g2, RectangleConstraint constraint) { // first arrange without constraints, and see if this fits within // the required ranges... Size2D s1 = arrangeNN(container, g2); if (constraint.getWidthRange().contains(s1.width)) { return s1; // TODO: we didn't check the height yet } else { RectangleConstraint c = constraint.toFixedWidth( constraint.getWidthRange().getUpperBound() ); return arrangeFR(container, g2, c); } } /** * Arranges the blocks in the container with a range constraint on the * width and a fixed height. * * @param container the container. * @param constraint the constraint. * @param g2 the graphics device. * * @return The size following the arrangement. */ protected Size2D arrangeRF(BlockContainer container, Graphics2D g2, RectangleConstraint constraint) { Size2D s = arrangeNF(container, g2, constraint); if (constraint.getWidthRange().contains(s.width)) { return s; } else { RectangleConstraint c = constraint.toFixedWidth( constraint.getWidthRange().constrain(s.getWidth()) ); return arrangeFF(container, g2, c); } } /** * Arranges the block with a range constraint on the width, and no * constraint on the height. * * @param container the container. * @param constraint the constraint. * @param g2 the graphics device. * * @return The size following the arrangement. */ protected Size2D arrangeRN(BlockContainer container, Graphics2D g2, RectangleConstraint constraint) { // first arrange without constraints, then see if the width fits // within the required range...if not, call arrangeFN() at max width Size2D s1 = arrangeNN(container, g2); if (constraint.getWidthRange().contains(s1.width)) { return s1; } else { RectangleConstraint c = constraint.toFixedWidth( constraint.getWidthRange().getUpperBound() ); return arrangeFN(container, g2, c); } } /** * Arranges the blocks without any constraints. This puts all blocks * into a single row. * * @param container the container. * @param g2 the graphics device. * * @return The size after the arrangement. */ protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) { double x = 0.0; double width = 0.0; double maxHeight = 0.0; List<Block> blocks = container.getBlocks(); int blockCount = blocks.size(); if (blockCount > 0) { Size2D[] sizes = new Size2D[blocks.size()]; for (int i = 0; i < blocks.size(); i++) { Block block = blocks.get(i); sizes[i] = block.arrange(g2, RectangleConstraint.NONE); width = width + sizes[i].getWidth(); maxHeight = Math.max(sizes[i].height, maxHeight); block.setBounds( new Rectangle2D.Double( x, 0.0, sizes[i].width, sizes[i].height ) ); x = x + sizes[i].width + this.horizontalGap; } if (blockCount > 1) { width = width + this.horizontalGap * (blockCount - 1); } if (this.verticalAlignment != VerticalAlignment.TOP) { for (Block block : blocks) { //Block b = (Block) blocks.get(i); if (this.verticalAlignment == VerticalAlignment.CENTER) { //TODO: shift block down by half } else if (this.verticalAlignment == VerticalAlignment.BOTTOM) { //TODO: shift block down to bottom } } } } return new Size2D(width, maxHeight); } /** * Arranges the blocks with no width constraint and a fixed height * constraint. This puts all blocks into a single row. * * @param container the container. * @param constraint the constraint. * @param g2 the graphics device. * * @return The size after the arrangement. */ protected Size2D arrangeNF(BlockContainer container, Graphics2D g2, RectangleConstraint constraint) { // TODO: for now we are ignoring the height constraint return arrangeNN(container, g2); } /** * Clears any cached information. */ @Override public void clear() { // no action required. } /** * Tests this instance for equality with an arbitrary object. * * @param obj the object (<code>null</code> permitted). * * @return A boolean. */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof FlowArrangement)) { return false; } FlowArrangement that = (FlowArrangement) obj; if (this.horizontalAlignment != that.horizontalAlignment) { return false; } if (this.verticalAlignment != that.verticalAlignment) { return false; } if (this.horizontalGap != that.horizontalGap) { return false; } if (this.verticalGap != that.verticalGap) { return false; } return true; } }