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.1 2005/10/25 20:39:38 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 public double getContentXOffset() { 264 return this.margin.getLeft() + this.border.getInsets().getLeft() + this.padding.getLeft(); 265 } 266 267 public double getContentYOffset() { 268 return this.margin.getTop() + this.border.getInsets().getTop() + this.padding.getTop(); 269 } 270 271 /** 272 * Sets the padding. 273 * 274 * @param top the top padding. 275 * @param left the left padding. 276 * @param bottom the bottom padding. 277 * @param right the right padding. 278 */ 279 public void setPadding(double top, double left, double bottom, 280 double right) { 281 setPadding(new RectangleInsets(top, left, bottom, right)); 282 } 283 284 /** 285 * Arranges the contents of the block, with no constraints, and returns 286 * the block size. 287 * 288 * @param g2 the graphics device. 289 * 290 * @return The block size (in Java2D units, never <code>null</code>). 291 */ 292 public Size2D arrange(Graphics2D g2) { 293 return arrange(g2, RectangleConstraint.NONE); 294 } 295 296 /** 297 * Arranges the contents of the block, within the given constraints, and 298 * returns the block size. 299 * 300 * @param g2 the graphics device. 301 * @param constraint the constraint (<code>null</code> not permitted). 302 * 303 * @return The block size (in Java2D units, never <code>null</code>). 304 */ 305 public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) { 306 Size2D base = new Size2D(getWidth(), getHeight()); 307 return constraint.calculateConstrainedSize(base); 308 } 309 310 /** 311 * Returns the current bounds of the block. 312 * 313 * @return The bounds. 314 */ 315 public Rectangle2D getBounds() { 316 return this.bounds; 317 } 318 319 /** 320 * Sets the bounds of the block. 321 * 322 * @param bounds the bounds (<code>null</code> not permitted). 323 */ 324 public void setBounds(Rectangle2D bounds) { 325 if (bounds == null) { 326 throw new IllegalArgumentException("Null 'bounds' argument."); 327 } 328 this.bounds = bounds; 329 } 330 331 /** 332 * Calculate the width available for content after subtracting 333 * the margin, border and padding space from the specified fixed 334 * width. 335 * 336 * @param fixedWidth the fixed width. 337 * 338 * @return The available space. 339 */ 340 protected double trimToContentWidth(double fixedWidth) { 341 double result = this.margin.trimWidth(fixedWidth); 342 result = this.border.getInsets().trimWidth(result); 343 result = this.padding.trimWidth(result); 344 return Math.max(result, 0.0); 345 } 346 347 /** 348 * Calculate the height available for content after subtracting 349 * the margin, border and padding space from the specified fixed 350 * height. 351 * 352 * @param fixedHeight the fixed height. 353 * 354 * @return The available space. 355 */ 356 protected double trimToContentHeight(double fixedHeight) { 357 double result = this.margin.trimHeight(fixedHeight); 358 result = this.border.getInsets().trimHeight(result); 359 result = this.padding.trimHeight(result); 360 return Math.max(result, 0.0); 361 } 362 363 /** 364 * Returns a constraint for the content of this block that will result in 365 * the bounds of the block matching the specified constraint. 366 * 367 * @param c the outer constraint (<code>null</code> not permitted). 368 * 369 * @return The content constraint. 370 */ 371 protected RectangleConstraint toContentConstraint(RectangleConstraint c) { 372 if (c == null) { 373 throw new IllegalArgumentException("Null 'c' argument."); 374 } 375 if (c.equals(RectangleConstraint.NONE)) { 376 return c; 377 } 378 double w = c.getWidth(); 379 Range wr = c.getWidthRange(); 380 double h = c.getHeight(); 381 Range hr = c.getHeightRange(); 382 double ww = trimToContentWidth(w); 383 double hh = trimToContentHeight(h); 384 Range wwr = trimToContentWidth(wr); 385 Range hhr = trimToContentHeight(hr); 386 return new RectangleConstraint( 387 ww, wwr, c.getWidthConstraintType(), 388 hh, hhr, c.getHeightConstraintType() 389 ); 390 } 391 392 private Range trimToContentWidth(Range r) { 393 if (r == null) { 394 return null; 395 } 396 double lowerBound = 0.0; 397 double upperBound = Double.POSITIVE_INFINITY; 398 if (r.getLowerBound() > 0.0) { 399 lowerBound = trimToContentWidth(r.getLowerBound()); 400 } 401 if (r.getUpperBound() < Double.POSITIVE_INFINITY) { 402 upperBound = trimToContentWidth(r.getUpperBound()); 403 } 404 return new Range(lowerBound, upperBound); 405 } 406 407 private Range trimToContentHeight(Range r) { 408 if (r == null) { 409 return null; 410 } 411 double lowerBound = 0.0; 412 double upperBound = Double.POSITIVE_INFINITY; 413 if (r.getLowerBound() > 0.0) { 414 lowerBound = trimToContentHeight(r.getLowerBound()); 415 } 416 if (r.getUpperBound() < Double.POSITIVE_INFINITY) { 417 upperBound = trimToContentHeight(r.getUpperBound()); 418 } 419 return new Range(lowerBound, upperBound); 420 } 421 422 /** 423 * Adds the margin, border and padding to the specified content width. 424 * 425 * @param contentWidth the content width. 426 * 427 * @return The adjusted width. 428 */ 429 protected double calculateTotalWidth(double contentWidth) { 430 double result = contentWidth; 431 result = this.padding.extendWidth(result); 432 result = this.border.getInsets().extendWidth(result); 433 result = this.margin.extendWidth(result); 434 return result; 435 } 436 437 /** 438 * Adds the margin, border and padding to the specified content height. 439 * 440 * @param contentHeight the content height. 441 * 442 * @return The adjusted height. 443 */ 444 protected double calculateTotalHeight(double contentHeight) { 445 double result = contentHeight; 446 result = this.padding.extendHeight(result); 447 result = this.border.getInsets().extendHeight(result); 448 result = this.margin.extendHeight(result); 449 return result; 450 } 451 452 /** 453 * Reduces the specified area by the amount of space consumed 454 * by the margin. 455 * 456 * @param area the area (<code>null</code> not permitted). 457 * 458 * @return The trimmed area. 459 */ 460 protected Rectangle2D trimMargin(Rectangle2D area) { 461 // defer argument checking... 462 this.margin.trim(area); 463 return area; 464 } 465 466 /** 467 * Reduces the specified area by the amount of space consumed 468 * by the border. 469 * 470 * @param area the area (<code>null</code> not permitted). 471 * 472 * @return The trimmed area. 473 */ 474 protected Rectangle2D trimBorder(Rectangle2D area) { 475 // defer argument checking... 476 this.border.getInsets().trim(area); 477 return area; 478 } 479 480 /** 481 * Reduces the specified area by the amount of space consumed 482 * by the padding. 483 * 484 * @param area the area (<code>null</code> not permitted). 485 * 486 * @return The trimmed area. 487 */ 488 protected Rectangle2D trimPadding(Rectangle2D area) { 489 // defer argument checking... 490 this.padding.trim(area); 491 return area; 492 } 493 494 /** 495 * Draws the border around the perimeter of the specified area. 496 * 497 * @param g2 the graphics device. 498 * @param area the area. 499 */ 500 protected void drawBorder(Graphics2D g2, Rectangle2D area) { 501 this.border.draw(g2, area); 502 } 503 504 /** 505 * Tests this block for equality with an arbitrary object. 506 * 507 * @param obj the object (<code>null</code> permitted). 508 * 509 * @return A boolean. 510 */ 511 public boolean equals(Object obj) { 512 if (obj == this) { 513 return true; 514 } 515 if (!(obj instanceof AbstractBlock)) { 516 return false; 517 } 518 AbstractBlock that = (AbstractBlock) obj; 519 if (!this.border.equals(that.border)) { 520 return false; 521 } 522 if (!this.bounds.equals(that.bounds)) { 523 return false; 524 } 525 if (!this.margin.equals(that.margin)) { 526 return false; 527 } 528 if (!this.padding.equals(that.padding)) { 529 return false; 530 } 531 if (this.height != that.height) { 532 return false; 533 } 534 if (this.width != that.width) { 535 return false; 536 } 537 return true; 538 } 539 540 /** 541 * Provides serialization support. 542 * 543 * @param stream the output stream. 544 * 545 * @throws IOException if there is an I/O error. 546 */ 547 private void writeObject(ObjectOutputStream stream) throws IOException { 548 stream.defaultWriteObject(); 549 SerialUtilities.writeShape(this.bounds, stream); 550 } 551 552 /** 553 * Provides serialization support. 554 * 555 * @param stream the input stream. 556 * 557 * @throws IOException if there is an I/O error. 558 * @throws ClassNotFoundException if there is a classpath problem. 559 */ 560 private void readObject(ObjectInputStream stream) 561 throws IOException, ClassNotFoundException { 562 stream.defaultReadObject(); 563 this.bounds = (Rectangle2D) SerialUtilities.readShape(stream); 564 } 565 566 }