1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
6  *
7  * Project Info:  http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
22  * USA.
23  *
24  * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
25  * Other names may be trademarks of their respective owners.]
26  *
27  * ----------------------
28  * BorderArrangement.java
29  * ----------------------
30  * (C) Copyright 2004-2013, by Object Refinery Limited.
31  *
32  * Original Author:  David Gilbert (for Object Refinery Limited);
33  * Contributor(s):   -;
34  *
35  * Changes:
36  * --------
37  * 22-Oct-2004 : Version 1 (DG);
38  * 08-Feb-2005 : Updated for changes in RectangleConstraint (DG);
39  * 24-Feb-2005 : Improved arrangeRR() method (DG);
40  * 03-May-2005 : Implemented Serializable and added equals() method (DG);
41  * 13-May-2005 : Fixed bugs in the arrange() method (DG);
42  * 08-Apr-2008 : Fixed bug in arrangeFF() method where width is too small for
43  *               left and right blocks (DG);
44  * 21-Nov-2013 : Fixed bug #1084 (DG);
45  *
46  */
47 
48 package org.jfree.chart.block;
49 
50 import java.awt.Graphics2D;
51 import java.awt.geom.Rectangle2D;
52 import java.io.Serializable;
53 
54 import org.jfree.data.Range;
55 import org.jfree.ui.RectangleEdge;
56 import org.jfree.ui.Size2D;
57 import org.jfree.util.ObjectUtilities;
58 
59 /**
60  * An arrangement manager that lays out blocks in a similar way to
61  * Swing's BorderLayout class.
62  */
63 public class BorderArrangement implements Arrangement, Serializable {
64 
65     /** For serialization. */
66     private static final long serialVersionUID = 506071142274883745L;
67 
68     /** The block (if any) at the center of the layout. */
69     private Block centerBlock;
70 
71     /** The block (if any) at the top of the layout. */
72     private Block topBlock;
73 
74     /** The block (if any) at the bottom of the layout. */
75     private Block bottomBlock;
76 
77     /** The block (if any) at the left of the layout. */
78     private Block leftBlock;
79 
80     /** The block (if any) at the right of the layout. */
81     private Block rightBlock;
82 
83     /**
84      * Creates a new instance.
85      */
BorderArrangement()86     public BorderArrangement() {
87     }
88 
89     /**
90      * Adds a block to the arrangement manager at the specified edge.
91      * If the key is not an instance of {@link RectangleEdge} the block will
92      * be added in the center.
93      *
94      * @param block  the block (<code>null</code> permitted).
95      * @param key  the edge (an instance of {@link RectangleEdge}) or
96      *             <code>null</code> for the center block.
97      */
98     @Override
add(Block block, Object key)99     public void add(Block block, Object key) {
100 
101         if (!(key instanceof RectangleEdge)) { // catches null also
102             this.centerBlock = block;
103         }
104         else {
105             RectangleEdge edge = (RectangleEdge) key;
106             if (edge == RectangleEdge.TOP) {
107                 this.topBlock = block;
108             }
109             else if (edge == RectangleEdge.BOTTOM) {
110                 this.bottomBlock = block;
111             }
112             else if (edge == RectangleEdge.LEFT) {
113                 this.leftBlock = block;
114             }
115             else if (edge == RectangleEdge.RIGHT) {
116                 this.rightBlock = block;
117             }
118         }
119     }
120 
121     /**
122      * Arranges the items in the specified container, subject to the given
123      * constraint.
124      *
125      * @param container  the container.
126      * @param g2  the graphics device.
127      * @param constraint  the constraint.
128      *
129      * @return The block size.
130      */
131     @Override
arrange(BlockContainer container, Graphics2D g2, RectangleConstraint constraint)132     public Size2D arrange(BlockContainer container, Graphics2D g2,
133             RectangleConstraint constraint) {
134         RectangleConstraint contentConstraint
135                 = container.toContentConstraint(constraint);
136         Size2D contentSize = null;
137         LengthConstraintType w = contentConstraint.getWidthConstraintType();
138         LengthConstraintType h = contentConstraint.getHeightConstraintType();
139         if (w == LengthConstraintType.NONE) {
140             if (h == LengthConstraintType.NONE) {
141                 contentSize = arrangeNN(container, g2);
142             }
143             else if (h == LengthConstraintType.FIXED) {
144                 throw new RuntimeException("Not implemented.");
145             }
146             else if (h == LengthConstraintType.RANGE) {
147                 throw new RuntimeException("Not implemented.");
148             }
149         }
150         else if (w == LengthConstraintType.FIXED) {
151             if (h == LengthConstraintType.NONE) {
152                 contentSize = arrangeFN(container, g2, constraint.getWidth());
153             }
154             else if (h == LengthConstraintType.FIXED) {
155                 contentSize = arrangeFF(container, g2, constraint);
156             }
157             else if (h == LengthConstraintType.RANGE) {
158                 contentSize = arrangeFR(container, g2, constraint);
159             }
160         }
161         else if (w == LengthConstraintType.RANGE) {
162             if (h == LengthConstraintType.NONE) {
163                 throw new RuntimeException("Not implemented.");
164             }
165             else if (h == LengthConstraintType.FIXED) {
166                 throw new RuntimeException("Not implemented.");
167             }
168             else if (h == LengthConstraintType.RANGE) {
169                 contentSize = arrangeRR(container, constraint.getWidthRange(),
170                         constraint.getHeightRange(), g2);
171             }
172         }
173         assert contentSize != null;
174         return new Size2D(container.calculateTotalWidth(contentSize.getWidth()),
175                 container.calculateTotalHeight(contentSize.getHeight()));
176     }
177 
178     /**
179      * Performs an arrangement without constraints.
180      *
181      * @param container  the container.
182      * @param g2  the graphics device.
183      *
184      * @return The container size after the arrangement.
185      */
arrangeNN(BlockContainer container, Graphics2D g2)186     protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) {
187         double[] w = new double[5];
188         double[] h = new double[5];
189         if (this.topBlock != null) {
190             Size2D size = this.topBlock.arrange(g2, RectangleConstraint.NONE);
191             w[0] = size.width;
192             h[0] = size.height;
193         }
194         if (this.bottomBlock != null) {
195             Size2D size = this.bottomBlock.arrange(g2,
196                     RectangleConstraint.NONE);
197             w[1] = size.width;
198             h[1] = size.height;
199         }
200         if (this.leftBlock != null) {
201             Size2D size = this.leftBlock.arrange(g2, RectangleConstraint.NONE);
202             w[2] = size.width;
203             h[2] = size.height;
204        }
205         if (this.rightBlock != null) {
206             Size2D size = this.rightBlock.arrange(g2, RectangleConstraint.NONE);
207             w[3] = size.width;
208             h[3] = size.height;
209         }
210 
211         h[2] = Math.max(h[2], h[3]);
212         h[3] = h[2];
213 
214         if (this.centerBlock != null) {
215             Size2D size = this.centerBlock.arrange(g2,
216                     RectangleConstraint.NONE);
217             w[4] = size.width;
218             h[4] = size.height;
219         }
220         double width = Math.max(w[0], Math.max(w[1], w[2] + w[4] + w[3]));
221         double centerHeight = Math.max(h[2], Math.max(h[3], h[4]));
222         double height = h[0] + h[1] + centerHeight;
223         if (this.topBlock != null) {
224             this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, width,
225                     h[0]));
226         }
227         if (this.bottomBlock != null) {
228             this.bottomBlock.setBounds(new Rectangle2D.Double(0.0,
229                     height - h[1], width, h[1]));
230         }
231         if (this.leftBlock != null) {
232             this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2],
233                     centerHeight));
234         }
235         if (this.rightBlock != null) {
236             this.rightBlock.setBounds(new Rectangle2D.Double(width - w[3],
237                     h[0], w[3], centerHeight));
238         }
239 
240         if (this.centerBlock != null) {
241             this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0],
242                     width - w[2] - w[3], centerHeight));
243         }
244         return new Size2D(width, height);
245     }
246 
247     /**
248      * Performs an arrangement with a fixed width and a range for the height.
249      *
250      * @param container  the container.
251      * @param g2  the graphics device.
252      * @param constraint  the constraint.
253      *
254      * @return The container size after the arrangement.
255      */
arrangeFR(BlockContainer container, Graphics2D g2, RectangleConstraint constraint)256     protected Size2D arrangeFR(BlockContainer container, Graphics2D g2,
257                                RectangleConstraint constraint) {
258         Size2D size1 = arrangeFN(container, g2, constraint.getWidth());
259         if (constraint.getHeightRange().contains(size1.getHeight())) {
260             return size1;
261         }
262         else {
263             double h = constraint.getHeightRange().constrain(size1.getHeight());
264             RectangleConstraint c2 = constraint.toFixedHeight(h);
265             return arrange(container, g2, c2);
266         }
267     }
268 
269     /**
270      * Arranges the container width a fixed width and no constraint on the
271      * height.
272      *
273      * @param container  the container.
274      * @param g2  the graphics device.
275      * @param width  the fixed width.
276      *
277      * @return The container size after arranging the contents.
278      */
arrangeFN(BlockContainer container, Graphics2D g2, double width)279     protected Size2D arrangeFN(BlockContainer container, Graphics2D g2,
280                                double width) {
281         double[] w = new double[5];
282         double[] h = new double[5];
283         RectangleConstraint c1 = new RectangleConstraint(width, null,
284                 LengthConstraintType.FIXED, 0.0, null,
285                 LengthConstraintType.NONE);
286         if (this.topBlock != null) {
287             Size2D size = this.topBlock.arrange(g2, c1);
288             w[0] = size.width;
289             h[0] = size.height;
290         }
291         if (this.bottomBlock != null) {
292             Size2D size = this.bottomBlock.arrange(g2, c1);
293             w[1] = size.width;
294             h[1] = size.height;
295         }
296         RectangleConstraint c2 = new RectangleConstraint(0.0,
297                 new Range(0.0, width), LengthConstraintType.RANGE,
298                 0.0, null, LengthConstraintType.NONE);
299         if (this.leftBlock != null) {
300             Size2D size = this.leftBlock.arrange(g2, c2);
301             w[2] = size.width;
302             h[2] = size.height;
303         }
304         if (this.rightBlock != null) {
305             double maxW = Math.max(width - w[2], 0.0);
306             RectangleConstraint c3 = new RectangleConstraint(0.0,
307                     new Range(Math.min(w[2], maxW), maxW),
308                     LengthConstraintType.RANGE, 0.0, null,
309                     LengthConstraintType.NONE);
310             Size2D size = this.rightBlock.arrange(g2, c3);
311             w[3] = size.width;
312             h[3] = size.height;
313         }
314 
315         h[2] = Math.max(h[2], h[3]);
316         h[3] = h[2];
317 
318         if (this.centerBlock != null) {
319             RectangleConstraint c4 = new RectangleConstraint(width - w[2]
320                     - w[3], null, LengthConstraintType.FIXED, 0.0, null,
321                     LengthConstraintType.NONE);
322             Size2D size = this.centerBlock.arrange(g2, c4);
323             w[4] = size.width;
324             h[4] = size.height;
325         }
326         double height = h[0] + h[1] + Math.max(h[2], Math.max(h[3], h[4]));
327         return arrange(container, g2, new RectangleConstraint(width, height));
328     }
329 
330     /**
331      * Performs an arrangement with range constraints on both the vertical
332      * and horizontal sides.
333      *
334      * @param container  the container.
335      * @param widthRange  the allowable range for the container width.
336      * @param heightRange  the allowable range for the container height.
337      * @param g2  the graphics device.
338      *
339      * @return The container size.
340      */
arrangeRR(BlockContainer container, Range widthRange, Range heightRange, Graphics2D g2)341     protected Size2D arrangeRR(BlockContainer container,
342                                Range widthRange, Range heightRange,
343                                Graphics2D g2) {
344         double[] w = new double[5];
345         double[] h = new double[5];
346         if (this.topBlock != null) {
347             RectangleConstraint c1 = new RectangleConstraint(widthRange,
348                     heightRange);
349             Size2D size = this.topBlock.arrange(g2, c1);
350             w[0] = size.width;
351             h[0] = size.height;
352         }
353         if (this.bottomBlock != null) {
354             Range heightRange2 = Range.shift(heightRange, -h[0], false);
355             RectangleConstraint c2 = new RectangleConstraint(widthRange,
356                     heightRange2);
357             Size2D size = this.bottomBlock.arrange(g2, c2);
358             w[1] = size.width;
359             h[1] = size.height;
360         }
361         Range heightRange3 = Range.shift(heightRange, -(h[0] + h[1]));
362         if (this.leftBlock != null) {
363             RectangleConstraint c3 = new RectangleConstraint(widthRange,
364                     heightRange3);
365             Size2D size = this.leftBlock.arrange(g2, c3);
366             w[2] = size.width;
367             h[2] = size.height;
368         }
369         Range widthRange2 = Range.shift(widthRange, -w[2], false);
370         if (this.rightBlock != null) {
371             RectangleConstraint c4 = new RectangleConstraint(widthRange2,
372                     heightRange3);
373             Size2D size = this.rightBlock.arrange(g2, c4);
374             w[3] = size.width;
375             h[3] = size.height;
376         }
377 
378         h[2] = Math.max(h[2], h[3]);
379         h[3] = h[2];
380         Range widthRange3 = Range.shift(widthRange, -(w[2] + w[3]), false);
381         if (this.centerBlock != null) {
382             RectangleConstraint c5 = new RectangleConstraint(widthRange3,
383                     heightRange3);
384             Size2D size = this.centerBlock.arrange(g2, c5);
385             w[4] = size.width;
386             h[4] = size.height;
387         }
388         double width = Math.max(w[0], Math.max(w[1], w[2] + w[4] + w[3]));
389         double height = h[0] + h[1] + Math.max(h[2], Math.max(h[3], h[4]));
390         if (this.topBlock != null) {
391             this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, width,
392                     h[0]));
393         }
394         if (this.bottomBlock != null) {
395             this.bottomBlock.setBounds(new Rectangle2D.Double(0.0,
396                     height - h[1], width, h[1]));
397         }
398         if (this.leftBlock != null) {
399             this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2],
400                     h[2]));
401         }
402         if (this.rightBlock != null) {
403             this.rightBlock.setBounds(new Rectangle2D.Double(width - w[3],
404                     h[0], w[3], h[3]));
405         }
406 
407         if (this.centerBlock != null) {
408             this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0],
409                     width - w[2] - w[3], height - h[0] - h[1]));
410         }
411         return new Size2D(width, height);
412     }
413 
414     /**
415      * Arranges the items within a container.
416      *
417      * @param container  the container.
418      * @param constraint  the constraint.
419      * @param g2  the graphics device.
420      *
421      * @return The container size after the arrangement.
422      */
arrangeFF(BlockContainer container, Graphics2D g2, RectangleConstraint constraint)423     protected Size2D arrangeFF(BlockContainer container, Graphics2D g2,
424                                RectangleConstraint constraint) {
425         double[] w = new double[5];
426         double[] h = new double[5];
427         w[0] = constraint.getWidth();
428         if (this.topBlock != null) {
429             RectangleConstraint c1 = new RectangleConstraint(w[0], null,
430                     LengthConstraintType.FIXED, 0.0,
431                     new Range(0.0, constraint.getHeight()),
432                     LengthConstraintType.RANGE);
433             Size2D size = this.topBlock.arrange(g2, c1);
434             h[0] = size.height;
435         }
436         w[1] = w[0];
437         if (this.bottomBlock != null) {
438             RectangleConstraint c2 = new RectangleConstraint(w[0], null,
439                     LengthConstraintType.FIXED, 0.0, new Range(0.0,
440                     constraint.getHeight() - h[0]), LengthConstraintType.RANGE);
441             Size2D size = this.bottomBlock.arrange(g2, c2);
442             h[1] = size.height;
443         }
444         h[2] = constraint.getHeight() - h[1] - h[0];
445         if (this.leftBlock != null) {
446             RectangleConstraint c3 = new RectangleConstraint(0.0,
447                     new Range(0.0, constraint.getWidth()),
448                     LengthConstraintType.RANGE, h[2], null,
449                     LengthConstraintType.FIXED);
450             Size2D size = this.leftBlock.arrange(g2, c3);
451             w[2] = size.width;
452         }
453         h[3] = h[2];
454         if (this.rightBlock != null) {
455             RectangleConstraint c4 = new RectangleConstraint(0.0,
456                     new Range(0.0, Math.max(constraint.getWidth() - w[2], 0.0)),
457                     LengthConstraintType.RANGE, h[2], null,
458                     LengthConstraintType.FIXED);
459             Size2D size = this.rightBlock.arrange(g2, c4);
460             w[3] = size.width;
461         }
462         h[4] = h[2];
463         w[4] = constraint.getWidth() - w[3] - w[2];
464         RectangleConstraint c5 = new RectangleConstraint(w[4], h[4]);
465         if (this.centerBlock != null) {
466             this.centerBlock.arrange(g2, c5);
467         }
468 
469         if (this.topBlock != null) {
470             this.topBlock.setBounds(new Rectangle2D.Double(0.0, 0.0, w[0],
471                     h[0]));
472         }
473         if (this.bottomBlock != null) {
474             this.bottomBlock.setBounds(new Rectangle2D.Double(0.0, h[0] + h[2],
475                     w[1], h[1]));
476         }
477         if (this.leftBlock != null) {
478             this.leftBlock.setBounds(new Rectangle2D.Double(0.0, h[0], w[2],
479                     h[2]));
480         }
481         if (this.rightBlock != null) {
482             this.rightBlock.setBounds(new Rectangle2D.Double(w[2] + w[4], h[0],
483                     w[3], h[3]));
484         }
485         if (this.centerBlock != null) {
486             this.centerBlock.setBounds(new Rectangle2D.Double(w[2], h[0], w[4],
487                     h[4]));
488         }
489         return new Size2D(constraint.getWidth(), constraint.getHeight());
490     }
491 
492     /**
493      * Clears the layout.
494      */
495     @Override
clear()496     public void clear() {
497         this.centerBlock = null;
498         this.topBlock = null;
499         this.bottomBlock = null;
500         this.leftBlock = null;
501         this.rightBlock = null;
502     }
503 
504     /**
505      * Tests this arrangement for equality with an arbitrary object.
506      *
507      * @param obj  the object (<code>null</code> permitted).
508      *
509      * @return A boolean.
510      */
511     @Override
equals(Object obj)512     public boolean equals(Object obj) {
513         if (obj == this) {
514             return true;
515         }
516         if (!(obj instanceof BorderArrangement)) {
517             return false;
518         }
519         BorderArrangement that = (BorderArrangement) obj;
520         if (!ObjectUtilities.equal(this.topBlock, that.topBlock)) {
521             return false;
522         }
523         if (!ObjectUtilities.equal(this.bottomBlock, that.bottomBlock)) {
524             return false;
525         }
526         if (!ObjectUtilities.equal(this.leftBlock, that.leftBlock)) {
527             return false;
528         }
529         if (!ObjectUtilities.equal(this.rightBlock, that.rightBlock)) {
530             return false;
531         }
532         if (!ObjectUtilities.equal(this.centerBlock, that.centerBlock)) {
533             return false;
534         }
535         return true;
536     }
537 }
538