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