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 * GridArrangement.java
029 * --------------------
030 * (C) Copyright 2005-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Tracy Hiltbrand (define hashCode);
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.Iterator;
043import java.util.List;
044import org.jfree.chart.ui.Size2D;
045
046/**
047 * Arranges blocks in a grid within their container.
048 */
049public class GridArrangement implements Arrangement, Serializable {
050
051    /** For serialization. */
052    private static final long serialVersionUID = -2563758090144655938L;
053
054    /** The rows. */
055    private int rows;
056
057    /** The columns. */
058    private int columns;
059
060    /**
061     * Creates a new grid arrangement.
062     *
063     * @param rows  the row count.
064     * @param columns  the column count.
065     */
066    public GridArrangement(int rows, int columns) {
067        this.rows = rows;
068        this.columns = columns;
069    }
070
071    /**
072     * Adds a block and a key which can be used to determine the position of
073     * the block in the arrangement.  This method is called by the container
074     * (you don't need to call this method directly) and gives the arrangement
075     * an opportunity to record the details if they are required.
076     *
077     * @param block  the block.
078     * @param key  the key ({@code null} permitted).
079     */
080    @Override
081    public void add(Block block, Object key) {
082        // can safely ignore
083    }
084
085    /**
086     * Arranges the blocks within the specified container, subject to the given
087     * constraint.
088     *
089     * @param container  the container ({@code null} not permitted).
090     * @param constraint  the constraint.
091     * @param g2  the graphics device.
092     *
093     * @return The size following the arrangement.
094     */
095    @Override
096    public Size2D arrange(BlockContainer container, Graphics2D g2,
097                          RectangleConstraint constraint) {
098        LengthConstraintType w = constraint.getWidthConstraintType();
099        LengthConstraintType h = constraint.getHeightConstraintType();
100        if (w == LengthConstraintType.NONE) {
101            if (h == LengthConstraintType.NONE) {
102                return arrangeNN(container, g2);
103            }
104            else if (h == LengthConstraintType.FIXED) {
105                return arrangeNF(container, g2, constraint);
106            }
107            else if (h == LengthConstraintType.RANGE) {
108                // find optimum height, then map to range
109                return arrangeNR(container, g2, constraint);
110            }
111        }
112        else if (w == LengthConstraintType.FIXED) {
113            if (h == LengthConstraintType.NONE) {
114                // find optimum height
115                return arrangeFN(container, g2, constraint);
116            }
117            else if (h == LengthConstraintType.FIXED) {
118                return arrangeFF(container, g2, constraint);
119            }
120            else if (h == LengthConstraintType.RANGE) {
121                // find optimum height and map to range
122                return arrangeFR(container, g2, constraint);
123            }
124        }
125        else if (w == LengthConstraintType.RANGE) {
126            // find optimum width and map to range
127            if (h == LengthConstraintType.NONE) {
128                // find optimum height
129                return arrangeRN(container, g2, constraint);
130            }
131            else if (h == LengthConstraintType.FIXED) {
132                // fixed width
133                return arrangeRF(container, g2, constraint);
134            }
135            else if (h == LengthConstraintType.RANGE) {
136                return arrangeRR(container, g2, constraint);
137            }
138        }
139        throw new RuntimeException("Should never get to here!");
140    }
141
142    /**
143     * Arranges the container with no constraint on the width or height.
144     *
145     * @param container  the container ({@code null} not permitted).
146     * @param g2  the graphics device.
147     *
148     * @return The size.
149     */
150    protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) {
151        double maxW = 0.0;
152        double maxH = 0.0;
153        List blocks = container.getBlocks();
154        Iterator iterator = blocks.iterator();
155        while (iterator.hasNext()) {
156            Block b = (Block) iterator.next();
157            if (b != null) {
158                Size2D s = b.arrange(g2, RectangleConstraint.NONE);
159                maxW = Math.max(maxW, s.width);
160                maxH = Math.max(maxH, s.height);
161            }
162        }
163        double width = this.columns * maxW;
164        double height = this.rows * maxH;
165        RectangleConstraint c = new RectangleConstraint(width, height);
166        return arrangeFF(container, g2, c);
167    }
168
169    /**
170     * Arranges the container with a fixed overall width and height.
171     *
172     * @param container  the container ({@code null} not permitted).
173     * @param g2  the graphics device.
174     * @param constraint  the constraint ({@code null} not permitted).
175     *
176     * @return The size following the arrangement.
177     */
178    protected Size2D arrangeFF(BlockContainer container, Graphics2D g2,
179                               RectangleConstraint constraint) {
180        double width = constraint.getWidth() / this.columns;
181        double height = constraint.getHeight() / this.rows;
182        List blocks = container.getBlocks();
183        for (int c = 0; c < this.columns; c++) {
184            for (int r = 0; r < this.rows; r++) {
185                int index = r * this.columns + c;
186                if (index >= blocks.size()) {
187                    break;
188                }
189                Block b = (Block) blocks.get(index);
190                if (b != null) {
191                    b.setBounds(new Rectangle2D.Double(c * width, r * height,
192                            width, height));
193                }
194            }
195        }
196        return new Size2D(this.columns * width, this.rows * height);
197    }
198
199    /**
200     * Arrange with a fixed width and a height within a given range.
201     *
202     * @param container  the container.
203     * @param constraint  the constraint.
204     * @param g2  the graphics device.
205     *
206     * @return The size of the arrangement.
207     */
208    protected Size2D arrangeFR(BlockContainer container, Graphics2D g2,
209                               RectangleConstraint constraint) {
210
211        RectangleConstraint c1 = constraint.toUnconstrainedHeight();
212        Size2D size1 = arrange(container, g2, c1);
213
214        if (constraint.getHeightRange().contains(size1.getHeight())) {
215            return size1;
216        }
217        else {
218            double h = constraint.getHeightRange().constrain(size1.getHeight());
219            RectangleConstraint c2 = constraint.toFixedHeight(h);
220            return arrange(container, g2, c2);
221        }
222    }
223
224    /**
225     * Arrange with a fixed height and a width within a given range.
226     *
227     * @param container  the container.
228     * @param constraint  the constraint.
229     * @param g2  the graphics device.
230     *
231     * @return The size of the arrangement.
232     */
233    protected Size2D arrangeRF(BlockContainer container, Graphics2D g2,
234                               RectangleConstraint constraint) {
235
236        RectangleConstraint c1 = constraint.toUnconstrainedWidth();
237        Size2D size1 = arrange(container, g2, c1);
238
239        if (constraint.getWidthRange().contains(size1.getWidth())) {
240            return size1;
241        }
242        else {
243            double w = constraint.getWidthRange().constrain(size1.getWidth());
244            RectangleConstraint c2 = constraint.toFixedWidth(w);
245            return arrange(container, g2, c2);
246        }
247    }
248
249    /**
250     * Arrange with a fixed width and no height constraint.
251     *
252     * @param container  the container.
253     * @param constraint  the constraint.
254     * @param g2  the graphics device.
255     *
256     * @return The size of the arrangement.
257     */
258    protected Size2D arrangeRN(BlockContainer container, Graphics2D g2,
259                               RectangleConstraint constraint) {
260
261        RectangleConstraint c1 = constraint.toUnconstrainedWidth();
262        Size2D size1 = arrange(container, g2, c1);
263
264        if (constraint.getWidthRange().contains(size1.getWidth())) {
265            return size1;
266        }
267        else {
268            double w = constraint.getWidthRange().constrain(size1.getWidth());
269            RectangleConstraint c2 = constraint.toFixedWidth(w);
270            return arrange(container, g2, c2);
271        }
272    }
273
274    /**
275     * Arrange with a fixed height and no width constraint.
276     *
277     * @param container  the container.
278     * @param constraint  the constraint.
279     * @param g2  the graphics device.
280     *
281     * @return The size of the arrangement.
282     */
283    protected Size2D arrangeNR(BlockContainer container, Graphics2D g2,
284                               RectangleConstraint constraint) {
285
286        RectangleConstraint c1 = constraint.toUnconstrainedHeight();
287        Size2D size1 = arrange(container, g2, c1);
288
289        if (constraint.getHeightRange().contains(size1.getHeight())) {
290            return size1;
291        }
292        else {
293            double h = constraint.getHeightRange().constrain(size1.getHeight());
294            RectangleConstraint c2 = constraint.toFixedHeight(h);
295            return arrange(container, g2, c2);
296        }
297    }
298
299    /**
300     * Arrange with ranges for both the width and height constraints.
301     *
302     * @param container  the container.
303     * @param constraint  the constraint.
304     * @param g2  the graphics device.
305     *
306     * @return The size of the arrangement.
307     */
308    protected Size2D arrangeRR(BlockContainer container, Graphics2D g2,
309                               RectangleConstraint constraint) {
310
311        Size2D size1 = arrange(container, g2, RectangleConstraint.NONE);
312
313        if (constraint.getWidthRange().contains(size1.getWidth())) {
314            if (constraint.getHeightRange().contains(size1.getHeight())) {
315                return size1;
316            }
317            else {
318                // width is OK, but height must be constrained
319                double h = constraint.getHeightRange().constrain(
320                        size1.getHeight());
321                RectangleConstraint cc = new RectangleConstraint(
322                        size1.getWidth(), h);
323                return arrangeFF(container, g2, cc);
324            }
325        }
326        else {
327            if (constraint.getHeightRange().contains(size1.getHeight())) {
328                // height is OK, but width must be constrained
329                double w = constraint.getWidthRange().constrain(
330                        size1.getWidth());
331                RectangleConstraint cc = new RectangleConstraint(w,
332                        size1.getHeight());
333                return arrangeFF(container, g2, cc);
334
335            }
336            else {
337                double w = constraint.getWidthRange().constrain(
338                        size1.getWidth());
339                double h = constraint.getHeightRange().constrain(
340                        size1.getHeight());
341                RectangleConstraint cc = new RectangleConstraint(w, h);
342                return arrangeFF(container, g2, cc);
343            }
344        }
345    }
346
347    /**
348     * Arrange with a fixed width and a height within a given range.
349     *
350     * @param container  the container.
351     * @param g2  the graphics device.
352     * @param constraint  the constraint.
353     *
354     * @return The size of the arrangement.
355     */
356    protected Size2D arrangeFN(BlockContainer container, Graphics2D g2,
357                               RectangleConstraint constraint) {
358
359        double width = constraint.getWidth() / this.columns;
360        RectangleConstraint bc = constraint.toFixedWidth(width);
361        List blocks = container.getBlocks();
362        double maxH = 0.0;
363        for (int r = 0; r < this.rows; r++) {
364            for (int c = 0; c < this.columns; c++) {
365                int index = r * this.columns + c;
366                if (index >= blocks.size()) {
367                    break;
368                }
369                Block b = (Block) blocks.get(index);
370                if (b != null) {
371                    Size2D s = b.arrange(g2, bc);
372                    maxH = Math.max(maxH, s.getHeight());
373                }
374            }
375        }
376        RectangleConstraint cc = constraint.toFixedHeight(maxH * this.rows);
377        return arrange(container, g2, cc);
378    }
379
380    /**
381     * Arrange with a fixed height and no constraint for the width.
382     *
383     * @param container  the container.
384     * @param g2  the graphics device.
385     * @param constraint  the constraint.
386     *
387     * @return The size of the arrangement.
388     */
389    protected Size2D arrangeNF(BlockContainer container, Graphics2D g2,
390                               RectangleConstraint constraint) {
391
392        double height = constraint.getHeight() / this.rows;
393        RectangleConstraint bc = constraint.toFixedHeight(height);
394        List blocks = container.getBlocks();
395        double maxW = 0.0;
396        for (int r = 0; r < this.rows; r++) {
397            for (int c = 0; c < this.columns; c++) {
398                int index = r * this.columns + c;
399                if (index >= blocks.size()) {
400                    break;
401                }
402                Block b = (Block) blocks.get(index);
403                if (b != null) {
404                    Size2D s = b.arrange(g2, bc);
405                    maxW = Math.max(maxW, s.getWidth());
406                }
407            }
408        }
409        RectangleConstraint cc = constraint.toFixedWidth(maxW * this.columns);
410        return arrange(container, g2, cc);
411    }
412
413    /**
414     * Clears any cached layout information retained by the arrangement.
415     */
416    @Override
417    public void clear() {
418        // nothing to clear
419    }
420
421    /**
422     * Compares this layout manager for equality with an arbitrary object.
423     *
424     * @param obj  the object.
425     *
426     * @return A boolean.
427     */
428    @Override
429    public boolean equals(Object obj) {
430        if (obj == this) {
431            return true;
432        }
433        if (!(obj instanceof GridArrangement)) {
434            return false;
435        }
436        GridArrangement that = (GridArrangement) obj;
437        if (this.columns != that.columns) {
438            return false;
439        }
440        if (this.rows != that.rows) {
441            return false;
442        }
443        return true;
444    }
445
446    @Override
447    public int hashCode() {
448        int hash = 7;
449        hash = 29 * hash + this.rows;
450        hash = 29 * hash + this.columns;
451        return hash;
452    }
453
454}