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