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