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 * 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    @Override
106    public void add(Block block, Object key) {
107        // since the flow layout is relatively straightforward, no information
108        // 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 g2  the graphics device.
119     * @param constraint  the size constraint.
120     *
121     * @return The size of the container after arrangement of the contents.
122     */
123    @Override
124    public Size2D arrange(BlockContainer container, Graphics2D g2,
125                          RectangleConstraint constraint) {
126
127        LengthConstraintType w = constraint.getWidthConstraintType();
128        LengthConstraintType h = constraint.getHeightConstraintType();
129        if (w == LengthConstraintType.NONE) {
130            if (h == LengthConstraintType.NONE) {
131                return arrangeNN(container, g2);
132            }
133            else if (h == LengthConstraintType.FIXED) {
134                throw new RuntimeException("Not implemented.");
135            }
136            else if (h == LengthConstraintType.RANGE) {
137                throw new RuntimeException("Not implemented.");
138            }
139        }
140        else if (w == LengthConstraintType.FIXED) {
141            if (h == LengthConstraintType.NONE) {
142                throw new RuntimeException("Not implemented.");
143            }
144            else if (h == LengthConstraintType.FIXED) {
145                return arrangeFF(container, g2, constraint);
146            }
147            else if (h == LengthConstraintType.RANGE) {
148                throw new RuntimeException("Not implemented.");
149            }
150        }
151        else if (w == LengthConstraintType.RANGE) {
152            if (h == LengthConstraintType.NONE) {
153                throw new RuntimeException("Not implemented.");
154            }
155            else if (h == LengthConstraintType.FIXED) {
156                return arrangeRF(container, g2, constraint);
157            }
158            else if (h == LengthConstraintType.RANGE) {
159                return arrangeRR(container, g2, constraint);
160            }
161        }
162        return new Size2D();  // TODO: complete this
163
164    }
165
166    /**
167     * Calculates and sets the bounds of all the items in the specified
168     * container, subject to the given constraint.  The <code>Graphics2D</code>
169     * can be used by some items (particularly items containing text) to
170     * calculate sizing parameters.
171     *
172     * @param container  the container whose items are being arranged.
173     * @param g2  the graphics device.
174     * @param constraint  the size constraint.
175     *
176     * @return The container size after the arrangement.
177     */
178    protected Size2D arrangeFF(BlockContainer container, Graphics2D g2,
179                               RectangleConstraint constraint) {
180        // TODO: implement properly
181        return arrangeNF(container, g2, constraint);
182    }
183
184    /**
185     * Calculates and sets the bounds of all the items in the specified
186     * container, subject to the given constraint.  The <code>Graphics2D</code>
187     * can be used by some items (particularly items containing text) to
188     * calculate sizing parameters.
189     *
190     * @param container  the container whose items are being arranged.
191     * @param constraint  the size constraint.
192     * @param g2  the graphics device.
193     *
194     * @return The container size after the arrangement.
195     */
196    protected Size2D arrangeNF(BlockContainer container, Graphics2D g2,
197                               RectangleConstraint constraint) {
198
199        List blocks = container.getBlocks();
200
201        double height = constraint.getHeight();
202        if (height <= 0.0) {
203            height = Double.POSITIVE_INFINITY;
204        }
205
206        double x = 0.0;
207        double y = 0.0;
208        double maxWidth = 0.0;
209        List itemsInColumn = new ArrayList();
210        for (int i = 0; i < blocks.size(); i++) {
211            Block block = (Block) blocks.get(i);
212            Size2D size = block.arrange(g2, RectangleConstraint.NONE);
213            if (y + size.height <= height) {
214                itemsInColumn.add(block);
215                block.setBounds(
216                    new Rectangle2D.Double(x, y, size.width, size.height)
217                );
218                y = y + size.height + this.verticalGap;
219                maxWidth = Math.max(maxWidth, size.width);
220            }
221            else {
222                if (itemsInColumn.isEmpty()) {
223                    // place in this column (truncated) anyway
224                    block.setBounds(
225                        new Rectangle2D.Double(
226                            x, y, size.width, Math.min(size.height, height - y)
227                        )
228                    );
229                    y = 0.0;
230                    x = x + size.width + this.horizontalGap;
231                }
232                else {
233                    // start new column
234                    itemsInColumn.clear();
235                    x = x + maxWidth + this.horizontalGap;
236                    y = 0.0;
237                    maxWidth = size.width;
238                    block.setBounds(
239                        new Rectangle2D.Double(
240                            x, y, size.width, Math.min(size.height, height)
241                        )
242                    );
243                    y = size.height + this.verticalGap;
244                    itemsInColumn.add(block);
245                }
246            }
247        }
248        return new Size2D(x + maxWidth, constraint.getHeight());
249    }
250
251    /**
252     * Arranges a container with range constraints for both the horizontal
253     * and vertical.
254     *
255     * @param container  the container.
256     * @param g2  the graphics device.
257     * @param constraint  the constraint.
258     *
259     * @return The size of the container.
260     */
261    protected Size2D arrangeRR(BlockContainer container, Graphics2D g2,
262                               RectangleConstraint constraint) {
263
264        // first arrange without constraints, and see if this fits within
265        // the required ranges...
266        Size2D s1 = arrangeNN(container, g2);
267        if (constraint.getHeightRange().contains(s1.height)) {
268            return s1;  // TODO: we didn't check the width yet
269        }
270        else {
271            RectangleConstraint c = constraint.toFixedHeight(
272                constraint.getHeightRange().getUpperBound()
273            );
274            return arrangeRF(container, g2, c);
275        }
276    }
277
278    /**
279     * Arranges the blocks in the container using a fixed height and a
280     * range for the width.
281     *
282     * @param container  the container.
283     * @param g2  the graphics device.
284     * @param constraint  the constraint.
285     *
286     * @return The size of the container after arrangement.
287     */
288    protected Size2D arrangeRF(BlockContainer container, Graphics2D g2,
289                               RectangleConstraint constraint) {
290
291        Size2D s = arrangeNF(container, g2, constraint);
292        if (constraint.getWidthRange().contains(s.width)) {
293            return s;
294        }
295        else {
296            RectangleConstraint c = constraint.toFixedWidth(
297                constraint.getWidthRange().constrain(s.getWidth())
298            );
299            return arrangeFF(container, g2, c);
300        }
301    }
302
303    /**
304     * Arranges the blocks without any constraints.  This puts all blocks
305     * into a single column.
306     *
307     * @param container  the container.
308     * @param g2  the graphics device.
309     *
310     * @return The size after the arrangement.
311     */
312    protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) {
313        double y = 0.0;
314        double height = 0.0;
315        double maxWidth = 0.0;
316        List blocks = container.getBlocks();
317        int blockCount = blocks.size();
318        if (blockCount > 0) {
319            Size2D[] sizes = new Size2D[blocks.size()];
320            for (int i = 0; i < blocks.size(); i++) {
321                Block block = (Block) blocks.get(i);
322                sizes[i] = block.arrange(g2, RectangleConstraint.NONE);
323                height = height + sizes[i].getHeight();
324                maxWidth = Math.max(sizes[i].width, maxWidth);
325                block.setBounds(
326                    new Rectangle2D.Double(
327                        0.0, y, sizes[i].width, sizes[i].height
328                    )
329                );
330                y = y + sizes[i].height + this.verticalGap;
331            }
332            if (blockCount > 1) {
333                height = height + this.verticalGap * (blockCount - 1);
334            }
335            if (this.horizontalAlignment != HorizontalAlignment.LEFT) {
336                for (int i = 0; i < blocks.size(); i++) {
337                    //Block b = (Block) blocks.get(i);
338                    if (this.horizontalAlignment
339                            == HorizontalAlignment.CENTER) {
340                        //TODO: shift block right by half
341                    }
342                    else if (this.horizontalAlignment
343                            == HorizontalAlignment.RIGHT) {
344                        //TODO: shift block over to right
345                    }
346                }
347            }
348        }
349        return new Size2D(maxWidth, height);
350    }
351
352    /**
353     * Clears any cached information.
354     */
355    @Override
356    public void clear() {
357        // no action required.
358    }
359
360    /**
361     * Tests this instance for equality with an arbitrary object.
362     *
363     * @param obj  the object (<code>null</code> permitted).
364     *
365     * @return A boolean.
366     */
367    @Override
368    public boolean equals(Object obj) {
369        if (obj == this) {
370            return true;
371        }
372        if (!(obj instanceof ColumnArrangement)) {
373            return false;
374        }
375        ColumnArrangement that = (ColumnArrangement) obj;
376        if (this.horizontalAlignment != that.horizontalAlignment) {
377            return false;
378        }
379        if (this.verticalAlignment != that.verticalAlignment) {
380            return false;
381        }
382        if (this.horizontalGap != that.horizontalGap) {
383            return false;
384        }
385        if (this.verticalGap != that.verticalGap) {
386            return false;
387        }
388        return true;
389    }
390
391
392}