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