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 * AbstractBlock.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 * 02-Feb-2005 : Added accessor methods for margin (DG); 039 * 04-Feb-2005 : Added equals() method and implemented Serializable (DG); 040 * 03-May-2005 : Added null argument checks (DG); 041 * 06-May-2005 : Added convenience methods for setting margin, border and 042 * padding (DG); 043 * ------------- JFREECHART 1.0.x --------------------------------------------- 044 * 16-Mar-2007 : Changed border from BlockBorder to BlockFrame, updated 045 * equals(), and implemented Cloneable (DG); 046 * 02-Jul-2013 : Use ParamChecks (DG); 047 * 048 */ 049 050package org.jfree.chart.block; 051 052import java.awt.Graphics2D; 053import java.awt.geom.Rectangle2D; 054import java.io.IOException; 055import java.io.ObjectInputStream; 056import java.io.ObjectOutputStream; 057import java.io.Serializable; 058import org.jfree.chart.util.ParamChecks; 059 060import org.jfree.data.Range; 061import org.jfree.io.SerialUtilities; 062import org.jfree.ui.RectangleInsets; 063import org.jfree.ui.Size2D; 064import org.jfree.util.ObjectUtilities; 065import org.jfree.util.PublicCloneable; 066import org.jfree.util.ShapeUtilities; 067 068/** 069 * A convenience class for creating new classes that implement 070 * the {@link Block} interface. 071 */ 072public class AbstractBlock implements Cloneable, Serializable { 073 074 /** For serialization. */ 075 private static final long serialVersionUID = 7689852412141274563L; 076 077 /** The id for the block. */ 078 private String id; 079 080 /** The margin around the outside of the block. */ 081 private RectangleInsets margin; 082 083 /** The frame (or border) for the block. */ 084 private BlockFrame frame; 085 086 /** The padding between the block content and the border. */ 087 private RectangleInsets padding; 088 089 /** 090 * The natural width of the block (may be overridden if there are 091 * constraints in sizing). 092 */ 093 private double width; 094 095 /** 096 * The natural height of the block (may be overridden if there are 097 * constraints in sizing). 098 */ 099 private double height; 100 101 /** 102 * The current bounds for the block (position of the block in Java2D space). 103 */ 104 private transient Rectangle2D bounds; 105 106 /** 107 * Creates a new block. 108 */ 109 protected AbstractBlock() { 110 this.id = null; 111 this.width = 0.0; 112 this.height = 0.0; 113 this.bounds = new Rectangle2D.Float(); 114 this.margin = RectangleInsets.ZERO_INSETS; 115 this.frame = BlockBorder.NONE; 116 this.padding = RectangleInsets.ZERO_INSETS; 117 } 118 119 /** 120 * Returns the id. 121 * 122 * @return The id (possibly <code>null</code>). 123 * 124 * @see #setID(String) 125 */ 126 public String getID() { 127 return this.id; 128 } 129 130 /** 131 * Sets the id for the block. 132 * 133 * @param id the id (<code>null</code> permitted). 134 * 135 * @see #getID() 136 */ 137 public void setID(String id) { 138 this.id = id; 139 } 140 141 /** 142 * Returns the natural width of the block, if this is known in advance. 143 * The actual width of the block may be overridden if layout constraints 144 * make this necessary. 145 * 146 * @return The width. 147 * 148 * @see #setWidth(double) 149 */ 150 public double getWidth() { 151 return this.width; 152 } 153 154 /** 155 * Sets the natural width of the block, if this is known in advance. 156 * 157 * @param width the width (in Java2D units) 158 * 159 * @see #getWidth() 160 */ 161 public void setWidth(double width) { 162 this.width = width; 163 } 164 165 /** 166 * Returns the natural height of the block, if this is known in advance. 167 * The actual height of the block may be overridden if layout constraints 168 * make this necessary. 169 * 170 * @return The height. 171 * 172 * @see #setHeight(double) 173 */ 174 public double getHeight() { 175 return this.height; 176 } 177 178 /** 179 * Sets the natural width of the block, if this is known in advance. 180 * 181 * @param height the width (in Java2D units) 182 * 183 * @see #getHeight() 184 */ 185 public void setHeight(double height) { 186 this.height = height; 187 } 188 189 /** 190 * Returns the margin. 191 * 192 * @return The margin (never <code>null</code>). 193 * 194 * @see #getMargin() 195 */ 196 public RectangleInsets getMargin() { 197 return this.margin; 198 } 199 200 /** 201 * Sets the margin (use {@link RectangleInsets#ZERO_INSETS} for no 202 * padding). 203 * 204 * @param margin the margin (<code>null</code> not permitted). 205 * 206 * @see #getMargin() 207 */ 208 public void setMargin(RectangleInsets margin) { 209 ParamChecks.nullNotPermitted(margin, "margin"); 210 this.margin = margin; 211 } 212 213 /** 214 * Sets the margin. 215 * 216 * @param top the top margin. 217 * @param left the left margin. 218 * @param bottom the bottom margin. 219 * @param right the right margin. 220 * 221 * @see #getMargin() 222 */ 223 public void setMargin(double top, double left, double bottom, 224 double right) { 225 setMargin(new RectangleInsets(top, left, bottom, right)); 226 } 227 228 /** 229 * Returns the border. 230 * 231 * @return The border (never <code>null</code>). 232 * 233 * @deprecated Use {@link #getFrame()} instead. 234 */ 235 public BlockBorder getBorder() { 236 if (this.frame instanceof BlockBorder) { 237 return (BlockBorder) this.frame; 238 } 239 else { 240 return null; 241 } 242 } 243 244 /** 245 * Sets the border for the block (use {@link BlockBorder#NONE} for 246 * no border). 247 * 248 * @param border the border (<code>null</code> not permitted). 249 * 250 * @see #getBorder() 251 * 252 * @deprecated Use {@link #setFrame(BlockFrame)} instead. 253 */ 254 public void setBorder(BlockBorder border) { 255 setFrame(border); 256 } 257 258 /** 259 * Sets a black border with the specified line widths. 260 * 261 * @param top the top border line width. 262 * @param left the left border line width. 263 * @param bottom the bottom border line width. 264 * @param right the right border line width. 265 */ 266 public void setBorder(double top, double left, double bottom, 267 double right) { 268 setFrame(new BlockBorder(top, left, bottom, right)); 269 } 270 271 /** 272 * Returns the current frame (border). 273 * 274 * @return The frame. 275 * 276 * @since 1.0.5 277 * @see #setFrame(BlockFrame) 278 */ 279 public BlockFrame getFrame() { 280 return this.frame; 281 } 282 283 /** 284 * Sets the frame (or border). 285 * 286 * @param frame the frame (<code>null</code> not permitted). 287 * 288 * @since 1.0.5 289 * @see #getFrame() 290 */ 291 public void setFrame(BlockFrame frame) { 292 ParamChecks.nullNotPermitted(frame, "frame"); 293 this.frame = frame; 294 } 295 296 /** 297 * Returns the padding. 298 * 299 * @return The padding (never <code>null</code>). 300 * 301 * @see #setPadding(RectangleInsets) 302 */ 303 public RectangleInsets getPadding() { 304 return this.padding; 305 } 306 307 /** 308 * Sets the padding (use {@link RectangleInsets#ZERO_INSETS} for no 309 * padding). 310 * 311 * @param padding the padding (<code>null</code> not permitted). 312 * 313 * @see #getPadding() 314 */ 315 public void setPadding(RectangleInsets padding) { 316 ParamChecks.nullNotPermitted(padding, "padding"); 317 this.padding = padding; 318 } 319 320 /** 321 * Sets the padding. 322 * 323 * @param top the top padding. 324 * @param left the left padding. 325 * @param bottom the bottom padding. 326 * @param right the right padding. 327 */ 328 public void setPadding(double top, double left, double bottom, 329 double right) { 330 setPadding(new RectangleInsets(top, left, bottom, right)); 331 } 332 333 /** 334 * Returns the x-offset for the content within the block. 335 * 336 * @return The x-offset. 337 * 338 * @see #getContentYOffset() 339 */ 340 public double getContentXOffset() { 341 return this.margin.getLeft() + this.frame.getInsets().getLeft() 342 + this.padding.getLeft(); 343 } 344 345 /** 346 * Returns the y-offset for the content within the block. 347 * 348 * @return The y-offset. 349 * 350 * @see #getContentXOffset() 351 */ 352 public double getContentYOffset() { 353 return this.margin.getTop() + this.frame.getInsets().getTop() 354 + this.padding.getTop(); 355 } 356 357 /** 358 * Arranges the contents of the block, with no constraints, and returns 359 * the block size. 360 * 361 * @param g2 the graphics device. 362 * 363 * @return The block size (in Java2D units, never <code>null</code>). 364 */ 365 public Size2D arrange(Graphics2D g2) { 366 return arrange(g2, RectangleConstraint.NONE); 367 } 368 369 /** 370 * Arranges the contents of the block, within the given constraints, and 371 * returns the block size. 372 * 373 * @param g2 the graphics device. 374 * @param constraint the constraint (<code>null</code> not permitted). 375 * 376 * @return The block size (in Java2D units, never <code>null</code>). 377 */ 378 public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) { 379 Size2D base = new Size2D(getWidth(), getHeight()); 380 return constraint.calculateConstrainedSize(base); 381 } 382 383 /** 384 * Returns the current bounds of the block. 385 * 386 * @return The bounds. 387 * 388 * @see #setBounds(Rectangle2D) 389 */ 390 public Rectangle2D getBounds() { 391 return this.bounds; 392 } 393 394 /** 395 * Sets the bounds of the block. 396 * 397 * @param bounds the bounds (<code>null</code> not permitted). 398 * 399 * @see #getBounds() 400 */ 401 public void setBounds(Rectangle2D bounds) { 402 ParamChecks.nullNotPermitted(bounds, "bounds"); 403 this.bounds = bounds; 404 } 405 406 /** 407 * Calculate the width available for content after subtracting 408 * the margin, border and padding space from the specified fixed 409 * width. 410 * 411 * @param fixedWidth the fixed width. 412 * 413 * @return The available space. 414 * 415 * @see #trimToContentHeight(double) 416 */ 417 protected double trimToContentWidth(double fixedWidth) { 418 double result = this.margin.trimWidth(fixedWidth); 419 result = this.frame.getInsets().trimWidth(result); 420 result = this.padding.trimWidth(result); 421 return Math.max(result, 0.0); 422 } 423 424 /** 425 * Calculate the height available for content after subtracting 426 * the margin, border and padding space from the specified fixed 427 * height. 428 * 429 * @param fixedHeight the fixed height. 430 * 431 * @return The available space. 432 * 433 * @see #trimToContentWidth(double) 434 */ 435 protected double trimToContentHeight(double fixedHeight) { 436 double result = this.margin.trimHeight(fixedHeight); 437 result = this.frame.getInsets().trimHeight(result); 438 result = this.padding.trimHeight(result); 439 return Math.max(result, 0.0); 440 } 441 442 /** 443 * Returns a constraint for the content of this block that will result in 444 * the bounds of the block matching the specified constraint. 445 * 446 * @param c the outer constraint (<code>null</code> not permitted). 447 * 448 * @return The content constraint. 449 */ 450 protected RectangleConstraint toContentConstraint(RectangleConstraint c) { 451 ParamChecks.nullNotPermitted(c, "c"); 452 if (c.equals(RectangleConstraint.NONE)) { 453 return c; 454 } 455 double w = c.getWidth(); 456 Range wr = c.getWidthRange(); 457 double h = c.getHeight(); 458 Range hr = c.getHeightRange(); 459 double ww = trimToContentWidth(w); 460 double hh = trimToContentHeight(h); 461 Range wwr = trimToContentWidth(wr); 462 Range hhr = trimToContentHeight(hr); 463 return new RectangleConstraint(ww, wwr, c.getWidthConstraintType(), 464 hh, hhr, c.getHeightConstraintType()); 465 } 466 467 private Range trimToContentWidth(Range r) { 468 if (r == null) { 469 return null; 470 } 471 double lowerBound = 0.0; 472 double upperBound = Double.POSITIVE_INFINITY; 473 if (r.getLowerBound() > 0.0) { 474 lowerBound = trimToContentWidth(r.getLowerBound()); 475 } 476 if (r.getUpperBound() < Double.POSITIVE_INFINITY) { 477 upperBound = trimToContentWidth(r.getUpperBound()); 478 } 479 return new Range(lowerBound, upperBound); 480 } 481 482 private Range trimToContentHeight(Range r) { 483 if (r == null) { 484 return null; 485 } 486 double lowerBound = 0.0; 487 double upperBound = Double.POSITIVE_INFINITY; 488 if (r.getLowerBound() > 0.0) { 489 lowerBound = trimToContentHeight(r.getLowerBound()); 490 } 491 if (r.getUpperBound() < Double.POSITIVE_INFINITY) { 492 upperBound = trimToContentHeight(r.getUpperBound()); 493 } 494 return new Range(lowerBound, upperBound); 495 } 496 497 /** 498 * Adds the margin, border and padding to the specified content width. 499 * 500 * @param contentWidth the content width. 501 * 502 * @return The adjusted width. 503 */ 504 protected double calculateTotalWidth(double contentWidth) { 505 double result = contentWidth; 506 result = this.padding.extendWidth(result); 507 result = this.frame.getInsets().extendWidth(result); 508 result = this.margin.extendWidth(result); 509 return result; 510 } 511 512 /** 513 * Adds the margin, border and padding to the specified content height. 514 * 515 * @param contentHeight the content height. 516 * 517 * @return The adjusted height. 518 */ 519 protected double calculateTotalHeight(double contentHeight) { 520 double result = contentHeight; 521 result = this.padding.extendHeight(result); 522 result = this.frame.getInsets().extendHeight(result); 523 result = this.margin.extendHeight(result); 524 return result; 525 } 526 527 /** 528 * Reduces the specified area by the amount of space consumed 529 * by the margin. 530 * 531 * @param area the area (<code>null</code> not permitted). 532 * 533 * @return The trimmed area. 534 */ 535 protected Rectangle2D trimMargin(Rectangle2D area) { 536 // defer argument checking... 537 this.margin.trim(area); 538 return area; 539 } 540 541 /** 542 * Reduces the specified area by the amount of space consumed 543 * by the border. 544 * 545 * @param area the area (<code>null</code> not permitted). 546 * 547 * @return The trimmed area. 548 */ 549 protected Rectangle2D trimBorder(Rectangle2D area) { 550 // defer argument checking... 551 this.frame.getInsets().trim(area); 552 return area; 553 } 554 555 /** 556 * Reduces the specified area by the amount of space consumed 557 * by the padding. 558 * 559 * @param area the area (<code>null</code> not permitted). 560 * 561 * @return The trimmed area. 562 */ 563 protected Rectangle2D trimPadding(Rectangle2D area) { 564 // defer argument checking... 565 this.padding.trim(area); 566 return area; 567 } 568 569 /** 570 * Draws the border around the perimeter of the specified area. 571 * 572 * @param g2 the graphics device. 573 * @param area the area. 574 */ 575 protected void drawBorder(Graphics2D g2, Rectangle2D area) { 576 this.frame.draw(g2, area); 577 } 578 579 /** 580 * Tests this block for equality with an arbitrary object. 581 * 582 * @param obj the object (<code>null</code> permitted). 583 * 584 * @return A boolean. 585 */ 586 @Override 587 public boolean equals(Object obj) { 588 if (obj == this) { 589 return true; 590 } 591 if (!(obj instanceof AbstractBlock)) { 592 return false; 593 } 594 AbstractBlock that = (AbstractBlock) obj; 595 if (!ObjectUtilities.equal(this.id, that.id)) { 596 return false; 597 } 598 if (!this.frame.equals(that.frame)) { 599 return false; 600 } 601 if (!this.bounds.equals(that.bounds)) { 602 return false; 603 } 604 if (!this.margin.equals(that.margin)) { 605 return false; 606 } 607 if (!this.padding.equals(that.padding)) { 608 return false; 609 } 610 if (this.height != that.height) { 611 return false; 612 } 613 if (this.width != that.width) { 614 return false; 615 } 616 return true; 617 } 618 619 /** 620 * Returns a clone of this block. 621 * 622 * @return A clone. 623 * 624 * @throws CloneNotSupportedException if there is a problem creating the 625 * clone. 626 */ 627 @Override 628 public Object clone() throws CloneNotSupportedException { 629 AbstractBlock clone = (AbstractBlock) super.clone(); 630 clone.bounds = (Rectangle2D) ShapeUtilities.clone(this.bounds); 631 if (this.frame instanceof PublicCloneable) { 632 PublicCloneable pc = (PublicCloneable) this.frame; 633 clone.frame = (BlockFrame) pc.clone(); 634 } 635 return clone; 636 } 637 638 /** 639 * Provides serialization support. 640 * 641 * @param stream the output stream. 642 * 643 * @throws IOException if there is an I/O error. 644 */ 645 private void writeObject(ObjectOutputStream stream) throws IOException { 646 stream.defaultWriteObject(); 647 SerialUtilities.writeShape(this.bounds, stream); 648 } 649 650 /** 651 * Provides serialization support. 652 * 653 * @param stream the input stream. 654 * 655 * @throws IOException if there is an I/O error. 656 * @throws ClassNotFoundException if there is a classpath problem. 657 */ 658 private void readObject(ObjectInputStream stream) 659 throws IOException, ClassNotFoundException { 660 stream.defaultReadObject(); 661 this.bounds = (Rectangle2D) SerialUtilities.readShape(stream); 662 } 663 664}