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 045package org.jfree.chart.block; 046 047import java.awt.Graphics2D; 048import java.awt.geom.Rectangle2D; 049import java.io.Serializable; 050 051import org.jfree.data.Range; 052import org.jfree.ui.RectangleEdge; 053import org.jfree.ui.Size2D; 054import 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 */ 060public 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}