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