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