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 * BorderArrangement.java
029 * ----------------------
030 * (C) Copyright 2004-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.Objects;
043import org.jfree.chart.ui.RectangleEdge;
044import org.jfree.chart.ui.Size2D;
045
046import org.jfree.data.Range;
047
048/**
049 * An arrangement manager that lays out blocks in a similar way to
050 * Swing's BorderLayout class.
051 */
052public class BorderArrangement implements Arrangement, Serializable {
053
054    /** For serialization. */
055    private static final long serialVersionUID = 506071142274883745L;
056
057    /** The block (if any) at the center of the layout. */
058    private Block centerBlock;
059
060    /** The block (if any) at the top of the layout. */
061    private Block topBlock;
062
063    /** The block (if any) at the bottom of the layout. */
064    private Block bottomBlock;
065
066    /** The block (if any) at the left of the layout. */
067    private Block leftBlock;
068
069    /** The block (if any) at the right of the layout. */
070    private Block rightBlock;
071
072    /**
073     * Creates a new instance.
074     */
075    public BorderArrangement() {
076    }
077
078    /**
079     * Adds a block to the arrangement manager at the specified edge.
080     * If the key is not an instance of {@link RectangleEdge} the block will
081     * be added in the center.
082     *
083     * @param block  the block ({@code null} permitted).
084     * @param key  the edge (an instance of {@link RectangleEdge}) or
085     *             {@code null} for the center block.
086     */
087    @Override
088    public void add(Block block, Object key) {
089
090        if (!(key instanceof RectangleEdge)) { // catches null also
091            this.centerBlock = block;
092        }
093        else {
094            RectangleEdge edge = (RectangleEdge) key;
095            if (edge == RectangleEdge.TOP) {
096                this.topBlock = block;
097            }
098            else if (edge == RectangleEdge.BOTTOM) {
099                this.bottomBlock = block;
100            }
101            else if (edge == RectangleEdge.LEFT) {
102                this.leftBlock = block;
103            }
104            else if (edge == RectangleEdge.RIGHT) {
105                this.rightBlock = block;
106            }
107        }
108    }
109
110    /**
111     * Arranges the items in the specified container, subject to the given
112     * constraint.
113     *
114     * @param container  the container.
115     * @param g2  the graphics device.
116     * @param constraint  the constraint.
117     *
118     * @return The block size.
119     */
120    @Override
121    public Size2D arrange(BlockContainer container, Graphics2D g2,
122            RectangleConstraint constraint) {
123        RectangleConstraint contentConstraint
124                = container.toContentConstraint(constraint);
125        Size2D contentSize = null;
126        LengthConstraintType w = contentConstraint.getWidthConstraintType();
127        LengthConstraintType h = contentConstraint.getHeightConstraintType();
128        if (w == LengthConstraintType.NONE) {
129            if (h == LengthConstraintType.NONE) {
130                contentSize = arrangeNN(container, g2);
131            }
132            else if (h == LengthConstraintType.FIXED) {
133                throw new RuntimeException("Not implemented.");
134            }
135            else if (h == LengthConstraintType.RANGE) {
136                throw new RuntimeException("Not implemented.");
137            }
138        }
139        else if (w == LengthConstraintType.FIXED) {
140            if (h == LengthConstraintType.NONE) {
141                contentSize = arrangeFN(container, g2, constraint.getWidth());
142            }
143            else if (h == LengthConstraintType.FIXED) {
144                contentSize = arrangeFF(container, g2, constraint);
145            }
146            else if (h == LengthConstraintType.RANGE) {
147                contentSize = arrangeFR(container, g2, constraint);
148            }
149        }
150        else if (w == LengthConstraintType.RANGE) {
151            if (h == LengthConstraintType.NONE) {
152                throw new RuntimeException("Not implemented.");
153            }
154            else if (h == LengthConstraintType.FIXED) {
155                throw new RuntimeException("Not implemented.");
156            }
157            else if (h == LengthConstraintType.RANGE) {
158                contentSize = arrangeRR(container, constraint.getWidthRange(),
159                        constraint.getHeightRange(), g2);
160            }
161        }
162        assert contentSize != null; 
163        return new Size2D(container.calculateTotalWidth(contentSize.getWidth()),
164                container.calculateTotalHeight(contentSize.getHeight()));
165    }
166
167    /**
168     * Performs an arrangement without constraints.
169     *
170     * @param container  the container.
171     * @param g2  the graphics device.
172     *
173     * @return The container size after the arrangement.
174     */
175    protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) {
176        double[] w = new double[5];
177        double[] h = new double[5];
178        if (this.topBlock != null) {
179            Size2D size = this.topBlock.arrange(g2, RectangleConstraint.NONE);
180            w[0] = size.width;
181            h[0] = size.height;
182        }
183        if (this.bottomBlock != null) {
184            Size2D size = this.bottomBlock.arrange(g2,
185                    RectangleConstraint.NONE);
186            w[1] = size.width;
187            h[1] = size.height;
188        }
189        if (this.leftBlock != null) {
190            Size2D size = this.leftBlock.arrange(g2, RectangleConstraint.NONE);
191            w[2] = size.width;
192            h[2] = size.height;
193       }
194        if (this.rightBlock != null) {
195            Size2D size = this.rightBlock.arrange(g2, RectangleConstraint.NONE);
196            w[3] = size.width;
197            h[3] = size.height;
198        }
199
200        h[2] = Math.max(h[2], h[3]);
201        h[3] = h[2];
202
203        if (this.centerBlock != null) {
204            Size2D size = this.centerBlock.arrange(g2,
205                    RectangleConstraint.NONE);
206            w[4] = size.width;
207            h[4] = size.height;
208        }
209        double width = Math.max(w[0], Math.max(w[1], w[2] + w[4] + w[3]));
210        double centerHeight = Math.max(h[2], Math.max(h[3], h[4]));
211        double height = h[0] + h[1] + centerHeight;
212        if (this.topBlock != null) {
213            this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, width,
214                    h[0]));
215        }
216        if (this.bottomBlock != null) {
217            this.bottomBlock.setBounds(new Rectangle2D.Double(0.0,
218                    height - h[1], width, h[1]));
219        }
220        if (this.leftBlock != null) {
221            this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2],
222                    centerHeight));
223        }
224        if (this.rightBlock != null) {
225            this.rightBlock.setBounds(new Rectangle2D.Double(width - w[3],
226                    h[0], w[3], centerHeight));
227        }
228
229        if (this.centerBlock != null) {
230            this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0],
231                    width - w[2] - w[3], centerHeight));
232        }
233        return new Size2D(width, height);
234    }
235
236    /**
237     * Performs an arrangement with a fixed width and a range for the height.
238     *
239     * @param container  the container.
240     * @param g2  the graphics device.
241     * @param constraint  the constraint.
242     *
243     * @return The container size after the arrangement.
244     */
245    protected Size2D arrangeFR(BlockContainer container, Graphics2D g2,
246                               RectangleConstraint constraint) {
247        Size2D size1 = arrangeFN(container, g2, constraint.getWidth());
248        if (constraint.getHeightRange().contains(size1.getHeight())) {
249            return size1;
250        }
251        else {
252            double h = constraint.getHeightRange().constrain(size1.getHeight());
253            RectangleConstraint c2 = constraint.toFixedHeight(h);
254            return arrange(container, g2, c2);
255        }
256    }
257
258    /**
259     * Arranges the container width a fixed width and no constraint on the
260     * height.
261     *
262     * @param container  the container.
263     * @param g2  the graphics device.
264     * @param width  the fixed width.
265     *
266     * @return The container size after arranging the contents.
267     */
268    protected Size2D arrangeFN(BlockContainer container, Graphics2D g2,
269                               double width) {
270        double[] w = new double[5];
271        double[] h = new double[5];
272        RectangleConstraint c1 = new RectangleConstraint(width, null,
273                LengthConstraintType.FIXED, 0.0, null,
274                LengthConstraintType.NONE);
275        if (this.topBlock != null) {
276            Size2D size = this.topBlock.arrange(g2, c1);
277            w[0] = size.width;
278            h[0] = size.height;
279        }
280        if (this.bottomBlock != null) {
281            Size2D size = this.bottomBlock.arrange(g2, c1);
282            w[1] = size.width;
283            h[1] = size.height;
284        }
285        RectangleConstraint c2 = new RectangleConstraint(0.0,
286                new Range(0.0, width), LengthConstraintType.RANGE,
287                0.0, null, LengthConstraintType.NONE);
288        if (this.leftBlock != null) {
289            Size2D size = this.leftBlock.arrange(g2, c2);
290            w[2] = size.width;
291            h[2] = size.height;
292        }
293        if (this.rightBlock != null) {
294            double maxW = Math.max(width - w[2], 0.0);
295            RectangleConstraint c3 = new RectangleConstraint(0.0,
296                    new Range(Math.min(w[2], maxW), maxW),
297                    LengthConstraintType.RANGE, 0.0, null,
298                    LengthConstraintType.NONE);
299            Size2D size = this.rightBlock.arrange(g2, c3);
300            w[3] = size.width;
301            h[3] = size.height;
302        }
303
304        h[2] = Math.max(h[2], h[3]);
305        h[3] = h[2];
306
307        if (this.centerBlock != null) {
308            RectangleConstraint c4 = new RectangleConstraint(width - w[2]
309                    - w[3], null, LengthConstraintType.FIXED, 0.0, null,
310                    LengthConstraintType.NONE);
311            Size2D size = this.centerBlock.arrange(g2, c4);
312            w[4] = size.width;
313            h[4] = size.height;
314        }
315        double height = h[0] + h[1] + Math.max(h[2], Math.max(h[3], h[4]));
316        return arrange(container, g2, new RectangleConstraint(width, height));
317    }
318
319    /**
320     * Performs an arrangement with range constraints on both the vertical
321     * and horizontal sides.
322     *
323     * @param container  the container.
324     * @param widthRange  the allowable range for the container width.
325     * @param heightRange  the allowable range for the container height.
326     * @param g2  the graphics device.
327     *
328     * @return The container size.
329     */
330    protected Size2D arrangeRR(BlockContainer container,
331                               Range widthRange, Range heightRange,
332                               Graphics2D g2) {
333        double[] w = new double[5];
334        double[] h = new double[5];
335        if (this.topBlock != null) {
336            RectangleConstraint c1 = new RectangleConstraint(widthRange,
337                    heightRange);
338            Size2D size = this.topBlock.arrange(g2, c1);
339            w[0] = size.width;
340            h[0] = size.height;
341        }
342        if (this.bottomBlock != null) {
343            Range heightRange2 = Range.shift(heightRange, -h[0], false);
344            RectangleConstraint c2 = new RectangleConstraint(widthRange,
345                    heightRange2);
346            Size2D size = this.bottomBlock.arrange(g2, c2);
347            w[1] = size.width;
348            h[1] = size.height;
349        }
350        Range heightRange3 = Range.shift(heightRange, -(h[0] + h[1]));
351        if (this.leftBlock != null) {
352            RectangleConstraint c3 = new RectangleConstraint(widthRange,
353                    heightRange3);
354            Size2D size = this.leftBlock.arrange(g2, c3);
355            w[2] = size.width;
356            h[2] = size.height;
357        }
358        Range widthRange2 = Range.shift(widthRange, -w[2], false);
359        if (this.rightBlock != null) {
360            RectangleConstraint c4 = new RectangleConstraint(widthRange2,
361                    heightRange3);
362            Size2D size = this.rightBlock.arrange(g2, c4);
363            w[3] = size.width;
364            h[3] = size.height;
365        }
366
367        h[2] = Math.max(h[2], h[3]);
368        h[3] = h[2];
369        Range widthRange3 = Range.shift(widthRange, -(w[2] + w[3]), false);
370        if (this.centerBlock != null) {
371            RectangleConstraint c5 = new RectangleConstraint(widthRange3,
372                    heightRange3);
373            Size2D size = this.centerBlock.arrange(g2, c5);
374            w[4] = size.width;
375            h[4] = size.height;
376        }
377        double width = Math.max(w[0], Math.max(w[1], w[2] + w[4] + w[3]));
378        double height = h[0] + h[1] + Math.max(h[2], Math.max(h[3], h[4]));
379        if (this.topBlock != null) {
380            this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, width,
381                    h[0]));
382        }
383        if (this.bottomBlock != null) {
384            this.bottomBlock.setBounds(new Rectangle2D.Double(0.0,
385                    height - h[1], width, h[1]));
386        }
387        if (this.leftBlock != null) {
388            this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2],
389                    h[2]));
390        }
391        if (this.rightBlock != null) {
392            this.rightBlock.setBounds(new Rectangle2D.Double(width - w[3],
393                    h[0], w[3], h[3]));
394        }
395
396        if (this.centerBlock != null) {
397            this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0],
398                    width - w[2] - w[3], height - h[0] - h[1]));
399        }
400        return new Size2D(width, height);
401    }
402
403    /**
404     * Arranges the items within a container.
405     *
406     * @param container  the container.
407     * @param constraint  the constraint.
408     * @param g2  the graphics device.
409     *
410     * @return The container size after the arrangement.
411     */
412    protected Size2D arrangeFF(BlockContainer container, Graphics2D g2,
413                               RectangleConstraint constraint) {
414        double[] w = new double[5];
415        double[] h = new double[5];
416        w[0] = constraint.getWidth();
417        if (this.topBlock != null) {
418            RectangleConstraint c1 = new RectangleConstraint(w[0], null,
419                    LengthConstraintType.FIXED, 0.0,
420                    new Range(0.0, constraint.getHeight()),
421                    LengthConstraintType.RANGE);
422            Size2D size = this.topBlock.arrange(g2, c1);
423            h[0] = size.height;
424        }
425        w[1] = w[0];
426        if (this.bottomBlock != null) {
427            RectangleConstraint c2 = new RectangleConstraint(w[0], null,
428                    LengthConstraintType.FIXED, 0.0, new Range(0.0,
429                    constraint.getHeight() - h[0]), LengthConstraintType.RANGE);
430            Size2D size = this.bottomBlock.arrange(g2, c2);
431            h[1] = size.height;
432        }
433        h[2] = constraint.getHeight() - h[1] - h[0];
434        if (this.leftBlock != null) {
435            RectangleConstraint c3 = new RectangleConstraint(0.0,
436                    new Range(0.0, constraint.getWidth()),
437                    LengthConstraintType.RANGE, h[2], null,
438                    LengthConstraintType.FIXED);
439            Size2D size = this.leftBlock.arrange(g2, c3);
440            w[2] = size.width;
441        }
442        h[3] = h[2];
443        if (this.rightBlock != null) {
444            RectangleConstraint c4 = new RectangleConstraint(0.0,
445                    new Range(0.0, Math.max(constraint.getWidth() - w[2], 0.0)),
446                    LengthConstraintType.RANGE, h[2], null,
447                    LengthConstraintType.FIXED);
448            Size2D size = this.rightBlock.arrange(g2, c4);
449            w[3] = size.width;
450        }
451        h[4] = h[2];
452        w[4] = constraint.getWidth() - w[3] - w[2];
453        RectangleConstraint c5 = new RectangleConstraint(w[4], h[4]);
454        if (this.centerBlock != null) {
455            this.centerBlock.arrange(g2, c5);
456        }
457
458        if (this.topBlock != null) {
459            this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, w[0],
460                    h[0]));
461        }
462        if (this.bottomBlock != null) {
463            this.bottomBlock.setBounds(new Rectangle2D.Double(0.0, h[0] + h[2],
464                    w[1], h[1]));
465        }
466        if (this.leftBlock != null) {
467            this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2],
468                    h[2]));
469        }
470        if (this.rightBlock != null) {
471            this.rightBlock.setBounds(new Rectangle2D.Double(w[2] + w[4], h[0],
472                    w[3], h[3]));
473        }
474        if (this.centerBlock != null) {
475            this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0], w[4],
476                    h[4]));
477        }
478        return new Size2D(constraint.getWidth(), constraint.getHeight());
479    }
480
481    /**
482     * Clears the layout.
483     */
484    @Override
485    public void clear() {
486        this.centerBlock = null;
487        this.topBlock = null;
488        this.bottomBlock = null;
489        this.leftBlock = null;
490        this.rightBlock = null;
491    }
492
493    /**
494     * Tests this arrangement for equality with an arbitrary object.
495     *
496     * @param obj  the object ({@code null} permitted).
497     *
498     * @return A boolean.
499     */
500    @Override
501    public boolean equals(Object obj) {
502        if (obj == this) {
503            return true;
504        }
505        if (!(obj instanceof BorderArrangement)) {
506            return false;
507        }
508        BorderArrangement that = (BorderArrangement) obj;
509        if (!Objects.equals(this.topBlock, that.topBlock)) {
510            return false;
511        }
512        if (!Objects.equals(this.bottomBlock, that.bottomBlock)) {
513            return false;
514        }
515        if (!Objects.equals(this.leftBlock, that.leftBlock)) {
516            return false;
517        }
518        if (!Objects.equals(this.rightBlock, that.rightBlock)) {
519            return false;
520        }
521        if (!Objects.equals(this.centerBlock, that.centerBlock)) {
522            return false;
523        }
524        return true;
525    }
526
527    @Override
528    public int hashCode() {
529        int hash = 5;
530        hash = 67 * hash + Objects.hashCode(this.centerBlock);
531        hash = 67 * hash + Objects.hashCode(this.topBlock);
532        hash = 67 * hash + Objects.hashCode(this.bottomBlock);
533        hash = 67 * hash + Objects.hashCode(this.leftBlock);
534        hash = 67 * hash + Objects.hashCode(this.rightBlock);
535        return hash;
536    }
537}