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