001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * -------------- 028 * TextTitle.java 029 * -------------- 030 * (C) Copyright 2000-present, by David Berry and Contributors. 031 * 032 * Original Author: David Berry; 033 * Contributor(s): David Gilbert; 034 * Nicolas Brodu; 035 * Peter Kolb - patch 2603321; 036 * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); 037 * 038 */ 039 040package org.jfree.chart.title; 041 042import java.awt.Color; 043import java.awt.Font; 044import java.awt.Graphics2D; 045import java.awt.Paint; 046import java.awt.geom.Rectangle2D; 047import java.io.IOException; 048import java.io.ObjectInputStream; 049import java.io.ObjectOutputStream; 050import java.io.Serializable; 051import java.util.Objects; 052import org.jfree.chart.HashUtils; 053 054import org.jfree.chart.block.BlockResult; 055import org.jfree.chart.block.EntityBlockParams; 056import org.jfree.chart.block.LengthConstraintType; 057import org.jfree.chart.block.RectangleConstraint; 058import org.jfree.chart.entity.ChartEntity; 059import org.jfree.chart.entity.EntityCollection; 060import org.jfree.chart.entity.StandardEntityCollection; 061import org.jfree.chart.entity.TitleEntity; 062import org.jfree.chart.event.TitleChangeEvent; 063import org.jfree.chart.text.G2TextMeasurer; 064import org.jfree.chart.text.TextBlock; 065import org.jfree.chart.text.TextBlockAnchor; 066import org.jfree.chart.text.TextUtils; 067import org.jfree.chart.ui.HorizontalAlignment; 068import org.jfree.chart.ui.RectangleEdge; 069import org.jfree.chart.ui.RectangleInsets; 070import org.jfree.chart.ui.Size2D; 071import org.jfree.chart.ui.VerticalAlignment; 072import org.jfree.chart.util.PaintUtils; 073import org.jfree.chart.util.Args; 074import org.jfree.chart.util.PublicCloneable; 075import org.jfree.chart.util.SerialUtils; 076import org.jfree.data.Range; 077 078/** 079 * A chart title that displays a text string with automatic wrapping as 080 * required. 081 */ 082public class TextTitle extends Title implements Serializable, Cloneable, PublicCloneable { 083 084 /** For serialization. */ 085 private static final long serialVersionUID = 8372008692127477443L; 086 087 /** The default font. */ 088 public static final Font DEFAULT_FONT = new Font("SansSerif", Font.BOLD, 12); 089 090 /** The default text color. */ 091 public static final Paint DEFAULT_TEXT_PAINT = Color.BLACK; 092 093 /** The title text. */ 094 private String text; 095 096 /** The font used to display the title. */ 097 private Font font; 098 099 /** The text alignment. */ 100 private HorizontalAlignment textAlignment; 101 102 /** The paint used to display the title text. */ 103 private transient Paint paint; 104 105 /** The background paint. */ 106 private transient Paint backgroundPaint; 107 108 /** The tool tip text (can be {@code null}). */ 109 private String toolTipText; 110 111 /** The URL text (can be {@code null}). */ 112 private String urlText; 113 114 /** The content. */ 115 private TextBlock content; 116 117 /** 118 * A flag that controls whether the title expands to fit the available 119 * space.. 120 */ 121 private boolean expandToFitSpace = false; 122 123 /** 124 * The maximum number of lines to display. 125 */ 126 private int maximumLinesToDisplay = Integer.MAX_VALUE; 127 128 /** 129 * Creates a new title, using default attributes where necessary. 130 */ 131 public TextTitle() { 132 this(""); 133 } 134 135 /** 136 * Creates a new title, using default attributes where necessary. 137 * 138 * @param text the title text ({@code null} not permitted). 139 */ 140 public TextTitle(String text) { 141 this(text, TextTitle.DEFAULT_FONT, TextTitle.DEFAULT_TEXT_PAINT, 142 Title.DEFAULT_POSITION, Title.DEFAULT_HORIZONTAL_ALIGNMENT, 143 Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING); 144 } 145 146 /** 147 * Creates a new title, using default attributes where necessary. 148 * 149 * @param text the title text ({@code null} not permitted). 150 * @param font the title font ({@code null} not permitted). 151 */ 152 public TextTitle(String text, Font font) { 153 this(text, font, TextTitle.DEFAULT_TEXT_PAINT, Title.DEFAULT_POSITION, 154 Title.DEFAULT_HORIZONTAL_ALIGNMENT, 155 Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING); 156 } 157 158 /** 159 * Creates a new title. 160 * 161 * @param text the text for the title ({@code null} not permitted). 162 * @param font the title font ({@code null} not permitted). 163 * @param paint the title paint ({@code null} not permitted). 164 * @param position the title position ({@code null} not permitted). 165 * @param horizontalAlignment the horizontal alignment ({@code null} 166 * not permitted). 167 * @param verticalAlignment the vertical alignment ({@code null} not 168 * permitted). 169 * @param padding the space to leave around the outside of the title. 170 */ 171 public TextTitle(String text, Font font, Paint paint, 172 RectangleEdge position, 173 HorizontalAlignment horizontalAlignment, 174 VerticalAlignment verticalAlignment, 175 RectangleInsets padding) { 176 177 super(position, horizontalAlignment, verticalAlignment, padding); 178 179 if (text == null) { 180 throw new NullPointerException("Null 'text' argument."); 181 } 182 if (font == null) { 183 throw new NullPointerException("Null 'font' argument."); 184 } 185 if (paint == null) { 186 throw new NullPointerException("Null 'paint' argument."); 187 } 188 this.text = text; 189 this.font = font; 190 this.paint = paint; 191 // the textAlignment and the horizontalAlignment are separate things, 192 // but it makes sense for the default textAlignment to match the 193 // title's horizontal alignment... 194 this.textAlignment = horizontalAlignment; 195 this.backgroundPaint = null; 196 this.content = null; 197 this.toolTipText = null; 198 this.urlText = null; 199 200 } 201 202 /** 203 * Returns the title text. 204 * 205 * @return The text (never {@code null}). 206 * 207 * @see #setText(String) 208 */ 209 public String getText() { 210 return this.text; 211 } 212 213 /** 214 * Sets the title to the specified text and sends a 215 * {@link TitleChangeEvent} to all registered listeners. 216 * 217 * @param text the text ({@code null} not permitted). 218 */ 219 public void setText(String text) { 220 Args.nullNotPermitted(text, "text"); 221 if (!this.text.equals(text)) { 222 this.text = text; 223 notifyListeners(new TitleChangeEvent(this)); 224 } 225 } 226 227 /** 228 * Returns the text alignment. This controls how the text is aligned 229 * within the title's bounds, whereas the title's horizontal alignment 230 * controls how the title's bounding rectangle is aligned within the 231 * drawing space. 232 * 233 * @return The text alignment. 234 */ 235 public HorizontalAlignment getTextAlignment() { 236 return this.textAlignment; 237 } 238 239 /** 240 * Sets the text alignment and sends a {@link TitleChangeEvent} to 241 * all registered listeners. 242 * 243 * @param alignment the alignment ({@code null} not permitted). 244 */ 245 public void setTextAlignment(HorizontalAlignment alignment) { 246 Args.nullNotPermitted(alignment, "alignment"); 247 this.textAlignment = alignment; 248 notifyListeners(new TitleChangeEvent(this)); 249 } 250 251 /** 252 * Returns the font used to display the title string. 253 * 254 * @return The font (never {@code null}). 255 * 256 * @see #setFont(Font) 257 */ 258 public Font getFont() { 259 return this.font; 260 } 261 262 /** 263 * Sets the font used to display the title string. Registered listeners 264 * are notified that the title has been modified. 265 * 266 * @param font the new font ({@code null} not permitted). 267 * 268 * @see #getFont() 269 */ 270 public void setFont(Font font) { 271 Args.nullNotPermitted(font, "font"); 272 if (!this.font.equals(font)) { 273 this.font = font; 274 notifyListeners(new TitleChangeEvent(this)); 275 } 276 } 277 278 /** 279 * Returns the paint used to display the title string. 280 * 281 * @return The paint (never {@code null}). 282 * 283 * @see #setPaint(Paint) 284 */ 285 public Paint getPaint() { 286 return this.paint; 287 } 288 289 /** 290 * Sets the paint used to display the title string. Registered listeners 291 * are notified that the title has been modified. 292 * 293 * @param paint the new paint ({@code null} not permitted). 294 * 295 * @see #getPaint() 296 */ 297 public void setPaint(Paint paint) { 298 Args.nullNotPermitted(paint, "paint"); 299 if (!this.paint.equals(paint)) { 300 this.paint = paint; 301 notifyListeners(new TitleChangeEvent(this)); 302 } 303 } 304 305 /** 306 * Returns the background paint. 307 * 308 * @return The paint (possibly {@code null}). 309 */ 310 public Paint getBackgroundPaint() { 311 return this.backgroundPaint; 312 } 313 314 /** 315 * Sets the background paint and sends a {@link TitleChangeEvent} to all 316 * registered listeners. If you set this attribute to {@code null}, 317 * no background is painted (which makes the title background transparent). 318 * 319 * @param paint the background paint ({@code null} permitted). 320 */ 321 public void setBackgroundPaint(Paint paint) { 322 this.backgroundPaint = paint; 323 notifyListeners(new TitleChangeEvent(this)); 324 } 325 326 /** 327 * Returns the tool tip text. 328 * 329 * @return The tool tip text (possibly {@code null}). 330 */ 331 public String getToolTipText() { 332 return this.toolTipText; 333 } 334 335 /** 336 * Sets the tool tip text to the specified text and sends a 337 * {@link TitleChangeEvent} to all registered listeners. 338 * 339 * @param text the text ({@code null} permitted). 340 */ 341 public void setToolTipText(String text) { 342 this.toolTipText = text; 343 notifyListeners(new TitleChangeEvent(this)); 344 } 345 346 /** 347 * Returns the URL text. 348 * 349 * @return The URL text (possibly {@code null}). 350 */ 351 public String getURLText() { 352 return this.urlText; 353 } 354 355 /** 356 * Sets the URL text to the specified text and sends a 357 * {@link TitleChangeEvent} to all registered listeners. 358 * 359 * @param text the text ({@code null} permitted). 360 */ 361 public void setURLText(String text) { 362 this.urlText = text; 363 notifyListeners(new TitleChangeEvent(this)); 364 } 365 366 /** 367 * Returns the flag that controls whether or not the title expands to fit 368 * the available space. 369 * 370 * @return The flag. 371 */ 372 public boolean getExpandToFitSpace() { 373 return this.expandToFitSpace; 374 } 375 376 /** 377 * Sets the flag that controls whether the title expands to fit the 378 * available space, and sends a {@link TitleChangeEvent} to all registered 379 * listeners. 380 * 381 * @param expand the flag. 382 */ 383 public void setExpandToFitSpace(boolean expand) { 384 this.expandToFitSpace = expand; 385 notifyListeners(new TitleChangeEvent(this)); 386 } 387 388 /** 389 * Returns the maximum number of lines to display. 390 * 391 * @return The maximum. 392 * 393 * @see #setMaximumLinesToDisplay(int) 394 */ 395 public int getMaximumLinesToDisplay() { 396 return this.maximumLinesToDisplay; 397 } 398 399 /** 400 * Sets the maximum number of lines to display and sends a 401 * {@link TitleChangeEvent} to all registered listeners. 402 * 403 * @param max the maximum. 404 * 405 * @see #getMaximumLinesToDisplay() 406 */ 407 public void setMaximumLinesToDisplay(int max) { 408 this.maximumLinesToDisplay = max; 409 notifyListeners(new TitleChangeEvent(this)); 410 } 411 412 /** 413 * Arranges the contents of the block, within the given constraints, and 414 * returns the block size. 415 * 416 * @param g2 the graphics device. 417 * @param constraint the constraint ({@code null} not permitted). 418 * 419 * @return The block size (in Java2D units, never {@code null}). 420 */ 421 @Override 422 public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) { 423 RectangleConstraint cc = toContentConstraint(constraint); 424 LengthConstraintType w = cc.getWidthConstraintType(); 425 LengthConstraintType h = cc.getHeightConstraintType(); 426 Size2D contentSize = null; 427 if (w == LengthConstraintType.NONE) { 428 if (h == LengthConstraintType.NONE) { 429 contentSize = arrangeNN(g2); 430 } 431 else if (h == LengthConstraintType.RANGE) { 432 throw new RuntimeException("Not yet implemented."); 433 } 434 else if (h == LengthConstraintType.FIXED) { 435 throw new RuntimeException("Not yet implemented."); 436 } 437 } 438 else if (w == LengthConstraintType.RANGE) { 439 if (h == LengthConstraintType.NONE) { 440 contentSize = arrangeRN(g2, cc.getWidthRange()); 441 } 442 else if (h == LengthConstraintType.RANGE) { 443 contentSize = arrangeRR(g2, cc.getWidthRange(), 444 cc.getHeightRange()); 445 } 446 else if (h == LengthConstraintType.FIXED) { 447 throw new RuntimeException("Not yet implemented."); 448 } 449 } 450 else if (w == LengthConstraintType.FIXED) { 451 if (h == LengthConstraintType.NONE) { 452 contentSize = arrangeFN(g2, cc.getWidth()); 453 } 454 else if (h == LengthConstraintType.RANGE) { 455 throw new RuntimeException("Not yet implemented."); 456 } 457 else if (h == LengthConstraintType.FIXED) { 458 throw new RuntimeException("Not yet implemented."); 459 } 460 } 461 assert contentSize != null; // suppress compiler warning 462 return new Size2D(calculateTotalWidth(contentSize.getWidth()), 463 calculateTotalHeight(contentSize.getHeight())); 464 } 465 466 /** 467 * Arranges the content for this title assuming no bounds on the width 468 * or the height, and returns the required size. This will reflect the 469 * fact that a text title positioned on the left or right of a chart will 470 * be rotated by 90 degrees. 471 * 472 * @param g2 the graphics target. 473 * 474 * @return The content size. 475 */ 476 protected Size2D arrangeNN(Graphics2D g2) { 477 Range max = new Range(0.0, Float.MAX_VALUE); 478 return arrangeRR(g2, max, max); 479 } 480 481 /** 482 * Arranges the content for this title assuming a fixed width and no bounds 483 * on 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 * @param w the width. 489 * 490 * @return The content size. 491 */ 492 protected Size2D arrangeFN(Graphics2D g2, double w) { 493 RectangleEdge position = getPosition(); 494 if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) { 495 float maxWidth = (float) w; 496 g2.setFont(this.font); 497 this.content = TextUtils.createTextBlock(this.text, this.font, 498 this.paint, maxWidth, this.maximumLinesToDisplay, 499 new G2TextMeasurer(g2)); 500 this.content.setLineAlignment(this.textAlignment); 501 Size2D contentSize = this.content.calculateDimensions(g2); 502 if (this.expandToFitSpace) { 503 return new Size2D(maxWidth, contentSize.getHeight()); 504 } 505 else { 506 return contentSize; 507 } 508 } 509 else if (position == RectangleEdge.LEFT || position 510 == RectangleEdge.RIGHT) { 511 float maxWidth = Float.MAX_VALUE; 512 g2.setFont(this.font); 513 this.content = TextUtils.createTextBlock(this.text, this.font, 514 this.paint, maxWidth, this.maximumLinesToDisplay, 515 new G2TextMeasurer(g2)); 516 this.content.setLineAlignment(this.textAlignment); 517 Size2D contentSize = this.content.calculateDimensions(g2); 518 519 // transpose the dimensions, because the title is rotated 520 if (this.expandToFitSpace) { 521 return new Size2D(contentSize.getHeight(), maxWidth); 522 } 523 else { 524 return new Size2D(contentSize.height, contentSize.width); 525 } 526 } 527 else { 528 throw new RuntimeException("Unrecognised exception."); 529 } 530 } 531 532 /** 533 * Arranges the content for this title assuming a range constraint for the 534 * width and no bounds on the height, and returns the required size. This 535 * will reflect the fact that a text title positioned on the left or right 536 * of a chart will be rotated by 90 degrees. 537 * 538 * @param g2 the graphics target. 539 * @param widthRange the range for the width. 540 * 541 * @return The content size. 542 */ 543 protected Size2D arrangeRN(Graphics2D g2, Range widthRange) { 544 Size2D s = arrangeNN(g2); 545 if (widthRange.contains(s.getWidth())) { 546 return s; 547 } 548 double ww = widthRange.constrain(s.getWidth()); 549 return arrangeFN(g2, ww); 550 } 551 552 /** 553 * Returns the content size for the title. This will reflect the fact that 554 * a text title positioned on the left or right of a chart will be rotated 555 * 90 degrees. 556 * 557 * @param g2 the graphics device. 558 * @param widthRange the width range. 559 * @param heightRange the height range. 560 * 561 * @return The content size. 562 */ 563 protected Size2D arrangeRR(Graphics2D g2, Range widthRange, 564 Range heightRange) { 565 RectangleEdge position = getPosition(); 566 if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) { 567 float maxWidth = (float) widthRange.getUpperBound(); 568 g2.setFont(this.font); 569 this.content = TextUtils.createTextBlock(this.text, this.font, 570 this.paint, maxWidth, this.maximumLinesToDisplay, 571 new G2TextMeasurer(g2)); 572 this.content.setLineAlignment(this.textAlignment); 573 Size2D contentSize = this.content.calculateDimensions(g2); 574 if (this.expandToFitSpace) { 575 return new Size2D(maxWidth, contentSize.getHeight()); 576 } 577 else { 578 return contentSize; 579 } 580 } 581 else if (position == RectangleEdge.LEFT || position 582 == RectangleEdge.RIGHT) { 583 float maxWidth = (float) heightRange.getUpperBound(); 584 g2.setFont(this.font); 585 this.content = TextUtils.createTextBlock(this.text, this.font, 586 this.paint, maxWidth, this.maximumLinesToDisplay, 587 new G2TextMeasurer(g2)); 588 this.content.setLineAlignment(this.textAlignment); 589 Size2D contentSize = this.content.calculateDimensions(g2); 590 591 // transpose the dimensions, because the title is rotated 592 if (this.expandToFitSpace) { 593 return new Size2D(contentSize.getHeight(), maxWidth); 594 } 595 else { 596 return new Size2D(contentSize.height, contentSize.width); 597 } 598 } 599 else { 600 throw new RuntimeException("Unrecognised exception."); 601 } 602 } 603 604 /** 605 * Draws the title on a Java 2D graphics device (such as the screen or a 606 * printer). 607 * 608 * @param g2 the graphics device. 609 * @param area the area allocated for the title. 610 */ 611 @Override 612 public void draw(Graphics2D g2, Rectangle2D area) { 613 draw(g2, area, null); 614 } 615 616 /** 617 * Draws the block within the specified area. 618 * 619 * @param g2 the graphics device. 620 * @param area the area. 621 * @param params if this is an instance of {@link EntityBlockParams} it 622 * is used to determine whether or not an 623 * {@link EntityCollection} is returned by this method. 624 * 625 * @return An {@link EntityCollection} containing a chart entity for the 626 * title, or {@code null}. 627 */ 628 @Override 629 public Object draw(Graphics2D g2, Rectangle2D area, Object params) { 630 if (this.content == null) { 631 return null; 632 } 633 area = trimMargin(area); 634 drawBorder(g2, area); 635 if (this.text.equals("")) { 636 return null; 637 } 638 ChartEntity entity = null; 639 if (params instanceof EntityBlockParams) { 640 EntityBlockParams p = (EntityBlockParams) params; 641 if (p.getGenerateEntities()) { 642 entity = new TitleEntity(area, this, this.toolTipText, 643 this.urlText); 644 } 645 } 646 area = trimBorder(area); 647 if (this.backgroundPaint != null) { 648 g2.setPaint(this.backgroundPaint); 649 g2.fill(area); 650 } 651 area = trimPadding(area); 652 RectangleEdge position = getPosition(); 653 if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) { 654 drawHorizontal(g2, area); 655 } 656 else if (position == RectangleEdge.LEFT 657 || position == RectangleEdge.RIGHT) { 658 drawVertical(g2, area); 659 } 660 BlockResult result = new BlockResult(); 661 if (entity != null) { 662 StandardEntityCollection sec = new StandardEntityCollection(); 663 sec.add(entity); 664 result.setEntityCollection(sec); 665 } 666 return result; 667 } 668 669 /** 670 * Draws a the title horizontally within the specified area. This method 671 * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw} 672 * method. 673 * 674 * @param g2 the graphics device. 675 * @param area the area for the title. 676 */ 677 protected void drawHorizontal(Graphics2D g2, Rectangle2D area) { 678 Rectangle2D titleArea = (Rectangle2D) area.clone(); 679 g2.setFont(this.font); 680 g2.setPaint(this.paint); 681 TextBlockAnchor anchor = null; 682 float x = 0.0f; 683 HorizontalAlignment horizontalAlignment = getHorizontalAlignment(); 684 if (horizontalAlignment == HorizontalAlignment.LEFT) { 685 x = (float) titleArea.getX(); 686 anchor = TextBlockAnchor.TOP_LEFT; 687 } 688 else if (horizontalAlignment == HorizontalAlignment.RIGHT) { 689 x = (float) titleArea.getMaxX(); 690 anchor = TextBlockAnchor.TOP_RIGHT; 691 } 692 else if (horizontalAlignment == HorizontalAlignment.CENTER) { 693 x = (float) titleArea.getCenterX(); 694 anchor = TextBlockAnchor.TOP_CENTER; 695 } 696 float y = 0.0f; 697 RectangleEdge position = getPosition(); 698 if (position == RectangleEdge.TOP) { 699 y = (float) titleArea.getY(); 700 } 701 else if (position == RectangleEdge.BOTTOM) { 702 y = (float) titleArea.getMaxY(); 703 if (horizontalAlignment == HorizontalAlignment.LEFT) { 704 anchor = TextBlockAnchor.BOTTOM_LEFT; 705 } 706 else if (horizontalAlignment == HorizontalAlignment.CENTER) { 707 anchor = TextBlockAnchor.BOTTOM_CENTER; 708 } 709 else if (horizontalAlignment == HorizontalAlignment.RIGHT) { 710 anchor = TextBlockAnchor.BOTTOM_RIGHT; 711 } 712 } 713 this.content.draw(g2, x, y, anchor); 714 } 715 716 /** 717 * Draws a the title vertically within the specified area. This method 718 * will be called from the {@link #draw(Graphics2D, Rectangle2D) draw} 719 * method. 720 * 721 * @param g2 the graphics device. 722 * @param area the area for the title. 723 */ 724 protected void drawVertical(Graphics2D g2, Rectangle2D area) { 725 Rectangle2D titleArea = (Rectangle2D) area.clone(); 726 g2.setFont(this.font); 727 g2.setPaint(this.paint); 728 TextBlockAnchor anchor = null; 729 float y = 0.0f; 730 VerticalAlignment verticalAlignment = getVerticalAlignment(); 731 if (verticalAlignment == VerticalAlignment.TOP) { 732 y = (float) titleArea.getY(); 733 anchor = TextBlockAnchor.TOP_RIGHT; 734 } 735 else if (verticalAlignment == VerticalAlignment.BOTTOM) { 736 y = (float) titleArea.getMaxY(); 737 anchor = TextBlockAnchor.TOP_LEFT; 738 } 739 else if (verticalAlignment == VerticalAlignment.CENTER) { 740 y = (float) titleArea.getCenterY(); 741 anchor = TextBlockAnchor.TOP_CENTER; 742 } 743 float x = 0.0f; 744 RectangleEdge position = getPosition(); 745 if (position == RectangleEdge.LEFT) { 746 x = (float) titleArea.getX(); 747 } 748 else if (position == RectangleEdge.RIGHT) { 749 x = (float) titleArea.getMaxX(); 750 if (verticalAlignment == VerticalAlignment.TOP) { 751 anchor = TextBlockAnchor.BOTTOM_RIGHT; 752 } 753 else if (verticalAlignment == VerticalAlignment.CENTER) { 754 anchor = TextBlockAnchor.BOTTOM_CENTER; 755 } 756 else if (verticalAlignment == VerticalAlignment.BOTTOM) { 757 anchor = TextBlockAnchor.BOTTOM_LEFT; 758 } 759 } 760 this.content.draw(g2, x, y, anchor, x, y, -Math.PI / 2.0); 761 } 762 763 /** 764 * Tests this title for equality with another object. 765 * 766 * @param obj the object ({@code null} permitted). 767 * 768 * @return {@code true} or {@code false}. 769 */ 770 @Override 771 public boolean equals(Object obj) { 772 if (obj == this) { 773 return true; 774 } 775 if (!(obj instanceof TextTitle)) { 776 return false; 777 } 778 TextTitle that = (TextTitle) obj; 779 if (!Objects.equals(this.text, that.text)) { 780 return false; 781 } 782 if (!Objects.equals(this.font, that.font)) { 783 return false; 784 } 785 if (!PaintUtils.equal(this.paint, that.paint)) { 786 return false; 787 } 788 if (!Objects.equals(this.textAlignment, that.textAlignment)) { 789 return false; 790 } 791 if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) { 792 return false; 793 } 794 if (this.maximumLinesToDisplay != that.maximumLinesToDisplay) { 795 return false; 796 } 797 if (this.expandToFitSpace != that.expandToFitSpace) { 798 return false; 799 } 800 if (!Objects.equals(this.toolTipText, that.toolTipText)) { 801 return false; 802 } 803 if (!Objects.equals(this.urlText, that.urlText)) { 804 return false; 805 } 806 if (!Objects.equals(this.content, that.content)) { 807 return false; 808 } 809 if (!that.canEqual(this)) { 810 return false; 811 } 812 return super.equals(obj); 813 } 814 815 /** 816 * Ensures symmetry between super/subclass implementations of equals. For 817 * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. 818 * 819 * @param other Object 820 * 821 * @return true ONLY if the parameter is THIS class type 822 */ 823 @Override 824 public boolean canEqual(Object other) { 825 // fix the "equals not symmetric" problem 826 return (other instanceof TextTitle); 827 } 828 829 /** 830 * Returns a hash code. 831 * 832 * @return A hash code. 833 */ 834 @Override 835 public int hashCode() { 836 int hash = super.hashCode(); // equals calls superclass, hashCode must also 837 hash = 83 * hash + Objects.hashCode(this.text); 838 hash = 83 * hash + Objects.hashCode(this.font); 839 hash = 83 * hash + Objects.hashCode(this.textAlignment); 840 hash = 83 * hash + HashUtils.hashCodeForPaint(this.paint); 841 hash = 83 * hash + HashUtils.hashCodeForPaint(this.backgroundPaint); 842 hash = 83 * hash + Objects.hashCode(this.toolTipText); 843 hash = 83 * hash + Objects.hashCode(this.urlText); 844 hash = 83 * hash + Objects.hashCode(this.content); 845 hash = 83 * hash + (this.expandToFitSpace ? 1 : 0); 846 hash = 83 * hash + this.maximumLinesToDisplay; 847 return hash; 848 } 849 850 /** 851 * Returns a clone of this object. 852 * 853 * @return A clone. 854 * 855 * @throws CloneNotSupportedException never. 856 */ 857 @Override 858 public Object clone() throws CloneNotSupportedException { 859 return super.clone(); 860 } 861 862 /** 863 * Provides serialization support. 864 * 865 * @param stream the output stream. 866 * 867 * @throws IOException if there is an I/O error. 868 */ 869 private void writeObject(ObjectOutputStream stream) throws IOException { 870 stream.defaultWriteObject(); 871 SerialUtils.writePaint(this.paint, stream); 872 SerialUtils.writePaint(this.backgroundPaint, stream); 873 } 874 875 /** 876 * Provides serialization support. 877 * 878 * @param stream the input stream. 879 * 880 * @throws IOException if there is an I/O error. 881 * @throws ClassNotFoundException if there is a classpath problem. 882 */ 883 private void readObject(ObjectInputStream stream) 884 throws IOException, ClassNotFoundException { 885 stream.defaultReadObject(); 886 this.paint = SerialUtils.readPaint(stream); 887 this.backgroundPaint = SerialUtils.readPaint(stream); 888 } 889 890} 891