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(
164 container, constraint.getWidthRange(),
165 constraint.getHeightRange(), g2
166 );
167 }
168 }
169 return new Size2D(
170 container.calculateTotalWidth(contentSize.getWidth()),
171 container.calculateTotalHeight(contentSize.getHeight())
172 );
173 }
174
175 /**
176 * Performs an arrangement without constraints.
177 *
178 * @param container the container.
179 * @param g2 the graphics device.
180 *
181 * @return The container size after the arrangement.
182 */
183 protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) {
184 double[] w = new double[5];
185 double[] h = new double[5];
186 if (this.topBlock != null) {
187 Size2D size = this.topBlock.arrange(
188 g2, RectangleConstraint.NONE
189 );
190 w[0] = size.width;
191 h[0] = size.height;
192 }
193 if (this.bottomBlock != null) {
194 Size2D size = this.bottomBlock.arrange(
195 g2, RectangleConstraint.NONE
196 );
197 w[1] = size.width;
198 h[1] = size.height;
199 }
200 if (this.leftBlock != null) {
201 Size2D size = this.leftBlock.arrange(
202 g2, RectangleConstraint.NONE
203 );
204 w[2] = size.width;
205 h[2] = size.height;
206 }
207 if (this.rightBlock != null) {
208 Size2D size = this.rightBlock.arrange(
209 g2, RectangleConstraint.NONE
210 );
211 w[3] = size.width;
212 h[3] = size.height;
213 }
214
215 h[2] = Math.max(h[2], h[3]);
216 h[3] = h[2];
217
218 if (this.centerBlock != null) {
219 Size2D size = this.centerBlock.arrange(
220 g2, RectangleConstraint.NONE
221 );
222 w[4] = size.width;
223 h[4] = size.height;
224 }
225 double width = Math.max(w[0], Math.max(w[1], w[2] + w[4] + w[3]));
226 double centerHeight = Math.max(h[2], Math.max(h[3], h[4]));
227 double height = h[0] + h[1] + centerHeight;
228 if (this.topBlock != null) {
229 this.topBlock.setBounds(
230 new Rectangle2D.Double(0.0, 0.0, width, h[0])
231 );
232 }
233 if (this.bottomBlock != null) {
234 this.bottomBlock.setBounds(
235 new Rectangle2D.Double(0.0, height - h[1], width, h[1])
236 );
237 }
238 if (this.leftBlock != null) {
239 this.leftBlock.setBounds(
240 new Rectangle2D.Double(0.0, h[0], w[2], centerHeight)
241 );
242 }
243 if (this.rightBlock != null) {
244 this.rightBlock.setBounds(
245 new Rectangle2D.Double(width - w[3], h[0], w[3], centerHeight)
246 );
247 }
248
249 if (this.centerBlock != null) {
250 this.centerBlock.setBounds(
251 new Rectangle2D.Double(
252 w[2], h[0], width - w[2] - w[3], centerHeight
253 )
254 );
255 }
256 return new Size2D(width, height);
257 }
258
259 /**
260 * Performs an arrangement with a fixed width and a range for the height.
261 *
262 * @param container the container.
263 * @param g2 the graphics device.
264 * @param constraint the constraint.
265 *
266 * @return The container size after the arrangement.
267 */
268 protected Size2D arrangeFR(BlockContainer container, Graphics2D g2,
269 RectangleConstraint constraint) {
270 Size2D size1 = arrangeFN(container, g2, constraint.getWidth());
271 if (constraint.getHeightRange().contains(size1.getHeight())) {
272 return size1;
273 }
274 else {
275 double h = constraint.getHeightRange().constrain(size1.getHeight());
276 RectangleConstraint c2 = constraint.toFixedHeight(h);
277 return arrange(container, g2, c2);
278 }
279 }
280
281 /**
282 * Arranges the container width a fixed width and no constraint on the
283 * height.
284 *
285 * @param container the container.
286 * @param g2 the graphics device.
287 * @param width the fixed width.
288 *
289 * @return The container size after arranging the contents.
290 */
291 protected Size2D arrangeFN(BlockContainer container, Graphics2D g2,
292 double width) {
293 double[] w = new double[5];
294 double[] h = new double[5];
295 RectangleConstraint c1 = new RectangleConstraint(
296 width, null, LengthConstraintType.FIXED,
297 0.0, null, LengthConstraintType.NONE
298 );
299 if (this.topBlock != null) {
300 Size2D size = this.topBlock.arrange(g2, c1);
301 w[0] = size.width;
302 h[0] = size.height;
303 }
304 if (this.bottomBlock != null) {
305 Size2D size = this.bottomBlock.arrange(g2, c1);
306 w[1] = size.width;
307 h[1] = size.height;
308 }
309 RectangleConstraint c2 = new RectangleConstraint(
310 0.0, new Range(0.0, width), LengthConstraintType.RANGE,
311 0.0, null, LengthConstraintType.NONE
312 );
313 if (this.leftBlock != null) {
314 Size2D size = this.leftBlock.arrange(g2, c2);
315 w[2] = size.width;
316 h[2] = size.height;
317 }
318 if (this.rightBlock != null) {
319 double maxW = Math.max(width - w[2], 0.0);
320 RectangleConstraint c3 = new RectangleConstraint(
321 0.0, new Range(Math.min(w[2], maxW), maxW),
322 LengthConstraintType.RANGE,
323 0.0, null, LengthConstraintType.NONE
324 );
325 Size2D size = this.rightBlock.arrange(g2, c3);
326 w[3] = size.width;
327 h[3] = size.height;
328 }
329
330 h[2] = Math.max(h[2], h[3]);
331 h[3] = h[2];
332
333 if (this.centerBlock != null) {
334 RectangleConstraint c4 = new RectangleConstraint(
335 width - w[2] - w[3], null, LengthConstraintType.FIXED,
336 0.0, null, LengthConstraintType.NONE
337 );
338 Size2D size = this.centerBlock.arrange(g2, c4);
339 w[4] = size.width;
340 h[4] = size.height;
341 }
342 double height = h[0] + h[1] + Math.max(h[2], Math.max(h[3], h[4]));
343 return arrange(container, g2, new RectangleConstraint(width, height));
344 }
345
346 /**
347 * Performs an arrangement with range constraints on both the vertical
348 * and horizontal sides.
349 *
350 * @param container the container.
351 * @param widthRange the allowable range for the container width.
352 * @param heightRange the allowable range for the container height.
353 * @param g2 the graphics device.
354 *
355 * @return The container size.
356 */
357 protected Size2D arrangeRR(BlockContainer container,
358 Range widthRange, Range heightRange,
359 Graphics2D g2) {
360 double[] w = new double[5];
361 double[] h = new double[5];
362 if (this.topBlock != null) {
363 RectangleConstraint c1 = new RectangleConstraint(
364 widthRange, heightRange
365 );
366 Size2D size = this.topBlock.arrange(g2, c1);
367 w[0] = size.width;
368 h[0] = size.height;
369 }
370 if (this.bottomBlock != null) {
371 Range heightRange2 = Range.shift(heightRange, -h[0], false);
372 RectangleConstraint c2 = new RectangleConstraint(
373 widthRange, heightRange2
374 );
375 Size2D size = this.bottomBlock.arrange(g2, c2);
376 w[1] = size.width;
377 h[1] = size.height;
378 }
379 Range heightRange3 = Range.shift(heightRange, -(h[0] + h[1]));
380 if (this.leftBlock != null) {
381 RectangleConstraint c3 = new RectangleConstraint(
382 widthRange, heightRange3
383 );
384 Size2D size = this.leftBlock.arrange(g2, c3);
385 w[2] = size.width;
386 h[2] = size.height;
387 }
388 Range widthRange2 = Range.shift(widthRange, -w[2], false);
389 if (this.rightBlock != null) {
390 RectangleConstraint c4 = new RectangleConstraint(
391 widthRange2, heightRange3
392 );
393 Size2D size = this.rightBlock.arrange(g2, c4);
394 w[3] = size.width;
395 h[3] = size.height;
396 }
397
398 h[2] = Math.max(h[2], h[3]);
399 h[3] = h[2];
400 Range widthRange3 = Range.shift(widthRange, -(w[2] + w[3]), false);
401 if (this.centerBlock != null) {
402 RectangleConstraint c5 = new RectangleConstraint(
403 widthRange3, heightRange3
404 );
405 // TODO: the width and height ranges should be reduced by the
406 // height required for the top and bottom, and the width required
407 // by the left and right
408 Size2D size = this.centerBlock.arrange(g2, c5);
409 w[4] = size.width;
410 h[4] = size.height;
411 }
412 double width = Math.max(w[0], Math.max(w[1], w[2] + w[4] + w[3]));
413 double height = h[0] + h[1] + Math.max(h[2], Math.max(h[3], h[4]));
414 if (this.topBlock != null) {
415 this.topBlock.setBounds(
416 new Rectangle2D.Double(0.0, 0.0, width, h[0])
417 );
418 }
419 if (this.bottomBlock != null) {
420 this.bottomBlock.setBounds(
421 new Rectangle2D.Double(0.0, height - h[1], width, h[1])
422 );
423 }
424 if (this.leftBlock != null) {
425 this.leftBlock.setBounds(
426 new Rectangle2D.Double(0.0, h[0], w[2], h[2])
427 );
428 }
429 if (this.rightBlock != null) {
430 this.rightBlock.setBounds(
431 new Rectangle2D.Double(width - w[3], h[0], w[3], h[3])
432 );
433 }
434
435 if (this.centerBlock != null) {
436 this.centerBlock.setBounds(
437 new Rectangle2D.Double(
438 w[2], h[0], width - w[2] - w[3], height - h[0] - h[1]
439 )
440 );
441 }
442 return new Size2D(width, height);
443 }
444
445 /**
446 * Arranges the items within a container.
447 *
448 * @param container the container.
449 * @param constraint the constraint.
450 * @param g2 the graphics device.
451 *
452 * @return The container size after the arrangement.
453 */
454 protected Size2D arrangeFF(BlockContainer container, Graphics2D g2,
455 RectangleConstraint constraint) {
456 double[] w = new double[5];
457 double[] h = new double[5];
458 w[0] = constraint.getWidth();
459 if (this.topBlock != null) {
460 RectangleConstraint c1 = new RectangleConstraint(
461 w[0], null, LengthConstraintType.FIXED,
462 0.0, new Range(0.0, constraint.getHeight()),
463 LengthConstraintType.RANGE
464 );
465 Size2D size = this.topBlock.arrange(g2, c1);
466 h[0] = size.height;
467 }
468 w[1] = w[0];
469 if (this.bottomBlock != null) {
470 RectangleConstraint c2 = new RectangleConstraint(
471 w[0], null, LengthConstraintType.FIXED,
472 0.0, new Range(0.0, constraint.getHeight() - h[0]),
473 LengthConstraintType.RANGE
474 );
475 Size2D size = this.bottomBlock.arrange(g2, c2);
476 h[1] = size.height;
477 }
478 h[2] = constraint.getHeight() - h[1] - h[0];
479 if (this.leftBlock != null) {
480 RectangleConstraint c3 = new RectangleConstraint(
481 0.0, new Range(0.0, constraint.getWidth()),
482 LengthConstraintType.RANGE,
483 h[2], null, LengthConstraintType.FIXED
484 );
485 Size2D size = this.leftBlock.arrange(g2, c3);
486 w[2] = size.width;
487 }
488 h[3] = h[2];
489 if (this.rightBlock != null) {
490 RectangleConstraint c4 = new RectangleConstraint(
491 0.0, new Range(0.0, constraint.getWidth() - w[2]),
492 LengthConstraintType.RANGE,
493 h[2], null, LengthConstraintType.FIXED
494 );
495 Size2D size = this.rightBlock.arrange(g2, c4);
496 w[3] = size.width;
497 }
498 h[4] = h[2];
499 w[4] = constraint.getWidth() - w[3] - w[2];
500 RectangleConstraint c5 = new RectangleConstraint(w[4], h[4]);
501 if (this.centerBlock != null) {
502 this.centerBlock.arrange(g2, c5);
503 }
504
505 if (this.topBlock != null) {
506 this.topBlock.setBounds(
507 new Rectangle2D.Double(0.0, 0.0, w[0], h[0])
508 );
509 }
510 if (this.bottomBlock != null) {
511 this.bottomBlock.setBounds(
512 new Rectangle2D.Double(0.0, h[0] + h[2], w[1], h[1])
513 );
514 }
515 if (this.leftBlock != null) {
516 this.leftBlock.setBounds(
517 new Rectangle2D.Double(0.0, h[0], w[2], h[2])
518 );
519 }
520 if (this.rightBlock != null) {
521 this.rightBlock.setBounds(
522 new Rectangle2D.Double(w[2] + w[4], h[0], w[3], h[3])
523 );
524 }
525 if (this.centerBlock != null) {
526 this.centerBlock.setBounds(
527 new Rectangle2D.Double(w[2], h[0], w[4], h[4])
528 );
529 }
530 return new Size2D(constraint.getWidth(), constraint.getHeight());
531 }
532
533 /**
534 * Clears the layout.
535 */
536 public void clear() {
537 this.centerBlock = null;
538 this.topBlock = null;
539 this.bottomBlock = null;
540 this.leftBlock = null;
541 this.rightBlock = null;
542 }
543
544 /**
545 * Tests this arrangement for equality with an arbitrary object.
546 *
547 * @param obj the object (<code>null</code> permitted).
548 *
549 * @return A boolean.
550 */
551 public boolean equals(Object obj) {
552 if (obj == this) {
553 return true;
554 }
555 if (!(obj instanceof BorderArrangement)) {
556 return false;
557 }
558 BorderArrangement that = (BorderArrangement) obj;
559 if (!ObjectUtilities.equal(this.topBlock, that.topBlock)) {
560 return false;
561 }
562 if (!ObjectUtilities.equal(this.bottomBlock, that.bottomBlock)) {
563 return false;
564 }
565 if (!ObjectUtilities.equal(this.leftBlock, that.leftBlock)) {
566 return false;
567 }
568 if (!ObjectUtilities.equal(this.rightBlock, that.rightBlock)) {
569 return false;
570 }
571 if (!ObjectUtilities.equal(this.centerBlock, that.centerBlock)) {
572 return false;
573 }
574 return true;
575 }
576 }