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