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