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