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