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