Source for org.jfree.chart.title.Title

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2005, 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-2005, 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:  * $Id: Title.java,v 1.10.2.1 2005/10/25 20:58:34 mungady Exp $
  37:  *
  38:  * Changes (from 21-Aug-2001)
  39:  * --------------------------
  40:  * 21-Aug-2001 : Added standard header (DG);
  41:  * 18-Sep-2001 : Updated header (DG);
  42:  * 14-Nov-2001 : Package com.jrefinery.common.ui.* changed to 
  43:  *               com.jrefinery.ui.* (DG);
  44:  * 07-Feb-2002 : Changed blank space around title from Insets --> Spacer, to 
  45:  *               allow for relative or absolute spacing (DG);
  46:  * 25-Jun-2002 : Removed unnecessary imports (DG);
  47:  * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  48:  * 14-Oct-2002 : Changed the event listener storage structure (DG);
  49:  * 11-Sep-2003 : Took care of listeners while cloning (NB);
  50:  * 22-Sep-2003 : Spacer cannot be null. Added nullpointer checks for this (TM);
  51:  * 08-Jan-2003 : Renamed AbstractTitle --> Title and moved to separate 
  52:  *               package (DG);
  53:  * 26-Oct-2004 : Refactored to implement Block interface, and removed redundant 
  54:  *               constants (DG);
  55:  * 11-Jan-2005 : Removed deprecated code in preparation for the 1.0.0 
  56:  *               release (DG);
  57:  * 02-Feb-2005 : Changed Spacer --> RectangleInsets for padding (DG);
  58:  * 03-May-2005 : Fixed problem in equals() method (DG);
  59:  * 
  60:  */
  61: 
  62: package org.jfree.chart.title;
  63: 
  64: import java.awt.Graphics2D;
  65: import java.awt.geom.Rectangle2D;
  66: import java.io.IOException;
  67: import java.io.ObjectInputStream;
  68: import java.io.ObjectOutputStream;
  69: import java.io.Serializable;
  70: 
  71: import javax.swing.event.EventListenerList;
  72: 
  73: import org.jfree.chart.block.AbstractBlock;
  74: import org.jfree.chart.block.Block;
  75: import org.jfree.chart.event.TitleChangeEvent;
  76: import org.jfree.chart.event.TitleChangeListener;
  77: import org.jfree.ui.HorizontalAlignment;
  78: import org.jfree.ui.RectangleEdge;
  79: import org.jfree.ui.RectangleInsets;
  80: import org.jfree.ui.VerticalAlignment;
  81: import org.jfree.util.ObjectUtilities;
  82: 
  83: /**
  84:  * The base class for all chart titles.  A chart can have multiple titles, 
  85:  * appearing at the top, bottom, left or right of the chart.
  86:  * <P>
  87:  * Concrete implementations of this class will render text and images, and 
  88:  * hence do the actual work of drawing titles.
  89:  *
  90:  * @author David Berry
  91:  */
  92: public abstract class Title extends AbstractBlock 
  93:                             implements Block, Cloneable, Serializable {
  94: 
  95:     /** For serialization. */
  96:     private static final long serialVersionUID = -6675162505277817221L;
  97:     
  98:     /** The default title position. */
  99:     public static final RectangleEdge DEFAULT_POSITION = RectangleEdge.TOP;
 100: 
 101:     /** The default horizontal alignment. */
 102:     public static final HorizontalAlignment 
 103:         DEFAULT_HORIZONTAL_ALIGNMENT = HorizontalAlignment.CENTER;
 104: 
 105:     /** The default vertical alignment. */
 106:     public static final VerticalAlignment 
 107:         DEFAULT_VERTICAL_ALIGNMENT = VerticalAlignment.CENTER;
 108: 
 109:     /** Default title padding. */
 110:     public static final RectangleInsets DEFAULT_PADDING = new RectangleInsets(
 111:         1, 1, 1, 1
 112:     );
 113: 
 114:     /** The title position. */
 115:     private RectangleEdge position;
 116: 
 117:     /** The horizontal alignment of the title content. */
 118:     private HorizontalAlignment horizontalAlignment;
 119: 
 120:     /** The vertical alignment of the title content. */
 121:     private VerticalAlignment verticalAlignment;
 122: 
 123:     /** Storage for registered change listeners. */
 124:     private transient EventListenerList listenerList;
 125: 
 126:     /** 
 127:      * A flag that can be used to temporarily disable the listener mechanism. 
 128:      */
 129:     private boolean notify;
 130: 
 131:     /**
 132:      * Creates a new title, using default attributes where necessary.
 133:      */
 134:     protected Title() {
 135:         this(
 136:             Title.DEFAULT_POSITION,
 137:             Title.DEFAULT_HORIZONTAL_ALIGNMENT,
 138:             Title.DEFAULT_VERTICAL_ALIGNMENT,
 139:             Title.DEFAULT_PADDING
 140:         );
 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(
 158:             position, horizontalAlignment, verticalAlignment,
 159:             Title.DEFAULT_PADDING
 160:         );
 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,
 181:                     RectangleInsets padding) {
 182: 
 183:         // check arguments...
 184:         if (position == null) {
 185:             throw new IllegalArgumentException("Null 'position' argument.");
 186:         }
 187:         if (horizontalAlignment == null) {
 188:             throw new IllegalArgumentException(
 189:                 "Null 'horizontalAlignment' argument."
 190:             );
 191:         }
 192: 
 193:         if (verticalAlignment == null) {
 194:             throw new IllegalArgumentException(
 195:                 "Null 'verticalAlignment' argument."
 196:             );
 197:         }
 198:         if (padding == null) {
 199:             throw new IllegalArgumentException("Null 'spacer' argument.");
 200:         }
 201: 
 202:         this.position = position;
 203:         this.horizontalAlignment = horizontalAlignment;
 204:         this.verticalAlignment = verticalAlignment;
 205:         setPadding(padding);
 206:         this.listenerList = new EventListenerList();
 207:         this.notify = true;
 208: 
 209:     }
 210: 
 211:     /**
 212:      * Returns the position of the title.
 213:      *
 214:      * @return The title position (never <code>null</code>).
 215:      */
 216:     public RectangleEdge getPosition() {
 217:         return this.position;
 218:     }
 219: 
 220:     /**
 221:      * Sets the position for the title and sends a {@link TitleChangeEvent} to 
 222:      * all registered listeners.
 223:      *
 224:      * @param position  the position (<code>null</code> not permitted).
 225:      */
 226:     public void setPosition(RectangleEdge position) {
 227:         if (position == null) {
 228:             throw new IllegalArgumentException("Null 'position' argument.");
 229:         }
 230:         if (this.position != position) {
 231:             this.position = position;
 232:             notifyListeners(new TitleChangeEvent(this));
 233:         }
 234:     }
 235: 
 236:     /**
 237:      * Returns the horizontal alignment of the title.
 238:      *
 239:      * @return The horizontal alignment (never <code>null</code>).
 240:      */
 241:     public HorizontalAlignment getHorizontalAlignment() {
 242:         return this.horizontalAlignment;
 243:     }
 244: 
 245:     /**
 246:      * Sets the horizontal alignment for the title and sends a 
 247:      * {@link TitleChangeEvent} to all registered listeners.
 248:      *
 249:      * @param alignment  the horizontal alignment (<code>null</code> not 
 250:      *                   permitted).
 251:      */
 252:     public void setHorizontalAlignment(HorizontalAlignment alignment) {
 253:         if (alignment == null) {
 254:             throw new IllegalArgumentException("Null 'alignment' argument.");
 255:         }
 256:         if (this.horizontalAlignment != alignment) {
 257:             this.horizontalAlignment = alignment;
 258:             notifyListeners(new TitleChangeEvent(this));
 259:         }
 260:     }
 261: 
 262:     /**
 263:      * Returns the vertical alignment of the title.
 264:      *
 265:      * @return The vertical alignment (never <code>null</code>).
 266:      */
 267:     public VerticalAlignment getVerticalAlignment() {
 268:         return this.verticalAlignment;
 269:     }
 270: 
 271:     /**
 272:      * Sets the vertical alignment for the title, and notifies any registered
 273:      * listeners of the change.
 274:      *
 275:      * @param alignment  the new vertical alignment (TOP, MIDDLE or BOTTOM, 
 276:      *                   <code>null</code> not permitted).
 277:      */
 278:     public void setVerticalAlignment(VerticalAlignment alignment) {
 279:         if (alignment == null) {
 280:             throw new IllegalArgumentException("Null 'alignment' argument.");
 281:         }
 282:         if (this.verticalAlignment != alignment) {
 283:             this.verticalAlignment = alignment;
 284:             notifyListeners(new TitleChangeEvent(this));
 285:         }
 286:     }
 287: 
 288:     /**
 289:      * Returns the flag that indicates whether or not the notification 
 290:      * mechanism is enabled.
 291:      *
 292:      * @return The flag.
 293:      */
 294:     public boolean getNotify() {
 295:         return this.notify;
 296:     }
 297: 
 298:     /**
 299:      * Sets the flag that indicates whether or not the notification mechanism
 300:      * is enabled.  There are certain situations (such as cloning) where you
 301:      * want to turn notification off temporarily.
 302:      *
 303:      * @param flag  the new value of the flag.
 304:      */
 305:     public void setNotify(boolean flag) {
 306:         this.notify = flag;
 307:         if (flag) {
 308:             notifyListeners(new TitleChangeEvent(this));   
 309:         }
 310:     }
 311: 
 312:     /**
 313:      * Draws the title on a Java 2D graphics device (such as the screen or a 
 314:      * printer).
 315:      *
 316:      * @param g2  the graphics device.
 317:      * @param area  the area allocated for the title (subclasses should not
 318:      *              draw outside this area).
 319:      */
 320:     public abstract void draw(Graphics2D g2, Rectangle2D area);
 321: 
 322:     /**
 323:      * Returns a clone of the title.
 324:      * <P>
 325:      * One situation when this is useful is when editing the title properties -
 326:      * you can edit a clone, and then it is easier to cancel the changes if
 327:      * necessary.
 328:      *
 329:      * @return A clone of the title.
 330:      *
 331:      * @throws CloneNotSupportedException not thrown by this class, but it may 
 332:      *         be thrown by subclasses.
 333:      */
 334:     public Object clone() throws CloneNotSupportedException {
 335: 
 336:         Title duplicate = (Title) super.clone();
 337:         duplicate.listenerList = new EventListenerList();
 338:         // RectangleInsets is immutable => same reference in clone OK
 339:         return duplicate;
 340:     }
 341: 
 342:     /**
 343:      * Registers an object for notification of changes to the title.
 344:      *
 345:      * @param listener  the object that is being registered.
 346:      */
 347:     public void addChangeListener(TitleChangeListener listener) {
 348:         this.listenerList.add(TitleChangeListener.class, listener);
 349:     }
 350: 
 351:     /**
 352:      * Unregisters an object for notification of changes to the chart title.
 353:      *
 354:      * @param listener  the object that is being unregistered.
 355:      */
 356:     public void removeChangeListener(TitleChangeListener listener) {
 357:         this.listenerList.remove(TitleChangeListener.class, listener);
 358:     }
 359: 
 360:     /**
 361:      * Notifies all registered listeners that the chart title has changed in 
 362:      * some way.
 363:      *
 364:      * @param event  an object that contains information about the change to 
 365:      *               the title.
 366:      */
 367:     protected void notifyListeners(TitleChangeEvent event) {
 368:         if (this.notify) {
 369:             Object[] listeners = this.listenerList.getListenerList();
 370:             for (int i = listeners.length - 2; i >= 0; i -= 2) {
 371:                 if (listeners[i] == TitleChangeListener.class) {
 372:                     ((TitleChangeListener) listeners[i + 1]).titleChanged(
 373:                         event
 374:                     );
 375:                 }
 376:             }
 377:         }
 378:     }
 379: 
 380:     /**
 381:      * Tests an object for equality with this title.
 382:      *
 383:      * @param obj  the object (<code>null</code> not permitted).
 384:      *
 385:      * @return <code>true</code> or <code>false</code>.
 386:      */
 387:     public boolean equals(Object obj) {
 388:         if (obj == this) {
 389:             return true;
 390:         }
 391:         if (!(obj instanceof Title)) {
 392:             return false;
 393:         }
 394:         if (!super.equals(obj)) {
 395:             return false;   
 396:         }
 397:         Title that = (Title) obj;
 398:         if (this.position != that.position) {
 399:             return false;
 400:         }
 401:         if (this.horizontalAlignment != that.horizontalAlignment) {
 402:             return false;
 403:         }
 404:         if (this.verticalAlignment != that.verticalAlignment) {
 405:             return false;
 406:         }
 407:         if (this.notify != that.notify) {
 408:             return false;
 409:         }
 410:         return true;
 411:     }
 412: 
 413:     /**
 414:      * Returns a hashcode for the title.
 415:      * 
 416:      * @return The hashcode.
 417:      */
 418:     public int hashCode() {
 419:         int result = 193;
 420:         result = 37 * result + ObjectUtilities.hashCode(this.position);    
 421:         result = 37 * result 
 422:             + ObjectUtilities.hashCode(this.horizontalAlignment);    
 423:         result = 37 * result + ObjectUtilities.hashCode(this.verticalAlignment);
 424:         return result;
 425:     }
 426:         
 427:     /**
 428:      * Provides serialization support.
 429:      *
 430:      * @param stream  the output stream.
 431:      *
 432:      * @throws IOException  if there is an I/O error.
 433:      */
 434:     private void writeObject(ObjectOutputStream stream) throws IOException {
 435:         stream.defaultWriteObject();
 436:     }
 437: 
 438:     /**
 439:      * Provides serialization support.
 440:      *
 441:      * @param stream  the input stream.
 442:      *
 443:      * @throws IOException  if there is an I/O error.
 444:      * @throws ClassNotFoundException  if there is a classpath problem.
 445:      */
 446:     private void readObject(ObjectInputStream stream) 
 447:         throws IOException, ClassNotFoundException {
 448:         stream.defaultReadObject();
 449:         this.listenerList = new EventListenerList();
 450:     }
 451: 
 452: }