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