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 * Title.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 * Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); 036 * 037 */ 038 039package org.jfree.chart.title; 040 041import java.awt.Graphics2D; 042import java.awt.geom.Rectangle2D; 043import java.io.IOException; 044import java.io.ObjectInputStream; 045import java.io.ObjectOutputStream; 046import java.io.Serializable; 047import java.util.Objects; 048 049import javax.swing.event.EventListenerList; 050 051import org.jfree.chart.block.AbstractBlock; 052import org.jfree.chart.block.Block; 053import org.jfree.chart.event.TitleChangeEvent; 054import org.jfree.chart.event.TitleChangeListener; 055import org.jfree.chart.ui.HorizontalAlignment; 056import org.jfree.chart.ui.RectangleEdge; 057import org.jfree.chart.ui.RectangleInsets; 058import org.jfree.chart.ui.VerticalAlignment; 059import org.jfree.chart.util.Args; 060 061/** 062 * The base class for all chart titles. A chart can have multiple titles, 063 * appearing at the top, bottom, left or right of the chart. 064 * <P> 065 * Concrete implementations of this class will render text and images, and 066 * hence do the actual work of drawing titles. 067 */ 068public abstract class Title extends AbstractBlock 069 implements Block, Cloneable, Serializable { 070 071 /** For serialization. */ 072 private static final long serialVersionUID = -6675162505277817221L; 073 074 /** The default title position. */ 075 public static final RectangleEdge DEFAULT_POSITION = RectangleEdge.TOP; 076 077 /** The default horizontal alignment. */ 078 public static final HorizontalAlignment 079 DEFAULT_HORIZONTAL_ALIGNMENT = HorizontalAlignment.CENTER; 080 081 /** The default vertical alignment. */ 082 public static final VerticalAlignment 083 DEFAULT_VERTICAL_ALIGNMENT = VerticalAlignment.CENTER; 084 085 /** Default title padding. */ 086 public static final RectangleInsets DEFAULT_PADDING = new RectangleInsets( 087 1, 1, 1, 1); 088 089 /** 090 * A flag that controls whether or not the title is visible. 091 */ 092 public boolean visible; 093 094 /** The title position. */ 095 private RectangleEdge position; 096 097 /** The horizontal alignment of the title content. */ 098 private HorizontalAlignment horizontalAlignment; 099 100 /** The vertical alignment of the title content. */ 101 private VerticalAlignment verticalAlignment; 102 103 /** Storage for registered change listeners. */ 104 private transient EventListenerList listenerList; 105 106 /** 107 * A flag that can be used to temporarily disable the listener mechanism. 108 */ 109 private boolean notify; 110 111 /** 112 * Creates a new title, using default attributes where necessary. 113 */ 114 protected Title() { 115 this(Title.DEFAULT_POSITION, 116 Title.DEFAULT_HORIZONTAL_ALIGNMENT, 117 Title.DEFAULT_VERTICAL_ALIGNMENT, Title.DEFAULT_PADDING); 118 } 119 120 /** 121 * Creates a new title, using default attributes where necessary. 122 * 123 * @param position the position of the title ({@code null} not 124 * permitted). 125 * @param horizontalAlignment the horizontal alignment of the title 126 * ({@code null} not permitted). 127 * @param verticalAlignment the vertical alignment of the title 128 * ({@code null} not permitted). 129 */ 130 protected Title(RectangleEdge position, 131 HorizontalAlignment horizontalAlignment, 132 VerticalAlignment verticalAlignment) { 133 134 this(position, horizontalAlignment, verticalAlignment, 135 Title.DEFAULT_PADDING); 136 137 } 138 139 /** 140 * Creates a new title. 141 * 142 * @param position the position of the title ({@code null} not 143 * permitted). 144 * @param horizontalAlignment the horizontal alignment of the title (LEFT, 145 * CENTER or RIGHT, {@code null} not 146 * permitted). 147 * @param verticalAlignment the vertical alignment of the title (TOP, 148 * MIDDLE or BOTTOM, {@code null} not 149 * permitted). 150 * @param padding the amount of space to leave around the outside of the 151 * title ({@code null} not permitted). 152 */ 153 protected Title(RectangleEdge position, 154 HorizontalAlignment horizontalAlignment, 155 VerticalAlignment verticalAlignment, RectangleInsets padding) { 156 157 Args.nullNotPermitted(position, "position"); 158 Args.nullNotPermitted(horizontalAlignment, "horizontalAlignment"); 159 Args.nullNotPermitted(verticalAlignment, "verticalAlignment"); 160 Args.nullNotPermitted(padding, "padding"); 161 162 this.visible = true; 163 this.position = position; 164 this.horizontalAlignment = horizontalAlignment; 165 this.verticalAlignment = verticalAlignment; 166 setPadding(padding); 167 this.listenerList = new EventListenerList(); 168 this.notify = true; 169 } 170 171 /** 172 * Returns a flag that controls whether or not the title should be 173 * drawn. The default value is {@code true}. 174 * 175 * @return A boolean. 176 * 177 * @see #setVisible(boolean) 178 */ 179 public boolean isVisible() { 180 return this.visible; 181 } 182 183 /** 184 * Sets a flag that controls whether or not the title should be drawn, and 185 * sends a {@link TitleChangeEvent} to all registered listeners. 186 * 187 * @param visible the new flag value. 188 * 189 * @see #isVisible() 190 */ 191 public void setVisible(boolean visible) { 192 this.visible = visible; 193 notifyListeners(new TitleChangeEvent(this)); 194 } 195 196 /** 197 * Returns the position of the title. 198 * 199 * @return The title position (never {@code null}). 200 */ 201 public RectangleEdge getPosition() { 202 return this.position; 203 } 204 205 /** 206 * Sets the position for the title and sends a {@link TitleChangeEvent} to 207 * all registered listeners. 208 * 209 * @param position the position ({@code null} not permitted). 210 */ 211 public void setPosition(RectangleEdge position) { 212 Args.nullNotPermitted(position, "position"); 213 if (this.position != position) { 214 this.position = position; 215 notifyListeners(new TitleChangeEvent(this)); 216 } 217 } 218 219 /** 220 * Returns the horizontal alignment of the title. 221 * 222 * @return The horizontal alignment (never {@code null}). 223 */ 224 public HorizontalAlignment getHorizontalAlignment() { 225 return this.horizontalAlignment; 226 } 227 228 /** 229 * Sets the horizontal alignment for the title and sends a 230 * {@link TitleChangeEvent} to all registered listeners. 231 * 232 * @param alignment the horizontal alignment ({@code null} not 233 * permitted). 234 */ 235 public void setHorizontalAlignment(HorizontalAlignment alignment) { 236 Args.nullNotPermitted(alignment, "alignment"); 237 if (this.horizontalAlignment != alignment) { 238 this.horizontalAlignment = alignment; 239 notifyListeners(new TitleChangeEvent(this)); 240 } 241 } 242 243 /** 244 * Returns the vertical alignment of the title. 245 * 246 * @return The vertical alignment (never {@code null}). 247 */ 248 public VerticalAlignment getVerticalAlignment() { 249 return this.verticalAlignment; 250 } 251 252 /** 253 * Sets the vertical alignment for the title, and notifies any registered 254 * listeners of the change. 255 * 256 * @param alignment the new vertical alignment (TOP, MIDDLE or BOTTOM, 257 * {@code null} not permitted). 258 */ 259 public void setVerticalAlignment(VerticalAlignment alignment) { 260 Args.nullNotPermitted(alignment, "alignment"); 261 if (this.verticalAlignment != alignment) { 262 this.verticalAlignment = alignment; 263 notifyListeners(new TitleChangeEvent(this)); 264 } 265 } 266 267 /** 268 * Returns the flag that indicates whether or not the notification 269 * mechanism is enabled. 270 * 271 * @return The flag. 272 */ 273 public boolean getNotify() { 274 return this.notify; 275 } 276 277 /** 278 * Sets the flag that indicates whether or not the notification mechanism 279 * is enabled. There are certain situations (such as cloning) where you 280 * want to turn notification off temporarily. 281 * 282 * @param flag the new value of the flag. 283 */ 284 public void setNotify(boolean flag) { 285 this.notify = flag; 286 if (flag) { 287 notifyListeners(new TitleChangeEvent(this)); 288 } 289 } 290 291 /** 292 * Draws the title on a Java 2D graphics device (such as the screen or a 293 * printer). 294 * 295 * @param g2 the graphics device. 296 * @param area the area allocated for the title (subclasses should not 297 * draw outside this area). 298 */ 299 @Override 300 public abstract void draw(Graphics2D g2, Rectangle2D area); 301 302 /** 303 * Returns a clone of the title. 304 * <P> 305 * One situation when this is useful is when editing the title properties - 306 * you can edit a clone, and then it is easier to cancel the changes if 307 * necessary. 308 * 309 * @return A clone of the title. 310 * 311 * @throws CloneNotSupportedException not thrown by this class, but it may 312 * be thrown by subclasses. 313 */ 314 @Override 315 public Object clone() throws CloneNotSupportedException { 316 Title duplicate = (Title) super.clone(); 317 duplicate.listenerList = new EventListenerList(); 318 // RectangleInsets is immutable => same reference in clone OK 319 return duplicate; 320 } 321 322 /** 323 * Registers an object for notification of changes to the title. 324 * 325 * @param listener the object that is being registered. 326 */ 327 public void addChangeListener(TitleChangeListener listener) { 328 this.listenerList.add(TitleChangeListener.class, listener); 329 } 330 331 /** 332 * Unregisters an object for notification of changes to the chart title. 333 * 334 * @param listener the object that is being unregistered. 335 */ 336 public void removeChangeListener(TitleChangeListener listener) { 337 this.listenerList.remove(TitleChangeListener.class, listener); 338 } 339 340 /** 341 * Notifies all registered listeners that the chart title has changed in 342 * some way. 343 * 344 * @param event an object that contains information about the change to 345 * the title. 346 */ 347 protected void notifyListeners(TitleChangeEvent event) { 348 if (this.notify) { 349 Object[] listeners = this.listenerList.getListenerList(); 350 for (int i = listeners.length - 2; i >= 0; i -= 2) { 351 if (listeners[i] == TitleChangeListener.class) { 352 ((TitleChangeListener) listeners[i + 1]).titleChanged( 353 event); 354 } 355 } 356 } 357 } 358 359 /** 360 * Tests an object for equality with this title. 361 * 362 * @param obj the object ({@code null} not permitted). 363 * 364 * @return {@code true} or {@code false}. 365 */ 366 @Override 367 public boolean equals(Object obj) { 368 if (obj == this) { 369 return true; 370 } 371 if (!(obj instanceof Title)) { 372 return false; 373 } 374 Title that = (Title) obj; 375 if (this.visible != that.visible) { 376 return false; 377 } 378 if (!Objects.equals(this.position, that.position)) { 379 return false; 380 } 381 if (!Objects.equals(this.horizontalAlignment, that.horizontalAlignment)) { 382 return false; 383 } 384 if (!Objects.equals(this.verticalAlignment, that.verticalAlignment)) { 385 return false; 386 } 387 if (this.notify != that.notify) { 388 return false; 389 } 390 return super.equals(obj); 391 } 392 393 /** 394 * Ensures symmetry between super/subclass implementations of equals. For 395 * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. 396 * 397 * @param other Object 398 * 399 * @return true ONLY if the parameter is THIS class type 400 */ 401 @Override 402 public boolean canEqual(Object other) { 403 // fix the "equals not symmetric" problem 404 return (other instanceof Title); 405 } 406 407 /** 408 * Returns a hashcode for the title. 409 * 410 * @return The hashcode. 411 */ 412 @Override 413 public int hashCode() { 414 int hash = super.hashCode(); // equals calls superclass, hashCode must also 415 hash = 97 * hash + (this.visible ? 1 : 0); 416 hash = 97 * hash + Objects.hashCode(this.position); 417 hash = 97 * hash + Objects.hashCode(this.horizontalAlignment); 418 hash = 97 * hash + Objects.hashCode(this.verticalAlignment); 419 hash = 97 * hash + (this.notify ? 1 : 0); 420 return hash; 421 } 422 423 /** 424 * Provides serialization support. 425 * 426 * @param stream the output stream. 427 * 428 * @throws IOException if there is an I/O error. 429 */ 430 private void writeObject(ObjectOutputStream stream) throws IOException { 431 stream.defaultWriteObject(); 432 } 433 434 /** 435 * Provides serialization support. 436 * 437 * @param stream the input stream. 438 * 439 * @throws IOException if there is an I/O error. 440 * @throws ClassNotFoundException if there is a classpath problem. 441 */ 442 private void readObject(ObjectInputStream stream) 443 throws IOException, ClassNotFoundException { 444 stream.defaultReadObject(); 445 this.listenerList = new EventListenerList(); 446 } 447 448}