001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2013, 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    @Override
107    public void add(Block block, Object key) {
108        // since the flow layout is relatively straightforward,
109        // no information needs to be recorded here
110    }
111
112    /**
113     * Calculates and sets the bounds of all the items in the specified
114     * container, subject to the given constraint.  The <code>Graphics2D</code>
115     * can be used by some items (particularly items containing text) to
116     * calculate sizing parameters.
117     *
118     * @param container  the container whose items are being arranged.
119     * @param constraint  the size constraint.
120     * @param g2  the graphics device.
121     *
122     * @return The size of the container after arrangement of the contents.
123     */
124    @Override
125    public Size2D arrange(BlockContainer container, Graphics2D g2,
126                          RectangleConstraint constraint) {
127
128        LengthConstraintType w = constraint.getWidthConstraintType();
129        LengthConstraintType h = constraint.getHeightConstraintType();
130        if (w == LengthConstraintType.NONE) {
131            if (h == LengthConstraintType.NONE) {
132                return arrangeNN(container, g2);
133            }
134            else if (h == LengthConstraintType.FIXED) {
135                return arrangeNF(container, g2, constraint);
136            }
137            else if (h == LengthConstraintType.RANGE) {
138                throw new RuntimeException("Not implemented.");
139            }
140        }
141        else if (w == LengthConstraintType.FIXED) {
142            if (h == LengthConstraintType.NONE) {
143                return arrangeFN(container, g2, constraint);
144            }
145            else if (h == LengthConstraintType.FIXED) {
146                return arrangeFF(container, g2, constraint);
147            }
148            else if (h == LengthConstraintType.RANGE) {
149                return arrangeFR(container, g2, constraint);
150            }
151        }
152        else if (w == LengthConstraintType.RANGE) {
153            if (h == LengthConstraintType.NONE) {
154                return arrangeRN(container, g2, constraint);
155            }
156            else if (h == LengthConstraintType.FIXED) {
157                return arrangeRF(container, g2, constraint);
158            }
159            else if (h == LengthConstraintType.RANGE) {
160                return arrangeRR(container, g2, constraint);
161            }
162        }
163        throw new RuntimeException("Unrecognised constraint type.");
164
165    }
166
167    /**
168     * Arranges the blocks in the container with a fixed width and no height
169     * constraint.
170     *
171     * @param container  the container.
172     * @param constraint  the constraint.
173     * @param g2  the graphics device.
174     *
175     * @return The size.
176     */
177    protected Size2D arrangeFN(BlockContainer container, Graphics2D g2,
178                               RectangleConstraint constraint) {
179
180        List blocks = container.getBlocks();
181        double width = constraint.getWidth();
182
183        double x = 0.0;
184        double y = 0.0;
185        double maxHeight = 0.0;
186        List itemsInRow = new ArrayList();
187        for (int i = 0; i < blocks.size(); i++) {
188            Block block = (Block) blocks.get(i);
189            Size2D size = block.arrange(g2, RectangleConstraint.NONE);
190            if (x + size.width <= width) {
191                itemsInRow.add(block);
192                block.setBounds(
193                    new Rectangle2D.Double(x, y, size.width, size.height)
194                );
195                x = x + size.width + this.horizontalGap;
196                maxHeight = Math.max(maxHeight, size.height);
197            }
198            else {
199                if (itemsInRow.isEmpty()) {
200                    // place in this row (truncated) anyway
201                    block.setBounds(
202                        new Rectangle2D.Double(
203                            x, y, Math.min(size.width, width - x), size.height
204                        )
205                    );
206                    x = 0.0;
207                    y = y + size.height + this.verticalGap;
208                }
209                else {
210                    // start new row
211                    itemsInRow.clear();
212                    x = 0.0;
213                    y = y + maxHeight + this.verticalGap;
214                    maxHeight = size.height;
215                    block.setBounds(
216                        new Rectangle2D.Double(
217                            x, y, Math.min(size.width, width), size.height
218                        )
219                    );
220                    x = size.width + this.horizontalGap;
221                    itemsInRow.add(block);
222                }
223            }
224        }
225        return new Size2D(constraint.getWidth(), y + maxHeight);
226    }
227
228    /**
229     * Arranges the blocks in the container with a fixed width and a range
230     * constraint on the height.
231     *
232     * @param container  the container.
233     * @param constraint  the constraint.
234     * @param g2  the graphics device.
235     *
236     * @return The size following the arrangement.
237     */
238    protected Size2D arrangeFR(BlockContainer container, Graphics2D g2,
239                               RectangleConstraint constraint) {
240
241        Size2D s = arrangeFN(container, g2, constraint);
242        if (constraint.getHeightRange().contains(s.height)) {
243            return s;
244        }
245        else {
246            RectangleConstraint c = constraint.toFixedHeight(
247                constraint.getHeightRange().constrain(s.getHeight())
248            );
249            return arrangeFF(container, g2, c);
250        }
251    }
252
253    /**
254     * Arranges the blocks in the container with the overall height and width
255     * specified as fixed constraints.
256     *
257     * @param container  the container.
258     * @param constraint  the constraint.
259     * @param g2  the graphics device.
260     *
261     * @return The size following the arrangement.
262     */
263    protected Size2D arrangeFF(BlockContainer container, Graphics2D g2,
264                               RectangleConstraint constraint) {
265
266        // TODO: implement this properly
267        return arrangeFN(container, g2, constraint);
268    }
269
270    /**
271     * Arranges the blocks with the overall width and height to fit within
272     * specified ranges.
273     *
274     * @param container  the container.
275     * @param constraint  the constraint.
276     * @param g2  the graphics device.
277     *
278     * @return The size after the arrangement.
279     */
280    protected Size2D arrangeRR(BlockContainer container, Graphics2D g2,
281                               RectangleConstraint constraint) {
282
283        // first arrange without constraints, and see if this fits within
284        // the required ranges...
285        Size2D s1 = arrangeNN(container, g2);
286        if (constraint.getWidthRange().contains(s1.width)) {
287            return s1;  // TODO: we didn't check the height yet
288        }
289        else {
290            RectangleConstraint c = constraint.toFixedWidth(
291                constraint.getWidthRange().getUpperBound()
292            );
293            return arrangeFR(container, g2, c);
294        }
295    }
296
297    /**
298     * Arranges the blocks in the container with a range constraint on the
299     * width and a fixed height.
300     *
301     * @param container  the container.
302     * @param constraint  the constraint.
303     * @param g2  the graphics device.
304     *
305     * @return The size following the arrangement.
306     */
307    protected Size2D arrangeRF(BlockContainer container, Graphics2D g2,
308                               RectangleConstraint constraint) {
309
310        Size2D s = arrangeNF(container, g2, constraint);
311        if (constraint.getWidthRange().contains(s.width)) {
312            return s;
313        }
314        else {
315            RectangleConstraint c = constraint.toFixedWidth(
316                constraint.getWidthRange().constrain(s.getWidth())
317            );
318            return arrangeFF(container, g2, c);
319        }
320    }
321
322    /**
323     * Arranges the block with a range constraint on the width, and no
324     * constraint on the height.
325     *
326     * @param container  the container.
327     * @param constraint  the constraint.
328     * @param g2  the graphics device.
329     *
330     * @return The size following the arrangement.
331     */
332    protected Size2D arrangeRN(BlockContainer container, Graphics2D g2,
333                               RectangleConstraint constraint) {
334        // first arrange without constraints, then see if the width fits
335        // within the required range...if not, call arrangeFN() at max width
336        Size2D s1 = arrangeNN(container, g2);
337        if (constraint.getWidthRange().contains(s1.width)) {
338            return s1;
339        }
340        else {
341            RectangleConstraint c = constraint.toFixedWidth(
342                constraint.getWidthRange().getUpperBound()
343            );
344            return arrangeFN(container, g2, c);
345        }
346    }
347
348    /**
349     * Arranges the blocks without any constraints.  This puts all blocks
350     * into a single row.
351     *
352     * @param container  the container.
353     * @param g2  the graphics device.
354     *
355     * @return The size after the arrangement.
356     */
357    protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) {
358        double x = 0.0;
359        double width = 0.0;
360        double maxHeight = 0.0;
361        List blocks = container.getBlocks();
362        int blockCount = blocks.size();
363        if (blockCount > 0) {
364            Size2D[] sizes = new Size2D[blocks.size()];
365            for (int i = 0; i < blocks.size(); i++) {
366                Block block = (Block) blocks.get(i);
367                sizes[i] = block.arrange(g2, RectangleConstraint.NONE);
368                width = width + sizes[i].getWidth();
369                maxHeight = Math.max(sizes[i].height, maxHeight);
370                block.setBounds(
371                    new Rectangle2D.Double(
372                        x, 0.0, sizes[i].width, sizes[i].height
373                    )
374                );
375                x = x + sizes[i].width + this.horizontalGap;
376            }
377            if (blockCount > 1) {
378                width = width + this.horizontalGap * (blockCount - 1);
379            }
380            if (this.verticalAlignment != VerticalAlignment.TOP) {
381                for (int i = 0; i < blocks.size(); i++) {
382                    //Block b = (Block) blocks.get(i);
383                    if (this.verticalAlignment == VerticalAlignment.CENTER) {
384                        //TODO: shift block down by half
385                    }
386                    else if (this.verticalAlignment
387                            == VerticalAlignment.BOTTOM) {
388                        //TODO: shift block down to bottom
389                    }
390                }
391            }
392        }
393        return new Size2D(width, maxHeight);
394    }
395
396    /**
397     * Arranges the blocks with no width constraint and a fixed height
398     * constraint.  This puts all blocks into a single row.
399     *
400     * @param container  the container.
401     * @param constraint  the constraint.
402     * @param g2  the graphics device.
403     *
404     * @return The size after the arrangement.
405     */
406    protected Size2D arrangeNF(BlockContainer container, Graphics2D g2,
407                               RectangleConstraint constraint) {
408        // TODO: for now we are ignoring the height constraint
409        return arrangeNN(container, g2);
410    }
411
412    /**
413     * Clears any cached information.
414     */
415    @Override
416    public void clear() {
417        // no action required.
418    }
419
420    /**
421     * Tests this instance for equality with an arbitrary object.
422     *
423     * @param obj  the object (<code>null</code> permitted).
424     *
425     * @return A boolean.
426     */
427    @Override
428    public boolean equals(Object obj) {
429        if (obj == this) {
430            return true;
431        }
432        if (!(obj instanceof FlowArrangement)) {
433            return false;
434        }
435        FlowArrangement that = (FlowArrangement) obj;
436        if (this.horizontalAlignment != that.horizontalAlignment) {
437            return false;
438        }
439        if (this.verticalAlignment != that.verticalAlignment) {
440            return false;
441        }
442        if (this.horizontalGap != that.horizontalGap) {
443            return false;
444        }
445        if (this.verticalGap != that.verticalGap) {
446            return false;
447        }
448        return true;
449    }
450
451}