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