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