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-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: }