Source for org.jfree.chart.title.TextTitle

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2008, 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:  * TextTitle.java
  29:  * --------------
  30:  * (C) Copyright 2000-2008, by David Berry and Contributors.
  31:  *
  32:  * Original Author:  David Berry;
  33:  * Contributor(s):   David Gilbert (for Object Refinery Limited);
  34:  *                   Nicolas Brodu;
  35:  *
  36:  * Changes (from 18-Sep-2001)
  37:  * --------------------------
  38:  * 18-Sep-2001 : Added standard header (DG);
  39:  * 07-Nov-2001 : Separated the JCommon Class Library classes, JFreeChart now
  40:  *               requires jcommon.jar (DG);
  41:  * 09-Jan-2002 : Updated Javadoc comments (DG);
  42:  * 07-Feb-2002 : Changed Insets --> Spacer in AbstractTitle.java (DG);
  43:  * 06-Mar-2002 : Updated import statements (DG);
  44:  * 25-Jun-2002 : Removed redundant imports (DG);
  45:  * 18-Sep-2002 : Fixed errors reported by Checkstyle (DG);
  46:  * 28-Oct-2002 : Small modifications while changing JFreeChart class (DG);
  47:  * 13-Mar-2003 : Changed width used for relative spacing to fix bug 703050 (DG);
  48:  * 26-Mar-2003 : Implemented Serializable (DG);
  49:  * 15-Jul-2003 : Fixed null pointer exception (DG);
  50:  * 11-Sep-2003 : Implemented Cloneable (NB)
  51:  * 22-Sep-2003 : Added checks for null values and throw nullpointer
  52:  *               exceptions (TM);
  53:  *               Background paint was not serialized.
  54:  * 07-Oct-2003 : Added fix for exception caused by empty string in title (DG);
  55:  * 29-Oct-2003 : Added workaround for text alignment in PDF output (DG);
  56:  * 03-Feb-2004 : Fixed bug in getPreferredWidth() method (DG);
  57:  * 17-Feb-2004 : Added clone() method and fixed bug in equals() method (DG);
  58:  * 01-Apr-2004 : Changed java.awt.geom.Dimension2D to org.jfree.ui.Size2D
  59:  *               because of JDK bug 4976448 which persists on JDK 1.3.1.  Also
  60:  *               fixed bug in getPreferredHeight() method (DG);
  61:  * 29-Apr-2004 : Fixed bug in getPreferredWidth() method - see bug id
  62:  *               944173 (DG);
  63:  * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0
  64:  *               release (DG);
  65:  * 08-Feb-2005 : Updated for changes in RectangleConstraint class (DG);
  66:  * 11-Feb-2005 : Implemented PublicCloneable (DG);
  67:  * 20-Apr-2005 : Added support for tooltips (DG);
  68:  * 26-Apr-2005 : Removed LOGGER (DG);
  69:  * 06-Jun-2005 : Modified equals() to handle GradientPaint (DG);
  70:  * 06-Jul-2005 : Added flag to control whether or not the title expands to
  71:  *               fit the available space (DG);
  72:  * 07-Oct-2005 : Added textAlignment attribute (DG);
  73:  * ------------- JFREECHART 1.0.x RELEASED ------------------------------------
  74:  * 13-Dec-2005 : Fixed bug 1379331 - incorrect drawing with LEFT or RIGHT
  75:  *               title placement (DG);
  76:  * 19-Dec-2007 : Implemented some of the missing arrangement options (DG);
  77:  * 28-Apr-2008 : Added option for maximum lines, and fixed minor bugs in
  78:  *               equals() method (DG);
  79:  *
  80:  */
  81: 
  82: package org.jfree.chart.title;
  83: 
  84: import java.awt.Color;
  85: import java.awt.Font;
  86: import java.awt.Graphics2D;
  87: import java.awt.Paint;
  88: import java.awt.geom.Rectangle2D;
  89: import java.io.IOException;
  90: import java.io.ObjectInputStream;
  91: import java.io.ObjectOutputStream;
  92: import java.io.Serializable;
  93: 
  94: import org.jfree.chart.block.BlockResult;
  95: import org.jfree.chart.block.EntityBlockParams;
  96: import org.jfree.chart.block.LengthConstraintType;
  97: import org.jfree.chart.block.RectangleConstraint;
  98: import org.jfree.chart.entity.ChartEntity;
  99: import org.jfree.chart.entity.EntityCollection;
 100: import org.jfree.chart.entity.StandardEntityCollection;
 101: import org.jfree.chart.event.TitleChangeEvent;
 102: import org.jfree.data.Range;
 103: import org.jfree.io.SerialUtilities;
 104: import org.jfree.text.G2TextMeasurer;
 105: import org.jfree.text.TextBlock;
 106: import org.jfree.text.TextBlockAnchor;
 107: import org.jfree.text.TextUtilities;
 108: import org.jfree.ui.HorizontalAlignment;
 109: import org.jfree.ui.RectangleEdge;
 110: import org.jfree.ui.RectangleInsets;
 111: import org.jfree.ui.Size2D;
 112: import org.jfree.ui.VerticalAlignment;
 113: import org.jfree.util.ObjectUtilities;
 114: import org.jfree.util.PaintUtilities;
 115: import org.jfree.util.PublicCloneable;
 116: 
 117: /**
 118:  * A chart title that displays a text string with automatic wrapping as
 119:  * required.
 120:  */
 121: public class TextTitle extends Title
 122:                        implements Serializable, Cloneable, PublicCloneable {
 123: 
 124:     /** For serialization. */
 125:     private static final long serialVersionUID = 8372008692127477443L;
 126: 
 127:     /** The default font. */
 128:     public static final Font DEFAULT_FONT = new Font("SansSerif", Font.BOLD,
 129:             12);
 130: 
 131:     /** The default text color. */
 132:     public static final Paint DEFAULT_TEXT_PAINT = Color.black;
 133: 
 134:     /** The title text. */
 135:     private String text;
 136: 
 137:     /** The font used to display the title. */
 138:     private Font font;
 139: 
 140:     /** The text alignment. */
 141:     private HorizontalAlignment textAlignment;
 142: 
 143:     /** The paint used to display the title text. */
 144:     private transient Paint paint;
 145: 
 146:     /** The background paint. */
 147:     private transient Paint backgroundPaint;
 148: 
 149:     /** The tool tip text (can be <code>null</code>). */
 150:     private String toolTipText;
 151: 
 152:     /** The URL text (can be <code>null</code>). */
 153:     private String urlText;
 154: 
 155:     /** The content. */
 156:     private TextBlock content;
 157: 
 158:     /**
 159:      * A flag that controls whether the title expands to fit the available
 160:      * space..
 161:      */
 162:     private boolean expandToFitSpace = false;
 163: 
 164:     /**
 165:      * The maximum number of lines to display.
 166:      *
 167:      * @since 1.0.10
 168:      */
 169:     private int maximumLinesToDisplay = Integer.MAX_VALUE;
 170: 
 171:     /**
 172:      * Creates a new title, using default attributes where necessary.
 173:      */
 174:     public TextTitle() {
 175:         this("");
 176:     }
 177: 
 178:     /**
 179:      * Creates a new title, using default attributes where necessary.
 180:      *
 181:      * @param text  the title text (<code>null</code> not permitted).
 182:      */
 183:     public TextTitle(String text) {
 184:         this(text, TextTitle.DEFAULT_FONT, TextTitle.DEFAULT_TEXT_PAINT,
 185:                 Title.DEFAULT_POSITION, Title.DEFAULT_HORIZONTAL_ALIGNMENT,
 186:                 Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING);
 187:     }
 188: 
 189:     /**
 190:      * Creates a new title, using default attributes where necessary.
 191:      *
 192:      * @param text  the title text (<code>null</code> not permitted).
 193:      * @param font  the title font (<code>null</code> not permitted).
 194:      */
 195:     public TextTitle(String text, Font font) {
 196:         this(text, font, TextTitle.DEFAULT_TEXT_PAINT, Title.DEFAULT_POSITION,
 197:                 Title.DEFAULT_HORIZONTAL_ALIGNMENT,
 198:                 Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING);
 199:     }
 200: 
 201:     /**
 202:      * Creates a new title.
 203:      *
 204:      * @param text  the text for the title (<code>null</code> not permitted).
 205:      * @param font  the title font (<code>null</code> not permitted).
 206:      * @param paint  the title paint (<code>null</code> not permitted).
 207:      * @param position  the title position (<code>null</code> not permitted).
 208:      * @param horizontalAlignment  the horizontal alignment (<code>null</code>
 209:      *                             not permitted).
 210:      * @param verticalAlignment  the vertical alignment (<code>null</code> not
 211:      *                           permitted).
 212:      * @param padding  the space to leave around the outside of the title.
 213:      */
 214:     public TextTitle(String text, Font font, Paint paint,
 215:                      RectangleEdge position,
 216:                      HorizontalAlignment horizontalAlignment,
 217:                      VerticalAlignment verticalAlignment,
 218:                      RectangleInsets padding) {
 219: 
 220:         super(position, horizontalAlignment, verticalAlignment, padding);
 221: 
 222:         if (text == null) {
 223:             throw new NullPointerException("Null 'text' argument.");
 224:         }
 225:         if (font == null) {
 226:             throw new NullPointerException("Null 'font' argument.");
 227:         }
 228:         if (paint == null) {
 229:             throw new NullPointerException("Null 'paint' argument.");
 230:         }
 231:         this.text = text;
 232:         this.font = font;
 233:         this.paint = paint;
 234:         // the textAlignment and the horizontalAlignment are separate things,
 235:         // but it makes sense for the default textAlignment to match the
 236:         // title's horizontal alignment...
 237:         this.textAlignment = horizontalAlignment;
 238:         this.backgroundPaint = null;
 239:         this.content = null;
 240:         this.toolTipText = null;
 241:         this.urlText = null;
 242: 
 243:     }
 244: 
 245:     /**
 246:      * Returns the title text.
 247:      *
 248:      * @return The text (never <code>null</code>).
 249:      *
 250:      * @see #setText(String)
 251:      */
 252:     public String getText() {
 253:         return this.text;
 254:     }
 255: 
 256:     /**
 257:      * Sets the title to the specified text and sends a
 258:      * {@link TitleChangeEvent} to all registered listeners.
 259:      *
 260:      * @param text  the text (<code>null</code> not permitted).
 261:      */
 262:     public void setText(String text) {
 263:         if (text == null) {
 264:             throw new IllegalArgumentException("Null 'text' argument.");
 265:         }
 266:         if (!this.text.equals(text)) {
 267:             this.text = text;
 268:             notifyListeners(new TitleChangeEvent(this));
 269:         }
 270:     }
 271: 
 272:     /**
 273:      * Returns the text alignment.  This controls how the text is aligned
 274:      * within the title's bounds, whereas the title's horizontal alignment
 275:      * controls how the title's bounding rectangle is aligned within the
 276:      * drawing space.
 277:      *
 278:      * @return The text alignment.
 279:      */
 280:     public HorizontalAlignment getTextAlignment() {
 281:         return this.textAlignment;
 282:     }
 283: 
 284:     /**
 285:      * Sets the text alignment and sends a {@link TitleChangeEvent} to
 286:      * all registered listeners.
 287:      *
 288:      * @param alignment  the alignment (<code>null</code> not permitted).
 289:      */
 290:     public void setTextAlignment(HorizontalAlignment alignment) {
 291:         if (alignment == null) {
 292:             throw new IllegalArgumentException("Null 'alignment' argument.");
 293:         }
 294:         this.textAlignment = alignment;
 295:         notifyListeners(new TitleChangeEvent(this));
 296:     }
 297: 
 298:     /**
 299:      * Returns the font used to display the title string.
 300:      *
 301:      * @return The font (never <code>null</code>).
 302:      *
 303:      * @see #setFont(Font)
 304:      */
 305:     public Font getFont() {
 306:         return this.font;
 307:     }
 308: 
 309:     /**
 310:      * Sets the font used to display the title string.  Registered listeners
 311:      * are notified that the title has been modified.
 312:      *
 313:      * @param font  the new font (<code>null</code> not permitted).
 314:      *
 315:      * @see #getFont()
 316:      */
 317:     public void setFont(Font font) {
 318:         if (font == null) {
 319:             throw new IllegalArgumentException("Null 'font' argument.");
 320:         }
 321:         if (!this.font.equals(font)) {
 322:             this.font = font;
 323:             notifyListeners(new TitleChangeEvent(this));
 324:         }
 325:     }
 326: 
 327:     /**
 328:      * Returns the paint used to display the title string.
 329:      *
 330:      * @return The paint (never <code>null</code>).
 331:      *
 332:      * @see #setPaint(Paint)
 333:      */
 334:     public Paint getPaint() {
 335:         return this.paint;
 336:     }
 337: 
 338:     /**
 339:      * Sets the paint used to display the title string.  Registered listeners
 340:      * are notified that the title has been modified.
 341:      *
 342:      * @param paint  the new paint (<code>null</code> not permitted).
 343:      *
 344:      * @see #getPaint()
 345:      */
 346:     public void setPaint(Paint paint) {
 347:         if (paint == null) {
 348:             throw new IllegalArgumentException("Null 'paint' argument.");
 349:         }
 350:         if (!this.paint.equals(paint)) {
 351:             this.paint = paint;
 352:             notifyListeners(new TitleChangeEvent(this));
 353:         }
 354:     }
 355: 
 356:     /**
 357:      * Returns the background paint.
 358:      *
 359:      * @return The paint (possibly <code>null</code>).
 360:      */
 361:     public Paint getBackgroundPaint() {
 362:         return this.backgroundPaint;
 363:     }
 364: 
 365:     /**
 366:      * Sets the background paint and sends a {@link TitleChangeEvent} to all
 367:      * registered listeners.  If you set this attribute to <code>null</code>,
 368:      * no background is painted (which makes the title background transparent).
 369:      *
 370:      * @param paint  the background paint (<code>null</code> permitted).
 371:      */
 372:     public void setBackgroundPaint(Paint paint) {
 373:         this.backgroundPaint = paint;
 374:         notifyListeners(new TitleChangeEvent(this));
 375:     }
 376: 
 377:     /**
 378:      * Returns the tool tip text.
 379:      *
 380:      * @return The tool tip text (possibly <code>null</code>).
 381:      */
 382:     public String getToolTipText() {
 383:         return this.toolTipText;
 384:     }
 385: 
 386:     /**
 387:      * Sets the tool tip text to the specified text and sends a
 388:      * {@link TitleChangeEvent} to all registered listeners.
 389:      *
 390:      * @param text  the text (<code>null</code> permitted).
 391:      */
 392:     public void setToolTipText(String text) {
 393:         this.toolTipText = text;
 394:         notifyListeners(new TitleChangeEvent(this));
 395:     }
 396: 
 397:     /**
 398:      * Returns the URL text.
 399:      *
 400:      * @return The URL text (possibly <code>null</code>).
 401:      */
 402:     public String getURLText() {
 403:         return this.urlText;
 404:     }
 405: 
 406:     /**
 407:      * Sets the URL text to the specified text and sends a
 408:      * {@link TitleChangeEvent} to all registered listeners.
 409:      *
 410:      * @param text  the text (<code>null</code> permitted).
 411:      */
 412:     public void setURLText(String text) {
 413:         this.urlText = text;
 414:         notifyListeners(new TitleChangeEvent(this));
 415:     }
 416: 
 417:     /**
 418:      * Returns the flag that controls whether or not the title expands to fit
 419:      * the available space.
 420:      *
 421:      * @return The flag.
 422:      */
 423:     public boolean getExpandToFitSpace() {
 424:         return this.expandToFitSpace;
 425:     }
 426: 
 427:     /**
 428:      * Sets the flag that controls whether the title expands to fit the
 429:      * available space, and sends a {@link TitleChangeEvent} to all registered
 430:      * listeners.
 431:      *
 432:      * @param expand  the flag.
 433:      */
 434:     public void setExpandToFitSpace(boolean expand) {
 435:         this.expandToFitSpace = expand;
 436:         notifyListeners(new TitleChangeEvent(this));
 437:     }
 438: 
 439:     /**
 440:      * Returns the maximum number of lines to display.
 441:      *
 442:      * @return The maximum.
 443:      *
 444:      * @since 1.0.10
 445:      *
 446:      * @see #setMaximumLinesToDisplay(int)
 447:      */
 448:     public int getMaximumLinesToDisplay() {
 449:         return this.maximumLinesToDisplay;
 450:     }
 451: 
 452:     /**
 453:      * Sets the maximum number of lines to display and sends a
 454:      * {@link TitleChangeEvent} to all registered listeners.
 455:      *
 456:      * @param max  the maximum.
 457:      *
 458:      * @since 1.0.10.
 459:      *
 460:      * @see #getMaximumLinesToDisplay()
 461:      */
 462:     public void setMaximumLinesToDisplay(int max) {
 463:         this.maximumLinesToDisplay = max;
 464:         notifyListeners(new TitleChangeEvent(this));
 465:     }
 466: 
 467:     /**
 468:      * Arranges the contents of the block, within the given constraints, and
 469:      * returns the block size.
 470:      *
 471:      * @param g2  the graphics device.
 472:      * @param constraint  the constraint (<code>null</code> not permitted).
 473:      *
 474:      * @return The block size (in Java2D units, never <code>null</code>).
 475:      */
 476:     public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
 477:         RectangleConstraint cc = toContentConstraint(constraint);
 478:         LengthConstraintType w = cc.getWidthConstraintType();
 479:         LengthConstraintType h = cc.getHeightConstraintType();
 480:         Size2D contentSize = null;
 481:         if (w == LengthConstraintType.NONE) {
 482:             if (h == LengthConstraintType.NONE) {
 483:                 contentSize = arrangeNN(g2);
 484:             }
 485:             else if (h == LengthConstraintType.RANGE) {
 486:                 throw new RuntimeException("Not yet implemented.");
 487:             }
 488:             else if (h == LengthConstraintType.FIXED) {
 489:                 throw new RuntimeException("Not yet implemented.");
 490:             }
 491:         }
 492:         else if (w == LengthConstraintType.RANGE) {
 493:             if (h == LengthConstraintType.NONE) {
 494:                 contentSize = arrangeRN(g2, cc.getWidthRange());
 495:             }
 496:             else if (h == LengthConstraintType.RANGE) {
 497:                 contentSize = arrangeRR(g2, cc.getWidthRange(),
 498:                         cc.getHeightRange());
 499:             }
 500:             else if (h == LengthConstraintType.FIXED) {
 501:                 throw new RuntimeException("Not yet implemented.");
 502:             }
 503:         }
 504:         else if (w == LengthConstraintType.FIXED) {
 505:             if (h == LengthConstraintType.NONE) {
 506:                 contentSize = arrangeFN(g2, cc.getWidth());
 507:             }
 508:             else if (h == LengthConstraintType.RANGE) {
 509:                 throw new RuntimeException("Not yet implemented.");
 510:             }
 511:             else if (h == LengthConstraintType.FIXED) {
 512:                 throw new RuntimeException("Not yet implemented.");
 513:             }
 514:         }
 515:         return new Size2D(calculateTotalWidth(contentSize.getWidth()),
 516:                 calculateTotalHeight(contentSize.getHeight()));
 517:     }
 518: 
 519:     /**
 520:      * Arranges the content for this title assuming no bounds on the width
 521:      * or the height, and returns the required size.  This will reflect the
 522:      * fact that a text title positioned on the left or right of a chart will
 523:      * be rotated by 90 degrees.
 524:      *
 525:      * @param g2  the graphics target.
 526:      *
 527:      * @return The content size.
 528:      *
 529:      * @since 1.0.9
 530:      */
 531:     protected Size2D arrangeNN(Graphics2D g2) {
 532:         Range max = new Range(0.0, Float.MAX_VALUE);
 533:         return arrangeRR(g2, max, max);
 534:     }
 535: 
 536:     /**
 537:      * Arranges the content for this title assuming a fixed width and no bounds
 538:      * on the height, and returns the required size.  This will reflect the
 539:      * fact that a text title positioned on the left or right of a chart will
 540:      * be rotated by 90 degrees.
 541:      *
 542:      * @param g2  the graphics target.
 543:      * @param w  the width.
 544:      *
 545:      * @return The content size.
 546:      *
 547:      * @since 1.0.9
 548:      */
 549:     protected Size2D arrangeFN(Graphics2D g2, double w) {
 550:         RectangleEdge position = getPosition();
 551:         if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
 552:             float maxWidth = (float) w;
 553:             g2.setFont(this.font);
 554:             this.content = TextUtilities.createTextBlock(this.text, this.font,
 555:                     this.paint, maxWidth, this.maximumLinesToDisplay,
 556:                     new G2TextMeasurer(g2));
 557:             this.content.setLineAlignment(this.textAlignment);
 558:             Size2D contentSize = this.content.calculateDimensions(g2);
 559:             if (this.expandToFitSpace) {
 560:                 return new Size2D(maxWidth, contentSize.getHeight());
 561:             }
 562:             else {
 563:                 return contentSize;
 564:             }
 565:         }
 566:         else if (position == RectangleEdge.LEFT || position
 567:                 == RectangleEdge.RIGHT) {
 568:             float maxWidth = Float.MAX_VALUE;
 569:             g2.setFont(this.font);
 570:             this.content = TextUtilities.createTextBlock(this.text, this.font,
 571:                     this.paint, maxWidth, this.maximumLinesToDisplay,
 572:                     new G2TextMeasurer(g2));
 573:             this.content.setLineAlignment(this.textAlignment);
 574:             Size2D contentSize = this.content.calculateDimensions(g2);
 575: 
 576:             // transpose the dimensions, because the title is rotated
 577:             if (this.expandToFitSpace) {
 578:                 return new Size2D(contentSize.getHeight(), maxWidth);
 579:             }
 580:             else {
 581:                 return new Size2D(contentSize.height, contentSize.width);
 582:             }
 583:         }
 584:         else {
 585:             throw new RuntimeException("Unrecognised exception.");
 586:         }
 587:     }
 588: 
 589:     /**
 590:      * Arranges the content for this title assuming a range constraint for the
 591:      * width and no bounds on the height, and returns the required size.  This
 592:      * will reflect the fact that a text title positioned on the left or right
 593:      * of a chart will be rotated by 90 degrees.
 594:      *
 595:      * @param g2  the graphics target.
 596:      * @param widthRange  the range for the width.
 597:      *
 598:      * @return The content size.
 599:      *
 600:      * @since 1.0.9
 601:      */
 602:     protected Size2D arrangeRN(Graphics2D g2, Range widthRange) {
 603:         Size2D s = arrangeNN(g2);
 604:         if (widthRange.contains(s.getWidth())) {
 605:             return s;
 606:         }
 607:         double ww = widthRange.constrain(s.getWidth());
 608:         return arrangeFN(g2, ww);
 609:     }
 610: 
 611:     /**
 612:      * Returns the content size for the title.  This will reflect the fact that
 613:      * a text title positioned on the left or right of a chart will be rotated
 614:      * 90 degrees.
 615:      *
 616:      * @param g2  the graphics device.
 617:      * @param widthRange  the width range.
 618:      * @param heightRange  the height range.
 619:      *
 620:      * @return The content size.
 621:      */
 622:     protected Size2D arrangeRR(Graphics2D g2, Range widthRange,
 623:             Range heightRange) {
 624:         RectangleEdge position = getPosition();
 625:         if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
 626:             float maxWidth = (float) widthRange.getUpperBound();
 627:             g2.setFont(this.font);
 628:             this.content = TextUtilities.createTextBlock(this.text, this.font,
 629:                     this.paint, maxWidth, this.maximumLinesToDisplay,
 630:                     new G2TextMeasurer(g2));
 631:             this.content.setLineAlignment(this.textAlignment);
 632:             Size2D contentSize = this.content.calculateDimensions(g2);
 633:             if (this.expandToFitSpace) {
 634:                 return new Size2D(maxWidth, contentSize.getHeight());
 635:             }
 636:             else {
 637:                 return contentSize;
 638:             }
 639:         }
 640:         else if (position == RectangleEdge.LEFT || position
 641:                 == RectangleEdge.RIGHT) {
 642:             float maxWidth = (float) heightRange.getUpperBound();
 643:             g2.setFont(this.font);
 644:             this.content = TextUtilities.createTextBlock(this.text, this.font,
 645:                     this.paint, maxWidth, this.maximumLinesToDisplay,
 646:                     new G2TextMeasurer(g2));
 647:             this.content.setLineAlignment(this.textAlignment);
 648:             Size2D contentSize = this.content.calculateDimensions(g2);
 649: 
 650:             // transpose the dimensions, because the title is rotated
 651:             if (this.expandToFitSpace) {
 652:                 return new Size2D(contentSize.getHeight(), maxWidth);
 653:             }
 654:             else {
 655:                 return new Size2D(contentSize.height, contentSize.width);
 656:             }
 657:         }
 658:         else {
 659:             throw new RuntimeException("Unrecognised exception.");
 660:         }
 661:     }
 662: 
 663:     /**
 664:      * Draws the title on a Java 2D graphics device (such as the screen or a
 665:      * printer).
 666:      *
 667:      * @param g2  the graphics device.
 668:      * @param area  the area allocated for the title.
 669:      */
 670:     public void draw(Graphics2D g2, Rectangle2D area) {
 671:         draw(g2, area, null);
 672:     }
 673: 
 674:     /**
 675:      * Draws the block within the specified area.
 676:      *
 677:      * @param g2  the graphics device.
 678:      * @param area  the area.
 679:      * @param params  if this is an instance of {@link EntityBlockParams} it
 680:      *                is used to determine whether or not an
 681:      *                {@link EntityCollection} is returned by this method.
 682:      *
 683:      * @return An {@link EntityCollection} containing a chart entity for the
 684:      *         title, or <code>null</code>.
 685:      */
 686:     public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
 687:         if (this.content == null) {
 688:             return null;
 689:         }
 690:         area = trimMargin(area);
 691:         drawBorder(g2, area);
 692:         if (this.text.equals("")) {
 693:             return null;
 694:         }
 695:         ChartEntity entity = null;
 696:         if (params instanceof EntityBlockParams) {
 697:             EntityBlockParams p = (EntityBlockParams) params;
 698:             if (p.getGenerateEntities()) {
 699:                 entity = new ChartEntity(area, this.toolTipText, this.urlText);
 700:             }
 701:         }
 702:         area = trimBorder(area);
 703:         if (this.backgroundPaint != null) {
 704:             g2.setPaint(this.backgroundPaint);
 705:             g2.fill(area);
 706:         }
 707:         area = trimPadding(area);
 708:         RectangleEdge position = getPosition();
 709:         if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
 710:             drawHorizontal(g2, area);
 711:         }
 712:         else if (position == RectangleEdge.LEFT
 713:                  || position == RectangleEdge.RIGHT) {
 714:             drawVertical(g2, area);
 715:         }
 716:         BlockResult result = new BlockResult();
 717:         if (entity != null) {
 718:             StandardEntityCollection sec = new StandardEntityCollection();
 719:             sec.add(entity);
 720:             result.setEntityCollection(sec);
 721:         }
 722:         return result;
 723:     }
 724: 
 725:     /**
 726:      * Draws a the title horizontally within the specified area.  This method
 727:      * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw}
 728:      * method.
 729:      *
 730:      * @param g2  the graphics device.
 731:      * @param area  the area for the title.
 732:      */
 733:     protected void drawHorizontal(Graphics2D g2, Rectangle2D area) {
 734:         Rectangle2D titleArea = (Rectangle2D) area.clone();
 735:         g2.setFont(this.font);
 736:         g2.setPaint(this.paint);
 737:         TextBlockAnchor anchor = null;
 738:         float x = 0.0f;
 739:         HorizontalAlignment horizontalAlignment = getHorizontalAlignment();
 740:         if (horizontalAlignment == HorizontalAlignment.LEFT) {
 741:             x = (float) titleArea.getX();
 742:             anchor = TextBlockAnchor.TOP_LEFT;
 743:         }
 744:         else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
 745:             x = (float) titleArea.getMaxX();
 746:             anchor = TextBlockAnchor.TOP_RIGHT;
 747:         }
 748:         else if (horizontalAlignment == HorizontalAlignment.CENTER) {
 749:             x = (float) titleArea.getCenterX();
 750:             anchor = TextBlockAnchor.TOP_CENTER;
 751:         }
 752:         float y = 0.0f;
 753:         RectangleEdge position = getPosition();
 754:         if (position == RectangleEdge.TOP) {
 755:             y = (float) titleArea.getY();
 756:         }
 757:         else if (position == RectangleEdge.BOTTOM) {
 758:             y = (float) titleArea.getMaxY();
 759:             if (horizontalAlignment == HorizontalAlignment.LEFT) {
 760:                 anchor = TextBlockAnchor.BOTTOM_LEFT;
 761:             }
 762:             else if (horizontalAlignment == HorizontalAlignment.CENTER) {
 763:                 anchor = TextBlockAnchor.BOTTOM_CENTER;
 764:             }
 765:             else if (horizontalAlignment == HorizontalAlignment.RIGHT) {
 766:                 anchor = TextBlockAnchor.BOTTOM_RIGHT;
 767:             }
 768:         }
 769:         this.content.draw(g2, x, y, anchor);
 770:     }
 771: 
 772:     /**
 773:      * Draws a the title vertically within the specified area.  This method
 774:      * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw}
 775:      * method.
 776:      *
 777:      * @param g2  the graphics device.
 778:      * @param area  the area for the title.
 779:      */
 780:     protected void drawVertical(Graphics2D g2, Rectangle2D area) {
 781:         Rectangle2D titleArea = (Rectangle2D) area.clone();
 782:         g2.setFont(this.font);
 783:         g2.setPaint(this.paint);
 784:         TextBlockAnchor anchor = null;
 785:         float y = 0.0f;
 786:         VerticalAlignment verticalAlignment = getVerticalAlignment();
 787:         if (verticalAlignment == VerticalAlignment.TOP) {
 788:             y = (float) titleArea.getY();
 789:             anchor = TextBlockAnchor.TOP_RIGHT;
 790:         }
 791:         else if (verticalAlignment == VerticalAlignment.BOTTOM) {
 792:             y = (float) titleArea.getMaxY();
 793:             anchor = TextBlockAnchor.TOP_LEFT;
 794:         }
 795:         else if (verticalAlignment == VerticalAlignment.CENTER) {
 796:             y = (float) titleArea.getCenterY();
 797:             anchor = TextBlockAnchor.TOP_CENTER;
 798:         }
 799:         float x = 0.0f;
 800:         RectangleEdge position = getPosition();
 801:         if (position == RectangleEdge.LEFT) {
 802:             x = (float) titleArea.getX();
 803:         }
 804:         else if (position == RectangleEdge.RIGHT) {
 805:             x = (float) titleArea.getMaxX();
 806:             if (verticalAlignment == VerticalAlignment.TOP) {
 807:                 anchor = TextBlockAnchor.BOTTOM_RIGHT;
 808:             }
 809:             else if (verticalAlignment == VerticalAlignment.CENTER) {
 810:                 anchor = TextBlockAnchor.BOTTOM_CENTER;
 811:             }
 812:             else if (verticalAlignment == VerticalAlignment.BOTTOM) {
 813:                 anchor = TextBlockAnchor.BOTTOM_LEFT;
 814:             }
 815:         }
 816:         this.content.draw(g2, x, y, anchor, x, y, -Math.PI / 2.0);
 817:     }
 818: 
 819:     /**
 820:      * Tests this title for equality with another object.
 821:      *
 822:      * @param obj  the object (<code>null</code> permitted).
 823:      *
 824:      * @return <code>true</code> or <code>false</code>.
 825:      */
 826:     public boolean equals(Object obj) {
 827:         if (obj == this) {
 828:             return true;
 829:         }
 830:         if (!(obj instanceof TextTitle)) {
 831:             return false;
 832:         }
 833:         TextTitle that = (TextTitle) obj;
 834:         if (!ObjectUtilities.equal(this.text, that.text)) {
 835:             return false;
 836:         }
 837:         if (!ObjectUtilities.equal(this.font, that.font)) {
 838:             return false;
 839:         }
 840:         if (!PaintUtilities.equal(this.paint, that.paint)) {
 841:             return false;
 842:         }
 843:         if (this.textAlignment != that.textAlignment) {
 844:             return false;
 845:         }
 846:         if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
 847:             return false;
 848:         }
 849:         if (this.maximumLinesToDisplay != that.maximumLinesToDisplay) {
 850:             return false;
 851:         }
 852:         if (this.expandToFitSpace != that.expandToFitSpace) {
 853:             return false;
 854:         }
 855:         if (!ObjectUtilities.equal(this.toolTipText, that.toolTipText)) {
 856:             return false;
 857:         }
 858:         if (!ObjectUtilities.equal(this.urlText, that.urlText)) {
 859:             return false;
 860:         }
 861:         return super.equals(obj);
 862:     }
 863: 
 864:     /**
 865:      * Returns a hash code.
 866:      *
 867:      * @return A hash code.
 868:      */
 869:     public int hashCode() {
 870:         int result = super.hashCode();
 871:         result = 29 * result + (this.text != null ? this.text.hashCode() : 0);
 872:         result = 29 * result + (this.font != null ? this.font.hashCode() : 0);
 873:         result = 29 * result + (this.paint != null ? this.paint.hashCode() : 0);
 874:         result = 29 * result + (this.backgroundPaint != null
 875:                 ? this.backgroundPaint.hashCode() : 0);
 876:         return result;
 877:     }
 878: 
 879:     /**
 880:      * Returns a clone of this object.
 881:      *
 882:      * @return A clone.
 883:      *
 884:      * @throws CloneNotSupportedException never.
 885:      */
 886:     public Object clone() throws CloneNotSupportedException {
 887:         return super.clone();
 888:     }
 889: 
 890:     /**
 891:      * Provides serialization support.
 892:      *
 893:      * @param stream  the output stream.
 894:      *
 895:      * @throws IOException  if there is an I/O error.
 896:      */
 897:     private void writeObject(ObjectOutputStream stream) throws IOException {
 898:         stream.defaultWriteObject();
 899:         SerialUtilities.writePaint(this.paint, stream);
 900:         SerialUtilities.writePaint(this.backgroundPaint, stream);
 901:     }
 902: 
 903:     /**
 904:      * Provides serialization support.
 905:      *
 906:      * @param stream  the input stream.
 907:      *
 908:      * @throws IOException  if there is an I/O error.
 909:      * @throws ClassNotFoundException  if there is a classpath problem.
 910:      */
 911:     private void readObject(ObjectInputStream stream)
 912:             throws IOException, ClassNotFoundException {
 913:         stream.defaultReadObject();
 914:         this.paint = SerialUtilities.readPaint(stream);
 915:         this.backgroundPaint = SerialUtilities.readPaint(stream);
 916:     }
 917: 
 918: }
 919: