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