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