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