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