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