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