Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * ---------- 28: * Title.java 29: * ---------- 30: * (C) Copyright 2000-2007, by David Berry and Contributors. 31: * 32: * Original Author: David Berry; 33: * Contributor(s): David Gilbert (for Object Refinery Limited); 34: * Nicolas Brodu; 35: * 36: * Changes (from 21-Aug-2001) 37: * -------------------------- 38: * 21-Aug-2001 : Added standard header (DG); 39: * 18-Sep-2001 : Updated header (DG); 40: * 14-Nov-2001 : Package com.jrefinery.common.ui.* changed to 41: * com.jrefinery.ui.* (DG); 42: * 07-Feb-2002 : Changed blank space around title from Insets --> Spacer, to 43: * allow for relative or absolute spacing (DG); 44: * 25-Jun-2002 : Removed unnecessary imports (DG); 45: * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG); 46: * 14-Oct-2002 : Changed the event listener storage structure (DG); 47: * 11-Sep-2003 : Took care of listeners while cloning (NB); 48: * 22-Sep-2003 : Spacer cannot be null. Added nullpointer checks for this (TM); 49: * 08-Jan-2003 : Renamed AbstractTitle --> Title and moved to separate 50: * package (DG); 51: * 26-Oct-2004 : Refactored to implement Block interface, and removed redundant 52: * constants (DG); 53: * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0 54: * release (DG); 55: * 02-Feb-2005 : Changed Spacer --> RectangleInsets for padding (DG); 56: * 03-May-2005 : Fixed problem in equals() method (DG); 57: * 58: */ 59: 60: package org.jfree.chart.title; 61: 62: import java.awt.Graphics2D; 63: import java.awt.geom.Rectangle2D; 64: import java.io.IOException; 65: import java.io.ObjectInputStream; 66: import java.io.ObjectOutputStream; 67: import java.io.Serializable; 68: 69: import javax.swing.event.EventListenerList; 70: 71: import org.jfree.chart.block.AbstractBlock; 72: import org.jfree.chart.block.Block; 73: import org.jfree.chart.event.TitleChangeEvent; 74: import org.jfree.chart.event.TitleChangeListener; 75: import org.jfree.ui.HorizontalAlignment; 76: import org.jfree.ui.RectangleEdge; 77: import org.jfree.ui.RectangleInsets; 78: import org.jfree.ui.VerticalAlignment; 79: import org.jfree.util.ObjectUtilities; 80: 81: /** 82: * The base class for all chart titles. A chart can have multiple titles, 83: * appearing at the top, bottom, left or right of the chart. 84: * <P> 85: * Concrete implementations of this class will render text and images, and 86: * hence do the actual work of drawing titles. 87: */ 88: public abstract class Title extends AbstractBlock 89: implements Block, Cloneable, Serializable { 90: 91: /** For serialization. */ 92: private static final long serialVersionUID = -6675162505277817221L; 93: 94: /** The default title position. */ 95: public static final RectangleEdge DEFAULT_POSITION = RectangleEdge.TOP; 96: 97: /** The default horizontal alignment. */ 98: public static final HorizontalAlignment 99: 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: }