Source for org.jfree.chart.block.AbstractBlock

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  * 
  27:  * ------------------
  28:  * AbstractBlock.java
  29:  * ------------------
  30:  * (C) Copyright 2004-2007, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: AbstractBlock.java,v 1.12.2.4 2007/03/20 08:31:20 mungady Exp $
  36:  *
  37:  * Changes:
  38:  * --------
  39:  * 22-Oct-2004 : Version 1 (DG);
  40:  * 02-Feb-2005 : Added accessor methods for margin (DG);
  41:  * 04-Feb-2005 : Added equals() method and implemented Serializable (DG);
  42:  * 03-May-2005 : Added null argument checks (DG);
  43:  * 06-May-2005 : Added convenience methods for setting margin, border and 
  44:  *               padding (DG);
  45:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  46:  * 16-Mar-2007 : Changed border from BlockBorder to BlockFrame, updated 
  47:  *               equals(), and implemented Cloneable (DG);
  48:  * 
  49:  */
  50: 
  51: package org.jfree.chart.block;
  52: 
  53: import java.awt.Graphics2D;
  54: import java.awt.geom.Rectangle2D;
  55: import java.io.IOException;
  56: import java.io.ObjectInputStream;
  57: import java.io.ObjectOutputStream;
  58: import java.io.Serializable;
  59: 
  60: import org.jfree.data.Range;
  61: import org.jfree.io.SerialUtilities;
  62: import org.jfree.ui.RectangleInsets;
  63: import org.jfree.ui.Size2D;
  64: import org.jfree.util.ObjectUtilities;
  65: import org.jfree.util.PublicCloneable;
  66: import org.jfree.util.ShapeUtilities;
  67: 
  68: /**
  69:  * A convenience class for creating new classes that implement 
  70:  * the {@link Block} interface.
  71:  */
  72: public class AbstractBlock implements Cloneable, Serializable {
  73: 
  74:     /** For serialization. */
  75:     private static final long serialVersionUID = 7689852412141274563L;
  76:     
  77:     /** The id for the block. */
  78:     private String id;
  79:     
  80:     /** The margin around the outside of the block. */
  81:     private RectangleInsets margin;
  82:     
  83:     /** The frame (or border) for the block. */
  84:     private BlockFrame frame;
  85: 
  86:     /** The padding between the block content and the border. */
  87:     private RectangleInsets padding;
  88:     
  89:     /** 
  90:      * The natural width of the block (may be overridden if there are 
  91:      * constraints in sizing).
  92:      */
  93:     private double width;
  94:     
  95:     /** 
  96:      * The natural height of the block (may be overridden if there are 
  97:      * constraints in sizing).
  98:      */
  99:     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:         if (margin == null) {
 210:             throw new IllegalArgumentException("Null 'margin' argument.");   
 211:         }
 212:         this.margin = margin;
 213:     }
 214: 
 215:     /**
 216:      * Sets the margin.
 217:      * 
 218:      * @param top  the top margin.
 219:      * @param left  the left margin.
 220:      * @param bottom  the bottom margin.
 221:      * @param right  the right margin.
 222:      * 
 223:      * @see #getMargin()
 224:      */
 225:     public void setMargin(double top, double left, double bottom, 
 226:                           double right) {
 227:         setMargin(new RectangleInsets(top, left, bottom, right));
 228:     }
 229: 
 230:     /**
 231:      * Returns the border.
 232:      * 
 233:      * @return The border (never <code>null</code>).
 234:      * 
 235:      * @deprecated Use getBlockFrame() instead.
 236:      */
 237:     public BlockBorder getBorder() {
 238:         if (this.frame instanceof BlockBorder) {
 239:             return (BlockBorder) this.frame;
 240:         }
 241:         else {
 242:             return null;
 243:         }
 244:     }
 245:     
 246:     /**
 247:      * Sets the border for the block (use {@link BlockBorder#NONE} for
 248:      * no border).
 249:      * 
 250:      * @param border  the border (<code>null</code> not permitted).
 251:      * 
 252:      * @see #getBorder()
 253:      * 
 254:      * @deprecated Use setBorderFrame() instead.
 255:      */
 256:     public void setBorder(BlockBorder border) {
 257:         setFrame(border);
 258:     }
 259:     
 260:     /**
 261:      * Sets a black border with the specified line widths.
 262:      * 
 263:      * @param top  the top border line width.
 264:      * @param left  the left border line width.
 265:      * @param bottom  the bottom border line width.
 266:      * @param right  the right border line width.
 267:      */
 268:     public void setBorder(double top, double left, double bottom, 
 269:                           double right) {
 270:         setFrame(new BlockBorder(top, left, bottom, right));
 271:     }
 272:     
 273:     /**
 274:      * Returns the current frame (border).
 275:      * 
 276:      * @return The frame.
 277:      * 
 278:      * @since 1.0.5
 279:      */
 280:     public BlockFrame getFrame() {
 281:         return this.frame;
 282:     }
 283:     
 284:     /**
 285:      * Sets the frame (or border).
 286:      * 
 287:      * @param frame  the frame (<code>null</code> not permitted).
 288:      * 
 289:      * @since 1.0.5
 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: }