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