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 * 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 *
058 */
059
060 package org.jfree.chart.title;
061
062 import java.awt.Graphics2D;
063 import java.awt.geom.Rectangle2D;
064 import java.io.IOException;
065 import java.io.ObjectInputStream;
066 import java.io.ObjectOutputStream;
067 import java.io.Serializable;
068
069 import javax.swing.event.EventListenerList;
070
071 import org.jfree.chart.block.AbstractBlock;
072 import org.jfree.chart.block.Block;
073 import org.jfree.chart.event.TitleChangeEvent;
074 import org.jfree.chart.event.TitleChangeListener;
075 import org.jfree.ui.HorizontalAlignment;
076 import org.jfree.ui.RectangleEdge;
077 import org.jfree.ui.RectangleInsets;
078 import org.jfree.ui.VerticalAlignment;
079 import org.jfree.util.ObjectUtilities;
080
081 /**
082 * The base class for all chart titles. A chart can have multiple titles,
083 * appearing at the top, bottom, left or right of the chart.
084 * <P>
085 * Concrete implementations of this class will render text and images, and
086 * hence do the actual work of drawing titles.
087 */
088 public abstract class Title extends AbstractBlock
089 implements Block, Cloneable, Serializable {
090
091 /** For serialization. */
092 private static final long serialVersionUID = -6675162505277817221L;
093
094 /** The default title position. */
095 public static final RectangleEdge DEFAULT_POSITION = RectangleEdge.TOP;
096
097 /** The default horizontal alignment. */
098 public static final HorizontalAlignment
099 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 }